├── README.md ├── bft-mirror.user.js └── bft.user.js /README.md: -------------------------------------------------------------------------------- 1 | # BiliFilter / 必滤 2 | 一个可以过滤BiliBili上不顺眼的东西的`油猴脚本`,支持按用户/标题/甚至是视频时长过滤内容。 3 | 4 | ![BF](https://github.com/ChizhaoEngine/BiliFilter/assets/114285377/41b1d937-bfaf-426f-b138-d3ea00b54648) 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 |
1540 |
1541 | UID过滤器 共计{{this.userFilterRulesRaw.length}}组规则集 1542 | 1548 | 1549 | 1555 |
1556 |
1557 | 1558 |
1559 |
1560 | 1561 | 1563 | 1564 | 1566 | 1567 | 1569 | 1571 | 1572 |
1573 |
1574 |
{{ ruleSet.name }}{{ ruleSet.describe }}
1575 |
共{{ruleSet.rules.length }}条 | {{ ruleSet.lastUpdate | formatDate 1576 | }}
1577 |
1578 |
1579 | 1580 | 1587 | 1594 | 1601 | 1607 | 1613 |
1614 |
1615 |
1616 | 1618 | 1619 |
1620 |
1621 |
1622 | 1624 | 1625 |
1626 |
1627 |
1628 | 1630 | 1631 |
1632 |
1633 |
1634 | 1636 | 1637 |
1638 |
1639 |
1640 | 1643 | 1646 | 1649 | 1650 | {{ currentPage + 1 }} / {{ totalPages }} 1651 | 1653 |
1654 |
1655 | 1656 | 1657 | 1658 |
1660 |

#{{ currentPage * pageSize + ruleIndex +1}}

1661 | 1668 |

{{rule.lastUpdate | formatDate}}

