From 428a4bdf0f591f7dafaafe0d085c4b4d3ab04b4c Mon Sep 17 00:00:00 2001 From: Diandd <87189302+Diandd@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:35:02 +0800 Subject: [PATCH] feat: purify ads and promotions in vertical video mode (#1670) --- .../java/me/iacn/biliroaming/SettingDialog.kt | 58 +++++++++++++++++ .../java/me/iacn/biliroaming/XposedInit.kt | 1 + .../biliroaming/hook/StoryPlayerAdHook.kt | 65 +++++++++++++++++++ app/src/main/res/values-zh-rTW/strings.xml | 4 +- app/src/main/res/values/strings.xml | 4 +- app/src/main/res/xml/prefs_setting.xml | 5 ++ 6 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/me/iacn/biliroaming/hook/StoryPlayerAdHook.kt diff --git a/app/src/main/java/me/iacn/biliroaming/SettingDialog.kt b/app/src/main/java/me/iacn/biliroaming/SettingDialog.kt index acb1d9805d..21a45fee5a 100644 --- a/app/src/main/java/me/iacn/biliroaming/SettingDialog.kt +++ b/app/src/main/java/me/iacn/biliroaming/SettingDialog.kt @@ -80,6 +80,7 @@ class SettingDialog(context: Context) : AlertDialog.Builder(context) { packageName + "_preferences", Context.MODE_MULTI_PROCESS ) + updatePurifyStoryVideoAdBlockedCount() if (!prefs.getBoolean("hidden", false)) { val hiddenGroup = findPreference("hidden_group") as PreferenceCategory preferenceScreen.removePreference(hiddenGroup) @@ -109,6 +110,7 @@ class SettingDialog(context: Context) : AlertDialog.Builder(context) { findPreference("filter_search")?.onPreferenceClickListener = this findPreference("filter_comment")?.onPreferenceClickListener = this findPreference("copy_access_key")?.onPreferenceClickListener = this + findPreference("purify_story_video_ad")?.onPreferenceClickListener = this checkCompatibleVersion() searchItems = retrieve(preferenceScreen) checkUpdate() @@ -866,6 +868,61 @@ class SettingDialog(context: Context) : AlertDialog.Builder(context) { return true } + private fun onPurifyStoryVideoAdClick(): Boolean { + AlertDialog.Builder(activity).apply { + val tagMap = linkedMapOf( + "ad" to "广告(推荐勾选)", + "short" to "短剧(推荐勾选)", + "shopping" to "购物(推荐勾选)", + "tv" to "电视剧(强烈不推荐勾选,此处仅为视频同款电视剧,勾选后将无法见带有此标签视频!", + "doc" to "纪录片(强烈不推荐勾选,此处仅为视频同款纪录片,勾选后将无法见带有此标签视频!)", + "ent" to "娱乐(强烈不推荐勾选,此处仅为视频同款内容,勾选后将无法见带有此标签视频!)", + "movie" to "电影(强烈不推荐勾选,此处仅为视频同款电影,勾选后将无法见带有此标签视频!)", + "music" to "音乐(强烈不推荐勾选,此处仅为视频同款音乐,勾选后将无法看见带有此标签视频!", + "topic" to "话题(强烈不推荐勾选,此处仅为视频相关话题,勾选后将无法看见带有此标签视频!", + ) + + val prefKey = "purify_story_video_ad_tags" + val oldPurifyAdTags = sPrefs.getStringSet(prefKey, emptySet()) ?: emptySet() + + + val keys = tagMap.keys.toList() + val values = tagMap.values.toTypedArray() + + val checkedTags = BooleanArray(keys.size) { index -> + keys[index] in oldPurifyAdTags + } + + + setTitle(context.getString(R.string.purify_story_video_ad_title)) + setPositiveButton(context.getString(android.R.string.ok)) { _, _ -> + val selected = mutableSetOf() + for (i in keys.indices) { + if (checkedTags[i]) selected.add(keys[i]) + } + sPrefs.edit().putStringSet(prefKey, selected).apply() + Toast.makeText(activity, "已保存净化选项,重启客户端生效", Toast.LENGTH_SHORT).show() + } + + setNegativeButton(android.R.string.cancel, null) + + setNeutralButton("重置") { _, _ -> + sPrefs.edit().remove(prefKey).apply() + } + + setMultiChoiceItems(values, checkedTags) { _, which, isChecked -> + checkedTags[which] = isChecked + } + }.show() + return true + } + + private fun updatePurifyStoryVideoAdBlockedCount() { + val blockedCount = sPrefs.getInt("purify_story_video_ad_blocked_count", 0) + findPreference("purify_story_video_ad")?.summary = getString(R.string.purify_story_video_ad_summary) + + "(累计拦截 $blockedCount 条)" + } + @Deprecated("Deprecated in Java") override fun onPreferenceClick(preference: Preference) = when (preference.key) { "version" -> onVersionClick() @@ -887,6 +944,7 @@ class SettingDialog(context: Context) : AlertDialog.Builder(context) { "filter_search" -> onFilterSearchClick() "filter_comment" -> onFilterCommentClick() "copy_access_key" -> onCopyAccessKeyClick() + "purify_story_video_ad" -> onPurifyStoryVideoAdClick() else -> false } } diff --git a/app/src/main/java/me/iacn/biliroaming/XposedInit.kt b/app/src/main/java/me/iacn/biliroaming/XposedInit.kt index a1ea0586a9..a863c99d4b 100644 --- a/app/src/main/java/me/iacn/biliroaming/XposedInit.kt +++ b/app/src/main/java/me/iacn/biliroaming/XposedInit.kt @@ -123,6 +123,7 @@ class XposedInit : IXposedHookLoadPackage, IXposedHookZygoteInit { startHook { SpeedHook(lpparam.classLoader) } startHook { MultiWindowHook(lpparam.classLoader) } startHook { LiveQualityHook(lpparam.classLoader) } + startHook { StoryPlayerAdHook(lpparam.classLoader) } } lpparam.processName.endsWith(":web") -> { diff --git a/app/src/main/java/me/iacn/biliroaming/hook/StoryPlayerAdHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/StoryPlayerAdHook.kt new file mode 100644 index 0000000000..32755ec402 --- /dev/null +++ b/app/src/main/java/me/iacn/biliroaming/hook/StoryPlayerAdHook.kt @@ -0,0 +1,65 @@ +package me.iacn.biliroaming.hook + +import me.iacn.biliroaming.utils.Log +import me.iacn.biliroaming.utils.from +import me.iacn.biliroaming.utils.hookBeforeMethod +import me.iacn.biliroaming.utils.sPrefs + +class StoryPlayerAdHook(classLoader: ClassLoader) : BaseHook(classLoader) { + override fun startHook() { + + + val purifyTags = sPrefs.getStringSet("purify_story_video_ad_tags", emptySet()) ?: emptySet() + if (purifyTags.isEmpty()) return + + Log.d("startHook: StoryPlayerAd, purifyTags: ${purifyTags}") + + "com.bilibili.video.story.player.StoryPagerPlayer".from(mClassLoader) + ?.hookBeforeMethod("n1", List::class.java) { params -> + val storyDetailList = params.args[0] as? MutableList<*> ?: return@hookBeforeMethod + val toRemove = mutableListOf() + + storyDetailList.forEach { + val storyDetail = it!!::class.java + val getCartIconInfoMethod = storyDetail.getDeclaredMethod("getCartIconInfo") + val isAdMethod = storyDetail.getDeclaredMethod("isAd") + getCartIconInfoMethod.isAccessible = true + isAdMethod.isAccessible = true + + val isAd = isAdMethod.invoke(it) as? Boolean + + var cartInfoText: String? = null + val cartIconInfo = getCartIconInfoMethod.invoke(it) + if (cartIconInfo != null) { + val cartClass = cartIconInfo.javaClass + val getEntryTextMethod = cartClass.getDeclaredMethod("getEntryText") + getEntryTextMethod.isAccessible = true + cartInfoText = getEntryTextMethod.invoke(cartIconInfo) as? String + } + + val shouldRemove = when { + "ad" in purifyTags && isAd == true -> true + "short" in purifyTags && cartInfoText == "短剧" -> true + "shopping" in purifyTags && cartInfoText == "购物" -> true + "tv" in purifyTags && cartInfoText == "电视剧" -> true + "doc" in purifyTags && cartInfoText == "纪录片" -> true + "ent" in purifyTags && cartInfoText == "娱乐" -> true + "movie" in purifyTags && cartInfoText == "电影" -> true + "music" in purifyTags && cartInfoText == "音乐" -> true + "topic" in purifyTags && cartInfoText == "话题" -> true + else -> false + } + if (shouldRemove) { + toRemove.add(it) + } + } + + storyDetailList.removeAll(toRemove.toSet()) + val blockedCount = sPrefs.getInt("purify_story_video_ad_blocked_count", 0) + sPrefs.edit().putInt("purify_story_video_ad_blocked_count", blockedCount + toRemove.size).apply() + } + + } + + +} diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 52afb69370..e0e9de232c 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -282,5 +282,7 @@ 自訂首頁推薦小卡(雙列顯示的)封面比例 搜尋結果移除推廣 跳過視頻激勵廣告 - 受不了了!我要看個爽!(需重啟兩次嗶哩嗶哩) + 有了這個,終於能過第二關了😋(需重啟兩次嗶哩嗶哩) + 淨化豎屏模式廣告/推廣 + 有了這個,連第一關都看不到了😭 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f4c5280aa6..1c2aefc924 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -307,5 +307,7 @@ 隐藏直播模糊遮罩 隐藏部分直播的模糊遮罩 跳过视频激励广告 - 受不了了!我要看个爽!(需重启两次哔哩哔哩) + 有了这个,终于能过第二关了😋(需重启两次哔哩哔哩) + 净化竖屏模式广告/推广 + 有了这个,连第一关都看不到了😭 diff --git a/app/src/main/res/xml/prefs_setting.xml b/app/src/main/res/xml/prefs_setting.xml index 1e99a9a2aa..366d22255d 100644 --- a/app/src/main/res/xml/prefs_setting.xml +++ b/app/src/main/res/xml/prefs_setting.xml @@ -318,6 +318,11 @@ android:key="skip_reward_ad" android:summary="@string/skip_reward_ad_summary" android:title="@string/skip_reward_ad_title" /> +