Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
076f1c2479 | ||
|
|
48f0cecbe4 | ||
|
|
015ce16efc | ||
|
|
97c30af97d | ||
|
|
7727f37a18 | ||
|
|
1985bf307b | ||
|
|
351917cbbd | ||
|
|
7062792215 | ||
|
|
1b47dcf787 | ||
|
|
cb1230a7ab | ||
|
|
29816c940a | ||
|
|
723964bc29 | ||
|
|
6ec6b0032b | ||
|
|
0a2b4725fb | ||
|
|
5dc2c501b1 | ||
|
|
b7c143788b | ||
|
|
ef64be8da6 |
@@ -4,20 +4,30 @@ const path = require('node:path');
|
|||||||
function releaseEnvironment(context) {
|
function releaseEnvironment(context) {
|
||||||
const env = context.env ?? process.env;
|
const env = context.env ?? process.env;
|
||||||
const serverUrl = env.GITEA_URL?.replace(/\/$/, '');
|
const serverUrl = env.GITEA_URL?.replace(/\/$/, '');
|
||||||
|
const publicUrl = env.GITEA_PUBLIC_URL?.replace(/\/$/, '');
|
||||||
const repository = env.GITEA_REPOSITORY;
|
const repository = env.GITEA_REPOSITORY;
|
||||||
const token = env.GITEA_TOKEN;
|
const token = env.GITEA_TOKEN;
|
||||||
|
|
||||||
if (!serverUrl || !repository || !token) {
|
if (!serverUrl || !publicUrl || !repository || !token) {
|
||||||
throw new Error('GITEA_URL, GITEA_REPOSITORY, and GITEA_TOKEN are required');
|
throw new Error(
|
||||||
|
'GITEA_URL, GITEA_PUBLIC_URL, GITEA_REPOSITORY, and GITEA_TOKEN are required',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!/^https?:\/\//.test(serverUrl)) {
|
if (!/^https?:\/\//.test(serverUrl)) {
|
||||||
throw new Error('GITEA_URL must be an HTTP(S) URL');
|
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)) {
|
if (!/^[^/]+\/[^/]+$/.test(repository)) {
|
||||||
throw new Error('GITEA_REPOSITORY must use the owner/repository format');
|
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 = {}) {
|
async function giteaRequest(url, token, options = {}) {
|
||||||
@@ -43,10 +53,11 @@ module.exports.verifyConditions = async (_pluginConfig, context) => {
|
|||||||
|
|
||||||
module.exports.prepare = async (pluginConfig, context) => {
|
module.exports.prepare = async (pluginConfig, context) => {
|
||||||
const { nextRelease, cwd } = context;
|
const { nextRelease, cwd } = context;
|
||||||
const notes = nextRelease.notes?.trim();
|
const rawNotes = nextRelease.notes?.trim();
|
||||||
if (!notes) return;
|
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 changelogPath = path.resolve(cwd, pluginConfig.changelogFile ?? 'CHANGELOG.md');
|
||||||
const releaseJsonPath = path.resolve(cwd, pluginConfig.releaseJsonPath ?? 'patches-bundle.json');
|
const releaseJsonPath = path.resolve(cwd, pluginConfig.releaseJsonPath ?? 'patches-bundle.json');
|
||||||
|
|
||||||
@@ -67,7 +78,7 @@ module.exports.prepare = async (pluginConfig, context) => {
|
|||||||
const releaseData = {
|
const releaseData = {
|
||||||
created_at: new Date().toISOString().replace(/\.\d{3}Z$/, ''),
|
created_at: new Date().toISOString().replace(/\.\d{3}Z$/, ''),
|
||||||
description: notes,
|
description: notes,
|
||||||
download_url: `${serverUrl}/${repository}/releases/download/${nextRelease.gitTag}/${assetName}`,
|
download_url: `${publicUrl}/${repository}/releases/download/${nextRelease.gitTag}/${assetName}`,
|
||||||
signature_download_url: '',
|
signature_download_url: '',
|
||||||
version,
|
version,
|
||||||
};
|
};
|
||||||
@@ -75,7 +86,7 @@ module.exports.prepare = async (pluginConfig, context) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports.publish = 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 { branch, cwd, nextRelease } = context;
|
||||||
const repositoryPath = repository.split('/').map(encodeURIComponent).join('/');
|
const repositoryPath = repository.split('/').map(encodeURIComponent).join('/');
|
||||||
const apiUrl = `${serverUrl}/api/v1/repos/${repositoryPath}`;
|
const apiUrl = `${serverUrl}/api/v1/repos/${repositoryPath}`;
|
||||||
@@ -96,7 +107,7 @@ module.exports.publish = async (pluginConfig, context) => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
body: nextRelease.notes,
|
body: makeUrlsPublic(nextRelease.notes, serverUrl, publicUrl),
|
||||||
draft: false,
|
draft: false,
|
||||||
name: nextRelease.name,
|
name: nextRelease.name,
|
||||||
prerelease: false,
|
prerelease: false,
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
GITEA_URL: ${{ gitea.server_url }}
|
GITEA_URL: ${{ gitea.server_url }}
|
||||||
|
GITEA_PUBLIC_URL: https://git.krtss.de
|
||||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
GITEA_REF_NAME: ${{ gitea.ref_name }}
|
GITEA_REF_NAME: ${{ gitea.ref_name }}
|
||||||
run: npx semantic-release
|
run: npx semantic-release
|
||||||
|
|||||||
+2
-2
@@ -28,6 +28,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"gradle-semantic-release-plugin",
|
||||||
[
|
[
|
||||||
"./.gitea/scripts/morphe-release.cjs",
|
"./.gitea/scripts/morphe-release.cjs",
|
||||||
{
|
{
|
||||||
@@ -36,11 +37,10 @@
|
|||||||
"assetDirectory": "patches/build/libs"
|
"assetDirectory": "patches/build/libs"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"gradle-semantic-release-plugin",
|
|
||||||
[
|
[
|
||||||
"@semantic-release/exec",
|
"@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"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -1,3 +1,51 @@
|
|||||||
|
## [1.1.5](https://git.krtss.de/StanPonomarev/twitter-patch/compare/v1.1.4...v1.1.5) (2026-08-23)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* vending update screen ([48f0cec](https://git.krtss.de/StanPonomarev/twitter-patch/commit/48f0cecbe47d73ee06c15915d5c57694856b904e))
|
||||||
|
|
||||||
|
## [1.1.4](https://git.krtss.de/StanPonomarev/twitter-patch/compare/v1.1.3...v1.1.4) (2026-08-22)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* more update required fixes ([97c30af](https://git.krtss.de/StanPonomarev/twitter-patch/commit/97c30af97d00e7f92b5c2801e9e744f00704758a))
|
||||||
|
|
||||||
|
## [1.1.3](https://git.krtss.de/StanPonomarev/twitter-patch/compare/v1.1.2...v1.1.3) (2026-08-22)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* xchat shutting down the app ([1985bf3](https://git.krtss.de/StanPonomarev/twitter-patch/commit/1985bf307b4552655216079082757a53af00140b))
|
||||||
|
|
||||||
|
## [1.1.2](https://git.krtss.de/StanPonomarev/twitter-patch/compare/v1.1.1...v1.1.2) (2026-08-22)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* manifest ([7062792](https://git.krtss.de/StanPonomarev/twitter-patch/commit/7062792215590cce79caa6e78354ee73375eaa79))
|
||||||
|
|
||||||
|
## [1.1.1](https://git.krtss.de/StanPonomarev/twitter-patch/compare/v1.1.0...v1.1.1) (2026-08-21)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* update screen ([cb1230a](https://git.krtss.de/StanPonomarev/twitter-patch/commit/cb1230a7abf46edb11f2b8dad100b091dd672aee))
|
||||||
|
|
||||||
|
## [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)
|
## [1.0.1](http://gitea:3000/StanPonomarev/twitter-patch/compare/v1.0.0...v1.0.1) (2026-08-21)
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ Patch bundle for X (Twitter), maintained by
|
|||||||
[Stan Ponomarev](https://git.krtss.de/StanPonomarev).
|
[Stan Ponomarev](https://git.krtss.de/StanPonomarev).
|
||||||
|
|
||||||
<!-- PATCHES_START -->
|
<!-- PATCHES_START -->
|
||||||
> **[v1.0.1](http://gitea:3000/StanPonomarev/twitter-patch/releases/tag/v1.0.1)** • `main` • 1 patches total
|
> **[v1.1.5](https://git.krtss.de/StanPonomarev/twitter-patch/releases/tag/v1.1.5)** • `main` • 2 patches total
|
||||||
<details open>
|
<details open>
|
||||||
<summary>📦 X • 1 patch</summary>
|
<summary>📦 X • 2 patches</summary>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
**🎯 Supported versions:**
|
**🎯 Supported versions:**
|
||||||
@@ -17,6 +17,7 @@ Patch bundle for X (Twitter), maintained by
|
|||||||
| 💊 Patch | 📜 Description | ⚙️ Options |
|
| 💊 Patch | 📜 Description | ⚙️ Options |
|
||||||
|----------|----------------|-----------|
|
|----------|----------------|-----------|
|
||||||
| [Disable outdated version screen](#disable-outdated-version-screen) | Prevents X from blocking app access when it marks the client version as outdated. | |
|
| [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>
|
</details>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 1.0.1
|
version = 1.1.5
|
||||||
|
|||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"created_at": "2026-08-21T15:07:17",
|
"created_at": "2026-08-23T05:14:44",
|
||||||
"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))",
|
"description": "## [1.1.5](https://git.krtss.de/StanPonomarev/twitter-patch/compare/v1.1.4...v1.1.5) (2026-08-23)\n\n### 🐛 Bug Fixes\n\n* vending update screen ([48f0cec](https://git.krtss.de/StanPonomarev/twitter-patch/commit/48f0cecbe47d73ee06c15915d5c57694856b904e))",
|
||||||
"download_url": "http://gitea:3000/StanPonomarev/twitter-patch/releases/download/v1.0.1/patches-1.0.1.mpp",
|
"download_url": "https://git.krtss.de/StanPonomarev/twitter-patch/releases/download/v1.1.5/patches-1.1.5.mpp",
|
||||||
"signature_download_url": "",
|
"signature_download_url": "",
|
||||||
"version": "1.0.1"
|
"version": "1.1.5"
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-1
@@ -1,11 +1,39 @@
|
|||||||
{
|
{
|
||||||
"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.",
|
"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.5",
|
||||||
"patches": [
|
"patches": [
|
||||||
{
|
{
|
||||||
"name": "Disable outdated version screen",
|
"name": "Disable outdated version screen",
|
||||||
"description": "Prevents X from blocking app access when it marks the client version as outdated.",
|
"description": "Prevents X from blocking app access when it marks the client version as outdated.",
|
||||||
"default": true,
|
"default": true,
|
||||||
|
"dependencies": [
|
||||||
|
"ResourcePatch"
|
||||||
|
],
|
||||||
|
"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": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Remove timeline Upgrade button",
|
||||||
|
"description": "Removes the Premium Upgrade button from the timeline's top bar.",
|
||||||
|
"default": true,
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"compatiblePackages": [
|
"compatiblePackages": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ tasks {
|
|||||||
register<JavaExec>("generatePatchesList") {
|
register<JavaExec>("generatePatchesList") {
|
||||||
description = "Build patch with patch list"
|
description = "Build patch with patch list"
|
||||||
|
|
||||||
dependsOn(build)
|
dependsOn(buildAndroid)
|
||||||
|
|
||||||
classpath = sourceSets["main"].runtimeClasspath + patchListGeneratorClasspath
|
classpath = sourceSets["main"].runtimeClasspath + patchListGeneratorClasspath
|
||||||
mainClass.set("util.PatchListGeneratorKt")
|
mainClass.set("util.PatchListGeneratorKt")
|
||||||
|
|||||||
+9
-4
@@ -23,7 +23,7 @@ import kotlin.test.assertTrue
|
|||||||
|
|
||||||
class MorphePatchIntegrationTest {
|
class MorphePatchIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
fun `applies outdated version patch to target APKM`() {
|
fun `applies patches to target APKM`() {
|
||||||
val inputApkm = requiredPathProperty("integrationTest.inputApkm")
|
val inputApkm = requiredPathProperty("integrationTest.inputApkm")
|
||||||
val patchBundle = requiredPathProperty("integrationTest.patchBundle")
|
val patchBundle = requiredPathProperty("integrationTest.patchBundle")
|
||||||
val morpheCache = requiredPathProperty("integrationTest.morpheCache")
|
val morpheCache = requiredPathProperty("integrationTest.morpheCache")
|
||||||
@@ -47,7 +47,8 @@ class MorphePatchIntegrationTest {
|
|||||||
"patch",
|
"patch",
|
||||||
"--patches=${patchBundle.absolutePathString()}",
|
"--patches=${patchBundle.absolutePathString()}",
|
||||||
"--exclusive",
|
"--exclusive",
|
||||||
"--enable=$PATCH_NAME",
|
"--enable=$OUTDATED_PATCH_NAME",
|
||||||
|
"--enable=$UPGRADE_PATCH_NAME",
|
||||||
"--unsigned",
|
"--unsigned",
|
||||||
"--out=${patchedApk.absolutePathString()}",
|
"--out=${patchedApk.absolutePathString()}",
|
||||||
"--result-file=${resultFile.absolutePathString()}",
|
"--result-file=${resultFile.absolutePathString()}",
|
||||||
@@ -91,7 +92,10 @@ class MorphePatchIntegrationTest {
|
|||||||
|
|
||||||
val appliedPatchNames = result.getAsJsonArray("appliedPatches")
|
val appliedPatchNames = result.getAsJsonArray("appliedPatches")
|
||||||
.map { it.asJsonObject.get("name").asString }
|
.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")
|
assertTrue(result.getAsJsonArray("failedPatches").isEmpty, "Morphe reported failed patches")
|
||||||
} finally {
|
} finally {
|
||||||
workDirectory.toFile().deleteRecursively()
|
workDirectory.toFile().deleteRecursively()
|
||||||
@@ -142,7 +146,8 @@ class MorphePatchIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
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_VERSION = "1.13.1"
|
||||||
const val MORPHE_SHA256 = "a212531f68d3ada4b504bf9dac3e02460589f3bf8868ee66e1b4780152d3466b"
|
const val MORPHE_SHA256 = "a212531f68d3ada4b504bf9dac3e02460589f3bf8868ee66e1b4780152d3466b"
|
||||||
const val MORPHE_URL =
|
const val MORPHE_URL =
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package app.twitter.patches
|
|||||||
import app.morphe.patcher.extensions.InstructionExtensions.addInstructions
|
import app.morphe.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
import app.morphe.patcher.Fingerprint
|
import app.morphe.patcher.Fingerprint
|
||||||
import app.morphe.patcher.patch.bytecodePatch
|
import app.morphe.patcher.patch.bytecodePatch
|
||||||
|
import app.morphe.patcher.patch.resourcePatch
|
||||||
import app.twitter.patches.shared.Constants.COMPATIBILITY_X
|
import app.twitter.patches.shared.Constants.COMPATIBILITY_X
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
private val outdatedVersionEnforcementFingerprint = Fingerprint(
|
private val outdatedVersionEnforcementFingerprint = Fingerprint(
|
||||||
definingClass = "Lcom/twitter/subsystem/clientshutdown/o;",
|
definingClass = "Lcom/twitter/subsystem/clientshutdown/o;",
|
||||||
@@ -13,6 +15,41 @@ private val outdatedVersionEnforcementFingerprint = Fingerprint(
|
|||||||
strings = listOf("Target version is ", "Detected shutdown")
|
strings = listOf("Target version is ", "Detected shutdown")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val forceUpdateNavigationFingerprint = Fingerprint(
|
||||||
|
definingClass = "Lcom/x/android/main/helpers/o8;",
|
||||||
|
name = "a",
|
||||||
|
returnType = "Lcom/x/compose/core/o2;",
|
||||||
|
parameters = listOf(
|
||||||
|
"Lcom/x/navigation/RootNavigationArgs;",
|
||||||
|
"Lcom/arkivanov/decompose/c;",
|
||||||
|
"Lcom/x/navigation/f1;"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val shutdownForegroundHandlerFingerprint = Fingerprint(
|
||||||
|
definingClass = "Lcom/twitter/subsystem/clientshutdown/m;",
|
||||||
|
name = "invoke",
|
||||||
|
returnType = "Ljava/lang/Object;",
|
||||||
|
parameters = listOf("Ljava/lang/Object;"),
|
||||||
|
strings = listOf("Entered foreground, shutdown is ", "Launching app update")
|
||||||
|
)
|
||||||
|
|
||||||
|
private val appUpdateLauncherFingerprint = Fingerprint(
|
||||||
|
definingClass = "Lcom/twitter/subsystem/clientshutdown/i;",
|
||||||
|
name = "a",
|
||||||
|
returnType = "V",
|
||||||
|
parameters = listOf("Ljava/lang/String;"),
|
||||||
|
strings = listOf("Launching app update")
|
||||||
|
)
|
||||||
|
|
||||||
|
private val shutdownEntryPointDisablerFingerprint = Fingerprint(
|
||||||
|
definingClass = "Lcom/twitter/subsystem/clientshutdown/i;",
|
||||||
|
name = "b",
|
||||||
|
returnType = "V",
|
||||||
|
parameters = emptyList(),
|
||||||
|
strings = listOf("com.twitter.clientshutdown.update.AppUpdateActivityAlias")
|
||||||
|
)
|
||||||
|
|
||||||
private val shutdownStateFingerprint = Fingerprint(
|
private val shutdownStateFingerprint = Fingerprint(
|
||||||
definingClass = "Lcom/twitter/subsystem/clientshutdown/q;",
|
definingClass = "Lcom/twitter/subsystem/clientshutdown/q;",
|
||||||
name = "isShutdown",
|
name = "isShutdown",
|
||||||
@@ -20,6 +57,59 @@ private val shutdownStateFingerprint = Fingerprint(
|
|||||||
parameters = emptyList()
|
parameters = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val appUpdateScreenRendererFingerprint = Fingerprint(
|
||||||
|
definingClass = "Lcom/twitter/clientshutdown/update/j;",
|
||||||
|
name = "<init>",
|
||||||
|
returnType = "V",
|
||||||
|
parameters = listOf(
|
||||||
|
"Landroid/view/View;",
|
||||||
|
"Landroid/app/Activity;",
|
||||||
|
"Lcom/twitter/app/common/activity/b;",
|
||||||
|
"Lcom/twitter/subsystem/clientshutdown/api/c;"
|
||||||
|
),
|
||||||
|
strings = listOf("rootView", "activityFinisher", "eventLogHelper")
|
||||||
|
)
|
||||||
|
|
||||||
|
private val forceUpdateScreenRendererFingerprint = Fingerprint(
|
||||||
|
definingClass = "Lcom/x/forceupdate/g;",
|
||||||
|
name = "a",
|
||||||
|
returnType = "V",
|
||||||
|
parameters = listOf(
|
||||||
|
"Lkotlin/jvm/functions/Function0;",
|
||||||
|
"Lkotlin/jvm/functions/Function1;",
|
||||||
|
"Landroidx/compose/ui/Modifier;",
|
||||||
|
"Landroidx/compose/runtime/Composer;",
|
||||||
|
"I"
|
||||||
|
),
|
||||||
|
strings = listOf("onUpdateClick", "onLearnMoreClick")
|
||||||
|
)
|
||||||
|
|
||||||
|
private val playCoreUpdateManagerFingerprint = Fingerprint(
|
||||||
|
definingClass = "Lcom/x/android/appupdate/b${'$'}a;",
|
||||||
|
name = "invokeSuspend",
|
||||||
|
returnType = "Ljava/lang/Object;",
|
||||||
|
parameters = listOf("Ljava/lang/Object;"),
|
||||||
|
strings = listOf("x_lite_in_app_update_enabled")
|
||||||
|
)
|
||||||
|
|
||||||
|
private const val APP_UPDATE_ACTIVITY = "com.twitter.clientshutdown.update.AppUpdateActivity"
|
||||||
|
|
||||||
|
private val disableOutdatedVersionActivityPatch = resourcePatch {
|
||||||
|
execute {
|
||||||
|
document("AndroidManifest.xml").use { document ->
|
||||||
|
val appUpdateActivity = document.getElementsByTagName("activity")
|
||||||
|
.let { activities ->
|
||||||
|
(0 until activities.length)
|
||||||
|
.asSequence()
|
||||||
|
.map { activities.item(it) }
|
||||||
|
.filterIsInstance<Element>()
|
||||||
|
.single { it.getAttribute("android:name") == APP_UPDATE_ACTIVITY }
|
||||||
|
}
|
||||||
|
appUpdateActivity.setAttribute("android:enabled", "false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val disableOutdatedVersionScreenPatch = bytecodePatch(
|
val disableOutdatedVersionScreenPatch = bytecodePatch(
|
||||||
name = "Disable outdated version screen",
|
name = "Disable outdated version screen",
|
||||||
@@ -27,8 +117,44 @@ val disableOutdatedVersionScreenPatch = bytecodePatch(
|
|||||||
default = true
|
default = true
|
||||||
) {
|
) {
|
||||||
compatibleWith(COMPATIBILITY_X)
|
compatibleWith(COMPATIBILITY_X)
|
||||||
|
dependsOn(disableOutdatedVersionActivityPatch)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
|
forceUpdateNavigationFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
move-object/from16 v2, p1
|
||||||
|
instance-of v0, v2, Lcom/x/navigation/ForceUpdateArgs;
|
||||||
|
if-eqz v0, :force_update_allowed
|
||||||
|
sget-object v0, Lcom/x/navigation/MainLandingArgs${'$'}TabType;->HOME:Lcom/x/navigation/MainLandingArgs${'$'}TabType;
|
||||||
|
const/4 v1, 0x0
|
||||||
|
new-instance v2, Lcom/x/navigation/MainLandingArgs;
|
||||||
|
invoke-direct {v2, v0, v1, v1, v1}, Lcom/x/navigation/MainLandingArgs;-><init>(Lcom/x/navigation/MainLandingArgs${'$'}TabType;Lcom/x/navigation/AcceptGroupInviteArgs;Lcom/x/navigation/RootNavigationArgs;Lcom/x/models/PostIdentifier;)V
|
||||||
|
move-object/16 p1, v2
|
||||||
|
:force_update_allowed
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
shutdownForegroundHandlerFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
sget-object v0, Lkotlin/Unit;->a:Lkotlin/Unit;
|
||||||
|
return-object v0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
appUpdateLauncherFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"return-void"
|
||||||
|
)
|
||||||
|
|
||||||
|
shutdownEntryPointDisablerFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-virtual {p0}, Lcom/twitter/subsystem/clientshutdown/i;->c()V
|
||||||
|
return-void
|
||||||
|
"""
|
||||||
|
)
|
||||||
outdatedVersionEnforcementFingerprint.method.addInstructions(
|
outdatedVersionEnforcementFingerprint.method.addInstructions(
|
||||||
0,
|
0,
|
||||||
"""
|
"""
|
||||||
@@ -44,5 +170,31 @@ val disableOutdatedVersionScreenPatch = bytecodePatch(
|
|||||||
return v0
|
return v0
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
appUpdateScreenRendererFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
new-instance v0, Lcom/twitter/subsystem/clientshutdown/a;
|
||||||
|
invoke-direct {v0, p2}, Lcom/twitter/subsystem/clientshutdown/a;-><init>(Landroid/content/Context;)V
|
||||||
|
const/4 v1, 0x2
|
||||||
|
const-string v2, "com.twitter.clientshutdown.update.AppUpdateActivityAlias"
|
||||||
|
invoke-virtual {v0, v1, v2}, Lcom/twitter/subsystem/clientshutdown/a;->b(ILjava/lang/String;)V
|
||||||
|
invoke-virtual {p2}, Landroid/app/Activity;->finish()V
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
forceUpdateScreenRendererFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"return-void"
|
||||||
|
)
|
||||||
|
|
||||||
|
playCoreUpdateManagerFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
sget-object v0, Lkotlin/Unit;->a:Lkotlin/Unit;
|
||||||
|
return-object v0
|
||||||
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user