7 Commits
Author SHA1 Message Date
semantic-release-bot 29816c940a chore: Release v1.1.0 [skip ci]
## [1.1.0](http://gitea:3000/StanPonomarev/twitter-patch/compare/v1.0.3...v1.1.0) (2026-08-21)

### ✨ New Features

* remove upgrade button ([723964b](http://gitea:3000/StanPonomarev/twitter-patch/commit/723964bc29d0a9edc7ad1915c9cf5669fe9e6f95))
2026-08-21 18:50:40 +00:00
StanPonomarev 723964bc29 feat: remove upgrade button
Release / Release (push) Successful in 56s
2026-08-21 20:49:42 +02:00
semantic-release-bot 6ec6b0032b chore: Release v1.0.3 [skip ci]
## [1.0.3](http://gitea:3000/StanPonomarev/twitter-patch/compare/v1.0.2...v1.0.3) (2026-08-21)

### 🐛 Bug Fixes

* artifact ([0a2b472](http://gitea:3000/StanPonomarev/twitter-patch/commit/0a2b4725fb53bf0fa741dc4be1a9914ad1e5be8b))
2026-08-21 16:15:17 +00:00
StanPonomarev 0a2b4725fb fix: artifact
Release / Release (push) Successful in 54s
2026-08-21 18:14:25 +02:00
StanPonomarev 5dc2c501b1 add AGENTS.md
Release / Release (push) Successful in 54s
2026-08-21 17:49:25 +02:00
semantic-release-bot b7c143788b chore: Release v1.0.2 [skip ci]
## [1.0.2](http://gitea:3000/StanPonomarev/twitter-patch/compare/v1.0.1...v1.0.2) (2026-08-21)

### 🐛 Bug Fixes

* release again ([ef64be8](http://gitea:3000/StanPonomarev/twitter-patch/commit/ef64be8da665680ebf9fadcf60ae79046594292d))
2026-08-21 15:48:27 +00:00
StanPonomarev ef64be8da6 fix: release again
Release / Release (push) Successful in 1m5s
2026-08-21 17:45:56 +02:00
12 changed files with 139 additions and 24 deletions
+20 -9
View File
@@ -4,20 +4,30 @@ const path = require('node:path');
function releaseEnvironment(context) {
const env = context.env ?? process.env;
const serverUrl = env.GITEA_URL?.replace(/\/$/, '');
const publicUrl = env.GITEA_PUBLIC_URL?.replace(/\/$/, '');
const repository = env.GITEA_REPOSITORY;
const token = env.GITEA_TOKEN;
if (!serverUrl || !repository || !token) {
throw new Error('GITEA_URL, GITEA_REPOSITORY, and GITEA_TOKEN are required');
if (!serverUrl || !publicUrl || !repository || !token) {
throw new Error(
'GITEA_URL, GITEA_PUBLIC_URL, GITEA_REPOSITORY, and GITEA_TOKEN are required',
);
}
if (!/^https?:\/\//.test(serverUrl)) {
throw new Error('GITEA_URL must be an HTTP(S) URL');
}
if (!/^https?:\/\//.test(publicUrl)) {
throw new Error('GITEA_PUBLIC_URL must be an HTTP(S) URL');
}
if (!/^[^/]+\/[^/]+$/.test(repository)) {
throw new Error('GITEA_REPOSITORY must use the owner/repository format');
}
return { serverUrl, repository, token };
return { serverUrl, publicUrl, repository, token };
}
function makeUrlsPublic(value, serverUrl, publicUrl) {
return value?.split(serverUrl).join(publicUrl);
}
async function giteaRequest(url, token, options = {}) {
@@ -43,10 +53,11 @@ module.exports.verifyConditions = async (_pluginConfig, context) => {
module.exports.prepare = async (pluginConfig, context) => {
const { nextRelease, cwd } = context;
const notes = nextRelease.notes?.trim();
if (!notes) return;
const rawNotes = nextRelease.notes?.trim();
if (!rawNotes) return;
const { serverUrl, repository } = releaseEnvironment(context);
const { serverUrl, publicUrl, repository } = releaseEnvironment(context);
const notes = makeUrlsPublic(rawNotes, serverUrl, publicUrl);
const changelogPath = path.resolve(cwd, pluginConfig.changelogFile ?? 'CHANGELOG.md');
const releaseJsonPath = path.resolve(cwd, pluginConfig.releaseJsonPath ?? 'patches-bundle.json');
@@ -67,7 +78,7 @@ module.exports.prepare = async (pluginConfig, context) => {
const releaseData = {
created_at: new Date().toISOString().replace(/\.\d{3}Z$/, ''),
description: notes,
download_url: `${serverUrl}/${repository}/releases/download/${nextRelease.gitTag}/${assetName}`,
download_url: `${publicUrl}/${repository}/releases/download/${nextRelease.gitTag}/${assetName}`,
signature_download_url: '',
version,
};
@@ -75,7 +86,7 @@ module.exports.prepare = async (pluginConfig, context) => {
};
module.exports.publish = async (pluginConfig, context) => {
const { serverUrl, repository, token } = releaseEnvironment(context);
const { serverUrl, publicUrl, repository, token } = releaseEnvironment(context);
const { branch, cwd, nextRelease } = context;
const repositoryPath = repository.split('/').map(encodeURIComponent).join('/');
const apiUrl = `${serverUrl}/api/v1/repos/${repositoryPath}`;
@@ -96,7 +107,7 @@ module.exports.publish = async (pluginConfig, context) => {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
body: nextRelease.notes,
body: makeUrlsPublic(nextRelease.notes, serverUrl, publicUrl),
draft: false,
name: nextRelease.name,
prerelease: false,
+1
View File
@@ -52,6 +52,7 @@ jobs:
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_URL: ${{ gitea.server_url }}
GITEA_PUBLIC_URL: https://git.krtss.de
GITEA_REPOSITORY: ${{ gitea.repository }}
GITEA_REF_NAME: ${{ gitea.ref_name }}
run: npx semantic-release
+2 -2
View File
@@ -28,6 +28,7 @@
}
}
],
"gradle-semantic-release-plugin",
[
"./.gitea/scripts/morphe-release.cjs",
{
@@ -36,11 +37,10 @@
"assetDirectory": "patches/build/libs"
}
],
"gradle-semantic-release-plugin",
[
"@semantic-release/exec",
{
"prepareCmd": "./gradlew generatePatchesList && jq '.version=\"${nextRelease.version}\"' patches-list.json > patches-list.json.tmp && mv patches-list.json.tmp patches-list.json && python3 .gitea/scripts/generate_patches_readme.py \"$GITEA_URL\" \"$GITEA_REPOSITORY\" \"$GITEA_REF_NAME\" patches-list.json README.md"
"prepareCmd": "./gradlew generatePatchesList && jq '.version=\"${nextRelease.version}\"' patches-list.json > patches-list.json.tmp && mv patches-list.json.tmp patches-list.json && python3 .gitea/scripts/generate_patches_readme.py \"$GITEA_PUBLIC_URL\" \"$GITEA_REPOSITORY\" \"$GITEA_REF_NAME\" patches-list.json README.md"
}
],
[
+21
View File
@@ -0,0 +1,21 @@
# Agent Instructions
## Release-managed files
Agents must never create, edit, delete, restore, regenerate, format, resolve conflicts in, or stage files managed by the release pipeline.
Protected files:
- `CHANGELOG.md`
- `gradle.properties`
- `patches-bundle.json`
- `patches-list.json`
- `README.md`
These files are generated, updated, and committed exclusively by `semantic-release`. Manual changes or conflict resolutions cause divergence from the release commit and create pull or rebase conflicts.
When a task requires different generated content, change only the source, generator, or release configuration responsible for producing it. Leave every protected file untouched and tell the user that the next release must regenerate it.
If a protected file is conflicted, do not resolve, restore, or stage it. Stop and report the conflict to the user.
Do not run release or generation commands that modify protected files, including `npx semantic-release`, `./gradlew generatePatchesList`, or `.gitea/scripts/generate_patches_readme.py`. A dry run is allowed only when it is proven not to write any protected file.
+18
View File
@@ -1,3 +1,21 @@
## [1.1.0](https://git.krtss.de/StanPonomarev/twitter-patch/compare/v1.0.3...v1.1.0) (2026-08-21)
### ✨ New Features
* remove upgrade button ([723964b](https://git.krtss.de/StanPonomarev/twitter-patch/commit/723964bc29d0a9edc7ad1915c9cf5669fe9e6f95))
## [1.0.3](https://git.krtss.de/StanPonomarev/twitter-patch/compare/v1.0.2...v1.0.3) (2026-08-21)
### 🐛 Bug Fixes
* artifact ([0a2b472](https://git.krtss.de/StanPonomarev/twitter-patch/commit/0a2b4725fb53bf0fa741dc4be1a9914ad1e5be8b))
## [1.0.2](https://git.krtss.de/StanPonomarev/twitter-patch/compare/v1.0.1...v1.0.2) (2026-08-21)
### 🐛 Bug Fixes
* release again ([ef64be8](https://git.krtss.de/StanPonomarev/twitter-patch/commit/ef64be8da665680ebf9fadcf60ae79046594292d))
## [1.0.1](http://gitea:3000/StanPonomarev/twitter-patch/compare/v1.0.0...v1.0.1) (2026-08-21)
### 🐛 Bug Fixes
+3 -2
View File
@@ -4,9 +4,9 @@ Patch bundle for X (Twitter), maintained by
[Stan Ponomarev](https://git.krtss.de/StanPonomarev).
<!-- PATCHES_START -->
> **[v1.0.1](http://gitea:3000/StanPonomarev/twitter-patch/releases/tag/v1.0.1)**&nbsp;&nbsp;•&nbsp;&nbsp;`main`&nbsp;&nbsp;•&nbsp;&nbsp;1 patches total
> **[v1.1.0](https://git.krtss.de/StanPonomarev/twitter-patch/releases/tag/v1.1.0)**&nbsp;&nbsp;•&nbsp;&nbsp;`main`&nbsp;&nbsp;•&nbsp;&nbsp;2 patches total
<details open>
<summary>📦 X&nbsp;&nbsp;•&nbsp;&nbsp;1 patch</summary>
<summary>📦 X&nbsp;&nbsp;•&nbsp;&nbsp;2 patches</summary>
<br>
**🎯 Supported versions:**
@@ -17,6 +17,7 @@ Patch bundle for X (Twitter), maintained by
| 💊&nbsp;Patch | 📜&nbsp;Description | ⚙️&nbsp;Options |
|----------|----------------|-----------|
| [Disable outdated version screen](#disable-outdated-version-screen) | Prevents X from blocking app access when it marks the client version as outdated. | |
| [Remove timeline Upgrade button](#remove-timeline-upgrade-button) | Removes the Premium Upgrade button from the timeline's top bar. | |
</details>
+1 -1
View File
@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
version = 1.0.1
version = 1.1.0
+4 -4
View File
@@ -1,7 +1,7 @@
{
"created_at": "2026-08-21T15:07:17",
"description": "## [1.0.1](http://gitea:3000/StanPonomarev/twitter-patch/compare/v1.0.0...v1.0.1) (2026-08-21)\n\n### 🐛 Bug Fixes\n\n* update release strat ([818e291](http://gitea:3000/StanPonomarev/twitter-patch/commit/818e291ffdfa17eff5f57e9a47e3f73a00b7c050))",
"download_url": "http://gitea:3000/StanPonomarev/twitter-patch/releases/download/v1.0.1/patches-1.0.1.mpp",
"created_at": "2026-08-21T18:50:29",
"description": "## [1.1.0](https://git.krtss.de/StanPonomarev/twitter-patch/compare/v1.0.3...v1.1.0) (2026-08-21)\n\n### ✨ New Features\n\n* remove upgrade button ([723964b](https://git.krtss.de/StanPonomarev/twitter-patch/commit/723964bc29d0a9edc7ad1915c9cf5669fe9e6f95))",
"download_url": "https://git.krtss.de/StanPonomarev/twitter-patch/releases/download/v1.1.0/patches-1.1.0.mpp",
"signature_download_url": "",
"version": "1.0.1"
"version": "1.1.0"
}
+27 -1
View File
@@ -1,6 +1,6 @@
{
"NOTE": "Do NOT manually edit this file. This file is automatically updated when semantic release (release.yml) runs. Manually editing this file can break your releases and break third party tools that use this file.",
"version": "1.0.1",
"version": "1.1.0",
"patches": [
{
"name": "Disable outdated version screen",
@@ -27,6 +27,32 @@
}
],
"options": []
},
{
"name": "Remove timeline Upgrade button",
"description": "Removes the Premium Upgrade button from the timeline's top bar.",
"default": true,
"dependencies": [],
"compatiblePackages": [
{
"packageName": "com.twitter.android",
"name": "X",
"description": null,
"apkFileType": "APKM",
"appIconColor": "#000000",
"signatures": null,
"targets": [
{
"version": "12.7.1-release.0",
"versionCodes": null,
"isExperimental": false,
"minSdk": 28,
"description": null
}
]
}
],
"options": []
}
]
}
+1 -1
View File
@@ -43,7 +43,7 @@ tasks {
register<JavaExec>("generatePatchesList") {
description = "Build patch with patch list"
dependsOn(build)
dependsOn(buildAndroid)
classpath = sourceSets["main"].runtimeClasspath + patchListGeneratorClasspath
mainClass.set("util.PatchListGeneratorKt")
@@ -23,7 +23,7 @@ import kotlin.test.assertTrue
class MorphePatchIntegrationTest {
@Test
fun `applies outdated version patch to target APKM`() {
fun `applies patches to target APKM`() {
val inputApkm = requiredPathProperty("integrationTest.inputApkm")
val patchBundle = requiredPathProperty("integrationTest.patchBundle")
val morpheCache = requiredPathProperty("integrationTest.morpheCache")
@@ -47,7 +47,8 @@ class MorphePatchIntegrationTest {
"patch",
"--patches=${patchBundle.absolutePathString()}",
"--exclusive",
"--enable=$PATCH_NAME",
"--enable=$OUTDATED_PATCH_NAME",
"--enable=$UPGRADE_PATCH_NAME",
"--unsigned",
"--out=${patchedApk.absolutePathString()}",
"--result-file=${resultFile.absolutePathString()}",
@@ -91,7 +92,10 @@ class MorphePatchIntegrationTest {
val appliedPatchNames = result.getAsJsonArray("appliedPatches")
.map { it.asJsonObject.get("name").asString }
assertTrue(PATCH_NAME in appliedPatchNames, "Expected patch was not applied: $appliedPatchNames")
assertTrue(
appliedPatchNames.containsAll(listOf(OUTDATED_PATCH_NAME, UPGRADE_PATCH_NAME)),
"Expected patches were not applied: $appliedPatchNames"
)
assertTrue(result.getAsJsonArray("failedPatches").isEmpty, "Morphe reported failed patches")
} finally {
workDirectory.toFile().deleteRecursively()
@@ -142,7 +146,8 @@ class MorphePatchIntegrationTest {
}
private companion object {
const val PATCH_NAME = "Disable outdated version screen"
const val OUTDATED_PATCH_NAME = "Disable outdated version screen"
const val UPGRADE_PATCH_NAME = "Remove timeline Upgrade button"
const val MORPHE_VERSION = "1.13.1"
const val MORPHE_SHA256 = "a212531f68d3ada4b504bf9dac3e02460589f3bf8868ee66e1b4780152d3466b"
const val MORPHE_URL =
@@ -0,0 +1,32 @@
package app.twitter.patches
import app.morphe.patcher.patch.resourcePatch
import app.twitter.patches.shared.Constants.COMPATIBILITY_X
import org.w3c.dom.Element
private const val TOOLBAR_ITEMS_PATH = "res/menu/toolbar_items.xml"
private const val UPGRADE_BUTTON_ID = "premium_upsell_upgrade"
@Suppress("unused")
val removeTimelineUpgradeButtonPatch = resourcePatch(
name = "Remove timeline Upgrade button",
description = "Removes the Premium Upgrade button from the timeline's top bar.",
default = true
) {
compatibleWith(COMPATIBILITY_X)
execute {
document(TOOLBAR_ITEMS_PATH).use { document ->
val menu = document.documentElement
val upgradeButton = (0 until menu.childNodes.length)
.asSequence()
.map { menu.childNodes.item(it) }
.filterIsInstance<Element>()
.single {
it.getAttribute("android:id").substringAfterLast('/') == UPGRADE_BUTTON_ID
}
menu.removeChild(upgradeButton)
}
}
}