From fe00527ea3372103a7c6933e6c82fa59148708fc Mon Sep 17 00:00:00 2001 From: sve1r Date: Wed, 12 Apr 2023 15:11:14 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20=20=E6=9B=B4=E6=96=B0=20?= =?UTF-8?q?=E7=9F=A5=E4=B9=8E=E9=83=A8=E5=88=86=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Rewrite/AdBlock/Zhihu.adblock | 39 +- Rewrite/AdBlock/Zhihu.conf | 155 +- Scripts/AdBlock/Zhihu/OpenAds.js | 5 + Scripts/AdBlock/Zhihu/Zhihu.js | 2746 ++++++++++++++++------------ Scripts/AdBlock/Zhihu/Zhihu.min.js | 1 + Scripts/AdBlock/Zhihu/Zhihu_dep.js | 1794 ++++++++++++++++++ 6 files changed, 3456 insertions(+), 1284 deletions(-) create mode 100644 Scripts/AdBlock/Zhihu/OpenAds.js create mode 100644 Scripts/AdBlock/Zhihu/Zhihu.min.js create mode 100644 Scripts/AdBlock/Zhihu/Zhihu_dep.js diff --git a/Rewrite/AdBlock/Zhihu.adblock b/Rewrite/AdBlock/Zhihu.adblock index 9bcd2a4..78267ea 100644 --- a/Rewrite/AdBlock/Zhihu.adblock +++ b/Rewrite/AdBlock/Zhihu.adblock @@ -1,7 +1,5 @@ - hostname = 118.89.204.198,103.41.167.237,2402:4e00:1200:ed00:0:9089:6dac:96b6,www.zhihu.com,api.zhihu.com,zhuanlan.zhihu.com,appcloud2.zhihu.com,m-cloud.zhihu.com,103.41.167.236,103.41.167.234,103.41.167.235,103.41.167.226 -# 拦截DNS解析 ^https?:\/\/118\.89\.204\.198 url reject-dict ^https?:\/\/103\.41\.167\.237 url reject-dict ^https?:\/\/2402:4e00:1200:ed00:0:9089:6dac:96b6 url reject-200 @@ -49,56 +47,57 @@ hostname = 118.89.204.198,103.41.167.237,2402:4e00:1200:ed00:0:9089:6dac:96b6,ww # 暂不清楚作用 ^https?:\/\/api\.zhihu\.com\/commercial_api\/banners_v3\/app_topstory_banner url reject-dict +^https?:\/\/api\.zhihu\.com\/ad-style-service\/request url reject # 获取用户信息 - 隔离用户数据,开启本地会员等 -^https?:\/\/api\.zhihu\.com\/people\/ url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/people\/ url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 关键词屏蔽 - 解锁本地的关键词屏蔽功能,需要开启本地VIP -^https?:\/\/api\.zhihu\.com\/feed-root\/block url script-analyze-echo-response https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/feed-root\/block url script-analyze-echo-response https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 优化软件配置 - 优化下发的配置文件来实现某些效果 -^https?:\/\/m-cloud\.zhihu\.com\/api\/cloud\/config\/all\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -^https?:\/\/appcloud2\.zhihu\.com\/v\d+\/config url script-analyze-echo-response https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/m-cloud\.zhihu\.com\/api\/cloud\/config\/all\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js +^https?:\/\/appcloud2\.zhihu\.com\/v\d+\/config url script-analyze-echo-response https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 用户信息 - 修改用户盐值 -^https?:\/\/api\.zhihu\.com\/user-credit\/basis url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/user-credit\/basis url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 关注页 - 内容优化及屏蔽转发的黑名单用户想法 -^https?:\/\/api\.zhihu\.com\/moments_v3\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/moments_v3\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 推荐页 - 移除黑名单用户发布的文章、去除广告,及自定义一些屏蔽项目 -^https:\/\/api\.zhihu\.com\/topstory\/recommend url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https:\/\/api\.zhihu\.com\/topstory\/recommend url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 黑名单页 - 同步黑名单数据 -^https?:\/\/api\.zhihu\.com\/settings\/blocked_users url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/settings\/blocked_users url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 问题的回答列表 - 移除黑名单用户的回答、去除广告 -^https?:\/\/api\.zhihu\.com\/(v4\/)?questions\/\d+ url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/(v4\/)?questions\/\d+ url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 回答信息流 - 移除黑名单用户的回答、去除广告 -^https?:\/\/api\.zhihu\.com\/next-data url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/next-data url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 回答页底部评论摘要 - 移除黑名单用户发表的评论 -^https?:\/\/www\.zhihu\.com\/api\/v4\/comment_v5\/answers\/\d+\/abstract_comment\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/www\.zhihu\.com\/api\/v4\/comment_v5\/answers\/\d+\/abstract_comment\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 评论页及子页面 - 去除黑名单用户发表的评论 -^https?:\/\/api\.zhihu\.com\/(comment_v5\/)?(answers|comments?|articles|pins)\/\d+\/(root_|child_)?comments? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/(comment_v5\/)?(answers|comments?|articles|pins)\/\d+\/(root_|child_)?comments? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 消息页 - 折叠官方消息、屏蔽营销消息 -^https?:\/\/api\.zhihu\.com\/notifications\/v3\/(message|timeline\/entry\/system_message) url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/notifications\/v3\/(message|timeline\/entry\/system_message) url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 回答页 - 内容优化,付费、营销、推广内容文首提醒 -^https?:\/\/www\.zhihu\.com\/appview\/v2\/answer\/.*(entry=(?!(preload-topstory|preload-search|preload-subscription)))? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/www\.zhihu\.com\/appview\/v2\/answer\/.*(entry=(?!(preload-topstory|preload-search|preload-subscription)))? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 回答页 - 屏蔽下翻出现的黑名单用户的回答 -^https?:\/\/api\.zhihu\.com\/next\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/next\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 文章页 - 去除底部广告 -^https?:\/\/www\.zhihu\.com\/api\/v\d\/articles\/\d+\/recommendation\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/www\.zhihu\.com\/api\/v\d\/articles\/\d+\/recommendation\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 热榜页 - 去除广告 -^https?:\/\/api\.zhihu\.com\/topstory\/hot-lists?(\?|\/) url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/topstory\/hot-lists?(\?|\/) url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js # 搜索页 - 去除预置广告 -^https?:\/\/api\.zhihu\.com\/search\/preset_words\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js +^https?:\/\/api\.zhihu\.com\/search\/preset_words\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js diff --git a/Rewrite/AdBlock/Zhihu.conf b/Rewrite/AdBlock/Zhihu.conf index b40efb3..af8eb33 100644 --- a/Rewrite/AdBlock/Zhihu.conf +++ b/Rewrite/AdBlock/Zhihu.conf @@ -1,59 +1,106 @@ -# 脚本来自: https://github.com/blackmatrix7/ios_rule_script/ +# 哲也同学 +# 先问是不是再问为什么 -hostname = www.zhihu.com,api.zhihu.com,zhuanlan.zhihu.com,appcloud2.zhihu.com,103.41.167.236,103.41.167.234,103.41.167.235,103.41.167.226 +hostname = 118.89.204.198,103.41.167.237,2402:4e00:1200:ed00:0:9089:6dac:96b6,www.zhihu.com,api.zhihu.com,zhuanlan.zhihu.com,appcloud2.zhihu.com,m-cloud.zhihu.com,103.41.167.236,103.41.167.234,103.41.167.235,103.41.167.226 -# 知乎处理用户信息 -^https?:\/\/api\.zhihu\.com\/people\/ url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎信息流去广告 -^https?:\/\/api\.zhihu\.com\/(moments|topstory)(\/|\?)?(recommend|action=|feed_type=)(?!\/people) url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎回答列表去广告 -^https?:\/\/api\.zhihu\.com\/(v4\/)?questions\/\d+ url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎获取黑名单 -^https?:\/\/api\.zhihu\.com\/settings\/blocked_users url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎官方消息去广告 -^https?:\/\/api\.zhihu\.com\/notifications\/v3\/(message|timeline\/entry\/system_message) url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎预置关键词去广告 -^https?:\/\/api\.zhihu\.com\/search\/preset_words\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 优化软件配置 -^https?:\/\/appcloud2\.zhihu\.com\/v\d+\/config url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎热搜去广告 -^https?:\/\/api\.zhihu\.com\/search\/top_search\/tabs\/hot\/items url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎热榜去广告 -^https?:\/\/api\.zhihu\.com\/topstory\/hot-lists?(\?|\/) url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎评论去广告 -^https?:\/\/api\.zhihu\.com\/(comment_v5\/)?(answers|comments?|articles|pins)\/\d+\/(root_|child_)?comments? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎回答列表去广告 -^https?:\/\/www\.zhihu\.com\/appview\/v2\/answer\/.*(entry=(?!(preload-topstory|preload-search|preload-subscription)))? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎屏蔽关键词解锁 -^https?:\/\/api\.zhihu\.com\/feed-root\/block url script-analyze-echo-response https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 知乎8.3.0移除推荐页顶部项 -^https?:\/\/api\.zhihu\.com\/feed-root\/sections\/query\/v2 url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.js -# 拦截知乎开屏广告 -URL-REGEX,^https?:\/\/api\.zhihu\.com\/commercial_api\/launch_v2\? url reject-dict -# 知乎去除最常访问 -^https?:\/\/api\.zhihu\.com\/moments\/recent url reject-dict -# 知乎去除回答下的广告 -^https?:\/\/www\.zhihu\.com\/api\/v4\/answers\/\d+\/recommendations url reject-dict -# 知乎其他广告拦截 -^https?:\/\/api\.zhihu\.com\/(notifications\/v\d\/count|v\d\/package|me\/guides|drama\/living-info|ad|fringe|commercial|market\/popovers|.*featured-comment-ad|ad-style-service) url reject-dict -# 知乎拦截部分预加载 -# ^https?:\/\/www\.zhihu\.com\/appview\/(p|v2\/answer|zvideo)\/.*entry=(preload-topstory|preload-search|preload-subscription) url reject-dict -# 知乎网页版去广告 -^https?:\/\/www\.zhihu\.com\/api\/v4/(questions|anwsers)\/\d+/related-readings url reject-dict -^https?:\/\/www\.zhihu\.com\/api\/v4\/hot_recommendation url reject-dict -^https?:\/\/www\.zhihu\.com\/commercial_api\/banners_v3\/mobile_banner url reject-dict -^https?:\/\/zhuanlan\.zhihu\.com\/api\/articles\/\d+\/recommendation url reject-dict -# 知乎品牌提问广告 +^https?:\/\/118\.89\.204\.198 url reject-dict +^https?:\/\/103\.41\.167\.237 url reject-dict +^https?:\/\/2402:4e00:1200:ed00:0:9089:6dac:96b6 url reject-200 + +# 屏蔽下发的配置,如皮肤等 +^https?:\/\/api\.zhihu\.com\/ab\/api\/v1\/products\/zhihu\/platforms\/ios\/config url reject + +# 屏蔽我的页面开通会员的卡片 +# ^https?:\/\/api\.zhihu\.com\/unlimited\/go\/my_card url reject + +# 拦截开屏广告 +^https?:\/\/api\.zhihu\.com\/commercial_api\/launch_v2\? url reject-dict +^https?:\/\/api\.zhihu\.com\/commercial_api\/real_time_launch_v2\? url reject-dict + +# 拦截品牌提问广告 ^https?:\/\/api\.zhihu\.com\/brand\/question\/\d+/card\? url reject-dict ^https?:\/\/www\.zhihu\.com\/api\/v\d+\/brand\/question/\d+/card\? url reject-dict -# 屏蔽消息页面上拉的用户精选 -^https?:\/\/api\.zhihu\.com/moments/hybrid\? url reject-dict -# 知乎下发的配置,如皮肤等 -^https?:\/\/api\.zhihu\.com\/ab\/api\/v1\/products\/zhihu\/platforms\/ios\/config url reject-200 -# 屏蔽知乎“我的”页面推荐开通会员的卡片 -^https?:\/\/api\.zhihu\.com\/people\/self\/new_user_card url reject-200 -# 知乎去除Tab页关注人头像 -^https?:\/\/api\.zhihu\.com\/moments\/tab_v2 url reject-dict -# 屏蔽知乎8.3.0版本首页顶部的视频角标 -^https?:\/\/api\.zhihu\.com\/explore\/entry\/tips url reject + +# 去除底部标签页关注人角标 +^https?:\/\/api\.zhihu\.com\/moments\/tab_v2 url reject-dict + +# 去除消息通知角标 +^https?:\/\/api\.zhihu\.com\/(notifications\/v\d\/count) url reject-dict + +# 拦截回答下的卡片广告 +^https?:\/\/www\.zhihu\.com\/api\/v\d\/answers\/\d+\/recommendations url reject-dict + +# 拦截应用内弹窗 +^https?:\/\/api\.zhihu\.com\/me\/guides url reject-dict + +# 去除关注页最常访问 +^https?:\/\/api\.zhihu\.com\/moments\/recent url reject-dict + +# 拦截推荐页顶部广告 +^https?:\/\/api\.zhihu\.com\/api\/v4\/ecom_data\/config url reject-dict + +# 底栏加号的广告 +^https?:\/\/api\.zhihu\.com\/content-distribution-core\/bubble\/common\/settings url reject-dict + +# 推荐页搜索栏左侧图标 +^https?:\/\/api\.zhihu\.com\/feed\/render\/revisit\/current_reading url reject-dict + +# 疑似推荐页内容更新红点 +^https?:\/\/api\.zhihu\.com\/feed\/render\/revisit\/tag_config url reject-dict + +# 暂不清楚作用 +^https?:\/\/api\.zhihu\.com\/commercial_api\/banners_v3\/app_topstory_banner url reject-dict +^https?:\/\/api\.zhihu\.com\/ad-style-service\/request url reject + +# 获取用户信息 - 隔离用户数据,开启本地会员等 +^https?:\/\/api\.zhihu\.com\/people\/ url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 关键词屏蔽 - 解锁本地的关键词屏蔽功能,需要开启本地VIP +^https?:\/\/api\.zhihu\.com\/feed-root\/block url script-analyze-echo-response https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 优化软件配置 - 优化下发的配置文件来实现某些效果 +^https?:\/\/m-cloud\.zhihu\.com\/api\/cloud\/config\/all\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js +^https?:\/\/appcloud2\.zhihu\.com\/v\d+\/config url script-analyze-echo-response https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 用户信息 - 修改用户盐值 +^https?:\/\/api\.zhihu\.com\/user-credit\/basis url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 关注页 - 内容优化及屏蔽转发的黑名单用户想法 +^https?:\/\/api\.zhihu\.com\/moments_v3\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 推荐页 - 移除黑名单用户发布的文章、去除广告,及自定义一些屏蔽项目 +^https:\/\/api\.zhihu\.com\/topstory\/recommend url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 黑名单页 - 同步黑名单数据 +^https?:\/\/api\.zhihu\.com\/settings\/blocked_users url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 问题的回答列表 - 移除黑名单用户的回答、去除广告 +^https?:\/\/api\.zhihu\.com\/(v4\/)?questions\/\d+ url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 回答信息流 - 移除黑名单用户的回答、去除广告 +^https?:\/\/api\.zhihu\.com\/next-data url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 回答页底部评论摘要 - 移除黑名单用户发表的评论 +^https?:\/\/www\.zhihu\.com\/api\/v4\/comment_v5\/answers\/\d+\/abstract_comment\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 评论页及子页面 - 去除黑名单用户发表的评论 +^https?:\/\/api\.zhihu\.com\/(comment_v5\/)?(answers|comments?|articles|pins)\/\d+\/(root_|child_)?comments? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 消息页 - 折叠官方消息、屏蔽营销消息 +^https?:\/\/api\.zhihu\.com\/notifications\/v3\/(message|timeline\/entry\/system_message) url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 回答页 - 内容优化,付费、营销、推广内容文首提醒 +^https?:\/\/www\.zhihu\.com\/appview\/v2\/answer\/.*(entry=(?!(preload-topstory|preload-search|preload-subscription)))? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 回答页 - 屏蔽下翻出现的黑名单用户的回答 +^https?:\/\/api\.zhihu\.com\/next\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 文章页 - 去除底部广告 +^https?:\/\/www\.zhihu\.com\/api\/v\d\/articles\/\d+\/recommendation\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 热榜页 - 去除广告 +^https?:\/\/api\.zhihu\.com\/topstory\/hot-lists?(\?|\/) url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js + +# 搜索页 - 去除预置广告 +^https?:\/\/api\.zhihu\.com\/search\/preset_words\? url script-response-body https://cdn.jsdelivr.net/gh/sve1r/Rules-For-Quantumult-X@develop/Scripts/AdBlock/Zhihu/Zhihu.min.js diff --git a/Scripts/AdBlock/Zhihu/OpenAds.js b/Scripts/AdBlock/Zhihu/OpenAds.js new file mode 100644 index 0000000..adf812d --- /dev/null +++ b/Scripts/AdBlock/Zhihu/OpenAds.js @@ -0,0 +1,5 @@ + +var body = $response.body + .replace(/img_play_duration\\":\d+/g, 'img_play_duration":0') + .replace(/launch_timeout\\":\d+/g, 'launch_timeout":0'); +$done({ body }); diff --git a/Scripts/AdBlock/Zhihu/Zhihu.js b/Scripts/AdBlock/Zhihu/Zhihu.js index 16da55c..99e2868 100644 --- a/Scripts/AdBlock/Zhihu/Zhihu.js +++ b/Scripts/AdBlock/Zhihu/Zhihu.js @@ -3,130 +3,103 @@ const blockedUsersKey = "zhihu_blocked_users"; const currentUserInfoKey = "zhihu_current_userinfo"; const keywordBlockKey = "zhihu_keyword_block"; const blackAnswersIdKey = "zhihu_black_answers"; -const userCreditScore = "zhihu_credit_score"; -// 默认屏蔽推荐列表的用户,通常不是真实用户,无法通过加入黑名单屏蔽 +const userCreditScoreKey = "zhihu_credit_score"; +const zheyeServerKey = "zheye_server_url"; const defaultAnswerBlockedUsers = ["会员推荐", "盐选推荐"]; -const keywordMaxCount = 1000; // 允许设置的关键词数量 +const keywordMaxCount = 1e3; const $ = MagicJS(scriptName, "INFO"); - -/** - * @description: 获取用户信息 - * @return {*} - */ function getUserInfo() { - let defaultUserInfo = { id: "default", is_vip: false }; + let t = { id: "default", is_vip: false }; try { - let userInfo = $.data.read(currentUserInfoKey); - if (typeof userInfo === "string") userInfo = JSON.parse(userInfo); - if (!!userInfo && userInfo.hasOwnProperty("id")) { - return userInfo; + let e = $.data.read(currentUserInfoKey); + if (typeof e === "string") e = JSON.parse(e); + if (!!e && e.hasOwnProperty("id")) { + return e; } else { - return defaultUserInfo; + return t; } - } catch (err) { - $.logger.error(`获取用户信息出现异常:${err}`); - return defaultUserInfo; + } catch (e) { + $.logger.error(`获取用户信息出现异常:${e}`); + return t; } } - -/** - * 优化软件配置 - * @return {*} - */ function modifyAppConfig() { - let response = null; + let t = null; try { if (!!$.response.body) { - let obj = JSON.parse($.response.body); - obj["config"]["homepage_feed_tab"]["tab_infos"] = obj["config"][ + let e = JSON.parse($.response.body); + e["config"]["homepage_feed_tab"]["tab_infos"] = e["config"][ "homepage_feed_tab" ]["tab_infos"].filter((e) => { - // 将活动标签设置为已过期 if (e["tab_type"] === "activity_tab") { - e["end_time"] = (new Date() - 120000).toString().slice(0, 10); + e["end_time"] = (new Date() - 12e4).toString().slice(0, 10); return true; } else { return false; } }); - obj["config"]["zvideo_max_number"] = 1; - // 似乎是控制内部弹窗 - obj["config"]["is_show_followguide_alert"] = false; - // 似乎是某个地方的标签,待定 - delete obj["config"]["hp_channel_tab"]; - // 灰色模式 - if (obj["config"]["zombie_conf"]) { - obj["config"]["zombie_conf"]["zombieEnable"] = false; + e["config"]["zvideo_max_number"] = 1; + e["config"]["is_show_followguide_alert"] = false; + delete e["config"]["hp_channel_tab"]; + if (e["config"]["zombie_conf"]) { + e["config"]["zombie_conf"]["zombieEnable"] = false; } - if (obj["config"]["gray_mode"]) { - obj["config"]["gray_mode"]["enable"] = false; - obj["config"]["gray_mode"]["start_time"] = "4092566400"; - obj["config"]["gray_mode"]["end_time"] = "4092566400"; + if (e["config"]["gray_mode"]) { + e["config"]["gray_mode"]["enable"] = false; + e["config"]["gray_mode"]["start_time"] = "4092566400"; + e["config"]["gray_mode"]["end_time"] = "4092566400"; } - // 屏蔽8.X版本以上本地DNS解析,以下修改不清楚哪些是有效的,暂时全部保留 - if (obj["config"].hasOwnProperty("zhcnh_thread_sync")) { - $.logger.debug(JSON.stringify(obj["config"]["zhcnh_thread_sync"])); - obj["config"]["zhcnh_thread_sync"]["LocalDNSSetHostWhiteList"] = []; - obj["config"]["zhcnh_thread_sync"]["isOpenLocalDNS"] = "0"; - obj["config"]["zhcnh_thread_sync"]["ZHBackUpIP_Switch_Open"] = "0"; - obj["config"]["zhcnh_thread_sync"]["dns_ip_detector_operation_lock"] = + if (e["config"].hasOwnProperty("zhcnh_thread_sync")) { + $.logger.debug(JSON.stringify(e["config"]["zhcnh_thread_sync"])); + e["config"]["zhcnh_thread_sync"]["LocalDNSSetHostWhiteList"] = []; + e["config"]["zhcnh_thread_sync"]["isOpenLocalDNS"] = "0"; + e["config"]["zhcnh_thread_sync"]["ZHBackUpIP_Switch_Open"] = "0"; + e["config"]["zhcnh_thread_sync"]["dns_ip_detector_operation_lock"] = "1"; - obj["config"]["zhcnh_thread_sync"][ + e["config"]["zhcnh_thread_sync"][ "ZHHTTPSessionManager_setupZHHTTPHeaderField" ] = "1"; } - response = { body: JSON.stringify(obj) }; + t = { body: JSON.stringify(e) }; } - } catch (err) { - $.logger.error(`优化软件配置出现异常:${err}`); + } catch (e) { + $.logger.error(`优化软件配置出现异常:${e}`); } - return response; + return t; } - -/** - * 修改云端下发的配置 - * @return {*} - */ function modifyMCloudConfig() { - let response = null; + let t = null; try { if (!!$.response.body) { - let obj = JSON.parse($.response.body); - if (obj.data && obj.data["configs"]) { - // 去除灰色主题 - obj.data["configs"].forEach((element) => { - if (element["configKey"] === "feed_gray_theme") { - element["configValue"].start_time = "1669824000"; - element["configValue"].end_time = "1669824001"; - element.status = false; + let e = JSON.parse($.response.body); + if (e.data && e.data["configs"]) { + e.data["configs"].forEach((e) => { + if (e["configKey"] === "feed_gray_theme") { + e["configValue"].start_time = "1669824000"; + e["configValue"].end_time = "1669824001"; + e.status = false; } }); } - const body = JSON.stringify(obj); - $.logger.debug(body); - response = { body: body }; + const r = JSON.stringify(e); + $.logger.debug(r); + t = { body: r }; } - } catch (err) { - $.logger.error(`优化软件配置出现异常:${err}`); + } catch (e) { + $.logger.error(`优化软件配置出现异常:${e}`); } - return response; + return t; } - -/** - * 屏蔽关键词解锁 - * @return {*} - */ function unlockBlockedKeywords() { - let response = null; + let i = null; try { - const userInfo = getUserInfo(); - // 获取屏蔽关键词列表 + const s = getUserInfo(); if ($.request.method === "GET") { - let keywords = $.data.read(keywordBlockKey, null, userInfo.id); - if (!keywords) { - keywords = []; + let e = $.data.read(keywordBlockKey, null, s.id); + if (!e) { + e = []; } - let headers = { + let t = { "Cache-Control": "no-cache, no-store, must-revalidate, private, max-age=0", Connection: "keep-alive", @@ -138,27 +111,23 @@ function unlockBlockedKeywords() { "X-Cache-Lookup": "Cache Miss", "x-cdn-provider": "tencent", }; - let body = JSON.stringify({ + let r = JSON.stringify({ success: true, is_vip: true, kw_min_length: 2, kw_max_length: 100, kw_max_count: keywordMaxCount, - data: keywords, + data: e, }); if ($.env.isQuanX) { - response = { body: body, headers: headers, status: "HTTP/1.1 200 OK" }; + i = { body: r, headers: t, status: "HTTP/1.1 200 OK" }; } else { - response = { response: { body: body, headers: headers, status: 200 } }; + i = { response: { body: r, headers: t, status: 200 } }; } - $.logger.debug(`获取本地脚本屏蔽关键词:\n${keywords.join("、")}`); - } - - // 添加屏蔽关键词 - else if ($.request.method === "POST") { + $.logger.debug(`获取本地脚本屏蔽关键词:\n${e.join("、")}`); + } else if ($.request.method === "POST") { if (!!$.request.body) { - // 构造 response headers - let headers = { + let t = { "Cache-Control": "no-cache, no-store, must-revalidate, private, max-age=0", Connection: "keep-alive", @@ -170,73 +139,50 @@ function unlockBlockedKeywords() { "X-Cache-Lookup": "Cache Miss", "x-cdn-provider": "tencent", }; - // 读取关键词 - let keyword = decodeURIComponent($.request.body).match( - /keyword=(.*)/ - )[1]; - let keywords = $.data.read(keywordBlockKey, null, userInfo.id); - if (!keywords) { - keywords = []; + let r = decodeURIComponent($.request.body).match(/keyword=(.*)/)[1]; + let n = $.data.read(keywordBlockKey, null, s.id); + if (!n) { + n = []; } - // 判断关键词是否存在 - let keywordExists = false; - for (let i = 0; i < keywords.length; i++) { - if (keyword === keywords[i]) { - keywordExists = true; + let o = false; + for (let e = 0; e < n.length; e++) { + if (r === n[e]) { + o = true; break; } } - // 不存在添加,存在返回异常 - if (keywordExists === false) { - keywords.push(keyword); - $.data.write(keywordBlockKey, keywords, userInfo.id); - let body = JSON.stringify({ success: true }); + if (o === false) { + n.push(r); + $.data.write(keywordBlockKey, n, s.id); + let e = JSON.stringify({ success: true }); if ($.env.isQuanX) { - response = { - body: body, - headers: headers, - status: "HTTP/1.1 200 OK", - }; + i = { body: e, headers: t, status: "HTTP/1.1 200 OK" }; } else { - response = { - response: { body: body, headers: headers, status: 200 }, - }; + i = { response: { body: e, headers: t, status: 200 } }; } - $.logger.debug(`添加本地脚本屏蔽关键词“${keyword}”`); + $.logger.debug(`添加本地脚本屏蔽关键词“${r}”`); } else { - let body = JSON.stringify({ - error: { - message: "关键词已存在", - code: 100002, - }, + let e = JSON.stringify({ + error: { message: "关键词已存在", code: 100002 }, }); if ($.env.isQuanX) { - response = { - body: body, - headers: headers, - status: "HTTP/1.1 400 Bad Request", - }; + i = { body: e, headers: t, status: "HTTP/1.1 400 Bad Request" }; } else { - response = { - response: { body: body, headers: headers, status: 400 }, - }; + i = { response: { body: e, headers: t, status: 400 } }; } } } - } - - // 删除屏蔽关键词 - else if ($.request.method === "DELETE") { - let keyword = decodeURIComponent($.request.url).match(/keyword=(.*)/)[1]; - let keywords = $.data.read(keywordBlockKey, null, userInfo.id); - if (!keywords) { - keywords = []; + } else if ($.request.method === "DELETE") { + let t = decodeURIComponent($.request.url).match(/keyword=(.*)/)[1]; + let e = $.data.read(keywordBlockKey, null, s.id); + if (!e) { + e = []; } - keywords = keywords.filter((e) => { - return e !== keyword; + e = e.filter((e) => { + return e !== t; }); - $.data.write(keywordBlockKey, keywords, userInfo.id); - let headers = { + $.data.write(keywordBlockKey, e, s.id); + let r = { "Cache-Control": "no-cache, no-store, must-revalidate, private, max-age=0", Connection: "keep-alive", @@ -248,65 +194,58 @@ function unlockBlockedKeywords() { "X-Cache-Lookup": "Cache Miss", "x-cdn-provider": "tencent", }; - let body = JSON.stringify({ success: true }); + let n = JSON.stringify({ success: true }); if ($.env.isQuanX) { - response = { body: body, headers: headers, status: "HTTP/1.1 200 OK" }; + i = { body: n, headers: r, status: "HTTP/1.1 200 OK" }; } else { - response = { response: { body: body, headers: headers, status: 200 } }; + i = { response: { body: n, headers: r, status: 200 } }; } - $.logger.debug(`删除本地脚本屏蔽关键词:“${keyword}”`); + $.logger.debug(`删除本地脚本屏蔽关键词:“${t}”`); } - } catch (err) { - $.logger.debug(`关键词屏蔽操作出现异常:${err}`); + } catch (e) { + $.logger.debug(`关键词屏蔽操作出现异常:${e}`); } - return response; + return i; } - -/** - * 处理登录用户信息 - * - * @return {*} - */ function processUserInfo() { - let response = null; + let t = null; try { - let obj = JSON.parse($.response.body); + let e = JSON.parse($.response.body); $.data.write(blackAnswersIdKey, []); $.logger.debug(`用户登录用户信息,接口响应:${$.response.body}`); if ( - obj && - obj["id"] && - obj.hasOwnProperty("vip_info") && - obj["vip_info"].hasOwnProperty("is_vip") + e && + e["id"] && + e.hasOwnProperty("vip_info") && + e["vip_info"].hasOwnProperty("is_vip") ) { - const userInfo = { - id: obj["id"], - is_vip: obj["vip_info"]["is_vip"] - ? obj["vip_info"]["is_vip"] !== undefined + const r = { + id: e["id"], + is_vip: e["vip_info"]["is_vip"] + ? e["vip_info"]["is_vip"] !== undefined : false, }; $.logger.debug( - `当前用户id:${obj["id"]},是否为VIP:${obj["vip_info"]["is_vip"]}` + `当前用户id:${e["id"]},是否为VIP:${e["vip_info"]["is_vip"]}` ); - $.data.write(currentUserInfoKey, userInfo); - // 在APP显示VIP,仅自己可见,打开后才能使用屏蔽关键词解锁 + $.data.write(currentUserInfoKey, r); if ( $.data.read("zhihu_settings_fake_vip") !== false && - obj["vip_info"]["is_vip"] === false + e["vip_info"]["is_vip"] === false ) { - obj["vip_info"]["is_vip"] = true; - obj["vip_info"]["vip_type"] = 2; - obj["vip_info"]["vip_icon"] = { + e["vip_info"]["is_vip"] = true; + e["vip_info"]["vip_type"] = 2; + e["vip_info"]["vip_icon"] = { url: "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae", night_mode_url: "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae", }; - obj["vip_info"]["vip_icon_v2"] = { + e["vip_info"]["vip_icon_v2"] = { url: "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae", night_mode_url: "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae", }; - obj["vip_info"]["entrance"] = { + e["vip_info"]["entrance"] = { icon: { url: "https://pic3.zhimg.com/v2-5b7012c8c22fd520f5677305e1e551bf.png", night_mode_url: @@ -321,7 +260,7 @@ function processUserInfo() { sub_title_new: null, identity: "super_svip", }; - obj["vip_info"]["entrance_new"] = { + e["vip_info"]["entrance_new"] = { left_button: { title: "精选会员内容", description: "为您严选好内容", @@ -333,7 +272,7 @@ function processUserInfo() { jump_url: "zhihu://vip/purchase", }, }; - obj["vip_info"]["entrance_v2"] = { + e["vip_info"]["entrance_v2"] = { title: "我的超级盐选会员", sub_title: "畅享 5000W+ 优质内容", jump_url: "zhihu://market/home", @@ -343,46 +282,32 @@ function processUserInfo() { card_jump_url: "zhihu://market/home", }; $.logger.debug("设置用户为本地盐选会员"); - response = { body: JSON.stringify(obj) }; + t = { body: JSON.stringify(e) }; } } else { $.logger.warning( `没有获取到本次登录用户信息,如未对功能造成影响,请忽略此日志。` ); } - } catch (err) { - $.logger.error(`获取当前用户信息出现异常:${err}`); + } catch (e) { + $.logger.error(`获取当前用户信息出现异常:${e}`); } - return response; + return t; } - -/** - * @description: 黑名单管理 - * @return {*} - */ function manageBlackUser() { - const userInfo = getUserInfo(); - let defaultBlockedUsers = {}; - let customBlockedUsers = $.data.read(blockedUsersKey, "", userInfo.id); - customBlockedUsers = - typeof customBlockedUsers === "object" && !!customBlockedUsers - ? customBlockedUsers - : {}; - defaultAnswerBlockedUsers.forEach((element) => { - customBlockedUsers[element] = "00000000000000000000000000000000"; - defaultBlockedUsers[element] = "00000000000000000000000000000000"; + const r = getUserInfo(); + let t = {}; + let n = $.data.read(blockedUsersKey, "", r.id); + n = typeof n === "object" && !!n ? n : {}; + defaultAnswerBlockedUsers.forEach((e) => { + n[e] = "00000000000000000000000000000000"; + t[e] = "00000000000000000000000000000000"; }); - $.logger.debug( - `当前用户id:${userInfo.id},脚本黑名单:${JSON.stringify( - customBlockedUsers - )}` - ); - // 获取黑名单 + $.logger.debug(`当前用户id:${r.id},脚本黑名单:${JSON.stringify(n)}`); if ($.request.method === "GET") { try { - // 加载黑名单首页时,清空历史黑名单,仅保留脚本默认黑名单 if ($.request.url.indexOf("offset") < 0) { - customBlockedUsers = defaultBlockedUsers; + n = t; $.logger.debug( "脚本黑名单已清空,请滑动至黑名单末尾保证重新获取完成。" ); @@ -390,91 +315,80 @@ function manageBlackUser() { "开始同步黑名单数据,请滑动至黑名单末尾,直至弹出“同步成功”的通知。" ); } - let obj = JSON.parse($.response.body); - if (!!obj["data"]) { + let e = JSON.parse($.response.body); + if (!!e["data"]) { $.logger.debug( - `本次滑动获取的黑名单信息:${JSON.stringify(obj["data"])}` + `本次滑动获取的黑名单信息:${JSON.stringify(e["data"])}` ); - obj["data"].forEach((element) => { - if (element["name"] !== "[已重置]") { - customBlockedUsers[element["name"]] = element["id"]; + e["data"].forEach((e) => { + if (e["name"] !== "[已重置]") { + n[e["name"]] = e["id"]; } }); - $.data.write(blockedUsersKey, customBlockedUsers, userInfo.id); - if (obj["paging"]["is_end"] === true) { + $.data.write(blockedUsersKey, n, r.id); + if (e["paging"]["is_end"] === true) { $.notification.post( `同步黑名单数据成功!当前黑名单共${ - Object.keys(customBlockedUsers).length - - defaultAnswerBlockedUsers.length + Object.keys(n).length - defaultAnswerBlockedUsers.length }人。\n脚本内置黑名单${defaultAnswerBlockedUsers.length}人。` ); - $.logger.debug( - `脚本黑名单内容:${JSON.stringify(customBlockedUsers)}。` - ); + $.logger.debug(`脚本黑名单内容:${JSON.stringify(n)}。`); } } else { $.logger.warning(`获取黑名单失败,接口响应不合法:${$.response.body}`); } - } catch (err) { + } catch (e) { $.data.del(blockedUsersKey); - $.logger.error(`获取黑名单失败,异常信息:${err}`); + $.logger.error(`获取黑名单失败,异常信息:${e}`); $.notification.post("获取黑名单失败,执行异常,已清空黑名单。"); } - } - // 写入黑名单 - else if ($.request.method === "POST") { + } else if ($.request.method === "POST") { try { - let obj = JSON.parse($.response.body); - if (obj.hasOwnProperty("name") && obj.hasOwnProperty("id")) { + let e = JSON.parse($.response.body); + if (e.hasOwnProperty("name") && e.hasOwnProperty("id")) { $.logger.debug( - `当前需要加入黑名单的用户Id:${obj["id"]},用户名:${obj["name"]}` + `当前需要加入黑名单的用户Id:${e["id"]},用户名:${e["name"]}` ); - if (obj["id"]) { - customBlockedUsers[obj["name"]] = obj["id"]; - $.data.write(blockedUsersKey, customBlockedUsers, userInfo.id); + if (e["id"]) { + n[e["name"]] = e["id"]; + $.data.write(blockedUsersKey, n, r.id); $.logger.debug( `${ - obj["name"] - }写入脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify( - customBlockedUsers - )}` + e["name"] + }写入脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify(n)}` ); - $.notification.post(`已将用户“${obj["name"]}”写入脚本黑名单。`); + $.notification.post(`已将用户“${e["name"]}”写入脚本黑名单。`); } else { - $.logger.error( - `${obj["name"]}写入脚本黑名单失败,没有获取到用户Id。` - ); - $.notification.post(`将用户“${obj["name"]}”写入脚本黑名单失败!`); + $.logger.error(`${e["name"]}写入脚本黑名单失败,没有获取到用户Id。`); + $.notification.post(`将用户“${e["name"]}”写入脚本黑名单失败!`); } } else { $.logger.warning(`写入黑名单失败,接口响应不合法:${$.response.body}`); $.notification.post("写入脚本黑名单失败,接口返回不合法。"); } - } catch (err) { - $.logger.error(`写入黑名单失败,异常信息:${err}`); + } catch (e) { + $.logger.error(`写入黑名单失败,异常信息:${e}`); $.notification.post("写入脚本黑名单失败,执行异常,请查阅日志。"); } - } - // 移出黑名单 - else if ($.request.method === "DELETE") { + } else if ($.request.method === "DELETE") { try { - let obj = JSON.parse($.response.body); - if (obj.success) { - let user_id = $.request.url.match( + let e = JSON.parse($.response.body); + if (e.success) { + let t = $.request.url.match( /^https?:\/\/api\.zhihu\.com\/settings\/blocked_users\/([0-9a-zA-Z]*)/ )[1]; - if (user_id) { - $.logger.debug(`当前需要移出黑名单的用户Id:${user_id}`); - for (let username in customBlockedUsers) { - if (customBlockedUsers[username] === user_id) { - delete customBlockedUsers[username]; - $.data.write(blockedUsersKey, customBlockedUsers, userInfo.id); + if (t) { + $.logger.debug(`当前需要移出黑名单的用户Id:${t}`); + for (let e in n) { + if (n[e] === t) { + delete n[e]; + $.data.write(blockedUsersKey, n, r.id); $.logger.debug( - `${username}移出脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify( - customBlockedUsers + `${e}移出脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify( + n )}` ); - $.notification.post(`已将用户“${username}”移出脚本黑名单!`); + $.notification.post(`已将用户“${e}”移出脚本黑名单!`); break; } } @@ -490,855 +404,747 @@ function manageBlackUser() { $.logger.warning(`移出黑名单失败,接口响应不合法:${$.response.body}`); $.notification.post("移出脚本黑名单失败,接口返回不合法。"); } - } catch (err) { - $.logger.error(`移出黑名单失败,异常信息:${err}`); + } catch (e) { + $.logger.error(`移出黑名单失败,异常信息:${e}`); $.notification.post("移出脚本黑名单失败,执行异常,请查阅日志。"); } } } - -/** - * 黑名单增强 - 浏览黑名单用户信息时自动加入脚本黑名单 - * @return {*} - */ function autoInsertBlackList() { - let response = null; + let e = null; try { - let obj = JSON.parse($.response.body); - // 删除MCN信息 - delete obj["mcn_user_info"]; - response = { body: JSON.stringify(obj) }; - // 如已是黑名单用户,但不在脚本黑名单中,则自动加入 - if (obj.name && obj.id && obj["is_blocking"] === true) { - const userInfo = getUserInfo(); - let customBlockedUsers = $.data.read(blockedUsersKey, "", userInfo.id); - customBlockedUsers = - typeof customBlockedUsers === "object" && !!customBlockedUsers - ? customBlockedUsers - : {}; - if (!customBlockedUsers[obj.name]) { + let t = JSON.parse($.response.body); + delete t["mcn_user_info"]; + e = { body: JSON.stringify(t) }; + if (t.name && t.id && t["is_blocking"] === true) { + const r = getUserInfo(); + let e = $.data.read(blockedUsersKey, "", r.id); + e = typeof e === "object" && !!e ? e : {}; + if (!e[t.name]) { $.logger.debug( - `当前需要加入黑名单的用户Id:${obj["id"]},用户名:${obj["name"]}` + `当前需要加入黑名单的用户Id:${t["id"]},用户名:${t["name"]}` ); - customBlockedUsers[obj["name"]] = obj["id"]; - $.data.write(blockedUsersKey, customBlockedUsers, userInfo.id); + e[t["name"]] = t["id"]; + $.data.write(blockedUsersKey, e, r.id); $.logger.debug( - `${ - obj["name"] - }写入脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify( - customBlockedUsers + `${t["name"]}写入脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify( + e )}` ); - $.notification.post(`已自动将用户“${obj["name"]}”写入脚本黑名单。`); + $.notification.post(`已自动将用户“${t["name"]}”写入脚本黑名单。`); } } - } catch (err) { - $.logger.error(`去除MCN信息出现异常:${err}`); + } catch (e) { + $.logger.error(`去除MCN信息出现异常:${e}`); } - return response; + return e; } - -/** - * 关注列表去广告 - * - * @return {*} - */ function removeMoments() { - let response = null; + let r = null; try { - let obj = JSON.parse( + let e = JSON.parse( $.response.body.replace(/(\w+"+\s?):\s?(\d{15,})/g, '$1:"$2"') ); - const user_info = getUserInfo(); - let customBlockedUsers = $.data.read(blockedUsersKey, "", user_info.id); - customBlockedUsers = !!customBlockedUsers ? customBlockedUsers : {}; - let data; - - const settings_remove_stream = $.data.read( - "zhihu_settings_moments_stream", - false - ); - const settings_remove_recommend = $.data.read( - "zhihu_settings_moments_recommend", - false - ); - const settings_remove_activity = $.data.read( - "zhihu_settings_moments_activity", - false - ); - const settings_blocked_users = $.data.read( - "zhihu_settings_blocked_users", - false - ); - - data = obj.data.filter((item) => { - // 转发的想法是否含有黑名单用户 - const isBlackUserPin = - settings_blocked_users && - item.target && - item.target["origin_pin"] && - item.target["origin_pin"].author && - typeof customBlockedUsers[item.target["origin_pin"].author.name] != - "undefined"; - // 是否为流媒体 - const isStream = - settings_remove_stream && item["target_type"] === "zvideo"; - // 是否为推荐关注用户 - const isRecommend = - settings_remove_recommend && item.type === "recommend_user_card_list"; - // 是否为关注的问题有新动态 - const isActivity = - settings_remove_activity && item.type === "message_activity_card"; - return !(isBlackUserPin || isStream || isRecommend || isActivity); + const n = getUserInfo(); + let i = $.data.read(blockedUsersKey, "", n.id); + i = !!i ? i : {}; + let t; + const s = $.data.read("zhihu_settings_moments_stream", false); + const a = $.data.read("zhihu_settings_moments_recommend", false); + const l = $.data.read("zhihu_settings_moments_activity", false); + const c = $.data.read("zhihu_settings_blocked_users", false); + t = e.data.filter((e) => { + const t = + c && + e.target && + e.target["origin_pin"] && + e.target["origin_pin"].author && + typeof i[e.target["origin_pin"].author.name] != "undefined"; + const r = s && e["target_type"] === "zvideo"; + const n = a && e.type === "recommend_user_card_list"; + const o = l && e.type === "message_activity_card"; + return !(t || r || n || o); }); - obj["data"] = data; - response = { body: JSON.stringify(obj) }; - } catch (err) { - $.logger.error(`关注列表去广告出现异常:${err}`); + e["data"] = t; + r = { body: JSON.stringify(e) }; + } catch (e) { + $.logger.error(`关注列表去广告出现异常:${e}`); } - return response; + return r; } - -/** - * 推荐去广告与黑名单增强 - * - * @return {*} - */ -function removeRecommend() { - let response = null; +function _setRecommendTag(e, t, r = "GBK02A") { + if (!e["common_card"]["footline"]) { + e["common_card"]["footline"] = { elements: [] }; + } + e["common_card"]["footline"]["elements"].unshift({ + tag: { text: t, color: r, type: "MASK_ROUNDED_RECTANGLE" }, + }); +} +async function _checkPaidContentByCloud(e, n) { + const t = $.data.read(zheyeServerKey); + if (!t) { + $.notification.post( + "未设置服务端地址,无法进行付费/推广内容探测。\n请配置服务端地址,或使用本地探测。" + ); + } else { + $.logger.debug(`向云端请求以下链接\n${e.join("\n")}`); + const r = `${t}/api/v1/answer/links`; + $.logger.debug(`服务端地址\n${r}`); + await $.http + .post({ + url: r, + headers: { "Content-Type": "application/json" }, + body: e, + }) + .then((r) => { + $.logger.debug( + `云端探测结果<${typeof r.body}>\n${JSON.stringify(r.body)}` + ); + for (let t = 0; t < r.body.length; t++) { + try { + let e = r.body[t]; + if (e !== "") { + _setRecommendTag(n[t], e, "GBK02A"); + } + } catch (e) { + $.logger.error(e); + } + } + }) + .catch((e) => { + $.logger.error(`云端请求出现异常\n${JSON.stringify(e)}`); + }); + } +} +async function _checkPaidContentByLocal(n, o) { + $.logger.debug(`将在本地请求以下链接\n${n.join("\n")}`); + let t = []; + function r(t) { + return new Promise((r) => { + const e = n[t]; + if ( + !e || + e === "" || + !e.startsWith("https://www.zhihu.com/appview/v2/answer") + ) { + r(""); + } else { + $.http + .get({ url: n[t], timeout: 1e3 }) + .then((e) => { + const t = e.body; + if ( + (t.indexOf("查看完整内容") >= 0 || + t.indexOf("查看全部章节") >= 0) && + t.indexOf("paid") >= 0 + ) { + r("付费内容"); + } else if ( + t.indexOf("ad-link-card") >= 0 || + t.indexOf("xg.zhihu.com") >= 0 || + t.indexOf("营销平台") >= 0 + ) { + r("营销推广"); + } else if (t.indexOf("mcn-link-card") >= 0) { + r("购物推广"); + } else { + r(""); + } + }) + .catch((e) => { + $.logger.error(`本地请求出现异常\n${JSON.stringify(e)}`); + r(""); + }); + } + }); + } + for (let e = 0; e < n.length; e++) { + t.push(r(e)); + } + await Promise.all(t).then((r) => { + $.logger.debug(`本地探测结果<${r.length}>\n${JSON.stringify(r)}`); + for (let t = 0; t < r.length; t++) { + try { + let e = r[t]; + if (e !== "") { + _setRecommendTag(o[t], e, "GBK02A"); + } + } catch (e) { + $.logger.error(e); + } + } + }); +} +async function removeRecommend() { + let t = null; try { - // 移除推荐列表中的想法 - const settings_remove_pin = $.data.read( - "zhihu_settings_recommend_pin", - false - ); - // 移除推荐列表的流媒体 - const settings_recommend_stream = $.data.read( - "zhihu_settings_recommend_stream", - false - ); - // 移除推荐列表的文章 - const settings_remove_article = $.data.read( - "zhihu_settings_remove_article", - false - ); - // 屏蔽黑名单用户 - const settings_blocked_users = $.data.read( - "zhihu_settings_blocked_users", - false - ); - // 屏蔽关键词内容 - const settings_blocked_keywords = $.data.read( - "zhihu_settings_blocked_keywords", - true - ); - // 获取用户信息 - const user_info = getUserInfo(); - - let keywords = $.data.read(keywordBlockKey, "", user_info.id); - keywords = settings_blocked_keywords && !!keywords ? keywords : []; - let customBlockedUsers = $.data.read(blockedUsersKey, "", user_info.id); - customBlockedUsers = - settings_blocked_users && !!customBlockedUsers ? customBlockedUsers : {}; - - const newData = (element) => { - const elementStr = JSON.stringify(element); - // 是否为广告 - const isAd = - element["card_type"] === "slot_event_card" || - element["card_type"] === "slot_video_event_card" || - element.hasOwnProperty("ad") || - // 非常恶心伪装成普通内容的广告 - (element["brief"] && element["brief"].indexOf("slot_card") >= 0) || - // 训练营 - (element["extra"] && element["extra"]["type"] === "Training"); - // 是否为流媒体 - const isStream = - isAd !== true && - elementStr.search( - /"(type|style)+"\s?:\s?"(drama|zvideo|Video|BIG_IMAGE)+"/i - ) >= 0; - const removeStream = isStream && settings_recommend_stream; - // 是否为想法 - const isPin = - isStream !== true && - elementStr.search(/"(type|style)+"\s?:\s?"pin"/i) >= 0; - const removePin = isPin && settings_remove_pin; - // 是否为文章 - const isArticle = - elementStr.search(/"(type|style)+"\s?:\s?"article"/i) >= 0; - const removeArticle = isArticle && settings_remove_article; - // 是否匹配脚本关键词过滤 - let matchKeyword = false; - if (isStream !== true && settings_blocked_keywords) { - for (let i = 0; i < keywords.length; i++) { - if (elementStr.search(keywords[i]) >= 0) { + const g = $.data.read("zhihu_settings_recommend_pin", false); + const p = $.data.read("zhihu_settings_recommend_stream", false); + const h = $.data.read("zhihu_settings_remove_article", false); + const y = $.data.read("zhihu_settings_blocked_users", false); + const m = $.data.read("zhihu_settings_blocked_keywords", true); + const r = $.data.read("zhihu_settings_check_paid_content", false); + const o = $.data.read("zhihu_settings_request_content", "local"); + const i = getUserInfo(); + let u = $.data.read(keywordBlockKey, "", i.id); + u = m && !!u ? u : []; + let f = $.data.read(blockedUsersKey, "", i.id); + f = y && !!f ? f : {}; + const s = (o) => { + const e = JSON.stringify(o); + const t = + o["card_type"] === "slot_event_card" || + o["card_type"] === "slot_video_event_card" || + o.hasOwnProperty("ad") || + (o["brief"] && o["brief"].indexOf("slot_card") >= 0) || + (o["extra"] && o["extra"]["type"] === "Training"); + const r = + t !== true && + e.search(/"(type|style)+"\s?:\s?"(drama|zvideo|Video|BIG_IMAGE)+"/i) >= + 0; + const n = r && p; + const i = r !== true && e.search(/"(type|style)+"\s?:\s?"pin"/i) >= 0; + const s = i && g; + const a = e.search(/"(type|style)+"\s?:\s?"article"/i) >= 0; + const l = a && h; + let c = false; + if (r !== true && m) { + for (let n = 0; n < u.length; n++) { + if (e.search(u[n]) >= 0) { if ($.isDebug) { - let elementTitle = - element["common_card"]["feed_content"]["title"]["panel_text"]; - let elementContent = - element["common_card"]["feed_content"]["content"]["panel_text"]; - let actionUrl = ""; + let e = o["common_card"]["feed_content"]["title"]["panel_text"]; + let t = o["common_card"]["feed_content"]["content"]["panel_text"]; + let r = ""; try { - actionUrl = - element["common_card"]["feed_content"]["title"]["action"][ + r = + o["common_card"]["feed_content"]["title"]["action"][ "intent_url" ]; } catch {} $.logger.debug( - `匹配关键字:\n${keywords[i]}\n标题:\n${elementTitle}\n内容:\n${elementContent}` + `匹配关键字:\n${u[n]}\n标题:\n${e}\n内容:\n${t}` ); $.notification.debug( scriptName, - `关键字:${keywords[i]}`, - `${elementTitle}\n${elementContent}`, - actionUrl + `关键字:${u[n]}`, + `${e}\n${t}`, + r ); } - matchKeyword = true; + c = true; break; } } } - // 是否为黑名单用户 - let isBlockedUser; + let d; try { - isBlockedUser = - matchKeyword !== true && - settings_blocked_users && - customBlockedUsers && - element["common_card"]["feed_content"]["source_line"]["elements"][1][ + d = + c !== true && + y && + f && + o["common_card"]["feed_content"]["source_line"]["elements"][1][ "text" - ]["panel_text"] in customBlockedUsers; + ]["panel_text"] in f; } catch { - isBlockedUser = false; + d = false; } - return !( - isAd || - removePin || - removeArticle || - removeStream || - matchKeyword || - isBlockedUser - ); + return !(t || s || l || n || c || d); }; - - // 修复number类型精度丢失 - let obj = JSON.parse( + let e = JSON.parse( $.response.body.replace(/(\w+"+\s?):\s?(\d{15,})/g, '$1:"$2"') ); - - if (obj["data"].length > 0 && newData.length === 0) { - $.notification.post("所有推荐内容都已被过滤,建议调整脚本过滤配置。"); - } - obj["data"] = obj["data"].filter(newData); - response = { body: JSON.stringify(obj) }; - } catch (err) { - $.logger.error(`推荐列表去广告出现异常:${err}`); - } - return response; -} - -/** - * 回答列表去广告与黑名单增强 - * - * @return {*} - */ -function removeQuestions() { - let response = null; - try { - const userInfo = getUserInfo(); - let customBlockedUsers = $.data.read(blockedUsersKey, "", userInfo.id); - customBlockedUsers = !!customBlockedUsers ? customBlockedUsers : {}; - let obj = JSON.parse($.response.body); - const settingsBlockedUsers = $.data.read( - "zhihu_settings_blocked_users", - false - ); - $.logger.debug(`当前黑名单列表: ${JSON.stringify(customBlockedUsers)}`); - // 黑名单用户的回答Id - let blackUserAnswersId = $.data.read(blackAnswersIdKey, []); - // 去除广告 - delete obj["ad_info"]; - // 去除回答列表中的黑名单用户 - if (obj["data"]) { - let newData = []; - for (let element of obj.data) { - let blackUserName = ""; - const answerId = element.target.id.toString(); - try { - if ("target" in element) { - blackUserName = element["target"]["author"]["name"]; + let n = e["data"].filter(s); + if (r === true) { + let r = []; + for (let t = 0; t < n.length; t++) { + if (n[t]) { + try { + let e = + n[t]["common_card"]["feed_content"]["title"]["action"][ + "intent_url" + ]; + e = e.replace( + /^https:\/\/zhihu\.com\/question\/\d+\/answer\//, + "https://www.zhihu.com/appview/v2/answer/" + ); + r.push(e); + } catch { + r.push(""); } - } catch (ex) { - $.logger.error(`获取回答列表用户名出现异常:${ex}`); - } - const isBlackUser = - typeof customBlockedUsers[blackUserName] != "undefined"; - const removeBlackUserAnswer = settingsBlockedUsers && isBlackUser; - // 显示仅作者自己可见的回答,允许复制 - if ("target" in element) { - element["target"]["visible_only_to_author"] = false; - element["target"]["is_visible"] = true; - element["target"]["is_copyable"] = true; - } - if (!removeBlackUserAnswer) { - newData.push(element); - } else if ( - removeBlackUserAnswer === true && - blackUserAnswersId.includes(answerId) === false - ) { - blackUserAnswersId.push(answerId); - $.notification.debug( - `记录黑名单用户${blackUserName}的回答Id:${answerId}` - ); } } - obj.data = newData; + if (r.length > 0 && o === "cloud") { + await _checkPaidContentByCloud(r, n); + } else if (r.length > 0 && o === "local") { + await _checkPaidContentByLocal(r, n); + } } - $.data.write(blackAnswersIdKey, blackUserAnswersId); - const body = JSON.stringify(obj); - $.logger.debug(`修改后的回答列表数据:${body}`); - response = { body: body }; - } catch (err) { - $.logger.error(`回答列表去广告出现异常:${err}`); + e["data"] = n; + t = { body: JSON.stringify(e) }; + } catch (e) { + $.logger.error(`推荐列表去广告出现异常:${e}`); } - return response; + return t; } - -/** - * 回答内容优化 - * - * @return {*} - */ -function modifyAnswer() { - let response = null; +function removeQuestions() { + let t = null; try { - let html = $.response.body; - let insertText = ""; - - // 付费内容提醒 + const r = getUserInfo(); + let n = $.data.read(blockedUsersKey, "", r.id); + n = !!n ? n : {}; + let e = JSON.parse($.response.body); + const i = $.data.read("zhihu_settings_blocked_users", false); + $.logger.debug(`当前黑名单列表: ${JSON.stringify(n)}`); + let o = $.data.read(blackAnswersIdKey, []); + delete e["ad_info"]; + if (e["data"]) { + let r = []; + for (let t of e.data) { + let e = ""; + const a = t.target.id.toString(); + try { + if ("target" in t) { + e = t["target"]["author"]["name"]; + } + } catch (e) { + $.logger.error(`获取回答列表用户名出现异常:${e}`); + } + const l = typeof n[e] != "undefined"; + const c = i && l; + if ("target" in t) { + t["target"]["visible_only_to_author"] = false; + t["target"]["is_visible"] = true; + t["target"]["is_copyable"] = true; + } + if (!c) { + r.push(t); + } else if (c === true && o.includes(a) === false) { + o.push(a); + $.notification.debug(`记录黑名单用户${e}的回答Id:${a}`); + } + } + e.data = r; + } + $.data.write(blackAnswersIdKey, o); + const s = JSON.stringify(e); + $.logger.debug(`修改后的回答列表数据:${s}`); + t = { body: s }; + } catch (e) { + $.logger.error(`回答列表去广告出现异常:${e}`); + } + return t; +} +function modifyAnswer() { + let r = null; + try { + let e = $.response.body; + let t = ""; if ( - (html.indexOf("查看完整内容") >= 0 || - html.indexOf("查看全部章节") >= 0) && - html.indexOf("paid") >= 0 + (e.indexOf("查看完整内容") >= 0 || e.indexOf("查看全部章节") >= 0) && + e.indexOf("paid") >= 0 ) { - insertText = + t = '
本文为付费内容
'; - } - - // 营销推广提醒 - else if ( - html.indexOf("ad-link-card") >= 0 || - html.indexOf("xg.zhihu.com") >= 0 || - html.indexOf("营销平台") >= 0 + } else if ( + e.indexOf("ad-link-card") >= 0 || + e.indexOf("xg.zhihu.com") >= 0 || + e.indexOf("营销平台") >= 0 ) { - insertText = + t = '
本文含有营销推广
'; - } - - // 购物推广提醒 - else if (html.indexOf("mcn-link-card") >= 0) { - insertText = + } else if (e.indexOf("mcn-link-card") >= 0) { + t = '
本文含有购物推广
'; - } - - // 彩蛋 - else if (Math.floor(Math.random() * 200) === 7) { - insertText = + } else if (Math.floor(Math.random() * 200) === 7) { + t = '
本文为免费内容
'; } - - if (insertText !== "") { - const matchStr = html.match(/(richText[^<]*>)(.)/)[1]; - const start = html.lastIndexOf(matchStr) + matchStr.length; - response = { - body: html.slice(0, start) + insertText + html.slice(start), - }; + if (t !== "") { + const n = e.match(/(richText[^<]*>)(.)/)[1]; + const o = e.lastIndexOf(n) + n.length; + r = { body: e.slice(0, o) + t + e.slice(o) }; } - } catch (err) { - $.logger.error(`付费内容提醒出现异常:${err}`); + } catch (e) { + $.logger.error(`付费内容提醒出现异常:${e}`); } - return response; + return r; } - -/** - * 评论去广告及黑名单增强 - * - * @return {*} - */ function removeComment() { - let response = null; + let e = null; try { if (!!$.response.body) { - let obj = JSON.parse($.response.body); - obj["ad_info"] = {}; - // 屏蔽黑名单用户 + let t = JSON.parse($.response.body); + t["ad_info"] = {}; if ($.data.read("zhihu_settings_blocked_users", false) === true) { - let user_info = getUserInfo(); - let customBlockedUsers = $.data.read(blockedUsersKey, "", user_info.id); - customBlockedUsers = !!customBlockedUsers ? customBlockedUsers : {}; - let newComments = []; - let blockCommentIdObj = {}; - if (typeof obj.root != "undefined") { - // 屏蔽黑名单用户的评论 - const rootUserName = obj.root.author.name; - const isBlackRootUser = - typeof customBlockedUsers[rootUserName] != "undefined"; - if (isBlackRootUser === true) { - obj.root.is_delete = true; - obj.root.can_reply = false; - obj.root.can_like = false; - obj.root.author.name = "黑名单用户"; - obj.root.author.avatar_url = + let e = getUserInfo(); + let s = $.data.read(blockedUsersKey, "", e.id); + s = !!s ? s : {}; + let a = []; + let l = {}; + if (typeof t.root != "undefined") { + const n = t.root.author.name; + const o = typeof s[n] != "undefined"; + if (o === true) { + t.root.is_delete = true; + t.root.can_reply = false; + t.root.can_like = false; + t.root.author.name = "黑名单用户"; + t.root.author.avatar_url = "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"; } } - - if (typeof obj.data != "undefined") { - obj.data.forEach((comment) => { - // 屏蔽黑名单用户的评论 - // 评论人昵称 - const commentUserName = comment.author.name; - // 回复哪个人的评论(仅适用于独立子评论页面请求) - let replyUserName = ""; - if (comment["reply_to_author"] && comment["reply_to_author"].name) { - replyUserName = comment["reply_to_author"].name; + if (typeof t.data != "undefined") { + t.data.forEach((e) => { + const t = e.author.name; + let r = ""; + if (e["reply_to_author"] && e["reply_to_author"].name) { + r = e["reply_to_author"].name; } - const isSubComment = replyUserName !== ""; - const isBlackCommentUser = - typeof customBlockedUsers[commentUserName] != "undefined"; - const isBlackReplyUser = - typeof customBlockedUsers[replyUserName] != "undefined"; - if (isBlackCommentUser === true || isBlackReplyUser === true) { - if ( - isBlackCommentUser && - !isSubComment && - $.request.url.indexOf("root_comment") > 0 - ) { - $.notification.debug( - `屏蔽黑名单用户“${commentUserName}”的主评论。` - ); + const n = r !== ""; + const o = typeof s[t] != "undefined"; + const i = typeof s[r] != "undefined"; + if (o === true || i === true) { + if (o && !n && $.request.url.indexOf("root_comment") > 0) { + $.notification.debug(`屏蔽黑名单用户“${t}”的主评论。`); } else if ( - !isBlackCommentUser && - isSubComment && - !isBlackReplyUser && + !o && + n && + !i && + $.request.url.indexOf("child_comment") > 0 + ) { + $.notification.debug(`屏蔽黑名单用户“${t}”的子评论。`); + } else if ( + o && + !i && $.request.url.indexOf("child_comment") > 0 ) { $.notification.debug( - `屏蔽黑名单用户“${commentUserName}”的子评论。` + `屏蔽黑名单用户“${t}”回复“${r}”的子评论。` ); - } else if ( - isBlackCommentUser && - !isBlackReplyUser && - $.request.url.indexOf("child_comment") > 0 - ) { + } else if (o && i && $.request.url.indexOf("child_comment") > 0) { $.notification.debug( - `屏蔽黑名单用户“${commentUserName}”回复“${replyUserName}”的子评论。` - ); - } else if ( - isBlackCommentUser && - isBlackReplyUser && - $.request.url.indexOf("child_comment") > 0 - ) { - $.notification.debug( - `屏蔽黑名单用户“${commentUserName}”回复黑名单用户“${replyUserName}”的子评论。` + `屏蔽黑名单用户“${t}”回复黑名单用户“${r}”的子评论。` ); } - blockCommentIdObj[comment.id] = commentUserName; - if (isBlackCommentUser) { - comment.is_delete = true; - comment.can_reply = false; - comment.can_like = false; - comment.author.exposed_medal = {}; - comment.author.name = "[黑名单用户]"; - comment.author.avatar_url = + l[e.id] = t; + if (o) { + e.is_delete = true; + e.can_reply = false; + e.can_like = false; + e.author.exposed_medal = {}; + e.author.name = "[黑名单用户]"; + e.author.avatar_url = "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"; } - if (isBlackReplyUser) { - comment["reply_to_author"].name = "[黑名单用户]"; - comment["reply_to_author"].exposed_medal = {}; - comment["reply_to_author"].avatar_url = + if (i) { + e["reply_to_author"].name = "[黑名单用户]"; + e["reply_to_author"].exposed_medal = {}; + e["reply_to_author"].avatar_url = "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"; } } - if (comment.child_comments) { - let newChildComments = []; - comment.child_comments.forEach((childComment) => { - // 屏蔽黑名单用户的评论 - const childCommentUserName = childComment.author.name; - const childCommentReplyUserName = - typeof childComment["reply_to_author"] != "undefined" - ? childComment["reply_to_author"].name + if (e.child_comments) { + let i = []; + e.child_comments.forEach((e) => { + const t = e.author.name; + const r = + typeof e["reply_to_author"] != "undefined" + ? e["reply_to_author"].name : ""; - const isChildBlackCommentUser = - typeof customBlockedUsers[childCommentUserName] != - "undefined"; - const isChildBlackReplyUser = - typeof customBlockedUsers[childCommentReplyUserName] != - "undefined"; - if (isChildBlackCommentUser || isChildBlackReplyUser) { - if (isChildBlackCommentUser === true) { - $.notification.debug( - `屏蔽黑名单用户“${childCommentUserName}”的子评论。` - ); - blockCommentIdObj[childComment.id] = childCommentUserName; - childComment.is_delete = true; - childComment.can_reply = false; - childComment.can_like = false; - childComment.author.name = "[黑名单用户]"; - childComment.author.exposed_medal = {}; - childComment.author.avatar_url = + const n = typeof s[t] != "undefined"; + const o = typeof s[r] != "undefined"; + if (n || o) { + if (n === true) { + $.notification.debug(`屏蔽黑名单用户“${t}”的子评论。`); + l[e.id] = t; + e.is_delete = true; + e.can_reply = false; + e.can_like = false; + e.author.name = "[黑名单用户]"; + e.author.exposed_medal = {}; + e.author.avatar_url = "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"; } - - if (isChildBlackReplyUser === true) { - $.logger.debug( - `修改前的子评论数据:\n${JSON.stringify(childComment)}` - ); - childComment["reply_to_author"].name = "[黑名单用户]"; - childComment["reply_to_author"].exposed_medal = {}; - childComment["reply_to_author"].avatar_url = + if (o === true) { + $.logger.debug(`修改前的子评论数据:\n${JSON.stringify(e)}`); + e["reply_to_author"].name = "[黑名单用户]"; + e["reply_to_author"].exposed_medal = {}; + e["reply_to_author"].avatar_url = "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"; $.notification.debug( - `隐藏“${childCommentUserName}”回复黑名单用户“${childCommentReplyUserName}”的名称与头像。` - ); - $.logger.debug( - `修改后的子评论数据:\n${JSON.stringify(childComment)}` + `隐藏“${t}”回复黑名单用户“${r}”的名称与头像。` ); + $.logger.debug(`修改后的子评论数据:\n${JSON.stringify(e)}`); } } - newChildComments.push(childComment); + i.push(e); }); - comment.child_comments = newChildComments; + e.child_comments = i; } - newComments.push(comment); + a.push(e); }); } - obj.data = newComments; + t.data = a; } - const body = JSON.stringify(obj); - $.logger.debug(`过滤后的评论数据:\n${body}`); - response = { body: body }; + const r = JSON.stringify(t); + $.logger.debug(`过滤后的评论数据:\n${r}`); + e = { body: r }; } - } catch (err) { - $.logger.error(`去除评论广告出现异常:${err}`); + } catch (e) { + $.logger.error(`去除评论广告出现异常:${e}`); } - return response; + return e; } - -/** - * 移除文章页面的广告 - * @return {*} - */ function removeArticleAd() { - let response = null; + let t = null; try { if (!!$.response.body) { - let obj = JSON.parse($.response.body); - obj["ad_info"] = {}; - const body = JSON.stringify(obj); - $.logger.debug(`过滤后的文章数据:\n${body}`); - response = { body: body }; + let e = JSON.parse($.response.body); + e["ad_info"] = {}; + const r = JSON.stringify(e); + $.logger.debug(`过滤后的文章数据:\n${r}`); + t = { body: r }; } - } catch (err) { - $.logger.error(`去除文章广告出现异常:${err}`); + } catch (e) { + $.logger.error(`去除文章广告出现异常:${e}`); } - return response; + return t; } - -/** - * 屏蔽官方营销消息 - * - * @return {*} - */ function removeMarketingMsg() { - let response = null; + let t = null; try { - let obj = JSON.parse($.response.body); - let newItems = []; - for (let item of obj["data"]) { - if (item["detail_title"] === "官方帐号消息") { - let unread_count = item["unread_count"]; - if (unread_count > 0) { - item["content"]["text"] = "未读消息" + unread_count + "条"; + let e = JSON.parse($.response.body); + let r = []; + for (let t of e["data"]) { + if (t["detail_title"] === "官方帐号消息") { + let e = t["unread_count"]; + if (e > 0) { + t["content"]["text"] = "未读消息" + e + "条"; } else { - item["content"]["text"] = "全部消息已读"; + t["content"]["text"] = "全部消息已读"; } - item["is_read"] = true; - item["unread_count"] = 0; - newItems.push(item); - } else if (item["detail_title"] !== "活动助手") { - newItems.push(item); + t["is_read"] = true; + t["unread_count"] = 0; + r.push(t); + } else if (t["detail_title"] !== "活动助手") { + r.push(t); } } - obj["data"] = newItems; - response = { body: JSON.stringify(obj) }; - } catch (err) { - $.logger.error(`屏蔽官方营销消息出现异常:${err}`); + e["data"] = r; + t = { body: JSON.stringify(e) }; + } catch (e) { + $.logger.error(`屏蔽官方营销消息出现异常:${e}`); } - return response; + return t; } - -/** - * 热榜去广告 - * - * @return {*} - */ function removeHotListAds() { - let response = null; + let t = null; try { if (!!$.response.body) { - let obj = JSON.parse($.response.body); - if ("data" in obj) { - obj["data"] = obj["data"].filter((e) => { + let e = JSON.parse($.response.body); + if ("data" in e) { + e["data"] = e["data"].filter((e) => { return ( e["type"] === "hot_list_feed" || e["type"] === "hot_list_feed_video" ); }); } - response = { body: JSON.stringify(obj) }; + t = { body: JSON.stringify(e) }; } - } catch (err) { - $.logger.error(`去除热榜广告出现异常:${err}`); + } catch (e) { + $.logger.error(`去除热榜广告出现异常:${e}`); } - return response; + return t; } - -/** - * 去除预置关键字广告 - * - * @return {*} - */ function removeKeywordAds() { - let response = null; + let t = null; try { if (!!$.response.body) { $.logger.debug(`预置关键字返回:${$.response.body}`); - let obj = JSON.parse($.response.body); - if (obj.hasOwnProperty("preset_words") && obj["preset_words"]["words"]) { - obj["preset_words"]["words"] = obj["preset_words"]["words"].filter( - (element) => { - return element["type"] !== "ad"; - } - ); - response = { body: JSON.stringify(obj) }; + let e = JSON.parse($.response.body); + if (e.hasOwnProperty("preset_words") && e["preset_words"]["words"]) { + e["preset_words"]["words"] = e["preset_words"]["words"].filter((e) => { + return e["type"] !== "ad"; + }); + t = { body: JSON.stringify(e) }; } } - } catch (err) { - $.logger.error(`去除预置关键字广告出现异常:${err}`); + } catch (e) { + $.logger.error(`去除预置关键字广告出现异常:${e}`); } - return response; + return t; } - -/** - * 移除回答翻页时出现的黑名单用户的回答 - * 小概率会移除失败 - * @return {*} - */ function removeNextBlackUserAnswer() { - let response = null; + let t = null; try { if (!!$.response.body) { - let obj = JSON.parse($.response.body); - const blackUserAnswersId = $.data.read(blackAnswersIdKey, []); - if (blackUserAnswersId.length > 0) { - let newData = []; - obj.data.forEach((element) => { - const tag = blackUserAnswersId.includes(element.id.toString()); - if (tag === false) { - // 去除可能的广告 - element.ad_info = { data: "" }; - newData.push(element); + let e = JSON.parse($.response.body); + const n = $.data.read(blackAnswersIdKey, []); + if (n.length > 0) { + let r = []; + e.data.forEach((e) => { + const t = n.includes(e.id.toString()); + if (t === false) { + e.ad_info = { data: "" }; + r.push(e); } else { $.notification.debug( - `屏蔽翻页过程中出现的黑名单用户回答Id:${element.id}` + `屏蔽翻页过程中出现的黑名单用户回答Id:${e.id}` ); } }); - // 重新为答案排序 - for (let i = 0; i < newData.length; i++) { - if (newData[i]["extra"] && newData[i]["extra"]["question_index"]) { - newData[i]["extra"]["question_index"] = i + 1; + for (let e = 0; e < r.length; e++) { + if (r[e]["extra"] && r[e]["extra"]["question_index"]) { + r[e]["extra"]["question_index"] = e + 1; } - if (newData[i]["strategy_info"]) { - newData[i]["strategy_info"]["global_index"] = i + 1; - newData[i]["strategy_info"]["strategy_index"] = i + 1; + if (r[e]["strategy_info"]) { + r[e]["strategy_info"]["global_index"] = e + 1; + r[e]["strategy_info"]["strategy_index"] = e + 1; } } - obj.data = newData; + e.data = r; } - response = { body: JSON.stringify(obj) }; + t = { body: JSON.stringify(e) }; } - } catch (err) { - $.logger.error(`屏蔽下翻黑名单用户的回答出现异常:${err}`); + } catch (e) { + $.logger.error(`屏蔽下翻黑名单用户的回答出现异常:${e}`); } - return response; + return t; } - function modifyAnswersNextData() { - let response = null; + let o = null; try { if (!!$.response.body) { - let obj = JSON.parse($.response.body); - let user_info = getUserInfo(); - let customBlockedUsers = $.data.read(blockedUsersKey, {}, user_info.id); - $.logger.debug(`脚本黑名单用户:\n${JSON.stringify(customBlockedUsers)}`); - let newData = []; - obj.data.data.forEach((element) => { - element["ad_info"] = { data: "" }; - const isBlackUser = - typeof customBlockedUsers[element.data.author.name] != "undefined"; - $.logger.debug( - `用户${element.data.author.name}是否在黑名单中:${isBlackUser}` - ); + let e = JSON.parse($.response.body); + let t = getUserInfo(); + let r = $.data.read(blockedUsersKey, {}, t.id); + $.logger.debug(`脚本黑名单用户:\n${JSON.stringify(r)}`); + let n = []; + e.data.data.forEach((e) => { + e["ad_info"] = { data: "" }; + const t = typeof r[e.data.author.name] != "undefined"; + $.logger.debug(`用户${e.data.author.name}是否在黑名单中:${t}`); if ( $.data.read("zhihu_settings_blocked_users", false) === false || - isBlackUser === false + t === false ) { - newData.push(element); + n.push(e); } }); - obj.data.data = newData; - response = { body: JSON.stringify(obj) }; + e.data.data = n; + o = { body: JSON.stringify(e) }; } - } catch (err) { - $.logger.error(`屏蔽回答信息流黑名单用户及广告:${err}`); + } catch (e) { + $.logger.error(`屏蔽回答信息流黑名单用户及广告:${e}`); } - return response; + return o; } - -/** - * 修改盐值 - * - * @return {*} - */ function changeUserCredit() { $.notification.debug("开始修改用户盐值"); - let response = null; + let t = null; try { if (!!$.response.body) { - // 自定义盐值 - const score = parseInt($.data.read(userCreditScore, 780)); - $.logger.debug(`准备修改用户盐值为${score}`); - let obj = JSON.parse($.response.body); - if (obj["credit_basis"].total_score < score) { - obj["credit_basis"].total_score = score; - $.logger.debug(`已修改用户盐值为:${score}`); + const r = parseInt($.data.read(userCreditScoreKey, 780)); + $.logger.debug(`准备修改用户盐值为${r}`); + let e = JSON.parse($.response.body); + if (e["credit_basis"].total_score < r) { + e["credit_basis"].total_score = r; + $.logger.debug(`已修改用户盐值为:${r}`); } - response = { body: JSON.stringify(obj) }; + t = { body: JSON.stringify(e) }; } - } catch (err) { - $.logger.error(`修改用户盐值出现异常:${err}`); + } catch (e) { + $.logger.error(`修改用户盐值出现异常:${e}`); } - return response; + return t; } - -(() => { - let response = null; +(async () => { + let e = null; if ($.isResponse) { switch (true) { - // 获取用户信息 - 隔离用户数据,开启本地盐选会员等 case /^https:\/\/api\.zhihu\.com\/people\/self$/.test($.request.url): - response = processUserInfo(); + e = processUserInfo(); break; - // 优化软件配置 - 优化下发的配置文件来实现某些效果 case $.data.read("zhihu_settings_app_conf", false) === true && /^https?:\/\/appcloud2\.zhihu\.com\/v\d+\/config/.test($.request.url): - response = modifyAppConfig(); + e = modifyAppConfig(); break; case $.data.read("zhihu_settings_app_conf", false) === true && /^https?:\/\/m-cloud\.zhihu\.com\/api\/cloud\/config\/all\?/.test( $.request.url ): - response = modifyMCloudConfig(); + e = modifyMCloudConfig(); break; - // 修改用户盐值 - 仅当自定义盐值大于真实盐值时生效 case /^https?:\/\/api\.zhihu\.com\/user-credit\/basis/.test( $.request.url ): - $.notification.debug("准备修改用户盐值"); - response = changeUserCredit(); + e = changeUserCredit(); break; - // 推荐页 - 移除黑名单用户发布的文章、去除广告,及自定义一些屏蔽项目 case /^https:\/\/api\.zhihu\.com\/topstory\/recommend/.test( $.request.url ): - response = removeRecommend(); + e = await removeRecommend(); break; - // 问题的回答列表 - 移除黑名单用户的回答、去除广告 case /^https?:\/\/api\.zhihu\.com\/(v4\/)?questions\/\d+/.test( $.request.url ): - response = removeQuestions(); + e = removeQuestions(); break; - // 回答信息流 - 移除黑名单用户的回答、去除广告 case /^https?:\/\/api\.zhihu\.com\/next-data\?/.test($.request.url): - response = modifyAnswersNextData(); + e = modifyAnswersNextData(); break; - // 消息页 - 折叠官方消息、屏蔽营销消息 case $.data.read("zhihu_settings_sys_msg", true) !== false && /^https?:\/\/api\.zhihu\.com\/notifications\/v3\/message/.test( $.request.url ): - response = removeMarketingMsg(); + e = removeMarketingMsg(); break; - // 评论页及子页面 - 去除黑名单用户发表的评论 case /^https?:\/\/api\.zhihu\.com\/comment_v5\/(answers|pins|comments?|articles)\/\d+\/(root|child)_comment/.test( $.request.url ): - response = removeComment(); + e = removeComment(); break; - // 文章页 - 去除底部卡片广告 case /^https?:\/\/www\.zhihu\.com\/api\/v\d\/articles\/\d+\/recommendation\?/.test( $.request.url ): - response = removeArticleAd(); + e = removeArticleAd(); break; - // 回答页底部评论摘要 - 移除黑名单用户发表的评论 case /^https?:\/\/www\.zhihu\.com\/api\/v4\/comment_v5\/answers\/\d+\/abstract_comment\?/.test( $.request.url ): - response = removeComment(); + e = removeComment(); break; - // 回答内容优化 - 付费、营销、推广内容文首提醒 case $.data.read("zhihu_settings_answer_tip", true) === true && /^https?:\/\/www\.zhihu\.com\/appview\/v2\/answer\/.*(entry=(?!(preload-topstory|preload-search|preload-subscription)))?/.test( $.request.url ): - response = modifyAnswer(); + e = modifyAnswer(); break; - // 回答页 - 屏蔽下翻出现的黑名单用户 case $.data.read("zhihu_settings_blocked_users", false) !== false && /^https?:\/\/api\.zhihu\.com\/next\?/.test($.request.url): - response = removeNextBlackUserAnswer(); + e = removeNextBlackUserAnswer(); break; - // 黑名单增强 - 浏览黑名单用户信息时自动加入脚本黑名单 case $.data.read("zhihu_settings_blocked_users", true) === true && /^https?:\/\/api\.zhihu\.com\/people\/((?!self).)*$/.test( $.request.url ): - response = autoInsertBlackList(); + e = autoInsertBlackList(); break; - // 关注页 - 去广告 case /^https?:\/\/api\.zhihu\.com\/moments_v3\?/.test($.request.url): - response = removeMoments(); + e = removeMoments(); break; - // 热榜页 - 去广告 case $.data.read("zhihu_settings_hot_list", true) === true && /^https?:\/\/api\.zhihu\.com\/topstory\/hot-lists(\?|\/)/.test( $.request.url ): - response = removeHotListAds(); + e = removeHotListAds(); break; - // 搜索页 - 去除预置广告 case $.data.read("zhihu_settings_preset_words", true) === true && /^https?:\/\/api\.zhihu\.com\/search\/preset_words\?/.test( $.request.url ): - response = removeKeywordAds(); + e = removeKeywordAds(); break; - // 黑名单页 - 同步黑名单数据 case $.data.read("zhihu_settings_blocked_users", false) !== false && /^https?:\/\/api\.zhihu\.com\/settings\/blocked_users/.test( $.request.url @@ -1350,13 +1156,12 @@ function changeUserCredit() { break; } } else if ($.isRequest) { - // 屏蔽关键词解锁 if ( $.data.read("zhihu_settings_blocked_keywords", false) !== false && /^https?:\/\/api\.zhihu\.com\/feed-root\/block/.test($.request.url) === true ) { - response = unlockBlockedKeywords(response); + e = unlockBlockedKeywords(e); } } else { $.data.del(currentUserInfoKey); @@ -1364,431 +1169,952 @@ function changeUserCredit() { $.data.del(keywordBlockKey); $.notification.post("哲也同学数据清理完毕"); } - if (response) { - $.done(response); + if (e) { + $.done(e); } else { $.done(); } })(); - -// prettier-ignore -/** - * - * $$\ $$\ $$\ $$$$$\ $$$$$$\ $$$$$$\ - * $$$\ $$$ | \__| \__$$ |$$ __$$\ $$ ___$$\ - * $$$$\ $$$$ | $$$$$$\ $$$$$$\ $$\ $$$$$$$\ $$ |$$ / \__| \_/ $$ | - * $$\$$\$$ $$ | \____$$\ $$ __$$\ $$ |$$ _____| $$ |\$$$$$$\ $$$$$ / - * $$ \$$$ $$ | $$$$$$$ |$$ / $$ |$$ |$$ / $$\ $$ | \____$$\ \___$$\ - * $$ |\$ /$$ |$$ __$$ |$$ | $$ |$$ |$$ | $$ | $$ |$$\ $$ | $$\ $$ | - * $$ | \_/ $$ |\$$$$$$$ |\$$$$$$$ |$$ |\$$$$$$$\\$$$$$$ |\$$$$$$ | \$$$$$$ | - * \__| \__| \_______| \____$$ |\__| \_______|\______/ \______/ \______/ - * $$\ $$ | - * \$$$$$$ | - * \______/ - * - */ -// @formatter:off -function MagicJS(scriptName="MagicJS",logLevel="INFO"){const MagicEnvironment=()=>{const isLoon=typeof $loon!=="undefined";const isQuanX=typeof $task!=="undefined";const isNode=typeof module!=="undefined";const isSurge=typeof $httpClient!=="undefined"&&!isLoon;const isStorm=typeof $storm!=="undefined";const isStash=typeof $environment!=="undefined"&&typeof $environment["stash-build"]!=="undefined";const isSurgeLike=isSurge||isLoon||isStorm||isStash;const isScriptable=typeof importModule!=="undefined";return{isLoon:isLoon,isQuanX:isQuanX,isNode:isNode,isSurge:isSurge,isStorm:isStorm,isStash:isStash,isSurgeLike:isSurgeLike,isScriptable:isScriptable,get name(){if(isLoon){return"Loon"}else if(isQuanX){return"QuantumultX"}else if(isNode){return"NodeJS"}else if(isSurge){return"Surge"}else if(isScriptable){return"Scriptable"}else{return"unknown"}},get build(){if(isSurge){return $environment["surge-build"]}else if(isStash){return $environment["stash-build"]}else if(isStorm){return $storm.buildVersion}},get language(){if(isSurge||isStash){return $environment["language"]}},get version(){if(isSurge){return $environment["surge-version"]}else if(isStash){return $environment["stash-version"]}else if(isStorm){return $storm.appVersion}else if(isNode){return process.version}},get system(){if(isSurge){return $environment["system"]}else if(isNode){return process.platform}},get systemVersion(){if(isStorm){return $storm.systemVersion}},get deviceName(){if(isStorm){return $storm.deviceName}}}};const MagicLogger=(scriptName,logLevel="INFO")=>{let _level=logLevel;const logLevels={SNIFFER:6,DEBUG:5,INFO:4,NOTIFY:3,WARNING:2,ERROR:1,CRITICAL:0,NONE:-1};const logEmoji={SNIFFER:"",DEBUG:"",INFO:"",NOTIFY:"",WARNING:"❗ ",ERROR:"❌ ",CRITICAL:"❌ ",NONE:""};const _log=(msg,level="INFO")=>{if(!(logLevels[_level]{_level=logLevel};return{getLevel:()=>{return _level},setLevel:setLevel,sniffer:msg=>{_log(msg,"SNIFFER")},debug:msg=>{_log(msg,"DEBUG")},info:msg=>{_log(msg,"INFO")},notify:msg=>{_log(msg,"NOTIFY")},warning:msg=>{_log(msg,"WARNING")},error:msg=>{_log(msg,"ERROR")},retry:msg=>{_log(msg,"RETRY")}}};return new class{constructor(scriptName,logLevel){this._startTime=Date.now();this.version="3.0.0";this.scriptName=scriptName;this.env=MagicEnvironment();this.logger=MagicLogger(scriptName,logLevel);this.http=typeof MagicHttp==="function"?MagicHttp(this.env,this.logger):undefined;this.data=typeof MagicData==="function"?MagicData(this.env,this.logger):undefined;this.notification=typeof MagicNotification==="function"?MagicNotification(this.scriptName,this.env,this.logger,this.http):undefined;this.utils=typeof MagicUtils==="function"?MagicUtils(this.env,this.logger):undefined;this.qinglong=typeof MagicQingLong==="function"?MagicQingLong(this.env,this.data,this.logger):undefined;if(typeof this.data!=="undefined"){let magicLoglevel=this.data.read("magic_loglevel");const barkUrl=this.data.read("magic_bark_url");if(magicLoglevel){this.logger.setLevel(magicLoglevel.toUpperCase())}if(barkUrl){this.notification.setBark(barkUrl)}}}get isRequest(){return typeof $request!=="undefined"&&typeof $response==="undefined"}get isResponse(){return typeof $response!=="undefined"}get isDebug(){return this.logger.level==="DEBUG"}get request(){return typeof $request!=="undefined"?$request:undefined}get response(){if(typeof $response!=="undefined"){if($response.hasOwnProperty("status"))$response["statusCode"]=$response["status"];if($response.hasOwnProperty("statusCode"))$response["status"]=$response["statusCode"];return $response}else{return undefined}}done=(value={})=>{this._endTime=Date.now();let span=(this._endTime-this._startTime)/1e3;this.logger.debug(`SCRIPT COMPLETED: ${span} S.`);if(typeof $done!=="undefined"){$done(value)}}}(scriptName,logLevel)} -function MagicNotification(scriptName, env, logger, http) { - let _barkUrl = null; - let _barkKey = null; - const setBark = (url) => { - try { - let _url = url.replace(/\/+$/g, ""); - _barkUrl = `${/^https?:\/\/([^/]*)/.exec(_url)[0]}/push`; - _barkKey = /\/([^\/]+)\/?$/.exec(_url)[1]; - } catch (ex) { - logger.error(`Bark url error: ${ex}.`); - } - }; - function post(title = scriptName, subTitle = "", body = "", opts = "") { - const _adaptOpts = (_opts) => { - try { - let newOpts = {}; - if (typeof _opts === "string") { - if (env.isLoon) newOpts = { openUrl: _opts }; - else if (env.isQuanX) newOpts = { "open-url": _opts }; - else if (env.isSurge) newOpts = { url: _opts }; - } else if (typeof _opts === "object") { - if (env.isLoon) { - newOpts["openUrl"] = !!_opts["open-url"] ? _opts["open-url"] : ""; - newOpts["mediaUrl"] = !!_opts["media-url"] - ? _opts["media-url"] - : ""; - } else if (env.isQuanX) { - newOpts = !!_opts["open-url"] || !!_opts["media-url"] ? _opts : {}; - } else if (env.isSurge) { - let openUrl = _opts["open-url"] || _opts["openUrl"]; - newOpts = openUrl ? { url: openUrl } : {}; - } +function MagicJS(e = "MagicJS", t = "INFO") { + const n = () => { + const e = typeof $loon !== "undefined"; + const t = typeof $task !== "undefined"; + const r = typeof module !== "undefined"; + const n = typeof $httpClient !== "undefined" && !e; + const o = typeof $storm !== "undefined"; + const i = + typeof $environment !== "undefined" && + typeof $environment["stash-build"] !== "undefined"; + const s = n || e || o || i; + const a = typeof importModule !== "undefined"; + return { + isLoon: e, + isQuanX: t, + isNode: r, + isSurge: n, + isStorm: o, + isStash: i, + isSurgeLike: s, + isScriptable: a, + get name() { + if (e) { + return "Loon"; + } else if (t) { + return "QuantumultX"; + } else if (r) { + return "NodeJS"; + } else if (n) { + return "Surge"; + } else if (a) { + return "Scriptable"; + } else { + return "unknown"; + } + }, + get build() { + if (n) { + return $environment["surge-build"]; + } else if (i) { + return $environment["stash-build"]; + } else if (o) { + return $storm.buildVersion; + } + }, + get language() { + if (n || i) { + return $environment["language"]; + } + }, + get version() { + if (n) { + return $environment["surge-version"]; + } else if (i) { + return $environment["stash-version"]; + } else if (o) { + return $storm.appVersion; + } else if (r) { + return process.version; + } + }, + get system() { + if (n) { + return $environment["system"]; + } else if (r) { + return process.platform; + } + }, + get systemVersion() { + if (o) { + return $storm.systemVersion; + } + }, + get deviceName() { + if (o) { + return $storm.deviceName; } - return newOpts; - } catch (err) { - logger.error(`通知选项转换失败${err}`); - } - return _opts; - }; - opts = _adaptOpts(opts); - if (arguments.length === 1) { - title = scriptName; - (subTitle = ""), (body = arguments[0]); - } - logger.notify( - `title:${title}\nsubTitle:${subTitle}\nbody:${body}\noptions:${ - typeof opts === "object" ? JSON.stringify(opts) : opts - }` - ); - if (env.isSurge) { - $notification.post(title, subTitle, body, opts); - } else if (env.isLoon) { - if (!!opts) $notification.post(title, subTitle, body, opts); - else $notification.post(title, subTitle, body); - } else if (env.isQuanX) { - $notify(title, subTitle, body, opts); - } - if (_barkUrl && _barkKey) { - bark(title, subTitle, body); - } - } - function debug(title = scriptName, subTitle = "", body = "", opts = "") { - if (logger.getLevel() === "DEBUG") { - if (arguments.length === 1) { - title = scriptName; - subTitle = ""; - body = arguments[0]; - } - this.post(title, subTitle, body, opts); - } - } - function bark(title = scriptName, subTitle = "", body = "", opts = "") { - if (typeof http === "undefined" || typeof http.post === "undefined") { - throw "Bark notification needs to import MagicHttp module."; - } - let options = { - url: _barkUrl, - headers: { "content-type": "application/json; charset=utf-8" }, - body: { - title: title, - body: subTitle ? `${subTitle}\n${body}` : body, - device_key: _barkKey, }, }; - http.post(options).catch((ex) => { - logger.error(`Bark notify error: ${ex}`); - }); - } - return { post: post, debug: debug, bark: bark, setBark: setBark }; -} -function MagicData(env, logger) { - let node = { fs: undefined, data: {} }; - if (env.isNode) { - node.fs = require("fs"); - try { - node.fs.accessSync( - "./magic.json", - node.fs.constants.R_OK | node.fs.constants.W_OK - ); - } catch (err) { - node.fs.writeFileSync("./magic.json", "{}", { encoding: "utf8" }); - } - node.data = require("./magic.json"); - } - const defaultValueComparator = (oldVal, newVal) => { - if (typeof newVal === "object") { - return false; - } else { - return oldVal === newVal; - } }; - const _typeConvertor = (val) => { - if (val === "true") { - return true; - } else if (val === "false") { - return false; - } else if (typeof val === "undefined") { - return null; - } else { - return val; - } + const o = (r, e = "INFO") => { + let n = e; + const o = { + SNIFFER: 6, + DEBUG: 5, + INFO: 4, + NOTIFY: 3, + WARNING: 2, + ERROR: 1, + CRITICAL: 0, + NONE: -1, + }; + const i = { + SNIFFER: "", + DEBUG: "", + INFO: "", + NOTIFY: "", + WARNING: "❗ ", + ERROR: "❌ ", + CRITICAL: "❌ ", + NONE: "", + }; + const t = (e, t = "INFO") => { + if (!(o[n] < o[t.toUpperCase()])) + console.log(`[${t}] [${r}]\n${i[t.toUpperCase()]}${e}\n`); + }; + const s = (e) => { + n = e; + }; + return { + getLevel: () => { + return n; + }, + setLevel: s, + sniffer: (e) => { + t(e, "SNIFFER"); + }, + debug: (e) => { + t(e, "DEBUG"); + }, + info: (e) => { + t(e, "INFO"); + }, + notify: (e) => { + t(e, "NOTIFY"); + }, + warning: (e) => { + t(e, "WARNING"); + }, + error: (e) => { + t(e, "ERROR"); + }, + retry: (e) => { + t(e, "RETRY"); + }, + }; }; - const _valConvertor = (val, default_, session, read_no_session) => { - if (session) { - try { - if (typeof val === "string") val = JSON.parse(val); - if (val["magic_session"] === true) { - val = val[session]; - } else { - val = null; + return new (class { + constructor(e, t) { + this._startTime = Date.now(); + this.version = "3.0.0"; + this.scriptName = e; + this.env = n(); + this.logger = o(e, t); + this.http = + typeof MagicHttp === "function" + ? MagicHttp(this.env, this.logger) + : undefined; + this.data = + typeof MagicData === "function" + ? MagicData(this.env, this.logger) + : undefined; + this.notification = + typeof MagicNotification === "function" + ? MagicNotification(this.scriptName, this.env, this.logger, this.http) + : undefined; + this.utils = + typeof MagicUtils === "function" + ? MagicUtils(this.env, this.logger) + : undefined; + this.qinglong = + typeof MagicQingLong === "function" + ? MagicQingLong(this.env, this.data, this.logger) + : undefined; + if (typeof this.data !== "undefined") { + let e = this.data.read("magic_loglevel"); + const r = this.data.read("magic_bark_url"); + if (e) { + this.logger.setLevel(e.toUpperCase()); + } + if (r) { + this.notification.setBark(r); } - } catch { - val = null; } } - if (typeof val === "string" && val !== "null") { + get isRequest() { + return ( + typeof $request !== "undefined" && typeof $response === "undefined" + ); + } + get isResponse() { + return typeof $response !== "undefined"; + } + get isDebug() { + return this.logger.level === "DEBUG"; + } + get request() { + return typeof $request !== "undefined" ? $request : undefined; + } + get response() { + if (typeof $response !== "undefined") { + if ($response.hasOwnProperty("status")) + $response["statusCode"] = $response["status"]; + if ($response.hasOwnProperty("statusCode")) + $response["status"] = $response["statusCode"]; + return $response; + } else { + return undefined; + } + } + done = (e = {}) => { + this._endTime = Date.now(); + let t = (this._endTime - this._startTime) / 1e3; + this.logger.info(`SCRIPT COMPLETED: ${t} S.`); + if (typeof $done !== "undefined") { + $done(e); + } + }; + })(e, t); +} +function MagicNotification(i, s, a, l) { + let c = null; + let d = null; + const e = (t) => { + try { + let e = t.replace(/\/+$/g, ""); + c = `${/^https?:\/\/([^/]*)/.exec(e)[0]}/push`; + d = /\/([^\/]+)\/?$/.exec(e)[1]; + } catch (e) { + a.error(`Bark url error: ${e}.`); + } + }; + function t(e = i, t = "", r = "", n = "") { + const o = (r) => { try { - val = JSON.parse(val); + let t = {}; + if (typeof r === "string") { + if (s.isLoon) t = { openUrl: r }; + else if (s.isQuanX) t = { "open-url": r }; + else if (s.isSurge) t = { url: r }; + } else if (typeof r === "object") { + if (s.isLoon) { + t["openUrl"] = !!r["open-url"] ? r["open-url"] : ""; + t["mediaUrl"] = !!r["media-url"] ? r["media-url"] : ""; + } else if (s.isQuanX) { + t = !!r["open-url"] || !!r["media-url"] ? r : {}; + } else if (s.isSurge) { + let e = r["open-url"] || r["openUrl"]; + t = e ? { url: e } : {}; + } + } + return t; + } catch (e) { + a.error(`通知选项转换失败${e}`); + } + return r; + }; + n = o(n); + if (arguments.length === 1) { + e = i; + (t = ""), (r = arguments[0]); + } + a.notify( + `title:${e}\nsubTitle:${t}\nbody:${r}\noptions:${ + typeof n === "object" ? JSON.stringify(n) : n + }` + ); + if (s.isSurge) { + $notification.post(e, t, r, n); + } else if (s.isLoon) { + if (!!n) $notification.post(e, t, r, n); + else $notification.post(e, t, r); + } else if (s.isQuanX) { + $notify(e, t, r, n); + } + if (c && d) { + u(e, t, r); + } + } + function r(e = i, t = "", r = "", n = "") { + if (a.getLevel() === "DEBUG") { + if (arguments.length === 1) { + e = i; + t = ""; + r = arguments[0]; + } + this.post(e, t, r, n); + } + } + function u(e = i, t = "", r = "", n = "") { + if (typeof l === "undefined" || typeof l.post === "undefined") { + throw "Bark notification needs to import MagicHttp module."; + } + let o = { + url: c, + headers: { "content-type": "application/json; charset=utf-8" }, + body: { title: e, body: t ? `${t}\n${r}` : r, device_key: d }, + }; + l.post(o).catch((e) => { + a.error(`Bark notify error: ${e}`); + }); + } + return { post: t, debug: r, bark: u, setBark: e }; +} +function MagicData(s, a) { + let l = { fs: undefined, data: {} }; + if (s.isNode) { + l.fs = require("fs"); + try { + l.fs.accessSync( + "./magic.json", + l.fs.constants.R_OK | l.fs.constants.W_OK + ); + } catch (e) { + l.fs.writeFileSync("./magic.json", "{}", { encoding: "utf8" }); + } + l.data = require("./magic.json"); + } + const c = (e, t) => { + if (typeof t === "object") { + return false; + } else { + return e === t; + } + }; + const d = (e) => { + if (e === "true") { + return true; + } else if (e === "false") { + return false; + } else if (typeof e === "undefined") { + return null; + } else { + return e; + } + }; + const u = (e, t, r, n) => { + if (r) { + try { + if (typeof e === "string") e = JSON.parse(e); + if (e["magic_session"] === true) { + e = e[r]; + } else { + e = null; + } + } catch { + e = null; + } + } + if (typeof e === "string" && e !== "null") { + try { + e = JSON.parse(e); } catch {} } - if (read_no_session === false && !!val && val["magic_session"] === true) { - val = null; + if (n === false && !!e && e["magic_session"] === true) { + e = null; } if ( - (val === null || typeof val === "undefined") && - default_ !== null && - typeof default_ !== "undefined" + (e === null || typeof e === "undefined") && + t !== null && + typeof t !== "undefined" ) { - val = default_; + e = t; } - val = _typeConvertor(val); - return val; + e = d(e); + return e; }; - const convertToObject = (obj) => { - if (typeof obj === "string") { - let data = {}; + const i = (t) => { + if (typeof t === "string") { + let e = {}; try { - data = JSON.parse(obj); - const type = typeof data; + e = JSON.parse(t); + const r = typeof e; if ( - type !== "object" || - data instanceof Array || - type === "bool" || - data === null + r !== "object" || + e instanceof Array || + r === "bool" || + e === null ) { - data = {}; + e = {}; } } catch {} - return data; + return e; } else if ( - obj instanceof Array || - obj === null || - typeof obj === "undefined" || - obj !== obj || - typeof obj === "boolean" + t instanceof Array || + t === null || + typeof t === "undefined" || + t !== t || + typeof t === "boolean" ) { return {}; } else { - return obj; + return t; } }; - const readForNode = ( - key, - default_ = null, - session = "", - read_no_session = false, - externalData = null - ) => { - let data = externalData || node.data; - if (!!data && typeof data[key] !== "undefined" && data[key] !== null) { - val = data[key]; + const f = (e, t = null, r = "", n = false, o = null) => { + let i = o || l.data; + if (!!i && typeof i[e] !== "undefined" && i[e] !== null) { + val = i[e]; } else { - val = !!session ? {} : null; + val = !!r ? {} : null; } - val = _valConvertor(val, default_, session, read_no_session); + val = u(val, t, r, n); return val; }; - const read = ( - key, - default_ = null, - session = "", - read_no_session = false, - externalData = null - ) => { - let val = ""; - if (externalData || env.isNode) { - val = readForNode(key, default_, session, read_no_session, externalData); + const g = (e, t = null, r = "", n = false, o = null) => { + let i = ""; + if (o || s.isNode) { + i = f(e, t, r, n, o); } else { - if (env.isSurgeLike) { - val = $persistentStore.read(key); - } else if (env.isQuanX) { - val = $prefs.valueForKey(key); + if (s.isSurgeLike) { + i = $persistentStore.read(e); + } else if (s.isQuanX) { + i = $prefs.valueForKey(e); } - val = _valConvertor(val, default_, session, read_no_session); + i = u(i, t, r, n); } - logger.debug( - `READ DATA [${key}]${ - !!session ? `[${session}]` : "" - } <${typeof val}>\n${JSON.stringify(val)}` + a.debug( + `READ DATA [${e}]${!!r ? `[${r}]` : ""} <${typeof i}>\n${JSON.stringify( + i + )}` ); - return val; + return i; }; - const writeForNode = (key, val, session = "", externalData = null) => { - let data = externalData || node.data; - data = convertToObject(data); - if (!!session) { - let obj = convertToObject(data[key]); - obj["magic_session"] = true; - obj[session] = val; - data[key] = obj; + const p = (t, r, n = "", e = null) => { + let o = e || l.data; + o = i(o); + if (!!n) { + let e = i(o[t]); + e["magic_session"] = true; + e[n] = r; + o[t] = e; } else { - data[key] = val; + o[t] = r; } - if (externalData !== null) { - externalData = data; + if (e !== null) { + e = o; } - return data; + return o; }; - const write = (key, val, session = "", externalData = null) => { - if (typeof val === "undefined" || val !== val) { + const h = (e, t, r = "", n = null) => { + if (typeof t === "undefined" || t !== t) { return false; } - if (!env.isNode && (typeof val === "boolean" || typeof val === "number")) { - val = String(val); + if (!s.isNode && (typeof t === "boolean" || typeof t === "number")) { + t = String(t); } - let data = ""; - if (externalData || env.isNode) { - data = writeForNode(key, val, session, externalData); + let o = ""; + if (n || s.isNode) { + o = p(e, t, r, n); } else { - if (!session) { - data = val; + if (!r) { + o = t; } else { - if (env.isSurgeLike) { - data = !!$persistentStore.read(key) - ? $persistentStore.read(key) - : data; - } else if (env.isQuanX) { - data = !!$prefs.valueForKey(key) ? $prefs.valueForKey(key) : data; + if (s.isSurgeLike) { + o = !!$persistentStore.read(e) ? $persistentStore.read(e) : o; + } else if (s.isQuanX) { + o = !!$prefs.valueForKey(e) ? $prefs.valueForKey(e) : o; } - data = convertToObject(data); - data["magic_session"] = true; - data[session] = val; + o = i(o); + o["magic_session"] = true; + o[r] = t; } } - if (!!data && typeof data === "object") { - data = JSON.stringify(data, null, 4); + if (!!o && typeof o === "object") { + o = JSON.stringify(o, null, 4); } - logger.debug( - `WRITE DATA [${key}]${ - session ? `[${session}]` : "" - } <${typeof val}>\n${JSON.stringify(val)}` + a.debug( + `WRITE DATA [${e}]${r ? `[${r}]` : ""} <${typeof t}>\n${JSON.stringify( + t + )}` ); - if (!externalData) { - if (env.isSurgeLike) { - return $persistentStore.write(data, key); - } else if (env.isQuanX) { - return $prefs.setValueForKey(data, key); - } else if (env.isNode) { + if (!n) { + if (s.isSurgeLike) { + return $persistentStore.write(o, e); + } else if (s.isQuanX) { + return $prefs.setValueForKey(o, e); + } else if (s.isNode) { try { - node.fs.writeFileSync("./magic.json", data); + l.fs.writeFileSync("./magic.json", o); return true; - } catch (err) { - logger.error(err); + } catch (e) { + a.error(e); return false; } } } return true; }; - const update = ( - key, - val, - session, - comparator = defaultValueComparator, - externalData = null - ) => { - val = _typeConvertor(val); - const oldValue = read(key, null, session, false, externalData); - if (comparator(oldValue, val) === true) { + const e = (t, r, n, o = c, i = null) => { + r = d(r); + const e = g(t, null, n, false, i); + if (o(e, r) === true) { return false; } else { - const result = write(key, val, session, externalData); - let newVal = read(key, null, session, false, externalData); - if (comparator === defaultValueComparator && typeof newVal === "object") { - return result; + const s = h(t, r, n, i); + let e = g(t, null, n, false, i); + if (o === c && typeof e === "object") { + return s; } - return comparator(val, newVal); + return o(r, e); } }; - const delForNode = (key, session, externalData) => { - let data = externalData || node.data; - data = convertToObject(data); - if (!!session) { - obj = convertToObject(data[key]); - delete obj[session]; - data[key] = obj; + const y = (e, t, r) => { + let n = r || l.data; + n = i(n); + if (!!t) { + obj = i(n[e]); + delete obj[t]; + n[e] = obj; } else { - delete data[key]; + delete n[e]; } - if (!!externalData) { - externalData = data; + if (!!r) { + r = n; } - return data; + return n; }; - const del = (key, session = "", externalData = null) => { - let data = {}; - if (externalData || env.isNode) { - data = delForNode(key, session, externalData); - if (!externalData) { - node.fs.writeFileSync("./magic.json", JSON.stringify(data, null, 4)); + const t = (e, t = "", r = null) => { + let n = {}; + if (r || s.isNode) { + n = y(e, t, r); + if (!r) { + l.fs.writeFileSync("./magic.json", JSON.stringify(n, null, 4)); } else { - externalData = data; + r = n; } } else { - if (!session) { - if (env.isStorm) { - return $persistentStore.remove(key); - } else if (env.isSurgeLike) { - return $persistentStore.write(null, key); - } else if (env.isQuanX) { - return $prefs.removeValueForKey(key); + if (!t) { + if (s.isStorm) { + return $persistentStore.remove(e); + } else if (s.isSurgeLike) { + return $persistentStore.write(null, e); + } else if (s.isQuanX) { + return $prefs.removeValueForKey(e); } } else { - if (env.isSurgeLike) { - data = $persistentStore.read(key); - } else if (env.isQuanX) { - data = $prefs.valueForKey(key); + if (s.isSurgeLike) { + n = $persistentStore.read(e); + } else if (s.isQuanX) { + n = $prefs.valueForKey(e); } - data = convertToObject(data); - delete data[session]; - const json = JSON.stringify(data, null, 4); - write(key, json); + n = i(n); + delete n[t]; + const o = JSON.stringify(n, null, 4); + h(e, o); } } - logger.debug(`DELETE KEY [${key}]${!!session ? `[${session}]` : ""}`); + a.debug(`DELETE KEY [${e}]${!!t ? `[${t}]` : ""}`); }; - const allSessionNames = (key, externalData = null) => { - let _sessions = []; - let data = read(key, null, null, true, externalData); - data = convertToObject(data); - if (data["magic_session"] !== true) { - _sessions = []; + const r = (e, t = null) => { + let r = []; + let n = g(e, null, null, true, t); + n = i(n); + if (n["magic_session"] !== true) { + r = []; } else { - _sessions = Object.keys(data).filter((key) => key !== "magic_session"); + r = Object.keys(n).filter((e) => e !== "magic_session"); } - logger.debug( - `READ ALL SESSIONS [${key}] <${typeof _sessions}>\n${JSON.stringify( - _sessions, - null, - 4 - )}` + a.debug( + `READ ALL SESSIONS [${e}] <${typeof r}>\n${JSON.stringify(r, null, 4)}` ); - return _sessions; + return r; }; - const allSessions = (key, externalData = null) => { - let _sessions = {}; - let data = read(key, null, null, true, externalData); - data = convertToObject(data); - if (data["magic_session"] === true) { - _sessions = { ...data }; - delete _sessions["magic_session"]; + const n = (e, t = null) => { + let r = {}; + let n = g(e, null, null, true, t); + n = i(n); + if (n["magic_session"] === true) { + r = { ...n }; + delete r["magic_session"]; } - logger.debug( - `READ ALL SESSIONS [${key}] <${typeof _sessions}>\n${JSON.stringify( - _sessions, - null, - 4 - )}` + a.debug( + `READ ALL SESSIONS [${e}] <${typeof r}>\n${JSON.stringify(r, null, 4)}` ); - return _sessions; + return r; }; return { - read: read, - write: write, - del: del, - update: update, - allSessions: allSessions, - allSessionNames: allSessionNames, - defaultValueComparator: defaultValueComparator, - convertToObject: convertToObject, + read: g, + write: h, + del: t, + update: e, + allSessions: n, + allSessionNames: r, + defaultValueComparator: c, + convertToObject: i, + }; +} +function MagicHttp(c, d) { + let r; + if (c.isNode) { + const a = require("axios"); + r = a.create(); + } + class e { + constructor(e = true) { + this.handlers = []; + this.isRequest = e; + } + use(e, t, r) { + if (typeof e === "function") { + d.debug(`Register fulfilled ${e.name}`); + } + if (typeof t === "function") { + d.debug(`Register rejected ${t.name}`); + } + this.handlers.push({ + fulfilled: e, + rejected: t, + synchronous: + r && typeof r.synchronous === "boolean" ? r.synchronous : false, + runWhen: r ? r.runWhen : null, + }); + return this.handlers.length - 1; + } + eject(e) { + if (this.handlers[e]) { + this.handlers[e] = null; + } + } + forEach(t) { + this.handlers.forEach((e) => { + if (e !== null) { + t(e); + } + }); + } + } + function o(e) { + let r = { ...e }; + if (!!r.params) { + if (!c.isNode) { + let e = Object.keys(r.params) + .map((e) => { + const t = encodeURIComponent(e); + r.url = r.url.replace(new RegExp(`${e}=[^&]*`, "ig"), ""); + r.url = r.url.replace(new RegExp(`${t}=[^&]*`, "ig"), ""); + return `${t}=${encodeURIComponent(r.params[e])}`; + }) + .join("&"); + if (r.url.indexOf("?") < 0) r.url += "?"; + if (!/(&|\?)$/g.test(r.url)) { + r.url += "&"; + } + r.url += e; + delete r.params; + d.debug(`Params to QueryString: ${r.url}`); + } + } + return r; + } + const u = (e, t) => { + let r = + typeof t === "object" ? { headers: {}, ...t } : { url: t, headers: {} }; + if (!r.method) { + r["method"] = e; + } + r = o(r); + if (r["rewrite"] === true) { + if (c.isSurge) { + r.headers["X-Surge-Skip-Scripting"] = false; + delete r["rewrite"]; + } else if (c.isQuanX) { + r["hints"] = false; + delete r["rewrite"]; + } + } + if (c.isSurgeLike) { + const n = r.headers["content-type"] || r.headers["Content-Type"]; + if ( + r["method"] !== "GET" && + n && + n.indexOf("application/json") >= 0 && + r.body instanceof Array + ) { + r.body = JSON.stringify(r.body); + d.debug(`Convert Array object to String: ${r.body}`); + } + } else if (c.isQuanX) { + if (r.hasOwnProperty("body") && typeof r["body"] !== "string") + r["body"] = JSON.stringify(r["body"]); + r["method"] = e; + } else if (c.isNode) { + if (e === "POST" || e === "PUT" || e === "PATCH" || e === "DELETE") { + r.data = r.data || r.body; + } else if (e === "GET") { + r.params = r.params || r.body; + } + delete r.body; + } + return r; + }; + const f = (t, r = null) => { + if (t) { + let e = { + ...t, + config: t.config || r, + status: t.statusCode || t.status, + body: t.body || t.data, + headers: t.headers || t.header, + }; + if (typeof e.body === "string") { + try { + e.body = JSON.parse(e.body); + } catch {} + } + delete e.data; + return e; + } else { + return t; + } + }; + const t = (r) => { + return Object.keys(r).reduce((e, t) => { + e[t.toLowerCase()] = r[t]; + return e; + }, {}); + }; + const n = (n) => { + return Object.keys(n).reduce((e, t) => { + const r = t + .split("-") + .map((e) => e[0].toUpperCase() + e.slice(1)) + .join("-"); + e[r] = n[t]; + return e; + }, {}); + }; + const g = (e, t = null) => { + if (!!e && e.status >= 400) { + d.debug(`Raise exception when status code is ${e.status}`); + return { + name: "RequestException", + message: `Request failed with status code ${e.status}`, + config: t || e.config, + response: e, + }; + } + }; + const i = { request: new e(), response: new e(false) }; + let p = []; + let h = []; + let y = true; + function $(e) { + e = o(e); + d.debug(`HTTP ${e["method"].toUpperCase()}:\n${JSON.stringify(e)}`); + return e; + } + function m(e) { + try { + e = !!e ? f(e) : e; + d.sniffer( + `HTTP ${e.config["method"].toUpperCase()}:\n${JSON.stringify( + e.config + )}\nSTATUS CODE:\n${e.status}\nRESPONSE:\n${ + typeof e.body === "object" ? JSON.stringify(e.body) : e.body + }` + ); + const t = g(e); + if (!!t) { + return Promise.reject(t); + } + return e; + } catch (t) { + d.error(t); + return e; + } + } + const _ = (t) => { + try { + p = []; + h = []; + i.request.forEach((e) => { + if (typeof e.runWhen === "function" && e.runWhen(t) === false) { + return; + } + y = y && e.synchronous; + p.unshift(e.fulfilled, e.rejected); + }); + i.response.forEach((e) => { + h.push(e.fulfilled, e.rejected); + }); + } catch (e) { + d.error(`Failed to register interceptors: ${e}.`); + } + }; + const s = (e, n) => { + let o; + const t = e.toUpperCase(); + n = u(t, n); + if (c.isNode) { + o = r; + } else { + if (c.isSurgeLike) { + o = (i) => { + return new Promise((n, o) => { + $httpClient[e.toLowerCase()](i, (t, r, e) => { + if (t) { + let e = { + name: t.name || t, + message: t.message || t, + stack: t.stack || t, + config: i, + response: f(r), + }; + o(e); + } else { + r.config = i; + r.body = e; + n(r); + } + }); + }); + }; + } else { + o = (o) => { + return new Promise((r, n) => { + $task + .fetch(o) + .then((e) => { + e = f(e, o); + const t = g(e, o); + if (t) { + return Promise.reject(t); + } + r(e); + }) + .catch((e) => { + let t = { + name: e.message || e.error, + message: e.message || e.error, + stack: e.error, + config: o, + response: !!e.response ? f(e.response) : null, + }; + n(t); + }); + }); + }; + } + } + let i; + _(n); + const s = [$, undefined]; + const a = [m, undefined]; + if (!y) { + d.debug("Interceptors are executed in asynchronous mode"); + let r = [o, undefined]; + Array.prototype.unshift.apply(r, s); + Array.prototype.unshift.apply(r, p); + r = r.concat(a); + r = r.concat(h); + i = Promise.resolve(n); + while (r.length) { + try { + let e = r.shift(); + let t = r.shift(); + if (!c.isNode && n["timeout"] && e === o) { + e = l; + } + if (typeof e === "function") { + d.debug(`Executing request fulfilled ${e.name}`); + } + if (typeof t === "function") { + d.debug(`Executing request rejected ${t.name}`); + } + i = i.then(e, t); + } catch (e) { + d.error(`request exception: ${e}`); + } + } + return i; + } else { + d.debug("Interceptors are executed in synchronous mode"); + Array.prototype.unshift.apply(p, s); + p = p.concat([$, undefined]); + while (p.length) { + let e = p.shift(); + let t = p.shift(); + try { + if (typeof e === "function") { + d.debug(`Executing request fulfilled ${e.name}`); + } + n = e(n); + } catch (e) { + if (typeof t === "function") { + d.debug(`Executing request rejected ${t.name}`); + } + t(e); + break; + } + } + try { + if (!c.isNode && n["timeout"]) { + i = l(n); + } else { + i = o(n); + } + } catch (e) { + return Promise.reject(e); + } + Array.prototype.unshift.apply(h, a); + while (h.length) { + i = i.then(h.shift(), h.shift()); + } + return i; + } + function l(r) { + try { + const e = new Promise((e, t) => { + setTimeout(() => { + let e = { + message: `timeout of ${r["timeout"]}ms exceeded.`, + config: r, + }; + t(e); + }, r["timeout"]); + }); + return Promise.race([o(r), e]); + } catch (e) { + d.error(`Request Timeout exception: ${e}.`); + } + } + }; + return { + request: s, + interceptors: i, + convertHeadersToLowerCase: t, + convertHeadersToCamelCase: n, + modifyResponse: f, + get: (e) => { + return s("GET", e); + }, + post: (e) => { + return s("POST", e); + }, + put: (e) => { + return s("PUT", e); + }, + patch: (e) => { + return s("PATCH", e); + }, + delete: (e) => { + return s("DELETE", e); + }, + head: (e) => { + return s("HEAD", e); + }, + options: (e) => { + return s("OPTIONS", e); + }, }; } -// @formatter:on diff --git a/Scripts/AdBlock/Zhihu/Zhihu.min.js b/Scripts/AdBlock/Zhihu/Zhihu.min.js new file mode 100644 index 0000000..8fa1c48 --- /dev/null +++ b/Scripts/AdBlock/Zhihu/Zhihu.min.js @@ -0,0 +1 @@ +const scriptName="哲也同学";const blockedUsersKey="zhihu_blocked_users";const currentUserInfoKey="zhihu_current_userinfo";const keywordBlockKey="zhihu_keyword_block";const blackAnswersIdKey="zhihu_black_answers";const userCreditScoreKey="zhihu_credit_score";const zheyeServerKey="zheye_server_url";const defaultAnswerBlockedUsers=["会员推荐","盐选推荐"];const keywordMaxCount=1e3;const $=MagicJS(scriptName,"INFO");function getUserInfo(){let t={id:"default",is_vip:false};try{let e=$.data.read(currentUserInfoKey);if(typeof e==="string")e=JSON.parse(e);if(!!e&&e.hasOwnProperty("id")){return e}else{return t}}catch(e){$.logger.error(`获取用户信息出现异常:${e}`);return t}}function modifyAppConfig(){let t=null;try{if(!!$.response.body){let e=JSON.parse($.response.body);e["config"]["homepage_feed_tab"]["tab_infos"]=e["config"]["homepage_feed_tab"]["tab_infos"].filter(e=>{if(e["tab_type"]==="activity_tab"){e["end_time"]=(new Date-12e4).toString().slice(0,10);return true}else{return false}});e["config"]["zvideo_max_number"]=1;e["config"]["is_show_followguide_alert"]=false;delete e["config"]["hp_channel_tab"];if(e["config"]["zombie_conf"]){e["config"]["zombie_conf"]["zombieEnable"]=false}if(e["config"]["gray_mode"]){e["config"]["gray_mode"]["enable"]=false;e["config"]["gray_mode"]["start_time"]="4092566400";e["config"]["gray_mode"]["end_time"]="4092566400"}if(e["config"].hasOwnProperty("zhcnh_thread_sync")){$.logger.debug(JSON.stringify(e["config"]["zhcnh_thread_sync"]));e["config"]["zhcnh_thread_sync"]["LocalDNSSetHostWhiteList"]=[];e["config"]["zhcnh_thread_sync"]["isOpenLocalDNS"]="0";e["config"]["zhcnh_thread_sync"]["ZHBackUpIP_Switch_Open"]="0";e["config"]["zhcnh_thread_sync"]["dns_ip_detector_operation_lock"]="1";e["config"]["zhcnh_thread_sync"]["ZHHTTPSessionManager_setupZHHTTPHeaderField"]="1"}t={body:JSON.stringify(e)}}}catch(e){$.logger.error(`优化软件配置出现异常:${e}`)}return t}function modifyMCloudConfig(){let t=null;try{if(!!$.response.body){let e=JSON.parse($.response.body);if(e.data&&e.data["configs"]){e.data["configs"].forEach(e=>{if(e["configKey"]==="feed_gray_theme"){e["configValue"].start_time="1669824000";e["configValue"].end_time="1669824001";e.status=false}})}const r=JSON.stringify(e);$.logger.debug(r);t={body:r}}}catch(e){$.logger.error(`优化软件配置出现异常:${e}`)}return t}function unlockBlockedKeywords(){let i=null;try{const s=getUserInfo();if($.request.method==="GET"){let e=$.data.read(keywordBlockKey,null,s.id);if(!e){e=[]}let t={"Cache-Control":"no-cache, no-store, must-revalidate, private, max-age=0",Connection:"keep-alive","Content-Type":"application/json;charset=utf-8",Pragma:"no-cache","Referrer-Policy":"no-referrer-when-downgrade",Server:"CLOUD ELB 1.0.0",Vary:"Accept-Encoding","X-Cache-Lookup":"Cache Miss","x-cdn-provider":"tencent"};let r=JSON.stringify({success:true,is_vip:true,kw_min_length:2,kw_max_length:100,kw_max_count:keywordMaxCount,data:e});if($.env.isQuanX){i={body:r,headers:t,status:"HTTP/1.1 200 OK"}}else{i={response:{body:r,headers:t,status:200}}}$.logger.debug(`获取本地脚本屏蔽关键词:\n${e.join("、")}`)}else if($.request.method==="POST"){if(!!$.request.body){let t={"Cache-Control":"no-cache, no-store, must-revalidate, private, max-age=0",Connection:"keep-alive","Content-Type":"application/json;charset=utf-8",Pragma:"no-cache","Referrer-Policy":"no-referrer-when-downgrade",Server:"CLOUD ELB 1.0.0",Vary:"Accept-Encoding","X-Cache-Lookup":"Cache Miss","x-cdn-provider":"tencent"};let r=decodeURIComponent($.request.body).match(/keyword=(.*)/)[1];let n=$.data.read(keywordBlockKey,null,s.id);if(!n){n=[]}let o=false;for(let e=0;e{return e!==t});$.data.write(keywordBlockKey,e,s.id);let r={"Cache-Control":"no-cache, no-store, must-revalidate, private, max-age=0",Connection:"keep-alive","Content-Type":"application/json;charset=utf-8",Pragma:"no-cache","Referrer-Policy":"no-referrer-when-downgrade",Server:"CLOUD ELB 1.0.0",Vary:"Accept-Encoding","X-Cache-Lookup":"Cache Miss","x-cdn-provider":"tencent"};let n=JSON.stringify({success:true});if($.env.isQuanX){i={body:n,headers:r,status:"HTTP/1.1 200 OK"}}else{i={response:{body:n,headers:r,status:200}}}$.logger.debug(`删除本地脚本屏蔽关键词:“${t}”`)}}catch(e){$.logger.debug(`关键词屏蔽操作出现异常:${e}`)}return i}function processUserInfo(){let t=null;try{let e=JSON.parse($.response.body);$.data.write(blackAnswersIdKey,[]);$.logger.debug(`用户登录用户信息,接口响应:${$.response.body}`);if(e&&e["id"]&&e.hasOwnProperty("vip_info")&&e["vip_info"].hasOwnProperty("is_vip")){const r={id:e["id"],is_vip:e["vip_info"]["is_vip"]?e["vip_info"]["is_vip"]!==undefined:false};$.logger.debug(`当前用户id:${e["id"]},是否为VIP:${e["vip_info"]["is_vip"]}`);$.data.write(currentUserInfoKey,r);if($.data.read("zhihu_settings_fake_vip")!==false&&e["vip_info"]["is_vip"]===false){e["vip_info"]["is_vip"]=true;e["vip_info"]["vip_type"]=2;e["vip_info"]["vip_icon"]={url:"https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae",night_mode_url:"https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae"};e["vip_info"]["vip_icon_v2"]={url:"https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae",night_mode_url:"https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae"};e["vip_info"]["entrance"]={icon:{url:"https://pic3.zhimg.com/v2-5b7012c8c22fd520f5677305e1e551bf.png",night_mode_url:"https://pic4.zhimg.com/v2-e51e3252d7a2cb016a70879defd5da0b.png"},title:"盐选会员 为你严选好内容",expires_day:"2099-12-31",sub_title:null,button_text:"首月 9 元",jump_url:"zhihu://vip/purchase",button_jump_url:"zhihu://vip/purchase",sub_title_new:null,identity:"super_svip"};e["vip_info"]["entrance_new"]={left_button:{title:"精选会员内容",description:"为您严选好内容",jump_url:"zhihu://market/home"},right_button:{title:"开通盐选会员",description:"畅享 10w+ 场优质内容等特权",jump_url:"zhihu://vip/purchase"}};e["vip_info"]["entrance_v2"]={title:"我的超级盐选会员",sub_title:"畅享 5000W+ 优质内容",jump_url:"zhihu://market/home",button_text:"查看会员",sub_title_color:"#F8E2C4",sub_title_list:["畅享 5000W+ 优质内容"],card_jump_url:"zhihu://market/home"};$.logger.debug("设置用户为本地盐选会员");t={body:JSON.stringify(e)}}}else{$.logger.warning(`没有获取到本次登录用户信息,如未对功能造成影响,请忽略此日志。`)}}catch(e){$.logger.error(`获取当前用户信息出现异常:${e}`)}return t}function manageBlackUser(){const r=getUserInfo();let t={};let n=$.data.read(blockedUsersKey,"",r.id);n=typeof n==="object"&&!!n?n:{};defaultAnswerBlockedUsers.forEach(e=>{n[e]="00000000000000000000000000000000";t[e]="00000000000000000000000000000000"});$.logger.debug(`当前用户id:${r.id},脚本黑名单:${JSON.stringify(n)}`);if($.request.method==="GET"){try{if($.request.url.indexOf("offset")<0){n=t;$.logger.debug("脚本黑名单已清空,请滑动至黑名单末尾保证重新获取完成。");$.notification.post("开始同步黑名单数据,请滑动至黑名单末尾,直至弹出“同步成功”的通知。")}let e=JSON.parse($.response.body);if(!!e["data"]){$.logger.debug(`本次滑动获取的黑名单信息:${JSON.stringify(e["data"])}`);e["data"].forEach(e=>{if(e["name"]!=="[已重置]"){n[e["name"]]=e["id"]}});$.data.write(blockedUsersKey,n,r.id);if(e["paging"]["is_end"]===true){$.notification.post(`同步黑名单数据成功!当前黑名单共${Object.keys(n).length-defaultAnswerBlockedUsers.length}人。\n脚本内置黑名单${defaultAnswerBlockedUsers.length}人。`);$.logger.debug(`脚本黑名单内容:${JSON.stringify(n)}。`)}}else{$.logger.warning(`获取黑名单失败,接口响应不合法:${$.response.body}`)}}catch(e){$.data.del(blockedUsersKey);$.logger.error(`获取黑名单失败,异常信息:${e}`);$.notification.post("获取黑名单失败,执行异常,已清空黑名单。")}}else if($.request.method==="POST"){try{let e=JSON.parse($.response.body);if(e.hasOwnProperty("name")&&e.hasOwnProperty("id")){$.logger.debug(`当前需要加入黑名单的用户Id:${e["id"]},用户名:${e["name"]}`);if(e["id"]){n[e["name"]]=e["id"];$.data.write(blockedUsersKey,n,r.id);$.logger.debug(`${e["name"]}写入脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify(n)}`);$.notification.post(`已将用户“${e["name"]}”写入脚本黑名单。`)}else{$.logger.error(`${e["name"]}写入脚本黑名单失败,没有获取到用户Id。`);$.notification.post(`将用户“${e["name"]}”写入脚本黑名单失败!`)}}else{$.logger.warning(`写入黑名单失败,接口响应不合法:${$.response.body}`);$.notification.post("写入脚本黑名单失败,接口返回不合法。")}}catch(e){$.logger.error(`写入黑名单失败,异常信息:${e}`);$.notification.post("写入脚本黑名单失败,执行异常,请查阅日志。")}}else if($.request.method==="DELETE"){try{let e=JSON.parse($.response.body);if(e.success){let t=$.request.url.match(/^https?:\/\/api\.zhihu\.com\/settings\/blocked_users\/([0-9a-zA-Z]*)/)[1];if(t){$.logger.debug(`当前需要移出黑名单的用户Id:${t}`);for(let e in n){if(n[e]===t){delete n[e];$.data.write(blockedUsersKey,n,r.id);$.logger.debug(`${e}移出脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify(n)}`);$.notification.post(`已将用户“${e}”移出脚本黑名单!`);break}}}else{$.logger.error("将用户移出脚本黑名单失败!\n建议从设置中刷新黑名单数据。");$.notification.post(`将用户移出脚本黑名单失败,没有获取到用户Id。\n建议从设置中刷新黑名单数据。`)}}else{$.logger.warning(`移出黑名单失败,接口响应不合法:${$.response.body}`);$.notification.post("移出脚本黑名单失败,接口返回不合法。")}}catch(e){$.logger.error(`移出黑名单失败,异常信息:${e}`);$.notification.post("移出脚本黑名单失败,执行异常,请查阅日志。")}}}function autoInsertBlackList(){let e=null;try{let t=JSON.parse($.response.body);delete t["mcn_user_info"];e={body:JSON.stringify(t)};if(t.name&&t.id&&t["is_blocking"]===true){const r=getUserInfo();let e=$.data.read(blockedUsersKey,"",r.id);e=typeof e==="object"&&!!e?e:{};if(!e[t.name]){$.logger.debug(`当前需要加入黑名单的用户Id:${t["id"]},用户名:${t["name"]}`);e[t["name"]]=t["id"];$.data.write(blockedUsersKey,e,r.id);$.logger.debug(`${t["name"]}写入脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify(e)}`);$.notification.post(`已自动将用户“${t["name"]}”写入脚本黑名单。`)}}}catch(e){$.logger.error(`去除MCN信息出现异常:${e}`)}return e}function removeMoments(){let r=null;try{let e=JSON.parse($.response.body.replace(/(\w+"+\s?):\s?(\d{15,})/g,'$1:"$2"'));const n=getUserInfo();let i=$.data.read(blockedUsersKey,"",n.id);i=!!i?i:{};let t;const s=$.data.read("zhihu_settings_moments_stream",false);const a=$.data.read("zhihu_settings_moments_recommend",false);const l=$.data.read("zhihu_settings_moments_activity",false);const c=$.data.read("zhihu_settings_blocked_users",false);t=e.data.filter(e=>{const t=c&&e.target&&e.target["origin_pin"]&&e.target["origin_pin"].author&&typeof i[e.target["origin_pin"].author.name]!="undefined";const r=s&&e["target_type"]==="zvideo";const n=a&&e.type==="recommend_user_card_list";const o=l&&e.type==="message_activity_card";return!(t||r||n||o)});e["data"]=t;r={body:JSON.stringify(e)}}catch(e){$.logger.error(`关注列表去广告出现异常:${e}`)}return r}function _setRecommendTag(e,t,r="GBK02A"){if(!e["common_card"]["footline"]){e["common_card"]["footline"]={elements:[]}}e["common_card"]["footline"]["elements"].unshift({tag:{text:t,color:r,type:"MASK_ROUNDED_RECTANGLE"}})}async function _checkPaidContentByCloud(e,n){const t=$.data.read(zheyeServerKey);if(!t){$.notification.post("未设置服务端地址,无法进行付费/推广内容探测。\n请配置服务端地址,或使用本地探测。")}else{$.logger.debug(`向云端请求以下链接\n${e.join("\n")}`);const r=`${t}/api/v1/answer/links`;$.logger.debug(`服务端地址\n${r}`);await $.http.post({url:r,headers:{"Content-Type":"application/json"},body:e}).then(r=>{$.logger.debug(`云端探测结果<${typeof r.body}>\n${JSON.stringify(r.body)}`);for(let t=0;t{$.logger.error(`云端请求出现异常\n${JSON.stringify(e)}`)})}}async function _checkPaidContentByLocal(n,o){$.logger.debug(`将在本地请求以下链接\n${n.join("\n")}`);let t=[];function r(t){return new Promise(r=>{const e=n[t];if(!e||e===""||!e.startsWith("https://www.zhihu.com/appview/v2/answer")){r("")}else{$.http.get({url:n[t],timeout:1e3}).then(e=>{const t=e.body;if((t.indexOf("查看完整内容")>=0||t.indexOf("查看全部章节")>=0)&&t.indexOf("paid")>=0){r("付费内容")}else if(t.indexOf("ad-link-card")>=0||t.indexOf("xg.zhihu.com")>=0||t.indexOf("营销平台")>=0){r("营销推广")}else if(t.indexOf("mcn-link-card")>=0){r("购物推广")}else{r("")}}).catch(e=>{$.logger.error(`本地请求出现异常\n${JSON.stringify(e)}`);r("")})}})}for(let e=0;e{$.logger.debug(`本地探测结果<${r.length}>\n${JSON.stringify(r)}`);for(let t=0;t{const e=JSON.stringify(o);const t=o["card_type"]==="slot_event_card"||o["card_type"]==="slot_video_event_card"||o.hasOwnProperty("ad")||o["brief"]&&o["brief"].indexOf("slot_card")>=0||o["extra"]&&o["extra"]["type"]==="Training";const r=t!==true&&e.search(/"(type|style)+"\s?:\s?"(drama|zvideo|Video|BIG_IMAGE)+"/i)>=0;const n=r&&p;const i=r!==true&&e.search(/"(type|style)+"\s?:\s?"pin"/i)>=0;const s=i&&g;const a=e.search(/"(type|style)+"\s?:\s?"article"/i)>=0;const l=a&&h;let c=false;if(r!==true&&m){for(let n=0;n=0){if($.isDebug){let e=o["common_card"]["feed_content"]["title"]["panel_text"];let t=o["common_card"]["feed_content"]["content"]["panel_text"];let r="";try{r=o["common_card"]["feed_content"]["title"]["action"]["intent_url"]}catch{}$.logger.debug(`匹配关键字:\n${u[n]}\n标题:\n${e}\n内容:\n${t}`);$.notification.debug(scriptName,`关键字:${u[n]}`,`${e}\n${t}`,r)}c=true;break}}}let d;try{d=c!==true&&y&&f&&o["common_card"]["feed_content"]["source_line"]["elements"][1]["text"]["panel_text"]in f}catch{d=false}return!(t||s||l||n||c||d)};let e=JSON.parse($.response.body.replace(/(\w+"+\s?):\s?(\d{15,})/g,'$1:"$2"'));let n=e["data"].filter(s);if(r===true){let r=[];for(let t=0;t0&&o==="cloud"){await _checkPaidContentByCloud(r,n)}else if(r.length>0&&o==="local"){await _checkPaidContentByLocal(r,n)}}e["data"]=n;t={body:JSON.stringify(e)}}catch(e){$.logger.error(`推荐列表去广告出现异常:${e}`)}return t}function removeQuestions(){let t=null;try{const r=getUserInfo();let n=$.data.read(blockedUsersKey,"",r.id);n=!!n?n:{};let e=JSON.parse($.response.body);const i=$.data.read("zhihu_settings_blocked_users",false);$.logger.debug(`当前黑名单列表: ${JSON.stringify(n)}`);let o=$.data.read(blackAnswersIdKey,[]);delete e["ad_info"];if(e["data"]){let r=[];for(let t of e.data){let e="";const a=t.target.id.toString();try{if("target"in t){e=t["target"]["author"]["name"]}}catch(e){$.logger.error(`获取回答列表用户名出现异常:${e}`)}const l=typeof n[e]!="undefined";const c=i&&l;if("target"in t){t["target"]["visible_only_to_author"]=false;t["target"]["is_visible"]=true;t["target"]["is_copyable"]=true}if(!c){r.push(t)}else if(c===true&&o.includes(a)===false){o.push(a);$.notification.debug(`记录黑名单用户${e}的回答Id:${a}`)}}e.data=r}$.data.write(blackAnswersIdKey,o);const s=JSON.stringify(e);$.logger.debug(`修改后的回答列表数据:${s}`);t={body:s}}catch(e){$.logger.error(`回答列表去广告出现异常:${e}`)}return t}function modifyAnswer(){let r=null;try{let e=$.response.body;let t="";if((e.indexOf("查看完整内容")>=0||e.indexOf("查看全部章节")>=0)&&e.indexOf("paid")>=0){t='
本文为付费内容
'}else if(e.indexOf("ad-link-card")>=0||e.indexOf("xg.zhihu.com")>=0||e.indexOf("营销平台")>=0){t='
本文含有营销推广
'}else if(e.indexOf("mcn-link-card")>=0){t='
本文含有购物推广
'}else if(Math.floor(Math.random()*200)===7){t='
本文为免费内容
'}if(t!==""){const n=e.match(/(richText[^<]*>)(.)/)[1];const o=e.lastIndexOf(n)+n.length;r={body:e.slice(0,o)+t+e.slice(o)}}}catch(e){$.logger.error(`付费内容提醒出现异常:${e}`)}return r}function removeComment(){let e=null;try{if(!!$.response.body){let t=JSON.parse($.response.body);t["ad_info"]={};if($.data.read("zhihu_settings_blocked_users",false)===true){let e=getUserInfo();let s=$.data.read(blockedUsersKey,"",e.id);s=!!s?s:{};let a=[];let l={};if(typeof t.root!="undefined"){const n=t.root.author.name;const o=typeof s[n]!="undefined";if(o===true){t.root.is_delete=true;t.root.can_reply=false;t.root.can_like=false;t.root.author.name="黑名单用户";t.root.author.avatar_url="https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"}}if(typeof t.data!="undefined"){t.data.forEach(e=>{const t=e.author.name;let r="";if(e["reply_to_author"]&&e["reply_to_author"].name){r=e["reply_to_author"].name}const n=r!=="";const o=typeof s[t]!="undefined";const i=typeof s[r]!="undefined";if(o===true||i===true){if(o&&!n&&$.request.url.indexOf("root_comment")>0){$.notification.debug(`屏蔽黑名单用户“${t}”的主评论。`)}else if(!o&&n&&!i&&$.request.url.indexOf("child_comment")>0){$.notification.debug(`屏蔽黑名单用户“${t}”的子评论。`)}else if(o&&!i&&$.request.url.indexOf("child_comment")>0){$.notification.debug(`屏蔽黑名单用户“${t}”回复“${r}”的子评论。`)}else if(o&&i&&$.request.url.indexOf("child_comment")>0){$.notification.debug(`屏蔽黑名单用户“${t}”回复黑名单用户“${r}”的子评论。`)}l[e.id]=t;if(o){e.is_delete=true;e.can_reply=false;e.can_like=false;e.author.exposed_medal={};e.author.name="[黑名单用户]";e.author.avatar_url="https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"}if(i){e["reply_to_author"].name="[黑名单用户]";e["reply_to_author"].exposed_medal={};e["reply_to_author"].avatar_url="https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"}}if(e.child_comments){let i=[];e.child_comments.forEach(e=>{const t=e.author.name;const r=typeof e["reply_to_author"]!="undefined"?e["reply_to_author"].name:"";const n=typeof s[t]!="undefined";const o=typeof s[r]!="undefined";if(n||o){if(n===true){$.notification.debug(`屏蔽黑名单用户“${t}”的子评论。`);l[e.id]=t;e.is_delete=true;e.can_reply=false;e.can_like=false;e.author.name="[黑名单用户]";e.author.exposed_medal={};e.author.avatar_url="https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"}if(o===true){$.logger.debug(`修改前的子评论数据:\n${JSON.stringify(e)}`);e["reply_to_author"].name="[黑名单用户]";e["reply_to_author"].exposed_medal={};e["reply_to_author"].avatar_url="https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg";$.notification.debug(`隐藏“${t}”回复黑名单用户“${r}”的名称与头像。`);$.logger.debug(`修改后的子评论数据:\n${JSON.stringify(e)}`)}}i.push(e)});e.child_comments=i}a.push(e)})}t.data=a}const r=JSON.stringify(t);$.logger.debug(`过滤后的评论数据:\n${r}`);e={body:r}}}catch(e){$.logger.error(`去除评论广告出现异常:${e}`)}return e}function removeArticleAd(){let t=null;try{if(!!$.response.body){let e=JSON.parse($.response.body);e["ad_info"]={};const r=JSON.stringify(e);$.logger.debug(`过滤后的文章数据:\n${r}`);t={body:r}}}catch(e){$.logger.error(`去除文章广告出现异常:${e}`)}return t}function removeMarketingMsg(){let t=null;try{let e=JSON.parse($.response.body);let r=[];for(let t of e["data"]){if(t["detail_title"]==="官方帐号消息"){let e=t["unread_count"];if(e>0){t["content"]["text"]="未读消息"+e+"条"}else{t["content"]["text"]="全部消息已读"}t["is_read"]=true;t["unread_count"]=0;r.push(t)}else if(t["detail_title"]!=="活动助手"){r.push(t)}}e["data"]=r;t={body:JSON.stringify(e)}}catch(e){$.logger.error(`屏蔽官方营销消息出现异常:${e}`)}return t}function removeHotListAds(){let t=null;try{if(!!$.response.body){let e=JSON.parse($.response.body);if("data"in e){e["data"]=e["data"].filter(e=>{return e["type"]==="hot_list_feed"||e["type"]==="hot_list_feed_video"})}t={body:JSON.stringify(e)}}}catch(e){$.logger.error(`去除热榜广告出现异常:${e}`)}return t}function removeKeywordAds(){let t=null;try{if(!!$.response.body){$.logger.debug(`预置关键字返回:${$.response.body}`);let e=JSON.parse($.response.body);if(e.hasOwnProperty("preset_words")&&e["preset_words"]["words"]){e["preset_words"]["words"]=e["preset_words"]["words"].filter(e=>{return e["type"]!=="ad"});t={body:JSON.stringify(e)}}}}catch(e){$.logger.error(`去除预置关键字广告出现异常:${e}`)}return t}function removeNextBlackUserAnswer(){let t=null;try{if(!!$.response.body){let e=JSON.parse($.response.body);const n=$.data.read(blackAnswersIdKey,[]);if(n.length>0){let r=[];e.data.forEach(e=>{const t=n.includes(e.id.toString());if(t===false){e.ad_info={data:""};r.push(e)}else{$.notification.debug(`屏蔽翻页过程中出现的黑名单用户回答Id:${e.id}`)}});for(let e=0;e{e["ad_info"]={data:""};const t=typeof r[e.data.author.name]!="undefined";$.logger.debug(`用户${e.data.author.name}是否在黑名单中:${t}`);if($.data.read("zhihu_settings_blocked_users",false)===false||t===false){n.push(e)}});e.data.data=n;o={body:JSON.stringify(e)}}}catch(e){$.logger.error(`屏蔽回答信息流黑名单用户及广告:${e}`)}return o}function changeUserCredit(){$.notification.debug("开始修改用户盐值");let t=null;try{if(!!$.response.body){const r=parseInt($.data.read(userCreditScoreKey,780));$.logger.debug(`准备修改用户盐值为${r}`);let e=JSON.parse($.response.body);if(e["credit_basis"].total_score{let e=null;if($.isResponse){switch(true){case/^https:\/\/api\.zhihu\.com\/people\/self$/.test($.request.url):e=processUserInfo();break;case $.data.read("zhihu_settings_app_conf",false)===true&&/^https?:\/\/appcloud2\.zhihu\.com\/v\d+\/config/.test($.request.url):e=modifyAppConfig();break;case $.data.read("zhihu_settings_app_conf",false)===true&&/^https?:\/\/m-cloud\.zhihu\.com\/api\/cloud\/config\/all\?/.test($.request.url):e=modifyMCloudConfig();break;case/^https?:\/\/api\.zhihu\.com\/user-credit\/basis/.test($.request.url):e=changeUserCredit();break;case/^https:\/\/api\.zhihu\.com\/topstory\/recommend/.test($.request.url):e=await removeRecommend();break;case/^https?:\/\/api\.zhihu\.com\/(v4\/)?questions\/\d+/.test($.request.url):e=removeQuestions();break;case/^https?:\/\/api\.zhihu\.com\/next-data\?/.test($.request.url):e=modifyAnswersNextData();break;case $.data.read("zhihu_settings_sys_msg",true)!==false&&/^https?:\/\/api\.zhihu\.com\/notifications\/v3\/message/.test($.request.url):e=removeMarketingMsg();break;case/^https?:\/\/api\.zhihu\.com\/comment_v5\/(answers|pins|comments?|articles)\/\d+\/(root|child)_comment/.test($.request.url):e=removeComment();break;case/^https?:\/\/www\.zhihu\.com\/api\/v\d\/articles\/\d+\/recommendation\?/.test($.request.url):e=removeArticleAd();break;case/^https?:\/\/www\.zhihu\.com\/api\/v4\/comment_v5\/answers\/\d+\/abstract_comment\?/.test($.request.url):e=removeComment();break;case $.data.read("zhihu_settings_answer_tip",true)===true&&/^https?:\/\/www\.zhihu\.com\/appview\/v2\/answer\/.*(entry=(?!(preload-topstory|preload-search|preload-subscription)))?/.test($.request.url):e=modifyAnswer();break;case $.data.read("zhihu_settings_blocked_users",false)!==false&&/^https?:\/\/api\.zhihu\.com\/next\?/.test($.request.url):e=removeNextBlackUserAnswer();break;case $.data.read("zhihu_settings_blocked_users",true)===true&&/^https?:\/\/api\.zhihu\.com\/people\/((?!self).)*$/.test($.request.url):e=autoInsertBlackList();break;case/^https?:\/\/api\.zhihu\.com\/moments_v3\?/.test($.request.url):e=removeMoments();break;case $.data.read("zhihu_settings_hot_list",true)===true&&/^https?:\/\/api\.zhihu\.com\/topstory\/hot-lists(\?|\/)/.test($.request.url):e=removeHotListAds();break;case $.data.read("zhihu_settings_preset_words",true)===true&&/^https?:\/\/api\.zhihu\.com\/search\/preset_words\?/.test($.request.url):e=removeKeywordAds();break;case $.data.read("zhihu_settings_blocked_users",false)!==false&&/^https?:\/\/api\.zhihu\.com\/settings\/blocked_users/.test($.request.url):manageBlackUser();break;default:$.logger.debug("没有匹配到任何请求,请确认配置正确。");break}}else if($.isRequest){if($.data.read("zhihu_settings_blocked_keywords",false)!==false&&/^https?:\/\/api\.zhihu\.com\/feed-root\/block/.test($.request.url)===true){e=unlockBlockedKeywords(e)}}else{$.data.del(currentUserInfoKey);$.data.del(blockedUsersKey);$.data.del(keywordBlockKey);$.notification.post("哲也同学数据清理完毕")}if(e){$.done(e)}else{$.done()}})();function MagicJS(e="MagicJS",t="INFO"){const n=()=>{const e=typeof $loon!=="undefined";const t=typeof $task!=="undefined";const r=typeof module!=="undefined";const n=typeof $httpClient!=="undefined"&&!e;const o=typeof $storm!=="undefined";const i=typeof $environment!=="undefined"&&typeof $environment["stash-build"]!=="undefined";const s=n||e||o||i;const a=typeof importModule!=="undefined";return{isLoon:e,isQuanX:t,isNode:r,isSurge:n,isStorm:o,isStash:i,isSurgeLike:s,isScriptable:a,get name(){if(e){return"Loon"}else if(t){return"QuantumultX"}else if(r){return"NodeJS"}else if(n){return"Surge"}else if(a){return"Scriptable"}else{return"unknown"}},get build(){if(n){return $environment["surge-build"]}else if(i){return $environment["stash-build"]}else if(o){return $storm.buildVersion}},get language(){if(n||i){return $environment["language"]}},get version(){if(n){return $environment["surge-version"]}else if(i){return $environment["stash-version"]}else if(o){return $storm.appVersion}else if(r){return process.version}},get system(){if(n){return $environment["system"]}else if(r){return process.platform}},get systemVersion(){if(o){return $storm.systemVersion}},get deviceName(){if(o){return $storm.deviceName}}}};const o=(r,e="INFO")=>{let n=e;const o={SNIFFER:6,DEBUG:5,INFO:4,NOTIFY:3,WARNING:2,ERROR:1,CRITICAL:0,NONE:-1};const i={SNIFFER:"",DEBUG:"",INFO:"",NOTIFY:"",WARNING:"❗ ",ERROR:"❌ ",CRITICAL:"❌ ",NONE:""};const t=(e,t="INFO")=>{if(!(o[n]{n=e};return{getLevel:()=>{return n},setLevel:s,sniffer:e=>{t(e,"SNIFFER")},debug:e=>{t(e,"DEBUG")},info:e=>{t(e,"INFO")},notify:e=>{t(e,"NOTIFY")},warning:e=>{t(e,"WARNING")},error:e=>{t(e,"ERROR")},retry:e=>{t(e,"RETRY")}}};return new class{constructor(e,t){this._startTime=Date.now();this.version="3.0.0";this.scriptName=e;this.env=n();this.logger=o(e,t);this.http=typeof MagicHttp==="function"?MagicHttp(this.env,this.logger):undefined;this.data=typeof MagicData==="function"?MagicData(this.env,this.logger):undefined;this.notification=typeof MagicNotification==="function"?MagicNotification(this.scriptName,this.env,this.logger,this.http):undefined;this.utils=typeof MagicUtils==="function"?MagicUtils(this.env,this.logger):undefined;this.qinglong=typeof MagicQingLong==="function"?MagicQingLong(this.env,this.data,this.logger):undefined;if(typeof this.data!=="undefined"){let e=this.data.read("magic_loglevel");const r=this.data.read("magic_bark_url");if(e){this.logger.setLevel(e.toUpperCase())}if(r){this.notification.setBark(r)}}}get isRequest(){return typeof $request!=="undefined"&&typeof $response==="undefined"}get isResponse(){return typeof $response!=="undefined"}get isDebug(){return this.logger.level==="DEBUG"}get request(){return typeof $request!=="undefined"?$request:undefined}get response(){if(typeof $response!=="undefined"){if($response.hasOwnProperty("status"))$response["statusCode"]=$response["status"];if($response.hasOwnProperty("statusCode"))$response["status"]=$response["statusCode"];return $response}else{return undefined}}done=(e={})=>{this._endTime=Date.now();let t=(this._endTime-this._startTime)/1e3;this.logger.info(`SCRIPT COMPLETED: ${t} S.`);if(typeof $done!=="undefined"){$done(e)}}}(e,t)}function MagicNotification(i,s,a,l){let c=null;let d=null;const e=t=>{try{let e=t.replace(/\/+$/g,"");c=`${/^https?:\/\/([^/]*)/.exec(e)[0]}/push`;d=/\/([^\/]+)\/?$/.exec(e)[1]}catch(e){a.error(`Bark url error: ${e}.`)}};function t(e=i,t="",r="",n=""){const o=r=>{try{let t={};if(typeof r==="string"){if(s.isLoon)t={openUrl:r};else if(s.isQuanX)t={"open-url":r};else if(s.isSurge)t={url:r}}else if(typeof r==="object"){if(s.isLoon){t["openUrl"]=!!r["open-url"]?r["open-url"]:"";t["mediaUrl"]=!!r["media-url"]?r["media-url"]:""}else if(s.isQuanX){t=!!r["open-url"]||!!r["media-url"]?r:{}}else if(s.isSurge){let e=r["open-url"]||r["openUrl"];t=e?{url:e}:{}}}return t}catch(e){a.error(`通知选项转换失败${e}`)}return r};n=o(n);if(arguments.length===1){e=i;t="",r=arguments[0]}a.notify(`title:${e}\nsubTitle:${t}\nbody:${r}\noptions:${typeof n==="object"?JSON.stringify(n):n}`);if(s.isSurge){$notification.post(e,t,r,n)}else if(s.isLoon){if(!!n)$notification.post(e,t,r,n);else $notification.post(e,t,r)}else if(s.isQuanX){$notify(e,t,r,n)}if(c&&d){u(e,t,r)}}function r(e=i,t="",r="",n=""){if(a.getLevel()==="DEBUG"){if(arguments.length===1){e=i;t="";r=arguments[0]}this.post(e,t,r,n)}}function u(e=i,t="",r="",n=""){if(typeof l==="undefined"||typeof l.post==="undefined"){throw"Bark notification needs to import MagicHttp module."}let o={url:c,headers:{"content-type":"application/json; charset=utf-8"},body:{title:e,body:t?`${t}\n${r}`:r,device_key:d}};l.post(o).catch(e=>{a.error(`Bark notify error: ${e}`)})}return{post:t,debug:r,bark:u,setBark:e}}function MagicData(s,a){let l={fs:undefined,data:{}};if(s.isNode){l.fs=require("fs");try{l.fs.accessSync("./magic.json",l.fs.constants.R_OK|l.fs.constants.W_OK)}catch(e){l.fs.writeFileSync("./magic.json","{}",{encoding:"utf8"})}l.data=require("./magic.json")}const c=(e,t)=>{if(typeof t==="object"){return false}else{return e===t}};const d=e=>{if(e==="true"){return true}else if(e==="false"){return false}else if(typeof e==="undefined"){return null}else{return e}};const u=(e,t,r,n)=>{if(r){try{if(typeof e==="string")e=JSON.parse(e);if(e["magic_session"]===true){e=e[r]}else{e=null}}catch{e=null}}if(typeof e==="string"&&e!=="null"){try{e=JSON.parse(e)}catch{}}if(n===false&&!!e&&e["magic_session"]===true){e=null}if((e===null||typeof e==="undefined")&&t!==null&&typeof t!=="undefined"){e=t}e=d(e);return e};const i=t=>{if(typeof t==="string"){let e={};try{e=JSON.parse(t);const r=typeof e;if(r!=="object"||e instanceof Array||r==="bool"||e===null){e={}}}catch{}return e}else if(t instanceof Array||t===null||typeof t==="undefined"||t!==t||typeof t==="boolean"){return{}}else{return t}};const f=(e,t=null,r="",n=false,o=null)=>{let i=o||l.data;if(!!i&&typeof i[e]!=="undefined"&&i[e]!==null){val=i[e]}else{val=!!r?{}:null}val=u(val,t,r,n);return val};const g=(e,t=null,r="",n=false,o=null)=>{let i="";if(o||s.isNode){i=f(e,t,r,n,o)}else{if(s.isSurgeLike){i=$persistentStore.read(e)}else if(s.isQuanX){i=$prefs.valueForKey(e)}i=u(i,t,r,n)}a.debug(`READ DATA [${e}]${!!r?`[${r}]`:""} <${typeof i}>\n${JSON.stringify(i)}`);return i};const p=(t,r,n="",e=null)=>{let o=e||l.data;o=i(o);if(!!n){let e=i(o[t]);e["magic_session"]=true;e[n]=r;o[t]=e}else{o[t]=r}if(e!==null){e=o}return o};const h=(e,t,r="",n=null)=>{if(typeof t==="undefined"||t!==t){return false}if(!s.isNode&&(typeof t==="boolean"||typeof t==="number")){t=String(t)}let o="";if(n||s.isNode){o=p(e,t,r,n)}else{if(!r){o=t}else{if(s.isSurgeLike){o=!!$persistentStore.read(e)?$persistentStore.read(e):o}else if(s.isQuanX){o=!!$prefs.valueForKey(e)?$prefs.valueForKey(e):o}o=i(o);o["magic_session"]=true;o[r]=t}}if(!!o&&typeof o==="object"){o=JSON.stringify(o,null,4)}a.debug(`WRITE DATA [${e}]${r?`[${r}]`:""} <${typeof t}>\n${JSON.stringify(t)}`);if(!n){if(s.isSurgeLike){return $persistentStore.write(o,e)}else if(s.isQuanX){return $prefs.setValueForKey(o,e)}else if(s.isNode){try{l.fs.writeFileSync("./magic.json",o);return true}catch(e){a.error(e);return false}}}return true};const e=(t,r,n,o=c,i=null)=>{r=d(r);const e=g(t,null,n,false,i);if(o(e,r)===true){return false}else{const s=h(t,r,n,i);let e=g(t,null,n,false,i);if(o===c&&typeof e==="object"){return s}return o(r,e)}};const y=(e,t,r)=>{let n=r||l.data;n=i(n);if(!!t){obj=i(n[e]);delete obj[t];n[e]=obj}else{delete n[e]}if(!!r){r=n}return n};const t=(e,t="",r=null)=>{let n={};if(r||s.isNode){n=y(e,t,r);if(!r){l.fs.writeFileSync("./magic.json",JSON.stringify(n,null,4))}else{r=n}}else{if(!t){if(s.isStorm){return $persistentStore.remove(e)}else if(s.isSurgeLike){return $persistentStore.write(null,e)}else if(s.isQuanX){return $prefs.removeValueForKey(e)}}else{if(s.isSurgeLike){n=$persistentStore.read(e)}else if(s.isQuanX){n=$prefs.valueForKey(e)}n=i(n);delete n[t];const o=JSON.stringify(n,null,4);h(e,o)}}a.debug(`DELETE KEY [${e}]${!!t?`[${t}]`:""}`)};const r=(e,t=null)=>{let r=[];let n=g(e,null,null,true,t);n=i(n);if(n["magic_session"]!==true){r=[]}else{r=Object.keys(n).filter(e=>e!=="magic_session")}a.debug(`READ ALL SESSIONS [${e}] <${typeof r}>\n${JSON.stringify(r,null,4)}`);return r};const n=(e,t=null)=>{let r={};let n=g(e,null,null,true,t);n=i(n);if(n["magic_session"]===true){r={...n};delete r["magic_session"]}a.debug(`READ ALL SESSIONS [${e}] <${typeof r}>\n${JSON.stringify(r,null,4)}`);return r};return{read:g,write:h,del:t,update:e,allSessions:n,allSessionNames:r,defaultValueComparator:c,convertToObject:i}}function MagicHttp(c,d){let r;if(c.isNode){const a=require("axios");r=a.create()}class e{constructor(e=true){this.handlers=[];this.isRequest=e}use(e,t,r){if(typeof e==="function"){d.debug(`Register fulfilled ${e.name}`)}if(typeof t==="function"){d.debug(`Register rejected ${t.name}`)}this.handlers.push({fulfilled:e,rejected:t,synchronous:r&&typeof r.synchronous==="boolean"?r.synchronous:false,runWhen:r?r.runWhen:null});return this.handlers.length-1}eject(e){if(this.handlers[e]){this.handlers[e]=null}}forEach(t){this.handlers.forEach(e=>{if(e!==null){t(e)}})}}function o(e){let r={...e};if(!!r.params){if(!c.isNode){let e=Object.keys(r.params).map(e=>{const t=encodeURIComponent(e);r.url=r.url.replace(new RegExp(`${e}=[^&]*`,"ig"),"");r.url=r.url.replace(new RegExp(`${t}=[^&]*`,"ig"),"");return`${t}=${encodeURIComponent(r.params[e])}`}).join("&");if(r.url.indexOf("?")<0)r.url+="?";if(!/(&|\?)$/g.test(r.url)){r.url+="&"}r.url+=e;delete r.params;d.debug(`Params to QueryString: ${r.url}`)}}return r}const u=(e,t)=>{let r=typeof t==="object"?{headers:{},...t}:{url:t,headers:{}};if(!r.method){r["method"]=e}r=o(r);if(r["rewrite"]===true){if(c.isSurge){r.headers["X-Surge-Skip-Scripting"]=false;delete r["rewrite"]}else if(c.isQuanX){r["hints"]=false;delete r["rewrite"]}}if(c.isSurgeLike){const n=r.headers["content-type"]||r.headers["Content-Type"];if(r["method"]!=="GET"&&n&&n.indexOf("application/json")>=0&&r.body instanceof Array){r.body=JSON.stringify(r.body);d.debug(`Convert Array object to String: ${r.body}`)}}else if(c.isQuanX){if(r.hasOwnProperty("body")&&typeof r["body"]!=="string")r["body"]=JSON.stringify(r["body"]);r["method"]=e}else if(c.isNode){if(e==="POST"||e==="PUT"||e==="PATCH"||e==="DELETE"){r.data=r.data||r.body}else if(e==="GET"){r.params=r.params||r.body}delete r.body}return r};const f=(t,r=null)=>{if(t){let e={...t,config:t.config||r,status:t.statusCode||t.status,body:t.body||t.data,headers:t.headers||t.header};if(typeof e.body==="string"){try{e.body=JSON.parse(e.body)}catch{}}delete e.data;return e}else{return t}};const t=r=>{return Object.keys(r).reduce((e,t)=>{e[t.toLowerCase()]=r[t];return e},{})};const n=n=>{return Object.keys(n).reduce((e,t)=>{const r=t.split("-").map(e=>e[0].toUpperCase()+e.slice(1)).join("-");e[r]=n[t];return e},{})};const g=(e,t=null)=>{if(!!e&&e.status>=400){d.debug(`Raise exception when status code is ${e.status}`);return{name:"RequestException",message:`Request failed with status code ${e.status}`,config:t||e.config,response:e}}};const i={request:new e,response:new e(false)};let p=[];let h=[];let y=true;function $(e){e=o(e);d.debug(`HTTP ${e["method"].toUpperCase()}:\n${JSON.stringify(e)}`);return e}function m(e){try{e=!!e?f(e):e;d.sniffer(`HTTP ${e.config["method"].toUpperCase()}:\n${JSON.stringify(e.config)}\nSTATUS CODE:\n${e.status}\nRESPONSE:\n${typeof e.body==="object"?JSON.stringify(e.body):e.body}`);const t=g(e);if(!!t){return Promise.reject(t)}return e}catch(t){d.error(t);return e}}const _=t=>{try{p=[];h=[];i.request.forEach(e=>{if(typeof e.runWhen==="function"&&e.runWhen(t)===false){return}y=y&&e.synchronous;p.unshift(e.fulfilled,e.rejected)});i.response.forEach(e=>{h.push(e.fulfilled,e.rejected)})}catch(e){d.error(`Failed to register interceptors: ${e}.`)}};const s=(e,n)=>{let o;const t=e.toUpperCase();n=u(t,n);if(c.isNode){o=r}else{if(c.isSurgeLike){o=i=>{return new Promise((n,o)=>{$httpClient[e.toLowerCase()](i,(t,r,e)=>{if(t){let e={name:t.name||t,message:t.message||t,stack:t.stack||t,config:i,response:f(r)};o(e)}else{r.config=i;r.body=e;n(r)}})})}}else{o=o=>{return new Promise((r,n)=>{$task.fetch(o).then(e=>{e=f(e,o);const t=g(e,o);if(t){return Promise.reject(t)}r(e)}).catch(e=>{let t={name:e.message||e.error,message:e.message||e.error,stack:e.error,config:o,response:!!e.response?f(e.response):null};n(t)})})}}}let i;_(n);const s=[$,undefined];const a=[m,undefined];if(!y){d.debug("Interceptors are executed in asynchronous mode");let r=[o,undefined];Array.prototype.unshift.apply(r,s);Array.prototype.unshift.apply(r,p);r=r.concat(a);r=r.concat(h);i=Promise.resolve(n);while(r.length){try{let e=r.shift();let t=r.shift();if(!c.isNode&&n["timeout"]&&e===o){e=l}if(typeof e==="function"){d.debug(`Executing request fulfilled ${e.name}`)}if(typeof t==="function"){d.debug(`Executing request rejected ${t.name}`)}i=i.then(e,t)}catch(e){d.error(`request exception: ${e}`)}}return i}else{d.debug("Interceptors are executed in synchronous mode");Array.prototype.unshift.apply(p,s);p=p.concat([$,undefined]);while(p.length){let e=p.shift();let t=p.shift();try{if(typeof e==="function"){d.debug(`Executing request fulfilled ${e.name}`)}n=e(n)}catch(e){if(typeof t==="function"){d.debug(`Executing request rejected ${t.name}`)}t(e);break}}try{if(!c.isNode&&n["timeout"]){i=l(n)}else{i=o(n)}}catch(e){return Promise.reject(e)}Array.prototype.unshift.apply(h,a);while(h.length){i=i.then(h.shift(),h.shift())}return i}function l(r){try{const e=new Promise((e,t)=>{setTimeout(()=>{let e={message:`timeout of ${r["timeout"]}ms exceeded.`,config:r};t(e)},r["timeout"])});return Promise.race([o(r),e])}catch(e){d.error(`Request Timeout exception: ${e}.`)}}};return{request:s,interceptors:i,convertHeadersToLowerCase:t,convertHeadersToCamelCase:n,modifyResponse:f,get:e=>{return s("GET",e)},post:e=>{return s("POST",e)},put:e=>{return s("PUT",e)},patch:e=>{return s("PATCH",e)},delete:e=>{return s("DELETE",e)},head:e=>{return s("HEAD",e)},options:e=>{return s("OPTIONS",e)}}} \ No newline at end of file diff --git a/Scripts/AdBlock/Zhihu/Zhihu_dep.js b/Scripts/AdBlock/Zhihu/Zhihu_dep.js new file mode 100644 index 0000000..16da55c --- /dev/null +++ b/Scripts/AdBlock/Zhihu/Zhihu_dep.js @@ -0,0 +1,1794 @@ +const scriptName = "哲也同学"; +const blockedUsersKey = "zhihu_blocked_users"; +const currentUserInfoKey = "zhihu_current_userinfo"; +const keywordBlockKey = "zhihu_keyword_block"; +const blackAnswersIdKey = "zhihu_black_answers"; +const userCreditScore = "zhihu_credit_score"; +// 默认屏蔽推荐列表的用户,通常不是真实用户,无法通过加入黑名单屏蔽 +const defaultAnswerBlockedUsers = ["会员推荐", "盐选推荐"]; +const keywordMaxCount = 1000; // 允许设置的关键词数量 +const $ = MagicJS(scriptName, "INFO"); + +/** + * @description: 获取用户信息 + * @return {*} + */ +function getUserInfo() { + let defaultUserInfo = { id: "default", is_vip: false }; + try { + let userInfo = $.data.read(currentUserInfoKey); + if (typeof userInfo === "string") userInfo = JSON.parse(userInfo); + if (!!userInfo && userInfo.hasOwnProperty("id")) { + return userInfo; + } else { + return defaultUserInfo; + } + } catch (err) { + $.logger.error(`获取用户信息出现异常:${err}`); + return defaultUserInfo; + } +} + +/** + * 优化软件配置 + * @return {*} + */ +function modifyAppConfig() { + let response = null; + try { + if (!!$.response.body) { + let obj = JSON.parse($.response.body); + obj["config"]["homepage_feed_tab"]["tab_infos"] = obj["config"][ + "homepage_feed_tab" + ]["tab_infos"].filter((e) => { + // 将活动标签设置为已过期 + if (e["tab_type"] === "activity_tab") { + e["end_time"] = (new Date() - 120000).toString().slice(0, 10); + return true; + } else { + return false; + } + }); + obj["config"]["zvideo_max_number"] = 1; + // 似乎是控制内部弹窗 + obj["config"]["is_show_followguide_alert"] = false; + // 似乎是某个地方的标签,待定 + delete obj["config"]["hp_channel_tab"]; + // 灰色模式 + if (obj["config"]["zombie_conf"]) { + obj["config"]["zombie_conf"]["zombieEnable"] = false; + } + if (obj["config"]["gray_mode"]) { + obj["config"]["gray_mode"]["enable"] = false; + obj["config"]["gray_mode"]["start_time"] = "4092566400"; + obj["config"]["gray_mode"]["end_time"] = "4092566400"; + } + // 屏蔽8.X版本以上本地DNS解析,以下修改不清楚哪些是有效的,暂时全部保留 + if (obj["config"].hasOwnProperty("zhcnh_thread_sync")) { + $.logger.debug(JSON.stringify(obj["config"]["zhcnh_thread_sync"])); + obj["config"]["zhcnh_thread_sync"]["LocalDNSSetHostWhiteList"] = []; + obj["config"]["zhcnh_thread_sync"]["isOpenLocalDNS"] = "0"; + obj["config"]["zhcnh_thread_sync"]["ZHBackUpIP_Switch_Open"] = "0"; + obj["config"]["zhcnh_thread_sync"]["dns_ip_detector_operation_lock"] = + "1"; + obj["config"]["zhcnh_thread_sync"][ + "ZHHTTPSessionManager_setupZHHTTPHeaderField" + ] = "1"; + } + response = { body: JSON.stringify(obj) }; + } + } catch (err) { + $.logger.error(`优化软件配置出现异常:${err}`); + } + return response; +} + +/** + * 修改云端下发的配置 + * @return {*} + */ +function modifyMCloudConfig() { + let response = null; + try { + if (!!$.response.body) { + let obj = JSON.parse($.response.body); + if (obj.data && obj.data["configs"]) { + // 去除灰色主题 + obj.data["configs"].forEach((element) => { + if (element["configKey"] === "feed_gray_theme") { + element["configValue"].start_time = "1669824000"; + element["configValue"].end_time = "1669824001"; + element.status = false; + } + }); + } + const body = JSON.stringify(obj); + $.logger.debug(body); + response = { body: body }; + } + } catch (err) { + $.logger.error(`优化软件配置出现异常:${err}`); + } + return response; +} + +/** + * 屏蔽关键词解锁 + * @return {*} + */ +function unlockBlockedKeywords() { + let response = null; + try { + const userInfo = getUserInfo(); + // 获取屏蔽关键词列表 + if ($.request.method === "GET") { + let keywords = $.data.read(keywordBlockKey, null, userInfo.id); + if (!keywords) { + keywords = []; + } + let headers = { + "Cache-Control": + "no-cache, no-store, must-revalidate, private, max-age=0", + Connection: "keep-alive", + "Content-Type": "application/json;charset=utf-8", + Pragma: "no-cache", + "Referrer-Policy": "no-referrer-when-downgrade", + Server: "CLOUD ELB 1.0.0", + Vary: "Accept-Encoding", + "X-Cache-Lookup": "Cache Miss", + "x-cdn-provider": "tencent", + }; + let body = JSON.stringify({ + success: true, + is_vip: true, + kw_min_length: 2, + kw_max_length: 100, + kw_max_count: keywordMaxCount, + data: keywords, + }); + if ($.env.isQuanX) { + response = { body: body, headers: headers, status: "HTTP/1.1 200 OK" }; + } else { + response = { response: { body: body, headers: headers, status: 200 } }; + } + $.logger.debug(`获取本地脚本屏蔽关键词:\n${keywords.join("、")}`); + } + + // 添加屏蔽关键词 + else if ($.request.method === "POST") { + if (!!$.request.body) { + // 构造 response headers + let headers = { + "Cache-Control": + "no-cache, no-store, must-revalidate, private, max-age=0", + Connection: "keep-alive", + "Content-Type": "application/json;charset=utf-8", + Pragma: "no-cache", + "Referrer-Policy": "no-referrer-when-downgrade", + Server: "CLOUD ELB 1.0.0", + Vary: "Accept-Encoding", + "X-Cache-Lookup": "Cache Miss", + "x-cdn-provider": "tencent", + }; + // 读取关键词 + let keyword = decodeURIComponent($.request.body).match( + /keyword=(.*)/ + )[1]; + let keywords = $.data.read(keywordBlockKey, null, userInfo.id); + if (!keywords) { + keywords = []; + } + // 判断关键词是否存在 + let keywordExists = false; + for (let i = 0; i < keywords.length; i++) { + if (keyword === keywords[i]) { + keywordExists = true; + break; + } + } + // 不存在添加,存在返回异常 + if (keywordExists === false) { + keywords.push(keyword); + $.data.write(keywordBlockKey, keywords, userInfo.id); + let body = JSON.stringify({ success: true }); + if ($.env.isQuanX) { + response = { + body: body, + headers: headers, + status: "HTTP/1.1 200 OK", + }; + } else { + response = { + response: { body: body, headers: headers, status: 200 }, + }; + } + $.logger.debug(`添加本地脚本屏蔽关键词“${keyword}”`); + } else { + let body = JSON.stringify({ + error: { + message: "关键词已存在", + code: 100002, + }, + }); + if ($.env.isQuanX) { + response = { + body: body, + headers: headers, + status: "HTTP/1.1 400 Bad Request", + }; + } else { + response = { + response: { body: body, headers: headers, status: 400 }, + }; + } + } + } + } + + // 删除屏蔽关键词 + else if ($.request.method === "DELETE") { + let keyword = decodeURIComponent($.request.url).match(/keyword=(.*)/)[1]; + let keywords = $.data.read(keywordBlockKey, null, userInfo.id); + if (!keywords) { + keywords = []; + } + keywords = keywords.filter((e) => { + return e !== keyword; + }); + $.data.write(keywordBlockKey, keywords, userInfo.id); + let headers = { + "Cache-Control": + "no-cache, no-store, must-revalidate, private, max-age=0", + Connection: "keep-alive", + "Content-Type": "application/json;charset=utf-8", + Pragma: "no-cache", + "Referrer-Policy": "no-referrer-when-downgrade", + Server: "CLOUD ELB 1.0.0", + Vary: "Accept-Encoding", + "X-Cache-Lookup": "Cache Miss", + "x-cdn-provider": "tencent", + }; + let body = JSON.stringify({ success: true }); + if ($.env.isQuanX) { + response = { body: body, headers: headers, status: "HTTP/1.1 200 OK" }; + } else { + response = { response: { body: body, headers: headers, status: 200 } }; + } + $.logger.debug(`删除本地脚本屏蔽关键词:“${keyword}”`); + } + } catch (err) { + $.logger.debug(`关键词屏蔽操作出现异常:${err}`); + } + return response; +} + +/** + * 处理登录用户信息 + * + * @return {*} + */ +function processUserInfo() { + let response = null; + try { + let obj = JSON.parse($.response.body); + $.data.write(blackAnswersIdKey, []); + $.logger.debug(`用户登录用户信息,接口响应:${$.response.body}`); + if ( + obj && + obj["id"] && + obj.hasOwnProperty("vip_info") && + obj["vip_info"].hasOwnProperty("is_vip") + ) { + const userInfo = { + id: obj["id"], + is_vip: obj["vip_info"]["is_vip"] + ? obj["vip_info"]["is_vip"] !== undefined + : false, + }; + $.logger.debug( + `当前用户id:${obj["id"]},是否为VIP:${obj["vip_info"]["is_vip"]}` + ); + $.data.write(currentUserInfoKey, userInfo); + // 在APP显示VIP,仅自己可见,打开后才能使用屏蔽关键词解锁 + if ( + $.data.read("zhihu_settings_fake_vip") !== false && + obj["vip_info"]["is_vip"] === false + ) { + obj["vip_info"]["is_vip"] = true; + obj["vip_info"]["vip_type"] = 2; + obj["vip_info"]["vip_icon"] = { + url: "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae", + night_mode_url: + "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae", + }; + obj["vip_info"]["vip_icon_v2"] = { + url: "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae", + night_mode_url: + "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae", + }; + obj["vip_info"]["entrance"] = { + icon: { + url: "https://pic3.zhimg.com/v2-5b7012c8c22fd520f5677305e1e551bf.png", + night_mode_url: + "https://pic4.zhimg.com/v2-e51e3252d7a2cb016a70879defd5da0b.png", + }, + title: "盐选会员 为你严选好内容", + expires_day: "2099-12-31", + sub_title: null, + button_text: "首月 9 元", + jump_url: "zhihu://vip/purchase", + button_jump_url: "zhihu://vip/purchase", + sub_title_new: null, + identity: "super_svip", + }; + obj["vip_info"]["entrance_new"] = { + left_button: { + title: "精选会员内容", + description: "为您严选好内容", + jump_url: "zhihu://market/home", + }, + right_button: { + title: "开通盐选会员", + description: "畅享 10w+ 场优质内容等特权", + jump_url: "zhihu://vip/purchase", + }, + }; + obj["vip_info"]["entrance_v2"] = { + title: "我的超级盐选会员", + sub_title: "畅享 5000W+ 优质内容", + jump_url: "zhihu://market/home", + button_text: "查看会员", + sub_title_color: "#F8E2C4", + sub_title_list: ["畅享 5000W+ 优质内容"], + card_jump_url: "zhihu://market/home", + }; + $.logger.debug("设置用户为本地盐选会员"); + response = { body: JSON.stringify(obj) }; + } + } else { + $.logger.warning( + `没有获取到本次登录用户信息,如未对功能造成影响,请忽略此日志。` + ); + } + } catch (err) { + $.logger.error(`获取当前用户信息出现异常:${err}`); + } + return response; +} + +/** + * @description: 黑名单管理 + * @return {*} + */ +function manageBlackUser() { + const userInfo = getUserInfo(); + let defaultBlockedUsers = {}; + let customBlockedUsers = $.data.read(blockedUsersKey, "", userInfo.id); + customBlockedUsers = + typeof customBlockedUsers === "object" && !!customBlockedUsers + ? customBlockedUsers + : {}; + defaultAnswerBlockedUsers.forEach((element) => { + customBlockedUsers[element] = "00000000000000000000000000000000"; + defaultBlockedUsers[element] = "00000000000000000000000000000000"; + }); + $.logger.debug( + `当前用户id:${userInfo.id},脚本黑名单:${JSON.stringify( + customBlockedUsers + )}` + ); + // 获取黑名单 + if ($.request.method === "GET") { + try { + // 加载黑名单首页时,清空历史黑名单,仅保留脚本默认黑名单 + if ($.request.url.indexOf("offset") < 0) { + customBlockedUsers = defaultBlockedUsers; + $.logger.debug( + "脚本黑名单已清空,请滑动至黑名单末尾保证重新获取完成。" + ); + $.notification.post( + "开始同步黑名单数据,请滑动至黑名单末尾,直至弹出“同步成功”的通知。" + ); + } + let obj = JSON.parse($.response.body); + if (!!obj["data"]) { + $.logger.debug( + `本次滑动获取的黑名单信息:${JSON.stringify(obj["data"])}` + ); + obj["data"].forEach((element) => { + if (element["name"] !== "[已重置]") { + customBlockedUsers[element["name"]] = element["id"]; + } + }); + $.data.write(blockedUsersKey, customBlockedUsers, userInfo.id); + if (obj["paging"]["is_end"] === true) { + $.notification.post( + `同步黑名单数据成功!当前黑名单共${ + Object.keys(customBlockedUsers).length - + defaultAnswerBlockedUsers.length + }人。\n脚本内置黑名单${defaultAnswerBlockedUsers.length}人。` + ); + $.logger.debug( + `脚本黑名单内容:${JSON.stringify(customBlockedUsers)}。` + ); + } + } else { + $.logger.warning(`获取黑名单失败,接口响应不合法:${$.response.body}`); + } + } catch (err) { + $.data.del(blockedUsersKey); + $.logger.error(`获取黑名单失败,异常信息:${err}`); + $.notification.post("获取黑名单失败,执行异常,已清空黑名单。"); + } + } + // 写入黑名单 + else if ($.request.method === "POST") { + try { + let obj = JSON.parse($.response.body); + if (obj.hasOwnProperty("name") && obj.hasOwnProperty("id")) { + $.logger.debug( + `当前需要加入黑名单的用户Id:${obj["id"]},用户名:${obj["name"]}` + ); + if (obj["id"]) { + customBlockedUsers[obj["name"]] = obj["id"]; + $.data.write(blockedUsersKey, customBlockedUsers, userInfo.id); + $.logger.debug( + `${ + obj["name"] + }写入脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify( + customBlockedUsers + )}` + ); + $.notification.post(`已将用户“${obj["name"]}”写入脚本黑名单。`); + } else { + $.logger.error( + `${obj["name"]}写入脚本黑名单失败,没有获取到用户Id。` + ); + $.notification.post(`将用户“${obj["name"]}”写入脚本黑名单失败!`); + } + } else { + $.logger.warning(`写入黑名单失败,接口响应不合法:${$.response.body}`); + $.notification.post("写入脚本黑名单失败,接口返回不合法。"); + } + } catch (err) { + $.logger.error(`写入黑名单失败,异常信息:${err}`); + $.notification.post("写入脚本黑名单失败,执行异常,请查阅日志。"); + } + } + // 移出黑名单 + else if ($.request.method === "DELETE") { + try { + let obj = JSON.parse($.response.body); + if (obj.success) { + let user_id = $.request.url.match( + /^https?:\/\/api\.zhihu\.com\/settings\/blocked_users\/([0-9a-zA-Z]*)/ + )[1]; + if (user_id) { + $.logger.debug(`当前需要移出黑名单的用户Id:${user_id}`); + for (let username in customBlockedUsers) { + if (customBlockedUsers[username] === user_id) { + delete customBlockedUsers[username]; + $.data.write(blockedUsersKey, customBlockedUsers, userInfo.id); + $.logger.debug( + `${username}移出脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify( + customBlockedUsers + )}` + ); + $.notification.post(`已将用户“${username}”移出脚本黑名单!`); + break; + } + } + } else { + $.logger.error( + "将用户移出脚本黑名单失败!\n建议从设置中刷新黑名单数据。" + ); + $.notification.post( + `将用户移出脚本黑名单失败,没有获取到用户Id。\n建议从设置中刷新黑名单数据。` + ); + } + } else { + $.logger.warning(`移出黑名单失败,接口响应不合法:${$.response.body}`); + $.notification.post("移出脚本黑名单失败,接口返回不合法。"); + } + } catch (err) { + $.logger.error(`移出黑名单失败,异常信息:${err}`); + $.notification.post("移出脚本黑名单失败,执行异常,请查阅日志。"); + } + } +} + +/** + * 黑名单增强 - 浏览黑名单用户信息时自动加入脚本黑名单 + * @return {*} + */ +function autoInsertBlackList() { + let response = null; + try { + let obj = JSON.parse($.response.body); + // 删除MCN信息 + delete obj["mcn_user_info"]; + response = { body: JSON.stringify(obj) }; + // 如已是黑名单用户,但不在脚本黑名单中,则自动加入 + if (obj.name && obj.id && obj["is_blocking"] === true) { + const userInfo = getUserInfo(); + let customBlockedUsers = $.data.read(blockedUsersKey, "", userInfo.id); + customBlockedUsers = + typeof customBlockedUsers === "object" && !!customBlockedUsers + ? customBlockedUsers + : {}; + if (!customBlockedUsers[obj.name]) { + $.logger.debug( + `当前需要加入黑名单的用户Id:${obj["id"]},用户名:${obj["name"]}` + ); + customBlockedUsers[obj["name"]] = obj["id"]; + $.data.write(blockedUsersKey, customBlockedUsers, userInfo.id); + $.logger.debug( + `${ + obj["name"] + }写入脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify( + customBlockedUsers + )}` + ); + $.notification.post(`已自动将用户“${obj["name"]}”写入脚本黑名单。`); + } + } + } catch (err) { + $.logger.error(`去除MCN信息出现异常:${err}`); + } + return response; +} + +/** + * 关注列表去广告 + * + * @return {*} + */ +function removeMoments() { + let response = null; + try { + let obj = JSON.parse( + $.response.body.replace(/(\w+"+\s?):\s?(\d{15,})/g, '$1:"$2"') + ); + const user_info = getUserInfo(); + let customBlockedUsers = $.data.read(blockedUsersKey, "", user_info.id); + customBlockedUsers = !!customBlockedUsers ? customBlockedUsers : {}; + let data; + + const settings_remove_stream = $.data.read( + "zhihu_settings_moments_stream", + false + ); + const settings_remove_recommend = $.data.read( + "zhihu_settings_moments_recommend", + false + ); + const settings_remove_activity = $.data.read( + "zhihu_settings_moments_activity", + false + ); + const settings_blocked_users = $.data.read( + "zhihu_settings_blocked_users", + false + ); + + data = obj.data.filter((item) => { + // 转发的想法是否含有黑名单用户 + const isBlackUserPin = + settings_blocked_users && + item.target && + item.target["origin_pin"] && + item.target["origin_pin"].author && + typeof customBlockedUsers[item.target["origin_pin"].author.name] != + "undefined"; + // 是否为流媒体 + const isStream = + settings_remove_stream && item["target_type"] === "zvideo"; + // 是否为推荐关注用户 + const isRecommend = + settings_remove_recommend && item.type === "recommend_user_card_list"; + // 是否为关注的问题有新动态 + const isActivity = + settings_remove_activity && item.type === "message_activity_card"; + return !(isBlackUserPin || isStream || isRecommend || isActivity); + }); + obj["data"] = data; + response = { body: JSON.stringify(obj) }; + } catch (err) { + $.logger.error(`关注列表去广告出现异常:${err}`); + } + return response; +} + +/** + * 推荐去广告与黑名单增强 + * + * @return {*} + */ +function removeRecommend() { + let response = null; + try { + // 移除推荐列表中的想法 + const settings_remove_pin = $.data.read( + "zhihu_settings_recommend_pin", + false + ); + // 移除推荐列表的流媒体 + const settings_recommend_stream = $.data.read( + "zhihu_settings_recommend_stream", + false + ); + // 移除推荐列表的文章 + const settings_remove_article = $.data.read( + "zhihu_settings_remove_article", + false + ); + // 屏蔽黑名单用户 + const settings_blocked_users = $.data.read( + "zhihu_settings_blocked_users", + false + ); + // 屏蔽关键词内容 + const settings_blocked_keywords = $.data.read( + "zhihu_settings_blocked_keywords", + true + ); + // 获取用户信息 + const user_info = getUserInfo(); + + let keywords = $.data.read(keywordBlockKey, "", user_info.id); + keywords = settings_blocked_keywords && !!keywords ? keywords : []; + let customBlockedUsers = $.data.read(blockedUsersKey, "", user_info.id); + customBlockedUsers = + settings_blocked_users && !!customBlockedUsers ? customBlockedUsers : {}; + + const newData = (element) => { + const elementStr = JSON.stringify(element); + // 是否为广告 + const isAd = + element["card_type"] === "slot_event_card" || + element["card_type"] === "slot_video_event_card" || + element.hasOwnProperty("ad") || + // 非常恶心伪装成普通内容的广告 + (element["brief"] && element["brief"].indexOf("slot_card") >= 0) || + // 训练营 + (element["extra"] && element["extra"]["type"] === "Training"); + // 是否为流媒体 + const isStream = + isAd !== true && + elementStr.search( + /"(type|style)+"\s?:\s?"(drama|zvideo|Video|BIG_IMAGE)+"/i + ) >= 0; + const removeStream = isStream && settings_recommend_stream; + // 是否为想法 + const isPin = + isStream !== true && + elementStr.search(/"(type|style)+"\s?:\s?"pin"/i) >= 0; + const removePin = isPin && settings_remove_pin; + // 是否为文章 + const isArticle = + elementStr.search(/"(type|style)+"\s?:\s?"article"/i) >= 0; + const removeArticle = isArticle && settings_remove_article; + // 是否匹配脚本关键词过滤 + let matchKeyword = false; + if (isStream !== true && settings_blocked_keywords) { + for (let i = 0; i < keywords.length; i++) { + if (elementStr.search(keywords[i]) >= 0) { + if ($.isDebug) { + let elementTitle = + element["common_card"]["feed_content"]["title"]["panel_text"]; + let elementContent = + element["common_card"]["feed_content"]["content"]["panel_text"]; + let actionUrl = ""; + try { + actionUrl = + element["common_card"]["feed_content"]["title"]["action"][ + "intent_url" + ]; + } catch {} + $.logger.debug( + `匹配关键字:\n${keywords[i]}\n标题:\n${elementTitle}\n内容:\n${elementContent}` + ); + $.notification.debug( + scriptName, + `关键字:${keywords[i]}`, + `${elementTitle}\n${elementContent}`, + actionUrl + ); + } + matchKeyword = true; + break; + } + } + } + // 是否为黑名单用户 + let isBlockedUser; + try { + isBlockedUser = + matchKeyword !== true && + settings_blocked_users && + customBlockedUsers && + element["common_card"]["feed_content"]["source_line"]["elements"][1][ + "text" + ]["panel_text"] in customBlockedUsers; + } catch { + isBlockedUser = false; + } + return !( + isAd || + removePin || + removeArticle || + removeStream || + matchKeyword || + isBlockedUser + ); + }; + + // 修复number类型精度丢失 + let obj = JSON.parse( + $.response.body.replace(/(\w+"+\s?):\s?(\d{15,})/g, '$1:"$2"') + ); + + if (obj["data"].length > 0 && newData.length === 0) { + $.notification.post("所有推荐内容都已被过滤,建议调整脚本过滤配置。"); + } + obj["data"] = obj["data"].filter(newData); + response = { body: JSON.stringify(obj) }; + } catch (err) { + $.logger.error(`推荐列表去广告出现异常:${err}`); + } + return response; +} + +/** + * 回答列表去广告与黑名单增强 + * + * @return {*} + */ +function removeQuestions() { + let response = null; + try { + const userInfo = getUserInfo(); + let customBlockedUsers = $.data.read(blockedUsersKey, "", userInfo.id); + customBlockedUsers = !!customBlockedUsers ? customBlockedUsers : {}; + let obj = JSON.parse($.response.body); + const settingsBlockedUsers = $.data.read( + "zhihu_settings_blocked_users", + false + ); + $.logger.debug(`当前黑名单列表: ${JSON.stringify(customBlockedUsers)}`); + // 黑名单用户的回答Id + let blackUserAnswersId = $.data.read(blackAnswersIdKey, []); + // 去除广告 + delete obj["ad_info"]; + // 去除回答列表中的黑名单用户 + if (obj["data"]) { + let newData = []; + for (let element of obj.data) { + let blackUserName = ""; + const answerId = element.target.id.toString(); + try { + if ("target" in element) { + blackUserName = element["target"]["author"]["name"]; + } + } catch (ex) { + $.logger.error(`获取回答列表用户名出现异常:${ex}`); + } + const isBlackUser = + typeof customBlockedUsers[blackUserName] != "undefined"; + const removeBlackUserAnswer = settingsBlockedUsers && isBlackUser; + // 显示仅作者自己可见的回答,允许复制 + if ("target" in element) { + element["target"]["visible_only_to_author"] = false; + element["target"]["is_visible"] = true; + element["target"]["is_copyable"] = true; + } + if (!removeBlackUserAnswer) { + newData.push(element); + } else if ( + removeBlackUserAnswer === true && + blackUserAnswersId.includes(answerId) === false + ) { + blackUserAnswersId.push(answerId); + $.notification.debug( + `记录黑名单用户${blackUserName}的回答Id:${answerId}` + ); + } + } + obj.data = newData; + } + $.data.write(blackAnswersIdKey, blackUserAnswersId); + const body = JSON.stringify(obj); + $.logger.debug(`修改后的回答列表数据:${body}`); + response = { body: body }; + } catch (err) { + $.logger.error(`回答列表去广告出现异常:${err}`); + } + return response; +} + +/** + * 回答内容优化 + * + * @return {*} + */ +function modifyAnswer() { + let response = null; + try { + let html = $.response.body; + let insertText = ""; + + // 付费内容提醒 + if ( + (html.indexOf("查看完整内容") >= 0 || + html.indexOf("查看全部章节") >= 0) && + html.indexOf("paid") >= 0 + ) { + insertText = + '
本文为付费内容
'; + } + + // 营销推广提醒 + else if ( + html.indexOf("ad-link-card") >= 0 || + html.indexOf("xg.zhihu.com") >= 0 || + html.indexOf("营销平台") >= 0 + ) { + insertText = + '
本文含有营销推广
'; + } + + // 购物推广提醒 + else if (html.indexOf("mcn-link-card") >= 0) { + insertText = + '
本文含有购物推广
'; + } + + // 彩蛋 + else if (Math.floor(Math.random() * 200) === 7) { + insertText = + '
本文为免费内容
'; + } + + if (insertText !== "") { + const matchStr = html.match(/(richText[^<]*>)(.)/)[1]; + const start = html.lastIndexOf(matchStr) + matchStr.length; + response = { + body: html.slice(0, start) + insertText + html.slice(start), + }; + } + } catch (err) { + $.logger.error(`付费内容提醒出现异常:${err}`); + } + return response; +} + +/** + * 评论去广告及黑名单增强 + * + * @return {*} + */ +function removeComment() { + let response = null; + try { + if (!!$.response.body) { + let obj = JSON.parse($.response.body); + obj["ad_info"] = {}; + // 屏蔽黑名单用户 + if ($.data.read("zhihu_settings_blocked_users", false) === true) { + let user_info = getUserInfo(); + let customBlockedUsers = $.data.read(blockedUsersKey, "", user_info.id); + customBlockedUsers = !!customBlockedUsers ? customBlockedUsers : {}; + let newComments = []; + let blockCommentIdObj = {}; + if (typeof obj.root != "undefined") { + // 屏蔽黑名单用户的评论 + const rootUserName = obj.root.author.name; + const isBlackRootUser = + typeof customBlockedUsers[rootUserName] != "undefined"; + if (isBlackRootUser === true) { + obj.root.is_delete = true; + obj.root.can_reply = false; + obj.root.can_like = false; + obj.root.author.name = "黑名单用户"; + obj.root.author.avatar_url = + "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"; + } + } + + if (typeof obj.data != "undefined") { + obj.data.forEach((comment) => { + // 屏蔽黑名单用户的评论 + // 评论人昵称 + const commentUserName = comment.author.name; + // 回复哪个人的评论(仅适用于独立子评论页面请求) + let replyUserName = ""; + if (comment["reply_to_author"] && comment["reply_to_author"].name) { + replyUserName = comment["reply_to_author"].name; + } + const isSubComment = replyUserName !== ""; + const isBlackCommentUser = + typeof customBlockedUsers[commentUserName] != "undefined"; + const isBlackReplyUser = + typeof customBlockedUsers[replyUserName] != "undefined"; + if (isBlackCommentUser === true || isBlackReplyUser === true) { + if ( + isBlackCommentUser && + !isSubComment && + $.request.url.indexOf("root_comment") > 0 + ) { + $.notification.debug( + `屏蔽黑名单用户“${commentUserName}”的主评论。` + ); + } else if ( + !isBlackCommentUser && + isSubComment && + !isBlackReplyUser && + $.request.url.indexOf("child_comment") > 0 + ) { + $.notification.debug( + `屏蔽黑名单用户“${commentUserName}”的子评论。` + ); + } else if ( + isBlackCommentUser && + !isBlackReplyUser && + $.request.url.indexOf("child_comment") > 0 + ) { + $.notification.debug( + `屏蔽黑名单用户“${commentUserName}”回复“${replyUserName}”的子评论。` + ); + } else if ( + isBlackCommentUser && + isBlackReplyUser && + $.request.url.indexOf("child_comment") > 0 + ) { + $.notification.debug( + `屏蔽黑名单用户“${commentUserName}”回复黑名单用户“${replyUserName}”的子评论。` + ); + } + blockCommentIdObj[comment.id] = commentUserName; + if (isBlackCommentUser) { + comment.is_delete = true; + comment.can_reply = false; + comment.can_like = false; + comment.author.exposed_medal = {}; + comment.author.name = "[黑名单用户]"; + comment.author.avatar_url = + "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"; + } + if (isBlackReplyUser) { + comment["reply_to_author"].name = "[黑名单用户]"; + comment["reply_to_author"].exposed_medal = {}; + comment["reply_to_author"].avatar_url = + "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"; + } + } + if (comment.child_comments) { + let newChildComments = []; + comment.child_comments.forEach((childComment) => { + // 屏蔽黑名单用户的评论 + const childCommentUserName = childComment.author.name; + const childCommentReplyUserName = + typeof childComment["reply_to_author"] != "undefined" + ? childComment["reply_to_author"].name + : ""; + const isChildBlackCommentUser = + typeof customBlockedUsers[childCommentUserName] != + "undefined"; + const isChildBlackReplyUser = + typeof customBlockedUsers[childCommentReplyUserName] != + "undefined"; + if (isChildBlackCommentUser || isChildBlackReplyUser) { + if (isChildBlackCommentUser === true) { + $.notification.debug( + `屏蔽黑名单用户“${childCommentUserName}”的子评论。` + ); + blockCommentIdObj[childComment.id] = childCommentUserName; + childComment.is_delete = true; + childComment.can_reply = false; + childComment.can_like = false; + childComment.author.name = "[黑名单用户]"; + childComment.author.exposed_medal = {}; + childComment.author.avatar_url = + "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"; + } + + if (isChildBlackReplyUser === true) { + $.logger.debug( + `修改前的子评论数据:\n${JSON.stringify(childComment)}` + ); + childComment["reply_to_author"].name = "[黑名单用户]"; + childComment["reply_to_author"].exposed_medal = {}; + childComment["reply_to_author"].avatar_url = + "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"; + $.notification.debug( + `隐藏“${childCommentUserName}”回复黑名单用户“${childCommentReplyUserName}”的名称与头像。` + ); + $.logger.debug( + `修改后的子评论数据:\n${JSON.stringify(childComment)}` + ); + } + } + newChildComments.push(childComment); + }); + comment.child_comments = newChildComments; + } + newComments.push(comment); + }); + } + obj.data = newComments; + } + const body = JSON.stringify(obj); + $.logger.debug(`过滤后的评论数据:\n${body}`); + response = { body: body }; + } + } catch (err) { + $.logger.error(`去除评论广告出现异常:${err}`); + } + return response; +} + +/** + * 移除文章页面的广告 + * @return {*} + */ +function removeArticleAd() { + let response = null; + try { + if (!!$.response.body) { + let obj = JSON.parse($.response.body); + obj["ad_info"] = {}; + const body = JSON.stringify(obj); + $.logger.debug(`过滤后的文章数据:\n${body}`); + response = { body: body }; + } + } catch (err) { + $.logger.error(`去除文章广告出现异常:${err}`); + } + return response; +} + +/** + * 屏蔽官方营销消息 + * + * @return {*} + */ +function removeMarketingMsg() { + let response = null; + try { + let obj = JSON.parse($.response.body); + let newItems = []; + for (let item of obj["data"]) { + if (item["detail_title"] === "官方帐号消息") { + let unread_count = item["unread_count"]; + if (unread_count > 0) { + item["content"]["text"] = "未读消息" + unread_count + "条"; + } else { + item["content"]["text"] = "全部消息已读"; + } + item["is_read"] = true; + item["unread_count"] = 0; + newItems.push(item); + } else if (item["detail_title"] !== "活动助手") { + newItems.push(item); + } + } + obj["data"] = newItems; + response = { body: JSON.stringify(obj) }; + } catch (err) { + $.logger.error(`屏蔽官方营销消息出现异常:${err}`); + } + return response; +} + +/** + * 热榜去广告 + * + * @return {*} + */ +function removeHotListAds() { + let response = null; + try { + if (!!$.response.body) { + let obj = JSON.parse($.response.body); + if ("data" in obj) { + obj["data"] = obj["data"].filter((e) => { + return ( + e["type"] === "hot_list_feed" || e["type"] === "hot_list_feed_video" + ); + }); + } + response = { body: JSON.stringify(obj) }; + } + } catch (err) { + $.logger.error(`去除热榜广告出现异常:${err}`); + } + return response; +} + +/** + * 去除预置关键字广告 + * + * @return {*} + */ +function removeKeywordAds() { + let response = null; + try { + if (!!$.response.body) { + $.logger.debug(`预置关键字返回:${$.response.body}`); + let obj = JSON.parse($.response.body); + if (obj.hasOwnProperty("preset_words") && obj["preset_words"]["words"]) { + obj["preset_words"]["words"] = obj["preset_words"]["words"].filter( + (element) => { + return element["type"] !== "ad"; + } + ); + response = { body: JSON.stringify(obj) }; + } + } + } catch (err) { + $.logger.error(`去除预置关键字广告出现异常:${err}`); + } + return response; +} + +/** + * 移除回答翻页时出现的黑名单用户的回答 + * 小概率会移除失败 + * @return {*} + */ +function removeNextBlackUserAnswer() { + let response = null; + try { + if (!!$.response.body) { + let obj = JSON.parse($.response.body); + const blackUserAnswersId = $.data.read(blackAnswersIdKey, []); + if (blackUserAnswersId.length > 0) { + let newData = []; + obj.data.forEach((element) => { + const tag = blackUserAnswersId.includes(element.id.toString()); + if (tag === false) { + // 去除可能的广告 + element.ad_info = { data: "" }; + newData.push(element); + } else { + $.notification.debug( + `屏蔽翻页过程中出现的黑名单用户回答Id:${element.id}` + ); + } + }); + // 重新为答案排序 + for (let i = 0; i < newData.length; i++) { + if (newData[i]["extra"] && newData[i]["extra"]["question_index"]) { + newData[i]["extra"]["question_index"] = i + 1; + } + if (newData[i]["strategy_info"]) { + newData[i]["strategy_info"]["global_index"] = i + 1; + newData[i]["strategy_info"]["strategy_index"] = i + 1; + } + } + obj.data = newData; + } + response = { body: JSON.stringify(obj) }; + } + } catch (err) { + $.logger.error(`屏蔽下翻黑名单用户的回答出现异常:${err}`); + } + return response; +} + +function modifyAnswersNextData() { + let response = null; + try { + if (!!$.response.body) { + let obj = JSON.parse($.response.body); + let user_info = getUserInfo(); + let customBlockedUsers = $.data.read(blockedUsersKey, {}, user_info.id); + $.logger.debug(`脚本黑名单用户:\n${JSON.stringify(customBlockedUsers)}`); + let newData = []; + obj.data.data.forEach((element) => { + element["ad_info"] = { data: "" }; + const isBlackUser = + typeof customBlockedUsers[element.data.author.name] != "undefined"; + $.logger.debug( + `用户${element.data.author.name}是否在黑名单中:${isBlackUser}` + ); + if ( + $.data.read("zhihu_settings_blocked_users", false) === false || + isBlackUser === false + ) { + newData.push(element); + } + }); + obj.data.data = newData; + response = { body: JSON.stringify(obj) }; + } + } catch (err) { + $.logger.error(`屏蔽回答信息流黑名单用户及广告:${err}`); + } + return response; +} + +/** + * 修改盐值 + * + * @return {*} + */ +function changeUserCredit() { + $.notification.debug("开始修改用户盐值"); + let response = null; + try { + if (!!$.response.body) { + // 自定义盐值 + const score = parseInt($.data.read(userCreditScore, 780)); + $.logger.debug(`准备修改用户盐值为${score}`); + let obj = JSON.parse($.response.body); + if (obj["credit_basis"].total_score < score) { + obj["credit_basis"].total_score = score; + $.logger.debug(`已修改用户盐值为:${score}`); + } + response = { body: JSON.stringify(obj) }; + } + } catch (err) { + $.logger.error(`修改用户盐值出现异常:${err}`); + } + return response; +} + +(() => { + let response = null; + if ($.isResponse) { + switch (true) { + // 获取用户信息 - 隔离用户数据,开启本地盐选会员等 + case /^https:\/\/api\.zhihu\.com\/people\/self$/.test($.request.url): + response = processUserInfo(); + break; + // 优化软件配置 - 优化下发的配置文件来实现某些效果 + case $.data.read("zhihu_settings_app_conf", false) === true && + /^https?:\/\/appcloud2\.zhihu\.com\/v\d+\/config/.test($.request.url): + response = modifyAppConfig(); + break; + case $.data.read("zhihu_settings_app_conf", false) === true && + /^https?:\/\/m-cloud\.zhihu\.com\/api\/cloud\/config\/all\?/.test( + $.request.url + ): + response = modifyMCloudConfig(); + break; + // 修改用户盐值 - 仅当自定义盐值大于真实盐值时生效 + case /^https?:\/\/api\.zhihu\.com\/user-credit\/basis/.test( + $.request.url + ): + $.notification.debug("准备修改用户盐值"); + response = changeUserCredit(); + break; + // 推荐页 - 移除黑名单用户发布的文章、去除广告,及自定义一些屏蔽项目 + case /^https:\/\/api\.zhihu\.com\/topstory\/recommend/.test( + $.request.url + ): + response = removeRecommend(); + break; + // 问题的回答列表 - 移除黑名单用户的回答、去除广告 + case /^https?:\/\/api\.zhihu\.com\/(v4\/)?questions\/\d+/.test( + $.request.url + ): + response = removeQuestions(); + break; + // 回答信息流 - 移除黑名单用户的回答、去除广告 + case /^https?:\/\/api\.zhihu\.com\/next-data\?/.test($.request.url): + response = modifyAnswersNextData(); + break; + // 消息页 - 折叠官方消息、屏蔽营销消息 + case $.data.read("zhihu_settings_sys_msg", true) !== false && + /^https?:\/\/api\.zhihu\.com\/notifications\/v3\/message/.test( + $.request.url + ): + response = removeMarketingMsg(); + break; + // 评论页及子页面 - 去除黑名单用户发表的评论 + case /^https?:\/\/api\.zhihu\.com\/comment_v5\/(answers|pins|comments?|articles)\/\d+\/(root|child)_comment/.test( + $.request.url + ): + response = removeComment(); + break; + // 文章页 - 去除底部卡片广告 + case /^https?:\/\/www\.zhihu\.com\/api\/v\d\/articles\/\d+\/recommendation\?/.test( + $.request.url + ): + response = removeArticleAd(); + break; + // 回答页底部评论摘要 - 移除黑名单用户发表的评论 + case /^https?:\/\/www\.zhihu\.com\/api\/v4\/comment_v5\/answers\/\d+\/abstract_comment\?/.test( + $.request.url + ): + response = removeComment(); + break; + // 回答内容优化 - 付费、营销、推广内容文首提醒 + case $.data.read("zhihu_settings_answer_tip", true) === true && + /^https?:\/\/www\.zhihu\.com\/appview\/v2\/answer\/.*(entry=(?!(preload-topstory|preload-search|preload-subscription)))?/.test( + $.request.url + ): + response = modifyAnswer(); + break; + // 回答页 - 屏蔽下翻出现的黑名单用户 + case $.data.read("zhihu_settings_blocked_users", false) !== false && + /^https?:\/\/api\.zhihu\.com\/next\?/.test($.request.url): + response = removeNextBlackUserAnswer(); + break; + // 黑名单增强 - 浏览黑名单用户信息时自动加入脚本黑名单 + case $.data.read("zhihu_settings_blocked_users", true) === true && + /^https?:\/\/api\.zhihu\.com\/people\/((?!self).)*$/.test( + $.request.url + ): + response = autoInsertBlackList(); + break; + // 关注页 - 去广告 + case /^https?:\/\/api\.zhihu\.com\/moments_v3\?/.test($.request.url): + response = removeMoments(); + break; + // 热榜页 - 去广告 + case $.data.read("zhihu_settings_hot_list", true) === true && + /^https?:\/\/api\.zhihu\.com\/topstory\/hot-lists(\?|\/)/.test( + $.request.url + ): + response = removeHotListAds(); + break; + // 搜索页 - 去除预置广告 + case $.data.read("zhihu_settings_preset_words", true) === true && + /^https?:\/\/api\.zhihu\.com\/search\/preset_words\?/.test( + $.request.url + ): + response = removeKeywordAds(); + break; + // 黑名单页 - 同步黑名单数据 + case $.data.read("zhihu_settings_blocked_users", false) !== false && + /^https?:\/\/api\.zhihu\.com\/settings\/blocked_users/.test( + $.request.url + ): + manageBlackUser(); + break; + default: + $.logger.debug("没有匹配到任何请求,请确认配置正确。"); + break; + } + } else if ($.isRequest) { + // 屏蔽关键词解锁 + if ( + $.data.read("zhihu_settings_blocked_keywords", false) !== false && + /^https?:\/\/api\.zhihu\.com\/feed-root\/block/.test($.request.url) === + true + ) { + response = unlockBlockedKeywords(response); + } + } else { + $.data.del(currentUserInfoKey); + $.data.del(blockedUsersKey); + $.data.del(keywordBlockKey); + $.notification.post("哲也同学数据清理完毕"); + } + if (response) { + $.done(response); + } else { + $.done(); + } +})(); + +// prettier-ignore +/** + * + * $$\ $$\ $$\ $$$$$\ $$$$$$\ $$$$$$\ + * $$$\ $$$ | \__| \__$$ |$$ __$$\ $$ ___$$\ + * $$$$\ $$$$ | $$$$$$\ $$$$$$\ $$\ $$$$$$$\ $$ |$$ / \__| \_/ $$ | + * $$\$$\$$ $$ | \____$$\ $$ __$$\ $$ |$$ _____| $$ |\$$$$$$\ $$$$$ / + * $$ \$$$ $$ | $$$$$$$ |$$ / $$ |$$ |$$ / $$\ $$ | \____$$\ \___$$\ + * $$ |\$ /$$ |$$ __$$ |$$ | $$ |$$ |$$ | $$ | $$ |$$\ $$ | $$\ $$ | + * $$ | \_/ $$ |\$$$$$$$ |\$$$$$$$ |$$ |\$$$$$$$\\$$$$$$ |\$$$$$$ | \$$$$$$ | + * \__| \__| \_______| \____$$ |\__| \_______|\______/ \______/ \______/ + * $$\ $$ | + * \$$$$$$ | + * \______/ + * + */ +// @formatter:off +function MagicJS(scriptName="MagicJS",logLevel="INFO"){const MagicEnvironment=()=>{const isLoon=typeof $loon!=="undefined";const isQuanX=typeof $task!=="undefined";const isNode=typeof module!=="undefined";const isSurge=typeof $httpClient!=="undefined"&&!isLoon;const isStorm=typeof $storm!=="undefined";const isStash=typeof $environment!=="undefined"&&typeof $environment["stash-build"]!=="undefined";const isSurgeLike=isSurge||isLoon||isStorm||isStash;const isScriptable=typeof importModule!=="undefined";return{isLoon:isLoon,isQuanX:isQuanX,isNode:isNode,isSurge:isSurge,isStorm:isStorm,isStash:isStash,isSurgeLike:isSurgeLike,isScriptable:isScriptable,get name(){if(isLoon){return"Loon"}else if(isQuanX){return"QuantumultX"}else if(isNode){return"NodeJS"}else if(isSurge){return"Surge"}else if(isScriptable){return"Scriptable"}else{return"unknown"}},get build(){if(isSurge){return $environment["surge-build"]}else if(isStash){return $environment["stash-build"]}else if(isStorm){return $storm.buildVersion}},get language(){if(isSurge||isStash){return $environment["language"]}},get version(){if(isSurge){return $environment["surge-version"]}else if(isStash){return $environment["stash-version"]}else if(isStorm){return $storm.appVersion}else if(isNode){return process.version}},get system(){if(isSurge){return $environment["system"]}else if(isNode){return process.platform}},get systemVersion(){if(isStorm){return $storm.systemVersion}},get deviceName(){if(isStorm){return $storm.deviceName}}}};const MagicLogger=(scriptName,logLevel="INFO")=>{let _level=logLevel;const logLevels={SNIFFER:6,DEBUG:5,INFO:4,NOTIFY:3,WARNING:2,ERROR:1,CRITICAL:0,NONE:-1};const logEmoji={SNIFFER:"",DEBUG:"",INFO:"",NOTIFY:"",WARNING:"❗ ",ERROR:"❌ ",CRITICAL:"❌ ",NONE:""};const _log=(msg,level="INFO")=>{if(!(logLevels[_level]{_level=logLevel};return{getLevel:()=>{return _level},setLevel:setLevel,sniffer:msg=>{_log(msg,"SNIFFER")},debug:msg=>{_log(msg,"DEBUG")},info:msg=>{_log(msg,"INFO")},notify:msg=>{_log(msg,"NOTIFY")},warning:msg=>{_log(msg,"WARNING")},error:msg=>{_log(msg,"ERROR")},retry:msg=>{_log(msg,"RETRY")}}};return new class{constructor(scriptName,logLevel){this._startTime=Date.now();this.version="3.0.0";this.scriptName=scriptName;this.env=MagicEnvironment();this.logger=MagicLogger(scriptName,logLevel);this.http=typeof MagicHttp==="function"?MagicHttp(this.env,this.logger):undefined;this.data=typeof MagicData==="function"?MagicData(this.env,this.logger):undefined;this.notification=typeof MagicNotification==="function"?MagicNotification(this.scriptName,this.env,this.logger,this.http):undefined;this.utils=typeof MagicUtils==="function"?MagicUtils(this.env,this.logger):undefined;this.qinglong=typeof MagicQingLong==="function"?MagicQingLong(this.env,this.data,this.logger):undefined;if(typeof this.data!=="undefined"){let magicLoglevel=this.data.read("magic_loglevel");const barkUrl=this.data.read("magic_bark_url");if(magicLoglevel){this.logger.setLevel(magicLoglevel.toUpperCase())}if(barkUrl){this.notification.setBark(barkUrl)}}}get isRequest(){return typeof $request!=="undefined"&&typeof $response==="undefined"}get isResponse(){return typeof $response!=="undefined"}get isDebug(){return this.logger.level==="DEBUG"}get request(){return typeof $request!=="undefined"?$request:undefined}get response(){if(typeof $response!=="undefined"){if($response.hasOwnProperty("status"))$response["statusCode"]=$response["status"];if($response.hasOwnProperty("statusCode"))$response["status"]=$response["statusCode"];return $response}else{return undefined}}done=(value={})=>{this._endTime=Date.now();let span=(this._endTime-this._startTime)/1e3;this.logger.debug(`SCRIPT COMPLETED: ${span} S.`);if(typeof $done!=="undefined"){$done(value)}}}(scriptName,logLevel)} +function MagicNotification(scriptName, env, logger, http) { + let _barkUrl = null; + let _barkKey = null; + const setBark = (url) => { + try { + let _url = url.replace(/\/+$/g, ""); + _barkUrl = `${/^https?:\/\/([^/]*)/.exec(_url)[0]}/push`; + _barkKey = /\/([^\/]+)\/?$/.exec(_url)[1]; + } catch (ex) { + logger.error(`Bark url error: ${ex}.`); + } + }; + function post(title = scriptName, subTitle = "", body = "", opts = "") { + const _adaptOpts = (_opts) => { + try { + let newOpts = {}; + if (typeof _opts === "string") { + if (env.isLoon) newOpts = { openUrl: _opts }; + else if (env.isQuanX) newOpts = { "open-url": _opts }; + else if (env.isSurge) newOpts = { url: _opts }; + } else if (typeof _opts === "object") { + if (env.isLoon) { + newOpts["openUrl"] = !!_opts["open-url"] ? _opts["open-url"] : ""; + newOpts["mediaUrl"] = !!_opts["media-url"] + ? _opts["media-url"] + : ""; + } else if (env.isQuanX) { + newOpts = !!_opts["open-url"] || !!_opts["media-url"] ? _opts : {}; + } else if (env.isSurge) { + let openUrl = _opts["open-url"] || _opts["openUrl"]; + newOpts = openUrl ? { url: openUrl } : {}; + } + } + return newOpts; + } catch (err) { + logger.error(`通知选项转换失败${err}`); + } + return _opts; + }; + opts = _adaptOpts(opts); + if (arguments.length === 1) { + title = scriptName; + (subTitle = ""), (body = arguments[0]); + } + logger.notify( + `title:${title}\nsubTitle:${subTitle}\nbody:${body}\noptions:${ + typeof opts === "object" ? JSON.stringify(opts) : opts + }` + ); + if (env.isSurge) { + $notification.post(title, subTitle, body, opts); + } else if (env.isLoon) { + if (!!opts) $notification.post(title, subTitle, body, opts); + else $notification.post(title, subTitle, body); + } else if (env.isQuanX) { + $notify(title, subTitle, body, opts); + } + if (_barkUrl && _barkKey) { + bark(title, subTitle, body); + } + } + function debug(title = scriptName, subTitle = "", body = "", opts = "") { + if (logger.getLevel() === "DEBUG") { + if (arguments.length === 1) { + title = scriptName; + subTitle = ""; + body = arguments[0]; + } + this.post(title, subTitle, body, opts); + } + } + function bark(title = scriptName, subTitle = "", body = "", opts = "") { + if (typeof http === "undefined" || typeof http.post === "undefined") { + throw "Bark notification needs to import MagicHttp module."; + } + let options = { + url: _barkUrl, + headers: { "content-type": "application/json; charset=utf-8" }, + body: { + title: title, + body: subTitle ? `${subTitle}\n${body}` : body, + device_key: _barkKey, + }, + }; + http.post(options).catch((ex) => { + logger.error(`Bark notify error: ${ex}`); + }); + } + return { post: post, debug: debug, bark: bark, setBark: setBark }; +} +function MagicData(env, logger) { + let node = { fs: undefined, data: {} }; + if (env.isNode) { + node.fs = require("fs"); + try { + node.fs.accessSync( + "./magic.json", + node.fs.constants.R_OK | node.fs.constants.W_OK + ); + } catch (err) { + node.fs.writeFileSync("./magic.json", "{}", { encoding: "utf8" }); + } + node.data = require("./magic.json"); + } + const defaultValueComparator = (oldVal, newVal) => { + if (typeof newVal === "object") { + return false; + } else { + return oldVal === newVal; + } + }; + const _typeConvertor = (val) => { + if (val === "true") { + return true; + } else if (val === "false") { + return false; + } else if (typeof val === "undefined") { + return null; + } else { + return val; + } + }; + const _valConvertor = (val, default_, session, read_no_session) => { + if (session) { + try { + if (typeof val === "string") val = JSON.parse(val); + if (val["magic_session"] === true) { + val = val[session]; + } else { + val = null; + } + } catch { + val = null; + } + } + if (typeof val === "string" && val !== "null") { + try { + val = JSON.parse(val); + } catch {} + } + if (read_no_session === false && !!val && val["magic_session"] === true) { + val = null; + } + if ( + (val === null || typeof val === "undefined") && + default_ !== null && + typeof default_ !== "undefined" + ) { + val = default_; + } + val = _typeConvertor(val); + return val; + }; + const convertToObject = (obj) => { + if (typeof obj === "string") { + let data = {}; + try { + data = JSON.parse(obj); + const type = typeof data; + if ( + type !== "object" || + data instanceof Array || + type === "bool" || + data === null + ) { + data = {}; + } + } catch {} + return data; + } else if ( + obj instanceof Array || + obj === null || + typeof obj === "undefined" || + obj !== obj || + typeof obj === "boolean" + ) { + return {}; + } else { + return obj; + } + }; + const readForNode = ( + key, + default_ = null, + session = "", + read_no_session = false, + externalData = null + ) => { + let data = externalData || node.data; + if (!!data && typeof data[key] !== "undefined" && data[key] !== null) { + val = data[key]; + } else { + val = !!session ? {} : null; + } + val = _valConvertor(val, default_, session, read_no_session); + return val; + }; + const read = ( + key, + default_ = null, + session = "", + read_no_session = false, + externalData = null + ) => { + let val = ""; + if (externalData || env.isNode) { + val = readForNode(key, default_, session, read_no_session, externalData); + } else { + if (env.isSurgeLike) { + val = $persistentStore.read(key); + } else if (env.isQuanX) { + val = $prefs.valueForKey(key); + } + val = _valConvertor(val, default_, session, read_no_session); + } + logger.debug( + `READ DATA [${key}]${ + !!session ? `[${session}]` : "" + } <${typeof val}>\n${JSON.stringify(val)}` + ); + return val; + }; + const writeForNode = (key, val, session = "", externalData = null) => { + let data = externalData || node.data; + data = convertToObject(data); + if (!!session) { + let obj = convertToObject(data[key]); + obj["magic_session"] = true; + obj[session] = val; + data[key] = obj; + } else { + data[key] = val; + } + if (externalData !== null) { + externalData = data; + } + return data; + }; + const write = (key, val, session = "", externalData = null) => { + if (typeof val === "undefined" || val !== val) { + return false; + } + if (!env.isNode && (typeof val === "boolean" || typeof val === "number")) { + val = String(val); + } + let data = ""; + if (externalData || env.isNode) { + data = writeForNode(key, val, session, externalData); + } else { + if (!session) { + data = val; + } else { + if (env.isSurgeLike) { + data = !!$persistentStore.read(key) + ? $persistentStore.read(key) + : data; + } else if (env.isQuanX) { + data = !!$prefs.valueForKey(key) ? $prefs.valueForKey(key) : data; + } + data = convertToObject(data); + data["magic_session"] = true; + data[session] = val; + } + } + if (!!data && typeof data === "object") { + data = JSON.stringify(data, null, 4); + } + logger.debug( + `WRITE DATA [${key}]${ + session ? `[${session}]` : "" + } <${typeof val}>\n${JSON.stringify(val)}` + ); + if (!externalData) { + if (env.isSurgeLike) { + return $persistentStore.write(data, key); + } else if (env.isQuanX) { + return $prefs.setValueForKey(data, key); + } else if (env.isNode) { + try { + node.fs.writeFileSync("./magic.json", data); + return true; + } catch (err) { + logger.error(err); + return false; + } + } + } + return true; + }; + const update = ( + key, + val, + session, + comparator = defaultValueComparator, + externalData = null + ) => { + val = _typeConvertor(val); + const oldValue = read(key, null, session, false, externalData); + if (comparator(oldValue, val) === true) { + return false; + } else { + const result = write(key, val, session, externalData); + let newVal = read(key, null, session, false, externalData); + if (comparator === defaultValueComparator && typeof newVal === "object") { + return result; + } + return comparator(val, newVal); + } + }; + const delForNode = (key, session, externalData) => { + let data = externalData || node.data; + data = convertToObject(data); + if (!!session) { + obj = convertToObject(data[key]); + delete obj[session]; + data[key] = obj; + } else { + delete data[key]; + } + if (!!externalData) { + externalData = data; + } + return data; + }; + const del = (key, session = "", externalData = null) => { + let data = {}; + if (externalData || env.isNode) { + data = delForNode(key, session, externalData); + if (!externalData) { + node.fs.writeFileSync("./magic.json", JSON.stringify(data, null, 4)); + } else { + externalData = data; + } + } else { + if (!session) { + if (env.isStorm) { + return $persistentStore.remove(key); + } else if (env.isSurgeLike) { + return $persistentStore.write(null, key); + } else if (env.isQuanX) { + return $prefs.removeValueForKey(key); + } + } else { + if (env.isSurgeLike) { + data = $persistentStore.read(key); + } else if (env.isQuanX) { + data = $prefs.valueForKey(key); + } + data = convertToObject(data); + delete data[session]; + const json = JSON.stringify(data, null, 4); + write(key, json); + } + } + logger.debug(`DELETE KEY [${key}]${!!session ? `[${session}]` : ""}`); + }; + const allSessionNames = (key, externalData = null) => { + let _sessions = []; + let data = read(key, null, null, true, externalData); + data = convertToObject(data); + if (data["magic_session"] !== true) { + _sessions = []; + } else { + _sessions = Object.keys(data).filter((key) => key !== "magic_session"); + } + logger.debug( + `READ ALL SESSIONS [${key}] <${typeof _sessions}>\n${JSON.stringify( + _sessions, + null, + 4 + )}` + ); + return _sessions; + }; + const allSessions = (key, externalData = null) => { + let _sessions = {}; + let data = read(key, null, null, true, externalData); + data = convertToObject(data); + if (data["magic_session"] === true) { + _sessions = { ...data }; + delete _sessions["magic_session"]; + } + logger.debug( + `READ ALL SESSIONS [${key}] <${typeof _sessions}>\n${JSON.stringify( + _sessions, + null, + 4 + )}` + ); + return _sessions; + }; + return { + read: read, + write: write, + del: del, + update: update, + allSessions: allSessions, + allSessionNames: allSessionNames, + defaultValueComparator: defaultValueComparator, + convertToObject: convertToObject, + }; +} +// @formatter:on