Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions app/src/main/java/me/iacn/biliroaming/BiliBiliPackage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex
val httpUrlClass by Weak { mHookInfo.okHttp.httpUrl.class_ from mClassLoader }
val preBuiltConfigClass by Weak { mHookInfo.preBuiltConfig.class_ from mClassLoader }
val dataSPClass by Weak { mHookInfo.dataSP.class_ from mClassLoader }
val fastjsonFieldAnnotation by Weak { "com.alibaba.fastjson.annotation.JSONField" from mClassLoader }
val gsonFieldAnnotation by Weak { "com.google.gson.annotations.SerializedName" from mClassLoader }
val pegasusParserClass by Weak { mHookInfo.pegasusParser from mClassLoader }

// for v8.17.0+
val useNewMossFunc = instance.viewMossClass?.declaredMethods?.any {
Expand Down Expand Up @@ -2310,6 +2313,22 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex
class_ = class_ { name = getDataSP.declaringClass.name }
get = method { name = getDataSP.name }
}
pegasusParser = class_ {
val getPegasusParser = dexHelper.findMethodUsingString(
"[Pegasus]PegasusParser",
false,
-1,
-1,
null,
-1,
null,
null,
null,
true
).asSequence().mapNotNull { dexHelper.decodeMethodIndex(it) }.firstOrNull()
?: return@class_
name = getPegasusParser.declaringClass.name
}
dexHelper.close()
}

Expand Down
193 changes: 114 additions & 79 deletions app/src/main/java/me/iacn/biliroaming/hook/PegasusHook.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package me.iacn.biliroaming.hook

import de.robv.android.xposed.XC_MethodHook
import me.iacn.biliroaming.BiliBiliPackage.Companion.instance
import me.iacn.biliroaming.utils.*
import me.iacn.biliroaming.utils.json.FastjsonHelper
import me.iacn.biliroaming.utils.json.GsonHelper
import me.iacn.biliroaming.utils.json.JsonHelper
import me.iacn.biliroaming.utils.json.getObjectAs
import me.iacn.biliroaming.utils.json.getObjectAsHelperOrNull
import me.iacn.biliroaming.utils.json.toJsonHelper