1669 |
1670 | 1672 | 1673 |
1674 |
1675 |
1676 | 1678 | 1679 |
1680 |
1681 |
1682 |
1683 | 1684 |
1685 | 1686 |
1687 |
1688 |
1689 | 1690 | 1691 | 1692 |
1693 |
1694 | `; 1695 | let dialogElement = document.createElement('div'); 1696 | dialogElement.id = 'bft-menu'; 1697 | dialogElement.innerHTML = dialogHtml; 1698 | document.body.appendChild(dialogElement); 1699 | let userFilterRulesRaw = GM_getValue("userFilterRules", []); 1700 | 1701 | new Vue({ 1702 | el: '#bft-editUserRulesMenu', 1703 | data: { 1704 | userFilterRulesRaw, 1705 | activeRuleSetIndex: -1, // 用于跟踪当前处于编辑状态的规则集的索引 1706 | pageSize: 100, // 展示规则条目时的每页规则数 1707 | currentPage: 0 // 当前规则条目当前页数 1708 | }, 1709 | computed: { 1710 | // 计算展示规则条目时所需要的页数 1711 | totalPages() { 1712 | if (this.userFilterRulesRaw && this.userFilterRulesRaw[this.activeRuleSetIndex]) { 1713 | return Math.ceil(this.userFilterRulesRaw[this.activeRuleSetIndex].rules.length / this.pageSize); 1714 | } 1715 | return 0; 1716 | }, 1717 | // 计算当前需要展示的条目 1718 | paginatedRules() { 1719 | if (this.userFilterRulesRaw && this.userFilterRulesRaw[this.activeRuleSetIndex]) { 1720 | const startIndex = this.currentPage * this.pageSize; 1721 | const endIndex = startIndex + this.pageSize; 1722 | return this.userFilterRulesRaw[this.activeRuleSetIndex].rules.slice(startIndex, endIndex); 1723 | } 1724 | return []; 1725 | } 1726 | }, 1727 | methods: { 1728 | // 修改 1729 | editRuleSet(index) { 1730 | this.activeRuleSetIndex = index; 1731 | this.currentPage = 0; // 展开新规则集时重置为第一页 1732 | }, 1733 | deleteRuleSet(index) { 1734 | // 删除规则集的逻辑 1735 | this.userFilterRulesRaw.splice(index, 1); 1736 | this.activeRuleSetIndex = -1; // 关闭二级悬浮窗 1737 | }, 1738 | convertToLocal(index) { 1739 | // 远程规则集转为本地规则集的逻辑 1740 | this.userFilterRulesRaw[index].link = 'local'; 1741 | }, 1742 | deleteRule(ruleSetIndex, ruleIndex) { 1743 | // 删除规则的逻辑 1744 | // this.userFilterRulesRaw[ruleSetIndex].rules.splice(ruleIndex, 1); 1745 | // 计算当前展示的规则在实际规则数组中的索引 1746 | const actualIndex = this.currentPage * this.pageSize + ruleIndex; 1747 | // 删除实际索引对应的规则 1748 | this.userFilterRulesRaw[ruleSetIndex].rules.splice(actualIndex, 1); 1749 | // 如果这一页没有元素了就更新页码 1750 | if (this.currentPage + 1 > this.totalPages) { 1751 | this.currentPage--; 1752 | } 1753 | 1754 | }, 1755 | addRule(index) { 1756 | // 添加规则的逻辑 1757 | this.userFilterRulesRaw[index].rules.push({ uid: 0, level: 3, lastUpdate: parseInt(Date.now() / 1000) }); 1758 | // 跳转至所在页 1759 | this.currentPage = this.totalPages - 1; 1760 | // 焦点指向新建元素的文本框 1761 | setTimeout(() => { 1762 | document.querySelector('.bft-ruleset-rulelist-item:last-child input').focus(); 1763 | }, 10); 1764 | }, 1765 | closeEditWindow() { 1766 | this.activeRuleSetIndex = -1; 1767 | }, 1768 | saveRuleSets() { 1769 | // console.debug(this.userFilterRulesRaw); 1770 | // 保存规则集的逻辑 1771 | // 将规则写入配置中 1772 | GM_setValue("userFilterRules", this.userFilterRulesRaw); 1773 | // 重载配置 1774 | reloadRules(); 1775 | // 删除设定面板HTML 1776 | document.getElementById('bft-menu').remove(); 1777 | // 添加已关闭面板的标记 1778 | GM_setValue("temp_isMenuOpen", false); 1779 | }, 1780 | closeWindow() { 1781 | // 关闭悬浮窗的逻辑 1782 | // 删除设定面板HTML 1783 | document.getElementById('bft-menu').remove(); 1784 | // 添加已关闭面板的标记 1785 | GM_setValue("temp_isMenuOpen", false); 1786 | }, 1787 | createRuleSet() { 1788 | // 创建新规则集的逻辑 1789 | this.userFilterRulesRaw.push({ 1790 | "name": "例子", 1791 | "describe": "一个栗子", 1792 | "enable": true, 1793 | "link": "local", 1794 | "lastUpdate": parseInt(Date.now() / 1000), 1795 | "level": 3, 1796 | "rules": [ 1797 | { 1798 | "uid": 0, 1799 | "level": 3, 1800 | "lastUpdate": parseInt(Date.now() / 1000) 1801 | } 1802 | ] 1803 | }); 1804 | }, 1805 | createRemoteRuleSet() { 1806 | // 创建新规则集的逻辑 1807 | this.userFilterRulesRaw.push({ 1808 | "name": "例子", 1809 | "describe": "一个栗子", 1810 | "enable": true, 1811 | "link": "", 1812 | "lastUpdate": parseInt(Date.now() / 1000), 1813 | "level": 3, 1814 | "rules": [ 1815 | { 1816 | "uid": 0, 1817 | "level": 3, 1818 | "lastUpdate": parseInt(Date.now() / 1000) 1819 | } 1820 | ] 1821 | }); 1822 | }, 1823 | updateRulesetTime(rulesetIndex) { 1824 | this.userFilterRulesRaw[rulesetIndex].lastUpdate = parseInt(Date.now() / 1000); 1825 | }, 1826 | updateRuleTime(rulesetIndex, index) { 1827 | this.userFilterRulesRaw[rulesetIndex].rules[index].lastUpdate = parseInt(Date.now() / 1000); 1828 | this.userFilterRulesRaw[rulesetIndex].lastUpdate = parseInt(Date.now() / 1000); 1829 | }, 1830 | outputRuleSet(index) { 1831 | // 导出规则集为json 1832 | let outPut = JSON.stringify(this.userFilterRulesRaw[index].rules); 1833 | // var jsonObj = JSON.parse(jsonStr); //转为对象 1834 | // 复制到粘贴板 1835 | GM.setClipboard(outPut); 1836 | // 同时展示一个文本框 1837 | interactiveDialog('output', '导出', '受浏览器限制,可能需要手动复制', 'text', outPut); 1838 | //提示 复制成功 1839 | console.info('[BF][配置]规则已经导入剪切板'); 1840 | showSnackbar('已导入剪切板', 'info', 5, '确认'); 1841 | }, 1842 | updateRuleSet(index) { 1843 | // 手动更新规则 1844 | this.frechRules(this.userFilterRulesRaw[index].link, index); 1845 | }, 1846 | async inputRuleSet(index) { 1847 | // 导入规则 1848 | // 获取内容 1849 | try { 1850 | var inputJson = await interactiveDialog('input', '输入Json以导入规则', '[{"uid":114514,"level":5,"lastUpdate":1680699306}]', 'text'); 1851 | // 待获取后删除对话框html 1852 | document.getElementById('bft-dialog').remove(); 1853 | if (inputJson != null && inputJson != "") { 1854 | let arrayInput = JSON.parse(inputJson); //转为对象 1855 | // console.log(arrayInput); 1856 | if (arrayInput.length != 0) { 1857 | // 将规则集的更新时间设为现在时间 1858 | this.userFilterRulesRaw[index].lastUpdate = Math.floor(Date.now() / 1000); 1859 | } 1860 | let errorMsg = []; 1861 | for (let m = 0; m < arrayInput.length; m++) { 1862 | // 如果原规则集中存在该用户则不导入 1863 | let isDup = false; 1864 | for (let i = 0; i < this.userFilterRulesRaw[index].rules.length; i++) { 1865 | if (arrayInput[m].uid == this.userFilterRulesRaw[index].rules[i].uid) { 1866 | // 一旦重复,isDup设为true,同时结束当前循环,跳过当前用户 1867 | isDup = true; 1868 | console.error("[BF][配置]导入规则时发现重复用户:" + this.userFilterRulesRaw[index].rules[i].uid + ",位于原规则的第" + (i + 1)); 1869 | errorMsg[errorMsg.length] = this.userFilterRulesRaw[index].rules[i].uid + '(#' + (i + 1) + ')'; 1870 | break; 1871 | } 1872 | } 1873 | if (isDup == false) { 1874 | // 塞入当前时间戳 1875 | arrayInput.lastUpdate = Math.floor(Date.now() / 1000); 1876 | // console.debug(arrayInput[m]); 1877 | // console.debug(this.userFilterRules[index].rules); 1878 | // 将新用户塞入规则 1879 | this.userFilterRulesRaw[index].rules.push(arrayInput[m]); 1880 | } 1881 | } 1882 | showSnackbar('已导入', 'info', 5, '关闭'); 1883 | // 在JavaScript中,对象之间的比较是基于引用的,而不是基于值的。所以,即使两个数组有相同的内容,它们也被视为不同的对象,它们的引用不相同。 1884 | // 因此,errorMsg !== [] 的比较结果始终为 true,即使 errorMsg 实际上是一个空数组 []。因为 errorMsg 和 [] 是两个不同的对象,它们的引用不同,所以条件始终为真。 1885 | if (errorMsg.length !== 0) { 1886 | showSnackbar(`检测到以下已存在用户:${errorMsg},这些用户未被导入`, 'warning', 3000, '关闭'); 1887 | } 1888 | } 1889 | } catch (error) { 1890 | if (error !== 'user cancel this operation') { 1891 | showSnackbar(`${error}`, 'error', 5, '关闭'); 1892 | } 1893 | } 1894 | 1895 | }, 1896 | frechRules(url, index) { 1897 | // 获取远程规则 1898 | GM_xmlhttpRequest({ 1899 | method: "GET", 1900 | url: url, 1901 | responseType: "json", // Expect a json response type 1902 | onload: function (response) { 1903 | // Check if the status code is 200 (OK) 1904 | if (response.status === 200 && response.response != undefined) { 1905 | // Get the response body as a json object 1906 | let json = response.response; 1907 | 1908 | // 转换 1909 | // let array = JSON.parse(json); 1910 | 1911 | // Add the array to the obj[prop] property 1912 | userFilterRulesRaw[index].rules = json; 1913 | console.log('[BF][配置]远程配置获取成功。'); 1914 | showSnackbar('远程配置获取成功', 'info', 5, '关闭'); 1915 | // 更新 规则中的用户的更新日期 1916 | userFilterRulesRaw[index].lastUpdate = Math.floor(Date.now() / 1000); 1917 | } else { 1918 | // Handle other status codes here, such as logging an error message 1919 | console.error("[BF][配置]远程配置格式异常,请检查链接是否有效。#" + response.statusText); 1920 | showSnackbar('远程配置获取失败,请检查配置文件格式或链接是否有效', 'error', 10, '关闭'); 1921 | } 1922 | }, 1923 | onerror: function (error) { 1924 | // Handle errors here, such as logging an error message 1925 | console.error("[BF][配置]无法获取远程配置。#" + error.message); 1926 | showSnackbar('远程配置获取失败' + error.message, 'error', 5, '关闭'); 1927 | 1928 | } 1929 | }); 1930 | }, 1931 | checkDuplicate(index, userIndex) { 1932 | // 检查是否和本规则集中的用户重复了 1933 | for (let f = 0; f < this.userFilterRulesRaw[index].rules.length; f++) { 1934 | if (this.userFilterRulesRaw[index].rules[userIndex].uid == this.userFilterRulesRaw[index].rules[f].uid && userIndex != f) { 1935 | console.error(`[BF][配置]该用户已存在(#${f + 1})`); 1936 | showSnackbar(`该用户已存在于该规则集中,(#${f + 1})`, 'error', 3000, '关闭'); 1937 | } 1938 | } 1939 | }, 1940 | outputBlacklistInBili() { 1941 | // 导出B站站内黑名单 1942 | let blacklist = []; 1943 | console.info('[BF][配置]开始请求,请等待大约5秒'); 1944 | showSnackbar('开始请求,请稍后,请不要执行其他操作', 'info', 5, '关闭'); 1945 | // 从API请求黑名单 1946 | let page = 1; 1947 | queryBlackList(); 1948 | function queryBlackList() { 1949 | GM_xmlhttpRequest({ 1950 | method: "GET", 1951 | url: "https://api.bilibili.com/x/relation/blacks?pn=" + page, 1952 | headers: { 1953 | "ps": "50" 1954 | }, 1955 | onload: function (response) { 1956 | //json转为数组 1957 | let data = JSON.parse(response.responseText); 1958 | 1959 | // console.debug("读取到的个数:", data.data.list.length); 1960 | if (data.code === 0) { 1961 | //请求成功 1962 | //将数据转为BF可使用的格式 1963 | // 遍历获取到的json,然后转为数组,放入blacklist 1964 | for (let p = 0; p < data.data.list.length; p++) { 1965 | blacklist[blacklist.length] = { "uid": data.data.list[p].mid, "level": 3, "lastUpdate": data.data.list[p].mtime, }; 1966 | }; 1967 | // 当随着page增加到获取不到黑名单为止 1968 | if (data.data.list.length != 0) { 1969 | //给page自增 1970 | page++; 1971 | // 重新调用这个函数 1972 | queryBlackList(); 1973 | } else { 1974 | //获取不到黑名单时,执行输出函数 1975 | outputBlackList(); 1976 | }; 1977 | 1978 | 1979 | } else if (data.code === -101) { 1980 | // 账号未登录 1981 | console.error("[BF][配置]请求失败,账号未登录。Error: "); 1982 | showSnackbar('请求失败,账号未登录。', 'error', 500, '关闭'); 1983 | 1984 | page = 114; 1985 | } else if (data.code === -404) { 1986 | page = 114; 1987 | console.error("[BF][配置]请求失败,无法从API获取信息。Error: "); 1988 | showSnackbar('请求失败,API错误。', 'error', 5, '关闭'); 1989 | } 1990 | }, 1991 | onerror: function (error) { 1992 | // Handle errors here, such as logging an error message 1993 | console.error("Error:"); 1994 | showSnackbar('请求失败。', 'error', 5, '关闭'); 1995 | } 1996 | 1997 | }); 1998 | }; 1999 | // 输出黑名单的函数 2000 | function outputBlackList() { 2001 | // 导出为json 2002 | let outPut = JSON.stringify(blacklist); 2003 | // var jsonObj = JSON.parse(jsonStr); //转为对象 2004 | // console.debug(outPut); 2005 | // 复制到粘贴板 2006 | GM.setClipboard(outPut); 2007 | // 同时展示一个文本框 2008 | interactiveDialog('output', '导出', '受浏览器限制,可能需要手动复制', 'text', outPut); 2009 | //提示 复制成功 2010 | console.info('[BF][配置]请求成功。黑名单已粘贴到剪切板。'); 2011 | showSnackbar('获取成功,已复制入剪切板', 'info', 5, '关闭'); 2012 | page == 100; 2013 | } 2014 | }, 2015 | // 翻页 下一页 2016 | nextPage() { 2017 | if (this.currentPage < this.totalPages - 1) { 2018 | this.currentPage++; 2019 | } 2020 | }, 2021 | // 翻页 上一页 2022 | prevPage() { 2023 | if (this.currentPage > 0) { 2024 | this.currentPage--; 2025 | } 2026 | }, 2027 | } 2028 | }); 2029 | } else if (GM_getValue("temp_isMenuOpen", false)) { 2030 | showSnackbar('已存在已经打开的设置面板,请先关闭', 'error', 5, '强行关闭', function () { 2031 | GM_setValue("temp_isMenuOpen", false); 2032 | }); 2033 | } 2034 | 2035 | } 2036 | // 关键词过滤设定 2037 | function bftSettingMenu_textFilter() { 2038 | if (document.getElementById('bft-menu') === null && !GM_getValue("temp_isMenuOpen", false)) { 2039 | // 添加已打开面板的标记 2040 | GM_setValue("temp_isMenuOpen", true); 2041 | let dialogHtml = ` 2042 |
2043 |
2044 | 关键词过滤器 共计{{this.textFilterRulesRaw.length}}组规则集 2045 | 2051 |
2052 |
2053 | 2054 |
2055 |
2056 | 2057 | 2059 | 2060 | 2062 | 2063 | 2065 | 2067 | 2068 |
2069 |
2070 |
{{ item.name }}{{ item.describe }}
2071 |
共{{ item.rules.length }}条 | {{ item.lastUpdate | 2072 | formatDate 2073 | }}
2074 |
2075 |
2076 | 2077 | 2084 | 2091 | 2098 | 2104 | 2109 |
2110 |
2111 |
2112 | 2114 | 2115 |
2116 |
2117 |
2118 | 2120 | 2121 |
2122 |
2123 |
2124 | 2126 | 2127 |
2128 |
2129 | 2130 | 2131 | 2135 |
2136 | 2139 |
2140 |
2141 | 2142 | 2144 |
2145 |
2146 | 2147 | 2148 |
2149 | 2150 |
2151 | 2152 |
2153 |
2154 |
2155 | 2156 | 2157 |
2158 | 2159 | `; 2160 | // 添加html 2161 | let dialogElement = document.createElement('div'); 2162 | dialogElement.id = 'bft-menu'; 2163 | dialogElement.innerHTML = dialogHtml; 2164 | document.body.appendChild(dialogElement); 2165 | let textFilterRulesRaw = GM_getValue("textFilterRules", []); 2166 | // 将各规则集的正则分行 2167 | for (let i = 0; i < textFilterRulesRaw.length; i++) { 2168 | textFilterRulesRaw[i].rules = textFilterRulesRaw[i].rules.join('\n'); 2169 | } 2170 | 2171 | var bftEditMenu = new Vue({ 2172 | el: '#bft-editTextrulesMenu', 2173 | data: { 2174 | textFilterRulesRaw, 2175 | activeRuleSetIndex: -1, // 用于跟踪当前处于编辑状态的规则集的索引 2176 | }, 2177 | computed: { 2178 | showRawRules() { 2179 | // 将分行的规则重组为数组 2180 | return JSON.stringify(this.textFilterRulesRaw[this.activeRuleSetIndex].rules.split('\n')); 2181 | } 2182 | }, 2183 | methods: { 2184 | 2185 | // 修改 2186 | editRuleSet(index) { 2187 | this.activeRuleSetIndex = index; 2188 | }, 2189 | closeEditWindow() { 2190 | this.activeRuleSetIndex = -1; 2191 | }, 2192 | jsonToLine(index) { 2193 | try { 2194 | // 将json格式的输入框的内容化为分行,填入分行框中 2195 | this.textFilterRulesRaw[index].rules = JSON.parse(document.querySelectorAll('.bft-ruleset .bft-textarea-container textarea')[1].value).join('\n'); 2196 | } catch (error) { 2197 | // 处理无效的 JSON 输入 2198 | showSnackbar('Json格式有误,请检查格式', 'error', 5, '关闭'); 2199 | } 2200 | 2201 | }, 2202 | saveRules() { 2203 | // 将分行列出的规则重新组成数组 2204 | this.textFilterRulesRaw.forEach((item) => { 2205 | item.rules = item.rules.split('\n'); 2206 | }); 2207 | // 将最后更新时间设为当前时间 2208 | this.textFilterRulesRaw.forEach((item) => { 2209 | item.lastUpdate = Math.floor(Date.now() / 1000); 2210 | }); 2211 | // 将规则写入配置中 2212 | GM_setValue("textFilterRules", this.textFilterRulesRaw); 2213 | // 重载配置 2214 | reloadRules(); 2215 | // 删除设定面板HTML 2216 | document.getElementById('bft-menu').remove(); 2217 | // 添加已关闭面板的标记 2218 | GM_setValue("temp_isMenuOpen", false); 2219 | }, 2220 | addRuleSet() { 2221 | // 创建一个新的规则集对象 2222 | const newRuleSet = { 2223 | name: '', 2224 | describe: '', 2225 | rules: '', 2226 | enable: true, 2227 | type: 'local', 2228 | link: '', 2229 | lastUpdate: Math.floor(Date.now() / 1000), 2230 | createDate: Math.floor(Date.now() / 1000) 2231 | }; 2232 | 2233 | // 将新的规则集对象添加到数组中 2234 | this.textFilterRulesRaw.push(newRuleSet); 2235 | }, 2236 | deleteRuleSet(index) { 2237 | // 删除指定规则集 2238 | this.textFilterRulesRaw.splice(index, 1); 2239 | }, 2240 | outputRuleSet(index) { 2241 | // 导出指定规则集 2242 | GM.setClipboard(JSON.stringify(GM_getValue("textFilterRules", [])[index].rules)); 2243 | showSnackbar('已导入剪切板', 'info', 5, '关闭'); 2244 | // 同时展示一个文本框 2245 | interactiveDialog('output', '导出', '受浏览器限制,可能需要手动复制', 'text', outPut); 2246 | }, 2247 | updateTime(index) { 2248 | // 为指定规则集更新最后更新时间 2249 | this.textFilterRulesRaw[index].lastUpdate = Math.floor(Date.now() / 1000); 2250 | }, 2251 | close() { 2252 | // 删除设定面板HTML 2253 | document.getElementById('bft-menu').remove(); 2254 | // 添加已关闭面板的标记 2255 | GM_setValue("temp_isMenuOpen", false); 2256 | }, 2257 | updateRuleSet(index) { 2258 | // 从url获取远程规则 2259 | GM_xmlhttpRequest({ 2260 | method: "GET", 2261 | url: this.textFilterRulesRaw[index].link, 2262 | responseType: "json", // Expect a json response type 2263 | onload: function (response) { 2264 | // Check if the status code is 200 (OK) 2265 | if (response.status === 200 && response.response != undefined) { 2266 | // Get the response body as a json object 2267 | let json = response.response; 2268 | // 转换 2269 | // let array = JSON.parse(json); 2270 | // Add the array to the obj[prop] property 2271 | // 将规则的正则分行 2272 | json = json.join('\n'); 2273 | // 写入暂存规则 2274 | textFilterRulesRaw[index].rules = json; 2275 | // 更新更新日期 2276 | textFilterRulesRaw[index].lastUpdate = Math.floor(Date.now() / 1000); 2277 | console.log(`[BF][配置]第${index}个规则集已成功获取远程规则`); 2278 | } else { 2279 | console.error(`[BF][配置]第${index}个规则集获取远程规则失败:格式错误,${response.statusText}`); 2280 | } 2281 | }, 2282 | onerror: function (error) { 2283 | // Handle errors here, such as logging an error message 2284 | console.error("Error: " + error.message); 2285 | console.error(`[BF][配置]第${index}个规则集获取远程规则失败:无法访问,${error.message}`); 2286 | } 2287 | }); 2288 | } 2289 | 2290 | } 2291 | }); 2292 | 2293 | } else if (GM_getValue("temp_isMenuOpen", false)) { 2294 | showSnackbar('已存在已经打开的设置面板,请先关闭', 'error', 5, '强行关闭', function () { 2295 | GM_setValue("temp_isMenuOpen", false); 2296 | }); 2297 | } 2298 | } 2299 | // 其他过滤设定 2300 | function bftSettingMenu_otherFilter() { 2301 | if (document.getElementById('bft-menu') === null && !GM_getValue("temp_isMenuOpen", false)) { 2302 | // 添加已打开面板的标记 2303 | GM_setValue("temp_isMenuOpen", true); 2304 | let dialogHtml = ` 2305 |
2306 |
2307 | 其他过滤 时长过滤 2308 |
2309 |
2310 | 2311 |
2312 |
2313 |
2314 | 2316 | 2317 |
2318 |
2319 |
2320 |
2321 |
2322 |
2323 | 2324 | 2325 |
2326 |
2327 | `; 2328 | let dialogElement = document.createElement('div'); 2329 | dialogElement.id = 'bft-menu'; 2330 | dialogElement.innerHTML = dialogHtml; 2331 | document.body.appendChild(dialogElement); 2332 | let otherFilterRulesRaw = GM_getValue("otherFilterRules", { duration: 0 }); 2333 | 2334 | var bftEditMenu = new Vue({ 2335 | el: '#bft-editOtherrulesMenu', 2336 | data: { 2337 | otherFilterRulesRaw 2338 | }, 2339 | methods: { 2340 | saveRules() { 2341 | // 将规则写入配置中 2342 | GM_setValue("otherFilterRules", this.otherFilterRulesRaw); 2343 | // 重载配置 2344 | reloadRules(); 2345 | // 删除设定面板HTML 2346 | document.getElementById('bft-menu').remove(); 2347 | // 添加已关闭面板的标记 2348 | GM_setValue("temp_isMenuOpen", false); 2349 | }, 2350 | close() { 2351 | // 删除设定面板HTML 2352 | document.getElementById('bft-menu').remove(); 2353 | // 添加已关闭面板的标记 2354 | GM_setValue("temp_isMenuOpen", false); 2355 | } 2356 | } 2357 | }); 2358 | } else if (GM_getValue("temp_isMenuOpen", false)) { 2359 | showSnackbar('已存在已经打开的设置面板,请先关闭', 'error', 5, '强行关闭', function () { 2360 | GM_setValue("temp_isMenuOpen", false); 2361 | }); 2362 | } 2363 | } 2364 | // 杂项设定 2365 | function bftSettingMenu_setting() { 2366 | if (document.getElementById('bft-menu') === null && !GM_getValue("temp_isMenuOpen", false)) { 2367 | // 添加已打开面板的标记 2368 | GM_setValue("temp_isMenuOpen", true); 2369 | let dialogHtml = ` 2370 |
2371 |
2372 | 杂项设置 2373 |
2374 |
2375 | 2376 |
2377 |
2378 |
2379 | 2381 | 2382 |
2383 |
2384 |
2385 | 2387 | 2388 |
2389 |
2390 |
2391 | 2392 | 2393 |
2394 |
2395 |
2396 |
2397 |
2398 | 2399 | 2400 |
2401 |
2402 | `; 2403 | let dialogElement = document.createElement('div'); 2404 | dialogElement.id = 'bft-menu'; 2405 | dialogElement.innerHTML = dialogHtml; 2406 | document.body.appendChild(dialogElement); 2407 | let settingRaw = GM_getValue("setting", { filterInterval: 1, autoUpdate: 6, enableFastAddUserFilterRules: true }); 2408 | var bftEditMenu = new Vue({ 2409 | el: '#bft-settingMenu', 2410 | data: { 2411 | settingRaw 2412 | }, 2413 | methods: { 2414 | saveRules() { 2415 | // 将规则写入配置中 2416 | GM_setValue("setting", this.settingRaw); 2417 | // 重载配置 2418 | reloadRules(); 2419 | // 删除设定面板HTML 2420 | document.getElementById('bft-menu').remove(); 2421 | // 添加已关闭面板的标记 2422 | GM_setValue("temp_isMenuOpen", false); 2423 | }, 2424 | close() { 2425 | // 删除设定面板HTML 2426 | document.getElementById('bft-menu').remove(); 2427 | // 添加已关闭面板的标记 2428 | GM_setValue("temp_isMenuOpen", false); 2429 | } 2430 | } 2431 | }); 2432 | } else if (GM_getValue("temp_isMenuOpen", false)) { 2433 | showSnackbar('已存在已经打开的设置面板,请先关闭', 'error', 5, '强行关闭', function () { 2434 | GM_setValue("temp_isMenuOpen", false); 2435 | }); 2436 | } 2437 | } 2438 | // 用户快速加入设置 不包括快速加入按钮 2439 | function fastAddUserFilterRules(uid) { 2440 | if (document.getElementById('bft-menu') === null) { 2441 | // console.debug('[BF]已选中', uid); 2442 | 2443 | let dialogHtml = ` 2444 |
2445 |
2446 | 快速加入 {{newRule.uid}} 2447 |
2448 |
2449 | 2450 |
2451 |
2452 | 2453 | 2456 |
2457 | 2459 | 2460 |
2461 |
2462 | 2463 |
2464 |
2465 |
2466 |
2467 | 2468 | 2469 |
2470 |
2471 | `; 2472 | let dialogElement = document.createElement('div'); 2473 | dialogElement.id = 'bft-menu'; 2474 | dialogElement.innerHTML = dialogHtml; 2475 | document.body.appendChild(dialogElement); 2476 | var bftEditMenu = new Vue({ 2477 | el: '#bft-fastAdd', 2478 | data: { 2479 | userFilterRulesRaw: GM_getValue("userFilterRules", [{ 2480 | "name": "示例", 2481 | "describe": "这是一个本地规则集的示例", 2482 | "enable": false, 2483 | "link": "local", 2484 | "lastUpdate": 1680699306, 2485 | "level": 3, 2486 | "rules": [ 2487 | { 2488 | "uid": "114514", 2489 | "level": "5", 2490 | "lastUpdate": 1680699306 2491 | } 2492 | ] 2493 | }]), 2494 | newRule: { uid: uid, level: 3, lastUpdate: parseInt(Date.now() / 1000) }, 2495 | rulesetIndex: [0] 2496 | }, 2497 | methods: { 2498 | saveRules() { 2499 | // 将规则写入配置中 2500 | // 检测规则集是否已经存在该用户 2501 | let isAdd = true; 2502 | for (let f = 0; f < this.userFilterRulesRaw[this.rulesetIndex[0]].rules.length; f++) { 2503 | if (this.newRule.uid == this.userFilterRulesRaw[this.rulesetIndex[0]].rules[f].uid) { 2504 | console.error('[BF][设置]无法添加,因为该用户已存在。#', f + 1); 2505 | showSnackbar(`无法添加,该用户已存在于该规则集中,(#${f + 1})`, 'error', 3000, '关闭'); 2506 | isAdd = false; 2507 | } 2508 | } 2509 | if (isAdd == true) { 2510 | // 更新 新用户时间 2511 | this.newRule.lastUpdate = Math.floor(Date.now() / 1000); 2512 | // console.debug(`加入的规则集${this.rulesetIndex[0]},${this.userFilterRulesRaw}`); 2513 | // 将新用户加入指定规则集 2514 | this.userFilterRulesRaw[this.rulesetIndex[0]].rules.push(this.newRule); 2515 | // 更新 规则更新日期 2516 | this.userFilterRulesRaw[this.rulesetIndex[0]].lastUpdate = Math.floor(Date.now() / 1000); 2517 | // 保存对话框中修改的配置至存储 2518 | GM_setValue("userFilterRules", this.userFilterRulesRaw); 2519 | console.info('[BF][设置]成功添加规则。'); 2520 | showSnackbar(`成功添加规则`, 'info', 5, '关闭'); 2521 | } 2522 | // 重载配置 2523 | reloadRules(); 2524 | // 删除设定面板HTML 2525 | document.getElementById('bft-menu').remove(); 2526 | }, 2527 | close() { 2528 | // 删除设定面板HTML 2529 | document.getElementById('bft-menu').remove(); 2530 | } 2531 | } 2532 | }); 2533 | } 2534 | 2535 | } 2536 | // 关于页面 独立模态对话框 2537 | function bftAboutDialog() { 2538 | if (document.getElementById('bft-AboutDialog') === null) { 2539 | 2540 | let dialogHtml = ` 2541 |
2542 |
2543 | 关于 2544 |
2545 |
2546 |
2547 |

2548 | 关于本脚本 2549 |

2550 |

2551 | 两岸猿声啼不住,轻舟已过万重山。 2552 |

2553 |

2554 | 作者 2555 |

2556 |

2557 |

2558 |

2559 | 外部链接 2560 |

2561 |

2562 | 使用文档 2563 | 开源地址 2564 | 问题报告 2565 |

2566 | 2568 |
2569 | 2570 |
2571 |
2572 | 2573 |
2574 |
2575 | `; 2576 | let dialogElement = document.createElement('div'); 2577 | dialogElement.id = 'bft-aboutDialog'; 2578 | dialogElement.innerHTML = dialogHtml; 2579 | document.body.appendChild(dialogElement); 2580 | // 其他 2581 | document.getElementById('bft-version').innerHTML = GM_info.script.version; 2582 | document.getElementById('bft-author').innerHTML = GM_info.script.author; 2583 | document.getElementById('bft-copyright').innerHTML = GM_info.script.copyright; 2584 | document.getElementById('bft-close-window').addEventListener("click", function () { 2585 | document.getElementById('bft-aboutDialog').remove(); 2586 | GM_setValue("temp_isMenuOpen", false); 2587 | }); 2588 | 2589 | } 2590 | } 2591 | // 开启设置面板的按钮 2592 | async function addOpenMenuButton() { 2593 | // 每 500ms 执行一次循环 2594 | while (document.body === null) { 2595 | await new Promise(function (resolve) { 2596 | // 通过延迟返回resolve来阻塞while的执行。 2597 | //setTimeout(resolve,100) 和 setTimeout(resolve(),100) 有很大区别,后者会会立即执行,然后它的返回值将在 100 毫秒后被调用,这将导致resolve会立刻执行,不管有没有调用其返回值,从而无法阻塞while循环100ms。 2598 | setTimeout(resolve, 500); 2599 | }); 2600 | } 2601 | // document.body 加载完后再执行 2602 | let dialogHtml = ` 2603 |
2605 | 2606 | 2607 | 2609 | 2610 | 2611 |
2612 |
2613 |
2615 | 2616 | 2618 | 2619 |
UID过滤设置
2620 |
2621 |
2623 | 2624 | 2625 | 2626 |
关键词过滤设置
2627 |
2628 |
2630 | 2631 | 2632 | 2634 | 2635 | 2636 |
其他过滤设置
2637 |
2638 |
2640 | 2641 | 2642 | 2643 | 2644 | 2645 | 2647 | 2649 | 2650 | 2651 | 2652 |
杂项设置
2653 |
2654 |
2655 | 2656 | 2657 | 2659 | 2660 |
关于
2661 | 2662 |
2663 |
2664 | `; 2665 | let dialogElement = document.createElement('div'); 2666 | dialogElement.classList.add('bft-fab'); 2667 | dialogElement.innerHTML = dialogHtml; 2668 | document.body.appendChild(dialogElement); 2669 | // 点击事件监听器 2670 | document.getElementById('bftFab_openMenu_user').addEventListener('click', function () { 2671 | bftSettingMenu_userFilter(); 2672 | }); 2673 | document.getElementById('bftFab_openMenu_text').addEventListener('click', function () { 2674 | bftSettingMenu_textFilter(); 2675 | }); 2676 | document.getElementById('bftFab_openMenu_other').addEventListener('click', function () { 2677 | bftSettingMenu_otherFilter(); 2678 | }); 2679 | document.getElementById('bftFab_openMenu_otherset').addEventListener('click', function () { 2680 | bftSettingMenu_setting(); 2681 | }); 2682 | document.getElementById('bftFab_openMenu_about').addEventListener('click', function () { 2683 | bftAboutDialog(); 2684 | }); 2685 | 2686 | } 2687 | // ----- 2688 | // 组件 2689 | // ----- 2690 | // Snackbar 2691 | // -- 2692 | // 显示 Snackbar 的函数 actionText与action可以不添加,即不显示按钮或不执行 2693 | function showSnackbar(message, level, time, actionText, action) { 2694 | // 创建 Snackbar 2695 | let snackbarContainer = document.createElement('div'); 2696 | snackbarContainer.classList.add('bft-snackbar'); 2697 | // 创建 logo 2698 | let snackbarIcon = document.createElement('div'); 2699 | snackbarIcon.classList.add('bft-snackbar-icon'); 2700 | switch (level) { 2701 | case 'error': 2702 | snackbarIcon.style = `fill: red;`; 2703 | snackbarIcon.innerHTML = ``; 2704 | break; 2705 | case 'warning': 2706 | snackbarIcon.style = `fill: #ffb772;`; 2707 | snackbarIcon.innerHTML = ``; 2708 | break; 2709 | case 'info': 2710 | snackbarIcon.style = `fill: #65a3ff;`; 2711 | snackbarIcon.innerHTML = ``; 2712 | break; 2713 | } 2714 | snackbarContainer.appendChild(snackbarIcon); 2715 | // 文本 2716 | let snackbarContent = document.createElement('span'); 2717 | snackbarContent.classList.add('bft-snackbar-content'); 2718 | snackbarContent.textContent = message; 2719 | // 为文本上tianjtitle,即鼠标悬浮时的提示 2720 | snackbarContent.title = message; 2721 | //并入容器 2722 | snackbarContainer.appendChild(snackbarContent); 2723 | 2724 | // 添加按钮 2725 | if (actionText && actionText !== "") { 2726 | let snackbarButton = document.createElement('button'); 2727 | snackbarButton.textContent = actionText; 2728 | snackbarButton.classList.add('bft-snackbar-button'); 2729 | snackbarButton.onclick = function () { 2730 | hideSnackbar(); 2731 | }; 2732 | if (action && typeof action === 'function') { 2733 | snackbarButton.onclick = function () { 2734 | action(); 2735 | hideSnackbar(); 2736 | }; 2737 | } 2738 | snackbarContainer.appendChild(snackbarButton); 2739 | } 2740 | // 创建容器 2741 | // 没有容器就创建容器 2742 | if (document.getElementsByClassName('bft-snackbar-container')[0]) { 2743 | var snackbarDiv = document.getElementsByClassName('bft-snackbar-container')[0]; 2744 | } else { 2745 | var snackbarDiv = document.createElement('div'); 2746 | snackbarDiv.classList.add('bft-snackbar-container'); 2747 | } 2748 | // 将 Snackbar 添加到容器中 2749 | snackbarDiv.appendChild(snackbarContainer); 2750 | // 将 Snackbar容器 添加到页面 2751 | document.body.appendChild(snackbarDiv); 2752 | 2753 | // 定义延时,一定时间后隐藏 Snackbar 2754 | setTimeout(function () { 2755 | hideSnackbar(); 2756 | }, time * 1000); // 这里设置显示时间 2757 | } 2758 | // 隐藏 Snackbar 的函数 2759 | function hideSnackbar() { 2760 | let snackbarContainer = document.getElementsByClassName('bft-snackbar')[0]; 2761 | if (snackbarContainer) { 2762 | document.getElementsByClassName('bft-snackbar')[0].remove(); 2763 | } 2764 | } 2765 | // -- 2766 | // 可交互式对话框 2767 | function interactiveDialog(type, title, dialogText, inputType = 'text', preInput) { 2768 | // 单文本框输入式 input 标题 标签 输入类型 预输入文本 2769 | // 返回promote对象 2770 | if (type === 'input' && document.getElementById('bft-dialog') === null) { 2771 | const dialogHtml = ` 2772 |
2773 |
2774 |
[null]
2775 |
2776 |
2777 | 2778 | 2779 |
2780 |
2781 |
2782 |
2783 | 2784 | 2785 |
2786 |
2787 |
2788 | `; 2789 | let dialogElement = document.createElement('div'); 2790 | dialogElement.id = 'bft-dialog'; 2791 | dialogElement.innerHTML = dialogHtml; 2792 | document.body.appendChild(dialogElement); 2793 | document.getElementById('bftDialog_title').innerText = title; 2794 | document.getElementById('bftDialog_label').innerText = dialogText; 2795 | document.getElementById('bftDialog_input').setAttribute('type', inputType); 2796 | if (preInput) { 2797 | document.getElementById('bftDialog_input').setAttribute('value', preInput); 2798 | } 2799 | // 创建一个Promise异步对象,等待后续操作 2800 | return new Promise((resolve, reject) => { 2801 | document.getElementById('bftDialog_confirm').addEventListener('click', function () { 2802 | // 提交时传回值 2803 | 2804 | // const value = "Some value"; // 假设这是从点击事件中获取的值 2805 | resolve(document.getElementById('bftDialog_input').value); // 将值传递给异步函数 2806 | }); 2807 | // 取消 2808 | document.getElementById('bftDialog_cancel').addEventListener('click', function () { 2809 | document.getElementById('bft-dialog').remove(); 2810 | reject('user cancel this operation'); 2811 | }); 2812 | }); 2813 | } 2814 | // 输出对话框,仅用于输出文本。 2815 | if (type === 'output' && document.getElementById('bft-dialog') === null) { 2816 | const dialogHtml = ` 2817 |
2818 |
2819 |
[null]
2820 |
2821 |
2822 | 2823 | 2824 |
2825 |
2826 |
2827 |
2828 | 2829 |
2830 |
2831 |
2832 | `; 2833 | let dialogElement = document.createElement('div'); 2834 | dialogElement.id = 'bft-dialog'; 2835 | dialogElement.innerHTML = dialogHtml; 2836 | document.body.appendChild(dialogElement); 2837 | document.getElementById('bftDialog_title').innerText = title; 2838 | document.getElementById('bftDialog_label').innerText = dialogText; 2839 | document.getElementById('bftDialog_input').setAttribute('type', inputType); 2840 | if (preInput) { 2841 | document.getElementById('bftDialog_input').setAttribute('value', preInput); 2842 | } 2843 | 2844 | // 关闭对话框 2845 | document.getElementById('bftDialog_close').addEventListener('click', function () { 2846 | document.getElementById('bft-dialog').remove(); 2847 | }); 2848 | } 2849 | } 2850 | // -- 2851 | // ----- 2852 | // 其他 2853 | // ----- 2854 | // 自动更新:关键词过滤 2855 | function autoUpdateTextRulesets() { 2856 | // 读取规则集 2857 | let textFilterRulesRaw = GM_getValue("textFilterRules", []); 2858 | textFilterRulesRaw.forEach(function (resource) { 2859 | // 只有是远程规则&&大于设定的更新时间才需要更新 2860 | if (resource.type === "remote" && (Math.floor(Date.now() / 3600000) - resource.lastUpdate / 3600) >= GM_getValue("setting", { filterInterval: 1, autoUpdate: 6, enableFastAddUserFilterRules: true }).autoUpdate && GM_getValue("setting", { filterInterval: 1, autoUpdate: 6, enableFastAddUserFilterRules: true }).autoUpdate != 0) { 2861 | console.log(`[BF][设置]规则集:${resource.name} 正在准备更新`); 2862 | GM_xmlhttpRequest({ 2863 | method: "GET", 2864 | url: resource.link, 2865 | responseType: "json", // Expect a json response type 2866 | onload: function (response) { 2867 | // Check if the status code is 200 (OK) 2868 | if (response.status === 200) { 2869 | // Get the response body as a json object 2870 | let json = response.response; 2871 | 2872 | // 转换 2873 | // let array = JSON.parse(json); 2874 | 2875 | // Add the array to the obj[prop] property 2876 | resource.rules = json; 2877 | console.log(json); 2878 | console.log(`规则集:${resource.name} 已更新`); 2879 | // 更新 规则中的用户的更新日期 2880 | resource.lastUpdate = Math.floor(Date.now() / 1000); 2881 | GM_setValue("textFilterRules", textFilterRulesRaw); 2882 | } else { 2883 | // Handle other status codes here, such as logging an error message 2884 | console.error("Request failed: " + response.statusText); 2885 | } 2886 | }, 2887 | onerror: function (error) { 2888 | // Handle errors here, such as logging an error message 2889 | console.error("Error: " + error.message); 2890 | } 2891 | }); 2892 | } 2893 | }); 2894 | } 2895 | // 自动更新:UID过滤 2896 | function autoUpdateUserRulesets() { 2897 | // 读取规则集 2898 | let userFilterRulesRaw = GM_getValue("userFilterRules", []); 2899 | userFilterRulesRaw.forEach(function (resource) { 2900 | if (resource.link != "local" && (Math.floor(Date.now() / 3600000) - resource.lastUpdate / 3600) >= GM_getValue("setting", { filterInterval: 1, autoUpdate: 6, enableFastAddUserFilterRules: true }).autoUpdate && GM_getValue("setting", { filterInterval: 1, autoUpdate: 6, enableFastAddUserFilterRules: true }).autoUpdate != 0) { 2901 | console.log(`[BF][设置]规则集:${resource.name} 正在准备更新`); 2902 | GM_xmlhttpRequest({ 2903 | method: "GET", 2904 | url: resource.link, 2905 | responseType: "json", // Expect a json response type 2906 | onload: function (response) { 2907 | // Check if the status code is 200 (OK) 2908 | if (response.status === 200) { 2909 | // Get the response body as a json object 2910 | let json = response.response; 2911 | 2912 | // 转换 2913 | // let array = JSON.parse(json); 2914 | 2915 | // Add the array to the obj[prop] property 2916 | resource.rules = json; 2917 | console.log(json); 2918 | console.log(`规则集:${resource.name} 已更新`); 2919 | // 更新 规则中的用户的更新日期 2920 | resource.lastUpdate = Math.floor(Date.now() / 1000); 2921 | GM_setValue("userFilterRules", userFilterRulesRaw); 2922 | } else { 2923 | // Handle other status codes here, such as logging an error message 2924 | console.error("Request failed: " + response.statusText); 2925 | } 2926 | }, 2927 | onerror: function (error) { 2928 | // Handle errors here, such as logging an error message 2929 | console.error("Error: " + error.message); 2930 | } 2931 | }); 2932 | } 2933 | }); 2934 | } 2935 | // 检测脚本更新 2936 | function autoUpdateScript() { 2937 | //发起一个get请求 2938 | GM_xmlhttpRequest({ 2939 | method: "GET", 2940 | url: "https://raw.githubusercontent.com/ChizhaoEngine/BiliFilter/main/bft.user.js", 2941 | onload: function (response) { 2942 | const versionMatch = response.responseText.match(/@version\s+([0-9.]+)/); 2943 | if (versionMatch[1] !== GM_info.script.version) { 2944 | showSnackbar('检测到BiliFilter需要更新', 'warning', 5, '更新', function () { 2945 | const newWindow = window.open("https://raw.githubusercontent.com/ChizhaoEngine/BiliFilter/main/bft.user.js"); 2946 | newWindow.opener = null; 2947 | }); 2948 | } 2949 | } 2950 | }); 2951 | } 2952 | autoUpdateScript(); 2953 | // 时间戳-->日期格式 2954 | Vue.filter('formatDate', function (value) { 2955 | if (value) { 2956 | // 创建一个 Date 对象 2957 | let dateRaw = Math.floor(value * 1000); 2958 | let date = new Date(dateRaw); 2959 | let year = date.getFullYear(); 2960 | // 获取月份,注意要加1 <--- 我是傻逼 2961 | let month = date.getMonth() + 1; 2962 | // 获取日期 2963 | let day = date.getDate(); 2964 | // 获取小时 2965 | let hour = date.getHours(); 2966 | // 获取分钟 2967 | let minute = date.getMinutes(); 2968 | // 获取秒钟 2969 | let second = date.getSeconds(); 2970 | 2971 | // 如果月份、日期、小时、分钟或秒钟小于10,就在前面补0 2972 | if (month < 10) { 2973 | month = '0' + month; 2974 | } 2975 | 2976 | if (day < 10) { 2977 | day = '0' + day; 2978 | } 2979 | 2980 | if (hour < 10) { 2981 | hour = '0' + hour; 2982 | } 2983 | 2984 | if (minute < 10) { 2985 | minute = '0' + minute; 2986 | } 2987 | 2988 | if (second < 10) { 2989 | second = '0' + second; 2990 | } 2991 | 2992 | // 拼接成 YYYY-MM-DD hh:mm:ss 的格式 2993 | return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second; 2994 | } 2995 | }); 2996 | 2997 | 2998 | // Your shit code here... 2999 | })(); --------------------------------------------------------------------------------