chore: Separate extensions by app (#3905)

This commit is contained in:
oSumAtrIX
2024-12-05 12:12:48 +01:00
committed by GitHub
parent 69ec47cbef
commit cc40246e60
314 changed files with 371 additions and 148 deletions

View File

@ -0,0 +1 @@
// Do not remove. Necessary for the extension plugin to be applied to the project.

View File

@ -0,0 +1 @@
<manifest/>

View File

@ -0,0 +1,9 @@
package app.revanced.extension.twitter.patches.hook.json
import org.json.JSONObject
abstract class BaseJsonHook : JsonHook {
abstract fun apply(json: JSONObject)
override fun transform(json: JSONObject) = json.apply { apply(json) }
}

View File

@ -0,0 +1,15 @@
package app.revanced.extension.twitter.patches.hook.json
import app.revanced.extension.twitter.patches.hook.patch.Hook
import org.json.JSONObject
interface JsonHook : Hook<JSONObject> {
/**
* Transform a JSONObject.
*
* @param json The JSONObject.
*/
fun transform(json: JSONObject): JSONObject
override fun hook(type: JSONObject) = transform(type)
}

View File

@ -0,0 +1,30 @@
package app.revanced.extension.twitter.patches.hook.json
import app.revanced.extension.twitter.patches.hook.patch.dummy.DummyHook
import app.revanced.extension.twitter.utils.json.JsonUtils.parseJson
import app.revanced.extension.twitter.utils.stream.StreamUtils
import org.json.JSONException
import java.io.IOException
import java.io.InputStream
object JsonHookPatch {
// Additional hooks added by corresponding patch.
private val hooks = buildList<JsonHook> {
add(DummyHook)
}
@JvmStatic
fun parseJsonHook(jsonInputStream: InputStream): InputStream {
var jsonObject = try {
parseJson(jsonInputStream)
} catch (ignored: IOException) {
return jsonInputStream // Unreachable.
} catch (ignored: JSONException) {
return jsonInputStream
}
for (hook in hooks) jsonObject = hook.hook(jsonObject)
return StreamUtils.fromString(jsonObject.toString())
}
}

View File

@ -0,0 +1,9 @@
package app.revanced.extension.twitter.patches.hook.patch
interface Hook<T> {
/**
* Hook the given type.
* @param type The type to hook
*/
fun hook(type: T): T
}

View File

@ -0,0 +1,15 @@
package app.revanced.extension.twitter.patches.hook.patch.ads
import app.revanced.extension.twitter.patches.hook.json.BaseJsonHook
import app.revanced.extension.twitter.patches.hook.twifucker.TwiFucker
import org.json.JSONObject
@Suppress("unused")
object HideAdsHook : BaseJsonHook() {
/**
* Strips JSONObject from promoted ads.
*
* @param json The JSONObject.
*/
override fun apply(json: JSONObject) = TwiFucker.hidePromotedAds(json)
}

View File

@ -0,0 +1,14 @@
package app.revanced.extension.twitter.patches.hook.patch.dummy
import app.revanced.extension.twitter.patches.hook.json.BaseJsonHook
import app.revanced.extension.twitter.patches.hook.json.JsonHookPatch
import org.json.JSONObject
/**
* Dummy hook to reserve a register in [JsonHookPatch.hooks] list.
*/
object DummyHook : BaseJsonHook() {
override fun apply(json: JSONObject) {
// Do nothing.
}
}

View File

@ -0,0 +1,14 @@
package app.revanced.extension.twitter.patches.hook.patch.recommendation
import app.revanced.extension.twitter.patches.hook.json.BaseJsonHook
import app.revanced.extension.twitter.patches.hook.twifucker.TwiFucker
import org.json.JSONObject
object RecommendedUsersHook : BaseJsonHook() {
/**
* Strips JSONObject from recommended users.
*
* @param json The JSONObject.
*/
override fun apply(json: JSONObject) = TwiFucker.hideRecommendedUsers(json)
}

View File

@ -0,0 +1,218 @@
package app.revanced.extension.twitter.patches.hook.twifucker
import android.util.Log
import app.revanced.extension.twitter.patches.hook.twifucker.TwiFuckerUtils.forEach
import app.revanced.extension.twitter.patches.hook.twifucker.TwiFuckerUtils.forEachIndexed
import org.json.JSONArray
import org.json.JSONObject
// https://raw.githubusercontent.com/Dr-TSNG/TwiFucker/880cdf1c1622e54ab45561ffcb4f53d94ed97bae/app/src/main/java/icu/nullptr/twifucker/hook/JsonHook.kt
internal object TwiFucker {
// root
private fun JSONObject.jsonGetInstructions(): JSONArray? = optJSONObject("timeline")?.optJSONArray("instructions")
private fun JSONObject.jsonGetData(): JSONObject? = optJSONObject("data")
private fun JSONObject.jsonHasRecommendedUsers(): Boolean = has("recommended_users")
private fun JSONObject.jsonRemoveRecommendedUsers() {
remove("recommended_users")
}
private fun JSONObject.jsonCheckAndRemoveRecommendedUsers() {
if (jsonHasRecommendedUsers()) {
Log.d("ReVanced", "Handle recommended users: $this")
jsonRemoveRecommendedUsers()
}
}
private fun JSONObject.jsonHasThreads(): Boolean = has("threads")
private fun JSONObject.jsonRemoveThreads() {
remove("threads")
}
private fun JSONObject.jsonCheckAndRemoveThreads() {
if (jsonHasThreads()) {
Log.d("ReVanced", "Handle threads: $this")
jsonRemoveThreads()
}
}
// data
private fun JSONObject.dataGetInstructions(): JSONArray? {
val timeline =
optJSONObject("user_result")?.optJSONObject("result")
?.optJSONObject("timeline_response")?.optJSONObject("timeline")
?: optJSONObject("timeline_response")?.optJSONObject("timeline")
?: optJSONObject("search")?.optJSONObject("timeline_response")?.optJSONObject("timeline")
?: optJSONObject("timeline_response")
return timeline?.optJSONArray("instructions")
}
private fun JSONObject.dataCheckAndRemove() {
dataGetInstructions()?.forEach { instruction ->
instruction.instructionCheckAndRemove { it.entriesRemoveAnnoyance() }
}
}
private fun JSONObject.dataGetLegacy(): JSONObject? =
optJSONObject("tweet_result")?.optJSONObject("result")?.let {
if (it.has("tweet")) {
it.optJSONObject("tweet")
} else {
it
}
}?.optJSONObject("legacy")
// entry
private fun JSONObject.entryHasPromotedMetadata(): Boolean =
optJSONObject("content")?.optJSONObject("item")?.optJSONObject("content")
?.optJSONObject("tweet")
?.has("promotedMetadata") == true || optJSONObject("content")?.optJSONObject("content")
?.has("tweetPromotedMetadata") == true || optJSONObject("item")?.optJSONObject("content")
?.has("tweetPromotedMetadata") == true
private fun JSONObject.entryGetContentItems(): JSONArray? =
optJSONObject("content")?.optJSONArray("items")
?: optJSONObject("content")?.optJSONObject("timelineModule")?.optJSONArray("items")
private fun JSONObject.entryIsTweetDetailRelatedTweets(): Boolean = optString("entryId").startsWith("tweetdetailrelatedtweets-")
private fun JSONObject.entryGetTrends(): JSONArray? = optJSONObject("content")?.optJSONObject("timelineModule")?.optJSONArray("items")
// trend
private fun JSONObject.trendHasPromotedMetadata(): Boolean =
optJSONObject("item")?.optJSONObject("content")?.optJSONObject("trend")
?.has("promotedMetadata") == true
private fun JSONArray.trendRemoveAds() {
val trendRemoveIndex = mutableListOf<Int>()
forEachIndexed { trendIndex, trend ->
if (trend.trendHasPromotedMetadata()) {
Log.d("ReVanced", "Handle trends ads $trendIndex $trend")
trendRemoveIndex.add(trendIndex)
}
}
for (i in trendRemoveIndex.asReversed()) {
remove(i)
}
}
// instruction
private fun JSONObject.instructionTimelineAddEntries(): JSONArray? = optJSONArray("entries")
private fun JSONObject.instructionGetAddEntries(): JSONArray? = optJSONObject("addEntries")?.optJSONArray("entries")
private fun JSONObject.instructionCheckAndRemove(action: (JSONArray) -> Unit) {
instructionTimelineAddEntries()?.let(action)
instructionGetAddEntries()?.let(action)
}
// entries
private fun JSONArray.entriesRemoveTimelineAds() {
val removeIndex = mutableListOf<Int>()
forEachIndexed { entryIndex, entry ->
entry.entryGetTrends()?.trendRemoveAds()
if (entry.entryHasPromotedMetadata()) {
Log.d("ReVanced", "Handle timeline ads $entryIndex $entry")
removeIndex.add(entryIndex)
}
val innerRemoveIndex = mutableListOf<Int>()
val contentItems = entry.entryGetContentItems()
contentItems?.forEachIndexed inner@{ itemIndex, item ->
if (item.entryHasPromotedMetadata()) {
Log.d("ReVanced", "Handle timeline replies ads $entryIndex $entry")
if (contentItems.length() == 1) {
removeIndex.add(entryIndex)
} else {
innerRemoveIndex.add(itemIndex)
}
return@inner
}
}
for (i in innerRemoveIndex.asReversed()) {
contentItems?.remove(i)
}
}
for (i in removeIndex.reversed()) {
remove(i)
}
}
private fun JSONArray.entriesRemoveTweetDetailRelatedTweets() {
val removeIndex = mutableListOf<Int>()
forEachIndexed { entryIndex, entry ->
if (entry.entryIsTweetDetailRelatedTweets()) {
Log.d("ReVanced", "Handle tweet detail related tweets $entryIndex $entry")
removeIndex.add(entryIndex)
}
}
for (i in removeIndex.reversed()) {
remove(i)
}
}
private fun JSONArray.entriesRemoveAnnoyance() {
entriesRemoveTimelineAds()
entriesRemoveTweetDetailRelatedTweets()
}
private fun JSONObject.entryIsWhoToFollow(): Boolean =
optString("entryId").let {
it.startsWith("whoToFollow-") || it.startsWith("who-to-follow-") || it.startsWith("connect-module-")
}
private fun JSONObject.itemContainsPromotedUser(): Boolean =
optJSONObject("item")?.optJSONObject("content")
?.has("userPromotedMetadata") == true || optJSONObject("item")?.optJSONObject("content")
?.optJSONObject("user")
?.has("userPromotedMetadata") == true || optJSONObject("item")?.optJSONObject("content")
?.optJSONObject("user")?.has("promotedMetadata") == true
fun JSONArray.entriesRemoveWhoToFollow() {
val entryRemoveIndex = mutableListOf<Int>()
forEachIndexed { entryIndex, entry ->
if (!entry.entryIsWhoToFollow()) return@forEachIndexed
Log.d("ReVanced", "Handle whoToFollow $entryIndex $entry")
entryRemoveIndex.add(entryIndex)
val items = entry.entryGetContentItems()
val userRemoveIndex = mutableListOf<Int>()
items?.forEachIndexed { index, item ->
item.itemContainsPromotedUser().let {
if (it) {
Log.d("ReVanced", "Handle whoToFollow promoted user $index $item")
userRemoveIndex.add(index)
}
}
}
for (i in userRemoveIndex.reversed()) {
items?.remove(i)
}
}
for (i in entryRemoveIndex.reversed()) {
remove(i)
}
}
fun hideRecommendedUsers(json: JSONObject) {
json.filterInstructions { it.entriesRemoveWhoToFollow() }
json.jsonCheckAndRemoveRecommendedUsers()
}
fun hidePromotedAds(json: JSONObject) {
json.filterInstructions { it.entriesRemoveAnnoyance() }
json.jsonGetData()?.dataCheckAndRemove()
}
private fun JSONObject.filterInstructions(action: (JSONArray) -> Unit) {
jsonGetInstructions()?.forEach { instruction ->
instruction.instructionCheckAndRemove(action)
}
}
}

View File

@ -0,0 +1,22 @@
package app.revanced.extension.twitter.patches.hook.twifucker
import org.json.JSONArray
import org.json.JSONObject
internal object TwiFuckerUtils {
inline fun JSONArray.forEach(action: (JSONObject) -> Unit) {
(0 until this.length()).forEach { i ->
if (this[i] is JSONObject) {
action(this[i] as JSONObject)
}
}
}
inline fun JSONArray.forEachIndexed(action: (index: Int, JSONObject) -> Unit) {
(0 until this.length()).forEach { i ->
if (this[i] is JSONObject) {
action(i, this[i] as JSONObject)
}
}
}
}

View File

@ -0,0 +1,16 @@
package app.revanced.twitter.patches.links;
public final class ChangeLinkSharingDomainPatch {
private static final String DOMAIN_NAME = "https://fxtwitter.com";
private static final String LINK_FORMAT = "%s/%s/status/%s";
public static String formatResourceLink(Object... formatArgs) {
String username = (String) formatArgs[0];
String tweetId = (String) formatArgs[1];
return String.format(LINK_FORMAT, DOMAIN_NAME, username, tweetId);
}
public static String formatLink(long tweetId, String username) {
return String.format(LINK_FORMAT, DOMAIN_NAME, username, tweetId);
}
}

View File

@ -0,0 +1,15 @@
package app.revanced.twitter.patches.links;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public final class OpenLinksWithAppChooserPatch {
public static void openWithChooser(final Context context, final Intent intent) {
Log.d("ReVanced", "Opening intent with chooser: " + intent);
intent.setAction("android.intent.action.VIEW");
context.startActivity(Intent.createChooser(intent, null));
}
}

View File

@ -0,0 +1,13 @@
package app.revanced.extension.twitter.utils.json
import app.revanced.extension.twitter.utils.stream.StreamUtils
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.io.InputStream
object JsonUtils {
@JvmStatic
@Throws(IOException::class, JSONException::class)
fun parseJson(jsonInputStream: InputStream) = JSONObject(StreamUtils.toString(jsonInputStream))
}

View File

@ -0,0 +1,24 @@
package app.revanced.extension.twitter.utils.stream
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
object StreamUtils {
@Throws(IOException::class)
fun toString(inputStream: InputStream): String {
ByteArrayOutputStream().use { result ->
val buffer = ByteArray(1024)
var length: Int
while (inputStream.read(buffer).also { length = it } != -1) {
result.write(buffer, 0, length)
}
return result.toString()
}
}
fun fromString(string: String): InputStream {
return ByteArrayInputStream(string.toByteArray())
}
}