├── README.md
├── bft-mirror.user.js
└── bft.user.js
/README.md:
--------------------------------------------------------------------------------
1 | # BiliFilter / 必滤
2 | 一个可以过滤BiliBili上不顺眼的东西的`油猴脚本`,支持按用户/标题/甚至是视频时长过滤内容。
3 |
4 | 
5 |
6 | ## 🐮🍺 介绍
7 |
8 | 通过规则集之类的进行屏蔽的脚本,其特性可总结为3点。
9 |
10 | 屏蔽:
11 | - 屏蔽首页、分区页的视频(暂时除了纪录片、电视剧、电影、综艺、国创、番剧分区)
12 | - 屏蔽视频播放页的视频推荐和评论
13 | - 屏蔽搜索页的搜索结果(暂时不支持根据用户屏蔽)
14 | - 屏蔽专栏页的评论
15 |
16 | 规则集:
17 | - 按功能分为用户规则集和标题评论规则集:
18 | - 用户规则集:支持对用户发出的视频、评论进行屏蔽
19 | - 标题评论规则集:支持使用正则
20 | - 按类型分为本地规则集和远程规则集:
21 | - 本地规则集:仅保存在本地的规则集,支持编辑、导入、导出规则集中的条目的操作
22 | - 远程规则集:从云端获取的规则集,支持更新、转为本地规则集的操作
23 |
24 | 其他过滤:
25 | - 时长过滤:屏蔽低于设定时长的视频
26 |
27 |
28 |
29 |
30 |
31 | ## ♿🚩 使用
32 |
33 | ### 准备
34 |
35 | 1. 首先请检查以下要求,确保您是可以使用这个脚本的。
36 | - 使用哔哩哔哩网页版(新版页面,即右下角那个三个点里面有`退出内测`和`返回旧版`两个按钮的页面),但并非手机版网页(即一般在手机上使用,域名是`https://m.bilibili.com`的网页)
37 | - 想屏蔽掉视频、评论、专栏,而不是页面中的广告(本脚本可过滤不了广告)
38 | - 能够使用搜索引擎,去了解自己不知道的名词
39 | 2. 检查您使用的浏览器,确保满足以下条件,如果没有,请安装一个满足条件的。以下是本人`推荐`的各个操作系统所适用的浏览器及最低的浏览器版本。
40 | - Mac OS、Ipad OS: Safari(10.3+)
41 | - Windows、Linux: Chrome(55+)、Edge(15+)、Firefox(52+)
42 | - Android:[X浏览器](https://www.xbext.com/index.html)、[KiwiBroser](https://kiwibrowser.com/)
43 | 3. 为您的浏览器安装可以运行油猴脚本的插件
44 | - Safari:[Stay2](https://apps.apple.com/cn/app/id1591620171),[安装指南](https://zhuanlan.zhihu.com/p/590956705)
45 | - Chrome&Edge:[篡改猴](https://microsoftedge.microsoft.com/addons/detail/%E7%AF%A1%E6%94%B9%E7%8C%B4/iikmkjmpaadaobahmlepeloendndfphd?hl=zh-CN)
46 | - Firefox:[Tampermonkey](https://addons.mozilla.org/zh-CN/firefox/addon/tampermonkey/)
47 | - X浏览器:不需要,该软件已经内置相关工具。
48 | - KiwiBrowser:[篡改猴](https://microsoftedge.microsoft.com/addons/detail/%E7%AF%A1%E6%94%B9%E7%8C%B4)
49 |
50 | ### 安装脚本
51 |
52 | 确保安装并激活插件后,点击[这里](https://github.com/ChizhaoEngine/BFT/raw/main/bft.user.js),浏览器会打开新页面,然后会弹出安装脚本的选项(各个软件提示不一,但大致类似)。如果弹出询问是否允许脚本连接到外部资源,则选择一律允许。
53 |
54 | 若出现无法访问的情况(连接超时),请使用[这个](https://ghproxy.homeboyc.cn/https://github.com/ChizhaoEngine/BiliFilter/raw/main/bft-mirror.user.js)。
55 |
56 | ### 使用
57 |
58 | 再次打开哔哩哔哩网页版,右侧便会出现一灰色悬浮按钮,移动鼠标指针靠近它,便会弹出菜单。您可以根据自己的实际情况进行配置。下面是快速入门指南。
59 |
60 | 1. 添加您的第一个用户过滤规则集
61 | - 打开本脚本的菜单,点击用户过滤设置
62 | - 点击右上方的`文件`图标,即可新建一个`本地规则集`
63 | 2. 配置用户过滤规则集
64 | - 点击每个规则集条目的右侧,从左向右第一个复选框,即可切换是否启用该规则集
65 | - 剩下三个按钮,从左至右依次是:导出、编辑、删除
66 | - 如果不小心点击了删除,请点击菜单下方的取消。在点击确定之前,您的更改不会被保存
67 | 3. 关于用户过滤中的 `标记等级`与`过滤等级`
68 | - `标记等级`是给需要屏蔽的每个用户拟定的数值,规定为1~5的整数。具体规定为,数值越小,越需要屏蔽
69 | - `过滤等级`是给每个规则集拟定的数值,也规定为1~5的整数。具体规定为,数值越高,屏蔽强度越高
70 | - 只有当用户的`标记等级`小于等于其对应的规则集的`过滤等级`,该用户才会被屏蔽
71 | 4. 编写标题内容过滤规则集的规则
72 | - 此处需要使用正则表达式编写规则,这里是正则表达式的快速入门指南
73 | - 左侧的输入框是每一行为一个规则,右侧则是以json格式编辑
74 | - 使用json编辑时,编辑完成后一定要点击`json推送`,使规则集同步到左侧
75 |
76 |
77 | ## 🕷️🐍 兼容性
78 |
79 | 与其他的油猴脚本待测试,如有兼容性问题请直接提issue。
80 |
81 | 若使用先前的远古版本,直接升级即可。
82 |
83 | 理论上支持 Javascript SE8 的浏览器即可运行该脚本。
84 |
85 | ## 🍆🍑 API的使用
86 |
87 | 仅获取b站黑名单会使用API。本脚本尽可能不使用API。
88 |
89 | ## 🕊️📄 计划
90 |
91 | - [ ] 支持屏蔽所有分区页
92 | - [ ] 支持对用户空间动态的屏蔽
93 | - [ ] 支持屏蔽用户私信
94 | - [ ] 支持根据用户屏蔽搜索页内容
95 | - [ ] 支持屏蔽专栏
96 | - [ ] 适配旧页面
97 | - [ ] 优化代码逻辑
98 |
99 | 更多建议或需求欢迎在issue提。
100 |
--------------------------------------------------------------------------------
/bft.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name BiliFilter3
3 | // @namespace https://github.com/ChizhaoEngine/BiliFilter
4 | // @version 0.3.15
5 | // @description 杀掉你不想看到的东西
6 | // @author 池沼动力
7 | // @license CC BY-NC-ND 4.0
8 | // @copyright CC BY-NC-ND 4.0
9 | // @match *.bilibili.com/*
10 | // @icon https://s2.loli.net/2023/01/24/caWi5nrZJDuvFsy.png
11 | // @run-at document-start
12 | // @grant GM_xmlhttpRequest
13 | // @grant GM_getValue
14 | // @grant GM_setValue
15 | // @grant GM_getResourceURL
16 | // @grant GM_getResourceText
17 | // @grant GM_registerMenuCommand
18 | // @grant GM.setClipboard
19 | // @grant GM_addStyle
20 | // @connect *
21 | // @require https://cdn.jsdelivr.net/npm/vue/dist/vue.js
22 | // @updateURL https://raw.githubusercontent.com/ChizhaoEngine/BiliFilter/main/bft.user.js
23 | // @downloadURL https://raw.githubusercontent.com/ChizhaoEngine/BiliFilter/main/bft.user.js
24 |
25 |
26 |
27 | // ==/UserScript==
28 |
29 | (function () {
30 | 'use strict';
31 | GM_addStyle(`
32 | /* 文本黑幕 */
33 | .bft-heimu span {
34 |
35 | opacity: 0;
36 | transition: opacity 0.3s ease;
37 | }
38 |
39 | .bft-heimu:hover span {
40 | opacity: 1;
41 | }
42 |
43 | /* 内容覆盖层 */
44 | .bft-overlay {
45 | position: relative;
46 | }
47 |
48 | .bft-overlay::after {
49 | content: "";
50 | position: absolute;
51 | top: 0;
52 | left: 0;
53 | width: 100%;
54 | height: 100%;
55 | background-color: #faf9f9;
56 | opacity: 1;
57 | transition: opacity 0.3s ease;
58 | pointer-events: none;
59 | z-index: 5;
60 | /* 提高层级,使覆盖层在内容上方 */
61 | border-radius: 5px;
62 | }
63 |
64 | .bft-overlay:hover::after {
65 | opacity: 0;
66 | }
67 |
68 | /* bft 统一样式 */
69 | /* 设置悬浮窗 */
70 | .bft-setting-window {
71 | display: block;
72 | position: fixed;
73 | top: 20px;
74 | /* 距离顶部的距离 */
75 | right: 20px;
76 | /* 距离右侧的距离 */
77 | /* 边距 */
78 | margin: auto;
79 | /* 宽度 */
80 | min-width: 35vh;
81 | max-width: 728px;
82 | /* 背景 */
83 | background-color: #efecfa;
84 | /* 圆角 */
85 | border-radius: 20px;
86 | transition: width 2s;
87 | width: auto;
88 | /* 层 */
89 | z-index: 9999;
90 | }
91 |
92 | .bft-setting-title {
93 | padding: 40px 24px 20px 24px;
94 | box-sizing: border-box;
95 | font-weight: 500;
96 | font-size: 20px;
97 | line-height: 24px;
98 | text-align: left;
99 | }
100 |
101 | small {
102 | font-size: 80%;
103 | opacity: 0.5;
104 | }
105 |
106 | /* 悬浮窗内容 */
107 | .bft-setting-contain {
108 | box-sizing: border-box;
109 | padding: 24px 24px 0px 24px;
110 | overflow-y: auto;
111 | font-size: 15px;
112 | line-height: 1.5;
113 | max-height: 75vh;
114 | }
115 |
116 |
117 | /* 规则集面板内容 */
118 | .bft-ruleset {
119 | display: flex;
120 | flex-wrap: wrap;
121 | align-items: center;
122 | box-sizing: border-box;
123 | min-height: 48px;
124 | padding: 0 16px;
125 | background-color: #fbf8ff;
126 | border-radius: 10px;
127 | margin-bottom: 10px;
128 | transition: height 0.5s, width 0.5s;
129 | /* 过渡效果,同时设置高度和宽度属性在0.5秒内变化 */
130 | height: auto;
131 | /* 设置为auto时,高度会自动根据内容变化 */
132 | }
133 |
134 | /* 规则集面板图标 */
135 | .bft-ruleset-icon {
136 | border-radius: 8px !important;
137 | min-width: 40px;
138 | max-width: 40px;
139 | height: 40px;
140 | margin-top: 8px;
141 | margin-bottom: 8px;
142 | line-height: 40px;
143 | background-color: #aaa6f4;
144 | /* 居中 */
145 | display: flex;
146 | /* 水平居中 */
147 | justify-content: center;
148 | /* 垂直居中 */
149 | align-items: center;
150 | }
151 |
152 | /* 规则集信息容器 */
153 | .bft-ruleset-info {
154 | flex-grow: 1;
155 | padding-top: 14px;
156 | padding-bottom: 14px;
157 | font-weight: 400;
158 | font-size: 16px;
159 | line-height: 20px;
160 | margin-left: 15px;
161 | }
162 |
163 | /* 规则集标题 */
164 | .bft-ruleset-info-title {
165 | max-width: 180px;
166 | /* 设置文本超过容器宽度时截断 */
167 | white-space: nowrap;
168 | /* 超过容器宽度的部分用省略号代替 */
169 | text-overflow: ellipsis;
170 | /* 隐藏超出容器宽度的内容 */
171 | overflow: hidden;
172 | font-weight: 500;
173 | font-size: 14px;
174 | letter-spacing: .04em;
175 |
176 | }
177 |
178 | .bft-ruleset-info-title small {
179 | margin-left: 5px;
180 | }
181 |
182 | /* 规则集其余信息 */
183 | .bft-ruleset-info-other {
184 | font-weight: 300;
185 | font-size: 14px;
186 | letter-spacing: .04em;
187 | opacity: 0.5;
188 | }
189 |
190 | /* 规则集操作 */
191 |
192 | .bft-ruleset-action {
193 | margin-left: 10px;
194 | min-width: 80px;
195 | display: flex;
196 | }
197 |
198 |
199 |
200 | /* 规则集操作:复选框 */
201 | .bft-ruleset-action input[type="checkbox"] {
202 | -webkit-appearance: none;
203 | -moz-appearance: none;
204 | appearance: none;
205 | margin-right: 6px;
206 | margin-top: 8px;
207 | width: 14px;
208 | height: 14px;
209 | border: 1.5px solid gray;
210 | border-radius: 4px;
211 | outline: none;
212 | }
213 |
214 | /* Unchecked state */
215 | .bft-ruleset-action input[type="checkbox"]:not(:checked) {
216 | background-color: #fff;
217 | }
218 |
219 | /* Checked state */
220 | .bft-ruleset-action input[type="checkbox"]:checked {
221 | background-color: gray;
222 | border-color: gray;
223 | }
224 |
225 | /* Custom checkmark icon */
226 | .bft-ruleset-action input[type="checkbox"]::before {
227 | content: "";
228 | display: inline-block;
229 | width: 5px;
230 | height: 1px;
231 | border: solid #fff;
232 | border-width: 0 0 2px 2px;
233 | transform: rotate(-45deg);
234 | position: relative;
235 | top: -5px;
236 | left: 2px;
237 | visibility: hidden;
238 | font-family: revert;
239 | box-sizing: revert;
240 | }
241 |
242 | /* Show checkmark for checked checkboxes */
243 | .bft-ruleset-action input[type="checkbox"]:checked::before {
244 | visibility: visible;
245 | }
246 |
247 | /* 规则集编辑面板内容 */
248 | .bft-ruleset-contain {
249 | padding-bottom: 14px;
250 | padding-top: 14px;
251 | font-weight: 400;
252 | font-size: 16px;
253 | line-height: 20px;
254 | flex-grow: 2;
255 | display: flex;
256 | flex-wrap: wrap;
257 | }
258 |
259 |
260 |
261 | /* UID过滤:规则条目操作 */
262 | .bft-ruleset-rulelist-action {
263 | margin: 10px;
264 | }
265 |
266 | /* 规则列表 */
267 | .bft-ruleset-rulelist-list {
268 | display: flex;
269 | flex-wrap: wrap;
270 | }
271 |
272 | /* 规则条目 */
273 | .bft-ruleset-rulelist-item {
274 | display: flex;
275 | width: 280px;
276 | flex-wrap: wrap;
277 | margin-left: 10px;
278 | }
279 |
280 | /* 条目操作按钮 */
281 | .bft-ruleset-rulelist-item button {
282 | margin-top: 5px;
283 | margin-right: 8px;
284 | }
285 |
286 | /* 条目输入框 */
287 | .bft-ruleset-rulelist-item .bft-input-container {
288 | width: 120px;
289 | }
290 |
291 | /* 条目标签 */
292 | .bft-ruleset-rulelist-item h1 {
293 | font-size: 1em;
294 | margin-left: 10px;
295 | width: 95px;
296 | margin: 10px 0px 0px 10px;
297 | font-weight: revert;
298 | }
299 |
300 | .bft-ruleset-rulelist-item h2 {
301 | margin-top: 10px;
302 | font-size: 0.7em;
303 | color: gray;
304 | font-weight: revert;
305 | line-height: revert;
306 | }
307 |
308 |
309 |
310 | /* 悬浮窗操作 */
311 | .bft-setting-action {
312 | box-sizing: border-box;
313 | padding: 10px 24px 20px 24px;
314 | text-align: right;
315 | }
316 |
317 | /* 关于 */
318 | .bft-about {
319 | display: flex;
320 | align-items: center;
321 | box-sizing: border-box;
322 | min-height: 48px;
323 | padding: 0 16px;
324 | background-color: #fbf8ff;
325 | border-radius: 10px;
326 | margin-bottom: 10px;
327 | transition: height 0.5s, width 0.5s;
328 | height: auto;
329 | flex-wrap: wrap;
330 | flex-direction: column;
331 | width: 300px;
332 | }
333 |
334 | .bft-about h1 {
335 | font-size: 1em;
336 | color: #7469ae;
337 | margin: 10px;
338 | padding: 0;
339 | }
340 |
341 | .bft-about p {
342 | font-size: 0.7em;
343 | color: #787878;
344 | margin: 10px;
345 | }
346 |
347 | .bft-about a {
348 | color: #787878;
349 | margin: 10px;
350 | cursor: pointer;
351 | text-decoration: none;
352 | }
353 |
354 | /* 右侧悬浮按钮 fab */
355 | .bft-fab {
356 | position: fixed;
357 | bottom: 40vh;
358 | right: -25px;
359 | transition: right 0.3s ease-in-out;
360 | z-index: 9999;
361 | }
362 |
363 | .bft-fab:hover {
364 | right: 0;
365 | }
366 |
367 |
368 | .bft-fab-big {
369 | width: 50px;
370 | height: 50px;
371 | background-color: #f6f6f6;
372 | border-radius: 50%;
373 | display: flex;
374 | align-items: center;
375 | justify-content: center;
376 | cursor: pointer;
377 | }
378 |
379 | .bft-fab-big svg{
380 | fill: #cdcdcd;
381 | }
382 |
383 | .bft-fab:hover .bft-fab-mini-contain {
384 | right: 10px;
385 | opacity: 1;
386 | }
387 |
388 | .bft-fab-mini-contain {
389 | display: flex;
390 | flex-direction: column;
391 | position: absolute;
392 | padding-bottom: 20px;
393 | bottom: 40px;
394 | right: -150px;
395 | opacity: 0;
396 | transition: right 1s linear(0 0%, 0 1.8%, 0.01 3.6%, 0.03 6.35%, 0.07 9.1%, 0.13 11.4%, 0.19 13.4%, 0.27 15%, 0.34 16.1%, 0.54 18.35%, 0.66 20.6%, 0.72 22.4%, 0.77 24.6%, 0.81 27.3%, 0.85 30.4%, 0.88 35.1%, 0.92 40.6%, 0.94 47.2%, 0.96 55%, 0.98 64%, 0.99 74.4%, 1 86.4%, 1 100%), opacity 0.3s ease-in-out;
397 | }
398 |
399 | .bft-fab-mini {
400 | display: flex;
401 | align-items: center;
402 | background-color: #f0ecfa;
403 | border-radius: 50px;
404 | margin-bottom: 10px;
405 | padding: 6px 12px;
406 | cursor: pointer;
407 | transition: background-color 0.3s ease-in-out;
408 | }
409 |
410 | .bft-fab-mini:hover {
411 | background-color: #e7e5f2;
412 | }
413 |
414 | .bft-fab-mini svg {
415 | fill: #afa3f4;
416 | margin-right: 8px;
417 | }
418 |
419 | .bft-fab-mini-label {
420 | font-size: 14px;
421 | color: #5a4969;
422 | white-space: nowrap;
423 | }
424 |
425 | .bft-fab-mini:last-child {
426 | margin-bottom: 0;
427 | }
428 |
429 | /* 其他组件 */
430 | /* 图标 */
431 |
432 |
433 | .bft-icon {
434 | display: block;
435 | width: 60%;
436 | /* 调整图标宽度根据需要 */
437 | height: 60%;
438 | /* 调整图标高度根据需要 */
439 | fill: white;
440 | /* 设置图标颜色 */
441 | text-align: center;
442 | /* 居中文本 */
443 | line-height: 24px;
444 | /* 确保图标在垂直方向居中 */
445 | }
446 |
447 | /* 按钮 */
448 | .bft-button {
449 | cursor: pointer;
450 | border-radius: 25px;
451 | background-color: #ffffff;
452 | border: none;
453 | height: 30px;
454 | min-width: 50px;
455 | padding: 5px 10px;
456 | font-size: 85%;
457 | }
458 |
459 |
460 | .bft-button:hover {
461 | background-color: #ece4fc;
462 | }
463 |
464 | .bft-button:active {
465 | background-color: #d5c8f7;
466 | }
467 |
468 | /* 图标按钮 */
469 | button.bft-button-icon {
470 | background-color: #fff;
471 | margin-left: 3px;
472 | width: 30px;
473 | height: 30px;
474 | font-size: 14px;
475 | line-height: 36px;
476 | letter-spacing: .04em;
477 | text-transform: uppercase;
478 | border: none;
479 | border-radius: 100px;
480 | outline: 0;
481 | cursor: pointer;
482 | touch-action: manipulation;
483 | will-change: box-shadow;
484 | padding: 7px;
485 |
486 | /* 居中 */
487 | display: flex;
488 | /* 水平居中 */
489 | justify-content: center;
490 | /* 垂直居中 */
491 | align-items: center;
492 | }
493 |
494 | button.bft-button-icon:hover {
495 | background-color: #ece4fc;
496 |
497 | }
498 |
499 | button.bft-button-icon:active {
500 | background-color: #d5c8f7;
501 | }
502 |
503 | button.bft-button-icon svg {
504 | height: 100%;
505 | width: 100%;
506 | fill: gray;
507 | }
508 |
509 | /* 覆盖B站的 :focus 样式 */
510 | body button:focus {
511 | background-color: white;
512 | outline: revert;
513 | }
514 |
515 | /* 文本框 */
516 | /* 输入框容器样式 */
517 | .bft-input-container {
518 | position: relative;
519 | margin: 10px;
520 | width: 100%;
521 | margin: 15px 10px 10px 10px;
522 | }
523 |
524 | /* 输入框样式 */
525 | .bft-input-field {
526 | width: 100%;
527 | padding: 5px 0;
528 | border: none;
529 | border-bottom: 2px solid #a6a6a6;
530 | outline: none;
531 | background: transparent;
532 | transition: border-bottom-color 0.3s ease;
533 | font-size: revert;
534 | }
535 |
536 | /* 删除输入框部分样式 */
537 | /* Firefox */
538 | input[type='number'] {
539 | -moz-appearance: textfield;
540 | }
541 |
542 | /* Webkit browsers like Safari and Chrome */
543 | input[type=number]::-webkit-inner-spin-button,
544 | input[type=number]::-webkit-outer-spin-button {
545 | -webkit-appearance: none;
546 | margin: 0;
547 | }
548 |
549 |
550 | /* 输入框获取焦点时下划线颜色变化 */
551 | .bft-input-field:focus {
552 | border-bottom-color: #8a80c1;
553 | }
554 |
555 | /* 输入框的placeholder标签样式 */
556 | .bft-input-label {
557 | position: absolute;
558 | top: 0;
559 | left: 0;
560 | pointer-events: none;
561 | transition: 0.3s ease all;
562 | color: gray;
563 | }
564 |
565 | /* 输入框获得焦点或有值时标签上移 */
566 | .bft-input-field:focus~.bft-input-label,
567 | .bft-input-field:valid~.bft-input-label {
568 | top: -15px;
569 | font-size: 14px;
570 | color: #8a80c1;
571 | }
572 |
573 | /* 输入框底部的边框条样式 */
574 | .bft-input-bar {
575 | position: absolute;
576 | bottom: 0;
577 | display: block;
578 | width: 0;
579 | height: 2px;
580 | background-color: #8a80c1;
581 | transition: 0.3s ease all;
582 | }
583 |
584 | /* 输入框获得焦点时底部边框条扩展 */
585 | .bft-input-field:focus~.bft-input-bar {
586 | width: 100%;
587 | }
588 |
589 | /* 无效值时的文本框 */
590 | /* 输入框样式 */
591 | .bft-input-field:invalid {
592 | width: 100%;
593 | padding: 5px 0;
594 | border: none;
595 | border-bottom: 2px solid #ff7272;
596 | outline: none;
597 | background: transparent;
598 | transition: border-bottom-color 0.3s ease;
599 | }
600 |
601 | .bft-input-field:focus:invalid {
602 | border-bottom-color: #ff9e9e;
603 | }
604 |
605 | .bft-input-label:invalid {
606 | position: absolute;
607 | top: 0;
608 | left: 0;
609 | pointer-events: none;
610 | transition: 0.3s ease all;
611 | color: gray;
612 | }
613 |
614 | .bft-input-field:invalid~.bft-input-label {
615 | top: -15px;
616 | font-size: 14px;
617 | color: #ff9e9e;
618 | }
619 |
620 | .bft-input-bar:invalid {
621 | position: absolute;
622 | bottom: 0;
623 | display: block;
624 | width: 0;
625 | height: 2px;
626 | background-color: #ff9e9e;
627 | transition: 0.3s ease all;
628 | }
629 |
630 | .bft-input-field:focus~.bft-input-bar:invalid {
631 | width: 100%;
632 | }
633 |
634 | /* 多行输入框 */
635 |
636 | .bft-textarea-container {
637 | min-width: 95px;
638 | margin: 10px;
639 | display: flex;
640 | max-width: 280px;
641 | flex-wrap: wrap;
642 | }
643 |
644 | .bft-textarea-container label {
645 | margin: 10px;
646 | width: 280px;
647 | font-size: 0.9em;
648 | color: gray;
649 | }
650 |
651 | .bft-textarea-container textarea {
652 | min-width: 80px;
653 | width: 280px;
654 | height: 80px;
655 | margin: 10px;
656 | border: none;
657 | width: 100%;
658 | padding: 10px;
659 | border-radius: 10px;
660 | outline: none;
661 | resize: vertical;
662 | background-color: #fff;
663 | /* 可以让用户在垂直方向调整Textarea大小 */
664 | }
665 |
666 | .bft-textarea-container textarea:focus {
667 | border: none;
668 | }
669 |
670 | /* 下拉选项框 */
671 | .bft-select {
672 | width: 200px;
673 | height: 36px;
674 | padding-right: 24px;
675 | padding-left: 20px;
676 | margin: 8px;
677 | font-size: 16px;
678 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M-.003 2.5l5 5 5-5h-10z' opacity='.54'/%3E%3C/svg%3E");
679 | background-repeat: no-repeat;
680 | background-position: right center;
681 | border: none;
682 | border-radius: 10px;
683 | outline: 0;
684 | cursor: pointer;
685 | -webkit-appearance: none;
686 | -moz-appearance: none;
687 | appearance: none;
688 | }
689 |
690 | label.bft-select-label {
691 | margin: 15px;
692 | font-size: 0.9em;
693 | color: gray;
694 | }
695 |
696 | /* Snackbar */
697 |
698 | .bft-snackbar-container {
699 | position: fixed;
700 | top: 16px;
701 | right: 10px;
702 | z-index: 9999;
703 | /* 提高层级,使覆盖层在内容上方 */
704 | }
705 |
706 | .bft-snackbar {
707 | margin-top: 8px;
708 | background-color: #ffffff;
709 | color: #000;
710 | padding: 8px 24px;
711 | border-radius: 10px;
712 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
713 | display: flex;
714 | font-size: 0.9em;
715 | width:50vh;
716 | justify-content: space-between;
717 | }
718 |
719 | .bft-snackbar-icon {
720 | display: flex;
721 | align-items: center;
722 | }
723 |
724 | .bft-snackbar-icon svg {
725 | height: 30px;
726 | width: 30px;
727 | fill: revert;
728 | }
729 |
730 | .bft-snackbar-content {
731 | display: flex;
732 | font-size: 1em;
733 | align-items: center;
734 | margin-left: 10px;
735 | padding: 15px 0;
736 | text-overflow: ellipsis;
737 | overflow: hidden;
738 | white-space: nowrap;
739 | }
740 |
741 | .bft-snackbar button {
742 | color: #7469ae;
743 | background-color: #ffffff;
744 | border: none;
745 | border-radius: 10px;
746 | padding: 0px 5px;
747 | cursor: pointer;
748 | margin-left: 16px;
749 | font-size: 0.9em;
750 | white-space: nowrap;
751 | }
752 |
753 | .bft-snackbar button:hover {
754 | background-color: #e6e6e6;
755 | }
756 |
757 | /* 可交互式对话框 */
758 | .bft-dialog-model {
759 | position: fixed;
760 | z-index: 9999;
761 | left: 0;
762 | top: 0;
763 | width: 100%;
764 | height: 100%;
765 | overflow: auto;
766 | background-color: rgba(0, 0, 0, 0.4);
767 | }
768 |
769 | .bft-dialog {
770 | display: block;
771 | position: relative;
772 | top: 30px;
773 | margin: auto;
774 | min-width: 25vh;
775 | max-width: 418px;
776 | min-height: 15px;
777 | background-color: #fff;
778 | border-radius: 20px;
779 | transition: width 2s;
780 | width: auto;
781 | z-index: 9999;
782 | }
783 |
784 | .bft-dialog-title {
785 | padding: 25px 16px 10px 24px;
786 | box-sizing: border-box;
787 | font-weight: 500;
788 | font-size: 15px;
789 | line-height: 24px;
790 | text-align: left;
791 | }
792 |
793 | .bft-dialog-content {
794 | padding: 15px;
795 | font-size: 12px;
796 | display: flex;
797 | flex-wrap: wrap;
798 | }
799 |
800 | .bft-dialog-action {
801 | box-sizing: border-box;
802 | padding: 0px 15px 12px 15px;
803 | text-align: right;
804 | }
805 |
806 |
807 |
808 | /* 样式工具 */
809 | /* 浮动左 */
810 | .bft-flow-left {
811 | float: left !important;
812 | }
813 |
814 | .bft-flow-right {
815 | float: right !important;
816 | }
817 |
818 |
819 |
820 |
821 | `);
822 |
823 |
824 | //当开启隐藏元素时,为页面添加相关css
825 | GM_addStyle(`
826 | .bft-need_deleteEle {
827 | display: none;
828 | }
829 | `);
830 |
831 | // 当浏览器关闭时,将面板标记为关闭
832 | window.addEventListener('beforeunload', (e) => {
833 | // 只有当本页面有设置面板打开时才需要
834 | if (document.getElementById('bft-menu')) {
835 | // 添加已关闭面板的标记
836 | GM_setValue("temp_isMenuOpen", false);
837 | }
838 | console.log(e);
839 |
840 | });
841 |
842 |
843 |
844 |
845 | // 载入规则
846 | var textFilterRules = GM_getValue("textFilterRules", []);
847 | var otherFilterRules = GM_getValue("otherFilterRules", { duration: 0 });
848 | var userFilterRules = GM_getValue("userFilterRules", []);
849 | // 重载规则&重置过滤
850 | function reloadRules() {
851 | textFilterRules = GM_getValue("textFilterRules", []);
852 | otherFilterRules = GM_getValue("otherFilterRules", { duration: 0 });
853 | userFilterRules = GM_getValue("userFilterRules", []);
854 | // 重置已过滤项
855 | let target = document.querySelectorAll('.bft-textFiltered, .bft-heimu, .bft-overlay, .bft-duration-filtered, .bft-user-filtered, .bft-need_deleteEle');
856 | target.forEach(element => {
857 | element.classList.remove('bft-textFiltered', 'bft-heimu', 'bft-overlay', 'bft-duration-filtered', 'bft-user-filtered');
858 | });
859 |
860 |
861 | }
862 | // 1s执行一次过滤
863 | setInterval(findAndBlock, GM_getValue("setting", { filterInterval: 1, autoUpdate: 6, enableFastAddUserFilterRules: true }).filterInterval * 1000);
864 | // 每隔 一段时间 对远程配置文件 检测更新
865 | setInterval(autoUpdateTextRulesets, 5000);
866 | setInterval(autoUpdateUserRulesets, 5000);
867 |
868 | // 定义设置菜单
869 | const menu_bft_userFilter = GM_registerMenuCommand("🐂UID过滤设置", function () {
870 | bftSettingMenu_userFilter();
871 | });
872 | const menu_bft_settingRules = GM_registerMenuCommand("📄关键词过滤设置", function () {
873 | bftSettingMenu_textFilter();
874 | });
875 |
876 | const menu_bft_otherFilter = GM_registerMenuCommand("⏱️其他过滤设置", function () {
877 | bftSettingMenu_otherFilter();
878 | });
879 | const menu_bft_setting = GM_registerMenuCommand("🦴杂项设置", function () {
880 | bftSettingMenu_setting();
881 | });
882 | const dialog_bft_about = GM_registerMenuCommand("🔖关于", function () {
883 | bftAboutDialog();
884 | });
885 | //根据不同页面执行不同过滤
886 | function findAndBlock() {
887 | if (window.location.hostname === "search.bilibili.com") {
888 | findTextandBlockinSearch();
889 | findDurationandBlockinSearch();
890 | filterVideoofSearch();
891 | }
892 | else if (window.location.href.includes("www.bilibili.com/video/")) {
893 | findTextandBlockinVideo();
894 | findDurationandBlockinVideo();
895 | findUserandBlockinVideo();
896 | filterVideoofVideo();
897 | // 快速加入用户
898 | if (GM_getValue("setting", { filterInterval: 1, autoUpdate: 6, enableFastAddUserFilterRules: true }).enableFastAddUserFilterRules) {
899 | addFastAddUserButtonInVideo();
900 | }
901 | } else if (window.location.href.includes("www.bilibili.com/read/")) {
902 | findTextandBlockinArticle();
903 | } else if (window.location.href.includes("www.bilibili.com/v/")) {
904 | findTextandBlockinFenqu1();
905 | } else if (window.location.hostname === "www.bilibili.com" && window.location.pathname === "/") {
906 | findTextandBlockinIndex();
907 | findDurationandBlockinIndex();
908 | filterVideoofFeedinIndex();
909 | } else if (window.location.href.includes("space.bilibili.com/")) {
910 | // 快速加入用户
911 | if (GM_getValue("setting", { filterInterval: 1, autoUpdate: 6, enableFastAddUserFilterRules: true }).enableFastAddUserFilterRules) {
912 | addFastAddUserButtonInSpace();
913 | }
914 | }
915 | };
916 | //
917 | //
918 | // 为每个界面都添加菜单按钮
919 | addOpenMenuButton();
920 |
921 |
922 |
923 | //--------------------------------------------------------------------------------
924 | //对页面直接修改的函数
925 | //--------------------------------------------------------------------------------
926 | //----------------------------------------
927 | // UID过滤
928 | //----------------------------------------
929 | // 定义isUserNeedFilter函数,查询是否屏蔽该用户,参数为uid,返回一个数组 [true/false,用户所属规则集名称,用户屏蔽等级]
930 | function isUserNeedFilter(uid) {
931 | // 外层遍历 规则集
932 | for (let i = 0; i < userFilterRules.length; i++) {
933 | for (let j = 0; j < userFilterRules[i].rules.length; j++) {
934 | // 如果uid匹配,则返回[true,所属规则集,level]
935 | if (uid == userFilterRules[i].rules[j].uid && (userFilterRules[i].enable === true) && (userFilterRules[i].rules[j].level <= userFilterRules[i].level)) {
936 | let result = [true, userFilterRules[i].name, userFilterRules[i].rules[j].level];
937 | // 结束循环
938 | j = userFilterRules[i].rules.length;
939 | i = userFilterRules.length - 1;
940 | return result;
941 | }
942 | }
943 | // 满足遍历完毕,返回false
944 | if (i == userFilterRules.length - 1) {
945 | let result = [false, "sb", "sb"];
946 | return result;
947 | }
948 |
949 | }
950 | return [false, "sb", "sb"];
951 | }
952 |
953 |
954 | //--------
955 | // 针对UID过滤视频播放页下面的评论
956 | //--------
957 | // 定义 findUserandBlockinVideo()函数 主函数,从这里开始执行。将会读取视频下方的评论区。使用isUserNeedFilter()查询用户是否满足条件
958 | function findUserandBlockinVideo() {
959 | // 如果无规则,则不执行
960 | if (userFilterRules.length != 0) {
961 | // console.log("执行过滤");
962 | //对主条目评论进行操作
963 | // 获取每条评论(不包含回复),转成类数组对象,用于确定评论序号便于后续使用
964 | let mainComment = document.getElementsByClassName("root-reply-container");
965 | // console.log("[读取评论用户]", mainComment);
966 | // 有几条评论就循环几次,mainCommentId是评论序号(从0开始)
967 | for (let mainCommentId = 0; mainCommentId < mainComment.length; mainCommentId++) {
968 | // 这些对象的html属性中的data-user-id的值就是uid
969 | let mainCommentUid = mainComment[mainCommentId].querySelector('div.content-warp div.user-info div.user-name').getAttribute("data-user-id");
970 | // 检测UID是否匹配记录中的
971 | // 满足则执行替换
972 | // 查询用户
973 | if (!mainComment[mainCommentId].classList.contains('bft-user-filtered') && isUserNeedFilter(mainCommentUid)[0] == true) {
974 | // console.log("find", mainCommentUid)
975 | console.log("[BF][用户][视频页评论]发现目标", mainCommentUid, '规则集:', isUserNeedFilter(mainCommentUid)[1], mainComment[mainCommentId]);
976 | // 打上需要删除的标记
977 | mainComment[mainCommentId].classList.add('bft-need_deleteEle');
978 | //执行叠加层
979 | // overrideMainComment(mainCommentId, isUserNeedFilter(mainCommentUid)[1], isUserNeedFilter(mainCommentUid)[2], mainCommentUid, "userBlackList");
980 | mainComment[mainCommentId].querySelector('div.content-warp div.root-reply span.reply-content-container.root-reply').classList.add('bft-heimu');
981 | }
982 | // 为检测后的内容打上标记
983 | mainComment[mainCommentId].classList.add('bft-user-filtered');
984 | }
985 |
986 |
987 |
988 | // 对评论回复进行操作
989 | // 获取每条回复,转成类数组对象,用于确定评论序号便于后续使用
990 | let subReply = document.getElementsByClassName("sub-reply-item");
991 | // 有几条评论就循环几次,subReplyId是评论序号(从0开始)
992 | for (let i = 0; i < subReply.length; i++) {
993 |
994 | // 从 一堆class为sub-reply-item的类数组对象中获取对应的uid,第几个评论就对应第几个class是sub-reply-avatar的对象
995 | // 这些对象的html属性中的data-user-id的值就是uid
996 | let subReplyUid = subReply[i].querySelector('div.sub-user-info div.sub-user-name').getAttribute("data-user-id");
997 | // 检测UID是否匹配记录中的
998 |
999 | if (!subReply[i].classList.contains('bft-user-filtered') && isUserNeedFilter(subReplyUid)[0] == true) {
1000 | // console.log("find", subReplyUid)
1001 | // 打上需要删除的标记
1002 | subReply[i].classList.add('bft-need_deleteEle');
1003 | //执行替换
1004 | // overrideSubReply(subReplyId, isUserNeedFilter(subReplyUid)[1], isUserNeedFilter(subReplyUid)[2], subReplyUid, "userBlackList");
1005 | console.log("[BF][用户][视频页评论]发现目标", subReplyUid, '规则集:', isUserNeedFilter(subReplyUid)[1], subReply[i]);
1006 | subReply[i].querySelector('span.reply-content-container.sub-reply-content').classList.add('bft-heimu');
1007 |
1008 | }
1009 | // 为检测过后的内容打上标记
1010 | subReply[i].classList.add('bft-user-filtered');
1011 |
1012 | }
1013 |
1014 | }
1015 | }
1016 |
1017 |
1018 | //---------
1019 | // 针对首页的推荐做出的屏蔽
1020 | //--------
1021 | // 针对主页中 class 为 bili-video-card is-rcmd 的视频进行过滤
1022 | function filterVideoofFeedinIndex() {
1023 | // 获取 所有class 为 bili-video-card is-rcmd 的元素
1024 | let videoCard = document.getElementsByClassName("bili-video-card is-rcmd");
1025 | // console.debug("执行首页视频feedcard过滤");
1026 | // 遍历各元素
1027 | for (let l = 0; l < videoCard.length; l++) {
1028 | // 获取 可探测uid的元素
1029 | let targetElement = videoCard[l].querySelector("div.bili-video-card.is-rcmd div.bili-video-card__wrap.__scale-wrap div.bili-video-card__info.__scale-disable div.bili-video-card__info--right div.bili-video-card__info--bottom a.bili-video-card__info--owner");
1030 | let href = targetElement.getAttribute("href");
1031 | // 从目标元素的href属性值(//space.bilibili.com/1956977928)中获取uid ,并使用isUserNeedFilter判定是否屏蔽
1032 | // 使用正则匹配
1033 | let regex = /(\d+)/;
1034 | let match = href.match(regex);
1035 | // console.debug(match[0]);
1036 | if (!videoCard[l].classList.contains('bft-user-filtered') && isUserNeedFilter(match[0])[0] === true) {
1037 | // 执行屏蔽
1038 | videoCard[l].classList.add('bft-overlay');
1039 | console.log('[BF][用户][首页视频]匹配到规则:', isUserNeedFilter(match[0])[1], videoCard[l]);
1040 | }
1041 | // 为过滤过的打上标记
1042 | videoCard[l].classList.add('bft-user-filtered');
1043 | }
1044 | }
1045 | //---------
1046 | // 针对视频播放页的右侧视频推荐做出的屏蔽(不含自动联播或合集屏蔽)
1047 | //--------
1048 | function filterVideoofVideo() {
1049 | // 获取 所有class 为 bili-video-card is-rcmd 的元素
1050 | let videoCard = document.getElementsByClassName("video-page-card-small");
1051 | // console.debug("执行右侧推荐视频过滤");
1052 | // 遍历各元素
1053 | for (let l = 0; l < videoCard.length; l++) {
1054 | // 获取 可探测uid的元素
1055 | let targetElement = videoCard[l].querySelector("div.card-box div.info div.upname a");
1056 | let href = targetElement.getAttribute("href");
1057 | // 从目标元素的href属性值(//space.bilibili.com/1956977928)中获取uid ,并使用isUserNeedFilter判定是否屏蔽
1058 | // 使用正则匹配
1059 | let regex = /(\d+)/;
1060 | let match = href.match(regex);
1061 | // console.debug(match[0]);
1062 | if (!videoCard[l].classList.contains('bft-user-filtered') && isUserNeedFilter(match[0])[0] === true) {
1063 | // 执行屏蔽
1064 | videoCard[l].classList.add('bft-overlay');
1065 | console.log('[BF][用户][视频页视频推荐]匹配到规则:', isUserNeedFilter(match[0])[1], videoCard[l]);
1066 | }
1067 | // 为过滤过的打上标记
1068 | videoCard[l].classList.add('bft-user-filtered');
1069 | }
1070 | }
1071 | //---------
1072 | // 针对搜索页做出的屏蔽
1073 | //--------
1074 | function filterVideoofSearch() {
1075 | // 过滤搜索的视频
1076 | let targetEle = document.getElementsByClassName('bili-video-card');
1077 | for (let j = 0; j < targetEle.length; j++) {
1078 | // 获取UID
1079 | let href = targetEle[j].querySelector('div.bili-video-card__wrap.__scale-wrap div.bili-video-card__info.__scale-disable div.bili-video-card__info--right p.bili-video-card__info--bottom a.bili-video-card__info--owner').getAttribute('href');
1080 | // 从目标元素的href属性值(//space.bilibili.com/1956977928)中获取uid ,并使用isUserNeedFilter判定是否屏蔽
1081 | // 使用正则匹配
1082 | let regex = /(\d+)/;
1083 | let match = href.match(regex);
1084 | // 判断是否过滤过,且是否符合过滤
1085 | if (!targetEle[j].classList.contains('bft-user-filtered') && isUserNeedFilter(match[0])[0] === true) {
1086 | // 执行屏蔽
1087 | targetEle[j].classList.add('bft-overlay');
1088 | console.log('[BF][用户][搜索页视频]匹配到规则:', isUserNeedFilter(match[0])[1], targetEle[j]);
1089 | }
1090 | // 为检测过的元素添加标记
1091 | targetEle[j].classList.add('bft-user-filtered');
1092 | }
1093 | // 过滤搜索的专栏
1094 | let targetArtEle = document.getElementsByClassName('b-article-card flex_start items_stretch search-article-card');
1095 | for (let j = 0; j < targetArtEle.length; j++) {
1096 | // 获取UID
1097 | let href = targetArtEle[j].querySelector('div.article-content.pr_md div.atc-author.text_ellipsis.fs_5 a.flex_start.flex_inline.text3').getAttribute('href');
1098 | // 从目标元素的href属性值(//space.bilibili.com/1956977928)中获取uid ,并使用isUserNeedFilter判定是否屏蔽
1099 | // 使用正则匹配
1100 | let regex = /(\d+)/;
1101 | let match = href.match(regex);
1102 | // 判断是否过滤过,且是否符合过滤
1103 | if (!targetArtEle[j].classList.contains('bft-user-filtered') && isUserNeedFilter(match[0])[0] === true) {
1104 | // 执行屏蔽
1105 | targetArtEle[j].classList.add('bft-overlay');
1106 | console.log('[BF][用户][搜索页视频]匹配到规则:', isUserNeedFilter(match[0])[1], targetArtEle[j]);
1107 | }
1108 | // 为检测过的元素添加标记
1109 | targetArtEle[j].classList.add('bft-user-filtered');
1110 | }
1111 |
1112 | // 番剧、影视应该用关键词过滤
1113 | }
1114 |
1115 | // // UID过滤功能结束
1116 |
1117 |
1118 | // ------------------------------
1119 | // 关键词过滤:主要功能函数
1120 | // ------------------------------
1121 | // 根据内容寻找并覆写 视频页
1122 | function findTextandBlockinVideo() {
1123 | // 寻找所有 .reoply-comternt 元素 用于视频评论区
1124 | let targetElements = document.getElementsByClassName('reply-content-container');
1125 |
1126 |
1127 | for (let i = 0; i < targetElements.length; i++) {
1128 | // 保证检测的是没有被过滤过的
1129 | if (!targetElements[i].classList.contains('bft-textFiltered')) {
1130 |
1131 | // 标记该元素为过滤过的
1132 | targetElements[i].classList.add('bft-textFiltered');
1133 | // //获取每个元素内包含的文本与B站的表情
1134 | // 创建一个空数组,用于存储文本内容和表情符号
1135 | var content = [];
1136 |
1137 | // 遍历元素的子节点
1138 | for (var node of targetElements[i].querySelector('span').childNodes) {
1139 | // 判断节点类型
1140 | if (node.nodeType === Node.TEXT_NODE) {
1141 | // 如果是文本节点,将文本内容存入数组
1142 | content.push(node.textContent);
1143 | } else if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'IMG') {
1144 | // 如果是元素,将表情符号的alt属性值存入数组
1145 | content.push(node.alt);
1146 | }
1147 | }
1148 |
1149 | // 拼接文本内容和表情符号
1150 | let targetText = content.join('');
1151 |
1152 | // console.debug('[BF][评论文本内容调试]', targetText); // 输出提取的结果
1153 |
1154 | // 请求函数,并且排除已过滤项
1155 | if (isTextNeedBlock(targetText)[0] === true) {
1156 | // 若需要过滤,则为文本覆盖层
1157 | targetElements[i].classList.add('bft-heimu');
1158 | // 调试
1159 | console.log('[BF][内容][评论]匹配到规则:', isTextNeedBlock(targetText)[1], targetText, targetElements[i]);
1160 | }
1161 | }
1162 |
1163 | }
1164 | // 寻找所有 .title 元素 用于视频页右侧推荐的视频
1165 | let targetElementsforRight = document.getElementsByClassName('video-page-card-small');
1166 | for (let i = 0; i < targetElementsforRight.length; i++) {
1167 | //获取每个视频的标题
1168 | var targetTextEle = targetElementsforRight[i].querySelector('div.card-box div.info a p.title');
1169 | var targetText = targetTextEle.textContent;
1170 | // 请求函数,并且排除已过滤项
1171 | if (isTextNeedBlock(targetText)[0] === true && !targetElementsforRight[i].classList.contains('bft-textFiltered')) {
1172 | // 若需要过滤,则将内部文本改为
1173 | targetElementsforRight[i].classList.add('bft-overlay');
1174 | // 调试
1175 | console.log('[BF][内容][视频]匹配到规则:', isTextNeedBlock(targetText)[1], targetElementsforRight[i]);
1176 | }
1177 | // 检测过的元素添加标记
1178 | targetElementsforRight[i].classList.add('bft-textFiltered');
1179 | }
1180 | }
1181 | // 根据内容寻找并覆写 专栏页
1182 | function findTextandBlockinArticle() {
1183 | // 过滤专栏页的评论
1184 | // 将评论文本和评论表情拼接,并将所有条目拼接为一个数组
1185 | let targetComEle = Array.from(document.getElementsByClassName('text')).concat(Array.from(document.getElementsByClassName('text-con')));
1186 | for (let i = 0; i < targetComEle.length; i++) {
1187 | // //获取每个元素内包含的文本与B站的表情
1188 | // 创建一个空数组,用于存储文本内容和表情符号
1189 | let content = [];
1190 |
1191 | // 遍历元素的子节点
1192 | for (let node of targetComEle[i].childNodes) {
1193 | // 判断节点类型
1194 | if (node.nodeType === Node.TEXT_NODE) {
1195 | // 如果是文本节点,将文本内容存入数组
1196 | content.push(node.textContent);
1197 | } else if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'IMG') {
1198 | // 如果是
元素,将表情符号的alt属性值存入数组
1199 | content.push(node.alt);
1200 | }
1201 | }
1202 |
1203 | // 拼接文本内容和表情符号
1204 | let targetComText = content.join('');
1205 | //判断是否需要过滤
1206 | if (isTextNeedBlock(targetComText)[0] && !targetComEle[i].classList.contains('bft-textFiltered')) {
1207 | // 若需要过滤
1208 | targetComEle[i].classList.add('bft-overlay');
1209 | // 调试
1210 | console.log('[BF][内容][专栏页评论]匹配到规则:', isTextNeedBlock(targetComText)[1], targetComEle[i]);
1211 | }
1212 | // 添加标记
1213 | targetComEle[i].classList.add('bft-textFiltered');
1214 | }
1215 | }
1216 | // 哼哼,啊啊啊啊,我就是萝莉控
1217 |
1218 | // 根据内容寻找并覆写 搜索页
1219 | function findTextandBlockinSearch() {
1220 | // 过滤搜索的视频
1221 | let targetEle = document.getElementsByClassName('bili-video-card');
1222 | for (let j = 0; j < targetEle.length; j++) {
1223 | let targetText = targetEle[j].querySelector('div.bili-video-card__wrap.__scale-wrap div.bili-video-card__info.__scale-disable div.bili-video-card__info--right a h3.bili-video-card__info--tit').getAttribute('title');
1224 | if (isTextNeedBlock(targetText)[0] && !targetEle[j].classList.contains('bft-textFiltered')) {
1225 | targetEle[j].classList.add('bft-overlay');
1226 | console.log('[BF][内容][搜索页视频]匹配到规则:', isTextNeedBlock(targetText)[1], targetEle[j]);
1227 | }
1228 | // 为检测过的元素添加标记
1229 | targetEle[j].classList.add('bft-textFiltered');
1230 | }
1231 | // 过滤搜索的专栏
1232 | let targetArtEle = document.getElementsByClassName('b-article-card flex_start items_stretch search-article-card');
1233 | for (let j = 0; j < targetArtEle.length; j++) {
1234 | let targetArtText = targetArtEle[j].querySelector('div.article-content.pr_md h2.b_text.i_card_title.mt_0 a.text1').getAttribute('title');
1235 | if (isTextNeedBlock(targetArtText)[0] && !targetArtEle[j].classList.contains('bft-textFiltered')) {
1236 | targetArtEle[j].classList.add('bft-overlay');
1237 | console.log('[BF][内容][搜索页专栏]匹配到规则:', isTextNeedBlock(targetArtText)[1], targetArtEle[j]);
1238 | }
1239 | // 为检测过的元素添加标记
1240 | targetArtEle[j].classList.add('bft-textFiltered');
1241 | }
1242 | // 过滤影视与番剧
1243 | let targetMedEle = document.getElementsByClassName('media-card');
1244 | for (let j = 0; j < targetMedEle.length; j++) {
1245 | let targetMedText = targetMedEle[j].querySelector('div.media-card-content div.media-card-content-head div.media-card-content-head-title a.text_ellipsis').getAttribute('title');
1246 | if (isTextNeedBlock(targetMedText)[0] && !targetMedEle[j].classList.contains('bft-textFiltered')) {
1247 | targetMedEle[j].classList.add('bft-overlay');
1248 | console.log('[BF][内容][搜索页影视与番剧]匹配到规则:', isTextNeedBlock(targetMedText)[1], targetMedEle[j]);
1249 | }
1250 | // 为检测过的元素添加标记
1251 | targetMedEle[j].classList.add('bft-textFiltered');
1252 | }
1253 | // 过滤直播间
1254 | let targetLivEle = document.getElementsByClassName('bili-live-card');
1255 | for (let j = 0; j < targetLivEle.length; j++) {
1256 | let targetLivText = targetLivEle[j].querySelectorAll('div.bili-live-card__wrap.__scale-wrap div.bili-live-card__info.__scale-disable div.bili-live-card__info--text h3.bili-live-card__info--tit a span')[1].innerHTML;
1257 | if (isTextNeedBlock(targetLivText)[0] && !targetLivEle[j].classList.contains('bft-textFiltered')) {
1258 | targetLivEle[j].classList.add('bft-overlay');
1259 | console.log('[BF][内容][搜索页直播]匹配到规则:', isTextNeedBlock(targetLivText)[1], targetLivEle[j]);
1260 | }
1261 | // 为检测过的元素添加标记
1262 | targetLivEle[j].classList.add('bft-textFiltered');
1263 | }
1264 | }
1265 |
1266 | // 根据内容寻找覆写 各分区主页(除了: 纪录片 电视剧 电影 综艺 国创 番剧)
1267 | function findTextandBlockinFenqu1() {
1268 | // 过滤视频
1269 | let targetEle = document.getElementsByClassName('bili-video-card');
1270 | for (let j = 0; j < targetEle.length; j++) {
1271 | let targetText = targetEle[j].querySelector('div.bili-video-card__wrap.__scale-wrap div.bili-video-card__info.__scale-disable div.bili-video-card__info--right h3.bili-video-card__info--tit').getAttribute('title');
1272 | if (isTextNeedBlock(targetText)[0] && !targetEle[j].classList.contains('bft-textFiltered')) {
1273 | targetEle[j].classList.add('bft-overlay');
1274 | console.log('[BF][内容][各分区页视频]匹配到规则:', isTextNeedBlock(targetText)[1], targetEle[j]);
1275 | }
1276 | // 为检测过的元素添加标记
1277 | targetEle[j].classList.add('bft-textFiltered');
1278 | }
1279 | }
1280 |
1281 | // 根据内容寻找覆写 首页
1282 | function findTextandBlockinIndex() {
1283 | // 过滤视频
1284 | let targetEle = document.getElementsByClassName('bili-video-card is-rcmd');
1285 | for (let j = 0; j < targetEle.length; j++) {
1286 | let targetText = targetEle[j].querySelector('div.bili-video-card__wrap.__scale-wrap div.bili-video-card__info.__scale-disable div.bili-video-card__info--right h3.bili-video-card__info--tit').getAttribute('title');
1287 | if (isTextNeedBlock(targetText)[0] && !targetEle[j].classList.contains('bft-textFiltered')) {
1288 | targetEle[j].classList.add('bft-overlay');
1289 | console.log('[BF][内容][首页视频]匹配到规则:', isTextNeedBlock(targetText)[1], targetEle[j]);
1290 | }
1291 | // 为检测过的元素添加标记
1292 | targetEle[j].classList.add('bft-textFiltered');
1293 | }
1294 | }
1295 |
1296 | // 根据内容判断是否需要屏蔽, 返回 [true,匹配到的正则表达式]
1297 | function isTextNeedBlock(text) {
1298 | for (let b = 0; b < textFilterRules.length; b++) {
1299 | // 遍历规则集
1300 | if (textFilterRules[b].enable === true) {
1301 | // 规则集为启用,再进行下一步
1302 | // 获取正则表达式
1303 | // 将字符串形式的正则表达式转换为正则表达式对象
1304 | let regexArray = textFilterRules[b].rules.map(function (regexString) {
1305 | return new RegExp(regexString);
1306 | });
1307 | for (let i = 0; i < regexArray.length; i++) {
1308 | // 遍历正则表达式,若匹配到则立刻break并返回
1309 | if (regexArray[i].test(text)) {
1310 | return [true, regexArray[i]];
1311 | } else if (i === regexArray.length - 1 && b === textFilterRules.length - 1) {
1312 | // 若遍历完表达式仍没匹配上,则返回 [false,null]
1313 | return [false, null];
1314 | }
1315 | }
1316 | }
1317 | }
1318 | return [false, null];
1319 | }
1320 |
1321 | // -----------------------------------
1322 | // 其他功能过滤:主要功能函数
1323 | // -----------------------------
1324 | // --
1325 | // 过滤指定时长视频
1326 | // --
1327 | // 过滤首页指定时长视频
1328 | function findDurationandBlockinIndex() {
1329 | let targetEle = document.getElementsByClassName('bili-video-card is-rcmd');
1330 | for (let i = 0; i < targetEle.length; i++) {
1331 | // 页面可能没完全加载,使用try来避免无法获取时长
1332 | try {
1333 | // 获取视频时长
1334 | let timeString = targetEle[i].querySelector('div.bili-video-card__wrap.__scale-wrap a div.bili-video-card__image.__scale-player-wrap div.bili-video-card__mask div.bili-video-card__stats span.bili-video-card__stats__duration').innerHTML;
1335 | // 转为秒
1336 | let timeArray = timeString.split(":");
1337 | let hours = 0;
1338 | let minutes = 0;
1339 | let seconds = 0;
1340 |
1341 | if (timeArray.length === 3) {
1342 | hours = parseInt(timeArray[0]);
1343 | minutes = parseInt(timeArray[1]);
1344 | seconds = parseInt(timeArray[2]);
1345 | } else if (timeArray.length === 2) {
1346 | minutes = parseInt(timeArray[0]);
1347 | seconds = parseInt(timeArray[1]);
1348 | }
1349 |
1350 | let totalSeconds = hours * 3600 + minutes * 60 + seconds;
1351 | // 判断
1352 | if (totalSeconds <= otherFilterRules.duration && !targetEle[i].classList.contains('bft-duration-filtered')) {
1353 | targetEle[i].classList.add('bft-overlay');
1354 | console.log('[BF][时长][首页视频]小于指定时间:', totalSeconds, targetEle[i]);
1355 |
1356 | }
1357 | // 为过滤过的打上标记
1358 | targetEle[i].classList.add('bft-duration-filtered');
1359 | } catch (error) {
1360 |
1361 | }
1362 |
1363 |
1364 | }
1365 | }
1366 | // 根据时长过滤视频页视频推荐
1367 | function findDurationandBlockinVideo() {
1368 | let targetEle = document.getElementsByClassName('video-page-card-small');
1369 | for (let i = 0; i < targetEle.length; i++) {
1370 | // 获取视频时长
1371 |
1372 | let timeString = targetEle[i].querySelector('div.card-box div.pic-box div.pic span.duration').innerHTML;
1373 | // 转为秒
1374 | let timeArray = timeString.split(":");
1375 | let hours = 0;
1376 | let minutes = 0;
1377 | let seconds = 0;
1378 |
1379 | if (timeArray.length === 3) {
1380 | hours = parseInt(timeArray[0]);
1381 | minutes = parseInt(timeArray[1]);
1382 | seconds = parseInt(timeArray[2]);
1383 | } else if (timeArray.length === 2) {
1384 | minutes = parseInt(timeArray[0]);
1385 | seconds = parseInt(timeArray[1]);
1386 | }
1387 |
1388 | let totalSeconds = hours * 3600 + minutes * 60 + seconds;
1389 | // 判断
1390 |
1391 | if (totalSeconds <= otherFilterRules.duration && !targetEle[i].classList.contains('bft-duration-filtered')) {
1392 | targetEle[i].classList.add('bft-overlay');
1393 | console.log('[BF][时长][视频页视频推荐]小于指定时间:', totalSeconds, targetEle[i]);
1394 |
1395 | }
1396 | // 为过滤过的打上标记
1397 | targetEle[i].classList.add('bft-duration-filtered');
1398 | }
1399 | }
1400 | // 根据时长过滤搜索页视频
1401 | function findDurationandBlockinSearch() {
1402 | let targetEle = document.getElementsByClassName('bili-video-card');
1403 | for (let i = 0; i < targetEle.length; i++) {
1404 | // 获取视频时长
1405 |
1406 | let timeString = targetEle[i].querySelector('div.bili-video-card__wrap.__scale-wrap a div.bili-video-card__image.__scale-player-wrap div.bili-video-card__mask div.bili-video-card__stats span.bili-video-card__stats__duration').innerHTML;
1407 | // 转为秒
1408 | let timeArray = timeString.split(":");
1409 | let hours = 0;
1410 | let minutes = 0;
1411 | let seconds = 0;
1412 |
1413 | if (timeArray.length === 3) {
1414 | hours = parseInt(timeArray[0]);
1415 | minutes = parseInt(timeArray[1]);
1416 | seconds = parseInt(timeArray[2]);
1417 | } else if (timeArray.length === 2) {
1418 | minutes = parseInt(timeArray[0]);
1419 | seconds = parseInt(timeArray[1]);
1420 | }
1421 |
1422 | let totalSeconds = hours * 3600 + minutes * 60 + seconds;
1423 | // 判断
1424 |
1425 | if (totalSeconds <= otherFilterRules.duration && !targetEle[i].classList.contains('bft-duration-filtered')) {
1426 | targetEle[i].classList.add('bft-overlay');
1427 | console.log('[BF][时长][搜索页视频]小于指定时间:', totalSeconds, targetEle[i]);
1428 |
1429 | }
1430 | // 为过滤过的打上标记
1431 | targetEle[i].classList.add('bft-duration-filtered');
1432 | }
1433 | }
1434 | // ------------------------------
1435 | // 为合适处添加快速添加用户按钮
1436 | // ------------------------------
1437 | // 在视频播放页添加按钮
1438 | function addFastAddUserButtonInVideo() {
1439 | // 针对root主评论操作
1440 | let rootReply = document.getElementsByClassName("content-warp");
1441 | for (let i = 0; i < rootReply.length; i++) {
1442 |
1443 | // 获取该层的用户ID
1444 | let rootReplyUidEle = rootReply[i].querySelector("div.user-info div.user-name");
1445 | let rootReplyUid = rootReplyUidEle.getAttribute("data-user-id");
1446 |
1447 | // 为操作菜单增加一个快捷按钮
1448 | // 检测是否存在这个快捷按钮,若不存在,则添加
1449 | let childElement = rootReply[i].querySelector("div.user-info button.bfx-fastadd");
1450 |
1451 | if (childElement == null) {
1452 |
1453 | let rootReplyFastAddEle = document.createElement('button');
1454 | rootReplyFastAddEle.innerText = '♻️';
1455 | rootReplyFastAddEle.classList.add('bfx-fastadd'); // 添加class属性
1456 | rootReplyFastAddEle.setAttribute('style', 'border-radius: 10px;background-color: #ffffff;border: none;height: 15px;width: 15px;');
1457 |
1458 | let rootReplyFastAddEleTarge = rootReply[i].querySelector("div.user-info");
1459 |
1460 |
1461 | rootReplyFastAddEle.addEventListener('click', function () {
1462 | // console.debug('按钮被点击了,评论序号为', i, "用户UID", rootReplyUid);
1463 | // 调函数,并传递评论序号
1464 | fastAddUserFilterRules(rootReplyUid);
1465 | });
1466 | // 加入按钮
1467 | rootReplyFastAddEleTarge.appendChild(rootReplyFastAddEle);
1468 | }
1469 |
1470 | }
1471 |
1472 | // 针对评论的回复的操作
1473 | let subReply = document.getElementsByClassName("sub-reply-item");
1474 | for (let i = 0; i < subReply.length; i++) {
1475 |
1476 | // 获取该层的用户ID
1477 | let subReplyUidEle = subReply[i].querySelector("div.sub-user-info div.sub-reply-avatar");
1478 | let subReplyUid = subReplyUidEle.getAttribute("data-user-id");
1479 |
1480 | // 为操作菜单增加一个快捷按钮
1481 | // 检测是否存在这个快捷按钮,若不存在,则添加
1482 | let childElement = subReply[i].querySelector("button.bfx-fastadd");
1483 |
1484 | if (childElement == null) {
1485 |
1486 | let subReplyFastAddEle = document.createElement('button');
1487 | subReplyFastAddEle.innerText = '♻️';
1488 | subReplyFastAddEle.classList.add('bfx-fastadd'); // 添加class属性
1489 | // 添加style
1490 | subReplyFastAddEle.setAttribute('style', 'border-radius: 10px;background-color: #ffffff;border: none;height: 15px;width: 15px;');
1491 |
1492 |
1493 |
1494 | subReplyFastAddEle.addEventListener('click', function () {
1495 | // console.debug('按钮被点击了,评论序号为', i, "用户UID", subReplyUid);
1496 | // 调用函数,并传递评论序号
1497 | fastAddUserFilterRules(subReplyUid);
1498 | });
1499 | // 加入按钮
1500 | subReply[i].appendChild(subReplyFastAddEle);
1501 | }
1502 |
1503 | }
1504 | }
1505 | // 在个人空间添加按钮
1506 | function addFastAddUserButtonInSpace() {
1507 | let childElement = document.querySelector('html body div#app.visitor div.h div.wrapper div.h-inner div.h-user div.h-info.clearfix div.h-basic div button.bfx-fastadd');
1508 | if (childElement === null) {
1509 |
1510 | let rootReplyFastAddEle = document.createElement('button');
1511 | rootReplyFastAddEle.innerText = '♻️';
1512 | rootReplyFastAddEle.classList.add('bfx-fastadd'); // 添加class属性
1513 | rootReplyFastAddEle.setAttribute('style', 'font-size: small');
1514 | let rootReplyFastAddEleTarge = document.querySelector("html body div#app.visitor div.h div.wrapper div.h-inner div.h-user div.h-info.clearfix div.h-basic div");
1515 | let rootReplyUid = window.location.pathname.split('/')[1];
1516 |
1517 | rootReplyFastAddEle.addEventListener('click', function () {
1518 | // console.debug('按钮被点击了,评论序号为', i, "用户UID", rootReplyUid);
1519 | // 调函数,并传递评论序号
1520 | fastAddUserFilterRules(rootReplyUid);
1521 | });
1522 | // 加入按钮
1523 | rootReplyFastAddEleTarge.appendChild(rootReplyFastAddEle);
1524 | }
1525 | }
1526 | // 我还是找不到对象
1527 |
1528 | // --------------------------------------------------------------------------
1529 | // 配置与设定弹窗函数
1530 | // --------------------------------------------------------------------------
1531 | // UID过滤设置
1532 | function bftSettingMenu_userFilter() {
1533 | // 确保没有其他面板被打开
1534 | if (document.getElementById('bft-menu') === null && !GM_getValue("temp_isMenuOpen", false)) {
1535 | // 添加已打开面板的标记
1536 | GM_setValue("temp_isMenuOpen", true);
1537 | //添加HTML
1538 | let dialogHtml = `
1539 |