fix(twitter): make hide-ads patch compatible with any version

Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
oSumAtrIX 2023-02-22 05:37:51 +01:00
parent f1e6cbcdf1
commit 665598836a
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
9 changed files with 314 additions and 0 deletions

View File

@ -0,0 +1,9 @@
package app.revanced.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.twitter.patches.hook.json
import app.revanced.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,28 @@
package app.revanced.twitter.patches.hook.json
import app.revanced.twitter.utils.json.JsonUtils.parseJson
import app.revanced.twitter.utils.stream.StreamUtils
import org.json.JSONException
import java.io.IOException
import java.io.InputStream
object JsonHookPatch {
private val hooks = buildList<JsonHook> {
// Modified by corresponding patch.
}
@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,11 @@
package app.revanced.twitter.patches.hook.patch
import androidx.annotation.NonNull
interface Hook<T> {
/**
* Hook the given type.
* @param type The type to hook
*/
fun hook(@NonNull type: T): T
}

View File

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

View File

@ -0,0 +1,177 @@
package app.revanced.twitter.patches.hook.twifucker
import android.util.Log
import app.revanced.twitter.patches.hook.twifucker.TwiFuckerUtils.forEach
import app.revanced.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("revabced", "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("timeline_response")
return timeline?.optJSONArray("instructions")
}
private fun JSONObject.dataCheckAndRemove() {
dataGetInstructions()?.forEach { instruction ->
instruction.instructionCheckAndRemove()
}
}
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() {
instructionTimelineAddEntries()?.entriesRemoveAnnoyance()
instructionGetAddEntries()?.entriesRemoveAnnoyance()
}
// 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()
}
fun hideRecommendedUsers(json: JSONObject) {
json.jsonCheckAndRemoveRecommendedUsers()
}
fun hidePromotedAds(json: JSONObject) {
json.jsonGetInstructions()?.forEach { instruction ->
instruction.instructionCheckAndRemove()
}
json.jsonGetData()?.dataCheckAndRemove()
}
}

View File

@ -0,0 +1,22 @@
package app.revanced.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,13 @@
package app.revanced.twitter.utils.json
import app.revanced.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.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())
}
}