diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e797a09..05e17971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # CHANGELOG +## 3.8.0 + +- 新增:动态过滤器 +- 新增:动态过滤 用户名过滤 +- 新增:动态过滤 标题过滤 +- 新增:动态过滤 时长过滤 +- 新增:直播页 折叠排行榜/大航海 +- 新增:直播页 禁用壁纸 +- 优化:评论区 踩/回复只在hover时显示,增加延迟减少跳变 +- 优化:顶栏 隐藏活动,搜索框样式 + ## 3.7.4 - 修复:动态页 隐藏动态右侧饰品 diff --git a/README.md b/README.md index f77cda9c..9f41a3fd 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ > - **页面净化:「首页、播放页、影视番剧播放页、直播间、搜索页、动态页、热门页、频道页」** > - **视频过滤:「首页、播放页、搜索页、热门页、频道页、空间页」** > - **评论过滤:「播放页、影视番剧播放页、动态页」** +> - **动态过滤:「动态页」** ![](images/usage.png) diff --git a/src/components/wordlist.ts b/src/components/wordlist.ts index 07ec6694..09d9366b 100644 --- a/src/components/wordlist.ts +++ b/src/components/wordlist.ts @@ -36,9 +36,7 @@ export class WordList { GM_setValue(`BILICLEANER_${this.listID}`, this.wordArr) } private getValue() { - debug(`key`, `BILICLEANER_${this.listID}`) this.wordArr = GM_getValue(`BILICLEANER_${this.listID}`, []) - debug(`list ${this.listID} getValue ${this.wordArr.length} lines`) this.wordSet = new Set(this.wordArr) } @@ -60,25 +58,6 @@ export class WordList { } } - // /** 添加多个值到列表 */ - // addValues(values: string[]) { - // try { - // this.getValue() - // values.forEach((value) => { - // value = value.trim() - // if (value && !this.wordSet.has(value)) { - // this.wordArr.push(value) - // this.wordSet.add(value) - // } - // }) - // this.setValue() - // debug(`list ${this.listID} add ${values.length} lines, OK`) - // } catch (err) { - // error(err) - // error(`list ${this.listID} add ${values.length} lines, ERROR`) - // } - // } - /** * 编辑整个列表 * @param values 编辑框内输入的列表 diff --git a/src/filters/commentFilter/filters/core.ts b/src/filters/commentFilter/filters/core.ts index a84cf444..8a89e5ff 100644 --- a/src/filters/commentFilter/filters/core.ts +++ b/src/filters/commentFilter/filters/core.ts @@ -75,7 +75,7 @@ class CoreCommentFilter { .catch((_result) => { // 命中黑名单 // debug(_result) - if (whiteTasks) { + if (whiteTasks.length) { Promise.all(whiteTasks) .then((_result) => { // 命中黑名单,未命中白名单 diff --git a/src/filters/commentFilter/pages/dynamic.ts b/src/filters/commentFilter/pages/dynamic.ts index dc9554cb..22ea6290 100644 --- a/src/filters/commentFilter/pages/dynamic.ts +++ b/src/filters/commentFilter/pages/dynamic.ts @@ -1,8 +1,7 @@ -import { GM_getValue } from '$' import { Group } from '../../../components/group' import { CheckboxItem, ButtonItem } from '../../../components/item' import { debugCommentFilter as debug, error } from '../../../utils/logger' -import { isPageDynamic } from '../../../utils/page-type' +import { isPageDynamic } from '../../../utils/pageType' import { showEle, waitForEle } from '../../../utils/tool' import { ContentAction, UsernameAction } from './actions/action' import coreCommentFilterInstance, { CommentSelectorFunc } from '../filters/core' @@ -16,15 +15,12 @@ let isContextMenuFuncRunning = false let isContextMenuUsernameEnable = false // 白名单功能开关 -let isRootCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_dynamic-comment-root-whitelist-status', false) -let isSubCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_dynamic-comment-sub-whitelist-status', false) -let isUploaderCommentWhitelistEnable: boolean = GM_getValue( - 'BILICLEANER_dynamic-comment-uploader-whitelist-status', - true, -) -let isPinnedCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_dynamic-comment-pinned-whitelist-status', true) -let isNoteCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_dynamic-comment-note-whitelist-status', true) -let isLinkCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_dynamic-comment-link-whitelist-status', true) +let isRootCommentWhitelistEnable = false +let isSubCommentWhitelistEnable = false +let isUploaderCommentWhitelistEnable = true +let isPinnedCommentWhitelistEnable = true +let isNoteCommentWhitelistEnable = true +let isLinkCommentWhitelistEnable = true if (isPageDynamic()) { let commentListContainer: HTMLElement diff --git a/src/filters/commentFilter/pages/video.ts b/src/filters/commentFilter/pages/video.ts index 0e2c2a84..e4823f1e 100644 --- a/src/filters/commentFilter/pages/video.ts +++ b/src/filters/commentFilter/pages/video.ts @@ -1,8 +1,7 @@ -import { GM_getValue } from '$' import { Group } from '../../../components/group' import { CheckboxItem, ButtonItem } from '../../../components/item' import { debugCommentFilter as debug, error } from '../../../utils/logger' -import { isPageBangumi, isPagePlaylist, isPageVideo } from '../../../utils/page-type' +import { isPageBangumi, isPagePlaylist, isPageVideo } from '../../../utils/pageType' import { showEle, waitForEle } from '../../../utils/tool' import { ContentAction, UsernameAction } from './actions/action' import coreCommentFilterInstance, { CommentSelectorFunc } from '../filters/core' @@ -16,12 +15,12 @@ let isContextMenuFuncRunning = false let isContextMenuUsernameEnable = false // 白名单功能开关 -let isRootCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_video-comment-root-whitelist-status', false) -let isSubCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_video-comment-sub-whitelist-status', false) -let isUploaderCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_video-comment-uploader-whitelist-status', true) -let isPinnedCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_video-comment-pinned-whitelist-status', true) -let isNoteCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_video-comment-note-whitelist-status', true) -let isLinkCommentWhitelistEnable: boolean = GM_getValue('BILICLEANER_video-comment-link-whitelist-status', true) +let isRootCommentWhitelistEnable = false +let isSubCommentWhitelistEnable = false +let isUploaderCommentWhitelistEnable = true +let isPinnedCommentWhitelistEnable = true +let isNoteCommentWhitelistEnable = true +let isLinkCommentWhitelistEnable = true if (isPageVideo() || isPageBangumi() || isPagePlaylist()) { let commentListContainer: HTMLElement diff --git a/src/filters/dynFilter/agency/agency.ts b/src/filters/dynFilter/agency/agency.ts new file mode 100644 index 00000000..b286a312 --- /dev/null +++ b/src/filters/dynFilter/agency/agency.ts @@ -0,0 +1,70 @@ +import dynDurationFilterInstance from '../filters/subfilters/dynDuration' +import dynTitleFilterInstance from '../filters/subfilters/dynTitle' +import dynUploaderFilterInstance from '../filters/subfilters/dynUploader' + +// 代理, 接收页面操作通知, 更新子过滤器的参数 +class DynUploaderFilterAgency { + notifyDynUploader(event: string, value?: string | string[]) { + switch (event) { + case 'disable': + dynUploaderFilterInstance.setStatus(false) + break + case 'enable': + dynUploaderFilterInstance.setStatus(true) + break + case 'add': + if (typeof value === 'string') { + if (value.trim()) { + dynUploaderFilterInstance.addParam(value.trim()) + } + } + break + case 'edit': + if (Array.isArray(value)) { + dynUploaderFilterInstance.setParams(value.map((v) => v.trim()).filter((v) => v)) + } + break + } + } + notifyDynTitle(event: string, value?: string | string[]) { + switch (event) { + case 'disable': + dynTitleFilterInstance.setStatus(false) + break + case 'enable': + dynTitleFilterInstance.setStatus(true) + break + case 'add': + if (typeof value === 'string') { + if (value.trim()) { + dynTitleFilterInstance.addParam(value.trim()) + } + } + break + case 'edit': + if (Array.isArray(value)) { + dynTitleFilterInstance.setParams(value.map((v) => v.trim()).filter((v) => v)) + } + break + } + } + notifyDynDuration(event: string, value?: number) { + switch (event) { + case 'disable': + dynDurationFilterInstance.setStatus(false) + break + case 'enable': + dynDurationFilterInstance.setStatus(true) + break + case 'change': + if (typeof value === 'number') { + dynDurationFilterInstance.setParams(value) + } + break + } + } +} + +// 单例 +const dynUploaderFilterAgencyInstance = new DynUploaderFilterAgency() +export default dynUploaderFilterAgencyInstance diff --git a/src/filters/dynFilter/filters/core.ts b/src/filters/dynFilter/filters/core.ts new file mode 100644 index 00000000..19cbf5df --- /dev/null +++ b/src/filters/dynFilter/filters/core.ts @@ -0,0 +1,129 @@ +import settings from '../../../settings' +import { debugDynFilter as debug, error, log } from '../../../utils/logger' +import { hideEle, isEleHide, showEle } from '../../../utils/tool' +import dynDurationFilterInstance from './subfilters/dynDuration' +import dynTitleFilterInstance from './subfilters/dynTitle' +import dynUploaderFilterInstance from './subfilters/dynUploader' + +export interface IDynSubFilter { + isEnable: boolean + setStatus(status: boolean): void + setParams?(value: string[] | number): void + addParam?(value: string): void + check(value: string): Promise +} + +export type DynSelectorFunc = { + dynUploader?: (dyn: HTMLElement) => string | null + dynDuration?: (dyn: HTMLElement) => string | null + dynTitle?: (dyn: HTMLElement) => string | null +} + +interface DynInfo { + dynUploader?: string | undefined + dynDuration?: string | undefined + dynTitle?: string | undefined +} + +class CoreDynFilter { + /** + * 对动态内容进行并发检测 + * @param dyns 动态列表 + * @param sign 是否标记已过滤项 + * @param selectorFunc 使用selector选取元素的函数 + */ + checkAll(dyns: HTMLElement[], sign = true, selectorFunc: DynSelectorFunc) { + try { + const checkDynUploader = dynUploaderFilterInstance.isEnable && selectorFunc.dynUploader !== undefined + const checkDynDuration = dynDurationFilterInstance.isEnable && selectorFunc.dynDuration !== undefined + const checkDynTitle = dynTitleFilterInstance.isEnable && selectorFunc.dynTitle !== undefined + + if (!checkDynUploader && !checkDynDuration && !checkDynTitle) { + // 黑名单全部关闭时 恢复全部动态 + dyns.forEach((dyn) => showEle(dyn)) + return + } + + dyns.forEach((dyn) => { + const info: DynInfo = {} + + // 构建黑白名单任务, 调用各个子过滤器的check()方法检测 + const blackTasks: Promise[] = [] + const whiteTasks: Promise[] = [] + + if (checkDynUploader) { + const dynUploader = selectorFunc.dynUploader!(dyn) + if (dynUploader) { + blackTasks.push(dynUploaderFilterInstance.check(dynUploader)) + info.dynUploader = dynUploader + } + } + if (checkDynDuration) { + const dynDuration = selectorFunc.dynDuration!(dyn) + if (dynDuration) { + blackTasks.push(dynDurationFilterInstance.check(dynDuration)) + info.dynDuration = dynDuration + } + } + if (checkDynTitle) { + const dynTitle = selectorFunc.dynTitle!(dyn) + if (dynTitle) { + blackTasks.push(dynTitleFilterInstance.check(dynTitle)) + info.dynTitle = dynTitle + } + } + + // 执行检测 + Promise.all(blackTasks) + .then((_result) => { + // 未命中黑名单 + // debug(_result) + showEle(dyn) + Promise.all(whiteTasks) + .then((_result) => {}) + .catch((_result) => {}) + }) + .catch((_result) => { + // 命中黑名单 + debug(_result) + if (whiteTasks.length) { + Promise.all(whiteTasks) + .then((_result) => { + // 命中黑名单,未命中白名单 + // debug(_result) + if (!isEleHide(dyn)) { + log(`hide dyn + dynUploader: ${info.dynUploader} + dynDuration: ${info.dynDuration} + dynTitle: ${info.dynTitle}`) + } + hideEle(dyn) + }) + .catch((_result) => { + // 命中白名单 + // debug(_result) + showEle(dyn) + }) + } else { + if (!isEleHide(dyn)) { + log(`hide dyn + dynUploader: ${info.dynUploader} + dynDuration: ${info.dynDuration} + dynTitle: ${info.dynTitle}`) + } + hideEle(dyn) + } + }) + + // 标记已过滤动态 + sign && dyn.setAttribute(settings.filterSign, '') + }) + } catch (err) { + error(err) + error('CoreDynFilter checkAll error') + } + } +} + +const coreDynFilterInstance = new CoreDynFilter() +export default coreDynFilterInstance diff --git a/src/filters/dynFilter/filters/subfilters/dynDuration.ts b/src/filters/dynFilter/filters/subfilters/dynDuration.ts new file mode 100644 index 00000000..57cbf922 --- /dev/null +++ b/src/filters/dynFilter/filters/subfilters/dynDuration.ts @@ -0,0 +1,51 @@ +import { IDynSubFilter } from '../core' + +class DynDurationFilter implements IDynSubFilter { + // 时长阈值, 单位秒 + private threshold = 0 + isEnable = false + + setStatus(status: boolean) { + this.isEnable = status + } + + setParams(threshold: number) { + this.threshold = threshold + } + + // dynDuration转换为秒数, 支持 HH:MM:SS, MM:SS, 纯数字 + dynDurationToSec = (dynDuration: string): number => { + dynDuration = dynDuration.trim() + if (dynDuration.match(/^(?:\d+:)?\d+:\d+$/)) { + const parts = dynDuration.split(':').map((part) => parseInt(part)) + if (parts.length === 3) { + return parts[0] * 3600 + parts[1] * 60 + parts[2] + } + if (parts.length === 2) { + return parts[0] * 60 + parts[1] + } + } else if (dynDuration.match(/^\d+$/)) { + return parseInt(dynDuration) + } + return -1 + } + + check(dynDuration: string): Promise { + return new Promise((resolve, reject) => { + if (!this.isEnable || this.threshold === 0) { + resolve(`DynDuration resolve, disable or 0`) + } else { + const seconds = this.dynDurationToSec(dynDuration) + if (seconds > 0 && seconds > this.threshold) { + resolve(`DynDuration OK`) + } else { + reject(`DynDuration too short`) + } + } + }) + } +} + +// 单例 +const dynDurationFilterInstance = new DynDurationFilter() +export default dynDurationFilterInstance diff --git a/src/filters/dynFilter/filters/subfilters/dynTitle.ts b/src/filters/dynFilter/filters/subfilters/dynTitle.ts new file mode 100644 index 00000000..323af2e6 --- /dev/null +++ b/src/filters/dynFilter/filters/subfilters/dynTitle.ts @@ -0,0 +1,60 @@ +import { error } from '../../../../utils/logger' +import { IDynSubFilter } from '../core' + +class DynTitleFilter implements IDynSubFilter { + isEnable = false + private dynTitleSet = new Set() + + setStatus(status: boolean) { + this.isEnable = status + } + + setParams(values: string[]) { + this.dynTitleSet = new Set(values.map((v) => v.trim()).filter((v) => v)) + } + + addParam(value: string) { + if (value.trim()) { + this.dynTitleSet.add(value.trim()) + } + } + + check(title: string): Promise { + // 忽略大小写 + title = title.trim().toLowerCase() + return new Promise((resolve, reject) => { + try { + if (!this.isEnable || title.length === 0 || this.dynTitleSet.size === 0) { + resolve(`DynTitle resolve, disable or empty`) + } + let flag = false + this.dynTitleSet.forEach((word) => { + if (word.startsWith('/') && word.endsWith('/')) { + // 关键词为正则表达式(反斜杠使用单斜杠),大小写不敏感,支持unicodeSets + const pattern = new RegExp(word.slice(1, -1), 'iv') + if (title.match(pattern)) { + // 命中黑名单正则 + flag = true + reject(`DynTitle reject, ${title} match ${word} in blacklist`) + } + } else { + if (word && title.includes(word.toLowerCase())) { + flag = true + reject(`DynTitle reject, ${title} match ${word} in blacklist`) + } + } + }) + if (!flag) { + resolve(`DynTitle resolve, title not match blacklist`) + } + } catch (err) { + error(err) + resolve(`DynTitle resolve, error`) + } + }) + } +} + +// 单例 +const dynTitleFilterInstance = new DynTitleFilter() +export default dynTitleFilterInstance diff --git a/src/filters/dynFilter/filters/subfilters/dynUploader.ts b/src/filters/dynFilter/filters/subfilters/dynUploader.ts new file mode 100644 index 00000000..65dc80d4 --- /dev/null +++ b/src/filters/dynFilter/filters/subfilters/dynUploader.ts @@ -0,0 +1,41 @@ +import { error } from '../../../../utils/logger' +import { IDynSubFilter } from '../core' + +class DynUploaderFilter implements IDynSubFilter { + isEnable = false + private dynUploaderSet = new Set() + + setStatus(status: boolean) { + this.isEnable = status + } + + setParams(values: string[]) { + this.dynUploaderSet = new Set(values.map((v) => v.trim()).filter((v) => v)) + } + + addParam(dynUploader: string) { + this.dynUploaderSet.add(dynUploader.trim()) + } + + check(dynUploader: string): Promise { + dynUploader = dynUploader.trim() + return new Promise((resolve, reject) => { + try { + if (!this.isEnable || dynUploader.length === 0 || this.dynUploaderSet.size === 0) { + resolve('dynUploader resolve, disable or empty') + } else if (this.dynUploaderSet.has(dynUploader)) { + reject(`dynUploader reject, ${dynUploader} in blacklist`) + } else { + resolve('dynUploader resolve') + } + } catch (err) { + error(err) + resolve(`dynUploader resolve, error`) + } + }) + } +} + +// 单例 +const dynUploaderFilterInstance = new DynUploaderFilter() +export default dynUploaderFilterInstance diff --git a/src/filters/dynFilter/pages/actions/action.ts b/src/filters/dynFilter/pages/actions/action.ts new file mode 100644 index 00000000..158dfb99 --- /dev/null +++ b/src/filters/dynFilter/pages/actions/action.ts @@ -0,0 +1,180 @@ +import { GM_getValue } from '$' +import { WordList } from '../../../../components/wordlist' +import dynUploaderFilterAgencyInstance from '../../agency/agency' +import dynDurationFilterInstance from '../../filters/subfilters/dynDuration' +import dynTitleFilterInstance from '../../filters/subfilters/dynTitle' +import dynUploaderFilterInstance from '../../filters/subfilters/dynUploader' + +interface DynFilterAction { + statusKey: string + valueKey: string + status: boolean + value: number | string | string[] + // 检测动态列表的函数 + checkDynList(fullSite: boolean): void + blacklist?: WordList + whitelist?: WordList + + enable(): void + disable(): void + change?(value: number): void + add?(value: string): void + edit?(value: string[]): void +} + +/** + * 将类的成员函数作为参数传递时,【必须】使用箭头函数包裹,避免出现this上下文丢失问题 + */ + +export class DynUploaderAction implements DynFilterAction { + statusKey: string + valueKey: string + checkDynList: (fullSite: boolean) => void + status: boolean + value: string[] + blacklist: WordList + + /** + * 动态区用户过滤操作 + * @param statusKey 是否启用的GM key + * @param valueKey 存储数据的GM key + * @param checkDynList 检测动态列表函数 + */ + constructor(statusKey: string, valueKey: string, checkDynList: (fullSite: boolean) => void) { + this.statusKey = statusKey + this.valueKey = valueKey + this.status = GM_getValue(`BILICLEANER_${this.statusKey}`, false) + this.value = GM_getValue(`BILICLEANER_${this.valueKey}`, []) + this.checkDynList = checkDynList + + // 配置子过滤器 + dynUploaderFilterInstance.setStatus(this.status) + dynUploaderFilterInstance.setParams(this.value) + + this.blacklist = new WordList( + this.valueKey, + '隐藏动态 用户名列表', + '每行一个用户名,保存时自动去重', + (values: string[]) => { + this.edit(values) + }, + ) + } + + enable() { + dynUploaderFilterAgencyInstance.notifyDynUploader('enable') + this.checkDynList(true) + this.status = true + } + disable() { + dynUploaderFilterAgencyInstance.notifyDynUploader('disable') + this.checkDynList(true) + this.status = false + } + add(value: string) { + this.blacklist.addValue(value) + dynUploaderFilterAgencyInstance.notifyDynUploader('add', value) + this.checkDynList(true) + } + edit(values: string[]) { + dynUploaderFilterAgencyInstance.notifyDynUploader('edit', values) + this.checkDynList(true) + } +} + +export class DynTitleKeywordAction implements DynFilterAction { + statusKey: string + valueKey: string + checkDynList: (fullSite: boolean) => void + status: boolean + value: string[] + blacklist: WordList + + /** + * 动态区标题关键词过滤操作 + * @param statusKey 是否启用的GM key + * @param valueKey 存储数据的GM key + * @param checkDynList 检测动态列表函数 + */ + constructor(statusKey: string, valueKey: string, checkDynList: (fullSite: boolean) => void) { + this.statusKey = statusKey + this.valueKey = valueKey + this.status = GM_getValue(`BILICLEANER_${this.statusKey}`, false) + this.value = GM_getValue(`BILICLEANER_${this.valueKey}`, []) + this.checkDynList = checkDynList + + // 配置子过滤器 + dynTitleFilterInstance.setStatus(this.status) + dynTitleFilterInstance.setParams(this.value) + + this.blacklist = new WordList( + this.valueKey, + '标题关键词 黑名单', + `每行一个关键词或正则,不区分大小写\n正则无需flag(默认iv模式)语法:/abc|\\d+/`, + (values: string[]) => { + this.edit(values) + }, + ) + } + + enable() { + dynUploaderFilterAgencyInstance.notifyDynTitle('enable') + this.checkDynList(true) + this.status = true + } + disable() { + dynUploaderFilterAgencyInstance.notifyDynTitle('disable') + this.checkDynList(true) + this.status = false + } + add(value: string) { + this.blacklist.addValue(value) + dynUploaderFilterAgencyInstance.notifyDynTitle('add', value) + this.checkDynList(true) + } + edit(values: string[]) { + dynUploaderFilterAgencyInstance.notifyDynTitle('edit', values) + this.checkDynList(true) + } +} + +export class DynDurationAction implements DynFilterAction { + statusKey: string + valueKey: string + checkDynList: (fullSite: boolean) => void + status: boolean + value: number + + /** + * 时长过滤操作 + * @param statusKey 是否启用的GM key + * @param valueKey 存储数据的GM key + * @param checkDynList 检测视频列表函数 + */ + constructor(statusKey: string, valueKey: string, checkDynList: (fullSite: boolean) => void) { + this.statusKey = statusKey + this.valueKey = valueKey + this.checkDynList = checkDynList + this.status = GM_getValue(`BILICLEANER_${this.statusKey}`, false) + this.value = GM_getValue(`BILICLEANER_${this.valueKey}`, 60) + // 配置子过滤器 + dynDurationFilterInstance.setStatus(this.status) + dynDurationFilterInstance.setParams(this.value) + } + enable() { + // 告知agency + dynUploaderFilterAgencyInstance.notifyDynDuration('enable') + // 触发全站过滤 + this.checkDynList(true) + this.status = true + } + disable() { + dynUploaderFilterAgencyInstance.notifyDynDuration('disable') + this.checkDynList(true) + this.status = false + } + change(value: number) { + dynUploaderFilterAgencyInstance.notifyDynDuration('change', value) + this.checkDynList(true) + } +} diff --git a/src/filters/dynFilter/pages/dynamic.ts b/src/filters/dynFilter/pages/dynamic.ts new file mode 100644 index 00000000..904703a2 --- /dev/null +++ b/src/filters/dynFilter/pages/dynamic.ts @@ -0,0 +1,239 @@ +import { Group } from '../../../components/group' +import { CheckboxItem, ButtonItem, NumberItem } from '../../../components/item' +import { debugDynFilter as debug, error } from '../../../utils/logger' +import { isPageDynamic } from '../../../utils/pageType' +import { waitForEle } from '../../../utils/tool' +import { DynDurationAction, DynTitleKeywordAction, DynUploaderAction } from './actions/action' +import coreDynFilterInstance, { DynSelectorFunc } from '../filters/core' +import settings from '../../../settings' +import { ContextMenu } from '../../../components/contextmenu' + +const dynamicPageDynFilterGroupList: Group[] = [] + +// 右键菜单功能 +let isContextMenuFuncRunning = false +let isContextMenuDynUploaderEnable = false + +if (isPageDynamic()) { + let dynListContainer: HTMLElement + + let isAllDyn = true // 是否为全部动态 + const dynSelectorFunc: DynSelectorFunc = { + dynUploader: (dyn: Element): string | null => { + if (!isAllDyn) { + return null + } + const dynUploader = dyn.querySelector('.bili-dyn-title__text')?.textContent?.trim() + return dynUploader ? dynUploader : null + }, + dynDuration: (dyn: Element): string | null => { + const dynDuration = dyn + .querySelector('.bili-dyn-card-video__cover-shadow .duration-time') + ?.textContent?.trim() + return dynDuration ? dynDuration : null + }, + dynTitle: (dyn: Element): string | null => { + const dynTitle = dyn.querySelector('.bili-dyn-card-video__title')?.textContent?.trim() + return dynTitle ? dynTitle : null + }, + } + + // 检测动态列表 + const checkDynList = (fullSite: boolean) => { + if (!dynListContainer) { + debug(`checkDynList dynListContainer not exist`) + return + } + + isAllDyn = !!dynListContainer.querySelector('.bili-dyn-list-tabs') + + try { + let dyns + if (fullSite) { + dyns = dynListContainer.querySelectorAll(`.bili-dyn-list__item`) + } else { + dyns = dynListContainer.querySelectorAll( + `.bili-dyn-list__item:not([${settings.filterSign}])`, + ) + } + dyns.length && coreDynFilterInstance.checkAll([...dyns], true, dynSelectorFunc) + debug(`check ${dyns.length} dyns`) + } catch (err) { + error(err) + error('checkDynList error') + } + } + + // 配置 行为实例 + const dynUploaderAction = new DynUploaderAction( + 'dyn-uploader-filter-status', + 'dyn-uploader-filter-value', + checkDynList, + ) + const dynDurationAction = new DynDurationAction( + 'dyn-duration-filter-status', + 'global-duration-filter-value', + checkDynList, + ) + const dynTitleKeywordAction = new DynTitleKeywordAction( + 'dyn-title-keyword-filter-status', + 'global-title-keyword-filter-value', + checkDynList, + ) + + // 监听动态列表内部变化, 有变化时检测动态列表 + const watchDynListContainer = () => { + const check = async (fullSite: boolean) => { + if (dynUploaderAction.status) { + checkDynList(fullSite) + } + } + if (dynListContainer) { + // 初次全站检测 + check(true).then().catch() + const dynObserver = new MutationObserver(() => { + // 增量检测 + check(false).then().catch() + }) + dynObserver.observe(dynListContainer, { childList: true, subtree: true }) + } + } + + try { + waitForEle( + document, + '.bili-dyn-home--member', + (node: HTMLElement): boolean => node.className === 'bili-dyn-home--member', + ).then((ele) => { + if (ele) { + dynListContainer = ele + watchDynListContainer() + } + }) + } catch (err) { + error(err) + error(`watch dyn list ERROR`) + } + + //======================================================================================= + + // 右键监听函数, 屏蔽动态用户 + const contextMenuFunc = () => { + if (isContextMenuFuncRunning) { + return + } + isContextMenuFuncRunning = true + const menu = new ContextMenu() + document.addEventListener('contextmenu', (e) => { + menu.hide() + if (e.target instanceof HTMLElement) { + const target = e.target + if (isContextMenuDynUploaderEnable && target.classList.contains('bili-dyn-title__text')) { + if (document.querySelector('.bili-dyn-list-tabs')) { + // 命中用户 + const dynUploader = target.textContent?.trim() + if (dynUploader) { + e.preventDefault() + menu.registerMenu(`隐藏用户:${dynUploader}`, () => { + dynUploaderAction.add(dynUploader) + }) + menu.show(e.clientX, e.clientY) + } + } + } else { + menu.hide() + } + } + }) + debug('contextMenuFunc listen contextmenu') + } + + //======================================================================================= + // 构建UI菜单 + + // UI组件, 用户名过滤part + const dynUploaderItems = [ + // 启用 用户名过滤 + new CheckboxItem({ + itemID: dynUploaderAction.statusKey, + description: '启用 用户名过滤 (右键单击用户名)\n仅对“全部动态”列表生效', + enableFunc: async () => { + // 启用右键菜单功能 + isContextMenuDynUploaderEnable = true + contextMenuFunc() + dynUploaderAction.enable() + }, + disableFunc: async () => { + // 禁用右键菜单功能 + isContextMenuDynUploaderEnable = false + dynUploaderAction.disable() + }, + }), + // 编辑 用户名列表 + new ButtonItem({ + itemID: 'dyn-dynUploader-edit-button', + description: '编辑 用户名列表', + name: '编辑', + itemFunc: async () => { + dynUploaderAction.blacklist.show() + }, + }), + ] + dynamicPageDynFilterGroupList.push(new Group('dyn-uploader-filter-group', '动态页 用户过滤', dynUploaderItems)) + + // UI组件, 时长过滤part + const durationItems = [ + // 启用 动态页时长过滤 + new CheckboxItem({ + itemID: dynDurationAction.statusKey, + description: '启用 时长过滤', + enableFunc: async () => { + dynDurationAction.enable() + }, + disableFunc: async () => { + dynDurationAction.disable() + }, + }), + // 设定最低时长 + new NumberItem({ + itemID: dynDurationAction.valueKey, + description: '设定最低时长 (0~300s)', + defaultValue: 60, + minValue: 0, + maxValue: 300, + disableValue: 0, + unit: '秒', + callback: async (value: number) => { + dynDurationAction.change(value) + }, + }), + ] + dynamicPageDynFilterGroupList.push(new Group('dyn-duration-filter-group', '动态页 时长过滤', durationItems)) + // UI组件, 标题关键词过滤part + const titleKeywordItems = [ + // 启用 动态页关键词过滤 + new CheckboxItem({ + itemID: dynTitleKeywordAction.statusKey, + description: '启用 标题关键词过滤', + enableFunc: async () => { + dynTitleKeywordAction.enable() + }, + disableFunc: async () => { + dynTitleKeywordAction.disable() + }, + }), + // 编辑 关键词黑名单 + new ButtonItem({ + itemID: 'dyn-title-keyword-edit-button', + description: '编辑 标题关键词黑名单(支持正则)', + name: '编辑', + // 按钮功能:打开编辑器 + itemFunc: async () => { + dynTitleKeywordAction.blacklist.show() + }, + }), + ] + dynamicPageDynFilterGroupList.push(new Group('dyn-title-filter-group', '动态页 标题关键词过滤', titleKeywordItems)) +} + +export { dynamicPageDynFilterGroupList } diff --git a/src/filters/videoFilter/filters/core.ts b/src/filters/videoFilter/filters/core.ts index ebf6554b..2731c7d0 100644 --- a/src/filters/videoFilter/filters/core.ts +++ b/src/filters/videoFilter/filters/core.ts @@ -158,7 +158,7 @@ class CoreVideoFilter { .catch((_result) => { // 命中黑名单 // debug(_result) - if (whiteTasks) { + if (whiteTasks.length) { Promise.all(whiteTasks) .then((_result) => { // 命中黑名单,未命中白名单 diff --git a/src/filters/videoFilter/pages/channel.ts b/src/filters/videoFilter/pages/channel.ts index 6890adca..a3880600 100644 --- a/src/filters/videoFilter/pages/channel.ts +++ b/src/filters/videoFilter/pages/channel.ts @@ -3,7 +3,7 @@ import { ButtonItem, CheckboxItem, NumberItem } from '../../../components/item' import { Group } from '../../../components/group' import coreFilterInstance, { VideoSelectorFunc } from '../filters/core' import settings from '../../../settings' -import { isPageChannel } from '../../../utils/page-type' +import { isPageChannel } from '../../../utils/pageType' import { matchBvid, waitForEle } from '../../../utils/tool' import { BvidAction, diff --git a/src/filters/videoFilter/pages/homepage.ts b/src/filters/videoFilter/pages/homepage.ts index 5b5a394e..e0234a3e 100644 --- a/src/filters/videoFilter/pages/homepage.ts +++ b/src/filters/videoFilter/pages/homepage.ts @@ -3,7 +3,7 @@ import { ButtonItem, CheckboxItem, NumberItem } from '../../../components/item' import { Group } from '../../../components/group' import coreFilterInstance, { VideoSelectorFunc } from '../filters/core' import settings from '../../../settings' -import { isPageHomepage } from '../../../utils/page-type' +import { isPageHomepage } from '../../../utils/pageType' import { ContextMenu } from '../../../components/contextmenu' import { matchBvid, showEle, waitForEle } from '../../../utils/tool' import { diff --git a/src/filters/videoFilter/pages/popular.ts b/src/filters/videoFilter/pages/popular.ts index 6b616f0d..da10c97f 100644 --- a/src/filters/videoFilter/pages/popular.ts +++ b/src/filters/videoFilter/pages/popular.ts @@ -3,7 +3,7 @@ import coreFilterInstance, { VideoSelectorFunc } from '../filters/core' import { ButtonItem, CheckboxItem, NumberItem } from '../../../components/item' import { Group } from '../../../components/group' import settings from '../../../settings' -import { isPagePopular } from '../../../utils/page-type' +import { isPagePopular } from '../../../utils/pageType' import { ContextMenu } from '../../../components/contextmenu' import { matchBvid, waitForEle } from '../../../utils/tool' import { diff --git a/src/filters/videoFilter/pages/search.ts b/src/filters/videoFilter/pages/search.ts index 2441d078..04f56da2 100644 --- a/src/filters/videoFilter/pages/search.ts +++ b/src/filters/videoFilter/pages/search.ts @@ -2,7 +2,7 @@ import { debugVideoFilter as debug, error } from '../../../utils/logger' import { ButtonItem, CheckboxItem, NumberItem } from '../../../components/item' import { Group } from '../../../components/group' import coreFilterInstance, { VideoSelectorFunc } from '../filters/core' -import { isPageSearch } from '../../../utils/page-type' +import { isPageSearch } from '../../../utils/pageType' import { ContextMenu } from '../../../components/contextmenu' import { matchBvid, showEle, waitForEle } from '../../../utils/tool' import { @@ -14,7 +14,6 @@ import { UploaderKeywordAction, UploaderWhitelistAction, } from './actions/action' -import { GM_getValue } from '$' const searchPageVideoFilterGroupList: Group[] = [] @@ -23,7 +22,7 @@ let isContextMenuFuncRunning = false let isContextMenuUploaderEnable = false let isContextMenuBvidEnable = false // 推荐位UP主视频的不被过滤,默认开启 -let isTopUploaderWhitelistEnable: boolean = GM_getValue('BILICLEANER_search-top-uploader-whitelist-filter-status', true) +let isTopUploaderWhitelistEnable = true if (isPageSearch()) { let videoListContainer: HTMLElement diff --git a/src/filters/videoFilter/pages/space.ts b/src/filters/videoFilter/pages/space.ts index 6857f781..c50b0441 100644 --- a/src/filters/videoFilter/pages/space.ts +++ b/src/filters/videoFilter/pages/space.ts @@ -2,7 +2,7 @@ import { debugVideoFilter as debug, error } from '../../../utils/logger' import { ButtonItem, CheckboxItem, NumberItem } from '../../../components/item' import { Group } from '../../../components/group' import coreFilterInstance, { VideoSelectorFunc } from '../filters/core' -import { isPageSpace } from '../../../utils/page-type' +import { isPageSpace } from '../../../utils/pageType' import { matchBvid, waitForEle } from '../../../utils/tool' import { BvidAction, DurationAction, TitleKeywordAction, TitleKeywordWhitelistAction } from './actions/action' import { ContextMenu } from '../../../components/contextmenu' diff --git a/src/filters/videoFilter/pages/video.ts b/src/filters/videoFilter/pages/video.ts index 6a47d888..6ece0886 100644 --- a/src/filters/videoFilter/pages/video.ts +++ b/src/filters/videoFilter/pages/video.ts @@ -2,7 +2,7 @@ import { debugVideoFilter as debug, error } from '../../../utils/logger' import coreFilterInstance, { VideoSelectorFunc } from '../filters/core' import { ButtonItem, CheckboxItem, NumberItem } from '../../../components/item' import { Group } from '../../../components/group' -import { isPagePlaylist, isPageVideo } from '../../../utils/page-type' +import { isPagePlaylist, isPageVideo } from '../../../utils/pageType' import { hideEle, isEleHide, matchBvid, showEle, waitForEle } from '../../../utils/tool' import { BvidAction, @@ -13,7 +13,6 @@ import { UploaderKeywordAction, UploaderWhitelistAction, } from './actions/action' -import { GM_getValue } from '$' import { ContextMenu } from '../../../components/contextmenu' const videoPageVideoFilterGroupList: Group[] = [] @@ -23,9 +22,9 @@ let isContextMenuFuncRunning = false let isContextMenuUploaderEnable = false let isContextMenuBvidEnable = false // 接下来播放是否免过滤 -let isNextPlayWhitelistEnable: boolean = GM_getValue('BILICLEANER_video-next-play-whitelist-filter-status', true) +let isNextPlayWhitelistEnable = true // 视频结束后播放器内推荐是否免过滤 -let isEndingWhitelistEnable: boolean = GM_getValue('BILICLEANER_video-ending-whitelist-filter-status', true) +let isEndingWhitelistEnable = true if (isPageVideo() || isPagePlaylist()) { let videoListContainer: HTMLElement // 构建SelectorFunc diff --git a/src/main.ts b/src/main.ts index 7fccd6f6..88912c85 100644 --- a/src/main.ts +++ b/src/main.ts @@ -25,7 +25,7 @@ import { isPageSearch, isPageSpace, isPageVideo, -} from './utils/page-type' +} from './utils/pageType' import { homepagePageVideoFilterGroupList } from './filters/videoFilter/pages/homepage' import { videoPageVideoFilterGroupList } from './filters/videoFilter/pages/video' import { popularPageVideoFilterGroupList } from './filters/videoFilter/pages/popular' @@ -36,6 +36,7 @@ import { spacePageVideoFilterGroupList } from './filters/videoFilter/pages/space import { dynamicPageCommentFilterGroupList } from './filters/commentFilter/pages/dynamic' import { watchlaterGroupList } from './rules/watchlater' import { spaceGroupList } from './rules/space' +import { dynamicPageDynFilterGroupList } from './filters/dynFilter/pages/dynamic' const main = async () => { // 载入元素屏蔽规则 @@ -69,6 +70,10 @@ const main = async () => { const COMMENT_FILTER_GROUPS = [...videoPageCommentFilterGroupList, ...dynamicPageCommentFilterGroupList] COMMENT_FILTER_GROUPS.forEach((e) => e.enableGroup()) + // 载入动态过滤器 + const DYN_FILTER_GROUPS = [...dynamicPageDynFilterGroupList] + DYN_FILTER_GROUPS.forEach((e) => e.enableGroup()) + // 全局启动/关闭快捷键 chrome: Alt+B,firefox: Ctrl+Alt+B let isGroupEnable = true document.addEventListener('keydown', (event) => { @@ -121,7 +126,8 @@ const main = async () => { regIDs.splice(0, regIDs.length) } const register = () => { - regIDs.push(GM_registerMenuCommand('✅页面净化设置', () => createPanelWithMode('rule', RULE_GROUPS))) + regIDs.push(GM_registerMenuCommand('✅页面净化优化', () => createPanelWithMode('rule', RULE_GROUPS))) + if ( isPageHomepage() || isPageVideo() || @@ -135,6 +141,13 @@ const main = async () => { GM_registerMenuCommand('✅视频过滤设置', () => createPanelWithMode('videoFilter', VIDEO_FILTER_GROUPS)), ) } + + if (isPageDynamic()) { + regIDs.push( + GM_registerMenuCommand('✅动态过滤设置', () => createPanelWithMode('dynFilter', DYN_FILTER_GROUPS)), + ) + } + if (isPageVideo() || isPageBangumi() || isPagePlaylist() || isPageDynamic()) { regIDs.push( GM_registerMenuCommand('✅评论过滤设置', () => diff --git a/src/rules/bangumi.ts b/src/rules/bangumi.ts index 7aa6f74f..56bbc2e6 100644 --- a/src/rules/bangumi.ts +++ b/src/rules/bangumi.ts @@ -2,7 +2,7 @@ import { GM_getValue, GM_setValue, unsafeWindow } from '$' import { Group } from '../components/group' import { CheckboxItem, NumberItem } from '../components/item' import { error } from '../utils/logger' -import { isPageBangumi } from '../utils/page-type' +import { isPageBangumi } from '../utils/pageType' import { waitForEle } from '../utils/tool' const bangumiGroupList: Group[] = [] @@ -1010,13 +1010,19 @@ if (isPageBangumi()) { itemID: 'video-page-hide-root-reply-dislike-reply-btn', description: '一级评论 踩/回复 只在hover时显示', defaultStatus: true, - itemCSS: `.reply-info:not(:has(i.disliked)) .reply-btn, - .reply-info:not(:has(i.disliked)) .reply-dislike { - visibility: hidden; + itemCSS: ` + .reply-item:not(:has(i.disliked)) :is(.reply-btn, .reply-dislike) { + opacity: 0; + } + @keyframes appear { + 0% {opacity: 0;} + 100% {opacity: 1;} } - .reply-item:hover .reply-info .reply-btn, - .reply-item:hover .reply-info .reply-dislike { - visibility: visible !important; + .reply-item:hover :is(.reply-btn, .reply-dislike) { + animation: appear; + animation-duration: 0.2s; + animation-delay: 0.3s; + animation-fill-mode: forwards; }`, }), // 二级评论 踩/回复 只在hover时显示, 默认开启 @@ -1024,13 +1030,19 @@ if (isPageBangumi()) { itemID: 'video-page-hide-sub-reply-dislike-reply-btn', description: '二级评论 踩/回复 只在hover时显示', defaultStatus: true, - itemCSS: `.sub-reply-container .sub-reply-item:not(:has(i.disliked)) .sub-reply-btn, - .sub-reply-container .sub-reply-item:not(:has(i.disliked)) .sub-reply-dislike { - visibility: hidden; + itemCSS: ` + .sub-reply-item:not(:has(i.disliked)) :is(.sub-reply-btn, .sub-reply-dislike) { + opacity: 0; + } + @keyframes appear { + 0% {opacity: 0;} + 100% {opacity: 1;} } - .sub-reply-container .sub-reply-item:hover .sub-reply-btn, - .sub-reply-container .sub-reply-item:hover .sub-reply-dislike { - visibility: visible !important; + .sub-reply-item:hover :is(.sub-reply-btn, .sub-reply-dislike) { + animation: appear; + animation-duration: 0.2s; + animation-delay: 0.3s; + animation-fill-mode: forwards; }`, }), // 隐藏 大表情 diff --git a/src/rules/channel.ts b/src/rules/channel.ts index eba39033..2546662a 100644 --- a/src/rules/channel.ts +++ b/src/rules/channel.ts @@ -1,6 +1,6 @@ import { Group } from '../components/group' import { CheckboxItem, NumberItem } from '../components/item' -import { isPageChannel } from '../utils/page-type' +import { isPageChannel } from '../utils/pageType' const channelGroupList: Group[] = [] diff --git a/src/rules/common.ts b/src/rules/common.ts index afe0cf47..0cfdcf4f 100644 --- a/src/rules/common.ts +++ b/src/rules/common.ts @@ -11,8 +11,8 @@ import { isPagePopular, isPageSearch, isPageVideo, -} from '../utils/page-type' -import URLCleanerInstance from '../utils/url-cleaner' +} from '../utils/pageType' +import URLCleanerInstance from '../utils/urlCleaner' const commonGroupList: Group[] = [] @@ -635,10 +635,14 @@ if (!isPageLiveHome()) { new CheckboxItem({ itemID: 'common-hide-nav-moveclip', description: '隐藏 活动/活动直播', - itemCSS: `.bili-header__bar li:has(.loc-mc-box) { + itemCSS: ` + .bili-header__bar li:has(.loc-mc-box) { + display: none !important; + } + .bili-header__bar .left-entry li.left-loc-entry:not(:has(.v-popover)):has([href^="https://live.bilibili.com/"]) { display: none !important; } - .bili-header__bar .left-entry li:not(:has(.v-popover)):has([href^="https://live.bilibili.com/"]) { + .bili-header__bar .left-entry li:not(:has(.v-popover)):has([href^="https://www.bilibili.com/bangumi/play/ss"]) { display: none !important; } /* 旧版header */ @@ -681,7 +685,11 @@ if (!isPageLiveHome()) { new CheckboxItem({ itemID: 'common-hide-nav-blackboard', description: '隐藏 所有官方活动(强制)', - itemCSS: `.bili-header__bar .left-entry li:has(>a[href*="bilibili.com/blackboard"]) { + itemCSS: ` + .bili-header__bar .left-entry .v-popover-wrap.left-loc-entry { + display: none !important; + } + .bili-header__bar .left-entry li:has(>a[href*="bilibili.com/blackboard"]) { display: none !important; } .bili-header__bar .left-entry li:has(>div>a[href*="bilibili.com/blackboard"]) { @@ -703,6 +711,17 @@ if (!isPageLiveHome()) { // 顶栏中间 const headerCenterItems = [ + // 修复 搜索框居中 + new CheckboxItem({ + itemID: 'common-nav-search-middle-justify', + description: '修复 搜索框居中', + itemCSS: ` + @media (min-width: 1100px) and (max-width: 1366.9px) { + .mini-header .center-search-container .center-search__bar { + margin: 0 auto; + } + }`, + }), // 隐藏 推荐搜索 new CheckboxItem({ itemID: 'common-hide-nav-search-rcmd', @@ -936,6 +955,11 @@ if (!isPageLiveHome()) { width: ???px !important; max-width: ???px !important; min-width: 0px !important; + } + @media (min-width: 1100px) and (max-width: 1366.9px) { + .mini-header .center-search-container .center-search__bar { + margin: 0 auto; + } }`, itemCSSPlaceholder: '???', }), diff --git a/src/rules/dynamic.ts b/src/rules/dynamic.ts index 10809583..1a8e6760 100644 --- a/src/rules/dynamic.ts +++ b/src/rules/dynamic.ts @@ -1,6 +1,6 @@ import { Group } from '../components/group' import { CheckboxItem } from '../components/item' -import { isPageDynamic } from '../utils/page-type' +import { isPageDynamic } from '../utils/pageType' import fontFaceRegular from './styles/fontFaceRegular.scss?inline' import fontFaceMedium from './styles/fontFaceMedium.scss?inline' @@ -410,7 +410,7 @@ if (isPageDynamic()) { itemID: 'video-page-hide-reply-box-textarea-placeholder', description: '隐藏 评论编辑器内占位文字', defaultStatus: true, - itemCSS: `.reply-box-textarea::placeholder {color: transparent !important;}`, + itemCSS: `:not(.reply-item) .reply-box-textarea::placeholder {color: transparent !important;}`, }), // 隐藏 评论区用户卡片 new CheckboxItem({ @@ -624,13 +624,19 @@ if (isPageDynamic()) { itemID: 'video-page-hide-root-reply-dislike-reply-btn', description: '一级评论 踩/回复 只在hover时显示', defaultStatus: true, - itemCSS: `.reply-info:not(:has(i.disliked)) .reply-btn, - .comment-container .reply-info:not(:has(i.disliked)) .reply-dislike { - visibility: hidden; + itemCSS: ` + .reply-item:not(:has(i.disliked)) :is(.reply-btn, .reply-dislike) { + opacity: 0; + } + @keyframes appear { + 0% {opacity: 0;} + 100% {opacity: 1;} } - .comment-container .reply-item:hover .reply-btn, - .comment-container .reply-item:hover .reply-dislike { - visibility: visible !important; + .reply-item:hover :is(.reply-btn, .reply-dislike) { + animation: appear; + animation-duration: 0.2s; + animation-delay: 0.3s; + animation-fill-mode: forwards; }`, }), // 二级评论 踩/回复 只在hover时显示, 默认开启 @@ -638,13 +644,19 @@ if (isPageDynamic()) { itemID: 'video-page-hide-sub-reply-dislike-reply-btn', description: '二级评论 踩/回复 只在hover时显示', defaultStatus: true, - itemCSS: `.sub-reply-item:not(:has(i.disliked)) .sub-reply-btn, - .comment-container .sub-reply-item:not(:has(i.disliked)) .sub-reply-dislike { - visibility: hidden; + itemCSS: ` + .sub-reply-item:not(:has(i.disliked)) :is(.sub-reply-btn, .sub-reply-dislike) { + opacity: 0; + } + @keyframes appear { + 0% {opacity: 0;} + 100% {opacity: 1;} } - .comment-container .sub-reply-item:hover .sub-reply-btn, - .comment-container .sub-reply-item:hover .sub-reply-dislike { - visibility: visible !important; + .sub-reply-item:hover :is(.sub-reply-btn, .sub-reply-dislike) { + animation: appear; + animation-duration: 0.2s; + animation-delay: 0.3s; + animation-fill-mode: forwards; }`, }), // 隐藏 大表情 diff --git a/src/rules/homepage.ts b/src/rules/homepage.ts index 751d75b6..f43cc62f 100644 --- a/src/rules/homepage.ts +++ b/src/rules/homepage.ts @@ -1,7 +1,7 @@ import { unsafeWindow } from '$' import { Group } from '../components/group' import { CheckboxItem, NumberItem, RadioItem } from '../components/item' -import { isPageHomepage } from '../utils/page-type' +import { isPageHomepage } from '../utils/pageType' import { debounce, waitForEle } from '../utils/tool' const homepageGroupList: Group[] = [] diff --git a/src/rules/live.ts b/src/rules/live.ts index 6e91bc69..5888308b 100644 --- a/src/rules/live.ts +++ b/src/rules/live.ts @@ -1,7 +1,7 @@ import { Group } from '../components/group' import { CheckboxItem } from '../components/item' import { debugRules as debug } from '../utils/logger' -import { isPageLiveHome, isPageLiveRoom } from '../utils/page-type' +import { isPageLiveHome, isPageLiveRoom } from '../utils/pageType' import fontFaceRegular from './styles/fontFaceRegular.scss?inline' let isCleanLiveDanmakuRunning = false @@ -62,11 +62,12 @@ if (isPageLiveRoom()) { defaultStatus: true, itemCSS: `#sidebar-vm {display: none !important;}`, }), - // 播放器皮肤 恢复默认配色 + // 禁用 播放器皮肤 new CheckboxItem({ itemID: 'live-page-default-skin', - description: '播放器皮肤 恢复默认配色', - itemCSS: `#head-info-vm { + description: '禁用 播放器皮肤', + itemCSS: ` + #head-info-vm { background-image: unset !important; /* color不加important, 适配Evolved黑暗模式 */ background-color: white; @@ -90,6 +91,17 @@ if (isPageLiveRoom()) { background-image: unset !important; } /* 右侧弹幕框背景 */ + #rank-list-vm, #rank-list-ctnr-box { + background-image: unset !important; + background-color: #efefef; + } + #rank-list-ctnr-box *:not(.fans-medal-content), + #rank-list-ctnr-box .tabs .pilot .hasOne .text-style, + #rank-list-ctnr-box .tabs .pilot .hasNot .text-style, + #rank-list-ctnr-box .live-skin-coloration-area .live-skin-main-text, + #rank-list-ctnr-box .guard-skin .nameBox a { + color: black !important; + } #chat-control-panel-vm .live-skin-coloration-area .live-skin-main-text { color: #C9CCD0 !important; fill: #C9CCD0 !important; @@ -107,7 +119,23 @@ if (isPageLiveRoom()) { #chat-control-panel-vm .icon-left-part>div:hover svg>path { fill: #00AEEC; } - `, + `, + }), + // 禁用 直播背景 + new CheckboxItem({ + itemID: 'live-page-remove-wallpaper', + description: '禁用 直播背景', + itemCSS: ` + .room-bg { + background-image: unset !important; + } + #player-ctnr { + box-shadow: 0 0 12px rgba(0, 0, 0, 0.2); + border-radius: 12px; + } + #aside-area-vm { + box-shadow: 0 0 12px rgba(0, 0, 0, 0.2); + }`, }), // 修复字体 new CheckboxItem({ @@ -129,7 +157,7 @@ if (isPageLiveRoom()) { itemID: 'activity-live-auto-jump', description: '活动直播页 自动跳转普通直播 (实验功能)', enableFunc: async () => { - if (document.querySelector('#internationalHeader')) { + if (document.querySelector('.rendererRoot')) { if (!location.href.includes('/blanc/')) { window.location.href = location.href.replace('live.bilibili.com/', 'live.bilibili.com/blanc/') } @@ -186,7 +214,8 @@ if (isPageLiveRoom()) { description: '隐藏 分享', defaultStatus: true, itemCSS: ` - #head-info-vm .upper-row .right-ctnr div:has(.icon-share, [src*="img/share"]) {display: none !important;}`, + #head-info-vm .upper-row .right-ctnr div:has(.icon-share, [src*="img/share"]) {display: none !important;} + #head-info-vm .header-info-ctnr .rows-ctnr .upper-row .more {display: none !important;}`, }), // 隐藏 人气榜, 默认开启 new CheckboxItem({ @@ -211,7 +240,7 @@ if (isPageLiveRoom()) { // 隐藏 全部直播信息栏 new CheckboxItem({ itemID: 'live-page-head-info-vm', - description: '隐藏 关闭整个信息栏', + description: '隐藏 整个信息栏', itemCSS: `#head-info-vm {display: none !important;} /* 补齐圆角, 不可important */ #player-ctnr { @@ -337,10 +366,40 @@ if (isPageLiveRoom()) { // 右栏 弹幕列表 const rightContainerItems = [ - // 隐藏 高能榜/大航海 + // 折叠 排行榜/大航海 + new CheckboxItem({ + itemID: 'live-page-rank-list-vm-fold', + description: '折叠 排行榜/大航海', + // calc中强调单位,var变量必须添加单位,否则fallback + itemCSS: ` + #rank-list-vm { + max-height: 32px; + transition: max-height 0.3s linear; + overflow: hidden; + } + .player-full-win #rank-list-vm { + border-radius: 0; + } + #rank-list-vm:hover { + max-height: 178px; + overflow: unset; + } + .chat-history-panel { + --rank-list-height: 32px; + height: calc(100% - var(--rank-list-height, 178px) - var(--chat-control-panel-height, 145px)) !important; + } + #chat-control-panel-vm { + height: var(--chat-control-panel-height, 145px) !important; + } + #aside-area-vm { + overflow: hidden; + } + `, + }), + // 隐藏 排行榜/大航海 new CheckboxItem({ itemID: 'live-page-rank-list-vm', - description: '隐藏 高能榜/大航海', + description: '隐藏 排行榜/大航海', // calc中强调单位,var变量必须添加单位,否则fallback itemCSS: ` #rank-list-vm { diff --git a/src/rules/popular.ts b/src/rules/popular.ts index 582cbd35..6fd5eb34 100644 --- a/src/rules/popular.ts +++ b/src/rules/popular.ts @@ -1,6 +1,6 @@ import { Group } from '../components/group' import { CheckboxItem, RadioItem } from '../components/item' -import { isPagePopular } from '../utils/page-type' +import { isPagePopular } from '../utils/pageType' import fontFaceRegular from './styles/fontFaceRegular.scss?inline' import fontFaceMedium from './styles/fontFaceMedium.scss?inline' diff --git a/src/rules/search.ts b/src/rules/search.ts index ef5bd111..657d904c 100644 --- a/src/rules/search.ts +++ b/src/rules/search.ts @@ -1,6 +1,6 @@ import { Group } from '../components/group' import { CheckboxItem } from '../components/item' -import { isPageSearch } from '../utils/page-type' +import { isPageSearch } from '../utils/pageType' const searchGroupList: Group[] = [] diff --git a/src/rules/space.ts b/src/rules/space.ts index e9bebcea..e3d58ffc 100644 --- a/src/rules/space.ts +++ b/src/rules/space.ts @@ -1,6 +1,6 @@ import { Group } from '../components/group' import { CheckboxItem } from '../components/item' -import { isPageSpace } from '../utils/page-type' +import { isPageSpace } from '../utils/pageType' import fontFaceRegular from './styles/fontFaceRegular.scss?inline' const spaceGroupList: Group[] = [] diff --git a/src/rules/video.ts b/src/rules/video.ts index 8da4d2bf..581f78fb 100644 --- a/src/rules/video.ts +++ b/src/rules/video.ts @@ -2,9 +2,9 @@ import { Group } from '../components/group' import { CheckboxItem, NumberItem } from '../components/item' import { debugRules as debug, error } from '../utils/logger' import { matchAvidBvid, matchBvid, waitForEle } from '../utils/tool' -import { isPageFestival, isPagePlaylist, isPageVideo } from '../utils/page-type' +import { isPageFestival, isPagePlaylist, isPageVideo } from '../utils/pageType' import { GM_getValue, GM_setValue, unsafeWindow } from '$' -import URLCleanerInstance from '../utils/url-cleaner' +import URLCleanerInstance from '../utils/urlCleaner' /** 宽屏模式监听 */ let _isWide = unsafeWindow.isWide @@ -1352,7 +1352,7 @@ if (isPageVideo() || isPagePlaylist()) { // 优化 右栏底部吸附 实验功能 new CheckboxItem({ itemID: 'video-page-right-container-sticky-optimize', - description: '优化 右栏底部吸附 (实验功能)', + description: '优化 右栏底部吸附 (实验功能)\n搭配“全屏时页面可滚动”使用', itemCSS: ` /* 修复右栏底部吸附计算top时位置跳变 */ .video-container-v1 .right-container { @@ -1830,13 +1830,19 @@ if (isPageVideo() || isPagePlaylist()) { itemID: 'video-page-hide-root-reply-dislike-reply-btn', description: '一级评论 踩/回复 只在hover时显示', defaultStatus: true, - itemCSS: `.reply-info:not(:has(i.disliked)) .reply-btn, - .comment-container .reply-info:not(:has(i.disliked)) .reply-dislike { - visibility: hidden; - } - .comment-container .reply-item:hover .reply-btn, - .comment-container .reply-item:hover .reply-dislike { - visibility: visible !important; + itemCSS: ` + .reply-item:not(:has(i.disliked)) :is(.reply-btn, .reply-dislike) { + opacity: 0; + } + @keyframes appear { + 0% {opacity: 0;} + 100% {opacity: 1;} + } + .reply-item:hover :is(.reply-btn, .reply-dislike) { + animation: appear; + animation-duration: 0.2s; + animation-delay: 0.3s; + animation-fill-mode: forwards; }`, }), // 二级评论 踩/回复 只在hover时显示, 默认开启 @@ -1844,13 +1850,19 @@ if (isPageVideo() || isPagePlaylist()) { itemID: 'video-page-hide-sub-reply-dislike-reply-btn', description: '二级评论 踩/回复 只在hover时显示', defaultStatus: true, - itemCSS: `.sub-reply-item:not(:has(i.disliked)) .sub-reply-btn, - .comment-container .sub-reply-item:not(:has(i.disliked)) .sub-reply-dislike { - visibility: hidden; - } - .comment-container .sub-reply-item:hover .sub-reply-btn, - .comment-container .sub-reply-item:hover .sub-reply-dislike { - visibility: visible !important; + itemCSS: ` + .sub-reply-item:not(:has(i.disliked)) :is(.sub-reply-btn, .sub-reply-dislike) { + opacity: 0; + } + @keyframes appear { + 0% {opacity: 0;} + 100% {opacity: 1;} + } + .sub-reply-item:hover :is(.sub-reply-btn, .sub-reply-dislike) { + animation: appear; + animation-duration: 0.2s; + animation-delay: 0.3s; + animation-fill-mode: forwards; }`, }), // 隐藏 大表情 diff --git a/src/rules/watchlater.ts b/src/rules/watchlater.ts index 91ee7f42..75d6e5eb 100644 --- a/src/rules/watchlater.ts +++ b/src/rules/watchlater.ts @@ -1,6 +1,6 @@ import { Group } from '../components/group' import { CheckboxItem } from '../components/item' -import { isPageWatchlater } from '../utils/page-type' +import { isPageWatchlater } from '../utils/pageType' import fontFaceRegular from './styles/fontFaceRegular.scss?inline' import fontFaceMedium from './styles/fontFaceMedium.scss?inline' diff --git a/src/settings.ts b/src/settings.ts index e566e6f4..ca1e66ca 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -4,6 +4,7 @@ export default { enableDebugRules: false, enableDebugVideoFilter: false, enableDebugCommentFilter: false, + enableDebugDynFilter: false, // 标记视频过滤器检测过的视频 filterSign: 'bili-cleaner-filtered', } diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 977af26f..011491e7 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -34,3 +34,4 @@ export const debugComponents = wrapper(console.log, settings.enableDebugComponen export const debugRules = wrapper(console.log, settings.enableDebugRules) export const debugVideoFilter = wrapper(console.log, settings.enableDebugVideoFilter) export const debugCommentFilter = wrapper(console.log, settings.enableDebugCommentFilter) +export const debugDynFilter = wrapper(console.log, settings.enableDebugDynFilter) diff --git a/src/utils/page-type.ts b/src/utils/pageType.ts similarity index 100% rename from src/utils/page-type.ts rename to src/utils/pageType.ts diff --git a/src/utils/url-cleaner.ts b/src/utils/urlCleaner.ts similarity index 100% rename from src/utils/url-cleaner.ts rename to src/utils/urlCleaner.ts diff --git a/vite.config.ts b/vite.config.ts index 99b72353..e54352c8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ userscript: { name: 'bilibili 页面净化大师', namespace: 'http://tampermonkey.net/', - version: '3.7.4', + version: '3.8.0', description: '净化 B站/哔哩哔哩 页面,支持「精简功能、播放器净化、过滤视频、过滤评论、全站黑白名单」,提供 300+ 功能,定制自己的 B 站', author: 'festoney8',