// ==UserScript== // @name 哔哩哔哩屏蔽增强器 // @namespace http://tampermonkey.net/ // @license Apache-2.0 // @version 2.9 // @author byhgz // @description 对B站视频或评论进行屏蔽,支持关键词模糊正则等,支持时长播放弹幕过滤等,如视频、评论、动态、直播间的评论等,详情可看下面支持的屏蔽类型 // @icon https://static.hdslb.com/images/favicon.ico // @noframes // @run-at document-start // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addStyle // @grant GM_unregisterMenuCommand // @grant GM_registerMenuCommand // @grant GM_openInTab // @grant GM_xmlhttpRequest // @exclude *://message.bilibili.com/pages/nav/header_sync // @exclude *://message.bilibili.com/pages/nav/index_new_pc_sync // @exclude *://live.bilibili.com/blackboard/dropdown-menu.html // @exclude *://live.bilibili.com/p/html/live-web-mng/* // @exclude *://www.bilibili.com/correspond/* // @match *://search.bilibili.com/* // @match *://t.bilibili.com/* // @match *://space.bilibili.com/* // @match *://live.bilibili.com/* // @match *://www.bilibili.com/* // @require https://cdn.jsdelivr.net/npm/vue@2 // @require https://unpkg.com/element-ui/lib/index.js // @require https://cdn.jsdelivr.net/npm/dexie@4.0.10/dist/dexie.min.js // @require https://scriptcat.org/lib/2697/0.5/gz_ui_css-v1.js // @source https://gitee.com/hangexi/BiBiBSPUserVideoMonkeyScript // homepage https://scriptcat.org/zh-CN/script-show-page/1029 // ==/UserScript== "use strict"; (function (Vue, Dexie) { 'use strict'; var gmUtil = { setData(key, content) { GM_setValue(key, content); }, getData(key, defaultValue) { return GM_getValue(key, defaultValue); }, delData(key) { if (!this.isData(key)) { return false; } GM_deleteValue(key); return true; }, isData(key) { return this.getData(key) !== undefined; }, addStyle(style) { GM_addStyle(style); }, addGMMenu(text, func, shortcutKey = null) { return GM_registerMenuCommand(text, func, shortcutKey); }, openInTab(url, options = {active: true, insert: true, setParent: true}) { GM_openInTab(url, options); } }; class EventEmitter { #regularEvents = { events: {}, futures: {} } #callbackEvents = { events: {}, callbackInterval: 1500 } on(eventName, callback) { const events = this.#regularEvents.events; if (events[eventName]) { events[eventName].push(callback); return } events[eventName] = []; events[eventName].push(callback); const futureEvents = this.#regularEvents.futures; if (futureEvents[eventName]) { for (const futureEvent of futureEvents[eventName]) { callback(...futureEvent); } delete futureEvents[eventName]; } } once(eventName, callback) { const onceCallback = (...args) => { callback(...args); this.#removeCallback(eventName, onceCallback); }; this.on(eventName, onceCallback); } handler(eventName, callback) { const handlerEvents = this.#callbackEvents.events; if (handlerEvents[eventName]) { throw new Error('该事件名已经存在,请更换事件名') } handlerEvents[eventName] = callback; } invoke(eventName, ...data) { return new Promise(resolve => { const handlerEvents = this.#callbackEvents.events; if (handlerEvents[eventName]) { resolve(handlerEvents[eventName](...data)); return } const i1 = setInterval(() => { if (handlerEvents[eventName]) { clearInterval(i1); resolve(handlerEvents[eventName](...data)); } }, this.#callbackEvents.callbackInterval); }) } send(eventName, ...data) { const ordinaryEvents = this.#regularEvents; const events = ordinaryEvents.events; const event = events[eventName]; if (event) { for (const callback of event) { callback(...data); } return; } const futures = ordinaryEvents.futures; if (futures[eventName]) { futures[eventName].push(data); return; } futures[eventName] = []; futures[eventName].push(data); } #removeCallback(eventName, callback) { const events = this.#regularEvents.events; if (events[eventName]) { events[eventName] = events[eventName].filter(cb => cb !== callback); } const handlerEvents = this.#callbackEvents.events; if (handlerEvents[eventName]) { handlerEvents[eventName] = handlerEvents[eventName].filter(cb => cb !== callback); } } off(eventName) { const events = this.#regularEvents.events; if (events[eventName]) { delete events[eventName]; return true } const handlerEvents = this.#callbackEvents.events; if (handlerEvents[eventName]) { delete handlerEvents[eventName]; return true } return false } setInvokeInterval(interval) { this.#callbackEvents.callbackInterval = interval; } getEvents() { return { regularEvents: this.#regularEvents, callbackEvents: this.#callbackEvents } } } const eventEmitter = new EventEmitter(); const setBorderColor = (color) => { gmUtil.setData("borderColor", color); }; const defBorderColor = "rgb(0, 243, 255)"; const getBorderColor = () => { return gmUtil.getData("borderColor", defBorderColor) }; const setOutputInformationFontColor = (color) => { gmUtil.setData("output_information_font_color", color); }; const defOutputInformationFontColor = "rgb(119,128,248)"; const getOutputInformationFontColor = () => { return gmUtil.getData("output_information_font_color", defOutputInformationFontColor) }; const setHighlightInformationColor = (color) => { gmUtil.setData("highlight_information_color", color); }; const defHighlightInformationColor = "rgb(234, 93, 93)"; const getHighlightInformationColor = () => { return gmUtil.getData("highlight_information_color", defHighlightInformationColor); }; const setDefaultColorInfo = () => { setBorderColor(defBorderColor); setOutputInformationFontColor(defOutputInformationFontColor); setHighlightInformationColor(defHighlightInformationColor); }; const getBOnlyTheHomepageIsBlocked = () => { return gmUtil.getData("bOnlyTheHomepageIsBlocked", false); }; const getAdaptationBAppCommerce = () => { return gmUtil.getData("adaptation-b-app-recommend", false) === true; }; const isShowRightTopMainButSwitch = () => { return gmUtil.getData("showRightTopMainButSwitch", true) === true; }; const isFirstFullDisplay = () => { return gmUtil.getData('isFirstFullDisplay', true) === true }; const isHalfHiddenIntervalAfterInitialDisplay = () => { return gmUtil.getData('is_half_hidden_interval_after_initial_display', true) === true }; const isCompatible_BEWLY_BEWLY = () => { return gmUtil.getData("compatible_BEWLY_BEWLY", false) === true; }; const isDiscardOldCommentAreas = () => { return gmUtil.getData("discardOldCommentAreas", false) === true; }; const isDelPlayerPageRightVideoList = () => { return gmUtil.getData("isDelPlayerPageRightVideoList", false) === true }; const bFuzzyAndRegularMatchingWordsToLowercase$1 = () => { return gmUtil.getData("bFuzzyAndRegularMatchingWordsToLowercase", false) }; const isRequestFrequencyVal = () => { return gmUtil.getData("requestFrequencyVal", 0.2) }; const isDisableNetRequestsBvVideoInfo = () => { return gmUtil.getData('isDisableNetRequestsBvVideoInfo', false) }; const isBlockFollowed = () => { return gmUtil.getData('blockFollowed', false) }; const isUpOwnerExclusive = () => { return gmUtil.getData('is_up_owner_exclusive', false) }; const isGenderRadioVal = () => { return gmUtil.getData('genderRadioVal', '不处理'); }; const isVipTypeRadioVal = () => { return gmUtil.getData('vipTypeRadioVal', '不处理'); }; const isSeniorMember = () => { return gmUtil.getData('is_senior_member', false) }; const isCopyrightRadio = () => { return gmUtil.getData('copyrightRadioVal', '不处理'); }; const isDelBottomComment = () => { return gmUtil.getData('isDelBottomComment', false) }; const isBlockVerticalVideo = () => { return gmUtil.getData('blockVerticalVideo', false) }; const isCheckTeamMember = () => { return gmUtil.getData('checkTeamMember', false) }; const getVideoLikeRate = () => { return gmUtil.getData('video_like_rate', 0.05) }; const isVideoLikeRateBlockingStatus = () => { return gmUtil.getData('video_like_rate_blocking_status', false) }; const isCoinLikesRatioRateBlockingStatus = () => { return gmUtil.getData('coin_likes_ratio_rate_blocking_status', false) }; const getCoinLikesRatioRate = () => { return gmUtil.getData('coin_likes_ratio_rate', 0.05) }; const isCoinLikesRatioRateDisabled = () => { return gmUtil.getData('coin_likes_ratio_rate_blocking_status', false) }; const isInteractiveRateBlockingStatus = () => { return gmUtil.getData('interactive_rate_blocking_status', false) }; const getInteractiveRate = () => { return gmUtil.getData('interactive_rate', 0.05) }; const isTripleRateBlockingStatus = () => { return gmUtil.getData('triple_rate_blocking_status', false) }; const getTripleRate = () => { return gmUtil.getData('triple_rate', 0.05) }; const getUidRangeMasking = () => { return gmUtil.getData('uid_range_masking', [0, 100]) }; const isUidRangeMaskingStatus = () => { return gmUtil.getData('uid_range_masking_status', false) }; const isTimeRangeMaskingStatus = () => { return gmUtil.getData('time_range_masking_status', false) }; const getTimeRangeMaskingArr = () => { return gmUtil.getData('time_range_masking', []) }; const isDelPlayerEndingPanel = () => { return gmUtil.getData('is_del_player_ending_panel', false) }; const isOpenDev = () => { return gmUtil.getData('open-dev', false) }; const setOpenDev = (bool) => { gmUtil.setData('open-dev', bool); }; const getCommentWordLimitVal = () => { return gmUtil.getData('comment_word_limit', -1) }; const getSubstituteWordsArr = () => { return gmUtil.getData('substitute_words', []) }; const isClearCommentEmoticons = () => { return gmUtil.getData('is_clear_comment_emoticons', false) }; const isReplaceCommentSearchTerms = () => { return gmUtil.getData('is_replace_comment_search_terms', false) }; const enableReplacementProcessing = () => { return gmUtil.getData('enable_replacement_processing', false) }; var localMKData = { getTripleRate, isTripleRateBlockingStatus, setBorderColor, getBorderColor, setOutputInformationFontColor, getOutputInformationFontColor, setHighlightInformationColor, getHighlightInformationColor, getBOnlyTheHomepageIsBlocked, getAdaptationBAppCommerce, setDefaultColorInfo, isCompatible_BEWLY_BEWLY, isDiscardOldCommentAreas, isShowRightTopMainButSwitch, isFirstFullDisplay, isHalfHiddenIntervalAfterInitialDisplay, isDelPlayerPageRightVideoList, bFuzzyAndRegularMatchingWordsToLowercase: bFuzzyAndRegularMatchingWordsToLowercase$1, isRequestFrequencyVal, isDisableNetRequestsBvVideoInfo, isBlockFollowed, isUpOwnerExclusive, isGenderRadioVal, isVipTypeRadioVal, isSeniorMember, isCopyrightRadio, isDelBottomComment, isBlockVerticalVideo, isCheckTeamMember, getVideoLikeRate, isVideoLikeRateBlockingStatus, isCoinLikesRatioRateBlockingStatus, getCoinLikesRatioRate, isCoinLikesRatioRateDisabled, isInteractiveRateBlockingStatus, getInteractiveRate, getUidRangeMasking, isUidRangeMaskingStatus, isTimeRangeMaskingStatus, isDelPlayerEndingPanel, getTimeRangeMaskingArr, getCommentWordLimitVal }; const group_url = 'http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=tFU0xLt1uO5u5CXI2ktQRLh_XGAHBl7C&authKey=KAf4rICQYjfYUi66WelJAGhYtbJLILVWumOm%2BO9nM5fNaaVuF9Iiw3dJoPsVRUak&noverify=0&group_code=876295632'; const scriptCat_js_url = 'https://scriptcat.org/zh-CN/script-show-page/1029'; const b_url = 'https://space.bilibili.com/473239155'; const common_question_url = 'https://docs.qq.com/doc/DSlJNR1NVcGR3eEto'; const update_log_url = 'https://docs.qq.com/doc/DSnhjSVZmRkpCd0Nj'; const adaptationBAppCommerce = localMKData.getAdaptationBAppCommerce(); const compatibleBEWLYBEWLY = localMKData.isCompatible_BEWLY_BEWLY(); const returnTempVal = {state: false}; var globalValue = { group_url, scriptCat_js_url, b_url, common_question_url, update_log_url, adaptationBAppCommerce, compatibleBEWLYBEWLY }; gmUtil.addGMMenu('主面板', () => { eventEmitter.send('主面板开关'); }, 'Q'); gmUtil.addGMMenu('脚本猫脚本更新页', () => { gmUtil.openInTab(globalValue.scriptCat_js_url); }, 'E'); gmUtil.addGMMenu('gf脚本更新页', () => { gmUtil.openInTab('https://greasyfork.org/zh-CN/scripts/461382'); }, 'W'); gmUtil.addGMMenu('加入or反馈', () => { gmUtil.openInTab(globalValue.group_url); }, "T"); gmUtil.addGMMenu('常见问题', () => { gmUtil.openInTab(globalValue.common_question_url); }, 'Y'); gmUtil.addGMMenu('更新日志', () => { gmUtil.openInTab(globalValue.update_log_url); }, 'U'); const start = () => { let loop = false; let msg; if (!Vue) { loop = true; msg = 'Vue is not defined,Vue未定义,请检查是否引入了Vue'; } if (!Dexie) { loop = true; msg = 'Dexie is not defined,Dexie未定义,请检查是否引入了Dexie'; } if (loop) { if (confirm('外部库验证失败:' + msg + `\n请联系作者核查问题\n可通过点击确定按钮跳转。 \n脚本主页信息中,有相关解决文档 \n或通过脚本信息底下联系方式联系作者解决`)) { gmUtil.openInTab(globalValue.scriptCat_js_url); gmUtil.openInTab(globalValue.group_url); } throw new Error(`外部库验证失败:${msg}`) } }; start(); const panel_settings_vue = { template: ` <div> <el-card shadow="never"> <template #header> <span>颜色设置</span> </template> <div class="el-horizontal-center"> 选择器 <el-color-picker v-model="input_color"/> </div> <el-button @click="setBorderColorBut">设置边框色</el-button> <el-button @click="setDefFontColorForOutputInformationBut">设置输出信息默认字体色</el-button> <el-button @click="setTheFontColorForOutputInformationBut">设置输出信息高亮字体色</el-button> <el-tooltip content="刷新页面生效"> <el-button @click="setDefInfoBut">恢复默认</el-button> </el-tooltip> </el-card> <el-card shadow="never"> <template #header> <span>页面右侧悬浮按钮设置</span> </template> <el-switch v-model="showRightTopMainButSwitch" active-text="显示按钮"></el-switch> <el-tooltip content="页面每次加载完之后是否完整展示按钮,否则半隐藏"> <el-switch v-model="isFirstFullDisplay" active-text="初次完整显示"></el-switch> </el-tooltip> <el-tooltip content="页面每次加载完之后如完整展示按钮时,间隔10秒后半隐藏处理"> <el-switch v-model="isHalfHiddenIntervalAfterInitialDisplay" active-text="初次显示后间隔半隐藏"/> </el-tooltip> </el-card> <el-card shadow="never"> <template #header> <span>说明</span> </template> <div>按键盘tab键上的~键为展开关闭主面板</div> </el-card> <el-card shadow="never"> <template #header> <span>devTools</span> </template> <el-input v-model.trim="devToolsInputVal" @keyup.enter.native="changeDevToolsInput"></el-input> </el-card> </div>`, data() { return { input_color: null, showRightTopMainButSwitch: localMKData.isShowRightTopMainButSwitch(), isFirstFullDisplay: localMKData.isFirstFullDisplay(), isHalfHiddenIntervalAfterInitialDisplay: localMKData.isHalfHiddenIntervalAfterInitialDisplay(), devToolsInputVal: '' } }, methods: { setBorderColorBut() { this.$confirm('是否设置面板边框颜色', '提示').then(() => { localMKData.setBorderColor(this.input_color); this.$alert("已设置面板边框颜色,刷新生效"); }); }, setDefFontColorForOutputInformationBut() { this.$confirm("是否设置输出信息默认字体颜色", "提示").then(() => { localMKData.setOutputInformationFontColor(this.input_color); this.$alert("已设置输出信息默认字体颜色,刷新生效"); }); }, setTheFontColorForOutputInformationBut() { this.$confirm('是要设置输出信息高亮字体颜色吗?').then(() => { localMKData.setHighlightInformationColor(this.input_color); this.$alert("已设置输出信息高亮字体颜色,刷新生效"); }); }, setDefInfoBut() { localMKData.setDefaultColorInfo(); this.$alert("已恢复默认颜色,刷新生效"); }, changeDevToolsInput() { if (this.devToolsInputVal === 'show-dev') { setOpenDev(true); eventEmitter.send('debugger-dev-show', true); this.devToolsInputVal = ''; } if (this.devToolsInputVal === 'stop-dev') { setOpenDev(false); eventEmitter.send('debugger-dev-show', false); this.devToolsInputVal = ''; } } }, watch: { showRightTopMainButSwitch(newVal) { gmUtil.setData("showRightTopMainButSwitch", newVal === true); eventEmitter.send('显隐主面板开关', newVal); }, isFirstFullDisplay(newVal) { gmUtil.setData('isFirstFullDisplay', newVal === true); }, isHalfHiddenIntervalAfterInitialDisplay(newBool) { gmUtil.setData('is_half_hidden_interval_after_initial_display', newBool === true); } } }; const getSelectOptions = () => { const options = [ { value: '模糊匹配', label: '模糊匹配', children: [] }, { value: '正则匹配', label: '正则匹配', children: [] }, { value: '多重匹配', label: '多重匹配', children: [] }, { value: '精确匹配', label: '精确匹配', children: [] } ]; for (let {name, key} of ruleKeyListData) { let children; if (name.includes('(模糊匹配)')) { children = options[0].children; } if (name.includes('(正则匹配)')) { children = options[1].children; } if (name.includes('(组合精确匹配)')) { children = options[2].children; } if (name.includes('(精确匹配)')) { children = options[3].children; } children.push({ value: key, label: name }); } return options }; const ruleKeyListData = [{ key: "name", name: "用户名(模糊匹配)", oldKey: "userNameKeyArr", oldName: "用户名黑名单模式(模糊匹配)" }, { key: "precise_name", name: "用户名(精确匹配)", oldKey: "userNameArr", oldName: "用户名黑名单模式(精确匹配)" }, { key: "nameCanonical", name: "用户名(正则匹配)" }, { key: "precise_uid", name: "用户uid(精确匹配)", oldKey: "userUIDArr", oldName: "用户uid黑名单模式(精确匹配)" }, { key: "precise_uid_white", name: "用户uid白名单(精确匹配)", oldKey: "userWhiteUIDArr", oldName: "用户uid白名单模式(精确匹配)" }, { key: "title", name: "标题(模糊匹配)", oldKey: "titleKeyArr", oldName: "标题黑名单模式(模糊匹配)" }, { key: "titleCanonical", name: "标题(正则匹配)", oldKey: "titleKeyCanonicalArr", oldName: "标题黑名单模式(正则匹配)" }, { key: "commentOn", name: "评论关键词(模糊匹配)", oldKey: "commentOnKeyArr", oldName: "评论关键词黑名单模式(模糊匹配)" }, { key: "commentOnCanonical", name: "评论关键词(正则匹配)", oldKey: "contentOnKeyCanonicalArr", oldName: "评论关键词黑名单模式(正则匹配)" }, { key: "precise_fanCard", name: "粉丝牌(精确匹配)", oldKey: "fanCardArr", oldName: "粉丝牌黑名单模式(精确匹配)" }, { key: "dynamic", name: "动态关键词(模糊匹配)", oldKey: "dynamicArr", oldName: "动态关键词内容黑名单模式(模糊匹配)" }, { key: "precise_tag", name: "话题tag标签(精确匹配)", }, { key: "tag", name: "话题tag标签(模糊匹配)", }, { key: "tagCanonical", name: "话题tag标签(正则匹配)" }, { key: "precise_partition", name: "直播分区(精确匹配)" }, { key: 'videoTag', name: '视频tag(模糊匹配)', }, { key: 'precise_videoTag', name: '视频tag(精确匹配)', }, { key: 'videoTagCanonical', name: '视频tag(正则匹配)', }, { key: 'videoTag_preciseCombination', name: '视频tag(组合精确匹配)' }, { key: 'hotSearchKey', name: '热搜关键词(模糊匹配)', }, { key: 'hotSearchKeyCanonical', name: '热搜关键词(正则匹配)' }, { key: 'precise_avatarPendantName', name: '头像挂件名(精确匹配)' }, { key: 'avatarPendantName', name: '头像挂件名(模糊匹配)' }, { key: 'signature', name: '用户签名(模糊匹配)' }, { key: 'signatureCanonical', name: '用户签名(正则匹配)' }, { key: 'videoDesc', name: '视频简介(模糊匹配)' }, { key: 'videoDescCanonical', name: '视频简介(正则匹配)' }, { key: 'precise_video_bv', name: '视频bv号(精确匹配)' } ]; const otherKeyListData = [ { name: '最小播放量', value: 'nMinimumPlay', associated: 'nMaximumPlayback', defVal: -1 }, { name: '最大播放量', value: 'nMaximumPlayback', associated: 'nMinimumPlay', bLarge: true, defVal: -1 }, { name: '最小弹幕数', value: 'nMinimumBarrage', associated: 'nMaximumBarrage', defVal: -1 }, { name: '最大弹幕数', value: 'nMaximumBarrage', associated: 'nMinimumBarrage', bLarge: true, defVal: -1 }, { name: '最小时长', value: 'nMinimumDuration', associated: 'nMaximumDuration', defVal: -1 }, { name: '最大时长', value: 'nMaximumDuration', associated: 'nMinimumDuration', bLarge: true, defVal: -1 }, { name: '最小用户等级过滤', value: 'nMinimumLevel', associated: 'nMaximumLevel', defVal: -1 }, { name: '最大用户等级过滤', value: 'nMaximumLevel', associated: 'nMinimumLevel', bLarge: true, defVal: -1 } ]; const getRuleKeyListData = () => { return ruleKeyListData; }; const getRuleKeyList = () => { return ruleKeyListData.map(item => { return item.key }) }; const getNameArr = () => { return gmUtil.getData("name", []); }; const getPreciseNameArr = () => { return gmUtil.getData("precise_name", []); }; const getNameCanonical = () => { return gmUtil.getData("nameCanonical", []); }; const getPreciseUidArr = () => { return gmUtil.getData("precise_uid", []); }; const getPreciseUidWhiteArr = () => { return gmUtil.getData("precise_uid_white", []); }; const getTitleArr = () => { return gmUtil.getData("title", []); }; const getTitleCanonicalArr = () => { return gmUtil.getData("titleCanonical", []); }; const getCommentOnArr = () => { return gmUtil.getData("commentOn", []); }; const getCommentOnCanonicalArr = () => { return gmUtil.getData("commentOnCanonical", []); }; const getPreciseTagArr = () => { return gmUtil.getData("precise_tag", []); }; const getTagArr = () => { return gmUtil.getData("tag", []); }; const getTagCanonicalArr = () => { return gmUtil.getData("tagCanonical", []); }; const getPreciseFanCardArr = () => { return gmUtil.getData("precise_fanCard", []); }; const getPrecisePartitionArr = () => { return gmUtil.getData("precise_partition", []); }; const getVideoTagArr = () => { return gmUtil.getData("videoTag", []); }; const getPreciseVideoTagArr = () => { return gmUtil.getData("precise_videoTag", []); }; const getVideoTagCanonicalArr = () => { return gmUtil.getData("videoTagCanonical", []); }; const getHotSearchKeyArr = () => { return gmUtil.getData("hotSearchKey", []); }; const getHotSearchKeyCanonicalArr = () => { return gmUtil.getData("hotSearchKeyCanonical", []); }; const clearKeyItem = (ruleKey) => { gmUtil.delData(ruleKey); }; const getVideoTagPreciseCombination = () => { return gmUtil.getData("videoTag_preciseCombination", []); }; const setVideoTagPreciseCombination = (list) => { gmUtil.setData("videoTag_preciseCombination", list); }; const getPreciseVideoBV = () => { return gmUtil.getData("precise_video_bv", []); }; var ruleKeyListData$1 = { getNameArr, getPreciseNameArr, getNameCanonical, getPreciseUidArr, getPreciseUidWhiteArr, getTitleArr, getTitleCanonicalArr, getCommentOnArr, getCommentOnCanonicalArr, getRuleKeyListData, getPreciseTagArr, getTagArr, getTagCanonicalArr, getPreciseFanCardArr, getPrecisePartitionArr, getVideoTagArr, getPreciseVideoTagArr, getVideoTagCanonicalArr, getHotSearchKeyArr, getHotSearchKeyCanonicalArr, otherKeyListData, clearKeyItem, getSelectOptions, getVideoTagPreciseCombination, setVideoTagPreciseCombination, getRuleKeyList, getPreciseVideoBV }; const verificationInputValue = (ruleValue, type) => { if (ruleValue === null) return {status: false, res: '内容不能为空'}; if (type === "precise_uid" || type === "precise_uid_white") { ruleValue = parseInt(ruleValue); if (isNaN(ruleValue)) { return { status: false, res: '请输入数字!' }; } } else { ruleValue.trim(); } if (ruleValue === '') { return {status: false, res: '内容不能为空'}; } return {status: true, res: ruleValue}; }; const addRule = (ruleValue, type) => { const verificationRes = verificationInputValue(ruleValue, type); if (!verificationRes.status) { return verificationRes } const arr = gmUtil.getData(type, []); if (arr.includes(verificationRes.res)) { return {status: false, res: '已存在此内容'}; } arr.push(verificationRes.res); gmUtil.setData(type, arr); return {status: true, res: '添加成功'}; }; const showAddRuleInput = async (type) => { let ruleValue; try { const {value} = await eventEmitter.invoke('el-prompt', '请输入要添加的规则内容', 'tip'); ruleValue = value; } catch (e) { return } const {res, status} = addRule(ruleValue, type); eventEmitter.send('el-msg', res); status && eventEmitter.send('刷新规则信息'); status && eventEmitter.send('通知屏蔽'); }; const delRule = (type, value) => { const verificationRes = verificationInputValue(value, type); if (!verificationRes.status) { return verificationRes } const {res} = verificationRes; const arr = gmUtil.getData(type, []); const indexOf = arr.indexOf(res); if (indexOf === -1) { return {status: false, res: '不存在此内容'}; } arr.splice(indexOf, 1); gmUtil.setData(type, arr); return {status: true, res: "移除成功"} }; const showDelRuleInput = async (type) => { let ruleValue; try { const {value} = await eventEmitter.invoke('el-prompt', '请输入要删除的规则内容', '删除指定规则'); ruleValue = value; } catch (e) { return } const {status, res} = delRule(type, ruleValue); eventEmitter.send('el-msg', res); status && eventEmitter.send('刷新规则信息'); }; const getRuleContent = (space = 0) => { const ruleMap = {}; for (let ruleKeyListDatum of ruleKeyListData$1.getRuleKeyListData()) { const key = ruleKeyListDatum.key; ruleMap[key] = gmUtil.getData(key, []); } return JSON.stringify(ruleMap, null, space); }; const verificationRuleMap = (keyArr, content) => { let parse; try { parse = JSON.parse(content); } catch (e) { alert('规则内容有误'); return false; } const newRule = {}; for (const key in parse) { if (!Array.isArray(parse[key])) { continue; } if (parse[key].length === 0) { continue; } newRule[key] = parse[key]; } if (Object.keys(newRule).length === 0) { alert('规则内容为空'); return false; } return newRule; }; const overwriteImportRules = (content) => { const map = verificationRuleMap(ruleKeyListData$1.getRuleKeyList(), content); if (map === false) return false; for (let key of Object.keys(map)) { gmUtil.setData(key, map[key]); } return true; }; const appendImportRules = (content) => { const map = verificationRuleMap(ruleKeyListData$1.getRuleKeyList(), content); if (map === false) return false; for (let key of Object.keys(map)) { const arr = gmUtil.getData(key, []); for (let item of map[key]) { if (!arr.includes(item)) { arr.push(item); } } gmUtil.setData(key, arr); } return true; }; const overwriteImportRulesV1 = (content) => { let parse; try { parse = JSON.parse(content); } catch (e) { alert('规则内容有误'); return false; } for (let ruleKeyListDatum of ruleKeyListData$1.getRuleKeyListData()) { const name = ruleKeyListDatum.oldName; const jsonRuleList = parse[name]; if (!jsonRuleList) { continue; } if (jsonRuleList.length === 0) { continue; } gmUtil.setData(ruleKeyListDatum.key, jsonRuleList); } return true; }; const addRulePreciseUid = (uid, isTip = true) => { const results = addRule(uid, "precise_uid"); if (isTip) { eventEmitter.send('el-notify', { title: '添加精确uid操作提示', message: results.res }); return results } return results; }; const delRUlePreciseUid = (uid, isTip = true) => { const results = delRule('precise_uid', uid); if (isTip) { eventEmitter.send('el-alert', results.res); return null } return results }; const addRulePreciseName = (name, tip = true) => { const results = addRule(name, "precise_name"); if (tip) { eventEmitter.send('el-msg', results.res); } return results; }; const findRuleItemValue = (type, value) => { return gmUtil.getData(type, []).find(item => item === value) || null }; const addItemRule = (arr, key, coverage = true) => { const complianceList = []; for (let v of arr) { const {status, res} = verificationInputValue(v, key); if (!status) return {status: false, msg: `内容中有误:${res}`} complianceList.push(v); } if (coverage) { gmUtil.setData(key, complianceList); return {status: true, msg: `添加成功-覆盖模式,数量:${complianceList.length}`} } const oldArr = gmUtil.getData(key, []); const newList = complianceList.filter(item => !oldArr.includes(item)); if (newList.length === 0) { return {status: false, msg: '内容重复'} } gmUtil.setData(key, oldArr.concat(newList)); return {status: true, msg: '添加成功-追加模式,新增数量:' + newList.length} }; const addPreciseUidItemRule = (uidArr, isTip = true, coverage = true) => { const {status, msg} = addItemRule(uidArr, 'precise_uid', coverage); if (isTip) { eventEmitter.send('el-alert', msg); return status } return {status, msg} }; var ruleUtil = { addRule, showAddRuleInput, showDelRuleInput, getRuleContent, overwriteImportRules, appendImportRules, overwriteImportRulesV1, addRulePreciseUid, addRulePreciseName, delRUlePreciseUid, findRuleItemValue, addItemRule, addPreciseUidItemRule }; const rule_set_value_dialog = { template: ` <div> <el-dialog :visible="show" width="30%" title="修改单项规则值" :close-on-click-modal="false" :modal="false"> {{ ruleName }}-{{ ruleType }} <el-form> <el-form-item label="要修改的值"> <el-input type="text" v-model="oldVal" clearable/> </el-form-item> <el-form-item label="修改后的值"> <el-input v-model="newVal" clearable/> </el-form-item> </el-form> <template #footer class="dialog-footer"> <el-button @click="show=false">取消</el-button> <el-button @click="okBut">确定</el-button> </template> </el-dialog> </div>`, data() { return { show: false, ruleType: "", ruleName: "", oldVal: '', newVal: '' } }, methods: { okBut() { let tempOldVal = this.oldVal.trim(); let tempNewVal = this.newVal.trim(); if (tempOldVal.length === 0 || tempNewVal.length === 0) { this.$alert("请输入要修改的值或新值"); return } if (tempNewVal === tempOldVal) { this.$alert("新值不能和旧值相同"); return; } const tempRuleType = this.ruleType; if (tempRuleType === 'precise_uid' || tempRuleType === 'precise_uid_white') { tempOldVal = parseInt(tempOldVal); tempNewVal = parseInt(tempNewVal); if (isNaN(tempOldVal) || isNaN(tempNewVal)) { this.$alert("请输入整数数字"); return } } if (!ruleUtil.findRuleItemValue(tempRuleType, tempOldVal)) { this.$alert("要修改的值不存在"); return; } if (ruleUtil.findRuleItemValue(tempRuleType, tempNewVal)) { this.$alert("新值已存在"); return; } const ruleArr = gmUtil.getData(tempRuleType, []); const indexOf = ruleArr.indexOf(tempOldVal); ruleArr[indexOf] = tempNewVal; gmUtil.setData(tempRuleType, ruleArr); this.$alert(`已将旧值【${tempOldVal}】修改成【${tempNewVal}】`); this.show = false; } }, watch: { show(newVal) { if (newVal === false) this.oldVal = this.newVal = ''; } }, created() { eventEmitter.on('修改规则对话框', ({type, name}) => { this.show = true; this.ruleType = type; this.ruleName = name; }); } }; const arraysLooseEqual = (arr1, arr2) => { if (arr1.length !== arr2.length) return false; const countMap = {}; const getKey = (value) => { if (typeof value === 'number' && Number.isNaN(value)) return '__NaN'; return JSON.stringify(value); }; for (const elem of arr1) { const key = getKey(elem); countMap[key] = (countMap[key] || 0) + 1; } for (const elem of arr2) { const key = getKey(elem); if (!countMap[key]) return false; // 不存在或数量不足 countMap[key]--; } return true; }; const arrayContains = (a, b) => { if (b.length === 0) return true; if (a.length < b.length) return false; const countMap = {}; const getKey = (value) => { if (typeof value === 'number' && Number.isNaN(value)) return '__NaN'; return JSON.stringify(value); }; for (const elem of a) { const key = getKey(elem); countMap[key] = (countMap[key] || 0) + 1; } for (const elem of b) { const key = getKey(elem); if (!countMap[key] || countMap[key] <= 0) return false; countMap[key]--; } return true; }; var arrUtil = { arraysLooseEqual, arrayContains }; const multiple_rule_edit_dialog_vue = { template: ` <div> <el-dialog :visible.sync="dialogVisible" title="多重规则" :modal="false" :close-on-click-modal="false" :close-on-press-escape="false"> <el-tag>{{ typeMap.name }}</el-tag> <el-card> <template #header>说明</template> <div>1.组合类型每条项至少大于1</div> <div>2.不能添加空项</div> <div>3.每组中的项不能超过15个字符</div> <div>4.不能重复添加已有的组合</div> <div>5.每组不能添加过包括已有的组合</div> <div>6.不能添加视频tag(精确匹配)已有的项,如需要,请先移除对应的项!包括视频tag(模糊匹配)</div> </el-card> <el-card> <el-input class="input-new-tag" v-if="inputVisible" v-model="inputValue" ref="saveTagInput" size="small" placeholder="多个项时请用英文符号分割" @keyup.enter.native="handleInputConfirm" @blur="handleInputConfirm" > </el-input> <el-button v-else size="small" @click="showInput">+ New Tag</el-button> <el-tag closable v-for="(item,index) in showTags" @close="handleTagClose(item,index)">{{ item|filterTag }} </el-tag> </el-card> </el-dialog> </div>`, data() { return { dialogVisible: false, inputVisible: false, inputValue: '', min: 2, typeMap: {}, showTags: [], } }, methods: { updateShowTags() { this.showTags = ruleKeyListData$1.getVideoTagPreciseCombination(); }, handleTagClose(tag, index) { if (tag === '') return; this.$confirm(`确定要删除 ${tag} 吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.showTags.splice(index, 1); ruleKeyListData$1.setVideoTagPreciseCombination(this.showTags); this.$message.success(`已移除 ${tag}`); eventEmitter.send('刷新规则信息', false); }); }, showInput() { this.inputVisible = true; this.$nextTick(_ => { this.$refs.saveTagInput.$refs.input.focus(); }); }, handleInputConfirm() { let inputValue = this.inputValue; this.inputVisible = false; if (inputValue === '') return; this.submitBut(inputValue); this.inputValue = ''; }, submitBut(inputValue) { const split = inputValue.split(','); if (split.length < this.min) { this.$message.error('最少添加' + this.min + '项'); return; } const preciseVideoTagArr = ruleKeyListData$1.getPreciseVideoTagArr(); const videoTagArr = ruleKeyListData$1.getVideoTagArr(); for (let showTag of split) { showTag = showTag.trim(); if (showTag === "") { this.$message.error('不能添加空项'); return; } if (preciseVideoTagArr.includes(showTag)) { this.$message.error('不能添加视频tag(精确匹配)已有的项,请先移除对应的项!'); return; } if (videoTagArr.includes(showTag)) { this.$message.error('不能添加视频tag(模糊匹配)已有的项,请先移除对应的项!'); return; } if (showTag.length > 15) { this.$message.error('项不能超过15个字符'); return; } } const arr = ruleKeyListData$1.getVideoTagPreciseCombination(); for (let mk_arr of arr) { if (arrUtil.arraysLooseEqual(mk_arr, split)) { this.$message.error('不能重复添加已有的组合!'); return } if (arrUtil.arrayContains(mk_arr, split)) { this.$message.error('该组合已添加过或包括该组合'); return } } arr.push(split); ruleKeyListData$1.setVideoTagPreciseCombination(arr); console.log(this.typeMap, split, arr); this.$message.success(`${this.typeMap.name}添加成功`); this.updateShowTags(); eventEmitter.send('刷新规则信息', false); } }, created() { eventEmitter.on('打开多重规则编辑对话框', (typeMap) => { this.typeMap = typeMap; this.dialogVisible = true; this.updateShowTags(); }); }, filters: { filterTag(tag) { return tag.join('||') } } }; const basic_rules_vue = { components: {rule_set_value_dialog, multiple_rule_edit_dialog_vue}, template: ` <div> <el-card shadow="never"> <template #header> <span>使用说明</span> </template> <div>1.基础规则类型较多,下拉框支持搜索定位,鼠标点击出现光标时支持筛选</div> <div>2.大部分情况下模糊匹配比精确匹配好用</div> <div>3.如果可以的话,请优先考虑根据uid精确屏蔽,而非使用用户名相关屏蔽,因用户名可以随意更改</div> <div>4.如果用户要添加自己的正则匹配相关的规则时,建议先去该网址进行测试再添加,避免浪费时间 <el-link href="https://www.jyshare.com/front-end/854/" target="_blank" type="primary">>>>正则表达式在线测试<<< </el-link> </div> <div> 5.如果更新脚本之后规则全没了,请点击下面的【旧规则自动转新规则】按钮,进行转换,如不行请通过关于和问题反馈选项卡中的反馈渠道联系作者 </div> <div>6.改动实时生效</div> <div>7. 分区包括子分区属于视频tag范畴,如需按分区屏蔽在对应视频tag类型添加</div> <div>8. 基础规则中的项和组合规则互斥,如xxx添加到视频tag多重规则,则不能添加到对应基础规则视频tag,反之同理,限类型,如组合精确匹配 </div> </el-card> <el-card shadow="never"> <template #header>选择规则</template> <el-cascader v-model="cascaderVal" @change="handleChangeCascader" :options="cascaderOptions" :show-all-levels="false" :props="{ expandTrigger: 'hover' }" filterable/> <el-divider/> <el-row> <el-col :span="12"> <el-button-group> <el-button @click="addRuleBut">添加</el-button> <el-button @click="setRuleBut">修改</el-button> <el-button @click="findItemAllBut">查询</el-button> <el-button @click="delBut">移除</el-button> </el-button-group> </el-col> <el-col :span="12"> <div class="el-horizontal-right"> <el-button-group> <el-button @click="clearItemRuleBut" type="danger">清空项</el-button> <el-button type="danger" @click="delAllBut">全部移除</el-button> </el-button-group> </div> </el-col> </el-row> </el-card> <rule_set_value_dialog/> <multiple_rule_edit_dialog_vue/> </div>`, data() { return { cascaderVal: ["精确匹配", "precise_uid"], cascaderOptions: ruleKeyListData$1.getSelectOptions(), ruleInfoArr: [], } }, methods: { handleChangeCascader(val) { console.log(val); }, addRuleBut() { const [model, mk_type] = this.cascaderVal; if (model === '多重匹配') { const typeMap = this.ruleInfoArr.find(item => item.type === mk_type); eventEmitter.send('打开多重规则编辑对话框', typeMap); return } ruleUtil.showAddRuleInput(mk_type); }, setRuleBut() { const [model, type] = this.cascaderVal; const typeMap = this.ruleInfoArr.find(item => item.type === type); if (model === '多重匹配') { eventEmitter.send('打开多重规则编辑对话框', typeMap); return } eventEmitter.send('修改规则对话框', typeMap); }, findItemAllBut() { const [model, type] = this.cascaderVal; const typeMap = this.ruleInfoArr.find(item => item.type === type); if (model === '多重匹配') { eventEmitter.send('打开多重规则编辑对话框', typeMap); return } const ruleData = gmUtil.getData(type, []); eventEmitter.send('展示内容对话框', JSON.stringify(ruleData, null, 4)); }, delAllBut() { this.$confirm('确定要删除所有规则吗?').then(() => { for (let x of this.ruleInfoArr) { gmUtil.delData(x.type); } this.$alert("删除全部规则成功"); eventEmitter.send('刷新规则信息'); }); }, delBut() { const [model, type] = this.cascaderVal; const typeMap = this.ruleInfoArr.find(item => item.type === type); if (model === '多重匹配') { eventEmitter.send('打开多重规则编辑对话框', typeMap); return } ruleUtil.showDelRuleInput(type); }, clearItemRuleBut() { const type = this.cascaderVal[1]; const find = this.ruleInfoArr.find(item => item.type === type); this.$confirm(`是要清空${find.name}的规则内容吗?`, 'tip').then(() => { ruleKeyListData$1.clearKeyItem(type); this.$alert(`已清空${find.name}的规则内容`); }); } }, watch: {}, created() { for (let newRuleKeyListElement of ruleKeyListData$1.getRuleKeyListData()) { this.ruleInfoArr.push({ type: newRuleKeyListElement.key, name: newRuleKeyListElement.name, }); } } }; const oldToNewRule = () => { const listData = ruleKeyListData$1.getRuleKeyListData().filter(item => item.oldKey); for (let data of listData) { const oldKeyDataArr = gmUtil.getData(data.oldKey, []); if (oldKeyDataArr.length === 0) { continue } const newKeyDataArr = gmUtil.getData(data.key, []); if (newKeyDataArr.length === 0) { gmUtil.setData(data.key, oldKeyDataArr); gmUtil.delData(data.oldKey); continue } for (let v of oldKeyDataArr) { const isExist = newKeyDataArr.find(item => item === v); if (!isExist) { newKeyDataArr.push(v); } } gmUtil.setData(data.key, newKeyDataArr); } }; var ruleConversion = { oldToNewRule }; const wait = (milliseconds = 1000) => { return new Promise(resolve => setTimeout(resolve, milliseconds)); }; const fileDownload = (content, fileName) => { const element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content)); element.setAttribute('download', fileName); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); }; const handleFileReader = (event) => { return new Promise((resolve, reject) => { const file = event.target.files[0]; if (!file) { reject('未读取到文件'); return; } let reader = new FileReader(); reader.onload = (e) => { const fileContent = e.target.result; resolve({file, content: fileContent}); reader = null; }; reader.readAsText(file); }); }; const isIterable = (obj) => { return obj != null && typeof obj[Symbol.iterator] === 'function'; }; const toTimeString = () => { return new Date().toLocaleString(); }; function smoothScroll(toTop = false, duration = 1000) { return new Promise((resolve) => { const start = window.scrollY; const end = toTop ? 0 : document.documentElement.scrollHeight - window.innerHeight; const change = end - start; const startTime = performance.now(); function animateScroll(currentTime) { const elapsedTime = currentTime - startTime; const progress = Math.min(elapsedTime / duration, 1); const easeInOutQuad = progress < 0.5 ? 2 * progress * progress : -1 + (4 - 2 * progress) * progress; window.scrollTo(0, start + change * easeInOutQuad); if (progress < 1) { requestAnimationFrame(animateScroll); } else { resolve(); } } requestAnimationFrame(animateScroll); }); } function debounce(func, wait = 1000) { let timeout; return function (...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; } function debounceAsync(asyncFunc, wait = 1000) { let timeout; let pendingPromise; return async function (...args) { const context = this; if (pendingPromise) { clearTimeout(timeout); await pendingPromise; } pendingPromise = new Promise((resolve) => { timeout = setTimeout(() => { pendingPromise = null; // 清除引用 resolve(asyncFunc.apply(context, args)); }, wait); }); return pendingPromise; }; } function throttle(func, limit) { let inThrottle; return function (...args) { const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } function throttleAsync(asyncFunc, limit) { let isThrottled = false; let pendingArgs = null; let pendingContext = null; let timeoutId; let pendingPromiseResolve; const throttled = async function (...args) { const context = this; if (isThrottled) { return new Promise((resolve) => { pendingArgs = args; pendingContext = context; pendingPromiseResolve = resolve; }); } isThrottled = true; try { return await asyncFunc.apply(context, args); } finally { timeoutId = setTimeout(() => { isThrottled = false; if (pendingArgs) { throttled.apply(pendingContext, pendingArgs).then(pendingPromiseResolve); pendingArgs = null; pendingContext = null; pendingPromiseResolve = null; } }, limit); } }; throttled.cancel = () => { clearTimeout(timeoutId); isThrottled = false; pendingArgs = null; pendingContext = null; pendingPromiseResolve = null; }; return throttled; } const parseUrl = (urlString) => { const url = new URL(urlString); const pathSegments = url.pathname.split('/').filter(segment => segment !== ''); const searchParams = new URLSearchParams(url.search.slice(1)); const queryParams = {}; for (const [key, value] of searchParams.entries()) { queryParams[key] = value; } return { protocol: url.protocol, hostname: url.hostname, port: url.port, pathname: url.pathname, pathSegments, search: url.search, queryParams, hash: url.hash }; }; const getLocalStorage = (key, isList = false, defaultValue = null) => { const item = localStorage.getItem(key); if (item === null) { return defaultValue } if (isList) { try { return JSON.parse(item) } catch (e) { console.error(`读取localStorage时尝试转换${key}的值失败`, e); return defaultValue } } return item }; const formatTimestamp = (timestamp, options = {}) => { if (!timestamp || isNaN(timestamp)) return 'Invalid Timestamp' const ts = String(timestamp).length === 10 ? +timestamp * 1000 : +timestamp; const timezoneOffset = (options.timezone || 0) * 60 * 60 * 1000; const date = new Date(ts + timezoneOffset); if (isNaN(date.getTime())) return 'Invalid Date' const timeObj = { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate(), hours: date.getUTCHours(), minutes: date.getUTCMinutes(), seconds: date.getUTCSeconds() }; if (options.returnObject) return timeObj const format = options.format || 'YYYY-MM-DD HH:mm:ss'; const pad = (n) => n.toString().padStart(2, '0'); return format .replace(/YYYY/g, timeObj.year) .replace(/YY/g, String(timeObj.year).slice(-2)) .replace(/MM/g, pad(timeObj.month)) .replace(/M/g, timeObj.month) .replace(/DD/g, pad(timeObj.day)) .replace(/D/g, timeObj.day) .replace(/HH/g, pad(timeObj.hours)) .replace(/H/g, timeObj.hours) .replace(/mm/g, pad(timeObj.minutes)) .replace(/m/g, timeObj.minutes) .replace(/ss/g, pad(timeObj.seconds)) .replace(/s/g, timeObj.seconds) }; const calculateLikeRate = (likeCount, viewCount) => { if (viewCount === 0) { return 0; } return parseInt((likeCount / viewCount) * 100) }; const calculateInteractionRate = (danmaku, reply, view) => { return parseInt((danmaku + reply) / view * 100) }; const calculateTripleRate = (favorite, coin, share, view) => { return parseInt((favorite + coin + share) / view * 100) }; const calculateCoinLikesRatioRate = (coin, like) => { return parseInt((coin + like) / view * 100) }; var defUtil = { wait, fileDownload, toTimeString, smoothScroll, debounce, debounceAsync, throttle, throttleAsync, parseUrl, handleFileReader, isIterable, getLocalStorage, formatTimestamp, calculateLikeRate, calculateInteractionRate, calculateTripleRate, calculateCoinLikesRatioRate }; var rule_export_import_vue = { template: ` <div> <el-card shadow="never"> <template #header> <span>导出规则</span> </template> <el-button @click="ruleOutToFIleBut">导出到文件</el-button> <el-button @click="outToInputBut">导出到编辑框</el-button> <el-button @click="ruleOutToConsoleBut">导出到控制台</el-button> </el-card> <el-card shadow="never"> <template #header> <el-row> <el-col :span="12"> <div class="el-horizontal-left">导入规则</div> </el-col> <el-col :span="12"> <div class="el-horizontal-right"> <el-button v-for="item in ruleReference" @click="xtipAlertBut(item.content,item.title)"> {{ item.title }} </el-button> </div> </el-col> </el-row> </template> <div>规则内容请在下面编辑框中导入</div> <div>旧版本的需要使用下面的v1旧版本导入规则</div> <div>旧版本的只能覆盖导入</div> <div>v1之后的版本可以选择覆盖和追加</div> <div>旧规则转新规则,用于2.0之前版本升上来旧规则内容丢失问题</div> <el-divider/> <div> <el-button @click="inputFIleRuleBut">读取外部规则文件</el-button> <el-button @click="overwriteImportRulesBut">覆盖导入规则</el-button> <el-button @click="appendImportRulesBut">追加导入规则</el-button> <el-button @click="overwriteImportRulesV1But">v1旧版本覆盖导入规则</el-button> <el-button @click="ruleOldToNewBut">旧规则自动转新规则</el-button> </div> <el-divider/> <div> <el-input autosize :autosize="{ minRows: 10, maxRows: 50}" type="textarea" v-model="ruleContentImport" placeholder="要导入的规则内容"></el-input> </div> </el-card> <input ref="file" type="file" accept="application/json" @change="handleFileUpload" style="display: none"> </div>`, data() { return { ruleContentImport: "", ruleReference: [ { title: "旧版本规则参考", content: ` {"用户名黑名单模式(精确匹配)":["账号已注销"],"BV号黑名单模式(精确匹配)":[], "用户名黑名单模式(模糊匹配)":["bili_","_bili"],"用户uid黑名单模式(精确匹配)":[442010132,76525078,225219967,3493283164588093], "用户uid白名单模式(精确匹配)":[344490740,1861980711],"标题黑名单模式(模糊匹配)":["激励计划","蚌不住","手游激励","游戏活动打卡"], "标题黑名单模式(正则匹配)":["感觉.*不如","不要笑.*挑战"],"评论关键词黑名单模式(模糊匹配)":["感觉不如","差不多的了"], "评论关键词黑名单模式(正则匹配)":["这不.+吗","玩.*的","不要笑.*挑战"],"粉丝牌黑名单模式(精确匹配)":[], "专栏关键词内容黑名单模式(模糊匹配)":[],"动态关键词内容黑名单模式(模糊匹配)":["拼多多","京东红包","京东618红包","618活动"]}` }, { title: "新版本规则参考", content: "待补充" } ], } }, methods: { overwriteImportRulesBut() { this.$confirm('是否要覆盖导入规则?').then(() => { const trim = this.ruleContentImport.trim(); if (ruleUtil.overwriteImportRules(trim)) { this.$alert('已覆盖导入成功!'); eventEmitter.send('刷新规则信息'); } }); }, appendImportRulesBut() { this.$confirm('是否要追加导入规则?').then(() => { const trim = this.ruleContentImport.trim(); if (ruleUtil.appendImportRules(trim)) { this.$message('已追加导入成功!'); eventEmitter.send('刷新规则信息'); } }); }, overwriteImportRulesV1But() { this.$confirm('旧版本-是否导入规则?').then(() => { const trim = this.ruleContentImport.trim(); if (ruleUtil.overwriteImportRulesV1(trim)) { this.$message('已导入成功!'); eventEmitter.send('刷新规则信息'); } }); }, xtipAlertBut(content, title) { this.$alert(content, title); }, ruleOldToNewBut() { ruleConversion.oldToNewRule(); eventEmitter.send('刷新规则信息'); this.$message('已转换成功!'); }, handleFileUpload(event) { defUtil.handleFileReader(event).then(data => { const {content} = data; try { JSON.parse(content); } catch (e) { this.$message('文件内容有误'); return; } this.ruleContentImport = content; this.$message('读取到内容,请按需覆盖或追加'); }); }, inputFIleRuleBut() { this.$refs.file.click(); }, outToInputBut() { this.ruleContentImport = ruleUtil.getRuleContent(2); this.$message('已导出到输入框中'); }, ruleOutToFIleBut() { let fileName = "b站屏蔽器规则-" + defUtil.toTimeString(); this.$prompt('请输入文件名', '保存为', { inputValue: fileName }).then(({value}) => { if (value === "" && value.includes(' ')) { this.$alert('文件名不能为空或包含空格'); return } const ruleContent = ruleUtil.getRuleContent(4); defUtil.fileDownload(ruleContent, value + ".json"); }); }, ruleOutToConsoleBut() { console.log(ruleUtil.getRuleContent()); this.$message('已导出到控制台上,F12打开控制台查看'); }, }, }; const comment_word_limit_vue = { template: ` <div> <el-card> <template #header>评论字数限制</template> <div>超出设置限制的字数时屏蔽(不包括),低于3则不生效</div> <div>改动即生效</div> <el-input-number v-model="value"/> </el-card> </div>`, data() { return { value: localMKData.getCommentWordLimitVal() } }, watch: { value(newVal, oldVal) { if (oldVal <= 3) return; if (newVal < 3) { this.$notify({ message: '已关闭屏蔽字数限制功能', type: 'warning', }); } gmUtil.setData('comment_word_limit', newVal); } } }; var other_parameter_filter = { components: {comment_word_limit_vue}, template: ` <div> <div style="display: flex"> <div style="width: 70vw"> <el-card> <template #header> <span>使用说明</span> </template> <ol> <li>如设置时长相关单位为秒</li> <li>如设置播放量和弹幕量相关单位为个</li> <li>设置最小播放量则小于该值的视频会屏蔽</li> <li>设置最大播放量则大于该值的视频会屏蔽</li> <li>设置最小弹幕量则小于该值的视频会屏蔽</li> <li>设置最大弹幕量则大于该值的视频会屏蔽</li> <li>设置最小时长则小于该值的视频会屏蔽</li> <li>设置最大时长则大于该值的视频会屏蔽</li> <li>设置最小用户等级则小于该值的会屏蔽,低于该值的会屏蔽掉</li> <li>设置最大用户等级则大于该值的会屏蔽,高于该值的会屏蔽掉</li> <li>取消相关限制条件则不做限制处理</li> <li>右侧信息关键条件-1则为未做任何限制处理</li> <li>最后因为设置限制条件冲突或限制太多,视频未能限制的情况下,请按需设置限制条件</li> </ol> </el-card> <input gz_type type="number" :min="inputMin" :max="inputMax" v-model="num"> <el-select v-model="selectValue" filterable> <el-option :value="item.value" v-for="item in selectList" :label="item.name"></el-option> </el-select> <div> <el-button @click="okVideoSelectBut">设置</el-button> <el-button @click="cancelBut">取消</el-button> <el-button @click="allCancelBut">全部取消</el-button> </div> </div> <div> <el-button @click="updateInfoBut">刷新</el-button> <div v-for="item in selectList" style="padding: 5px"> {{ item.name }}{{ item.defVal }} {{ item.name.includes('时长') ? '秒' : '' }} </div> </div> </div> <comment_word_limit_vue/> </div>`, data() { return { num: 0, selectList: ruleKeyListData$1.otherKeyListData, selectValue: 'nMinimumPlay', inputMax: "", inputMin: 0 } }, methods: { okVideoSelectBut() { const find = this.selectList.find(item => item.value === this.selectValue); const associatedVal = gmUtil.getData(find.associated, -1); const associatedFind = this.selectList.find(item => item.value === find.associated); if (this.num > associatedVal && associatedVal !== -1) { if (associatedFind.bLarge) { this.$alert(`要设置的${find.name}值不能大于${associatedFind.name}的值`); return } console.log('正常修改'); } this.$alert(`已设置${find.name},值为${this.num}`); gmUtil.setData(this.selectValue, this.num); this.updateInfo(); }, cancelBut() { gmUtil.setData(this.selectValue, -1); const find = this.selectList.find(item => item.value === this.selectValue); this.$alert(`已取消${find.name}的限制`); this.updateInfo(); }, allCancelBut() { for (let item of this.selectList) { gmUtil.setData(item.value, -1); } this.updateInfo(); }, updateInfo() { for (let item of this.selectList) { item.defVal = gmUtil.getData(item.value, -1); } }, updateInfoBut() { this.updateInfo(); this.$alert('已刷新'); }, }, watch: { selectValue(newVal) { const find = this.selectList.find(item => item.value === newVal); if (find.name.includes('用户等级')) { this.inputMin = 3; this.inputMax = 6; if (this.num > 6) { this.num = 6; } if (this.num < 3) { this.num = 3; } } else { this.inputMin = 0; this.inputMax = ''; } } }, created() { this.updateInfo(); } }; var rule_information_vue = { template: ` <div> <el-card> <template #header> <el-button @click="refreshInfoBut">刷新信息</el-button> </template> <div style=" display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;"> <div v-for="item in ruleInfoArr" :key="item.name"> <el-badge :value="item.len"> <el-button>{{ item.name }}</el-button> </el-badge> </div> </div> </el-card> </div>`, data() { return { ruleInfoArr: [], } }, methods: { refreshInfo(isTip = true) { for (let x of this.ruleInfoArr) { x.len = gmUtil.getData(x.type, []).length; } if (!isTip) return; this.$notify({ title: 'tip', message: '刷新规则信息成功', type: 'success', }); }, refreshInfoBut() { this.refreshInfo(); }, }, created() { for (let newRuleKeyListElement of ruleKeyListData$1.getRuleKeyListData()) { this.ruleInfoArr.push({ type: newRuleKeyListElement.key, name: newRuleKeyListElement.name, len: 0 }); } this.refreshInfo(false); eventEmitter.on('刷新规则信息', (isTip = true) => { this.refreshInfo(isTip); }); } }; class asynchronousIntervalQueue { #isProcessing = false; #pendingQueue = []; #interval = 200; constructor(options = {}) { this.#interval = options.interval || 200; } setInterval(interval) { this.#interval = interval; } add(func, config = {}) { return new Promise((resolve, reject) => { this.#pendingQueue.push({ funcFn: func, config: { interval: config.interval || null, }, resolve, reject }); if (!this.#isProcessing) { this.#processQueue(); } }); } async #processQueue() { this.#isProcessing = true; while (this.#pendingQueue.length > 0) { const task = this.#pendingQueue.shift(); try { let result; const funcFn = task.funcFn; if (funcFn instanceof Promise) { const template = await funcFn; if (template instanceof Function) { result = template(); } else { result = template; } } if (funcFn instanceof Function) { const template = funcFn(); if (template instanceof Promise) { result = await template; } else { result = template; } } task.resolve(result); } catch (error) { task.reject(error); } finally { const interval = task.config.interval || this.#interval; await new Promise(resolve => setTimeout(resolve, interval) ); } } this.#isProcessing = false; } clearPendingQueue() { this.#pendingQueue = []; this.#isProcessing = false; } } const requestIntervalQueue = new asynchronousIntervalQueue({ interval: localMKData.isRequestFrequencyVal() * 1000 }); var conditionalityVue = { template: ` <div> <el-switch v-model="bOnlyTheHomepageIsBlocked" active-text="仅首页屏蔽生效屏蔽"/> <el-tooltip content="模糊和正则匹配时,将匹配词转小写与规则值匹配。修改后刷新页面生效"> <el-switch v-model="bFuzzyAndRegularMatchingWordsToLowercase" active-text="模糊和正则匹配词转小写"/> </el-tooltip> <el-card> <template #header> <span>网络请求频率(单位秒)</span> <div>如设置0,则为不限制,比如设置2,则为每个请求之间隔2秒,可有效降低对B站api接口的压力,降低风控</div> <div>注意:设置过低可能会导致部分接口风控</div> <div>如接口风控了请先勾选下面的【禁用根据bv号网络请求获取视频信息】</div> <div>修改实时生效</div> </template> <el-switch v-model="isDisableNetRequestsBvVideoInfo" active-text="禁用根据bv号网络请求获取视频信息"/> <el-slider v-model="requestFrequencyVal" max="5" step="0.1" show-stops show-input :disabled="isDisableNetRequestsBvVideoInfo" ></el-slider> </el-card> </div>`, data() { return { requestFrequencyVal: localMKData.isRequestFrequencyVal(), bOnlyTheHomepageIsBlocked: localMKData.getBOnlyTheHomepageIsBlocked(), bFuzzyAndRegularMatchingWordsToLowercase: localMKData.bFuzzyAndRegularMatchingWordsToLowercase(), isDisableNetRequestsBvVideoInfo: false } }, methods: {}, watch: { bOnlyTheHomepageIsBlocked(newVal) { gmUtil.setData("bOnlyTheHomepageIsBlocked", newVal === true); }, bFuzzyAndRegularMatchingWordsToLowercase(newVal) { gmUtil.setData("bFuzzyAndRegularMatchingWordsToLowercase", newVal === true); }, isDisableNetRequestsBvVideoInfo(b) { gmUtil.setData('isDisableNetRequestsBvVideoInfo', b); }, requestFrequencyVal(n) { gmUtil.setData('requestFrequencyVal', n > 0 && n <= 5 ? n : 0.2); requestIntervalQueue.setInterval(n * 1000); } }, created() { eventEmitter.on('更新根据bv号网络请求获取视频信息状态', (b) => { this.isDisableNetRequestsBvVideoInfo = b; }); } }; const queue = new asynchronousIntervalQueue(); const getData = async (page = 1) => { const response = await fetch(`https://api.bilibili.com/x/relation/blacks?pn=${page}&ps=50&jsonp=jsonp`, { credentials: 'include' }); if (response.status !== 200) { eventEmitter.send('el-msg', '拉取黑名单数据响应失败.'); return {state: false} } const resJson = await response.json(); const {data: {list, total}, message, code} = resJson; if (code !== 0) { eventEmitter.send('el-msg', '请求相应内容失败:code=' + code); return {state: false, msg: `请求相应内容失败:msg=${message} code=` + code} } const newList = list.map(({face, mid, mtime, uname, sign}) => { return {face, mid, mtime, uname, sign} }); return {state: true, list: newList, total}; }; const blacklist_management_vue = { template: ` <div> <div>1.注意:该功能为b站自身的黑名单</div> <div>1.对应地址 <el-link target="_blank" href="https://account.bilibili.com/account/blacklist"> https://account.bilibili.com/account/blacklist </el-link> </div> <div>3.需要登录才可以使用</div> <el-card shadow="never" v-loading="isDivLoading" element-loading-text="拼命加载中"> <template #header> <el-row> <el-col :span="8"> <el-badge :value="total"> <el-tag>累计</el-tag> </el-badge> <el-badge :value="showList.length" style="margin-left: 45px"> <el-tag>显示数</el-tag> </el-badge> <div> <el-card shadow="never"> <template #header>请求的间隔({{ sliderInterval }}S)</template> <el-slider v-model="sliderInterval" step="0.1" max="10"></el-slider> </el-card> <el-button @click="getOnePageDataBut">获取第一页</el-button> <el-button @click="getAllBut">获取全部</el-button> <el-button type="warning" @click="clearTableBut">清空列表</el-button> <el-button @click="outDataToConsoleBut">导出控制台</el-button> <el-button @click="outDataToFileBut">导出文件</el-button> </div> </el-col> <el-col :span="16"> <el-card shadow="never"> <template #header><span>过滤</span></template> <div> <el-switch v-model="isCancelMaxLimit" active-text="取消列表显示最大限制"/> </div> <el-select v-model="select.val"> <el-option v-for="item in select.options" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> <el-input v-model="findVal"></el-input> </el-card> </el-col> </el-row> </template> <el-table :data="showList" stripe border> <el-table-column prop="mtime" label="时间" width="155px"> <template v-slot="scope"> {{ new Date(scope.row.mtime * 1000).toLocaleString() }} </template> </el-table-column> <el-table-column label="头像" width="55px"> <template v-slot="scope"> <el-avatar shape="square" :src="scope.row.face"></el-avatar> </template> </el-table-column> <el-table-column prop="uname" label="用户名" width="190px"></el-table-column> <el-table-column prop="mid" label="用户ID" width="180px"></el-table-column> <el-table-column prop="sign" label="签名"></el-table-column> <el-table-column label="标记" width="50px"> <template v-slot="scope"> 未定 </template> </el-table-column> <el-table-column label="操作"> <template #header> <el-button @click="tableAddUidBlackButAll">一键添加uid屏蔽</el-button> </template> <template v-slot="scope"> <el-button @click="tableOpenAddressBut(scope.row)">打开地址</el-button> <el-button @click="tableAddUidBlackBut(scope.row)">uid屏蔽</el-button> </template> </el-table-column> </el-table> <el-pagination :page-size="pageSize" background layout="prev, pager, next" :total="list.length" @current-change="handleCurrentChange" > </el-pagination> </el-card> </div>`, data() { return { select: { val: 'uname', options: [{ label: "用户UID", value: 'mid', }, { label: "用户名", value: 'uname', }, { label: '用户签名', value: 'sign' }] }, total: 0, list: [], showList: [], findVal: '', sliderInterval: 0.6, isDivLoading: false, isCancelMaxLimit: false, pageSize: 50 } }, methods: { filterTable(list, val) { const filter = list.filter(x => { const x1 = x[this.select.val]; if (Number.isInteger(x1)) { return x1.toString().includes(val) } return x1.includes(val); }); if (filter.length === 0) { this.$notify({ title: '没有匹配到数据', type: 'warning', duration: 2000 }); return [] } if (filter.length > 50 && !this.isCancelMaxLimit) { this.$notify({ title: '数据过多,已截取前50条', type: 'warning', duration: 2000 }); return filter.slice(0, 50); } return filter; }, async getOnePageDataBut() { const {state, list, total} = await getData(); if (!state) { return } this.list = list; this.showList = list; this.total = total; this.$message('获取成功'); }, tableOpenAddressBut(row) { gmUtil.openInTab(`https://space.bilibili.com/${row.mid}`); }, tableAddUidBlackBut(row) { const uid = row.mid; const name = row.uname; if (ruleUtil.findRuleItemValue('precise_uid', uid)) { this.$message(`该用户:${name}的uid:${uid}已添加过`); return; } this.$confirm(`确定添加${name}的uid:${uid}到uid精确屏蔽吗?`).then(() => { ruleUtil.addRulePreciseUid(uid); }); console.log(row); }, outDataToConsoleBut() { console.log('黑名单管理列表====start'); console.log(JSON.parse(JSON.stringify(this.list))); console.log('黑名单管理列表====end'); this.$alert('已导出到控制台,可通过f12查看'); }, outDataToFileBut() { this.$prompt('请输入文件名', '保存为', { inputValue: 'B站黑名单列表' }).then(({value}) => { if (value.trim() === '') { return } const tempData = { total: this.total, list: this.list }; const s = JSON.stringify(tempData, null, 4); defUtil.fileDownload(s, +value.trim() + '.json'); this.$alert('已导出到文件,请按需保存'); }); }, async getAllBut() { this.isDivLoading = true; const {state, list, total} = await getData(); if (!state) return if (total === 0) { this.isDivLoading = false; this.$message('没有更多数据了'); return; } this.total = total; const totalPage = Math.ceil(total / 50); if (totalPage === 1) { this.list = list; this.isDivLoading = false; return } this.list = list; for (let i = 2; i <= totalPage; i++) { const {state, list: resList} = await queue.add(() => getData(i)); if (!state) return list.push(...resList); } if (this.list.length > 50 && !this.isCancelMaxLimit) { this.showList = list.slice(0, 50); } else { this.showList = list; } this.showList = list; this.$message('获取成功'); this.isDivLoading = false; }, handleCurrentChange(page) { this.showList = this.list.slice((page - 1) * 50, page * 50); }, clearTableBut() { this.showList = this.list = []; this.$message('已清空列表'); }, tableAddUidBlackButAll() { if (this.list.length === 0) { this.$message('列表为空'); return } this.$confirm(`确定添加所有用户到uid精确屏蔽吗?`).then(() => { if (ruleUtil.addPreciseUidItemRule(this.list.map(x => x.mid), true, false)) { eventEmitter.send('刷新规则信息'); } }); } }, watch: { findVal(n) { this.showList = this.filterTable(this.list, n); }, sliderInterval(n) { queue.setInterval(n * 1000); }, isCancelMaxLimit(n) { this.pageSize = n ? 1000000 : 50; } }, created() { queue.setInterval(this.sliderInterval * 1000); } }; const uid_range_masking_vue = { template: ` <div> <el-card> <template #header> uid范围屏蔽 </template> <el-switch v-model="status" active-text="启用" style="margin-bottom: 10px"/> <div style="margin-bottom: 10px"> 范围内的uid都会被屏蔽掉,改动需重新设置方可生效,且再下次检查时屏蔽(如视频列表加载,评论加载)。比较关系【最小>=uid<=最大】 </div> <el-form label-position="left" :disabled="!status" style="width: 20%"> <el-form-item label="最小"> <el-input v-model.number="head"></el-input> </el-form-item> <el-form-item label="最大"> <el-input v-model.number="tail"></el-input> </el-form-item> <el-form-item class="el-horizontal-right"> <el-button @click="setRangeBut">设置</el-button> </el-form-item> </el-form> </el-card> </div>`, data() { return { status: localMKData.isUidRangeMaskingStatus(), head: 0, tail: 100 } }, methods: { setRangeBut() { this.$alert('设置成功'); gmUtil.setData('uid_range_masking', [this.head, this.tail]); } }, watch: { head(newVal, oldVal) { if (newVal > this.tail) { this.$message('最小值不能大于最大值'); this.head = oldVal; } }, tail(newVal, oldVal) { if (newVal < this.head) { this.$message('最大值不能小于最小值'); this.tail = oldVal; } }, status(n) { gmUtil.setData('uid_range_masking_status', n); } }, created() { const arr = localMKData.getUidRangeMasking(); this.head = arr[0]; this.tail = arr[1]; } }; const high_level_rule_vue = { components: { uid_range_masking_vue, }, template: ` <div> <uid_range_masking_vue/> <el-card> <template #header>视频类型</template> <div>选中的类型会被屏蔽</div> <el-radio-group v-model="copyrightRadioVal"> <el-radio-button label="原创"></el-radio-button> <el-radio-button label="转载"></el-radio-button> <el-radio-button label="不处理"></el-radio-button> </el-radio-group> <el-divider/> <el-switch v-model="is_vertical_val" active-text="屏蔽竖屏类视频"/> <el-switch v-model="blockFollowed" active-text="屏蔽已关注"/> <el-switch v-model="is_up_owner_exclusive" active-text="屏蔽充电专属视频"></el-switch> <el-switch v-model="is_senior_member_val" active-text="屏蔽硬核会员"/> <el-row> <el-col :span="12"> <el-card shadow="never"> <template #header>会员类型屏蔽</template> <el-radio-group v-model="vipTypeRadioVal"> <el-radio-button label="无"></el-radio-button> <el-radio-button label="月大会员"></el-radio-button> <el-radio-button label="年度及以上大会员"></el-radio-button> <el-radio-button label="不处理"></el-radio-button> </el-radio-group> </el-card> </el-col> <el-col :span="12"> <el-card shadow="never"> <template #header>性别屏蔽</template> <el-radio-group v-model="genderRadioVal"> <el-radio-button label="男性"></el-radio-button> <el-radio-button label="女性"></el-radio-button> <el-radio-button label="保密"></el-radio-button> <el-radio-button label="不处理"></el-radio-button> </el-radio-group> </el-card> </el-col> </el-row> </el-card> <el-card> <template #header>计算创作团队</template> <el-tooltip content="当作者未匹配上时检查其他成员"></el-tooltip> <el-switch v-model="is_check_team_member" active-text="检查创作团队中成员"/> </el-card> </div>`, data() { return { blockFollowed: localMKData.isBlockFollowed(), is_up_owner_exclusive: localMKData.isUpOwnerExclusive(), genderRadioVal: localMKData.isGenderRadioVal(), vipTypeRadioVal: localMKData.isVipTypeRadioVal(), is_senior_member_val: localMKData.isSeniorMember(), copyrightRadioVal: localMKData.isCopyrightRadio(), is_vertical_val: localMKData.isBlockVerticalVideo(), is_check_team_member: localMKData.isCheckTeamMember() } }, methods: {}, watch: { blockFollowed(n) { gmUtil.setData('blockFollowed', n); }, is_up_owner_exclusive(n) { gmUtil.setData('is_up_owner_exclusive', n); }, genderRadioVal(n) { gmUtil.setData('genderRadioVal', n); }, vipTypeRadioVal(n) { gmUtil.setData('vipTypeRadioVal', n); }, is_senior_member_val(n) { gmUtil.setData('is_senior_member', n); }, copyrightRadioVal(n) { gmUtil.setData('copyrightRadioVal', n); }, is_vertical_val(n) { gmUtil.setData('blockVerticalVideo', n); }, is_check_team_member(n) { gmUtil.setData('checkTeamMember', n); } } }; const card_slider_vue = { template: ` <div> <el-card shadow="never"> <template #header> <slot name="header"></slot> </template> <slot name="describe"></slot> <div style="display: flex; align-items: center"> <el-switch v-model="local_switchVal" :active-text="switchActiveText"/> <div style="flex: 1;margin-left: 15px"> <el-slider v-model="sliderVal" :step="step" :min="min" :max="max" show-input :range="range" :format-tooltip="formatTooltip" :disabled="disabled"></el-slider> </div> </div> </el-card> </div>`, props: { formatTooltip: { type: Function }, switchActiveText: {type: String, default: '启用'}, step: {type: Number, default: 1}, min: {type: Number, default: 0}, max: {type: Number, default: 100}, value: {type: Number, default: 0}, switchVal: {type: Boolean, default: false}, range: {type: Boolean, default: false} }, data() { return { local_switchVal: this.switchVal, disabled: !this.switchVal, sliderVal: this.value } }, methods: {}, watch: { value(n) { this.sliderVal = n; }, sliderVal(n) { this.$emit('input', n); }, disabled(n) { this.$emit('slider-disabled-change', n); }, switchVal(n) { this.local_switchVal = n; }, local_switchVal(n) { this.disabled = !n; this.$emit('update:switchVal', n); } } }; const video_metrics_filter_item_vue = { components: {card_slider_vue}, props: { headerTitle: {type: String}, describe: {type: String}, mkTypeRateKey: {type: String}, mkRateStatusKey: {type: String}, }, template: ` <div> <card_slider_vue v-model="ratioRateVal" :step="0.01" :min="0" :max="1" :switch-val.sync="rateBlockingStatus" :format-tooltip="reteFormatTooltip"> <template #header>{{ headerTitle }}</template> <template #describe>{{ describe }}</template> </card_slider_vue> </div>`, data() { return { rateBlockingStatus: gmUtil.getData(this.mkRateStatusKey, false), ratioRateVal: gmUtil.getData(this.mkTypeRateKey, 0.05), } }, methods: { reteFormatTooltip(val) { return (val * 100).toFixed(0) + '%' } }, watch: { ratioRateVal(n) { gmUtil.setData(this.mkTypeRateKey, n); }, rateBlockingStatus(n) { gmUtil.setData(this.mkRateStatusKey, n); } } }; const video_metrics_filter_vue = { components: {video_metrics_filter_item_vue}, template: ` <div> <el-card> <template #header>指标屏蔽(改动实时生效)</template> <video_metrics_filter_item_vue v-for="item in metricsFilterList" :key="item.headerTitle" :header-title="item.headerTitle" :describe="item.describe" :mk-rate-status-key="item.mkRateStatusKey" :mk-type-rate-key="item.mkTypeRateKey" /> </el-card> </div>`, data() { return { metricsFilterList: [ { headerTitle: '视频点赞率屏蔽', describe: '限制的点赞率,默认为2%,小于或等于值限时制的屏蔽该视频,公式【点赞率=点赞数/播放量*100】', mkRateStatusKey: 'video_like_rate_blocking_status', mkTypeRateKey: 'video_like_rate' }, { headerTitle: '视频互动率屏蔽', describe: '限制的占比率,默认为2%,小于或等于值限时制的屏蔽该视频,公式【(弹幕数+评论数)/播放数*100】', mkRateStatusKey: 'interactive_rate_blocking_status', mkTypeRateKey: 'interactive_rate' }, { headerTitle: '视频三连率屏蔽', describe: '限制的占比率,默认为2%,小于或等于值限时制的屏蔽该视频,公式【(收藏数+投币数+分享数)/播放数*100】', mkRateStatusKey: 'triple_rate_blocking_status', mkTypeRateKey: 'triple_rate' }, { headerTitle: '视频投币/点赞比(内容价值)屏蔽', describe: '限制的占比率,默认为2%,小于或等于值限时制的屏蔽该视频,投币成本较高,比值越高内容越优质。公式【投币数 / 获赞数】', mkRateStatusKey: 'coin_likes_ratio_rate_blocking_status', mkTypeRateKey: 'coin_likes_ratio_rate' } ] } } }; const saveTable = (tableData) => { const newList = []; for (let {status, r} of tableData) { if (r === null) { eventEmitter.send('el-alert', '表格内还有未设置时间范围的项,请先设置或删除才可以保存!'); return } const [startTime, endTime] = r; newList.push({ status, r: [startTime.getTime(), endTime.getTime()] }); } if (newList.length === 0) return; gmUtil.setData('time_range_masking', newList); eventEmitter.send('el-notify', { title: '保存成功', message: '已保存该时间范围屏蔽', type: 'success' }); }; const time_range_masking_table_vue = { template: ` <div> <el-table :data="tableData" stripe border> <el-table-column label="状态" width="120px"> <template v-slot="scope"> <el-switch v-model="scope.row.status" active-text="启用" @change="tableSwitchChange(scope.row)"/> </template> </el-table-column> <el-table-column label="时间范围" width="400px"> <template v-slot="scope"> <el-date-picker @change="tableDatePickerChange(scope.row)" v-model="scope.row.r" type="datetimerange" :picker-options="pickerOptions" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"> </el-date-picker> <el-tag>{{ scope.row.r }}</el-tag> </template> </el-table-column> <el-table-column> <template #header> <el-button type="info" @click="addBut">添加</el-button> <el-button @click="refreshTableData">刷新</el-button> <el-button @click="saveTableBut">保存</el-button> </template> <template v-slot="scope"> <el-button type="warning" @click="delItemBut(scope.row)">删除</el-button> </template> </el-table-column> </el-table> </div>`, data() { return { tableData: [], pickerOptions: { shortcuts: [ { text: '最近一周', onClick(picker) { const end = new Date(); const start = new Date(); start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); picker.$emit('pick', [start, end]); } }, { text: '最近一个月', onClick(picker) { const end = new Date(); const start = new Date(); start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); picker.$emit('pick', [start, end]); } }, { text: '最近三个月', onClick(picker) { const end = new Date(); const start = new Date(); start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); picker.$emit('pick', [start, end]); } } ] } } }, methods: { refreshTableData() { if (this.tableData.length > 0) { this.tableData.splice(0, this.tableData.length); } const timeRangeMaskingArr = localMKData.getTimeRangeMaskingArr(); if (timeRangeMaskingArr.length !== 0) { let index = 0; for (let {status, r} of timeRangeMaskingArr) { this.tableData.push({ index: index++, status, r: [new Date(r[0]), new Date(r[1])], startTimeStamp: r[0], endTimeStamp: r[1], }); } } }, restoreTheLastTimeRange(row) { let {startTimeStamp, endTimeStamp} = row; console.log('上次时间戳', startTimeStamp, endTimeStamp); if (startTimeStamp === null || startTimeStamp === undefined) { row.r = null; return } row.r = [new Date(startTimeStamp), new Date(endTimeStamp)]; console.log('已恢复上次时间范围', row); }, tableDatePickerChange(row) { const rowR = row.r; if (rowR === null) return let {oldStartTimeStamp, oldEndTimeStamp} = row; const newStartTimeStamp = rowR[0].getTime(); const newEndTimeStamp = rowR[1].getTime(); const comparisonSTS = newStartTimeStamp || oldStartTimeStamp; const comparisonETS = newEndTimeStamp || oldEndTimeStamp; for (let v of this.tableData) { if (v.r === null) continue; if (v.index === row.index) continue; const tempStartTimeStamp = v.r[0].getTime(); const tempEndTimeStamp = v.r[1].getTime(); if (tempStartTimeStamp === comparisonSTS && tempEndTimeStamp === comparisonETS) { this.$alert('已存在该时间范围屏蔽'); this.restoreTheLastTimeRange(row); return; } if (comparisonSTS >= tempStartTimeStamp && comparisonETS <= tempEndTimeStamp) { this.$alert('小于已添加过的时间范围'); this.restoreTheLastTimeRange(row); return; } } row.startTimeStamp = newStartTimeStamp; row.endTimeStamp = newEndTimeStamp; saveTable(this.tableData); }, tableSwitchChange(row) { if (row.r === null) return saveTable(this.tableData); }, addBut() { const length = this.tableData.length; this.tableData.push({ index: length, status: true, r: null, startTimeStamp: null, endTimeStamp: null }); this.$notify({message: '已添加一条时间范围屏蔽到底部'}); }, delItemBut(row) { if (row.startTimeStamp === null) { this.tableData.splice(row.index, 1); return; } for (let {r} of this.tableData) { if (r === null) { this.$alert('表格内还有未设置时间范围的项,请先设置或删除才可以保存!'); return } } this.$confirm('确定删除该条时间范围屏蔽吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.tableData.splice(row.index, 1); saveTable(this.tableData); this.$message({ type: 'success', message: '删除成功!' }); }); }, saveTableBut() { saveTable(this.tableData); } }, created() { this.refreshTableData(); } }; const time_range_masking_vue = { components: {time_range_masking_table_vue}, template: ` <div> <el-card> <template #header>时间范围</template> <div>使用说明</div> <div>1.不能添加重复的时间范围或者小于已添加过的时间范围</div> <div>2.修改时间范围的值和状态会自动保存(包括删除),会有提示</div> <div>3.每个时间范围可独立控制开关状态,关闭则该条范围不生效</div> <div>4.总开关优先级最高,关闭则所有时间范围不生效</div> <el-switch v-model="status" active-text="总开关"/> </el-card> <time_range_masking_table_vue/> </div>`, data() { return { status: localMKData.isTimeRangeMaskingStatus() } }, methods: { }, watch: { status(n) { this.$notify({ message: n ? '时间范围屏蔽已开启' : '时间范围屏蔽已关闭', type: n ? 'success' : 'warning' }); gmUtil.setData('time_range_masking_status', n); } }, created() { } }; const repl_processing_vue = { template: ` <div> <el-card shadow="never"> <template #header>说明 <el-row> <el-col :span="12"> <div>1.评论内容暂不支持替换表情</div> <div>2.如修改后或添加数据需保存方可生效</div> <div>3.暂不支持标题替换</div> <div>4.支持正则替换, <el-link target="_blank" type="primary" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll"> 详情参考js中的replaceAll用法 </el-link> </div> <div>5.搜索暂时先用快捷键ctrl+f代替</div> <div>6.作用域中,选择了评论表情再选其他之前需要取消选项评论表情</div> <div>7.评论表情,查找时要用英文输入法[]包裹表情关键词,留空为评论中移除该表情。反之替换普通文本内容。 <el-link target="_blank" href="https://docs.qq.com/doc/DSlJNR1NVcGR3eEto" title="页面中用搜索定位表情包对照表" type="primary">表情包对照表 </el-link> </div> </el-col> <el-col :span="12"> <el-card shadow="never"> <template #header>全局</template> <el-tooltip content="当该选项未启用时下面表格中的不生效"> <el-switch active-text="启用" v-model="enableReplacementProcessingVal"/> </el-tooltip> <el-switch v-model="clearCommentEmoticonsVal" active-text="清除评论表情"/> <el-tooltip content="将评论中的蓝色关键词带搜索小图标的内容替换成普通文本内容"> <el-switch v-model="isReplaceCommentSearchTermsVal" active-text="替换评论搜索词"/> </el-tooltip> </el-card> </el-col> </el-row> </template> </el-card> <el-table :data="tableData" border stripe> <el-table-column label="作用域" width="450px"> <template v-slot="scope"> <el-checkbox-group v-model="scope.row.actionScopes" @change="actionScopesChange"> <el-checkbox disabled border label="视频标题"/> <el-checkbox border label="评论内容"/> <el-checkbox border label="评论表情"/> </el-checkbox-group> </template> </el-table-column> <el-table-column label="查找"> <template v-slot="scope"> <el-input v-model="scope.row.findVal" maxlength="10" clearable @change="verifyDuplicate"/> </template> </el-table-column> <el-table-column label="替换"> <template v-slot="scope"> <el-input v-model="scope.row.replaceVal" maxlength="10" clearable/> </template> </el-table-column> <el-table-column label="操作"> <template #header> <el-button @click="addBut">添加</el-button> <el-button @click="refreshBut">刷新</el-button> <el-button type="success" @click="saveBut">保存</el-button> </template> <template v-slot="scope"> <el-button type="warning" @click="delItemBut(scope.row,scope.$index)">删除</el-button> </template> </el-table-column> </el-table> </div>`, data() { return { tableData: getSubstituteWordsArr(), enableReplacementProcessingVal: enableReplacementProcessing(), clearCommentEmoticonsVal: isClearCommentEmoticons(), isReplaceCommentSearchTermsVal: isReplaceCommentSearchTerms() } }, methods: { validate(item) { if (item.actionScopes.length === 0) { this.$message.error('请选择作用域再后续处理'); return } if (item.findVal === '') { this.$message.error('请输入查找内容再后续处理'); return } return true }, verifyDuplicate(val) { if (val === '') return; const set = new Set(); for (const v of this.tableData) { if (set.has(v.findVal)) { this.$alert(`已添加过该查找值,不可重复添加【${v.findVal}】`, '错误', { type: 'error' }); return; } set.add(v.findVal); } }, addBut() { this.tableData.unshift({ actionScopes: ['评论内容'], findVal: '', replaceVal: '' }); this.$notify({message: '已添加一条替换处理到顶部'}); }, delItemBut(row, index) { if (row.findVal === '' && row.replaceVal === '') { this.tableData.splice(index, 1); this.$notify({message: '已删除一条替换处理'}); return; } if (this.validate(row) !== true) return; this.$confirm('确定删除该条替换处理吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.tableData.splice(index, 1); this.$notify({message: '已删除一条替换处理'}); }); }, refreshBut() { this.tableData = getSubstituteWordsArr(); this.$message.info('已刷新'); }, saveBut() { if (this.tableData.length === 0) { this.$message.error('请先添加数据再保存!'); return } for (let item of this.tableData) { if (this.validate(item) !== true) return; } const duplicateRemoval = new Set(); for (const v of this.tableData) { if (duplicateRemoval.has(v.findVal)) { this.$alert(`查找内容不能重复【${v.findVal}】`, '错误', { type: 'error' }); return; } duplicateRemoval.add(v.findVal); } gmUtil.setData('substitute_words', this.tableData); this.$message.success('已保存'); }, actionScopesChange(newArr) { if (newArr.length === 0) return; if (newArr.some(v => v === '评论表情')) { newArr.splice(0, newArr.length, '评论表情'); } } }, watch: { clearCommentEmoticonsVal(n) { gmUtil.setData('is_clear_comment_emoticons', n); }, isReplaceCommentSearchTermsVal(n) { gmUtil.setData('is_replace_comment_search_terms', n); } } }; var ruleManagementVue = { components: { rule_export_import_vue, other_parameter_filter, rule_information_vue, conditionalityVue, basic_rules_vue, blacklist_management_vue, high_level_rule_vue, video_metrics_filter_vue, time_range_masking_vue, repl_processing_vue }, template: ` <div> <el-tabs type="border-card" tab-position="left"> <el-tab-pane label="基础规则"> <basic_rules_vue/> </el-tab-pane> <el-tab-pane label="高级规则" lazy> <high_level_rule_vue/> </el-tab-pane> <el-tab-pane label="其他规则" lazy> <other_parameter_filter/> </el-tab-pane> <el-tab-pane label="指标屏蔽" lazy> <video_metrics_filter_vue/> </el-tab-pane> <el-tab-pane label="范围屏蔽" lazy> <time_range_masking_vue/> </el-tab-pane> <el-tab-pane label="替换处理" lazy> <repl_processing_vue/> </el-tab-pane> <el-tab-pane label="导出导入" lazy> <rule_export_import_vue/> </el-tab-pane> <el-tab-pane label="条件限制" lazy> <conditionalityVue/> </el-tab-pane> <el-tab-pane label="规则信息"> <rule_information_vue/> </el-tab-pane> <el-tab-pane label="黑名单管理" lazy> <blacklist_management_vue/> </el-tab-pane> </el-tabs> </div>`, data() { return {} } }; const compatible_setting_vue = { template: ` <div> <el-card> <template #header>说明</template> <div>如果用户没有安装并使用对应脚本或插件,就不要开启相关兼容选项</div> </el-card> <el-card> <template #header>Bilibili-Gate脚本(bilibili-app-recommend)</template> <el-switch v-model="adaptationBAppRecommend" active-text="首页屏蔽适配"/> </el-card> <el-card> <template #header>BewlyBewly插件</template> <el-switch v-model="compatible_BEWLY_BEWLY" active-text="首页适配"/> </el-card> <el-card> <template #header>评论区</template> 使用之后需刷新对应页面才可生效,勾选即评论区使用新版获取方式,不再使用旧版方式 <div> <el-switch v-model="discardOldCommentAreasV" active-text="弃用旧版评论区处理"/> </div> </el-card> </div>`, data() { return { adaptationBAppRecommend: globalValue.adaptationBAppCommerce, compatible_BEWLY_BEWLY: globalValue.compatibleBEWLYBEWLY, discardOldCommentAreasV: localMKData.isDiscardOldCommentAreas() } }, watch: { adaptationBAppRecommend(newVal) { gmUtil.setData("adaptation-b-app-recommend", newVal === true); }, compatible_BEWLY_BEWLY(newVal) { gmUtil.setData("compatible_BEWLY_BEWLY", newVal === true); }, discardOldCommentAreasV(newVal) { gmUtil.setData("discardOldCommentAreas", newVal === true); } } }; const mk_db = new Dexie('mk-db'); mk_db.version(1).stores({ videoInfos: 'bv,tags,userInfo,videoInfo', }); const addVideoData = async (bv, data) => { const {tags, userInfo, videoInfo} = data; try { await mk_db.videoInfos.add({ bv, tags, userInfo, videoInfo }); } catch (e) { console.log(`添加视频数据失败`, data, e); return false } return true }; const bulkImportVideoInfos = async (friendsData) => { try { const lastKeyItem = await mk_db.videoInfos.bulkPut(friendsData); console.info('批量导入成功,最后一个插入的主键:', lastKeyItem); return {state: true, lastKeyItem} } catch (error) { console.error('批量导入时出错:', error); return {state: false, error} } }; const getVideoInfo = async () => { return await mk_db.videoInfos.toArray() }; const getVideoInfoCount = async () => { return await mk_db.videoInfos.count() }; const findVideoInfoByBv = async (bv) => { return await mk_db.videoInfos.get(bv) }; const clearVideoInfosTable = async () => { try { await mk_db.videoInfos.clear(); return true } catch (e) { console.log('清除videoInfos表失败', e); return false } }; const delVideoInfoItem = async (bv) => { try { const item = await findVideoInfoByBv(bv); if (!item) return false; await mk_db.videoInfos.delete(bv); return true } catch (e) { return false } }; const bulkDelVideoInfoItem = async (bvArr) => { const data = {state: false, success: [], fail: []}; try { const existingItem = await mk_db.videoInfos.bulkGet(bvArr); const existingKeys = existingItem.filter(item => item).map(item => item.bv); if (existingKeys.length === 0) { data.fail = bvArr; return data } data.state = true; data.success.push(...existingKeys); if (existingKeys.length !== bvArr.length) { data.fail.push(...bvArr.filter(item => !existingKeys.includes(item))); } await mk_db.videoInfos.bulkDelete(bvArr); return data } catch (e) { console.log('批量删除数据库中指定bv号失败:', e); return data } }; var bvDexie = { addVideoData, clearVideoInfosTable, bulkImportVideoInfos, getVideoInfo, getVideoInfoCount, delVideoInfoItem, bulkDelVideoInfoItem }; const cache_management_vue = { template: ` <div> <el-card> <template #header>说明</template> <div>1.每个域名中的缓存数据不同</div> <div>2.仅仅支持导入json格式</div> <div>3.下面导入默认追加模式</div> <div>4.当前域名 <el-tag>{{ hostname }}</el-tag> </div> </el-card> <el-card> <template #header>操作</template> <el-button @click="inputFIleBut">追加导入视频缓存数据</el-button> <input ref="inputDemo" type="file" @change="handleFileUpload" accept="application/json" style="display: none"> <el-button @click="clearPageVideoCacheDataBut">清空当前域名的视频缓存数据</el-button> <el-button @click="lookContentBut">查看内容</el-button> <el-button @click="lookContentLenBut">查看数据量</el-button> <el-button type="warning" @click="batchDelBut">批量删除</el-button> </el-card> <el-card> <template #header>导出</template> <el-button @click="outDbDataBut">导出至文件</el-button> <el-button @click="outToConsoleBut">导出至控制台</el-button> </el-card> </div>`, data() { return { hostname: window.location.hostname } }, methods: { outDbDataBut() { bvDexie.getVideoInfo().then((data) => { if (data.length === 0) { this.$message('当前域名下没有缓存视频数据'); return } data = { hostName: this.hostname, size: data.length, data: data }; defUtil.fileDownload(JSON.stringify(data, null, 4), 'mk-db-videoInfos-cache.json'); this.$message('已导出当前域名的缓存数据'); console.log(data); }); }, handleFileUpload(event) { defUtil.handleFileReader(event).then(data => { const {content} = data; let parse; try { parse = JSON.parse(content); } catch (e) { this.$message('文件内容有误'); return; } const {hostName = null, videoInfos = []} = parse; if (!hostName) { this.$message('hostName字段不存在'); return; } if (!defUtil.isIterable(videoInfos)) { this.$message('文件内容有误,非可迭代的数组!'); return; } if (videoInfos.length === 0) { this.$message('tags数据为空'); return; } for (let item of videoInfos) { if (!item['bv']) { this.$message('bv字段不存在'); return; } if (!item['tags']) { this.$message('tags字段不存在'); return; } if (!item['userInfo']) { this.$message('userInfo字段不存在'); return; } if (!item['videoInfo']) { this.$message('videoInfo字段不存在'); return; } } bvDexie.bulkImportVideoInfos(videoInfos).then((bool) => { if (bool) { this.$message('导入成功'); } else { this.$message('导入失败'); } }); }); }, inputFIleBut() { this.$refs.inputDemo.click(); }, clearPageVideoCacheDataBut() { this.$confirm('是否清空当前域名下的tags数据').then(() => { bvDexie.clearVideoInfosTable().then((bool) => { if (bool) { this.$message('已清空当前域名下的视频缓存数据'); } else { this.$message('清空失败'); } }); }); }, lookContentBut() { this.$confirm('当数据量过大时,可能卡顿,等待时间会较为长,是要继续吗').then(async () => { const loading = this.$loading({text: "获取中..."}); const r = await bvDexie.getVideoInfo(); loading.close(); eventEmitter.send('展示内容对话框', JSON.stringify(r)); this.$message('获取成功'); }); }, outToConsoleBut() { bvDexie.getVideoInfo().then(r => { this.$alert('已导出至控制台上,可通过f12等方式查看'); const hostname = this.hostname; console.log(`${hostname}的视频数据===start`); console.log(r); console.log(`${hostname}的视频数据=====end`); }); }, lookContentLenBut() { bvDexie.getVideoInfoCount().then((len) => { this.$alert(`数据量${len}`); }); }, batchDelBut() { this.$prompt('请输入删除的bv号,多个bv号用逗号隔开', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', }).then(async ({value}) => { value = value?.trim() || null || ""; if (value === null) return; const bvs = value.split(','); if (bvs.length === 1) { const bool = await bvDexie.delVideoInfoItem(bvs[0]); if (bool) { this.$message.success(`删除${value}的视频缓存数据成功`); } else { this.$message.warning(`删除失败,未找到${value}的视频缓存数据`); } return } const data = await bvDexie.bulkDelVideoInfoItem(bvs); if (data.state) { if (data.success.length === bvs.length) { this.$alert(`删除${data.success.join(',')}的视频缓存数据成功`, { type: 'success' }); } else { this.$alert(`删除${data.success.join(',')}的视频缓存数据成功,${data.fail.join(',')}的视频缓存数据未找到`, { type: 'warning' }); } } else { this.$message.warning(`删除失败,错误信息请看控制台`); } }); } }, created() { } }; var donateLayoutVue = { template: ` <div> <el-card shadow="hover"> <template #header> <span>零钱赞助</span> </template> <span>1元不嫌少,10元不嫌多,感谢支持!</span> <el-divider/> <span>生活不易,作者叹息</span> <el-divider/> <span>用爱发电不容易,您的支持是我最大的更新动力</span> </el-card> <el-divider/> <div class="el-vertical-center" @click="gotoAuthorBut"> <el-avatar size="large" src="//i0.hdslb.com/bfs/face/87e9c69a15f7d2b68294be165073c8e07a541e28.jpg@128w_128h_1c_1s.webp"></el-avatar> </div> <div class="el-vertical-center"> <el-button round type="primary" @click="showDialogBut">打赏点猫粮</el-button> </div> <el-dialog center :title="dialogIni.title" :visible.sync="dialogIni.show"> <div class="el-vertical-center"> <el-image v-for="item in list" :src="item.src" style="height: 300px" :preview-src-list="dialogIni.srcList"/> </div> </el-dialog> </div>`, data() { return { list: [ { name: "支付宝赞助", alt: "支付宝支持", src: "https://www.mikuchase.ltd/img/paymentCodeZFB.webp" }, {name: "微信赞助", alt: "微信支持", src: "https://www.mikuchase.ltd/img/paymentCodeWX.webp"}, {name: "QQ赞助", alt: "QQ支持", src: "https://www.mikuchase.ltd/img/paymentCodeQQ.webp"}, ], dialogIni: { title: "打赏点猫粮", show: false, srcList: [] } } }, methods: { showDialogBut() { this.dialogIni.show = true; }, gotoAuthorBut() { gmUtil.openInTab(globalValue.b_url); } }, created() { this.dialogIni.srcList = this.list.map(x => x.src); } }; const outputInformationFontColor$1 = localMKData.getOutputInformationFontColor(); const highlightInformationColor$1 = localMKData.getHighlightInformationColor(); var outputInformationVue = { template: ` <div> <el-button type="info" @click="clearInfoBut">清空消息</el-button> <div v-for="item in outputInfoArr" v-html="item"></div> </div>`, data() { return { outputInfoArr: [], } }, methods: { clearInfoBut() { this.$confirm('是否清空信息', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.outputInfoArr = []; this.$message('已清空信息'); }); } }, created() { eventEmitter.on('打印信息', (content) => { const liEL = document.createElement("li"); liEL.innerHTML = content; this.outputInfoArr.push(liEL.innerHTML); }); eventEmitter.on('屏蔽视频信息', (type, matching, videoData) => { const toTimeString = defUtil.toTimeString(); const {name, uid, title, videoUrl} = videoData; const info = `<b style="color: ${outputInformationFontColor$1}; " gz_bezel> ${toTimeString}-根据${type}-${matching ? `<b style="color: ${highlightInformationColor$1}">【${matching}】</b>` : ""}-屏蔽用户【${name}】uid= <a href="https://space.bilibili.com/${uid}" style="color: ${highlightInformationColor$1}" target="_blank">【${uid}】</a> 标题【<a href="${videoUrl}" target="_blank" style="color: ${highlightInformationColor$1}">${title}</a>】 </b>`; this.outputInfoArr.push(info); }); eventEmitter.on('屏蔽评论信息', (type, matching, commentData) => { const toTimeString = defUtil.toTimeString(); const {name, uid, content} = commentData; this.outputInfoArr.push(`<b style="color: ${outputInformationFontColor$1}; " gz_bezel> ${toTimeString}-根据${type}-${matching ? `<b style="color: ${highlightInformationColor$1}">【${matching}】</b>` : ""}-屏蔽用户【${name}】uid= <a href="https://space.bilibili.com/${uid}" style="color: ${highlightInformationColor$1}" target="_blank">【${uid}】</a> 评论【${content}】 </b>`); }); eventEmitter.on('正则匹配时异常', (errorData) => { const {msg, e} = errorData; this.outputInfoArr.push(msg); console.error(msg); throw new Error(e) }); } }; const look_content_dialog_vue = { template: ` <div> <el-dialog :fullscreen="true" title="提示" :visible.sync="dialogVisible" width="30%" :before-close="handleClose"> <el-input autosize type="textarea" v-model="content"></el-input> <span slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false">取 消</el-button> <el-button type="primary" @click="dialogVisible = false">确 定</el-button> </span> </el-dialog> </div>`, data() { return { dialogVisible: false, content: '' } }, methods: { handleClose(done) { this.$confirm('确认关闭?') .then(_ => { done(); }) .catch(_ => { }); } }, created() { eventEmitter.on('展示内容对话框', (newContent) => { this.content = newContent; this.$message('已更新内容'); this.dialogVisible = true; }); } }; class ValueCache { #mapCache = new Map(); set(key, value) { this.#mapCache.set(key, value); return value; } get(key, defaultValue = null) { const newVar = this.#mapCache.get(key); if (newVar) { return newVar; } return defaultValue; } getAll() { return this.#mapCache; } } const valueCache = new ValueCache(); var video_zone = { "动画": [ "MAD·AMV", "MMD·3D", "短片·手书", "配音", "手办·模玩", "特摄", "动漫杂谈" ], "番剧": [ "资讯", "官方延伸", "完结动画" ], "国创": [ "国产动画", "国产原创相关", "布袋戏", "资讯" ], "音乐": [ "原创音乐", "翻唱", "VOCALOID·UTAU", "演奏", "MV", "音乐现场", "音乐综合", "乐评盘点", "音乐教学" ], "舞蹈": [ "宅舞", "舞蹈综合", "舞蹈教程", "街舞", "明星舞蹈", "国风舞蹈" ], "游戏": [ "单机游戏", "电子竞技", "手机游戏", "网络游戏", "桌游棋牌", "GMV", "音游" ], "知识": [ "科学科普", "社科·法律·心理(原社科人文、原趣味科普人文)", "人文历史", "财经商业", "校园学习", "职业职场", "设计·创意", "野生技术协会", "演讲·公开课(已下线)", "星海(已下线)" ], "科技": [ "数码(原手机平板)", "软件应用", "计算机技术", "科工机械 (原工业·工程·机械)", "极客DIY", "电脑装机(已下线)", "摄影摄像(已下线)" ], "运动": [ "篮球", "足球", "健身", "竞技体育", "运动文化" ], "汽车": [ "汽车知识科普", "赛车", "改装玩车", "新能源车", "房车", "摩托车", "购车攻略", "汽车生活", "汽车文化(已下线)", "汽车极客(已下线)" ], "生活": [ "搞笑", "出行", "三农", "家居房产", "手工", "绘画", "日常", "亲子", "美食圈(重定向)", "动物圈(重定向)", "运动(重定向)", "汽车(重定向)" ], "美食": [ "美食制作(原[生活]->[美食圈])", "美食侦探", "美食测评", "田园美食" ], "动物圈": [ "喵星人", "汪星人", "动物二创", "野生动物", "小宠异宠" ], "鬼畜": [ "鬼畜调教", "音MAD", "人力VOCALOID", "鬼畜剧场" ], "时尚": [ "美妆护肤", "仿妆cos", "穿搭", "时尚潮流", "健身(重定向)" ], "资讯": [ "热点", "环球", "社会" ], "广告": [], "娱乐": [ "综艺", "娱乐杂谈", "粉丝创作", "明星综合" ], "影视": [ "影视杂谈", "影视剪辑", "小剧场", "预告·资讯" ], "纪录片": [ "人文·历史", "科学·探索·自然", "军事" ], "电影": [ "华语电影", "欧美电影", "日本电影" ], "电视剧": [ "国产剧" ] }; const findKey = (itemKey) => { for (let key in video_zone) { const arr = video_zone[key]; if (arr.some((i) => i === itemKey)) return key; } return null; }; var video_zoneData = {findKey}; const fetchGetBarrageBlockingWords = () => { return new Promise((resolve, reject) => { fetch('https://api.bilibili.com/x/dm/filter/user', { credentials: 'include' }) .then(response => response.json()) .then(({code, data, message}) => { if (code !== 0) { reject({state: false, msg: `请求相应内容失败:msg=${message} code=` + code}); return } const {rule} = data; const list = []; for (let r of rule) { const {type, filter, ctime} = r; if (type === 2) { continue } list.push({type, filter, ctime}); } resolve({state: true, data, list, msg: '获取成功'}); }); }) }; const fetchGetVideoInfo = async (bvId) => { const response = await fetch(`https://api.bilibili.com/x/web-interface/view/detail?bvid=${bvId}`); if (response.status !== 200) { eventEmitter.send('请求获取视频信息失败', response, bvId); return {state: false, msg: '网络请求失败', data: response} } const {code, data, message} = await response.json(); const defData = {state: false, msg: '默认失败信息'}; if (code !== 0) { defData.msg = message; return defData } defData.state = true; defData.msg = '获取成功'; const { View: { staff, tname, tname_v2, desc, pubdate, ctime, copyright, is_upower_exclusive, duration, dimension, stat: { view, danmaku, reply, favorite, coin, share, like }, }, Card: { follower, like_num, archive_count, following, article_count, card: { mid: uid, name, sex, level_info: { current_level }, pendant, nameplate, Official, official_verify, vip, sign, is_senior_member } }, Tags, participle } = data; const videoInfo = { staff, tname, tname_v2, desc, pubdate, ctime, copyright, is_upower_exclusive, duration, view, danmaku, reply, favorite, coin, share, participle, dimension, like }; const userInfo = { follower, like_num, archive_count, article_count, Official, official_verify, vip, uid, name, sex, current_level, pendant, nameplate, following, sign, is_senior_member }; const tags = []; for (let tag of Tags) { tags.push(tag['tag_name']); } tags.unshift(tname, tname_v2); const findKey = video_zoneData.findKey(tname); if (findKey) { tags.unshift(findKey); } defData.data = {videoInfo, userInfo, tags}; return defData }; var bFetch = { fetchGetVideoInfo, fetchGetBarrageBlockingWords }; const bAfterLoadingThePageOpenMainPanel = () => { return gmUtil.getData('bAfterLoadingThePageOpenMainPanel', false) }; const setBAfterLoadingThePageOpenMainPanel = (b) => { gmUtil.setData('bAfterLoadingThePageOpenMainPanel', b === true); }; const debugger_management_vue = { template: ` <div> <el-tabs tab-position="left"> <el-tab-pane label="基础"> <el-card shadow="never"> <template #header><span>测试</span></template> <el-button @click="demoBut">测试网络请求</el-button> <el-button @click="fetchGetVideoInfoBut">请求获取视频信息</el-button> <el-button @click="printValueCacheBut">打印valueCache值</el-button> <el-button @click="printEventBut">打印事件中心值</el-button> <el-button @click="printReqIntervalQueueVal">打印requestIntervalQueue值</el-button> <el-divider/> <el-switch v-model="bAfterLoadingThePageOpenMainPanel" active-text="加载完页面打开主面板"/> </el-card> </el-tab-pane> </el-tabs> </div>`, data() { return { bAfterLoadingThePageOpenMainPanel: bAfterLoadingThePageOpenMainPanel() } }, methods: { printValueCacheBut() { console.log(valueCache.getAll()); }, demoBut() { bFetch.fetchGetVideoInfo('BV152cWeXEhW').then(data => { console.log(data); debugger }); }, fetchGetVideoInfoBut() { this.$prompt('请输入视频bv号', { title: '请输入视频bv号', confirmButtonText: '确定', cancelButtonText: '取消', inputPattern: /^BV[A-Za-z0-9]{10}$/, inputErrorMessage: '请输入正确的视频bv号' }).then(({value}) => { bFetch.fetchGetVideoInfo(value).then(data => { console.log(data); debugger }); }); }, printEventBut() { console.log(eventEmitter.getEvents()); }, printReqIntervalQueueVal() { console.log(requestIntervalQueue); } }, watch: { bAfterLoadingThePageOpenMainPanel(b) { setBAfterLoadingThePageOpenMainPanel(b); } } }; const getUrlUID = (url) => { let uid; if (url.startsWith('http')) { const parseUrl = defUtil.parseUrl(url); uid = parseUrl.pathSegments[0]?.trim(); return parseInt(uid) } const isDoYouHaveAnyParameters = url.indexOf('?'); const lastIndexOf = url.lastIndexOf("/"); if (isDoYouHaveAnyParameters === -1) { if (url.endsWith('/')) { const nTheIndexOfTheLastSecondOccurrenceOfTheSlash = url.lastIndexOf('/', url.length - 2); uid = url.substring(nTheIndexOfTheLastSecondOccurrenceOfTheSlash + 1, url.length - 1); } else { uid = url.substring(lastIndexOf + 1); } } else { uid = url.substring(lastIndexOf + 1, isDoYouHaveAnyParameters); } return parseInt(uid); }; const getUrlBV = (url) => { let match = url.match(/video\/(.+)\//); if (match === null) { match = url.match(/video\/(.+)\?/); } if (match === null) { match = url.match(/video\/(.+)/); } return match?.[1]?.trim() || null; }; function findElementUntilFound(selector, config = {}) { const defConfig = { doc: document, interval: 1000, timeout: -1, }; config = {...defConfig, ...config}; return new Promise((resolve, reject) => { const i1 = setInterval(() => { const element = config.doc.querySelector(selector); if (element) { resolve(element); clearInterval(i1); } }, config.interval); if (config.timeout > 0) { setTimeout(() => { clearInterval(i1); reject(null); // 超时则返回 null }, config.timeout); } }); } const findElement = async (selector, config = {}) => { try { const defConfig = { doc: document, interval: 1000, timeout: -1, }; config = {...defConfig, ...config}; const el = await findElementUntilFound(selector, config); if (config.timeout === -1) { return el } return {state: true, data: el} } catch (e) { return {state: false, data: e} } }; const findElements = async (selector, config = {}) => { const defConfig = {doc: document, interval: 1000, timeout: -1}; config = {...defConfig, ...config}; try { const elList = await findElementsUntilFound(selector, config); if (config.timeout === -1) { return elList } return {state: true, data: elList} } catch (e) { return {state: false, data: e} } }; function findElementsUntilFound(selector, config = {}) { const defConfig = {doc: document, interval: 1000, timeout: -1}; config = {...defConfig, ...config}; return new Promise((resolve, reject) => { const i1 = setInterval(() => { const elements = config.doc.querySelectorAll(selector); if (elements.length > 0) { resolve(Array.from(elements)); clearInterval(i1); } }, config.interval); if (config.timeout > 0) { setTimeout(() => { clearInterval(i1); reject(null); // 超时则返回 null }, config.timeout); } }); } const findElementsAndBindEvents = (css, callback, config = {}) => { config = { ...{ interval: 2000, timeOut: 3000 }, config }; setTimeout(() => { findElementUntilFound(css, {interval: config.interval}).then((el) => { el.addEventListener("click", () => { callback(); }); }); }, config.timeOut); }; const updateCssVModal = () => { findElement('.v-modal').then(() => { const styleEl = document.createElement('style'); styleEl.innerHTML = `.v-modal { z-index: auto !important; }`; document.head.appendChild(styleEl); }); }; var elUtil = { getUrlUID, getUrlBV, findElement, findElements, findElementUntilFound, findElementsUntilFound, findElementsAndBindEvents, updateCssVModal }; const setTopInputPlaceholder = async () => { if (globalValue.compatibleBEWLYBEWLY) { return } const placeholder = valueCache.get('topInputPlaceholder'); if (placeholder === null) { return } const targetInput = await elUtil.findElement('.nav-search-input'); targetInput.placeholder = placeholder; eventEmitter.send('el-notify', { title: "tip", message: '已恢复顶部搜索框提示内容', position: 'bottom-right', }); }; const processTopInputContent = async () => { if (globalValue.compatibleBEWLYBEWLY) { return } if (!gmUtil.getData('isClearTopInputTipContent', false)) { return; } const targetInput = await elUtil.findElement('.nav-search-input'); if (targetInput.placeholder === '') { await defUtil.wait(1500); await processTopInputContent(); return } valueCache.set('topInputPlaceholder', targetInput.placeholder); targetInput.placeholder = ''; eventEmitter.send('el-msg', '清空了搜索框提示内容'); }; eventEmitter.on('执行清空顶部搜索框提示内容', () => { processTopInputContent(); }); var topInput = {processTopInputContent, setTopInputPlaceholder}; const page_processing_vue = { template: ` <div> <el-card> <template #header> <span>搜索页</span> </template> <el-switch v-model="isRemoveSearchBottomContent" active-text="屏蔽底部额外内容"/> </el-card> <el-card> <template #header> <span>播放页</span> </template> <el-switch v-model="isDelPlayerPageAd" active-text="屏蔽页面元素广告"/> <el-switch v-model="isDelPlayerPageRightGameAd" active-text="屏蔽右侧游戏推荐"/> <el-tooltip content="移除整个推荐列表,状态刷新生效"> <el-switch v-model="isDelPlayerPageRightVideoList" active-text="移除右侧推荐列表"/> </el-tooltip> <el-tooltip content="状态刷新生效"> <el-switch v-model="isDelBottomComment" active-text="移除评论区"/> </el-tooltip> <el-tooltip content="视频播放完之后会在播放器上显示推荐内容,开启之后移除播放器上整个推荐内容"> <el-switch v-model="isDelPlayerEndingPanelVal" active-text="移除播放完推荐层"/> </el-tooltip> </el-card> <el-card> <template #header> <span>顶部搜索框</span> </template> <el-switch v-model="isClearTopInputTipContent" active-text="清空内容"/> </el-card> </div>`, data() { return { isRemoveSearchBottomContent: gmUtil.getData('isRemoveSearchBottomContent', false), isDelPlayerPageAd: gmUtil.getData('isDelPlayerPageAd', false), isDelPlayerPageRightGameAd: gmUtil.getData('isDelPlayerPageRightGameAd', false), isDelPlayerPageRightVideoList: localMKData.isDelPlayerPageRightVideoList(), isDelBottomComment: localMKData.isDelBottomComment(), isClearTopInputTipContent: gmUtil.getData('isClearTopInputTipContent', false), isDelPlayerEndingPanelVal: localMKData.isDelPlayerEndingPanel() } }, methods: {}, watch: { isRemoveSearchBottomContent(b) { gmUtil.setData('isRemoveSearchBottomContent', b); }, isDelPlayerPageAd(b) { gmUtil.setData('isDelPlayerPageAd', b); }, isDelPlayerPageRightGameAd(b) { gmUtil.setData('isDelPlayerPageRightGameAd', b); }, isDelPlayerPageRightVideoList(b) { gmUtil.setData('isDelPlayerPageRightVideoList', b); }, isDelBottomComment(b) { gmUtil.setData('isDelBottomComment', b); }, isClearTopInputTipContent(b) { gmUtil.setData('isClearTopInputTipContent', b); if (b) { eventEmitter.send('执行清空顶部搜索框提示内容'); return } topInput.setTopInputPlaceholder(); }, isDelPlayerEndingPanelVal(n) { gmUtil.setData('is_del_player_ending_panel', n); } } }; const about_and_feedback_vue = { template: ` <div> <el-card> <template #header> <span>作者b站</span> </template> <el-link target="_blank" :href="b_url" type="primary">b站传送门</el-link> </el-card> <el-card> <template #header> <span>交流群</span> </template> <el-link :href='group_url' target="_blank" type="primary">====》Q群传送门《==== </el-link> <el-tooltip content="点击查看群二维码"> <el-tag @click="lookImgBut">876295632</el-tag> </el-tooltip> </el-card> <el-card> <template #header> <span>发布、更新、反馈地址</span> </template> <el-row> <el-col :span="12"> <el-card> <span>greasyfork</span> <el-link target="_blank" type="primary" href="https://greasyfork.org/scripts/461382/">===》传送门《=== </el-link> </el-card> </el-col> <el-col :span="12"> <el-card> <span>脚本猫</span> <el-link target="_blank" type="primary" :href="scriptCat_js_url"> ===》传送门《=== </el-link> </el-card> </el-col> </el-row> </el-card> <el-card> <template #header> <span>开源地址</span> </template> <el-row> <el-col :span="12"> <el-card> <span>gitee</span> <el-link target="_blank" type="primary" href="https://gitee.com/hangexi/BiBiBSPUserVideoMonkeyScript" >https://gitee.com/hangexi/BiBiBSPUserVideoMonkeyScript </el-link> </el-card> </el-col> <el-col :span="12"> <el-card> <span>github</span> <el-link target="_blank" type="primary" href="https://github.com/hgztask/BiBiBSPUserVideoMonkeyScript" >https://github.com/hgztask/BiBiBSPUserVideoMonkeyScript </el-link> </el-card> </el-col> </el-row> </el-card> <el-card> <template #header>常见问题和使用文档</template> <el-row> <el-col :span="12"> 常见问题 <el-link target="_blank" type="primary" :href="common_question_url">==>传送门<== </el-link> </el-col> <el-col :span="12"> 使用文档 <el-link target="_blank" type="primary" href="https://docs.qq.com/doc/DSmJqSkhFaktBeUdk?u=1a1ff7b128d64f188a8bfb71b5acb28c">==>传送门<== </el-link> </el-col> <div> 更新日志 <el-link target="_blank" type="primary" :href="update_log_url">==>传送门<==</el-link> </div> </el-row> </el-card> </div>`, data() { return { group_url: globalValue.group_url, scriptCat_js_url: globalValue.scriptCat_js_url, b_url: globalValue.b_url, common_question_url: globalValue.common_question_url, update_log_url: globalValue.update_log_url } }, methods: { lookImgBut() { eventEmitter.send('显示图片对话框', {image: "https://www.mikuchase.ltd/img/qq_group_876295632.webp"}); } }, created() { } }; const show_img_dialog_vue = { template: ` <div> <el-dialog center :title="title" :modal="isModal" :visible.sync="show"> <div class="el-vertical-center"> <el-image :src="imgSrc" :preview-src-list="imgList"/> </div> </el-dialog> </div>`, data() { return { show: false, title: "图片查看", imgList: [], imgSrc: '', isModal: true } }, created() { eventEmitter.on('显示图片对话框', ({image, title, images, isModal}) => { this.imgSrc = image; if (title) { this.title = title; } if (images) { this.imgList = images; } else { this.imgList = [image]; } if (isModal) { this.isModal = isModal; } this.show = true; }); } }; const sheet_dialog_vue = { props: { show: { type: Boolean, default: false }, list: { type: Array, default: () => [] }, closeOnClickModal: { type: Boolean, default: true }, title: { type: String, default: '选项' }, clickItemClose: { type: Boolean, default: false } }, template: ` <div> <el-dialog :visible="show" :title="title" width="30%" center :close-on-click-modal="closeOnClickModal" @close="$emit('close')"> <div> <el-row> <el-col v-for="item in list" :key="item.label"> <el-button style="width: 100%" @click="handleClick(item)">项目{{ item.label }}</el-button> </el-col> </el-row> </div> </el-dialog> </div>`, data() { return { dialogShow: true, list: [] } }, methods: { handleClick(item) { if (this.clickItemClose) { return; } this.$emit('options-click', item); } } }; const bullet_word_management_vue = { template: ` <div> <el-button @click="fetchGetBarrageBlockingWordsBut">获取弹幕屏蔽词</el-button> <el-button @click="outToJsonFIleBut">导出至json文件</el-button> </div>`, data: () => { return { resData: {}, resList: [], } }, methods: { async initial() { const {state, list, msg} = await bFetch.fetchGetBarrageBlockingWords(); if (!state) { this.$message.warning(msg); return false } this.resList = list; this.$notify({message: '已初始化', type: 'success'}); return true }, fetchGetBarrageBlockingWordsBut() { if (this.resList.length === 0) { this.$message.info('未有弹幕屏蔽词内容或未初始化'); return } const list = this.resList; this.$message.success(`已打印在控制台上,数量${list.length}`); console.log('获取弹幕屏蔽词_start====='); console.log(list); console.log('获取弹幕屏蔽词_end======='); }, outToJsonFIleBut() { } }, created() { this.initial(); } }; const mainLayoutEl = document.createElement('div'); if (document.head.querySelector('#element-ui-css') === null) { const linkElement = document.createElement('link'); linkElement.rel = 'stylesheet'; linkElement.href = 'https://unpkg.com/element-ui/lib/theme-chalk/index.css'; linkElement.id = 'element-ui-css'; document.head.appendChild(linkElement); console.log('挂载element-ui样式成功'); } window.addEventListener('load', () => { document.body.appendChild(mainLayoutEl); new Vue({ el: mainLayoutEl, template: ` <div> <el-drawer style="position: fixed" :visible.sync="drawer" direction="ltr" size="100%" :modal="false" :with-header="false"> <el-tabs type="border-card" v-model="tabsActiveName" @tab-click="tabClick"> <el-tab-pane label="面板设置" name="面板设置" lazy> <panel_settings_vue/> </el-tab-pane> <el-tab-pane label="规则管理" name="规则管理" lazy> <rule_management_vue/> </el-tab-pane> <el-tab-pane label="兼容设置" name="兼容设置" lazy> <compatible_setting_vue/> </el-tab-pane> <el-tab-pane label="缓存管理" name="缓存管理" lazy> <cache_management_vue/> </el-tab-pane> <el-tab-pane label="页面处理" name="页面处理" lazy> <page_processing_vue/> </el-tab-pane> <el-tab-pane v-if="debug_panel_show" label="弹幕词管理" name="弹幕词管理" lazy> <bullet_word_management_vue/> </el-tab-pane> <el-tab-pane label="输出信息" name="输出信息" lazy> <output_information_vue/> </el-tab-pane> <el-tab-pane label="支持打赏" name="支持打赏" lazy> <donate_layout_vue/> </el-tab-pane> <el-tab-pane label="关于和问题反馈" name="关于和问题反馈" lazy> <about_and_feedback_vue/> </el-tab-pane> <el-tab-pane label="调试测试" name="调试测试" lazy v-if="debug_panel_show"> <div v-show="debug_panel_show"> <debugger_management_vue/> </div> </el-tab-pane> </el-tabs> </el-drawer> <look_content_dialog_vue/> <show_img_dialog_vue/> <sheet_dialog_vue :show="sheet_dialog.show" :list="sheet_dialog.list" :title="sheet_dialog.title" @close="handleClose" :close-on-click-modal="sheet_dialog.closeOnClickModal" @options-click="handleOptionsClick"/> </div>`, components: { output_information_vue: outputInformationVue, donate_layout_vue: donateLayoutVue, rule_management_vue: ruleManagementVue, cache_management_vue, panel_settings_vue, compatible_setting_vue, look_content_dialog_vue, debugger_management_vue, page_processing_vue, about_and_feedback_vue, show_img_dialog_vue, sheet_dialog_vue, bullet_word_management_vue }, data() { return { drawer: false, tabsActiveName: gmUtil.getData('mainTabsActiveName', '规则管理'), debug_panel_show: isOpenDev(), sheet_dialog: { show: false, list: [], title: "", optionsClick: null, closeOnClickModal: true } } }, methods: { tabClick(tab) { gmUtil.setData('mainTabsActiveName', tab.name); }, handleClose() { this.sheet_dialog.show = false; }, handleOptionsClick(item) { let tempBool; const temp = this.sheet_dialog.optionsClick(item); if (temp === undefined) { tempBool = false; } else { tempBool = temp; } this.sheet_dialog.show = tempBool === true; } }, created() { eventEmitter.on('主面板开关', () => { const tempBool = this.drawer; this.drawer = !tempBool; }); eventEmitter.on('el-notify', (options) => { this.$notify(options); }); eventEmitter.on('el-msg', (...options) => { this.$message(...options); }); eventEmitter.on('el-alert', (...options) => { this.$alert(...options); }); eventEmitter.handler('el-confirm', (...options) => { return this.$confirm(...options); }); eventEmitter.on('debugger-dev-show', (bool) => { debugger this.debug_panel_show = bool; if (bool) { this.$alert('已开启测试调试面板', 'tip'); } else { this.$alert('已关闭测试调试面板', 'tip'); } }); eventEmitter.on('sheet-dialog', ({list, optionsClick, title = '选项', closeOnClickModal = false}) => { this.sheet_dialog.show = true; this.sheet_dialog.list = list; this.sheet_dialog.title = title; this.sheet_dialog.optionsClick = optionsClick; this.sheet_dialog.closeOnClickModal = closeOnClickModal; }); eventEmitter.handler('el-prompt', (...options) => { return this.$prompt(...options) }); eventEmitter.on('请求获取视频信息失败', (response, bvId) => { requestIntervalQueue.clearPendingQueue(); eventEmitter.send('更新根据bv号网络请求获取视频信息状态', true); this.$alert(`请求获取视频信息失败,状态码:${response.status},bv号:${bvId} \n。已自动禁用根据bv号网络请求获取视频信息状态 \n如需关闭,请在面板条件限制里手动关闭。`, '错误', { confirmButtonText: '确定', type: 'error' }); }); if (bAfterLoadingThePageOpenMainPanel()) { this.drawer = true; } } }); }); var defCss = ` .el-vertical-center { display: flex; justify-content: center; } .el-horizontal-center { display: flex; align-items: center; } .el-horizontal-right { display: flex; justify-content: flex-end; } .el-horizontal-left { display: flex; justify-content: flex-start; } `; gmUtil.addStyle(` [gz_bezel]{ border:1px solid ${localMKData.getBorderColor()} } `); gmUtil.addStyle(defCss); const toPlayCountOrBulletChat = (str) => { if (!str) { return -1 } str = str.split(/[\t\r\f\n\s]*/g).join(""); const replace = str.replace(/[^\d.]/g, ''); if (str.endsWith('万') || str.endsWith('万次') || str.endsWith('万弹幕')) { return parseFloat(replace) * 10000; } if (str.endsWith('次') || str.endsWith('弹幕')) { return parseInt(replace); } return parseInt(str) }; const timeStringToSeconds = (timeStr) => { if (!timeStr) { return -1 } const parts = timeStr.split(':'); switch (parts.length) { case 1: // 只有秒 return Number(parts[0]); case 2: // 分钟和秒 return Number(parts[0]) * 60 + Number(parts[1]); case 3: // 小时、分钟和秒 return Number(parts[0]) * 3600 + Number(parts[1]) * 60 + Number(parts[2]); default: throw new Error('Invalid time format'); } }; var sFormatUtil = { toPlayCountOrBulletChat, timeStringToSeconds }; const exactMatch = (ruleList, value) => { if (ruleList === null || ruleList === undefined) return false; if (!Array.isArray(ruleList)) return false return ruleList.some(item => item === value); }; const bFuzzyAndRegularMatchingWordsToLowercase = localMKData.bFuzzyAndRegularMatchingWordsToLowercase(); const regexMatch = (ruleList, value) => { if (ruleList === null || ruleList === undefined) return null; if (!Array.isArray(ruleList)) return null if (bFuzzyAndRegularMatchingWordsToLowercase) { value = value.toLowerCase(); } value = value.split(/[\t\r\f\n\s]*/g).join(""); const find = ruleList.find(item => { try { return value.search(item) !== -1; } catch (e) { const msg = `正则匹配失败,请检查规则列表中的正则表达式是否正确,错误信息:${e.message}`; eventEmitter.send('正则匹配时异常', {e, msg}); return false; } }); return find === undefined ? null : find; }; const fuzzyMatch = (ruleList, value) => { if (ruleList === null || ruleList === undefined || value === null) return null; if (!Array.isArray(ruleList)) return null const find = ruleList.find(item => value.toLowerCase().includes(item)); return find === undefined ? null : find; }; var ruleMatchingUtil = { exactMatch, regexMatch, fuzzyMatch }; const outputInformationFontColor = localMKData.getOutputInformationFontColor(); const highlightInformationColor = localMKData.getHighlightInformationColor(); const getLiveRoomCommentInfoHtml = (type, matching, commentData) => { const toTimeString = defUtil.toTimeString(); const {name, uid, content} = commentData; return `<b style="color: ${outputInformationFontColor}; " gz_bezel> ${toTimeString}-根据${type}-${matching ? `<b style="color: ${highlightInformationColor}">【${matching}】</b>` : ""}-屏蔽用户【${name}】uid= <a href="https://space.bilibili.com/${uid}" style="color: ${highlightInformationColor}" target="_blank">【${uid}】</a> 直播评论【${content}】 </b>` }; const getDynamicContentInfoHtml = (type, matching, dynamicData) => { const toTimeString = defUtil.toTimeString(); const {name, uid, content} = dynamicData; return `<b style="color: ${outputInformationFontColor}; " gz_bezel> ${toTimeString}-根据${type}-${matching ? `<b style="color: ${highlightInformationColor}">【${matching}】</b>` : ""}-屏蔽用户【${name}】uid= <a href="https://space.bilibili.com/${uid}" style="color: ${highlightInformationColor}" target="_blank">【${uid}】</a> 动态【${content}】 </b>` }; const getLiveRoomInfoHtml = (type, matching, liveRoomData) => { const toTimeString = defUtil.toTimeString(); const {name = null, uid = -1, title, liveUrl} = liveRoomData; return `<b style="color: ${outputInformationFontColor};" gz_bezel> ${toTimeString}-根据${type}${matching ? `<b style="color: ${highlightInformationColor}">【${matching}】</b>` : ""}-屏蔽用户【${name === null ? '' : name}】${uid === -1 ? "" : `uid= <a href="https://space.bilibili.com/${uid}" style="color: ${highlightInformationColor}" target="_blank">【${uid}】</a>`} 直播间标题【<a href="${liveUrl}" target="_blank" style="color: ${highlightInformationColor}">${title}</a>】 </b>` }; var output_informationTab = { getLiveRoomCommentInfoHtml, getDynamicContentInfoHtml, getLiveRoomInfoHtml }; class ElEventEmitter { #elEvents = new Map() addEvent(el, eventName, callback, repeated = false) { const elEvents = this.#elEvents; if (!elEvents.has(el)) { elEvents.set(el, {events: [], attrs: []}); } const {events, attrs} = elEvents.get(el); if (!repeated) { if (attrs.includes(eventName)) { return } } attrs.push(eventName); events.push({eventName, callback}); el.setAttribute(`gz-event`, JSON.stringify(attrs)); el.addEventListener(eventName, callback); } hasEventName(el, eventName) { const elEvents = this.#elEvents; if (elEvents.has(el)) { return true } const {attrs} = elEvents.get(el); return attrs.includes(eventName) } } const elEventEmitter = new ElEventEmitter(); const addBlockButton$1 = (data, tagCss = '', position = []) => { const {insertionPositionEl, explicitSubjectEl, css} = data.data; if (tagCss !== '') { if (insertionPositionEl.querySelector("." + tagCss)) return; } const buttonEL = document.createElement("button"); buttonEL.setAttribute("gz_type", ""); if (tagCss !== '') { buttonEL.className = tagCss; } buttonEL.textContent = "屏蔽"; if (position.length !== 0) { buttonEL.style.position = "absolute"; } if (position.includes("right")) { buttonEL.style.right = "0"; } if (position.includes("bottom")) { buttonEL.style.bottom = "0"; } if (css !== undefined) { for (let key of Object.keys(css)) { buttonEL.style[key] = css[key]; } } if (explicitSubjectEl) { buttonEL.style.display = "none"; elEventEmitter.addEvent(explicitSubjectEl, "mouseout", () => buttonEL.style.display = "none"); elEventEmitter.addEvent(explicitSubjectEl, "mouseover", () => buttonEL.style.display = ""); } insertionPositionEl.appendChild(buttonEL); buttonEL.addEventListener("click", (event) => { event.stopImmediatePropagation(); // 阻止事件冒泡和同一元素上的其他事件处理器 event.preventDefault(); // 阻止默认行为 const {uid = -1, name = null} = data.data; const showList = []; showList.push(uid !== -1 ? { label: `uid精确屏蔽-用户uid=${uid}-name=${name}`, value: "uid" } : { label: `用户名精确屏蔽(不推荐)-用户name=${name}`, value: 'name' }); eventEmitter.send('sheet-dialog', { title: "屏蔽选项", list: showList, optionsClick: (item) => { const {value} = item; if (value === 'uid') { if (uid === -1) { eventEmitter.send('el-msg', "该页面数据不存在uid字段"); return; } const {status} = ruleUtil.addRulePreciseUid(uid); if (status) { data.maskingFunc(); } return; } eventEmitter.invoke('el-confirm', '不推荐用户使用精确用户名来屏蔽,确定继续吗?').then(() => { ruleUtil.addRulePreciseName(name); }); } }); }); }; const addTopicDetailVideoBlockButton = (data) => { addBlockButton$1(data, "gz_shielding_button"); }; const addTopicDetailContentsBlockButton = (data) => { const position = data.data.position; const loop = position !== undefined; addBlockButton$1(data, "gz_shielding_topic_detail_button", loop ? position : []); }; const blockUserUid = (uid) => { if (ruleMatchingUtil.exactMatch(ruleKeyListData$1.getPreciseUidArr(), uid)) { return {state: true, type: "精确uid"}; } return returnTempVal; }; const blockCheckWhiteUserUid = (uid) => { return ruleMatchingUtil.exactMatch(ruleKeyListData$1.getPreciseUidWhiteArr(), uid); }; const blockExactAndFuzzyMatching = (val, config) => { if (!val) { return returnTempVal } if (config.exactKey) { if (ruleMatchingUtil.exactMatch(gmUtil.getData(config.exactKey, []), val)) { return {state: true, type: config.exactTypeName, matching: val} } } let matching; if (config.fuzzyKey) { matching = ruleMatchingUtil.fuzzyMatch(gmUtil.getData(config.fuzzyKey, []), val); if (matching) { return {state: true, type: config.fuzzyTypeName, matching} } } if (config.regexKey) { matching = ruleMatchingUtil.regexMatch(gmUtil.getData(config.regexKey, []), val); if (matching) { return {state: true, type: config.regexTypeName, matching} } } return returnTempVal }; const blockComment = (comment) => { return blockExactAndFuzzyMatching(comment, { fuzzyKey: 'commentOn', fuzzyTypeName: '模糊评论', regexKey: 'commentOnCanonical', regexTypeName: '正则评论' }) }; const blockAvatarPendant = (name) => { return blockExactAndFuzzyMatching(name, { exactKey: 'precise_avatarPendantName', exactTypeName: '精确头像挂件名', fuzzyKey: 'avatarPendantName', fuzzyTypeName: '模糊头像挂件名' }) }; const asyncBlockAvatarPendant = async (name) => { const res = blockAvatarPendant(name); if (res.state) return Promise.reject(res); }; const blockSignature = (signature) => { return blockExactAndFuzzyMatching(signature, { fuzzyKey: 'signature', fuzzyTypeName: '模糊用户签名', regexKey: 'signatureCanonical', regexTypeName: '正则用户签名' }) }; const asyncBlockSignature = async (signature) => { const res = blockSignature(signature); if (res.state) return Promise.reject(res); }; const blockVideoDesc = (desc) => { return blockExactAndFuzzyMatching(desc, { fuzzyKey: 'videoDesc', fuzzyTypeName: '视频简介(模糊匹配)' , regexKey: 'videoDescCanonical', regexTypeName: '视频简介(正则匹配)' }) }; const asyncBlockVideoDesc = async (desc) => { const res = blockVideoDesc(desc); if (res.state) return Promise.reject(res); }; const blockGender = (gender) => { const val = localMKData.isGenderRadioVal(); const state = val === gender && val !== '不处理'; if (state) { return {state: true, type: '性别屏蔽', matching: val} } return returnTempVal; }; const asyncBlockGender = async (gender) => { const res = blockGender(gender); if (res.state) { return Promise.reject(res) } }; const blockUserVip = (vipId) => { const val = localMKData.isVipTypeRadioVal(); const vipMap = { 0: '无', 1: '月大会员', 2: '年度及以上大会员' }; if (val === vipMap[vipId]) { return {state: true, type: '会员类型屏蔽', matching: val} } return returnTempVal }; const asyncBlockUserVip = async (vipId) => { const res = blockUserVip(vipId); if (res.state) { return Promise.reject(res) } }; const blockSeniorMember = (num) => { if (num === 1 && localMKData.isSeniorMember()) { return {state: true, type: '屏蔽硬核会员'} } return returnTempVal }; const asyncBlockSeniorMember = async (num) => { const res = blockSeniorMember(num); if (res.state) { return Promise.reject(res) } }; const blockVideoCopyright = (num) => { const val = localMKData.isCopyrightRadio(); const tempMap = { 1: '原创', 2: '转载' }; if (val === tempMap[num]) { return {state: true, type: '视频类型屏蔽', matching: val} } return returnTempVal }; const asyncBlockVideoCopyright = async (num) => { const res = blockVideoCopyright(num); if (res.state) { return Promise.reject(res) } }; const blockVerticalVideo = (dimension) => { if (!localMKData.isBlockVerticalVideo()) { return returnTempVal } if (!dimension) { return returnTempVal } const vertical = dimension.width < dimension.height; if (vertical) { return {state: true, type: '竖屏视频屏蔽', matching: vertical} } return returnTempVal }; const asyncBlockVerticalVideo = async (dimension) => { const res = blockVerticalVideo(dimension); if (res.state) return Promise.reject(res); }; const blockVideoLikeRate = (like, view) => { if (!like || !view || !localMKData.isVideoLikeRateBlockingStatus()) { return returnTempVal } const mk_likeRate = parseInt(localMKData.getVideoLikeRate() * 100); if (isNaN(mk_likeRate)) { return returnTempVal } const likeRate = defUtil.calculateLikeRate(like, view); if (likeRate <= mk_likeRate) { return { state: true, type: '视频点赞率屏蔽', matching: mk_likeRate + '%' , msg: `视频的点赞率为${likeRate}%,低于用户指定的限制${mk_likeRate}%,屏蔽该视频` } } return returnTempVal }; const asyncBlockVideoLikeRate = async (like, view) => { const res = blockVideoLikeRate(like, view); if (res.state) return Promise.reject(res); }; const blockVideoInteractiveRate = (danmaku, reply, view) => { if (!danmaku || !view || !localMKData.isInteractiveRateBlockingStatus()) { return returnTempVal } const mk_interactionRate = parseInt(localMKData.getInteractiveRate() * 100); const interactionRate = defUtil.calculateInteractionRate(danmaku, reply, view); if (interactionRate <= mk_interactionRate) { return { state: true, type: '视频互动率屏蔽', matching: mk_interactionRate + '%' , msg: `视频的互动率为${interactionRate}%,低于用户指定的限制${mk_interactionRate}%,屏蔽该视频` } } return returnTempVal }; const asyncBlockVideoInteractiveRate = async (danmaku, reply, view) => { const res = blockVideoInteractiveRate(danmaku, reply, view); if (res.state) return Promise.reject(res); }; const blockVideoTripleRate = (favorite, coin, share, view) => { if (!favorite || !coin || !share || !view || !localMKData.isTripleRateBlockingStatus()) { return returnTempVal } const mk_tripleRate = parseInt(localMKData.getTripleRate() * 100); const tripleRate = defUtil.calculateTripleRate(favorite, coin, share, view); if (tripleRate <= mk_tripleRate) { return { state: true, type: '视频三连率屏蔽', matching: mk_tripleRate + '%' , msg: `视频的三连率为${tripleRate}%,低于用户指定的限制${mk_tripleRate}%,屏蔽该视频` } } return returnTempVal }; const asyncBlockVideoTripleRate = async (favorite, coin, share, view) => { const res = blockVideoTripleRate(favorite, coin, share, view); if (res.state) return Promise.reject(res); }; const blockVideoCoinLikesRatioRate = (coin, like) => { if (!coin || !like || !localMKData.isCoinLikesRatioRateBlockingStatus()) { return returnTempVal } const mk_coinLikesRatioRate = parseInt(localMKData.getCoinLikesRatioRate() * 100); const coinLikesRatioRate = defUtil.calculateCoinLikesRatioRate(coin, like); if (coinLikesRatioRate <= mk_coinLikesRatioRate) { return { state: true, type: '视频投币/点赞比(内容价值)屏蔽', matching: mk_coinLikesRatioRate + '%', msg: `视频的投币/点赞比(内容价值)为${coinLikesRatioRate}%,低于用户指定的限制${mk_coinLikesRatioRate}%,屏蔽该视频` } } return returnTempVal }; const asyncBlockVideoCoinLikesRatioRate = async (coin, like) => { const res = blockVideoCoinLikesRatioRate(coin, like); if (res.state) return Promise.reject(res); }; const blockByLevel = (level) => { if (!level) { return returnTempVal } const min = gmUtil.getData('nMinimumLevel', -1); if (min > level) { return {state: true, type: "最小用户等级过滤", matching: min}; } const max = gmUtil.getData('nMaximumLevel', -1); if (max > level) { return {state: true, type: "最大用户等级过滤", matching: max}; } return returnTempVal }; const asyncBlockByLevel = async (level) => { const res = blockByLevel(level); if (res.state) return Promise.reject(res); }; const blockUserUidAndName = (uid, name) => { if (!uid || !name) { return returnTempVal } let returnVal = blockUidWholeProcess(uid); if (returnVal.state) { return returnVal } returnVal = blockUserName(name); if (returnVal.state) { return returnVal } return returnTempVal }; const asyncBlockUserUidAndName = async (uid, name) => { const res = blockUserUidAndName(uid, name); if (res.state) { return Promise.reject(res) } }; const blockVideoTeamMember = (teamMember) => { if (!teamMember) { return returnTempVal } for (let u of teamMember) { const returnVal = blockUserUidAndName(u.mid, u.name); if (returnVal.state) { return returnVal } } return returnTempVal }; const asyncBlockVideoTeamMember = async (teamMember) => { const res = blockVideoTeamMember(teamMember); if (res.state) return Promise.reject(res) }; const blockUserName = (name) => { return blockExactAndFuzzyMatching(name, { exactKey: 'precise_name', exactTypeName: '精确用户名', fuzzyKey: 'name', fuzzyTypeName: '模糊用户名', regexKey: 'nameCanonical', regexTypeName: '正则用户名' }) }; const blockVideoOrOtherTitle = (title) => { return blockExactAndFuzzyMatching(title, { fuzzyKey: 'title', fuzzyTypeName: '模糊标题', regexKey: 'titleCanonical', regexTypeName: '正则标题' }) }; const blockBasedVideoTag = (tags) => { const preciseVideoTagArr = ruleKeyListData$1.getPreciseVideoTagArr(); const videoTagArr = ruleKeyListData$1.getVideoTagArr(); if (preciseVideoTagArr.length <= 0 && videoTagArr.length <= 0) { return returnTempVal } for (let tag of tags) { if (ruleMatchingUtil.exactMatch(preciseVideoTagArr, tag)) { return {state: true, type: "精确视频tag", matching: tag} } let fuzzyMatch = ruleMatchingUtil.fuzzyMatch(videoTagArr, tag); if (fuzzyMatch) { return {state: true, type: "模糊视频tag", matching: fuzzyMatch} } fuzzyMatch = ruleMatchingUtil.regexMatch(ruleKeyListData$1.getVideoTagCanonicalArr(), tag); if (fuzzyMatch) { return {state: true, type: "正则视频tag", matching: fuzzyMatch} } } return returnTempVal }; const asyncBlockBasedVideoTag = async (tags) => { const res = blockBasedVideoTag(tags); if (res.state) return Promise.reject(res); }; const blockByUidRange = (uid) => { if (!localMKData.isUidRangeMaskingStatus()) { return returnTempVal } const [head, tail] = localMKData.getUidRangeMasking(); if (head >= uid <= tail) { return {state: true, type: "uid范围屏蔽", matching: `${head}=>${uid}<=${tail}`} } return returnTempVal }; const blockUidWholeProcess = (uid) => { if (!uid || blockCheckWhiteUserUid(uid)) return returnTempVal let returnVal = blockUserUid(uid); if (returnVal.state) { return returnVal; } return blockByUidRange(uid) }; const asyncBlockFollowedVideo = (following) => { if (following && localMKData.isBlockFollowed()) { return Promise.reject({state: true, type: '已关注'}) } }; const asyncBlockChargeVideo = (isUpOwnerExclusive) => { if (isUpOwnerExclusive && localMKData.isUpOwnerExclusive()) { return Promise.reject({state: true, type: '充电专属视频'}) } }; const blockTimeRangeMasking = (timestamp) => { if (!timestamp || !localMKData.isTimeRangeMaskingStatus()) { return returnTempVal } const timeRangeMaskingArr = localMKData.getTimeRangeMaskingArr(); if (timeRangeMaskingArr.length === 0) { return returnTempVal; } for (let {status, r: [startTimestamp, endTimestamp]} of timeRangeMaskingArr) { if (!status) continue const startSecondsTimestamp = Math.floor(startTimestamp / 1000); const endSecondsTimestamp = Math.floor(endTimestamp / 1000); if (startSecondsTimestamp >= timestamp <= endSecondsTimestamp) { const startToTime = new Date(startTimestamp).toLocaleString(); const endToTime = new Date(endTimestamp).toLocaleString(); const timestampToTime = new Date(timestamp * 1000).toLocaleString(); return {state: true, type: "时间范围屏蔽", matching: `${startToTime}=>${timestampToTime}<=${endToTime}`} } } return returnTempVal }; const asyncBlockTimeRangeMasking = async (timestamp) => { const res = blockTimeRangeMasking(timestamp); if (res.state) return Promise.reject(res); }; const shieldingDynamic = (dynamicData) => { const { content = null, el, title = null, tag = null } = dynamicData; let matching = null; if (content !== null) { matching = ruleMatchingUtil.fuzzyMatch(ruleKeyListData$1.getCommentOnArr(), content); if (matching !== null) { el?.remove(); return {state: true, type: "模糊评论内容", matching}; } matching = ruleMatchingUtil.regexMatch(ruleKeyListData$1.getCommentOnCanonicalArr(), content); if (matching !== null) { el?.remove(); return {state: true, type: "正则评论内容", matching}; } } if (title !== null) { matching = ruleMatchingUtil.fuzzyMatch(ruleKeyListData$1.getTitleArr(), title); if (matching !== null) { el?.remove(); return {state: true, type: "模糊标题", matching}; } matching = ruleMatchingUtil.regexMatch(ruleKeyListData$1.getTitleCanonicalArr(), title); if (matching !== null) { el?.remove(); return {state: true, type: "正则标题", matching}; } } if (tag !== null) { if (ruleMatchingUtil.exactMatch(ruleKeyListData$1.getPreciseTagArr(), tag)) { el?.remove(); return {state: true, type: "精确话题tag"}; } matching = ruleMatchingUtil.fuzzyMatch(ruleKeyListData$1.getTagArr(), tag); if (matching !== null) { el?.remove(); return {state: true, type: "模糊话题tag", matching}; } matching = ruleMatchingUtil.regexMatch(ruleKeyListData$1.getTagCanonicalArr(), tag); if (matching !== null) { el?.remove(); return {state: true, type: "正则话题tag", matching}; } } return returnTempVal }; const shieldingDynamicDecorated = (dynamicData) => { const {state, type, matching} = shieldingDynamic(dynamicData); if (state) { const infoHtml = output_informationTab.getDynamicContentInfoHtml(type, matching, dynamicData); eventEmitter.send('打印信息', infoHtml); } return state; }; const intervalExecutionStartShieldingVideoInert = (func, name = '') => { let i1 = -1; const start = () => { if (i1 !== -1) { return } console.log('开始执行屏蔽' + name); i1 = setInterval(() => { func(); console.log(`执行屏蔽${name}列表-定时器正在执行`); }, 800); }; const stop = () => { if (i1 === -1) { return } clearInterval(i1); console.log(`已停止执行屏蔽${name}列表`); i1 = -1; }; return {start, stop} }; var shielding = { shieldingDynamicDecorated, addTopicDetailVideoBlockButton, addTopicDetailContentsBlockButton, intervalExecutionStartShieldingVideoInert, addBlockButton: addBlockButton$1 }; class VideoInfoCache { #caches = []; getCaches() { return this.#caches; } getCount() { return this.#caches.length; } addData(data) { this.#caches.push(data); } is(bv) { return this.#caches.some(item => item.bv === bv); } find(bv) { const find = this.#caches.find(item => item.bv === bv); if (find) { return find } return null } async update() { this.#caches = await bvDexie.getVideoInfo(); return this.getCaches(); } } const videoInfoCache = new VideoInfoCache(); const asyncBlockVideoTagPreciseCombination = async (tags) => { if (tags.length <= 0) return; const mkArrTags = ruleKeyListData$1.getVideoTagPreciseCombination(); for (let mkTags of mkArrTags) { if (arrUtil.arrayContains(tags, mkTags)) return Promise.reject({ state: true, type: "多重tag屏蔽", matching: mkTags }) } }; const blockVideoBV = (bv) => { const bvs = ruleKeyListData$1.getPreciseVideoBV(); if (bvs.includes(bv)) return Promise.reject({ state: true, type: "精确bv号屏蔽", matching: bv }) return returnTempVal }; const shieldingVideo = (videoData) => { const { title, uid = -1, name, nDuration = -1, nBulletChat = -1, nPlayCount = -1, bv = null } = videoData; let returnVal = blockUserUidAndName(uid, name); if (returnVal.state) { return returnVal; } returnVal = blockVideoOrOtherTitle(title); if (returnVal.state) { return returnVal } returnVal = blockVideoBV(bv); if (returnVal.state) return returnVal; if (nDuration !== -1) { const min = gmUtil.getData('nMinimumDuration', -1); if (min > nDuration && min !== -1) { return {state: true, type: '最小时长', matching: min} } const max = gmUtil.getData('nMaximumDuration', -1); if (max < nDuration && max !== -1) { return {state: true, type: '最大时长', matching: max} } } if (nBulletChat !== -1) { const min = gmUtil.getData('nMinimumBarrage', -1); if (min > nBulletChat && min !== -1) { return {state: true, type: '最小弹幕数', matching: min} } const max = gmUtil.getData('nMaximumBarrage', -1); if (max < nBulletChat && max !== -1) { return {state: true, type: '最大弹幕数', matching: max} } } if (nPlayCount !== -1) { const min = gmUtil.getData('nMinimumPlay', -1); if (min > nPlayCount && min !== -1) { return {state: true, type: '最小播放量', matching: min} } const max = gmUtil.getData('nMaximumPlayback', -1); if (max < nPlayCount && max !== -1) { return {state: true, type: '最大播放量', matching: max} } } return returnTempVal; }; const shieldingVideoDecorated = (videoData, method = "remove") => { const {el} = videoData; if (el.style.display === "none") return true; const {state, type, matching = null} = shieldingVideo(videoData); if (state) { eventEmitter.send('event-屏蔽视频元素', {res: {state, type, matching}, method, videoData}); return true; } if (localMKData.isDisableNetRequestsBvVideoInfo()) return state; shieldingOtherVideoParameter(videoData, method); return state; }; eventEmitter.on('event-屏蔽视频元素', ({res, method = "remove", videoData}) => { if (!res) return const {type, matching} = res; const {el} = videoData; if (method === "remove") { el?.remove(); } else { el.style.display = "none"; } eventEmitter.send('屏蔽视频信息', type, matching, videoData); }); const shieldingOtherVideoParameter = async (videoData, method) => { const {bv = '-1'} = videoData; if (bv === '-1') return if (videoInfoCache.getCount() === 0) { await videoInfoCache.update(); } const find = videoInfoCache.find(bv); let result; if (find === null) { const {state, data, msg} = await requestIntervalQueue.add(() => bFetch.fetchGetVideoInfo(bv)); if (!state) { console.warn('获取视频信息失败:' + msg); return } result = data; if (await bvDexie.addVideoData(bv, result)) { await videoInfoCache.update(); console.log('mk-db-添加视频信息到数据库成功', result, videoData); } } else { result = find; } const {tags = [], userInfo, videoInfo} = result; asyncBlockUserUidAndName(userInfo.mid, userInfo.name) .then(() => asyncBlockVideoTagPreciseCombination(tags)) .then(() => asyncBlockBasedVideoTag(tags)) .then(() => asyncBlockVerticalVideo(videoInfo.dimension)) .then(() => asyncBlockVideoCopyright(videoInfo.copyright)) .then(() => asyncBlockChargeVideo(videoInfo?.is_upower_exclusive)) .then(() => asyncBlockFollowedVideo(videoInfo?.following)) .then(() => asyncBlockSeniorMember(userInfo.is_senior_member)) .then(() => asyncBlockVideoTeamMember(userInfo.mid)) .then(() => asyncBlockVideoLikeRate(videoInfo.like, videoInfo.view)) .then(() => asyncBlockVideoInteractiveRate(videoInfo.danmaku, videoInfo.reply, videoInfo.view)) .then(() => asyncBlockVideoTripleRate(videoInfo.favorite, videoInfo.coin, videoInfo.share, videoInfo.view)) .then(() => asyncBlockVideoCoinLikesRatioRate(videoInfo.coin, videoInfo.like)) .then(() => asyncBlockTimeRangeMasking(videoInfo.pubdate)) .then(() => asyncBlockVideoDesc(videoInfo?.desc)) .then(() => asyncBlockSignature(videoInfo?.sign)) .then(() => asyncBlockAvatarPendant(userInfo?.pendant?.name)) .then(() => asyncBlockByLevel(userInfo?.current_level || -1)) .then(() => asyncBlockGender(userInfo?.sex)) .then(() => asyncBlockUserVip(userInfo.vip.type)) .catch((v) => { const msg = v['msg']; if (msg) { console.warn(msg); } eventEmitter.send('event-屏蔽视频元素', {res: v, method, videoData}); }); }; eventEmitter.on('添加热门视频屏蔽按钮', (data) => { shielding.addBlockButton(data, "gz_shielding_button", ["right", "bottom"]); }); eventEmitter.on('视频添加屏蔽按钮-BewlyBewly', (data) => { shielding.addBlockButton(data, "gz_shielding_button", ['right', 'bottom']); }); eventEmitter.on('视频添加屏蔽按钮', (data) => { shielding.addBlockButton(data, "gz_shielding_button", ["right"]); }); var video_shielding = { shieldingVideoDecorated }; const isHome = (url, title) => { if (title !== "哔哩哔哩 (゜-゜)つロ 干杯~-bilibili") { return false } if (url === 'https://www.bilibili.com/') { return true } return url.includes('https://www.bilibili.com/?spm_id_from=') }; const deDesktopDownloadTipEl = async () => { const el = await elUtil.findElementUntilFound(".desktop-download-tip"); el?.remove(); const log = "已删除下载提示"; console.log(log, el); }; const getChangeTheVideoElList = async () => { const elList = await elUtil.findElementsUntilFound(".container.is-version8>.feed-card"); const list = []; for (let el of elList) { try { const tempData = getVideoData(el); const {userUrl} = tempData; const videoUrl = el.querySelector(".bili-video-card__info--tit>a")?.href || null; if (!userUrl.includes("//space.bilibili.com/")) { el?.remove(); const log = "遍历换一换视频列表中检测到异常内容,已将该元素移除"; console.log(log, el); continue; } const items = { ...tempData, ...{ videoUrl, el, insertionPositionEl: el.querySelector(".bili-video-card__info--bottom"), explicitSubjectEl: el.querySelector(".bili-video-card__info") } }; if (videoUrl?.includes('www.bilibili.com/video')) { items.bv = elUtil.getUrlBV(videoUrl); } list.push(items); } catch (e) { el.remove(); console.warn("获取视频信息失败"); } } return list }; const getVideoData = (el) => { const title = el.querySelector(".bili-video-card__info--tit").title; const name = el.querySelector(".bili-video-card__info--author").textContent.trim(); let nPlayCount = el.querySelector('.bili-video-card__stats--text')?.textContent.trim(); nPlayCount = sFormatUtil.toPlayCountOrBulletChat(nPlayCount); let nBulletChat = el.querySelector('.bili-video-card__stats--text')?.textContent.trim(); nBulletChat = sFormatUtil.toPlayCountOrBulletChat(nBulletChat); let nDuration = el.querySelector('.bili-video-card__stats__duration')?.textContent.trim(); nDuration = sFormatUtil.timeStringToSeconds(nDuration); const userUrl = el.querySelector(".bili-video-card__info--owner").getAttribute("href"); const uid = elUtil.getUrlUID(userUrl); return { title, name, uid, nPlayCount, nBulletChat, nDuration, userUrl } }; const getHomeVideoELList = async () => { const elList = await elUtil.findElementsUntilFound(".container.is-version8>.bili-video-card"); let list = []; for (let el of elList) { try { const tempData = getVideoData(el); const {userUrl} = tempData; if (!userUrl.includes("//space.bilibili.com/")) { el?.remove(); const log = "遍历换一换视频列表下面列表时检测到异常内容,已将该元素移除"; eventEmitter.send('打印信息', log); console.log(log, el); continue; } const videoUrl = el.querySelector(".bili-video-card__info--tit>a")?.href; const items = { ...tempData, ...{ videoUrl, el, insertionPositionEl: el.querySelector(".bili-video-card__info--bottom"), explicitSubjectEl: el.querySelector(".bili-video-card__info") } }; if (videoUrl?.includes('www.bilibili.com/video')) { items.bv = elUtil.getUrlBV(videoUrl); } list.push(items); } catch (e) { el?.remove(); console.log("遍历视频列表中检测到异常内容,已将该元素移除;"); } } return list; }; const startClearExcessContentList = () => { if (globalValue.adaptationBAppCommerce) return; setInterval(() => { const otherElList = document.querySelectorAll(".floor-single-card"); const liveList = document.querySelectorAll(".bili-live-card"); const elList = [...otherElList, ...liveList]; let rightAdEl = document.querySelector('.adcard'); if (rightAdEl) { elList.push(rightAdEl); } rightAdEl = document.querySelector('.fixed-card'); if (rightAdEl) { elList.push(rightAdEl); } for (let el of elList) { el?.remove(); console.log("已清理首页视频列表中多余的内容,直播选项卡,右侧大卡片广告,右侧小卡片广告等", el); } }, 1000); console.log("已启动每秒清理首页视频列表中多余的内容"); }; const startShieldingChangeVideoList = async () => { const list = await getChangeTheVideoElList(); for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue; } eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: startShieldingChangeVideoList}); } }; const startDebounceShieldingChangeVideoList = defUtil.debounce(startShieldingChangeVideoList, 200); const startShieldingHomeVideoList = async () => { const homeVideoELList = await getHomeVideoELList(); for (const videoData of homeVideoELList) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue; } eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: startShieldingHomeVideoList}); } }; const startDebounceShieldingHomeVideoList = defUtil.debounce(startShieldingHomeVideoList, 500); const scrollMouseUpAndDown = async () => { if (globalValue.adaptationBAppCommerce) return; await defUtil.smoothScroll(false, 100); return defUtil.smoothScroll(true, 600); }; var bilibiliHome = { isHome, startClearExcessContentList, startDebounceShieldingChangeVideoList, startDebounceShieldingHomeVideoList, scrollMouseUpAndDown, deDesktopDownloadTipEl, getVideoData }; var css = `.to_hide_xl { display: block !important; } `; const blockCommentWordLimit = (content) => { const commentWordLimit = localMKData.getCommentWordLimitVal(); if (commentWordLimit.length < 3) { return returnTempVal } if (content.length > commentWordLimit) { return {state: true, type: '屏蔽字数限制', matching: `字数限制为${commentWordLimit}`} } return returnTempVal; }; const shieldingComment = (commentsData) => { const {content, uid, name, level = -1} = commentsData; let returnVal = blockUserUidAndName(uid, name); if (returnVal.state) { return returnVal } returnVal = blockComment(content); if (returnVal.state) { return returnVal } if (level !== -1) { return blockByLevel(level); } return blockCommentWordLimit(content); }; const shieldingComments = (commentsDataList) => { for (let commentsData of commentsDataList) { if (shieldingCommentDecorated(commentsData)) continue; eventEmitter.send('评论添加屏蔽按钮', commentsData); const {replies = []} = commentsData; if (replies.length === 0) continue; for (let reply of replies) { if (shieldingCommentDecorated(reply)) continue; eventEmitter.send('评论添加屏蔽按钮', reply); } } }; const shieldingCommentDecorated = (commentsData) => { const {state, type, matching} = shieldingComment(commentsData); if (state) { commentsData.el?.remove(); eventEmitter.send('屏蔽评论信息', type, matching, commentsData); } eventEmitter.send('event-评论通知替换关键词', commentsData); return state; }; var comments_shielding = { shieldingComment, shieldingComments, shieldingCommentDecorated }; const shieldingLiveRoomContentDecorated = (liveRoomContent) => { let {state, type, matching} = comments_shielding.shieldingComment(liveRoomContent); const {el, fansMedal} = liveRoomContent; if (fansMedal !== null) { if (ruleMatchingUtil.exactMatch(ruleKeyListData$1.getPreciseFanCardArr(), fansMedal)) { el?.remove(); state = true; type = "精确粉丝牌"; } } if (state) { el?.remove(); } if (type) { const infoHtml = output_informationTab.getLiveRoomCommentInfoHtml(type, matching, liveRoomContent); eventEmitter.send('打印信息', infoHtml); } return state; }; const shieldingLiveRoom = (liveRoomData) => { const {name, title, partition, uid = -1} = liveRoomData; let returnVal; if (uid !== -1) { if (blockCheckWhiteUserUid(uid)) { return returnTempVal; } returnVal = blockUserUidAndName(uid, name); if (returnVal.state) { return returnVal } } returnVal = blockVideoOrOtherTitle(title); if (returnVal.state) { return returnVal } if (partition) { if (ruleMatchingUtil.exactMatch(ruleKeyListData$1.getPrecisePartitionArr(), partition)) { return {state: true, type: "精确直播分区"}; } } return returnTempVal; }; const shieldingLiveRoomDecorated = (liveRoomData) => { const {state, type, matching = null} = shieldingLiveRoom(liveRoomData); if (state) { liveRoomData.el?.remove(); const infoHtml = output_informationTab.getLiveRoomInfoHtml(type, matching, liveRoomData); eventEmitter.send('打印信息', infoHtml); } return state; }; const addLiveContentBlockButton = (commentsData) => { shielding.addBlockButton(commentsData, "gz_shielding_live_danmaku_button"); }; var live_shielding = { shieldingLiveRoomDecorated, addLiveContentBlockButton }; const installStyle = () => { const styleElement = document.createElement('style'); styleElement.textContent = css; document.head.appendChild(styleElement); }; const getLiveRoomList = async () => { const elList = await elUtil.findElements('.live-room-cards>.video-list-item'); const list = []; for (let el of elList) { const titleAEl = el.querySelector('.bili-live-card__info--tit>a'); const titleEl = el.querySelector('.bili-live-card__info--tit>a>span'); const userEl = el.querySelector('.bili-live-card__info--uname'); const liveUrl = titleAEl.href; const title = titleEl.textContent.trim(); const userUrl = userEl.href; const uid = elUtil.getUrlUID(userUrl); const name = userEl.textContent.trim(); list.push({ title, liveUrl, name, userUrl, uid, el, explicitSubjectEl: el.querySelector('.bili-live-card__info'), insertionPositionEl: userEl }); } return list }; const addBlockButton = (data) => { shielding.addBlockButton(data, '', ['right']); }; const startShieldingLiveRoomList = async () => { const list = await getLiveRoomList(); for (let liveData of list) { if (live_shielding.shieldingLiveRoomDecorated(liveData)) { continue } addBlockButton({data: liveData, maskingFunc: startShieldingLiveRoomList}); } }; const InstallLiveTopTabsListener = async () => { const el = await elUtil.findElement('.live-condition'); if (elEventEmitter.hasEventName(el, 'click')) return elEventEmitter.addEvent(el, 'click', async (event) => { const target = event.target; const label = target.textContent.trim(); if (label === '主播') { return } await startShieldingLiveRoomList(); InstallBottomPagingListener(); installTopRoomOrderListener(); }); console.log("直播顶部选项卡安装监听器已安装"); }; const InstallBottomPagingListener = async () => { const el = await elUtil.findElement('.vui_pagenation--btns'); if (elEventEmitter.hasEventName(el, 'click')) return elEventEmitter.addEvent(el, 'click', async (event) => { const target = event.target; if (target.tagName !== 'BUTTON') { return } await startShieldingLiveRoomList(); installTopRoomOrderListener(); }); console.log("底部分页安装监听器已安装"); }; const installTopRoomOrderListener = async () => { const el = await elUtil.findElement('.room-order'); if (elEventEmitter.hasEventName(el, 'click')) return elEventEmitter.addEvent(el, 'click', async (event) => { const target = event.target; console.log('顶部房间排序监听器触发了', target.textContent.trim(), target); await startShieldingLiveRoomList(); InstallBottomPagingListener(); installTopRoomOrderListener(); }); console.log('顶部房间排序监听器已安装'); }; var searchLive = { InstallLiveTopTabsListener, installStyle, startShieldingLiveRoomList, InstallBottomPagingListener, installTopRoomOrderListener }; const isSearch = (url) => { return url.includes("search.bilibili.com") }; const currentlyActivatedOptions = async () => { const el = await elUtil.findElement('.vui_tabs--nav-item-active .vui_tabs--nav-text'); const label = el.textContent.trim(); if (label === '直播') { await searchLive.startShieldingLiveRoomList(); searchLive.InstallLiveTopTabsListener(); searchLive.InstallBottomPagingListener(); elUtil.findElementUntilFound('.live-condition>.vui_button--active').then(activeEl => { if (activeEl.textContent.trim() !== '主播') { searchLive.installTopRoomOrderListener(); } }); } }; const searchTopTabsIWrapperInstallListener = async () => { const tempTabs = ['番剧', '影视', '用户']; const el = await elUtil.findElement('.vui_tabs--navbar>ul'); el.addEventListener("click", async (event) => { const eventTarget = event.target; if (eventTarget.className !== 'vui_tabs--nav-text') { return } const tabName = eventTarget.textContent.trim(); if (tempTabs.includes(tabName)) { return } if (tabName === '直播') { searchLive.installTopRoomOrderListener(); return } console.log("搜索页顶部选项卡监听器触发了", tabName); }); console.log("搜索页顶部选项卡安装监听器已安装"); }; const getVideoList$1 = async (css) => { const elList = await elUtil.findElements(css, {interval: 200}); const list = []; for (let el of elList) { const title = el.querySelector(".bili-video-card__info--tit").title; const userEl = el.querySelector(".bili-video-card__info--owner"); if (userEl === null) { console.log("获取不到该视频卡片的用户地址,", el); el?.remove(); continue } const userUrl = userEl.getAttribute("href"); if (!userUrl.includes("//space.bilibili.com/")) { el?.remove(); console.log("移除了非视频内容", userUrl, el); continue; } const videoUrl = el.querySelector(".bili-video-card__info--right>a")?.href; if (videoUrl?.includes('live.bilibili.com/')) { continue } const bv = elUtil.getUrlBV(videoUrl); const uid = elUtil.getUrlUID(userUrl); const name = userEl.querySelector(".bili-video-card__info--author").textContent.trim(); const bili_video_card__stats_item = el.querySelectorAll('.bili-video-card__stats--item'); let nPlayCount = bili_video_card__stats_item[0]?.textContent.trim(); nPlayCount = sFormatUtil.toPlayCountOrBulletChat(nPlayCount); let nBulletChat = bili_video_card__stats_item[1]?.textContent.trim(); nBulletChat = sFormatUtil.toPlayCountOrBulletChat(nBulletChat); let nDuration = el.querySelector('.bili-video-card__stats__duration')?.textContent.trim(); nDuration = sFormatUtil.timeStringToSeconds(nDuration); list.push({ title, userUrl, name, uid, bv, nPlayCount, nBulletChat, nDuration, el, videoUrl, insertionPositionEl: el.querySelector(".bili-video-card__info--bottom"), explicitSubjectEl: el.querySelector(".bili-video-card__info") }); } return list; }; const getTabComprehensiveSortedVideoList = () => { return getVideoList$1(".video.i_wrapper.search-all-list>.video-list>div"); }; const getOtherVideoList = () => { return getVideoList$1(".search-page.search-page-video>.video-list.row>div:not(:empty)"); }; const startShieldingCSVideoList = async () => { const list = await getTabComprehensiveSortedVideoList(); for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue; } eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: startShieldingCSVideoList}); } }; const startShieldingOtherVideoList = async () => { const list = await getOtherVideoList(); for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue; } eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: startShieldingOtherVideoList}); } }; const getTwTabActiveItem = async () => { const twoTabActiveItem = await elUtil.findElement('.vui_button.vui_button--tab.vui_button--active.mr_sm', {interval: 200}); const twoTabActiveItemLabel = twoTabActiveItem.textContent.trim(); return {el: twoTabActiveItemLabel, label: twoTabActiveItemLabel} }; const startShieldingVideoList$6 = async () => { const topTabActiveItem = await elUtil.findElement('.vui_tabs--nav-item.vui_tabs--nav-item-active', {interval: 200}); const topTabActiveItemLabel = topTabActiveItem.textContent.trim(); console.log(topTabActiveItemLabel); if (topTabActiveItemLabel !== '综合') { await startShieldingOtherVideoList(); return } const {label} = await getTwTabActiveItem(); if (label !== '综合排序') { await startShieldingOtherVideoList(); return } const parseUrl = defUtil.parseUrl(window.location.href); if (parseUrl.queryParams['page']) { await startShieldingOtherVideoList(); } else { await startShieldingCSVideoList(); processingExactSearchVideoCardContent(); } }; const processingExactSearchVideoCardContent = async () => { let res; try { res = await elUtil.findElement('.user-list.search-all-list', {interval: 50, timeout: 4000}); } catch (e) { return } let el; if (!res.state) { return } el = res.data; const infoCardEl = el.querySelector('.info-card'); const userNameEl = infoCardEl.querySelector('.user-name'); const name = userNameEl.textContent.trim(); const userUrl = userNameEl.href; const uid = elUtil.getUrlUID(userUrl); if (ruleMatchingUtil.exactMatch(ruleKeyListData$1.getPreciseUidArr(), uid)) { el.remove(); eventEmitter.send('打印信息', `根据精确uid匹配到用户${name}-【${uid}】`); return } let fuzzyMatch = ruleMatchingUtil.fuzzyMatch(ruleKeyListData$1.getNameArr(), name); if (fuzzyMatch) { el.remove(); eventEmitter.send('打印信息', `根据模糊用户名【${fuzzyMatch}】匹配到用户${name}-【${uid}】`); return } fuzzyMatch = ruleMatchingUtil.regexMatch(ruleKeyListData$1.getNameCanonical(), name); if (fuzzyMatch) { el.remove(); eventEmitter.send('打印信息', `根据正则用户名【${fuzzyMatch}】匹配到用户${name}-【${uid}】`); return } const insertionPositionEl = el.querySelector('.info-card.flex_start'); shielding.addBlockButton({ data: { name, uid, insertionPositionEl, } }); const videoElList = el.querySelectorAll('.video-list>.video-list-item'); const list = []; for (let videoEl of videoElList) { const titleEl = videoEl.querySelector('.bili-video-card__info--right>a'); const videoUrl = titleEl.href; const bv = elUtil.getUrlBV(videoUrl); const title = titleEl.textContent.trim(); let nDuration = videoEl.querySelector('.bili-video-card__stats__duration')?.textContent.trim(); nDuration = sFormatUtil.timeStringToSeconds(nDuration); let nPlayCount = videoEl.querySelector('.bili-video-card__stats--item>span')?.textContent.trim(); nPlayCount = sFormatUtil.toPlayCountOrBulletChat(nPlayCount); list.push({ title, userUrl, name, uid, bv, nPlayCount, nDuration, el: videoEl, videoUrl }); } for (let videoData of list) { video_shielding.shieldingVideoDecorated(videoData); } }; const delFooterContent = () => { if (!gmUtil.getData('isRemoveSearchBottomContent', false)) { return } elUtil.findElement('#biliMainFooter').then(el => { el.remove(); eventEmitter.send('打印信息', '已删除底部内容'); }); }; var searchModel = { isSearch, searchTopTabsIWrapperInstallListener, startShieldingVideoList: startShieldingVideoList$6, currentlyActivatedOptions, delFooterContent }; const isVideoPlayPage = (url = window.location.href) => { return url.includes("www.bilibili.com/video"); }; const selectUserBlocking = async () => { const {state} = await elUtil.findElement('.header.can-pointer', {timeout: 1800}); if (state) { const elList = document.querySelectorAll('.container>.membersinfo-upcard-wrap>.membersinfo-upcard'); const list = []; for (const el of elList) { const userUrl = el.querySelector('.avatar').href; const uid = elUtil.getUrlUID(userUrl); const name = el.querySelector('.staff-name').textContent.trim(); list.push({ label: `用户-name=${name}-uid=${uid}`, uid }); } eventEmitter.send('sheet-dialog', { title: '选择要屏蔽的用户(uid精确)', list, optionsClick: (item) => { ruleUtil.addRulePreciseUid(item.uid); return true } }); } else { const el = document.querySelector('.up-info-container'); const nameEl = el.querySelector('.up-info--right a.up-name'); const name = nameEl.textContent.trim(); const userUrl = nameEl.href; const uid = elUtil.getUrlUID(userUrl); console.log('点击了屏蔽按钮', name, userUrl, uid); eventEmitter.invoke('el-confirm', `用户uid=${uid}-name=${name}`, 'uid精确屏蔽方式').then(() => { if (uid === -1) { eventEmitter.send('el-msg', "该页面数据不存在uid字段"); return; } ruleUtil.addRulePreciseUid(uid); }); } }; const getGetTheVideoListOnTheRight$1 = async () => { await elUtil.findElementUntilFound(".video-page-card-small .b-img img"); delAd(); delGameAd(); const elList = await elUtil.findElements(".rec-list>.video-page-card-small", {interval: 1000}); const nextPlayEl = document.querySelector('.next-play>.video-page-card-small'); if (nextPlayEl) { elList.push(nextPlayEl); } const list = []; for (let el of elList) { try { const elInfo = el.querySelector(".info"); const title = elInfo.querySelector(".title").title; const name = elInfo.querySelector(".upname .name").textContent.trim(); const userUrl = elInfo.querySelector(".upname>a").href; const uid = elUtil.getUrlUID(userUrl); const playInfo = el.querySelector('.playinfo').innerHTML.trim(); const videoUrl = el.querySelector(".info>a").href; const bv = elUtil.getUrlBV(videoUrl); let nPlayCount = playInfo.match(/<\/svg>(.*)<svg/s)?.[1].trim(); nPlayCount = sFormatUtil.toPlayCountOrBulletChat(nPlayCount); let nBulletChat = playInfo.match(/class="dm".+<\/svg>(.+)$/s)?.[1].trim(); nBulletChat = sFormatUtil.toPlayCountOrBulletChat(nBulletChat); let nDuration = el.querySelector('.duration')?.textContent.trim(); nDuration = sFormatUtil.timeStringToSeconds(nDuration); list.push({ title, userUrl, name, uid, bv, nPlayCount, nBulletChat, nDuration, el, videoUrl, insertionPositionEl: el.querySelector(".playinfo"), explicitSubjectEl: elInfo }); } catch (e) { console.error("获取右侧视频列表失败:", e); } } return list; }; const startShieldingVideoList$5 = () => { if (localMKData.isDelPlayerPageRightVideoList()) { return } getGetTheVideoListOnTheRight$1().then((videoList) => { for (let videoData of videoList) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue; } eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: startShieldingVideoList$5}); } }); }; const findTheExpandButtonForTheListOnTheRightAndBindTheEvent$2 = () => { setTimeout(() => { elUtil.findElementUntilFound(".rec-footer", {interval: 2000}).then((el) => { console.log("找到右侧视频列表的展开按钮", el); el.addEventListener("click", () => { startShieldingVideoList$5(); }); }); }, 3000); }; const getPlayerVideoList = async () => { const elList = await elUtil.findElements('.bpx-player-ending-related>.bpx-player-ending-related-item'); const data = {list: [], cancelEl: null}; for (const el of elList) { const title = el.querySelector('.bpx-player-ending-related-item-title')?.textContent.trim(); const cancelEl = el.querySelector('.bpx-player-ending-related-item-cancel'); if (cancelEl) { data.cancelEl = cancelEl; } data.list.push({ title, el }); } return data }; const getVideoPlayerEndingPanelEl = async () => { return await elUtil.findElement('#bilibili-player .bpx-player-ending-wrap>.bpx-player-ending-panel', {interval: 50}) }; const setVideoPlayerEnded = async () => { const videoEl = await elUtil.findElement('#bilibili-player video'); const funcStart = async () => { const res = await getPlayerVideoList(); for (let {el, title} of res.list) { let matching = ruleMatchingUtil.fuzzyMatch(ruleKeyListData$1.getTitleArr(), title); if (matching !== null) { eventEmitter.send('打印信息', `根据-模糊标题-【${matching}】-屏蔽视频:${title}`); el.remove(); continue } matching = ruleMatchingUtil.regexMatch(ruleKeyListData$1.getTitleCanonicalArr(), title); if (matching !== null) { eventEmitter.send('打印信息', `根据-正则标题-【${matching}】-屏蔽视频:${title}`); el.remove(); } } }; videoEl.addEventListener('ended', () => { console.log('视频播放结束'); funcStart(); if (localMKData.isDelPlayerEndingPanel()) { getVideoPlayerEndingPanelEl().then(el => { el.remove(); eventEmitter.send('打印信息', '已删除播放页播放器中推荐层'); }); } }); }; const delAd = () => { if (!gmUtil.getData('isDelPlayerPageAd', false)) { return } elUtil.findElements('[class|=ad],#slide_ad').then(elList => { for (const el of elList) { el.style.display = 'none'; } eventEmitter.send('打印信息', '隐藏了播放页的页面广告'); }); }; const delRightVideoList = () => { if (!localMKData.isDelPlayerPageRightVideoList()) { return } elUtil.findElement('.recommend-list-v1').then(el => { el.style.visibility = "hidden"; eventEmitter.send('打印信息', '屏蔽了播放页的右侧推荐列表'); }); }; const delGameAd = () => { if (!gmUtil.getData('isDelPlayerPageRightGameAd', false)) { return } elUtil.findElement('.video-page-game-card-small', {timeout: 10000}).then(({state, data}) => { if (!state) { eventEmitter.send('打印信息', '没有找到播放页的右侧游戏推荐'); return } data?.remove(); eventEmitter.send('打印信息', '屏蔽了游戏推荐'); }); }; const delBottomCommentApp = () => { if (!localMKData.isDelBottomComment()) { return } elUtil.findElement('#commentapp').then(el => { el?.remove(); eventEmitter.send('打印信息', '移除了页面底部的评论区'); }); }; const delElManagement = () => { if (localMKData.isDelPlayerPageRightVideoList()) { delAd(); } delRightVideoList(); delBottomCommentApp(); }; var videoPlayModel = { isVideoPlayPage, startShieldingVideoList: startShieldingVideoList$5, findTheExpandButtonForTheListOnTheRightAndBindTheEvent: findTheExpandButtonForTheListOnTheRightAndBindTheEvent$2, selectUserBlocking, setVideoPlayerEnded, delElManagement }; const getPlayCountAndBulletChatAndDuration = (el) => { const playInfo = el.querySelector('.playinfo').innerHTML.trim(); let nPlayCount = playInfo.match(/<\/svg>(.*)<svg/s)?.[1].trim(); nPlayCount = sFormatUtil.toPlayCountOrBulletChat(nPlayCount); let nBulletChat = playInfo.match(/class="dm-icon".+<\/svg>(.+)$/s)?.[1].trim(); nBulletChat = sFormatUtil.toPlayCountOrBulletChat(nBulletChat); let nDuration = el.querySelector('.duration')?.textContent.trim(); nDuration = sFormatUtil.timeStringToSeconds(nDuration); return { nPlayCount, nBulletChat, nDuration } }; const getRightVideoDataList$1=(elList)=>{ const list = []; for (let el of elList) { const title = el.querySelector(".title").textContent.trim(); const userInfoEl = el.querySelector(".upname"); const name = userInfoEl.querySelector(".name").textContent.trim(); const userUrl = userInfoEl.href; const uid = elUtil.getUrlUID(userUrl); const videoUrl = el.querySelector(".info>a").href; const bv = elUtil.getUrlBV(videoUrl); list.push({ ...getPlayCountAndBulletChatAndDuration(el), ...{ title, name, userUrl, videoUrl, uid, bv, el, insertionPositionEl: el.querySelector(".playinfo"), explicitSubjectEl: el.querySelector(".info") } }); } return list; }; var generalFuc = {getRightVideoDataList: getRightVideoDataList$1}; const iscCollectionVideoPlayPage = (url) => { return url.includes("www.bilibili.com/list/ml") }; const getGetTheVideoListOnTheRight = async () => { const elList = await elUtil.findElementsUntilFound(".recommend-list-container>.video-card"); return generalFuc.getRightVideoDataList(elList); }; const startShieldingVideoList$4 = () => { getGetTheVideoListOnTheRight().then((videoList) => { const css = {right: "123px"}; for (let videoData of videoList) { if (video_shielding.shieldingVideoDecorated(videoData)) continue; videoData.css = css; eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: startShieldingVideoList$4}); } }); }; const findTheExpandButtonForTheListOnTheRightAndBindTheEvent$1 = () => { setTimeout(() => { elUtil.findElementUntilFound(".rec-footer", {interval: 2000}).then((el) => { el.addEventListener("click", () => { startShieldingVideoList$4(); }); }); }, 3000); }; var collectionVideoPlayPageModel = { iscCollectionVideoPlayPage, startShieldingVideoList: startShieldingVideoList$4, findTheExpandButtonForTheListOnTheRightAndBindTheEvent: findTheExpandButtonForTheListOnTheRightAndBindTheEvent$1 }; const addEventListenerUrlChange = (callback) => { let oldUrl = window.location.href; setInterval(() => { const newUrl = window.location.href; if (oldUrl === newUrl) return; oldUrl = newUrl; const title = document.title; callback(newUrl, oldUrl, title); }, 1000); }; const addEventListenerNetwork = (callback) => { new PerformanceObserver(() => { const entries = performance.getEntriesByType('resource'); const windowUrl = window.location.href; const winTitle = document.title; for (let entry of entries) { const url = entry.name; const initiatorType = entry.initiatorType; if (initiatorType === "img" || initiatorType === "css" || initiatorType === "link" || initiatorType === "beacon") { continue; } callback(url, windowUrl,winTitle, initiatorType); } performance.clearResourceTimings();//清除资源时间 }).observe({entryTypes: ['resource']}); }; function watchElementListLengthWithInterval(selector, callback, config={}) { const defConfig = {}; config = {...defConfig, ...config}; let previousLength = -1; const timer = setInterval(() => { if (previousLength === -1) { previousLength = document.querySelectorAll(selector).length; return } const currentElements = document.querySelectorAll(selector); const currentLength = currentElements.length; if (currentLength !== previousLength) { previousLength = currentLength; callback({ action: currentLength > previousLength ? 'add' : 'del', elements: currentElements, length: currentLength } ); } }, config.interval ); return stop = () => { clearInterval(timer); }; } var watch = { addEventListenerUrlChange, addEventListenerNetwork, watchElementListLengthWithInterval }; const isLiveRoom = (url) => { return url.search('/live.bilibili.com/\\d+') !== -1; }; const getChatItems = async () => { const elList = await elUtil.findElementsUntilFound("#chat-items>div"); if (elList.length >= 200) { for (let i = 0; i < 100; i++) { elList[i]?.remove(); } console.log("弹幕列表超过200,已删除前100条"); } const list = []; for (let el of elList) { if (el.className === "chat-item convention-msg border-box") { continue; } if (el.className === "chat-item misc-msg guard-buy") { continue; } const name = el.getAttribute("data-uname"); if (name === null) { continue; } const uid = el.getAttribute("data-uid"); const content = el.getAttribute("data-danmaku"); const timeStamp = el.getAttribute("data-timestamp"); const fansMedalEl = el.querySelector(".fans-medal-content"); const fansMedal = fansMedalEl === null ? null : fansMedalEl.textContent.trim(); list.push({ name, uid, content, timeStamp, fansMedal, el, insertionPositionEl: el, explicitSubjectEl: el }); } return list; }; const startShieldingLiveChatContents = async () => { const commentsDataList = await getChatItems(); for (let commentsData of commentsDataList) { if (shieldingLiveRoomContentDecorated(commentsData)) { continue; } live_shielding.addLiveContentBlockButton({data: commentsData, maskingFunc: startShieldingLiveChatContents}); } }; const addWatchLiveRoomChatItemsListener = () => { const throttle = defUtil.throttle(startShieldingLiveChatContents, 1000); watch.watchElementListLengthWithInterval("#chat-items>div", throttle); }; var liveRoomModel = { isLiveRoom, addWatchLiveRoomChatItemsListener }; const isVideoPlayWatchLaterPage = (url) => { return url.startsWith("https://www.bilibili.com/list/watchlater") }; const getRightVideoDataList = async () => { const elList = await elUtil.findElementsUntilFound(".recommend-video-card.video-card"); return generalFuc.getRightVideoDataList(elList); }; const startShieldingVideoList$3 = async () => { const videoList = await getRightVideoDataList(); const css = {right: "123px"}; for (let videoData of videoList) { videoData.css = css; if (video_shielding.shieldingVideoDecorated(videoData)) continue; eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: startShieldingVideoList$3}); } }; const startDebounceShieldingVideoList = defUtil.debounce(startShieldingVideoList$3, 1000); const findTheExpandButtonForTheListOnTheRightAndBindTheEvent = () => { elUtil.findElementsAndBindEvents(".rec-footer", startDebounceShieldingVideoList); }; var videoPlayWatchLater = { isVideoPlayWatchLaterPage, startDebounceShieldingVideoList, findTheExpandButtonForTheListOnTheRightAndBindTheEvent }; const getBewlyEl = async () => { let el = await elUtil.findElementUntilFound('#bewly', {interval: 500}); return el.shadowRoot; }; const isBEWLYPage = (url) => { return url.includes('www.bilibili.com/?page=') || url === 'https://www.bilibili.com/' || url.startsWith('https://www.bilibili.com/?spm_id_from=') }; const check_BEWLYPage_compatibility = async () => { const {state} = await elUtil.findElement('#bewly', {interval: 200, timeout: 5000}); if (state) { if (!globalValue.compatibleBEWLYBEWLY) { eventEmitter.send('el-alert', '检测到使用BewlyBewly插件但未开启兼容选项,需要启用相关兼容选项才可正常使用'); } } else { if (globalValue.compatibleBEWLYBEWLY) { eventEmitter.send('el-alert', '检测到未使用BewlyBewly插件却开启了兼容选项,请关闭兼容选项或启用bilibili_gate脚本后再启用相关兼容选项'); } } }; const getVideoList = async () => { const beEl = await getBewlyEl(); const elList = await elUtil.findElementsUntilFound('.video-card.group', {doc: beEl}); const list = []; for (let el of elList) { const parentElement = el.parentElement.parentElement; const title = el.querySelector('.keep-two-lines>a[title]').textContent.trim(); const userUrlEl = el.querySelector('.channel-name'); const userUrl = userUrlEl.href; const uid = elUtil.getUrlUID(userUrl); const name = userUrlEl.textContent.trim(); const playInfoEl = el.querySelector('[flex="~ items-center gap-1 wrap"]>div'); let playCount = playInfoEl.querySelector('span:first-child')?.textContent.trim() || null; playCount = sFormatUtil.toPlayCountOrBulletChat(playCount); let bulletChat = playInfoEl.querySelector('span:last-of-type')?.textContent.trim() || null; if (playInfoEl.querySelectorAll('span').length < 2) { bulletChat = -1; } else { bulletChat = sFormatUtil.toPlayCountOrBulletChat(bulletChat); } let nDuration = el.querySelector('[class*="group-hover:opacity-0"]')?.textContent.trim() || null; nDuration = sFormatUtil.timeStringToSeconds(nDuration); const videoUrl = el.querySelector('[href*="https://www.bilibili.com/video"]')?.href; const bv = elUtil.getUrlBV(videoUrl); const insertionPositionEl = el.querySelector('[class="group/desc"]'); list.push({ title, name, uid, bv, userUrl, videoUrl, playCount, bulletChat, nDuration, el: parentElement, insertionPositionEl, explicitSubjectEl: parentElement }); } return list }; const getRightTabs = async () => { const beEl = await getBewlyEl(); const els = await elUtil.findElementsUntilFound(".dock-content-inner>.b-tooltip-wrapper", {doc: beEl}); const list = []; for (let el of els) { const label = el.querySelector('.b-tooltip').textContent.trim(); const active = !!el.querySelector('.dock-item.group.active'); list.push({label, active, el}); } return list; }; const getHistoryVideoDataList = async () => { const beEL = await getBewlyEl(); const elList = await elUtil.findElementsUntilFound("a.group[flex][cursor-pointer]", {doc: beEL}); const list = []; for (let el of elList) { const titleEl = el.querySelector('h3.keep-two-lines'); const videoUrlEl = titleEl.parentElement; const userEl = videoUrlEl.nextElementSibling; const videoUrl = videoUrlEl.href; const bv = elUtil.getUrlBV(videoUrl); const userUrl = userEl.href; const uid = elUtil.getUrlUID(userUrl); const name = userEl.textContent.trim(); const title = titleEl?.textContent.trim(); const tempTime = el.querySelector('div[pos][rounded-8]')?.textContent.trim().split(/[\t\r\f\n\s]*/g).join(""); const match = tempTime?.match(/\/(.*)/); let nDuration = match?.[1]; nDuration = sFormatUtil.timeStringToSeconds(nDuration); list.push({ title, userUrl, name, uid, videoUrl, nDuration, bv, el, insertionPositionEl: videoUrlEl.parentElement, explicitSubjectEl: el }); } return list }; const startShieldingHistoryVideoList = async () => { const list = await getHistoryVideoDataList(); for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue } eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: startShieldingHistoryVideoList}); } }; const startShieldingVideoList$2 = async () => { const list = await getVideoList(); for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue } eventEmitter.send('视频添加屏蔽按钮-BewlyBewly', {data: videoData, maskingFunc: startShieldingVideoList$2}); } }; const intervalExecutionStartShieldingVideo$2 = () => { const res = shielding.intervalExecutionStartShieldingVideoInert(startShieldingVideoList$2, '视频'); return () => { return res } }; const intervalExecutionStartShieldingHistoryVideo = () => { const res = shielding.intervalExecutionStartShieldingVideoInert(startShieldingHistoryVideoList, '历史记录'); return () => { return res } }; const startShieldingVideo$1 = intervalExecutionStartShieldingVideo$2(); const startShieldingHistoryVideo = intervalExecutionStartShieldingHistoryVideo(); const rightTabsInsertListener = () => { getRightTabs().then(list => { for (let {el, label, active} of list) { el.addEventListener('click', () => { console.log('右侧选项卡栏点击了' + label, active); if (label === '首页') { homeTopTabsInsertListener(); startShieldingVideo$1().start(); } else { startShieldingVideo$1().stop(); } if (label === '观看历史') { startShieldingHistoryVideo().start(); } else { startShieldingHistoryVideo().stop(); } } ); } } ); }; const getHomeTopTabs = async () => { const beEl = await getBewlyEl(); const els = beEl.querySelectorAll('.home-tabs-inside>[data-overlayscrollbars-contents]>button'); const list = []; for (let el of els) { const label = el.textContent.trim(); const active = el.classList.contains('tab-activated'); list.push({label, active, el}); } if (list.some(tab => tab.active === true)) { return list } return await getHomeTopTabs() }; const excludeTabNames = ['正在关注', '订阅剧集', '直播']; const excludeRankingLeftTabNames = ['番剧', '综艺', '电视剧', '纪录片', '中国动画']; const homeTopTabsInsertListener = () => { getHomeTopTabs().then(list => { for (let {el, label} of list) { el.addEventListener('click', () => { console.log('点击了' + label); if (excludeTabNames.includes(label)) { startShieldingVideo$1().stop(); return } if (label === '排行') { rankingLeftTabsInsertListener(); } startShieldingVideo$1().start(); }); } }); }; const getRankingLeftTabs = async () => { const beEl = await getBewlyEl(); const elList = await elUtil.findElementsUntilFound('ul[flex="~ col gap-2"]>li', {doc: beEl}); const list = []; for (let el of elList) { const label = el.textContent.trim(); list.push({label, el}); } return list }; const rankingLeftTabsInsertListener = () => { getRankingLeftTabs().then(list => { for (let {el, label} of list) { el.addEventListener('click', () => { console.log('点击了' + label); if (excludeRankingLeftTabNames.includes(label)) { startShieldingVideo$1().stop(); return } startShieldingVideo$1().start(); }); } }); }; const installBEWLStyle = () => { getBewlyEl().then(el => { gz_ui_css.addStyle(el, el); }); }; const searchBoxInsertListener = async () => { const beEl = await getBewlyEl(); const input = await elUtil.findElementUntilFound('[placeholder="搜索观看历史"]', {doc: beEl}); input.addEventListener('keydown', (event) => { if (event.key === 'Enter' || event.keyCode === 13) { console.log('回车键被按下'); if (input['value'].length === 0) return setTimeout(startShieldingHistoryVideoList, 1500); } }); }; const startRun$1 = async (url) => { const parseUrl = defUtil.parseUrl(url); const {page} = parseUrl.queryParams; installBEWLStyle(); if (page === 'Home' || url.startsWith('https://www.bilibili.com/?spm_id_from=') || url === 'https://www.bilibili.com/' ) { startShieldingVideo$1().start(); homeTopTabsInsertListener(); } if (page === 'History') { startShieldingHistoryVideo().start(); searchBoxInsertListener(); } rightTabsInsertListener(); }; var compatibleBewlyBewly = { startRun: startRun$1, isBEWLYPage, check_BEWLYPage_compatibility }; const isNewHistoryPage = (url) => { return url.includes('://www.bilibili.com/history') }; const getDuration = (str) => { if (str === null) { return -1 } if (str.includes('已看完') || str === '') { return -1 } else { const match = str?.match(/\/(.*)/); if (match) { return sFormatUtil.timeStringToSeconds(match[1]); } } return -1 }; const getVideoDataList$2 = async () => { const elList = await elUtil.findElementsUntilFound('.section-cards.grid-mode>div'); const list = []; for (let el of elList) { const titleEl = el.querySelector('.bili-video-card__title'); const title = titleEl.textContent.trim(); const videoUrl = titleEl.firstElementChild.href||null; if (videoUrl?.includes('live.bilibili.com')) { continue } const bv=elUtil.getUrlBV(videoUrl); const userEl = el.querySelector('.bili-video-card__author'); const cardTag = el.querySelector('.bili-cover-card__tag')?.textContent.trim() || null; const name = userEl.textContent.trim(); const userUrl = userEl.href; const uid = elUtil.getUrlUID(userUrl); let nDuration = -1; if (cardTag !== '专栏') { nDuration = el.querySelector('.bili-cover-card__stat')?.textContent.trim() || null; nDuration = getDuration(nDuration); } const tempEL = el.querySelector('.bili-video-card__details'); list.push({ title, videoUrl, name, userUrl, nDuration, uid, el, bv, insertionPositionEl: tempEL, explicitSubjectEl: tempEL }); } return list }; const startShieldingVideoList$1 = async () => { const list = await getVideoDataList$2(); for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue; } shielding.addBlockButton({data: videoData, maskingFunc: startShieldingVideoList$1}, "gz_shielding_button"); } }; const intervalExecutionStartShieldingVideo$1 = () => { const res = shielding.intervalExecutionStartShieldingVideoInert(startShieldingVideoList$1, '历史记录项'); return () => { return res } }; const executionStartShieldingVideo = intervalExecutionStartShieldingVideo$1(); const getTopFilterLabel = async () => { const el = await elUtil.findElementUntilFound('.radio-filter>.radio-filter__item--active'); return el.textContent?.trim() }; const topFilterInsertListener = () => { elUtil.findElementUntilFound('.radio-filter').then((el => { el.addEventListener('click', (e) => { const target = e.target; const label = target.textContent?.trim(); console.log(`点击了${label}`); if (label === '直播') { executionStartShieldingVideo().stop(); return } executionStartShieldingVideo().start(); }); })); }; const startRun = () => { getTopFilterLabel().then(label => { if (label === '直播') { return } executionStartShieldingVideo().start(); }); topFilterInsertListener(); }; var newHistory = { isNewHistoryPage, intervalExecutionStartShieldingVideo: intervalExecutionStartShieldingVideo$1, startRun }; const startShieldingHotList = async () => { const elList = await elUtil.findElements(".trendings-col>.trending-item", {interval: 2000}); console.log("检查热搜关键词中..."); const hotSearchKeyArr = ruleKeyListData$1.getHotSearchKeyArr(); const hotSearchKeyCanonicalArr = ruleKeyListData$1.getHotSearchKeyCanonicalArr(); for (let el of elList) { const label = el.textContent.trim(); let match = ruleMatchingUtil.fuzzyMatch(hotSearchKeyArr, label); if (match) { el.remove(); eventEmitter.send('打印信息', `根据模糊热搜关键词-【${match}】-屏蔽-${label}`); continue; } match = ruleMatchingUtil.regexMatch(hotSearchKeyCanonicalArr, label); if (match) { eventEmitter.send('打印信息', `根据正则热搜关键词-【${match}】-屏蔽-${label}`); el.remove(); } } }; var hotSearch = { startShieldingHotList }; const isMessagePage = (url = window.location.href) => { return url.includes("message.bilibili.com"); }; const modifyTopItemsZIndex = () => { elUtil.findElement('#home_nav').then(el => { el.style.zIndex = 1000; eventEmitter.send('打印信息', '已修改顶部的z-index值为1'); }); }; var messagePage = { isMessagePage, modifyTopItemsZIndex, }; const isSpacePage = (url = window.location.href) => { return url.startsWith('https://space.bilibili.com/') }; const isPersonalHomepage = async () => { const keyStr = 'isPersonalHomepage'; const cache = valueCache.get(keyStr); if (cache) { return cache } const { state: newState, data: newData } = await elUtil.findElements('.nav-tab__item .nav-tab__item-text', {timeout: 2500}); if (newState) { const bool = newData.some(el => el.textContent.trim() === '设置'); valueCache.set('space_version', 'new'); return valueCache.set(keyStr, bool); } let {state} = await elUtil.findElement('.n-tab-links>.n-btn.n-setting>.n-text', {timeout: 1500}); valueCache.set('space_version', 'old'); return valueCache.set(keyStr, state); }; const getUserInfo = async () => { const spaceUserInfo = valueCache.get('space_userInfo'); if (spaceUserInfo) { return spaceUserInfo } await isPersonalHomepage(); const nameData = {}; nameData.uid = elUtil.getUrlUID(window.location.href); if (valueCache.get('space_version', 'new') === 'new') { nameData.name = await elUtil.findElement('.nickname').then(el => el.textContent.trim()); } else { nameData.name = await elUtil.findElement('#h-name').then(el => el.textContent.trim()); } if (!nameData.name) { const title = document.title; nameData.name = title.match(/(.+)的个人空间/)[1]; } valueCache.set('space_userInfo', nameData); return nameData }; var space = { isPersonalHomepage, isSpacePage, getUserInfo }; const getGateActivatedTab = async () => { const el = await elUtil.findElementUntilFound(".ant-radio-group>.ant-radio-button-wrapper-checked .css-1k4kcw8"); return el?.textContent.trim(); }; const check_bilibili_gate_compatibility = async () => { const {state} = await elUtil.findElement('.bilibili-gate-root', {interval: 300, timeout: 5000}); if (state) { if (!globalValue.adaptationBAppCommerce) { eventEmitter.send('el-alert', "检测到使用bilibili_gate脚本但未开启兼容选项,需要启用相关兼容选项才可正常使用"); } else { eventEmitter.send('el-notify', { title: "tip", message: '启用兼容bilibili-gate脚本', position: 'bottom-right', }); } return } if (globalValue.adaptationBAppCommerce) { eventEmitter.send('el-alert', "检测到未使用bilibili_gate脚本却开启了兼容选项,请关闭兼容选项或启用bilibili_gate脚本后再启用相关兼容选项"); } }; const getGateDataList = async () => { const elList = await elUtil.findElementsUntilFound(".bilibili-gate-video-grid>[data-bvid].bili-video-card"); const list = []; for (let el of elList) { const tempData = bilibiliHome.getVideoData(el); const videoUrl = el.querySelector("a.css-feo88y")?.href; const bv = elUtil.getUrlBV(videoUrl); const insertionPositionEl = el.querySelector(".bili-video-card__info--owner"); list.push({ ...tempData, ...{ videoUrl, el, bv, insertionPositionEl, explicitSubjectEl: el } }); } return list; }; const startShieldingGateVideoList = async () => { const list = await getGateDataList(); for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData, "hide")) { continue; } eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: startShieldingGateVideoList}); } }; const startIntervalShieldingGateVideoList = () => { const throttle = defUtil.throttle(startShieldingGateVideoList, 2000); setInterval(async () => { await getGateActivatedTab(); throttle(); }, 1500); }; var BLBLGate = { check_bilibili_gate_compatibility, startIntervalShieldingGateVideoList }; const bOnlyTheHomepageIsBlocked$2 = localMKData.getBOnlyTheHomepageIsBlocked(); const staticRoute = (title, url) => { console.log("静态路由", title, url); if (bilibiliHome.isHome(url, title)) { BLBLGate.check_bilibili_gate_compatibility(); compatibleBewlyBewly.check_BEWLYPage_compatibility(); if (globalValue.compatibleBEWLYBEWLY) return; bilibiliHome.scrollMouseUpAndDown().then(() => bilibiliHome.startDebounceShieldingChangeVideoList()); bilibiliHome.startClearExcessContentList(); bilibiliHome.deDesktopDownloadTipEl(); } if (bOnlyTheHomepageIsBlocked$2) return; topInput.processTopInputContent(); hotSearch.startShieldingHotList(); eventEmitter.send('通知屏蔽'); if (compatibleBewlyBewly.isBEWLYPage(url)) { if (globalValue.compatibleBEWLYBEWLY) { compatibleBewlyBewly.startRun(url); } } if (searchModel.isSearch(url)) { searchModel.searchTopTabsIWrapperInstallListener(); searchModel.startShieldingVideoList(); searchModel.currentlyActivatedOptions(); searchLive.installStyle(); searchModel.delFooterContent(); } if (videoPlayModel.isVideoPlayPage(url)) { elUtil.updateCssVModal(); videoPlayModel.findTheExpandButtonForTheListOnTheRightAndBindTheEvent(); videoPlayModel.setVideoPlayerEnded(); videoPlayModel.delElManagement(); } if (collectionVideoPlayPageModel.iscCollectionVideoPlayPage(url)) { collectionVideoPlayPageModel.findTheExpandButtonForTheListOnTheRightAndBindTheEvent(); } if (liveRoomModel.isLiveRoom(url)) { liveRoomModel.addWatchLiveRoomChatItemsListener(); } if (videoPlayWatchLater.isVideoPlayWatchLaterPage(url)) { elUtil.updateCssVModal(); videoPlayWatchLater.findTheExpandButtonForTheListOnTheRightAndBindTheEvent(); } if (newHistory.isNewHistoryPage(url)) { newHistory.startRun(); } if (messagePage.isMessagePage(url)) { messagePage.modifyTopItemsZIndex(); } if (space.isSpacePage()) { space.getUserInfo().then(userInfo => { console.info('userInfo', userInfo); }); } }; const dynamicRouting = (title, url) => { console.log("动态路由", title, url); if (bOnlyTheHomepageIsBlocked$2) return; eventEmitter.send('通知屏蔽'); }; var router = { staticRoute, dynamicRouting }; const isTopicDetailPage = (url) => { return url.includes("//www.bilibili.com/v/topic/detail/") }; const getDataList$1 = async () => { const elList = await elUtil.findElementsUntilFound(".list__topic-card"); const list = []; for (let el of elList) { const name = el.querySelector(".bili-dyn-title").textContent.trim(); const uidEl = el.querySelector(".bili-dyn-item__following"); const uid = parseInt(uidEl.getAttribute("data-mid")); const judgmentEl = el.querySelector(".bili-dyn-card-video__title"); const data = {name, uid, el, judgmentVideo: judgmentEl !== null}; if (judgmentEl !== null) { data.title = judgmentEl.textContent.trim(); const videoUrl = el.querySelector(".bili-dyn-card-video").href; data.videoUrl = videoUrl; data.bv = elUtil.getUrlBV(videoUrl); data.insertionPositionEl = el.querySelector(".bili-dyn-content__orig"); data.explicitSubjectEl = data.insertionPositionEl; } else { const dynTitle = el.querySelector(".dyn-card-opus__title"); const contentTitle = dynTitle === null ? "" : dynTitle.textContent.trim(); const contentBody = el.querySelector(".bili-rich-text>div").textContent.trim(); data.insertionPositionEl = el.querySelector(".dyn-card-opus"); data.explicitSubjectEl = data.insertionPositionEl; data.content = contentTitle + contentBody; } list.push(data); } return list; }; const __shieldingVideo = (videoData) => { if (video_shielding.shieldingVideoDecorated(videoData)) { return; } shielding.addTopicDetailVideoBlockButton({data: videoData, maskingFunc: startShielding}); }; const __shieldingDynamic = (dynamicData) => { if (comments_shielding.shieldingCommentDecorated(dynamicData)) { return; } shielding.addTopicDetailContentsBlockButton({data: dynamicData, maskingFunc: startShielding}); }; const startShielding = async () => { const list = await getDataList$1(); const css = {width: "100%"}; for (let data of list) { data.css = css; if (data.judgmentVideo) { __shieldingVideo(data); } else { __shieldingDynamic(data); } } }; var topicDetail = { isTopicDetailPage, startShielding }; eventEmitter.on('评论添加屏蔽按钮', (commentsData) => { shielding.addBlockButton({ data: commentsData, maskingFunc: startShieldingComments }, "gz_shielding_comment_button"); }); const getUrlUserLevel = (src) => { const levelMath = src?.match(/level_(.+)\.svg/) || null; let level = -1; if (levelMath !== null) { const levelRow = levelMath[1]; if (levelRow === 'h') { level = 7; } else { level = parseInt(levelRow); } } return level; }; const getOldUserLevel = (iEl) => { let level; const levelCLassName = iEl.classList[1]; if (levelCLassName === 'level-hardcore') { level = 7; } else { const levelMatch = levelCLassName.match(/level-(.+)/)?.[1] || ''; level = parseInt(levelMatch); } return level }; const getCommentSectionList = async () => { const commentApp = await elUtil.findElementUntilFound("bili-comments", {interval: 500}); const comments = await elUtil.findElementsUntilFound("#feed>bili-comment-thread-renderer", {doc: commentApp.shadowRoot, interval: 500}); const commentsData = []; let isLoaded = false; for (let el of comments) { const theOPEl = el.shadowRoot.getElementById("comment").shadowRoot; const theOPUserInfo = theOPEl.querySelector("bili-comment-user-info") .shadowRoot.getElementById("info"); const userNameEl = theOPUserInfo.querySelector("#user-name>a"); const userLevelSrc = theOPUserInfo.querySelector('#user-level>img')?.src || null; const level = getUrlUserLevel(userLevelSrc); isLoaded = theOPEl.querySelector("#content>bili-rich-text") .shadowRoot.querySelector("#contents>*") !== null; if (!isLoaded) { break; } const theOPContentEl = theOPEl.querySelector("#content>bili-rich-text") .shadowRoot.querySelector("#contents"); const theOPContent = theOPContentEl.textContent.trim(); const userName = userNameEl.textContent.trim(); const userUrl = userNameEl.href; const uid = elUtil.getUrlUID(userUrl); const replies = []; commentsData.push({ name: userName, userUrl, uid, level, content: theOPContent, replies, el, insertionPositionEl: theOPUserInfo, explicitSubjectEl: theOPEl.querySelector("#body"), contentsEl: theOPContentEl }); const inTheBuildingEls = el.shadowRoot.querySelector("bili-comment-replies-renderer") .shadowRoot.querySelectorAll("bili-comment-reply-renderer"); for (let inTheBuildingEl of inTheBuildingEls) { const inTheContentEl = inTheBuildingEl.shadowRoot; const biliCommentUserInfo = inTheContentEl.querySelector("bili-comment-user-info"); biliCommentUserInfo.style.display = 'block'; const inTheBuildingUserInfo = biliCommentUserInfo.shadowRoot.getElementById("info"); const inTheBuildingUserNameEl = inTheBuildingUserInfo.querySelector("#user-name>a"); const inTheBuildingUserName = inTheBuildingUserNameEl.textContent.trim(); const inTheBuildingUserUrl = inTheBuildingUserNameEl.href; const inTheBuildingUid = elUtil.getUrlUID(inTheBuildingUserUrl); const biliRichTextEL = inTheContentEl.querySelector("bili-rich-text"); const contentsEl = biliRichTextEL.shadowRoot.querySelector("#contents"); const inTheBuildingContent = contentsEl.textContent.trim(); const userLevelSrc = inTheBuildingUserInfo.querySelector('#user-level>img')?.src || null; const level = getUrlUserLevel(userLevelSrc); replies.push({ name: inTheBuildingUserName, userUrl: inTheBuildingUserUrl, uid: inTheBuildingUid, level, content: inTheBuildingContent, el: inTheBuildingEl, insertionPositionEl: inTheBuildingUserInfo, explicitSubjectEl: inTheBuildingEl, contentsEl }); } } if (!isLoaded) { await defUtil.wait(500); return getCommentSectionList() } return commentsData; }; const getOldCommentSectionList = async () => { let results; try { results = await elUtil.findElementsUntilFound(".reply-list>.reply-item", {timeout: 5000}); } catch (e) { return [] } const commentsData = []; for (let el of results) { const theOPEl = el.querySelector(".root-reply-container"); const theOPUserInfoEl = theOPEl.querySelector(".user-name"); const userName = theOPUserInfoEl.textContent.trim(); const uid = parseInt(theOPUserInfoEl.getAttribute("data-user-id")); const userUrl = `https://space.bilibili.com/${uid}`; const theOPContent = theOPEl.querySelector(".reply-content").textContent.trim(); const userInfoEl = el.querySelector(".user-info"); const iEl = userInfoEl.querySelector('i'); const level = getOldUserLevel(iEl); const replies = []; commentsData.push({ name: userName, userUrl, uid, content: theOPContent, level, replies, el, insertionPositionEl: userInfoEl, explicitSubjectEl: el.querySelector(".content-warp") }); const inTheBuildingEls = el.querySelectorAll(".sub-reply-container>.sub-reply-list>.sub-reply-item"); for (let inTheBuildingEl of inTheBuildingEls) { const subUserNameEl = inTheBuildingEl.querySelector(".sub-user-name"); const uid = parseInt(subUserNameEl.getAttribute("data-user-id")); const userName = subUserNameEl.textContent.trim(); const userUrl = `https://space.bilibili.com/${uid}`; const subContent = inTheBuildingEl.querySelector(".reply-content").textContent.trim(); const subUserInfoEl = inTheBuildingEl.querySelector(".sub-user-info"); const iEl = subUserInfoEl.querySelector('i'); const level = getOldUserLevel(iEl); const replyContentContainerEl = inTheBuildingEl.querySelector('span.reply-content-container'); replyContentContainerEl.style.display = 'block'; replies.push({ name: userName, userUrl, uid, level, content: subContent, el: inTheBuildingEl, insertionPositionEl: subUserInfoEl, explicitSubjectEl: inTheBuildingEl }); } } return commentsData; }; const startShieldingComments = async () => { if (videoPlayModel.isVideoPlayPage() && localMKData.isDelBottomComment()) { return } let list; const href = window.location.href; if (localMKData.isDiscardOldCommentAreas()) { list = await getCommentSectionList(); } else if (href.includes("https://space.bilibili.com/") || topicDetail.isTopicDetailPage(href)) { list = await getOldCommentSectionList(); } else { list = await getCommentSectionList(); } comments_shielding.shieldingComments(list); }; var commentSectionModel = { startShieldingComments }; const getVideDataList = async (isWeekly = false) => { const css = isWeekly ? ".video-list>.video-card" : ".card-list>.video-card"; const elList = await elUtil.findElementsUntilFound(css); const list = []; for (let el of elList) { const videoCardInfoEl = el.querySelector(".video-card__info"); const title = videoCardInfoEl.querySelector(".video-name").title.trim(); const name = videoCardInfoEl.querySelector(".up-name__text").title; const videoUrl = el.querySelector('.video-card__content>a')?.href || null; const bv = elUtil.getUrlBV(videoUrl); let nPlayCount = el.querySelector('.play-text').textContent.trim(); nPlayCount = sFormatUtil.toPlayCountOrBulletChat(nPlayCount); let nBulletChat = el.querySelector('.like-text').textContent.trim(); nBulletChat = sFormatUtil.toPlayCountOrBulletChat(nBulletChat); list.push({ el, title, name, videoUrl, bv, nPlayCount, nBulletChat, insertionPositionEl: videoCardInfoEl.querySelector("div"), explicitSubjectEl: videoCardInfoEl }); } return list; }; const startShieldingVideoList = async (isWeekly = false) => { const list = await getVideDataList(isWeekly); for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue; } eventEmitter.send('添加热门视频屏蔽按钮', {data: videoData, maskingFunc: startShieldingVideoList}); } }; var popularAll = { startShieldingVideoList }; const isDynamicPage = (url) => { return url.search("space.bilibili.com/\\d+/dynamic") !== -1; }; const getDataList = async () => { const elList = await elUtil.findElementsUntilFound(".bili-dyn-list__items>.bili-dyn-list__item"); const list = []; for (let el of elList) { const videoCardEl = el.querySelector(".bili-dyn-card-video__title"); const name = el.querySelector(".bili-dyn-title").textContent.trim(); const tagEl = el.querySelector(".bili-dyn-topic__text"); const data = {el, name}; if (tagEl !== null) { data.tag = tagEl.textContent.trim(); } data.judgmentVideo = videoCardEl !== null; if (data.judgmentVideo) { data.title = videoCardEl.textContent.trim(); } else { const contentTitleEL = el.querySelector(".dyn-card-opus>.dyn-card-opus__title"); const contentTitle = contentTitleEL === null ? "" : contentTitleEL.textContent.trim(); const contentElBody = el.querySelector(".bili-rich-text").textContent.trim(); data.content = contentTitle + contentElBody; } list.push(data); } return list; }; const startShieldingDynamicContent = async () => { const personalHomepage = await space.isPersonalHomepage(); if (personalHomepage) return; const list = await getDataList(); for (let dynamicContent of list) { shielding.shieldingDynamicDecorated(dynamicContent); } }; const startThrottleShieldingDynamicContent = defUtil.throttle(startShieldingDynamicContent, 2000); var dynamic = { isDynamicPage, startThrottleShieldingDynamicContent }; const isLiveSection = (url) => { return url.includes("live.bilibili.com/p/eden/area-tags") }; const getRoomCardDataList = async () => { const elList = await elUtil.findElementsUntilFound("#room-card-list>div"); const list = []; for (let el of elList) { const liveUrl = el.querySelector("#card").href; const name = el.querySelector(".Item_nickName_KO2QE").textContent.trim(); const title = el.querySelector(".Item_roomTitle_ax3eD").textContent.trim(); const partition = el.querySelector(".Item_area-name_PXDG4")?.textContent.trim() || null; const popularity = el.querySelector(".Item_onlineCount_FmOW6").textContent.trim(); list.push({liveUrl, name, title, partition, popularity, el}); } return list; }; const startShieldingLiveRoom$1 = async () => { const liveList = await getRoomCardDataList(); for (let liveData of liveList) { live_shielding.shieldingLiveRoomDecorated(liveData); } }; var liveSectionModel = { isLiveSection, startShieldingLiveRoom: startShieldingLiveRoom$1 }; const isLiveHomePage = (url) => { return url.includes("https://live.bilibili.com/?spm_id_from=333.1007.0.0") || url === "https://live.bilibili.com/" }; const getTopLiveRoomDataList = async () => { const verification = await elUtil.findElementUntilFound(".v-top>.aside-item .t-left.aside-item-tips.p-absolute.w-100.border-box"); if (verification.textContent.trim() === "--") { return await getTopLiveRoomDataList(); } const elList = await elUtil.findElementsUntilFound(".v-top>.aside-item", {interval: 2000}); const list = []; for (let el of elList) { const classList = el.classList; const active = classList.contains("active"); const title = el.getAttribute("title"); const {up_id: uid, room_id} = JSON.parse(el.getAttribute("data-report")); const liveUrl = `https://live.bilibili.com/${room_id}`; list.push({title, uid, active, liveUrl, el}); } return list; }; const getLiveRoomDataList = async () => { const elList = await elUtil.findElementsUntilFound(".room-card-wrapper.p-relative.dp-i-block"); const list = []; for (let el of elList) { const cardEl = el.querySelector(".room-card-ctnr.p-relative.w-100"); const cardData = JSON.parse(cardEl.getAttribute("data-bl-report-click") || ""); const {up_id: uid, room_id} = cardData.msg; const liveUrl = `https://live.bilibili.com/${room_id}`; const name = el.querySelector(".room-anchor>span").textContent.trim(); const title = el.querySelector(".room-title.card-text").textContent.trim(); const partition = el.querySelector(".area-name").textContent.trim(); const popularity = el.querySelector(".room-anchor .v-middle").textContent.trim(); list.push({name, title, partition, popularity, liveUrl, uid, el}); } return list; }; const startShieldingLiveRoom = async () => { const list = await getLiveRoomDataList(); for (let liveData of list) { live_shielding.shieldingLiveRoomDecorated(liveData); } }; const startShieldingTopLiveRoom = async () => { const list = await getTopLiveRoomDataList(); for (let liveData of list) { live_shielding.shieldingLiveRoomDecorated(liveData); } }; var liveHome = { isLiveHomePage, startShieldingLiveRoom, startShieldingTopLiveRoom }; const isPartition = (url = window.location.href) => { return url.includes('www.bilibili.com/v/'); }; const isNewPartition = (url = window.location.href) => { return url.includes('www.bilibili.com/c/') }; const getHotVideoDayList = async () => { const elList = await elUtil.findElementsUntilFound('.bili-rank-list-video__item'); const list = []; for (let el of elList) { let videoUrlEl = el.querySelector('a.rank-video-card'); const titleEl = el.querySelector('.rank-video-card__info--tit'); const videoUrl = videoUrlEl.href; const title = titleEl.textContent.trim(); const bv = elUtil.getUrlBV(videoUrl); list.push({ title, videoUrl, bv, el }); } return list }; const getVVideoDataList = async () => { const elList = await elUtil.findElementsUntilFound('.bili-video-card'); const list = []; const oneTitleEl = elList[0].querySelector('.bili-video-card__info--tit>a'); if (oneTitleEl === null) { await defUtil.wait(); return await getVVideoDataList() } for (let el of elList) { const titleEl = el.querySelector('.bili-video-card__info--tit>a'); if (titleEl === null) { continue } const userEl = el.querySelector('a.bili-video-card__info--owner'); const playAndDmu = el.querySelectorAll('.bili-video-card__stats--item>span'); let nDuration = el.querySelector('.bili-video-card__stats__duration')?.textContent.trim(); let nPlayCount = playAndDmu[0]?.textContent.trim(); nPlayCount = sFormatUtil.toPlayCountOrBulletChat(nPlayCount); let nBulletChat = playAndDmu[1]?.textContent.trim(); nBulletChat = sFormatUtil.toPlayCountOrBulletChat(nBulletChat); nDuration = sFormatUtil.toPlayCountOrBulletChat(nDuration); const title = titleEl.textContent.trim(); const videoUrl = titleEl.href; const userUrl = userEl.href; const name = userEl .querySelector('.bili-video-card__info--author') ?.textContent.trim() || null; const uid = elUtil.getUrlUID(userUrl); const bv = elUtil.getUrlBV(videoUrl); list.push({ name, title, uid, bv, userUrl, videoUrl, el, nPlayCount, nBulletChat, nDuration, explicitSubjectEl: el.querySelector('.bili-video-card__info'), insertionPositionEl: el.querySelector('.bili-video-card__info--bottom') }); } return list }; const getCVideoDataList = async () => { const elList = await elUtil.findElementsUntilFound('.bili-video-card'); const list = []; for (let el of elList) { const titleEl = el.querySelector('.bili-video-card__title'); const title = titleEl.textContent.trim(); const videoUrl = titleEl.querySelector('a').href; const bv = elUtil.getUrlBV(videoUrl); const userEl = el.querySelector('.bili-video-card__author'); const userUrl = userEl.href; const uid = elUtil.getUrlUID(userUrl); const name = userEl.querySelector('[title]').textContent.trim().split('·')[0].trim(); const statEls = el.querySelectorAll('.bili-cover-card__stats span'); const nPlayCount = sFormatUtil.toPlayCountOrBulletChat(statEls[0].textContent.trim()); const nBulletChat = sFormatUtil.toPlayCountOrBulletChat(statEls[1].textContent.trim()); const nDuration = sFormatUtil.timeStringToSeconds(statEls[2].textContent.trim()); const insertionPositionEl = el.querySelector('.bili-video-card__subtitle'); const explicitSubjectEl = el.querySelector('.bili-video-card__details'); list.push({ title, userUrl, uid, name, videoUrl, bv, nPlayCount, nBulletChat, nDuration, el, insertionPositionEl, explicitSubjectEl }); } return list }; const shieldingVideoList = async () => { let list; if (isPartition()) { list = await getVVideoDataList(); } else { list = await getCVideoDataList(); } for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue } eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: shieldingVideoList}); } }; const startShieldingHotVideoDayList = async () => { let list = await getHotVideoDayList(); for (let videoData of list) { video_shielding.shieldingVideoDecorated(videoData); } }; const startIntervalShieldingVideoList = () => { setInterval(async () => { await shieldingVideoList(); for (let el of document.querySelectorAll('.feed-card:empty')) { el?.remove(); console.log('已移除页面空白视频选项元素'); } }, 1500); }; var partition = { isPartition, isNewPartition, startIntervalShieldingVideoList, startShieldingHotVideoDayList }; const bOnlyTheHomepageIsBlocked$1 = localMKData.getBOnlyTheHomepageIsBlocked(); const observeNetwork = (url, windowUrl, winTitle, initiatorType) => { if (!url.includes('api')) { return; } if (bOnlyTheHomepageIsBlocked$1) { if (!bilibiliHome.isHome(windowUrl, winTitle)) { return; } } if (url.startsWith("https://api.bilibili.com/x/web-interface/wbi/index/top/feed/rcmd?web_location=")) { if (globalValue.compatibleBEWLYBEWLY) { return; } bilibiliHome.startDebounceShieldingChangeVideoList(); bilibiliHome.startDebounceShieldingHomeVideoList(); console.log("检测到首页加载了换一换视频列表和其下面的视频列表"); return; } if (url.startsWith("https://api.bilibili.com/x/v2/reply/wbi/main?oid=")) { console.log("检测到评论区楼主评论加载了"); commentSectionModel.startShieldingComments(); return; } if (url.startsWith("https://api.bilibili.com/x/v2/reply/reply?oid=")) { console.log("检测到评论区楼主层中的子层评论列表加载了"); commentSectionModel.startShieldingComments(); } if (url.startsWith("https://api.bilibili.com/x/web-interface/popular?ps=")) { popularAll.startShieldingVideoList(); } if (url.startsWith("https://api.bilibili.com/x/web-interface/popular/series/one?number=")) { popularAll.startShieldingVideoList(true); } if (url.startsWith("https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?offset=")) { console.log("检测到用户动态加载了"); dynamic.startThrottleShieldingDynamicContent(); } if (url.startsWith("https://api.live.bilibili.com/xlive/web-interface/v1/second/getList?platform=web&parent_area_id=")) { console.log("检测到直播间加载了分区下的房间列表"); liveSectionModel.startShieldingLiveRoom(); } if (url.startsWith("https://api.live.bilibili.com/xlive/web-interface/v1/index/getList?platform=web")) { console.log("检测到直播间加载了推荐房间列表"); liveHome.startShieldingLiveRoom(); } if (url.startsWith('https://api.bilibili.com/x/web-interface/ranking/region?day=')) { console.log("检测到专区热门排行榜加载了"); partition.startShieldingHotVideoDayList(); } }; var observeNetwork$1 = { observeNetwork }; const shielding_user_vue = { template: ` <div> <el-dropdown v-if="shieldingModelShow" @command="dropdownEvent"> <el-button round> 屏蔽操作<i class="el-icon-arrow-down el-icon--right"></i> </el-button> <el-dropdown-menu v-slot="dropdown"> <el-dropdown-item command="屏蔽uid" v-if="shieldingUseUIDrButShow">屏蔽(uid) </el-dropdown-item> <el-dropdown-item command="移除屏蔽uid" v-if="removedShieldingUIDrButShow">移除屏蔽(uid) </el-dropdown-item> <el-dropdown-item command="选择用户屏蔽" v-if="selectUserBlockingButShow">选择用户屏蔽</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div>`, data() { return { shieldingModelShow: true, shieldingUseUIDrButShow: false, removedShieldingUIDrButShow: false, selectUserBlockingButShow: false, uid: -1 } }, methods: { async dropdownEvent(item) { if (item === '屏蔽uid') { const {name, uid} = await space.getUserInfo(); this.$confirm(`是否屏蔽当前用户【${name}】uid=【${uid}】`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { const {status, res} = ruleUtil.addRulePreciseUid(uid); this.$alert(res); if (status) { this.shieldingUseUIDrButShow = false; this.removedShieldingUIDrButShow = true; } }); return } if (item === '移除屏蔽uid') { const {uid} = await space.getUserInfo(); ruleUtil.delRUlePreciseUid(uid); return } if (item === '选择用户屏蔽') { await videoPlayModel.selectUserBlocking(); return } this.$message('未知选项'); } }, async created() { if (videoPlayModel.isVideoPlayPage()) { this.selectUserBlockingButShow = true; } if (space.isSpacePage()) { this.urlUID = elUtil.getUrlUID(window.location.href); if (ruleKeyListData$1.getPreciseUidArr().includes(this.urlUID)) { this.shieldingModelShow = true; this.removedShieldingUIDrButShow = true; await this.$alert('当前用户为已标记uid黑名单', '提示'); return; } if (await space.isPersonalHomepage()) { this.shieldingModelShow = false; return; } this.shieldingModelShow = true; this.shieldingUseUIDrButShow = true; } } }; const addLayout = () => { const div = document.createElement('div'); const divStyle = div.style; divStyle.position = 'fixed'; divStyle.zIndex = '9000'; divStyle.right = "0"; divStyle.top = '13%'; divStyle.transition = 'transform 0.5s'; if (!localMKData.isFirstFullDisplay()) { divStyle.transform = 'translateX(80%)'; } else { if (localMKData.isHalfHiddenIntervalAfterInitialDisplay()) { setTimeout(() => { divStyle.transform = 'translateX(80%)'; eventEmitter.send('el-msg', '自动隐藏外部主面板显隐按钮'); }, 8000); } } const vueDiv = document.createElement('div'); div.appendChild(vueDiv); document.body.appendChild(div); const config = { components: { shielding_user_vue, }, el: vueDiv, template: ` <div v-show="panelShow" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave"> <div> <el-button round @click="showBut">主面板</el-button> </div> <shielding_user_vue/> </div>`, data() { return { panelShow: localMKData.isShowRightTopMainButSwitch(), } }, methods: { showBut() { eventEmitter.send('主面板开关'); }, handleMouseEnter() { divStyle.transform = "translateX(0)"; }, handleMouseLeave() { divStyle.transform = 'translateX(80%)'; } }, created() { eventEmitter.on('显隐主面板开关', (bool) => { this.panelShow = bool; }); } }; new Vue(config); }; var rightFloatingLayoutVue = { addLayout }; const generalUrl=[ "popular/rank/all", "popular/rank/douga", "popular/rank/music", "popular/rank/dance", "popular/rank/game", "popular/rank/knowledge", "popular/rank/tech", "popular/rank/sports", "popular/rank/car", "popular/rank/life", "popular/rank/food", "popular/rank/animal", "popular/rank/kichiku", "popular/rank/fashion", "popular/rank/ent", "popular/rank/cinephile", "popular/rank/origin", "popular/rank/rookie" ]; const isPopularHistory = (url) => { return url.includes("popular/history") }; const isPopularAllPage = (url) => { return url.includes("www.bilibili.com/v/popular/all"); }; const isPopularWeeklyPage = (url) => { return url.includes("www.bilibili.com/v/popular/weekly"); }; const isGeneralPopularRank=(url)=>{ return generalUrl.some(itemUrl => url.includes(itemUrl)); }; const getVideoDataList$1 = async () => { const elList = await elUtil.findElementsUntilFound(".rank-list>li"); const list = []; for (let el of elList) { const title = el.querySelector(".title").textContent.trim(); const userUrl = el.querySelector(".detail>a").href; const uid = elUtil.getUrlUID(userUrl); const name = el.querySelector(".up-name").textContent.trim(); const detailStateEls = el.querySelectorAll('.detail-state>.data-box'); let nPlayCount = detailStateEls[0].textContent.trim(); nPlayCount = sFormatUtil.toPlayCountOrBulletChat(nPlayCount); let nBulletChat = detailStateEls[1].textContent.trim(); nBulletChat = sFormatUtil.toPlayCountOrBulletChat(nBulletChat); const videoUrl = el.querySelector('.img>a')?.href || null; const bv = elUtil.getUrlBV(videoUrl); list.push({ title, userUrl, uid, name, videoUrl, bv, nPlayCount, nBulletChat, nDuration: -1, el, insertionPositionEl: el.querySelector(".detail-state"), explicitSubjectEl: el.querySelector(".info") }); } return list; }; const startShieldingRankVideoList = async () => { const list = await getVideoDataList$1(); for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue; } eventEmitter.send('添加热门视频屏蔽按钮', {data: videoData, maskingFunc: startShieldingRankVideoList}); } }; var popular = { isPopularHistory, isPopularAllPage, isGeneralPopularRank, isPopularWeeklyPage, startShieldingRankVideoList, }; const isOldHistory = (url) => { return url.includes('https://www.bilibili.com/account/history') }; const getVideoDataList = async () => { const elList = await elUtil.findElementsUntilFound('#history_list>.history-record'); const list = []; for (let el of elList) { const labelEL = el.querySelector('.cover-contain>.label'); if (labelEL !== null) { const label = labelEL.textContent.trim(); console.log(`排除${label}`); continue } const titleEl = el.querySelector('.title'); const userEl = el.querySelector('.w-info>span>a'); const title = titleEl.textContent.trim(); const videoUrl = titleEl.href; const bv = elUtil.getUrlBV(videoUrl); const name = userEl.textContent.trim(); const userUrl = userEl.href; const uid = elUtil.getUrlUID(userUrl); list.push({ title, videoUrl, name, userUrl, uid, el, bv, explicitSubjectEl: el.querySelector('.r-txt'), insertionPositionEl: el.querySelector('.subtitle') }); } return list }; const startShieldingVideo = async () => { console.log('开始屏蔽旧版历史记录视频列表'); const list = await getVideoDataList(); const css = {right: "45px"}; for (let videoData of list) { if (video_shielding.shieldingVideoDecorated(videoData)) { continue; } videoData.css = css; eventEmitter.send('视频添加屏蔽按钮', {data: videoData, maskingFunc: startShieldingVideo}); } console.log('屏蔽旧版历史记录视频列表完成'); }; const intervalExecutionStartShieldingVideo = () => { setInterval(startShieldingVideo, 2000); }; var oldHistory = { isOldHistory, intervalExecutionStartShieldingVideo }; const bOnlyTheHomepageIsBlocked = localMKData.getBOnlyTheHomepageIsBlocked(); eventEmitter.on('通知屏蔽', () => { const url = window.location.href; const title = document.title; if (bOnlyTheHomepageIsBlocked) return; if (searchModel.isSearch(url)) { searchModel.startShieldingVideoList(); } if (bilibiliHome.isHome(url, title)) { if (globalValue.compatibleBEWLYBEWLY) { return; } if (globalValue.adaptationBAppCommerce) { BLBLGate.startIntervalShieldingGateVideoList(); } bilibiliHome.startDebounceShieldingHomeVideoList(); } if (videoPlayModel.isVideoPlayPage(url)) { videoPlayModel.startShieldingVideoList(); } if (collectionVideoPlayPageModel.iscCollectionVideoPlayPage(url)) { collectionVideoPlayPageModel.startShieldingVideoList(); } if (popular.isPopularAllPage(url) || popular.isPopularHistory(url)) { popularAll.startShieldingVideoList(); } if (popular.isPopularWeeklyPage(url)) { popularAll.startShieldingVideoList(true); } if (popular.isGeneralPopularRank(url)) { popular.startShieldingRankVideoList(); } if (topicDetail.isTopicDetailPage(url)) { topicDetail.startShielding(); } if (dynamic.isDynamicPage(url)) { dynamic.startThrottleShieldingDynamicContent(); } if (videoPlayWatchLater.isVideoPlayWatchLaterPage(url)) { videoPlayWatchLater.startDebounceShieldingVideoList(); } if (liveSectionModel.isLiveSection(url)) { liveSectionModel.startShieldingLiveRoom(); } if (liveHome.isLiveHomePage(url)) { liveHome.startShieldingLiveRoom(); liveHome.startShieldingTopLiveRoom(); } if (oldHistory.isOldHistory(url)) { oldHistory.intervalExecutionStartShieldingVideo(); } if (partition.isPartition(url) || partition.isNewPartition(url)) { partition.startIntervalShieldingVideoList(); } }); const replaceKeywords = (arr, actionScope, content) => { if (arr.length === 0 || !enableReplacementProcessing()) return returnTempVal; for (const v of arr) { if (!content.includes(v.findVal)) continue; if (!v.actionScopes.some(aItem => aItem === actionScope)) continue; return { state: true, content: content.replaceAll(v.findVal, v.replaceVal) } } return returnTempVal }; const replaceEmoticons = (arr, el, alt) => { if (arr.length === 0 || !enableReplacementProcessing()) return returnTempVal; for (const v of arr) { if (!v.actionScopes.some(aItem => aItem === '评论表情')) continue; if (v.findVal !== alt) continue; if (v.replaceVal === '') { el?.remove(); return {state: true, model: 'del', content: alt}; } return { state: true, model: 'subStr', content: v.replaceVal } } return returnTempVal }; eventEmitter.on('event-评论通知替换关键词', (commentsData) => { const {contentsEl, name, uid} = commentsData; if (!contentsEl) return; const spanEls = contentsEl.querySelectorAll('span'); const imgEls = contentsEl.querySelectorAll('img'); const aEls = contentsEl.querySelectorAll('a'); const substituteWordsArr = getSubstituteWordsArr(); if (isClearCommentEmoticons()) { for (let imgEl of imgEls) { imgEl?.remove(); eventEmitter.send('打印信息', `已清除${name}的评论中的表情`); } } else { for (let imgEl of imgEls) { if (imgEl.getAttribute('replace') !== null) continue; const alt = imgEl.getAttribute('alt'); imgEl.setAttribute('replace', ''); if (alt === null) continue; imgEl.setAttribute('title', alt); const {state, model, content} = replaceEmoticons(substituteWordsArr, imgEl, alt); if (!state) continue; if (model === 'del') { eventEmitter.send('打印信息', `已清除用户${name}的评论中的表情`); continue; } if (model === 'subStr') { imgEl.outerHTML = `<span replace>${content}</span>`; eventEmitter.send('打印信息', `已替换用户${name}的评论中的表情:`); } } } if (isReplaceCommentSearchTerms()) { for (let aEl of aEls) { const text = aEl.textContent; aEl.outerHTML = `<span replace>${text}</span>`; eventEmitter.send('打印信息', `已替换用户${name}的评论中的搜索跳转关键词:`); } } for (let spanEl of spanEls) { if (spanEl.getAttribute('replace') !== null) continue; const elContent = spanEl.textContent; const {state, content} = replaceKeywords(substituteWordsArr, '评论内容', elContent); if (!state) continue; spanEl.textContent = content; spanEl.setAttribute('replace', ''); eventEmitter.send('打印信息', `已替换用户${name}的评论内容:原\n${elContent}现\n${content}`); } }); window.addEventListener('load', () => { console.log('页面加载完成'); rightFloatingLayoutVue.addLayout(); router.staticRoute(document.title, window.location.href); watch.addEventListenerUrlChange((newUrl, oldUrl, title) => { router.dynamicRouting(title, newUrl); }); }); watch.addEventListenerNetwork((url, windowUrl, winTitle, initiatorType) => { observeNetwork$1.observeNetwork(url, windowUrl, winTitle, initiatorType); }); document.addEventListener('keydown', function (event) { if (event.key === "`") { eventEmitter.send('主面板开关'); } }); })(Vue, Dexie);