mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-06-12 05:07:45 +02:00
chore: Separate extensions by app (#3905)
This commit is contained in:
1
extensions/twitter/build.gradle.kts
Normal file
1
extensions/twitter/build.gradle.kts
Normal file
@ -0,0 +1 @@
|
||||
// Do not remove. Necessary for the extension plugin to be applied to the project.
|
1
extensions/twitter/src/main/AndroidManifest.xml
Normal file
1
extensions/twitter/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
||||
<manifest/>
|
@ -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) }
|
||||
}
|
@ -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)
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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.
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user