class PegasusHook(classLoader: ClassLoader) : BaseHook(classLoader) {
private val hidden = sPrefs.getBoolean("hidden", false)
Expand Down Expand Up @@ -106,10 +113,10 @@ class PegasusHook(classLoader: ClassLoader) : BaseHook(classLoader) {
} ?: -1L

// 屏蔽过低的播放数
private fun isLowCountVideo(obj: Any): Boolean {
private fun isLowCountVideo(obj: JsonHelper): Boolean {
if (hideLowPlayCountLimit == 0L) return false
val text = obj.runCatchingOrNull {
getObjectFieldAs<String?>("coverLeftText1")
getObjectAs<String?>("cover_left_text_1")
}.orEmpty()
return text.toPlayCount().let {
if (it == -1L) false
Expand All @@ -122,11 +129,11 @@ class PegasusHook(classLoader: ClassLoader) : BaseHook(classLoader) {
}

// 屏蔽指定播放时长
private fun durationVideo(obj: Any): Boolean {
private fun durationVideo(obj: JsonHelper): Boolean {
if (hideLongDurationLimit == 0 && hideShortDurationLimit == 0)
return false
val duration = obj.getObjectField("playerArgs")
?.getObjectFieldAs("fakeDuration") ?: 0
val duration = obj.getObjectAsHelperOrNull("player_args")
?.getObjectAs("duration") ?: 0
if (hideLongDurationLimit != 0 && duration > hideLongDurationLimit)
return true
return hideShortDurationLimit != 0 && duration < hideShortDurationLimit
Expand All @@ -139,10 +146,10 @@ class PegasusHook(classLoader: ClassLoader) : BaseHook(classLoader) {
return hideShortDurationLimit != 0 && duration < hideShortDurationLimit
}

private fun isContainsBlockKwd(obj: Any): Boolean {
private fun isContainsBlockKwd(obj: JsonHelper): Boolean {
// 屏蔽标题关键词
if (kwdFilterTitleList.isNotEmpty()) {
val title = obj.getObjectFieldAs<String?>("title").orEmpty()
val title = obj.getObjectAs<String?>("title").orEmpty()
if (kwdFilterTitleRegexMode && title.isNotEmpty()) {
if (kwdFilterTitleRegexes.any { title.contains(it) })
return true
Expand All @@ -154,17 +161,17 @@ class PegasusHook(classLoader: ClassLoader) : BaseHook(classLoader) {

// 屏蔽UID
if (kwdFilterUidList.isNotEmpty()) {
val uid = obj.getObjectField("args")?.getLongField("upId") ?: 0L
val uid = obj.getObjectAsHelperOrNull("args")?.getObjectAs<Long>("up_id") ?: 0L
if (uid != 0L && kwdFilterUidList.any { it == uid })
return true
}

// 屏蔽UP主
if (kwdFilterUpnameList.isNotEmpty()) {
val upname = if (obj.getObjectField("goTo") == "picture") {
obj.runCatchingOrNull { getObjectFieldAs<String?>("desc") }.orEmpty()
val upname = if (obj.getObjectAs<String?>("goto") == "picture") {
obj.runCatchingOrNull { getObjectAs<String?>("desc") }.orEmpty()
} else {
obj.getObjectField("args")?.getObjectFieldAs<String?>("upName").orEmpty()
obj.getObjectAsHelperOrNull("args")?.getObjectAs<String?>("up_name").orEmpty()
}
if (kwdFilterUpnameRegexMode && upname.isNotEmpty()) {
if (kwdFilterUpnameRegexes.any { upname.contains(it) })
Expand All @@ -177,24 +184,24 @@ class PegasusHook(classLoader: ClassLoader) : BaseHook(classLoader) {

// 屏蔽分区
if (kwdFilterRnameList.isNotEmpty()) {
val rname = obj.getObjectField("args")
?.getObjectFieldAs<String?>("rname").orEmpty()
val rname = obj.getObjectAsHelperOrNull("args")
?.getObjectAs<String?>("rname").orEmpty()
if (rname.isNotEmpty() && kwdFilterRnameList.any { rname.contains(it) })
return true
}

// 屏蔽频道
if (kwdFilterTnameList.isNotEmpty()) {
val tname = obj.getObjectField("args")
?.getObjectFieldAs<String?>("tname").orEmpty()
val tname = obj.getObjectAsHelperOrNull("args")
?.getObjectAs<String?>("tname").orEmpty()
if (tname.isNotEmpty() && kwdFilterTnameList.any { tname.contains(it) })
return true
}

// 屏蔽推荐关键词(可能不存在,必须放最后)
if (kwdFilterReasonList.isNotEmpty()) {
val reason = obj.runCatchingOrNull {
getObjectField("rcmdReason")?.getObjectFieldAs<String?>("text").orEmpty()
getObjectAsHelperOrNull("rcmd_reason")?.getObjectAs<String?>("text").orEmpty()
}.orEmpty()
if (kwdFilterReasonRegexMode && reason.isNotEmpty()) {
if (kwdFilterReasonRegexes.any { reason.contains(it) })
Expand Down Expand Up @@ -248,25 +255,26 @@ class PegasusHook(classLoader: ClassLoader) : BaseHook(classLoader) {
return false
}

private fun ArrayList<Any>.appendReasons() = forEach { item ->
val title = item.getObjectFieldAs<String?>("title").orEmpty()
private fun ArrayList<Any>.appendReasons(jsonHelper: Class<JsonHelper>) = this.forEach {
val item = it.toJsonHelper(jsonHelper)
val title = item.getObjectAs<String?>("title").orEmpty()
val rcmdReason = item.runCatchingOrNull {
getObjectField("rcmdReason")?.getObjectFieldAs<String?>("text")
getObjectAsHelperOrNull("rcmd_reason")?.getObjectAs<String?>("text")
}.orEmpty()
val args = item.getObjectField("args")
val upId = args?.getLongField("upId") ?: 0L
val upName = if (item.getObjectField("goTo") == "picture") {
item.runCatchingOrNull { getObjectFieldAs<String?>("desc") }.orEmpty()
val args = item.getObjectAsHelperOrNull("args")
val upId = args?.getObjectAs<Long>("up_id") ?: 0L
val upName = if (item.getObject("goto") == "picture") {
item.runCatchingOrNull { getObjectAs<String?>("desc") }.orEmpty()
} else {
args?.getObjectFieldAs<String?>("upName").orEmpty()
args?.getObjectAs<String?>("up_name").orEmpty()
}
val categoryName = args?.getObjectFieldAs<String?>("rname").orEmpty()
val channelName = args?.getObjectFieldAs<String?>("tname").orEmpty()
val treePoint = item.getObjectFieldAs<MutableList<Any>?>("threePoint")
val categoryName = args?.getObjectAs<String?>("rname").orEmpty()
val channelName = args?.getObjectAs<String?>("tname").orEmpty()
val treePoint = item.getObjectAs<MutableList<Any>?>("three_point_v2")
val reasons = mutableListOf<Any>()
instance.treePointItemClass?.new()?.apply {
setObjectField("title", "漫游屏蔽")
setObjectField("subtitle", "(本地屏蔽,重启生效,可前往首页推送过滤器查看)")
setObjectField("subtitle", "本地屏蔽,重启生效,可前往首页推送过滤器查看")
setObjectField("type", "dislike")
if (title.isNotEmpty()) {
instance.dislikeReasonClass?.new()?.apply {
Expand Down Expand Up @@ -507,29 +515,45 @@ class PegasusHook(classLoader: ClassLoader) : BaseHook(classLoader) {

override fun startHook() {
Log.d("startHook: Pegasus")
instance.pegasusFeedClass?.hookAfterMethod(
instance.pegasusFeed(),
instance.responseBodyClass
) { param ->
param.result ?: return@hookAfterMethod
val data = param.result.getObjectField("data")
data?.getObjectField("config")?.apply {

fun hookPegasusFeedConvert(json: JsonHelper) {
json.getObject("config")!!.apply {
disableAutoRefresh()
customSmallCoverWhRatio()
}
if (!hidden) return@hookAfterMethod
data?.getObjectFieldAs<ArrayList<Any>>("items")?.run {
if (!hidden) return
json.getObjectAs<ArrayList<Any>>("items").run {
removeAll {
val cardGoto = it.getObjectFieldAs<String?>("cardGoto").orEmpty()
val cardType = it.getObjectFieldAs<String?>("cardType").orEmpty()
val goto = it.getObjectFieldAs<String?>("goTo").orEmpty()
filter.any { item ->
item in cardGoto || item in cardType || item in goto
} || isLowCountVideo(it) || isContainsBlockKwd(it) || durationVideo(it)
val item = it.toJsonHelper(json.javaClass)
val cardGoto = item.getObjectAs<String?>("card_goto").orEmpty()
val cardType = item.getObjectAs<String?>("card_type").orEmpty()
val goto = item.getObjectAs<String?>("goto").orEmpty()
filter.any {
it in cardGoto || it in cardType || it in goto
} || isLowCountVideo(item) || isContainsBlockKwd(item) || durationVideo(item)
}
appendReasons()
appendReasons(json.javaClass)
}
}

instance.pegasusFeedClass?.hookAfterMethod(
"convert",
Object::class.java
) { param ->
val data = param.result?.getObjectField("data") ?: return@hookAfterMethod
val json = data.toJsonHelper<FastjsonHelper>()
hookPegasusFeedConvert(json)
}

instance.pegasusParserClass?.hookAfterMethod(
"convert",
Object::class.java
) { param ->
val data = param.result?.getObjectField("data") ?: return@hookAfterMethod
val json = data.toJsonHelper<GsonHelper>()
hookPegasusFeedConvert(json)
}

if (!hidden) return
fun MutableList<Any>.filter() = removeAll {
isPromoteRelate(it) || isNotAvRelate(it) || (applyToRelate && (isLowCountRelate(it)
Expand Down Expand Up @@ -650,50 +674,61 @@ class PegasusHook(classLoader: ClassLoader) : BaseHook(classLoader) {
}
}

instance.cardClickProcessorClass?.declaredMethods
?.find { it.name == instance.onFeedClicked() }?.hookBeforeMethod { param ->
val reason = param.args[2]
if (reason == null || reason.getLongField("id") !in blockReasonIds)
return@hookBeforeMethod
val id = reason.getLongField("id")
val name = reason.getObjectFieldAs<String?>("name").orEmpty()
val value = name.substringAfter(":")
when (id) {
REASON_ID_TITLE -> {
val validValue =
if (kwdFilterTitleRegexMode) Regex.escape(value) else value
sPrefs.appendStringForSet("home_filter_keywords_title", validValue)
}
fun hookDislikeReason(reason: Any?): Boolean {
if (reason == null || reason.getLongField("id") !in blockReasonIds)
return false
val id = reason.getLongField("id")
val name = reason.getObjectFieldAs<String?>("name").orEmpty()
val value = name.substringAfter(":")
when (id) {
REASON_ID_TITLE -> {
val validValue =
if (kwdFilterTitleRegexMode) Regex.escape(value) else value
sPrefs.appendStringForSet("home_filter_keywords_title", validValue)
}

REASON_ID_RCMD_REASON -> {
val validValue =
if (kwdFilterReasonRegexMode) Regex.escape(value) else value
sPrefs.appendStringForSet("home_filter_keywords_reason", validValue)
}
REASON_ID_RCMD_REASON -> {
val validValue =
if (kwdFilterReasonRegexMode) Regex.escape(value) else value
sPrefs.appendStringForSet("home_filter_keywords_reason", validValue)
}

REASON_ID_UP_ID -> {
sPrefs.appendStringForSet("home_filter_keywords_uid", value)
}
REASON_ID_UP_ID -> {
sPrefs.appendStringForSet("home_filter_keywords_uid", value)
}

REASON_ID_UP_NAME -> {
val validValue =
if (kwdFilterUpnameRegexMode) Regex.escape(value) else value
sPrefs.appendStringForSet("home_filter_keywords_up", validValue)
}
REASON_ID_UP_NAME -> {
val validValue =
if (kwdFilterUpnameRegexMode) Regex.escape(value) else value
sPrefs.appendStringForSet("home_filter_keywords_up", validValue)
}

REASON_ID_CATEGORY_NAME -> {
sPrefs.appendStringForSet("home_filter_keywords_category", value)
}
REASON_ID_CATEGORY_NAME -> {
sPrefs.appendStringForSet("home_filter_keywords_category", value)
}

REASON_ID_CHANNEL_NAME -> {
sPrefs.appendStringForSet("home_filter_keywords_channel", value)
}
REASON_ID_CHANNEL_NAME -> {
sPrefs.appendStringForSet("home_filter_keywords_channel", value)
}
Log.toast("添加成功", force = true)
param.result = null
}
Log.toast("添加成功", force = true)
return true
}

instance.cardClickProcessorClass?.declaredMethods
?.find { it.name == instance.onFeedClicked() }?.hookBeforeMethod { param ->
if (hookDislikeReason(param.args[2])) {
param.result = null
}
}

"com.bilibili.pegasus.ext.threepoint.ThreePointKt".findClass(mClassLoader)
.declaredMethods.find { it.parameterTypes.size == 8 }
?.hookBeforeMethod {
if (hookDislikeReason(it.args[3])) {
it.result = null
}
}

fun MutableList<Any>.filterPopular() = removeIf {
when (it.callMethod("getItemCase")?.toString()) {
Expand Down
10 changes: 9 additions & 1 deletion app/src/main/java/me/iacn/biliroaming/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import java.io.File
import java.io.IOException
import java.io.InputStream
import java.lang.ref.WeakReference
import java.lang.reflect.Field
import java.lang.reflect.Proxy
import java.math.BigInteger
import java.net.URL
Expand Down Expand Up @@ -444,7 +445,7 @@ fun Any.dumpToString(): String {
val sb = StringBuilder()
sb.append("---- ${this.javaClass.name}@${this.hashCode().toHexString()} dump begin ----\n")
fun dumpClassFields(clazz: Class<*>) {
clazz.fields.forEach { field ->
clazz.declaredFields.forEach { field ->
if (field.isStatic) return@forEach
field.isAccessible = true
val v = field.get(this)
Expand All @@ -458,3 +459,10 @@ fun Any.dumpToString(): String {
sb.append("---- ${this.javaClass.name}@${this.hashCode().toHexString()} dump end ----")
return sb.toString()
}

fun Class<*>.findField(filter: (Field) -> Boolean): Field? {
return declaredFields.find { field ->
field.isAccessible = true
filter(field)
} ?: superclass?.findField(filter)
}
24 changes: 24 additions & 0 deletions app/src/main/java/me/iacn/biliroaming/utils/json/FastjsonHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package me.iacn.biliroaming.utils.json

import me.iacn.biliroaming.BiliBiliPackage.Companion.instance
import me.iacn.biliroaming.utils.callMethod
import me.iacn.biliroaming.utils.findField
import java.lang.reflect.Field

class FastjsonHelper : JsonHelper {
private lateinit var _data: Any
override var data: Any
get() = _data
set(value) {
_data = value
}

override fun getField(key: String): Field {
val field = data.javaClass.findField{ field ->
val annotation = field.getAnnotation(instance.fastjsonFieldAnnotation as Class<Annotation>) ?: return@findField false
annotation.callMethod("name") == key
} ?: throw NoSuchFieldException("No field found for key: $key in ${data.javaClass.name} or its superclasses")
return field
}

}
Loading