// ==UserScript== // @name youtube内容屏蔽器 // @namespace http://tampermonkey.net/ // @license GPL-3.0 // @version 1.0 // @author byhgz // @description 对油管站内内容屏蔽处理 // @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 // @match *://www.youtube.com/* // @exclude http://localhost:3002/ // @require data:text/plain;base64,d2luZG93LnRlc3RUcnVzdGVkID0gZnVuY3Rpb24oKSB7CmlmICh0eXBlb2Ygd2luZG93ICE9ICJ1bmRlZmluZWQiICYmCiAgICgndHJ1c3RlZFR5cGVzJyBpbiB3aW5kb3cpICYmCiAgICgnY3JlYXRlUG9saWN5JyBpbiB3aW5kb3cudHJ1c3RlZFR5cGVzKSAmJgogICAodHlwZW9mIHdpbmRvdy50cnVzdGVkVHlwZXMuY3JlYXRlUG9saWN5ID09ICJmdW5jdGlvbiIpKSB7Cgl3aW5kb3cudHJ1c3RlZFR5cGVzLmNyZWF0ZVBvbGljeSgnZGVmYXVsdCcsIHtjcmVhdGVTY3JpcHRVUkw6IHMgPT4gcywgY3JlYXRlU2NyaXB0OiBzID0+IHMsIGNyZWF0ZUhUTUw6IHMgPT4gc30pCn0gZWxzZSB7CglzZXRUaW1lb3V0KHdpbmRvdy50ZXN0VHJ1c3RlZCwgMTAwMCk7Cn0KfQp3aW5kb3cudGVzdFRydXN0ZWQoKTs= // @require https://unpkg.com/vue@2.7.16/dist/vue.min.js // @require https://unpkg.com/element-ui@2.15.14/lib/index.js // ==/UserScript== "use strict"; (function (Vue) { 'use strict'; class EventEmitter { #regularEvents = { events: {}, futures: {}, parametersDebounce: {}, preHandles: {} } #callbackEvents = { events: {}, callbackInterval: 1500 } #handlePendingEvents(eventName, callback) { const futureEvents = this.#regularEvents.futures; if (futureEvents[eventName]) { for (const eventData of futureEvents[eventName]) { const preHandleData = this.#executePreHandle(eventName, eventData); callback.apply(null, preHandleData); } delete futureEvents[eventName]; } } on(eventName, callback, overrideEvents = false) { const events = this.#regularEvents.events; if (events[eventName]) { if (overrideEvents) { events[eventName] = callback; this.#handlePendingEvents(eventName, callback); } return this; } events[eventName] = callback; this.#handlePendingEvents(eventName, callback); return this; } onPreHandle(eventName, callback) { const preHandles = this.#regularEvents.preHandles; preHandles[eventName] = callback; return this; } #executePreHandle(eventName, data) { const preHandles = this.#regularEvents.preHandles; const callback = preHandles[eventName]; if (callback) { return callback.apply(null, data); } return data; } 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) { const preHandleData = this.#executePreHandle(eventName, data); event.apply(null, preHandleData); return this; } const futures = ordinaryEvents.futures; if (futures[eventName]) { futures[eventName].push(data); return this; } futures[eventName] = []; futures[eventName].push(data); return this; } sendDebounce(eventName, ...data) { const parametersDebounce = this.#regularEvents.parametersDebounce; let timeOutConfig = parametersDebounce[eventName]; if (timeOutConfig) { clearTimeout(timeOutConfig.timeOut); timeOutConfig.timeOut = null; } else { timeOutConfig = parametersDebounce[eventName] = {wait: 1500, timeOut: null}; } timeOutConfig.timeOut = setTimeout(() => { this.send(eventName, ...data); }, timeOutConfig.wait); return this; } setDebounceWaitTime(eventName, wait) { const timeOutConfig = this.#regularEvents.parametersDebounce[eventName]; if (timeOutConfig) { timeOutConfig.wait = wait; } else { this.#regularEvents.parametersDebounce[eventName] = { wait: wait, timeOut: null }; } return this; } emit(eventName, ...data) { const callback = this.#regularEvents.events[eventName]; if (callback) { callback.apply(null, data); } return this; } 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(); var ruleKeyListDataJson = [ { "name": "用户名", "key": "username", "pattern": "模糊" }, { "name": "用户名", "key": "username_precise", "pattern": "精确" }, { "name": "用户名", "key": "username_regex", "pattern": "正则" }, { "name": "用户id", "key": "userId_precise", "pattern": "精确" }, { "name": "频道id", "key": "channelId_precise", "pattern": "精确" }, { "name": "标题", "key": "title", "pattern": "模糊" }, { "name": "标题", "key": "title_regex", "pattern": "正则" }, { "name": "视频id", "key": "videoId_precise", "pattern": "精确" }, { "name": "评论", "key": "comment", "pattern": "模糊" }, { "name": "评论", "key": "comment_regex", "pattern": "正则" }, { "name": "用户Id关联用户名", "key": "userId_username", "pattern": "关联" }, { "name": "用户Id关联频道Id", "key": "userId_channelId", "pattern": "关联" }, { "name": "用户名关联频道Id", "key": "username_channelId", "pattern": "关联" } ]; const getSelectOptions = () => { const options = [ { value: '模糊匹配', label: '模糊匹配', children: [] }, { value: '正则匹配', label: '正则匹配', children: [] }, { value: '精确匹配', label: '精确匹配', children: [] }, { value: '关联匹配', label: '关联匹配', children: [] } ]; for (const {name, key, pattern} of ruleKeyListDataJson) { switch (pattern) { case '模糊': options[0].children.push({value: key, label: name, pattern}); break; case '正则': options[1].children.push({value: key, label: name, pattern}); break; case '精确': options[2].children.push({value: key, label: name, pattern}); break; case '关联': options[3].children.push({value: key, label: name, pattern}); break; } } return options }; var ruleKeyListData = { getSelectOptions }; const verificationInputValue = (ruleValue) => { if (ruleValue === null) return {status: false, res: '内容不能为空'}; ruleValue.trim(); if (ruleValue === '') { return {status: false, res: '内容不能为空'}; } return {status: true, res: ruleValue}; }; const addRule = (ruleValue, type) => { const verificationRes = verificationInputValue(ruleValue); if (!verificationRes.status) { return verificationRes } const arr = GM_getValue(type, []); if (arr.includes(verificationRes.res)) { return {status: false, res: '已存在此内容'}; } arr.push(verificationRes.res); GM_setValue(type, arr); return {status: true, res: '添加成功'}; }; const batchAddRule = (ruleValues, type) => { const successList = []; const failList = []; const arr = GM_getValue(type, []); for (const v of ruleValues) { if (arr.includes(v)) { failList.push(v); continue; } arr.push(v); successList.push(v); } if (successList.length > 0) { GM_setValue(type, arr); } return { successList, failList } }; const findRuleItemValue = (type, value) => { return GM_getValue(type, []).find(item => item === value) || null }; const delRule = (type, value) => { const verificationRes = verificationInputValue(value); if (!verificationRes.status) { return verificationRes } const {res} = verificationRes; const arr = GM_getValue(type, []); const indexOf = arr.indexOf(res); if (indexOf === -1) { return {status: false, res: '不存在此内容'}; } arr.splice(indexOf, 1); GM_setValue(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.emit('event:刷新规则信息', false); }; const addRelationRule = (type, fragment) => { const fragmentsSplit = fragment.split('|'); if (fragmentsSplit.length !== 2 || fragmentsSplit.some(item => item.trim() === '')) { return {status: false, msg: '非法的关联规则,只要求一个|,或内容不可为空,请检查输入'} } const gmData = GM_getValue(type, []); if (gmData.length === 0) { gmData.push(fragment); GM_setValue(type, gmData); return {status: true, msg: '添加成功'} } const [fragmentsOneV, fragmentsTwoV] = fragmentsSplit; for (const item of gmData) { const [itemOneV, itemTwoV] = item.split('|'); if ((itemOneV === fragmentsOneV && itemTwoV === fragmentsTwoV) || (itemTwoV === fragmentsOneV && itemOneV === fragmentsTwoV) ) { return {status: false, msg: '已存在此关联规则'} } } gmData.push(fragment); GM_setValue(type, gmData); return {status: true, msg: '添加成功'} }; const batchAddRelationRule = (type, fragments) => { const successList = []; const failList = []; const gmData = GM_getValue(type, []); for (const fragment of fragments) { const fragmentsSplit = fragment.split('|'); if (fragmentsSplit.length !== 2 || fragmentsSplit.some(item => item.trim() === '')) { failList.push({fragment, msg: '非法的关联规则,只要求一个|,或内容不可为空,请检查输入'}); continue } if (gmData.length === 0) { gmData.push(fragment); successList.push(fragment); continue } const [fragmentsOneV, fragmentsTwoV] = fragmentsSplit; const exists = gmData.some(item => { const [itemOneV, itemTwoV] = item.split('|'); const b = item === fragment || (itemTwoV === fragmentsOneV && itemOneV === fragmentsTwoV); if (b) failList.push({fragment, msg: '已存在此关联规则'}); return b; }); if (exists) { continue } gmData.push(fragment); successList.push(fragment); } if (successList.length > 0) { GM_setValue(type, gmData); } return {successList, failList} }; var ruleUtil = { batchAddRule, addRule, addRelationRule, batchAddRelationRule, findRuleItemValue, showDelRuleInput }; var script$d = { props: { value: { type: Boolean, default: false }, ruleInfo: { type: Object, default: () => { return { key: 'ruleInfo默认key值', name: 'ruleInfo默认name值' } } } }, data: () => { return { dialogTitle: '', dialogVisible: false, inputVal: '', fragments: [], separator: ',', successAfterCloseVal: true } }, methods: { closeHandle() { this.inputVal = ''; }, addBut() { if (this.fragments.length === 0) { this.$message.warning('未有分割项,请输入'); return } for (const item of ruleKeyListDataJson) { if (item.key !== this.ruleInfo.key) continue; if (item.pattern !== '关联') continue; if (this.separator === '|') { this.$alert('关联规则的分隔符不能是|', {type: 'warning'}); return; } const {successList, failList} = ruleUtil.batchAddRelationRule(item.key, this.fragments); if (successList.length > 0) { let message = `成功项${successList.length}个:${successList.join(this.separator)}`; if (failList.length !== 0) { const failMsg = failList[0].msg; message += `失败项${failList.length}个:${failMsg}`; } this.$alert(message, '操作成功'); eventEmitter.emit('event:刷新规则信息', false); } else { this.$alert(`失败项${failList.length}个:${failList[0].msg}`, '操作失败'); } return; } const {successList, failList} = ruleUtil.batchAddRule(this.fragments, this.ruleInfo.key); this.$alert(`成功项${successList.length}个:${successList.join(this.separator)}\n 失败项${failList.length}个:${failList.join(this.separator)} `, 'tip'); if (successList.length > 0) { eventEmitter.emit('event:刷新规则信息', false); } if (successList.length > 0 && this.successAfterCloseVal) { this.dialogVisible = false; } } }, watch: { dialogVisible(val) { this.$emit('input', val); }, value(val) { this.dialogVisible = val; }, inputVal(val) { const list = []; for (let s of val.split(this.separator)) { if (s === "") continue; if (list.includes(s)) continue; s = s.trim(); list.push(s); } this.fragments = list; } } }; function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier , shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { const options = typeof script === 'function' ? script.options : script; if (template && template.render) { options.render = template.render; options.staticRenderFns = template.staticRenderFns; options._compiled = true; } return script; } const __vue_script__$d = script$d; var __vue_render__$d = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-dialog", { attrs: { "close-on-click-modal": false, "close-on-press-escape": false, title: "批量添加" + _vm.ruleInfo.name + "-" + _vm.ruleInfo.key, visible: _vm.dialogVisible, }, on: { "update:visible": function ($event) { _vm.dialogVisible = $event; }, close: _vm.closeHandle, }, scopedSlots: _vm._u([ { key: "footer", fn: function () { return [ _c("el-button", { on: { click: _vm.addBut } }, [ _vm._v("添加"), ]), ] }, proxy: true, }, ]), }, [ _c( "el-card", { attrs: { shadow: "never" } }, [ _c( "el-row", [ _c("el-col", { attrs: { span: 16 } }, [ _c("div", [_vm._v("1.分割项唯一,即重复xxx,只算1个")]), _vm._v(" "), _c("div", [ _vm._v( "2.如果是关联规则,固定格式为xxx|xxx,且两个值不能为空,如果需要分割,则不能用|分割,而是其他符号" ), ]), _vm._v(" "), _c("div", [ _vm._v( "3.关联规则为一对一关系,且不能有重复,包括顺序颠倒" ), ]), _vm._v(" "), _c("div", [_vm._v("4.空项跳过")]), ]), _vm._v(" "), _c( "el-col", { attrs: { span: 8 } }, [ _c("el-input", { staticStyle: { width: "200px" }, scopedSlots: _vm._u([ { key: "prepend", fn: function () { return [_vm._v("分隔符")] }, proxy: true, }, ]), model: { value: _vm.separator, callback: function ($$v) { _vm.separator = $$v; }, expression: "separator", }, }), _vm._v(" "), _c( "div", [ _c("el-switch", { attrs: { "active-text": "添加成功后关闭对话框" }, model: { value: _vm.successAfterCloseVal, callback: function ($$v) { _vm.successAfterCloseVal = $$v; }, expression: "successAfterCloseVal", }, }), ], 1 ), ], 1 ), ], 1 ), ], 1 ), _vm._v(" "), _c( "el-form", [ _c( "el-form-item", { directives: [ { name: "show", rawName: "v-show", value: _vm.fragments.length !== 0, expression: "fragments.length!==0", }, ], attrs: { label: "分割项" }, }, [ _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [ _vm._v("数量:\n "), _c("el-tag", [ _vm._v(_vm._s(_vm.fragments.length)), ]), ] }, proxy: true, }, ]), }, [ _vm._v(" "), _vm._l(_vm.fragments, function (v) { return _c( "el-tag", { key: v, staticStyle: { "margin-left": "5px" } }, [_vm._v(_vm._s(v))] ) }), ], 2 ), ], 1 ), _vm._v(" "), _c( "el-form-item", { attrs: { label: "输入项" } }, [ _c("el-input", { attrs: { type: "textarea" }, model: { value: _vm.inputVal, callback: function ($$v) { _vm.inputVal = $$v; }, expression: "inputVal", }, }), ], 1 ), ], 1 ), ], 1 ), ], 1 ) }; var __vue_staticRenderFns__$d = []; __vue_render__$d._withStripped = true; const __vue_inject_styles__$d = undefined; const __vue_component__$d = normalizeComponent( { render: __vue_render__$d, staticRenderFns: __vue_staticRenderFns__$d }, __vue_inject_styles__$d, __vue_script__$d); var script$c = { data() { return { show: false, ruleType: "", ruleName: "", oldVal: '', newVal: '' } }, methods: { okBut() { const tempOldVal = this.oldVal.trim(); const 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 (!ruleUtil.findRuleItemValue(tempRuleType, tempOldVal)) { this.$alert("要修改的值不存在"); return; } if (ruleUtil.findRuleItemValue(tempRuleType, tempNewVal)) { this.$alert("新值已存在"); return; } const ruleArr = GM_getValue(tempRuleType, []); const indexOf = ruleArr.indexOf(tempOldVal); ruleArr[indexOf] = tempNewVal; GM_setValue(tempRuleType, ruleArr); this.$alert(`已将旧值【${tempOldVal}】修改成【${tempNewVal}】`); this.show = false; } }, watch: { show(newVal) { if (newVal === false) this.oldVal = this.newVal = ''; } }, created() { eventEmitter.on('修改规则对话框', ({key, name}) => { this.show = true; this.ruleType = key; this.ruleName = name; }); } }; const __vue_script__$c = script$c; var __vue_render__$c = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-dialog", { attrs: { "close-on-click-modal": false, modal: false, visible: _vm.show, title: "修改单项规则值", width: "30%", }, on: { "update:visible": function ($event) { _vm.show = $event; }, }, scopedSlots: _vm._u([ { key: "footer", fn: function () { return [ _c( "el-button", { on: { click: function ($event) { _vm.show = false; }, }, }, [_vm._v("取消")] ), _vm._v(" "), _c("el-button", { on: { click: _vm.okBut } }, [ _vm._v("确定"), ]), ] }, proxy: true, }, ]), }, [ _vm._v( "\n " + _vm._s(_vm.ruleName) + "-" + _vm._s(_vm.ruleType) + "\n " ), _c( "el-form", [ _c( "el-form-item", { attrs: { label: "要修改的值" } }, [ _c("el-input", { attrs: { clearable: "", type: "text" }, model: { value: _vm.oldVal, callback: function ($$v) { _vm.oldVal = $$v; }, expression: "oldVal", }, }), ], 1 ), _vm._v(" "), _c( "el-form-item", { attrs: { label: "修改后的值" } }, [ _c("el-input", { attrs: { clearable: "" }, model: { value: _vm.newVal, callback: function ($$v) { _vm.newVal = $$v; }, expression: "newVal", }, }), ], 1 ), ], 1 ), ], 1 ), ], 1 ) }; var __vue_staticRenderFns__$c = []; __vue_render__$c._withStripped = true; const __vue_inject_styles__$c = undefined; const __vue_component__$c = normalizeComponent( { render: __vue_render__$c, staticRenderFns: __vue_staticRenderFns__$c }, __vue_inject_styles__$c, __vue_script__$c); var script$b = { data() { return { dialogVisible: false, typeMap: {}, showTags: [], } }, methods: { updateShowRuleTags() { this.showTags = GM_getValue(this.typeMap.key, []); }, handleTagClose(tag, index) { if (tag === '') return; this.$confirm(`确定要删除 ${tag} 吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.showTags.splice(index, 1); GM_setValue(this.typeMap.key, this.showTags); this.$message.success(`已移除 ${tag}`); eventEmitter.emit('event:刷新规则信息', false); }); }, closedHandle() { console.log('closed'); this.typeMap = {}; this.showTags.splice(0, this.showTags.length); } }, created() { eventEmitter.on('event-lookRuleDialog', (typeMap) => { this.typeMap = typeMap; this.dialogVisible = true; this.updateShowRuleTags(); }); } }; const __vue_script__$b = script$b; var __vue_render__$b = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-dialog", { attrs: { "close-on-click-modal": false, "close-on-press-escape": false, fullscreen: false, modal: false, visible: _vm.dialogVisible, title: "查看规则内容", }, on: { closed: _vm.closedHandle, "update:visible": function ($event) { _vm.dialogVisible = $event; }, }, }, [ _c( "el-card", { scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("规则信息")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("el-tag", [ _vm._v(_vm._s(_vm.typeMap.name + "|" + _vm.typeMap.key)), ]), _vm._v(" "), _c("el-tag", [_vm._v(_vm._s(_vm.showTags.length) + "个")]), ], 1 ), _vm._v(" "), _c( "el-card", _vm._l(_vm.showTags, function (item, index) { return _c( "el-tag", { key: index, attrs: { closable: "" }, on: { close: function ($event) { return _vm.handleTagClose(item, index) }, }, }, [_vm._v("\n " + _vm._s(item) + "\n ")] ) }), 1 ), ], 1 ), ], 1 ) }; var __vue_staticRenderFns__$b = []; __vue_render__$b._withStripped = true; const __vue_inject_styles__$b = undefined; const __vue_component__$b = normalizeComponent( { render: __vue_render__$b, staticRenderFns: __vue_staticRenderFns__$b }, __vue_inject_styles__$b, __vue_script__$b); const ruleCountList = []; for (const {key, name, pattern} of ruleKeyListDataJson) { ruleCountList.push({ name: pattern + name, key, len: GM_getValue(key, []).length, }); } var script$a = { data() { return { ruleCountList } }, methods: { refreshInfo(isTip = true) { for (const x of this.ruleCountList) { x.len = GM_getValue(x.key, []).length; } if (!isTip) return; this.$notify({title: 'tip', message: '刷新规则信息成功', type: 'success'}); }, refreshInfoBut() { this.refreshInfo(); }, lookRuleBut(item) { if (item.len === 0) { this.$message.warning('当前规则信息为空'); return; } eventEmitter.send('event-lookRuleDialog', item); } }, created() { this.refreshInfo(false); eventEmitter.on('event:刷新规则信息', (isTip = true) => { this.refreshInfo(isTip); }); } }; const __vue_script__$a = script$a; var __vue_render__$a = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [ _c("div", { staticClass: "el-horizontal-outside" }, [ _c("div", [_vm._v("规则信息")]), _vm._v(" "), _c( "div", [ _c("el-button", { on: { click: _vm.refreshInfoBut } }, [ _vm._v("刷新信息"), ]), ], 1 ), ]), ] }, proxy: true, }, ]), }, [ _vm._v(" "), _c( "div", { staticStyle: { display: "flex", "flex-wrap": "wrap", "row-gap": "2px", "justify-content": "flex-start", }, }, _vm._l(_vm.ruleCountList, function (item) { return _c( "el-button", { key: item.name, attrs: { size: "small" }, on: { click: function ($event) { return _vm.lookRuleBut(item) }, }, }, [ _vm._v("\n " + _vm._s(item.name) + "\n "), _c( "el-tag", { attrs: { effect: item.len > 0 ? "dark" : "light", size: "mini", }, }, [_vm._v("\n " + _vm._s(item.len) + "\n ")] ), ], 1 ) }), 1 ), ] ), ], 1 ) }; var __vue_staticRenderFns__$a = []; __vue_render__$a._withStripped = true; const __vue_inject_styles__$a = undefined; const __vue_component__$a = normalizeComponent( { render: __vue_render__$a, staticRenderFns: __vue_staticRenderFns__$a }, __vue_inject_styles__$a, __vue_script__$a); const ruleInfoArr = ruleKeyListDataJson; var script$9 = { components: {AddRuleDialog: __vue_component__$d, RuleSetValueDialog: __vue_component__$c, ViewRulesRuleDialog: __vue_component__$b, RuleInformationView: __vue_component__$a}, data() { return { cascaderVal: ['精确匹配', 'userId_precise'], cascaderOptions: ruleKeyListData.getSelectOptions(), addRuleDialogVisible: false, addRuleDialogRuleInfo: {key: '', name: ''} } }, methods: { handleChangeCascader(val) { console.log(val); }, batchAddBut() { const [_, key] = this.cascaderVal; this.addRuleDialogVisible = true; this.addRuleDialogRuleInfo = { key, name: ruleInfoArr.find(item => item.key === key).name }; }, setRuleBut() { const [_, key] = this.cascaderVal; const typeMap = ruleInfoArr.find(item => item.key === key); eventEmitter.send('修改规则对话框', typeMap); }, findItemAllBut() { const [_, key] = this.cascaderVal; const typeMap = ruleInfoArr.find(item => item.key === key); eventEmitter.send('event-lookRuleDialog', typeMap); }, delBut() { const [_, key] = this.cascaderVal; ruleUtil.showDelRuleInput(key); }, clearItemRuleBut() { const key = this.cascaderVal[1]; const find = ruleInfoArr.find(item => item.key === key); this.$confirm(`是要清空${find.name}的规则内容吗?`, 'tip').then(() => { GM_deleteValue(key); this.$alert(`已清空${find.name}的规则内容`); }); }, delAllBut() { this.$confirm('确定要删除所有规则吗?').then(() => { for (const x of ruleInfoArr) { GM_deleteValue(x.key); } this.$message.success("删除全部规则成功"); eventEmitter.emit('刷新规则信息', false); }); } }, watch: {}, created() { } }; const __vue_script__$9 = script$9; var __vue_render__$9 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c("el-cascader", { staticStyle: { width: "100%" }, attrs: { options: _vm.cascaderOptions, props: { expandTrigger: "hover" }, "show-all-levels": true, filterable: "", }, on: { change: _vm.handleChangeCascader }, model: { value: _vm.cascaderVal, callback: function ($$v) { _vm.cascaderVal = $$v; }, expression: "cascaderVal", }, }), _vm._v(" "), _c("el-divider"), _vm._v(" "), _c( "el-button-group", [ _c("el-button", { on: { click: _vm.batchAddBut } }, [ _vm._v("批量添加"), ]), _vm._v(" "), _c("el-button", { on: { click: _vm.setRuleBut } }, [_vm._v("修改")]), _vm._v(" "), _c("el-button", { on: { click: _vm.findItemAllBut } }, [ _vm._v("查看项内容"), ]), _vm._v(" "), _c("el-button", { on: { click: _vm.delBut } }, [_vm._v("移除")]), ], 1 ), _vm._v(" "), _c( "el-button-group", [ _c( "el-button", { attrs: { type: "danger" }, on: { click: _vm.clearItemRuleBut } }, [_vm._v("清空项")] ), _vm._v(" "), _c( "el-button", { attrs: { type: "danger" }, on: { click: _vm.delAllBut } }, [_vm._v("全部移除")] ), ], 1 ), _vm._v(" "), _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("说明")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("div", [_vm._v("1.规则的值唯一,且不重复。")]), _vm._v(" "), _c("div", [_vm._v("2.关联规则值同上,是双向关联关系。")]), _vm._v(" "), _c("div", [_vm._v("3.关联规则分割的两方为精确匹配")]), _vm._v(" "), _c("div", [ _vm._v( "\n 4.关联规则中某种类型的值颠倒顺序,也视为同一个,等于颠倒后的结果,比如用户id关联用户名中的@ikun|爱坤等于@iKun|爱坤\n " ), ]), _vm._v(" "), _c( "div", [ _c( "el-link", { attrs: { href: "https://www.jyshare.com/front-end/854/", target: "_blank", type: "primary", }, }, [_vm._v("\n 5.正则表达式测试地址\n ")] ), ], 1 ), ] ), _vm._v(" "), _c("RuleInformationView"), _vm._v(" "), _c("AddRuleDialog", { attrs: { "rule-info": _vm.addRuleDialogRuleInfo }, model: { value: _vm.addRuleDialogVisible, callback: function ($$v) { _vm.addRuleDialogVisible = $$v; }, expression: "addRuleDialogVisible", }, }), _vm._v(" "), _c("RuleSetValueDialog"), _vm._v(" "), _c("ViewRulesRuleDialog"), ], 1 ) }; var __vue_staticRenderFns__$9 = []; __vue_render__$9._withStripped = true; const __vue_inject_styles__$9 = undefined; const __vue_component__$9 = normalizeComponent( { render: __vue_render__$9, staticRenderFns: __vue_staticRenderFns__$9 }, __vue_inject_styles__$9, __vue_script__$9); const getDrawerShortcutKeyGm = () => { return GM_getValue('get_drawer_shortcut_key_gm', '`') }; const isDelSponsoredAdsGm= () => { return GM_getValue('is_del_sponsored_ads_gm', true) }; const isDelHomeShortsItemGm= () => { return GM_getValue('is_del_home_shorts_item_gm', true) }; const isDelLiveHomeTopBannerGm = () => { return GM_getValue('is_del_live_home_top_banner_gm', true) }; const isDelVideoPageSponsoredAdsGm = () => { return GM_getValue('is_del_video_page_sponsored_ads_gm', false) }; var script$8 = { data() { return { drawerShortcutKeyVal: getDrawerShortcutKeyGm(), theKeyPressedKeyVal: '' } }, methods: { setDrawerShortcutKeyBut() { const theKeyPressedKey = this.theKeyPressedKeyVal; const drawerShortcutKey = this.drawerShortcutKeyVal; if (drawerShortcutKey === theKeyPressedKey) { this.$message('不需要重复设置'); return; } GM_setValue('drawer_shortcut_key_gm', theKeyPressedKey); this.$notify({message: '已设置打开关闭主面板快捷键', type: 'success'}); this.drawerShortcutKeyVal = theKeyPressedKey; } }, created() { eventEmitter.on('event-keydownEvent', (event) => { this.theKeyPressedKeyVal = event.key; }); } }; const __vue_script__$8 = script$8; var __vue_render__$8 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_c("span", [_vm._v("快捷键")])] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("div", [ _vm._v("1.默认情况下,按键盘tab键上的~键为展开关闭主面板"), ]), _vm._v(" "), _c( "div", [ _vm._v("2.当前展开关闭主面板快捷键:\n "), _c("el-tag", [_vm._v(_vm._s(_vm.drawerShortcutKeyVal))]), ], 1 ), _vm._v("\n 当前按下的键\n "), _c("el-tag", [_vm._v(_vm._s(_vm.theKeyPressedKeyVal))]), _vm._v(" "), _c("el-button", { on: { click: _vm.setDrawerShortcutKeyBut } }, [ _vm._v("设置打开关闭主面板快捷键"), ]), ], 1 ), ], 1 ) }; var __vue_staticRenderFns__$8 = []; __vue_render__$8._withStripped = true; const __vue_inject_styles__$8 = undefined; const __vue_component__$8 = normalizeComponent( { render: __vue_render__$8, staticRenderFns: __vue_staticRenderFns__$8 }, __vue_inject_styles__$8, __vue_script__$8); const returnTempVal = {state: false}; 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 b_url = 'https://space.bilibili.com/473239155'; const scriptCat_js_url = 'https://scriptcat.org/zh-CN/script-show-page/4389'; const github_url = 'https://github.com/hgztask/mk_youtube_content_shield'; try { unsafeWindow['mk_win'] = window; } catch (e) { console.warn('挂载脚本window环境到前端环境失败', e); } Promise.resolve(); const promiseReject = Promise.reject(); var globalValue = { group_url, b_url, scriptCat_js_url, github_url }; var script$7 = { data() { return { group_url: globalValue.group_url, scriptCat_js_url: globalValue.scriptCat_js_url, b_url: globalValue.b_url, github_url: globalValue.github_url, update_log_url: globalValue.update_log_url, activeName: ['1', '2'] } }, methods: { lookImgBut() { eventEmitter.send('显示图片对话框', {image: "https://www.mikuchase.ltd/img/qq_group_876295632.webp"}); } } }; const __vue_script__$7 = script$7; var __vue_render__$7 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-collapse", { model: { value: _vm.activeName, callback: function ($$v) { _vm.activeName = $$v; }, expression: "activeName", }, }, [ _c( "el-collapse-item", { attrs: { name: "1", title: "作者b站" } }, [ _c( "el-link", { attrs: { href: _vm.b_url, target: "_blank", type: "primary" }, }, [_vm._v("b站传送门")] ), ], 1 ), _vm._v(" "), _c( "el-collapse-item", { attrs: { name: "2", title: "反馈交流群" } }, [ _c( "el-link", { attrs: { href: _vm.group_url, target: "_blank", type: "primary", }, }, [_vm._v("====》Q群传送门《====\n ")] ), _vm._v(" "), _c( "el-tooltip", { attrs: { content: "点击查看群二维码" } }, [ _c("el-tag", { on: { click: _vm.lookImgBut } }, [ _vm._v("876295632"), ]), ], 1 ), ], 1 ), _vm._v(" "), _c( "el-collapse-item", { attrs: { name: "3", title: "脚本猫更新地址" } }, [ _vm._v("\n 目前仅在脚本猫平台上更新发布\n "), _c( "el-link", { attrs: { href: _vm.scriptCat_js_url, target: "_blank", type: "primary", }, }, [_vm._v("脚本猫更新地址")] ), ], 1 ), _vm._v(" "), _c( "el-collapse-item", { attrs: { name: "4", title: "开源地址" } }, [ _c("div", [_vm._v("本脚本源代码已开源,欢迎大家Star或提交PR")]), _vm._v(" "), _c( "el-link", { attrs: { href: _vm.github_url, target: "_blank", type: "primary", }, }, [_vm._v("github开源地址")] ), ], 1 ), ], 1 ), ], 1 ) }; var __vue_staticRenderFns__$7 = []; __vue_render__$7._withStripped = true; const __vue_inject_styles__$7 = undefined; const __vue_component__$7 = normalizeComponent( { render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 }, __vue_inject_styles__$7, __vue_script__$7); var script$6 = { 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 __vue_script__$6 = script$6; var __vue_render__$6 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-dialog", { attrs: { modal: _vm.isModal, title: _vm.title, visible: _vm.show, center: "", }, on: { "update:visible": function ($event) { _vm.show = $event; }, }, }, [ _c( "div", { staticClass: "el-vertical-center" }, [ _c("el-image", { attrs: { "preview-src-list": _vm.imgList, src: _vm.imgSrc }, }), ], 1 ), ] ), ], 1 ) }; var __vue_staticRenderFns__$6 = []; __vue_render__$6._withStripped = true; const __vue_inject_styles__$6 = undefined; const __vue_component__$6 = normalizeComponent( { render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 }, __vue_inject_styles__$6, __vue_script__$6); var script$5 = { 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; } }, created() { this.dialogIni.srcList = this.list.map(x => x.src); } }; const __vue_script__$5 = script$5; var __vue_render__$5 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_c("span", [_vm._v("零钱赞助")])] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("span", [_vm._v("1元不嫌少,10元不嫌多,感谢支持!")]), _vm._v(" "), _c("el-divider"), _vm._v(" "), _c("span", [_vm._v("生活不易,作者叹息")]), _vm._v(" "), _c("el-divider"), _vm._v(" "), _c("span", [_vm._v("用爱发电不容易,您的支持是我最大的更新动力")]), ], 1 ), _vm._v(" "), _c("el-divider"), _vm._v(" "), _c( "div", { staticClass: "el-vertical-center" }, [ _c( "el-button", { attrs: { round: "", type: "primary" }, on: { click: _vm.showDialogBut }, }, [_vm._v("打赏点猫粮")] ), ], 1 ), _vm._v(" "), _c( "el-dialog", { attrs: { title: _vm.dialogIni.title, visible: _vm.dialogIni.show, center: "", }, on: { "update:visible": function ($event) { return _vm.$set(_vm.dialogIni, "show", $event) }, }, }, [ _c( "div", { staticClass: "el-vertical-center" }, _vm._l(_vm.list, function (item) { return _c("el-image", { key: item.name, staticStyle: { height: "300px" }, attrs: { "preview-src-list": _vm.dialogIni.srcList, src: item.src, }, }) }), 1 ), ] ), ], 1 ) }; var __vue_staticRenderFns__$5 = []; __vue_render__$5._withStripped = true; const __vue_inject_styles__$5 = undefined; const __vue_component__$5 = normalizeComponent( { render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 }, __vue_inject_styles__$5, __vue_script__$5); var script$4 = { data() { return { visible: false, optionsList: [], dialogTitle: '', optionsClick: null, closeOnClickModal: true, contents: [] } }, methods: { handleClose() { this.visible = false; if (this.contents.length > 0) { this.contents = []; } }, handleOptionsClick(item) { if (this.closeOnClickModal) { return; } let tempBool; const temp = this.optionsClick(item); if (temp === undefined) { tempBool = false; } else { tempBool = temp; } this.visible = tempBool === true; } }, created() { eventEmitter.on('sheet-dialog', ({ list, optionsClick, title = '选项', closeOnClickModal = false, contents }) => { this.visible = true; this.optionsList = list; this.dialogTitle = title; this.optionsClick = optionsClick; this.closeOnClickModal = closeOnClickModal; if (contents) { this.contents = contents; } }); } }; const __vue_script__$4 = script$4; var __vue_render__$4 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-dialog", { attrs: { "close-on-click-modal": _vm.closeOnClickModal, title: _vm.dialogTitle, visible: _vm.visible, center: "", width: "30%", }, on: { close: _vm.handleClose }, }, [ _c( "div", [ _c( "el-row", [ _c( "el-col", _vm._l(_vm.contents, function (v) { return _c("div", { key: v }, [_vm._v(_vm._s(v))]) }), 0 ), _vm._v(" "), _vm._l(_vm.optionsList, function (item) { return _c( "el-col", { key: item.label }, [ _c( "el-button", { staticStyle: { width: "100%" }, attrs: { title: item.title }, on: { click: function ($event) { return _vm.handleOptionsClick(item) }, }, }, [_vm._v(_vm._s(item.label) + "\n ")] ), ], 1 ) }), ], 2 ), ], 1 ), ] ), ], 1 ) }; var __vue_staticRenderFns__$4 = []; __vue_render__$4._withStripped = true; const __vue_inject_styles__$4 = undefined; const __vue_component__$4 = normalizeComponent( { render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 }, __vue_inject_styles__$4, __vue_script__$4); const isDOMElement = (obj) => { return (obj !== null && typeof obj === 'object' && 'nodeType' in obj); }; const inProgressCache = new Map(); const validationElFun = (config, selector) => { const element = config.doc.querySelector(selector); if (element === null) return null; return config.parseShadowRoot && element.shadowRoot ? element.shadowRoot : element; }; const __privateValidationElFun = (config, selector) => { const result = config.validationElFun(config, selector); return isDOMElement(result) ? result : null; }; const findElement = async (selector, config = {}) => { const defConfig = { doc: document, interval: 1000, timeout: -1, parseShadowRoot: false, cacheInProgress: true, validationElFun }; config = {...defConfig, ...config}; const result = __privateValidationElFun(config, selector); if (result !== null) return result; const cacheKey = `findElement:${selector}`; if (config.cacheInProgress) { const cachedPromise = inProgressCache.get(cacheKey); if (cachedPromise) { return cachedPromise; } } const p = new Promise((resolve) => { let timeoutId; const IntervalId = setInterval(() => { const result = __privateValidationElFun(config, selector); if (result === null) return; resolve(result); }, config.interval); const cleanup = () => { if (IntervalId) clearInterval(IntervalId); if (timeoutId) clearTimeout(timeoutId); if (config.cacheInProgress) { inProgressCache.delete(cacheKey); } }; if (config.timeout > 0) { timeoutId = setTimeout(() => { resolve(null); cleanup(); }, config.timeout); } }); if (config.cacheInProgress) { inProgressCache.set(cacheKey, p); } return p; }; const findElementChain = (selector, config = {}) => { const paths = []; const chainObj = { find(childSelector, childConfig = {}) { if (config.allparseShadowRoot) { childConfig.parseShadowRoot = true; } childSelector.trim(); if (childSelector === '' || childSelector.search(/^\d/) !== -1) { throw new Error('非法的元素选择器'); } const separator = config.separator ?? childConfig.separator; if (separator === undefined || separator === null || separator.trim() === '') { paths.push({selector: childSelector, config: childConfig}); } else { const selectorArr = childSelector.split(separator); if (selectorArr.length === 1) { paths.push({selector: childSelector, config: childConfig}); } else { for (let s of selectorArr) { s = s.trim(); if (s === '') continue; childConfig.originalSelector = childSelector; paths.push({selector: s, config: childConfig}); } } } return this }, get() { return new Promise(async (resolve) => { let currentDoc = null; for ({selector, config} of paths) { const resolvedConfig = {...config}; if (config.doc === null || config.doc === undefined) { resolvedConfig.doc = currentDoc ?? document; } else { resolvedConfig.doc = config.doc; } const res = await findElement(selector, resolvedConfig); if (res === null) { continue; } currentDoc = res; } resolve(currentDoc); }) } }; chainObj.find(selector, config); return chainObj; }; const findElements = async (selector, config = {}) => { const defConfig = {doc: document, interval: 1000, timeout: -1, parseShadowRoot: false}; config = {...defConfig, ...config}; return new Promise((resolve) => { const i1 = setInterval(() => { const els = config.doc.querySelectorAll(selector); if (els.length > 0) { const list = []; for (const el of els) { if (config.parseShadowRoot) { const shadowRoot = el?.shadowRoot; list.push(shadowRoot ? shadowRoot : el); continue; } list.push(el); } resolve(list); clearInterval(i1); } }, config.interval); if (config.timeout > 0) { setTimeout(() => { clearInterval(i1); resolve([]); // 超时则返回 空数组 }, config.timeout); } }); }; const installStyle = (style, config) => { const defConfig = { id: '#def_gz_style', el: document.head, cover: false, ...config }; const targetEl = defConfig.el.querySelector(defConfig.id); if (defConfig.cover && targetEl) { targetEl.textContent = style; return; } const styleElement = document.createElement('style'); styleElement.textContent = style; if (defConfig.id.startsWith('#')) { styleElement.id = defConfig.id.replace(/^#/, ''); } else { styleElement.setAttribute(styleElement.id, ''); } defConfig.el.appendChild(styleElement); }; var elUtil = { findElement, findElements, findElementChain, installStyle }; class IntervalExecutor { #interval = null; #func; #config; static #intervalExecutorList = []; #statusObj = {}; #keyIntervalName = ''; constructor(func, config = {}) { const defConfig = {timeout: 2000, processTips: false, intervalName: null}; this.#config = Object.assign(defConfig, config); if (this.#config.intervalName === null) { throw new Error('请设置间隔名称'); } this.#func = func; const intervalName = this.#config.intervalName; const intervalExecutorList = IntervalExecutor.#intervalExecutorList; const index = intervalExecutorList.length + 1; this.#keyIntervalName = `${intervalName}_${index}`; this.#statusObj = {status: false, key: this.#keyIntervalName, name: this.#config.intervalName}; intervalExecutorList.push(this); } static setIntervalExecutorStatus(keyIntervalName, status) { const find = IntervalExecutor.#intervalExecutorList.find(item => item.getKeyIntervalName() === keyIntervalName); if (find === undefined) return; if (status) { find.start(); } else { find.stop(); } } getKeyIntervalName = () => { return this.#keyIntervalName; } stop(msg = null) { const i = this.#interval; if (i === null) return; clearInterval(i); this.#interval = null; const processTips = this.#config.processTips; if (msg) { console.log(msg); } if (processTips) { console.log(`stop:检测${this.#config.intervalName}间隔执行器`); } this.#statusObj.status = false; eventEmitter.send('event:update:intervalExecutorStatus', this.#statusObj); } setTimeout(timeout) { this.#config.timeout = timeout; } start() { if (this.#interval !== null) return; this.#statusObj.status = true; eventEmitter.send('event:update:intervalExecutorStatus', this.#statusObj); this.#interval = setInterval(this.#func, this.#config.timeout); const processTips = this.#config.processTips; if (processTips) { console.log(`start:检测${this.#config.intervalName}间隔执行器`); } } } 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'); } }; const parseView = (viewTxt) => { const ViewIntStr = viewTxt.replace(/[^0-9]/g, ''); if (viewTxt.endsWith('万次观看') || viewTxt.endsWith('万人正在观看')) { return parseInt(ViewIntStr) * 10000; } else { return parseInt(ViewIntStr); } }; const getCompilationUserNames = (namesStr) => { if (namesStr === null || namesStr === undefined || namesStr === '') return null; const userNameList = []; for (const name of namesStr.split('、')) { if (name.endsWith('等')) { userNameList.push(name.substring(0, name.length - 1)); } else { userNameList.push(name); } } return userNameList; }; var strUtil = { timeStringToSeconds, parseView, getCompilationUserNames }; 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 exactMatch = (ruleList, value) => { if (ruleList === null || ruleList === undefined) return false; if (!Array.isArray(ruleList)) return false return ruleList.some(item => item === value); }; const regexMatch = (ruleList, value) => { if (ruleList === null || ruleList === undefined) return null; if (!Array.isArray(ruleList)) return null const find = ruleList.find(item => { try { return value.search(item) !== -1; } catch (e) { 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.includes(item)); return find === undefined ? null : find; }; var ruleMatchingUtil = { exactMatch, regexMatch, fuzzyMatch }; const blockExactAndFuzzyMatching = (val, config) => { if (!val) { return returnTempVal } const { exactKey, exactTypeName, exactRuleArr = GM_getValue(exactKey, []) } = config; if (exactKey) { if (ruleMatchingUtil.exactMatch(exactRuleArr, val)) { return {state: true, type: exactTypeName, matching: val} } } let matching; const { fuzzyKey, fuzzyTypeName, fuzzyRuleArr = GM_getValue(fuzzyKey, []), } = config; if (fuzzyKey) { matching = ruleMatchingUtil.fuzzyMatch(fuzzyRuleArr, val); if (matching) { return {state: true, type: fuzzyTypeName, matching} } } const { regexKey, regexTypeName, regexRuleArr = GM_getValue(regexKey, []) } = config; if (regexKey) { matching = ruleMatchingUtil.regexMatch(regexRuleArr, val); if (matching) { return {state: true, type: regexTypeName, matching} } } return returnTempVal }; const blockUserName = (name) => { return blockExactAndFuzzyMatching(name, { exactKey: 'username_precise', exactTypeName: '精确用户名', fuzzyKey: 'username', fuzzyTypeName: '模糊用户名', regexKey: 'username_regex', regexTypeName: '正则用户名' }) }; const blockUserId = (id) => { if (ruleMatchingUtil.exactMatch(GM_getValue('userId_precise', []), id)) { return {state: true, type: '精确用户id', matching: id}; } return returnTempVal; }; var shielding = {blockExactAndFuzzyMatching}; const blockTitle = (title) => { return shielding.blockExactAndFuzzyMatching(title, { fuzzyKey: 'title', fuzzyTypeName: '模糊标题', regexKey: 'title_regex', regexTypeName: '正则标题' }) }; const blockVideoId = (id) => { if (ruleMatchingUtil.exactMatch(GM_getValue('videoId_precise', []), id)) { return {state: true, type: '精确视频id', matching: id}; } return returnTempVal; }; const blockChannelId = (id) => { if (ruleMatchingUtil.exactMatch(GM_getValue('channelId_precise', []), id)) { return {state: true, type: '精确频道id', matching: id}; } return returnTempVal; }; const blockRelationRule = (type, oneV, twoV) => { if (oneV === null || oneV === undefined || oneV === '' || twoV === null || twoV === undefined || twoV === '') { return returnTempVal; } const typeRuleList = GM_getValue(type, []); if (typeRuleList.length === 0) { return returnTempVal; } const item = ruleKeyListDataJson.find(item => item.key === type); for (const fragment of typeRuleList) { const fragmentsSplit = fragment.split('|'); const [fragmentOneV, fragmentsTwoV] = fragmentsSplit; const verify = oneV ?? twoV; if (verify) { if (oneV === fragmentOneV) { return {state: true, type: item.name, matching: twoV, pattern: '关联'}; } if (oneV === fragmentsTwoV) { return {state: true, type: item.name, matching: oneV, pattern: '关联'}; } } } return returnTempVal; }; const blockUserIDAssociatedWithUserName = (userId, UserName) => { return blockRelationRule('userId_userName', userId, UserName) }; const blockUserIdAssociatedWithChannelId = (userId, channelId) => { return blockRelationRule('userId_channelId', userId, channelId) }; const blockUserNameAssociatedWithChannelId = (userName, channelId) => { return blockRelationRule('userName_channelId', userName, channelId) }; eventEmitter.on('event:插入屏蔽按钮', (videoOrCommentData) => { const {insertionPositionEl, explicitSubjectEl, el} = videoOrCommentData; let but = el.querySelector('button[gz_type]'); if (but !== null) return; but = document.createElement('button'); but.setAttribute('gz_type', ''); but.textContent = '屏蔽'; but.addEventListener('click', (event) => { event.stopImmediatePropagation(); // 阻止事件冒泡和同一元素上的其他事件处理器 event.preventDefault(); // 阻止默认行为 eventEmitter.emit('event:mask_options_dialog_box', videoOrCommentData); }); insertionPositionEl.appendChild(but); if (explicitSubjectEl) { but.style.display = "none"; elEventEmitter.addEvent(explicitSubjectEl, "mouseout", () => but.style.display = "none"); elEventEmitter.addEvent(explicitSubjectEl, "mouseover", () => but.style.display = ""); } }); const shieldingVideo = (videoData) => { const {title, userId, duration, userName, videoId, view, channelId, userNameList} = videoData; let res = blockTitle(title); if (res.state) return res; res = blockUserName(userName); if (userNameList) { for (const n of userNameList) { res = blockUserName(userName); if (res.state) return res; } } if (res.state) return res; res = blockUserId(userId); if (res.state) return res; res = blockVideoId(videoId); if (res.state) return res; res = blockChannelId(channelId); if (res.state) return res; res = blockUserIDAssociatedWithUserName(userId, userName); if (res.state) return res; res = blockUserIdAssociatedWithChannelId(userId, channelId); if (res.state) return res; res = blockUserNameAssociatedWithChannelId(userName, channelId); if (res.state) return res; return returnTempVal; }; const shieldingVideoDecorated = async (videoData) => { const testResults = shieldingVideo(videoData); const {state, type, matching} = testResults; if (state) { videoData['testResults'] = testResults; const {title} = videoData; eventEmitter.send('event:print-msg', {msg: `${type}规则【${matching}】屏蔽视频【${title}】`, data: videoData}); videoData.el.remove(); return } return promiseReject }; var video_shielding = { shieldingVideoDecorated, blockRelationRule }; const getUrlChannelId = (url) => { if (url.includes('www.youtube.com/channel')) { const match = url.match(/www.youtube.com\/channel\/(.+)/)?.[1]; return decodeURI(match); } return null; }; const getUrlUserId = (url) => { if (url.includes('www.youtube.com/@')) { const match = url.match(/www.youtube.com\/(.+)/)?.[1]; return decodeURI(match); } return null }; const getUrlVideoId = (url) => { if (url.includes('://www.youtube.com/watch?v=')) { const videoId = parseUrl(url).queryParams['v']; return decodeURI(videoId); } return null; }; 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 getUrlCompilationId = (url) => { if (url.includes('https://www.youtube.com/watch?v=')) { return parseUrl(url).queryParams['list'] } return null; }; var urlUtil = { getUrlChannelId, getUrlUserId, getUrlVideoId, parseUrl, getUrlCompilationId }; const isHomeUrlPage = () => { return window.location.href === 'https://www.youtube.com/'; }; const getVideoDataList = async () => { const elList = await elUtil.findElements('ytd-browse[page-subtype="home"] ytd-rich-grid-renderer>#contents>ytd-rich-item-renderer'); const list = []; for (const el of elList) { const durationEl = el.querySelector('.yt-badge-shape__text'); const durationTxt = durationEl ? durationEl.textContent.trim() : null; const sponsoredAds = el.querySelector('.yt-badge-shape--ad .yt-badge-shape__text'); if (sponsoredAds && sponsoredAds.textContent.trim() === '赞助商广告') { const delSponsoredAdsV = isDelSponsoredAdsGm(); if (delSponsoredAdsV) { el.remove(); console.log('已删除赞助商广告', el); continue; } } const titleContainerEl = el.querySelector('yt-lockup-metadata-view-model,#meta'); if (titleContainerEl === null) { continue; } const titleAEl = titleContainerEl.querySelector('.yt-lockup-metadata-view-model__title,#video-title-link'); const userAEl = titleContainerEl.querySelector('a.yt-core-attributed-string__link.yt-core-attributed-string__link--call-to-action-color.yt-core-attributed-string--link-inherit-color,ytd-channel-name a[href^="/@"]'); let view = -1, duration = -1, insertionPositionEl, userId = null, userName = null, userUrl = null, channelId = null, compilationId = null, userNameList = null; const vipEl = el.querySelector('.badge-style-type-members-only'); if (vipEl) { insertionPositionEl = titleContainerEl; } else { insertionPositionEl = el.querySelector('.yt-lockup-view-model__metadata'); if (insertionPositionEl === null) { insertionPositionEl = titleContainerEl; } } const viewEl = insertionPositionEl.querySelector('.yt-content-metadata-view-model__metadata-row:last-child>span:first-child'); if (durationTxt.includes(':')) { duration = strUtil.timeStringToSeconds(durationTxt); } if (viewEl) { const viewTxt = viewEl.textContent.trim(); view = strUtil.parseView(viewTxt); } const videoAddress = titleAEl.href; const videoId = urlUtil.getUrlVideoId(videoAddress); const title = titleAEl.textContent.trim(); if (durationTxt === '合辑') { compilationId = urlUtil.getUrlCompilationId(videoAddress); const namesEl = titleContainerEl.querySelector('.yt-content-metadata-view-model__metadata-row:first-child>span'); userNameList = strUtil.getCompilationUserNames(namesEl?.textContent.trim()); } else if (userAEl) { userName = userAEl.textContent.trim(); userUrl = decodeURI(userAEl.href); channelId = urlUtil.getUrlChannelId(userUrl); userId = urlUtil.getUrlUserId(userUrl); } if (durationTxt.endsWith('个视频')) { debugger//待后续完善 } list.push({ el, title, userId, channelId, durationTxt, duration, videoAddress, userName, userUrl, videoId, insertionPositionEl, explicitSubjectEl: insertionPositionEl, view, compilationId, userNameList }); } startHomeShortsItemDisplay(); return list; }; const checkHomeVideoBlock = async () => { const list = await getVideoDataList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } const adList = document.querySelectorAll('#masthead-ad'); if (adList.length === 0) return; const delSponsoredAdsV = isDelSponsoredAdsGm(); if (!delSponsoredAdsV) return for (const adEl of adList) { adEl.remove(); console.log('已删除大幅赞助商广告', adEl); } }; const intervalCheckHomeVideoList = new IntervalExecutor(checkHomeVideoBlock, {processTips: true, intervalName: '首页视频列表'}); const setHomeShortsItemDisplay = (hide = true) => { elUtil.findElements('ytd-browse[page-subtype="home"] ytd-rich-section-renderer').then(els => { for (const el of els) { if ((el.style.display === 'none' && hide) || (el.style.display === '' && !hide)) { continue } el.style.display = hide ? 'none' : ''; console.log(`已${hide ? '隐藏' : '显示'}首页 shorts 视频列表`, el); } }); }; const startHomeShortsItemDisplay = () => { if (isDelHomeShortsItemGm()) { setHomeShortsItemDisplay(); } }; var homePage = { isHomeUrlPage, startHomeShortsItemDisplay, intervalCheckHomeVideoList, }; const getMetaVideoList = (elList) => { const list = []; for (const el of elList) { const explicitSubjectEl = el.querySelector('#meta'); const insertionPositionEl = el.querySelector('#metadata'); const titleAEl = explicitSubjectEl.querySelector('a#video-title-link,a#video-title'); const userAEl = insertionPositionEl.querySelector('#text-container a'); const viewEl = insertionPositionEl.querySelector('#metadata-line>span'); const durationEl = el.querySelector('.yt-badge-shape__text'); const durationTxt = durationEl ? durationEl.textContent.trim() : null; let duration = -1, view = -1, channelId = null; if (durationEl) { if (durationTxt.includes(':')) { duration = strUtil.timeStringToSeconds(durationTxt); } } if (viewEl) { const viewText = viewEl.textContent.trim(); if (!viewText.includes('预定发布时间') || durationTxt !== '即将开始') { view = strUtil.parseView(viewText); } } const title = titleAEl.textContent.trim(); const videoAddress = titleAEl.href; const userUrl = userAEl.href; const videoId = urlUtil.getUrlVideoId(videoAddress); const userId = urlUtil.getUrlUserId(userUrl); if (userId === null) { channelId = urlUtil.getUrlChannelId(userUrl); } const userName = userAEl.textContent.trim(); list.push({ el, title, view, userId, videoAddress, userUrl, duration, videoId, insertionPositionEl, explicitSubjectEl, durationTxt, userName, channelId }); } return list; }; const extractCommentList = async (els) => { const list = []; for (const el of els) { if (el.tagName === 'YTD-CONTINUATION-ITEM-RENDERER') continue; const mainEl = el.querySelector('#comment #main'); const userIdEl = mainEl.querySelector('a#author-text'); const contentEl = mainEl.querySelector('#content-text'); const replies = el.querySelectorAll('#replies #contents>ytd-comment-view-model'); const insertionPositionEl = mainEl.querySelector('#header-author'); const userUrl = decodeURI(userIdEl.href); const userId = urlUtil.getUrlUserId(userUrl); const content = contentEl.textContent.trim(); const reply = []; list.push({ userId, userUrl, content, reply, insertionPositionEl, explicitSubjectEl: mainEl, el }); for (const replyEl of replies) { const replyUserIdEl = replyEl.querySelector('a#author-text'); const replyContentEl = replyEl.querySelector('#content-text'); const userUrl = decodeURI(replyUserIdEl.href); const userId = urlUtil.getUrlUserId(userUrl); const content = replyContentEl.textContent.trim(); const insertionPositionEl = replyEl.querySelector('#header-author'); reply.push({ userUrl, userId, content, el: replyEl, insertionPositionEl, explicitSubjectEl: replyEl }); } } return list }; var pageCommon = { getMetaVideoList, extractCommentList }; const defLiveHomeListSelector = 'ytd-rich-item-renderer.style-scope.ytd-rich-shelf-renderer'; let liveRootEl = null; const getLiveHomRootEl = async () => { if (liveRootEl !== null) return liveRootEl; liveRootEl = await elUtil.findElement('ytd-browse[page-subtype="live"]'); return liveRootEl; }; const isUrlPage$e = (url = location.href) => { return url.endsWith('://www.youtube.com/channel/UC4R8DWoMoI7CAwX8_LjQHig') || url.includes('://www.youtube.com/channel/UC4R8DWoMoI7CAwX8_LjQHig/livetab') }; const isUrlOtherLivePage = (url = location.href) => { return url.includes('://www.youtube.com/channel/UC4R8DWoMoI7CAwX8_LjQHig/livetab?ss=') }; const getLiveList = async () => { const liveHomRootEl = await getLiveHomRootEl(); let selector; if (isUrlOtherLivePage()) { selector = 'ytd-rich-item-renderer.style-scope.ytd-rich-grid-renderer'; } else { selector = defLiveHomeListSelector; } const elList = await elUtil.findElements(selector, {doc: liveHomRootEl}); return pageCommon.getMetaVideoList(elList); }; const intervalLiveListExecutor = new IntervalExecutor(async () => { const list = await getLiveList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: '直播列表'}); const removeLiveHomeTopBanner = async () => { if (!isUrlPage$e()) return; if (isUrlOtherLivePage()) return if (!isDelLiveHomeTopBannerGm()) return; const liveHomeEl = await getLiveHomRootEl(); const el = await elUtil.findElement('#content>ytd-carousel-item-renderer', {doc: liveHomeEl}); el.remove(); console.log('已删除直播首页顶部大横幅推荐', el); }; var liveHomePage = { isUrlPage: isUrlPage$e, getLiveList, intervalLiveListExecutor, removeLiveHomeTopBanner }; const blockComment = (comment) => { return shielding.blockExactAndFuzzyMatching(comment, { fuzzyKey: 'comment', fuzzyTypeName: '模糊评论', regexKey: 'comment_regex', regexTypeName: '正则评论' }) }; const shieldingComment = (commentsData) => { const {userId, userName, channelId, content} = commentsData; let returnVal = blockUserName(userName); if (returnVal.state) return returnVal; returnVal = blockComment(content); if (returnVal.state) return returnVal; returnVal = blockUserId(userId); if (returnVal.state) return returnVal; returnVal = blockChannelId(channelId); if (returnVal.state) return returnVal; returnVal = blockUserIDAssociatedWithUserName(userId, userName); if (returnVal.state) return returnVal; returnVal = blockUserIdAssociatedWithChannelId(userId, channelId); if (returnVal.state) return returnVal; returnVal = blockUserNameAssociatedWithChannelId(userName, channelId); if (returnVal.state) return returnVal; return returnTempVal }; const shieldingCommentDecorated = async (commentData) => { const testResults = shieldingComment(commentData); const {state, type, matching, el} = testResults; if (state) { commentData.testResults = testResults; const {content} = commentData; eventEmitter.send('event:print-msg', {msg: `${type}规则【${matching}】屏蔽评论【${content}】`, data: commentData}); el.remove(); return } const list = []; list.push(commentData); for (const replyData of commentData.reply) { const testResults = shieldingComment(replyData); const {state, type, matching, el} = testResults; if (state) { replyData.testResults = testResults; eventEmitter.send('event:print-msg', {msg: `根据【${type}】规则【${matching}】屏蔽评论`, data: replyData}); el.remove(); continue; } list.push(replyData); } return Promise.reject(list) }; var comments_shielding = { shieldingCommentDecorated }; var gzStyleCss = `button[gz_type] { display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; border: 1px solid #dcdfe6; color: #F07775; -webkit-appearance: none; text-align: center; box-sizing: border-box; outline: none; margin: 0; transition: .1s; font-weight: 500; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; padding: 10px 20px; font-size: 14px; border-radius: 8px; } button[gz_type="primary"] { color: #fff; background-color: #409eff; border-color: #409eff; } button[gz_type="success"] { color: #fff; background-color: #67c23a; border-color: #67c23a; } button[gz_type="info"] { color: #fff; background-color: #909399; border-color: #909399; } button[gz_type="warning"] { color: #fff; background-color: #e6a23c; border-color: #e6a23c; } button[gz_type="danger"] { color: #fff; background-color: #f56c6c; border-color: #f56c6c; } button[border] { border-radius: 20px; padding: 12px 23px; } input[gz_type] { font-family: 'Arial', sans-serif; font-size: 16px; padding: 10px; margin: 10px; border: 1px solid #ccc; border-radius: 4px; outline: none; } input[gz_type]:focus { border-color: #007bff; box-shadow: 0 0 5px rgba(0, 123, 255, 0.3); } select { font-family: 'Arial', sans-serif; font-size: 16px; padding: 10px; margin: 10px; border: 1px solid #ccc; border-radius: 4px; outline: none; background-color: white; color: #333; } select:focus { border-color: #007bff; box-shadow: 0 0 5px rgba(0, 123, 255, 0.3); } select:disabled { background-color: #f1f1f1; border-color: #ccc; color: #888; } button:hover { border-color: #646cff; } button[gz_type]:focus, button[gz_type]:focus-visible { outline: 4px auto -webkit-focus-ring-color; } `; const isLivePage = async () => { let el = await elUtil.findElement('#chatframe', {timeout: 2500}); if (el !== null) { return true; } el = await elUtil.findElement('#view-count[aria-label]'); const ariaLabel = el.getAttribute('aria-label'); if (ariaLabel !== '') { return true; } el = document.querySelector('#info-container #info.style-scope.ytd-watch-info-text>.style-scope.yt-formatted-string'); if (el === null) { return false; } const numStr = el.textContent.trim(); return !isNaN(numStr); }; const getChatMsgList = async () => { const chatFrameEl = await elUtil.findElement('#chatframe', { validationElFun: (config, selector) => { const el = document.querySelector(selector); if (el === null) return null; const iframeDocument = el.contentDocument; elUtil.findElement('head', {doc: iframeDocument}).then(headEl => { elUtil.installStyle(gzStyleCss, {el: headEl}); }); return iframeDocument; } }); const elList = await elUtil.findElements('#item-offset>#items yt-live-chat-text-message-renderer.yt-live-chat-item-list-renderer', {doc: chatFrameEl}); const list = []; const engagementEl = chatFrameEl.querySelector('yt-live-chat-viewer-engagement-message-renderer'); if (engagementEl) engagementEl.remove(); for (const el of elList) { const contentEl = el.querySelector('#content'); const nameEl = contentEl.querySelector('#author-name'); const userName = nameEl.textContent.trim(); const msgEl = contentEl.querySelector('#message'); const insertionPositionEl = el; const explicitSubjectEl = el; list.push({ el, userName, msg: msgEl.textContent.trim(), msgChildren: msgEl.childNodes, insertionPositionEl, explicitSubjectEl }); } return list; }; const checkChatMsgListBlock = async () => { const list = await getChatMsgList(); for (const itemData of list) { eventEmitter.send('event:插入屏蔽按钮', itemData); } }; const intervalChatMsgListBlockExecutor = new IntervalExecutor(async () => { if (await isLivePage()) { await checkChatMsgListBlock(); } else { intervalChatMsgListBlockExecutor.stop('播放页非直播状态,取消检测聊天弹幕操作'); } }, {processTips: true, intervalName: '播放聊天弹幕列表'}); var playLivePage = { isLivePage, getChatMsgList, intervalChatMsgListBlockExecutor }; const isUrlPage$d = (url = location.href) => { return url.includes('://www.youtube.com/watch?v=') }; const getCommentCount = () => { const el = document.querySelector('.count-text.style-scope.ytd-comments-header-renderer>.style-scope.yt-formatted-string'); if (el === null) return -1; const numStr = el.textContent.trim(); return strUtil.parseView(numStr) }; const getRightVideoList = async () => { const elList = await elUtil.findElements('#items>yt-lockup-view-model,.ytd-item-section-renderer.lockup.yt-lockup-view-model--wrapper'); const list = []; for (const el of elList) { if (el.classList.contains('ytd-horizontal-card-list-renderer')) { continue; } const titleContainerEl = el.querySelector('.yt-lockup-metadata-view-model'); if (titleContainerEl === null) { continue; } const durationEl = el.querySelector('yt-thumbnail-badge-view-model badge-shape>.yt-badge-shape__text'); const durationTxt = durationEl ? durationEl.textContent.trim() : null; if (durationTxt === null) { continue } let duration = -1, view = -1, compilationId, userNameList, userName; const titleAEl = titleContainerEl.querySelector('.yt-lockup-metadata-view-model__title'); const bottomInfoEls = titleContainerEl.querySelectorAll('.yt-content-metadata-view-model__metadata-row span[role="text"]'); if (durationTxt.includes(':')) { duration = strUtil.timeStringToSeconds(durationTxt); const viewTxt = bottomInfoEls[1].textContent.trim(); view = strUtil.parseView(viewTxt); } const videoAddress = titleAEl.href; if (durationTxt === '合辑') { compilationId = urlUtil.getUrlCompilationId(videoAddress); const namesEl = titleContainerEl.querySelector('.yt-content-metadata-view-model__metadata-row:first-child>span'); userNameList = strUtil.getCompilationUserNames(namesEl?.textContent.trim()); } else { userName = bottomInfoEls[0].textContent.trim(); } const videoId = urlUtil.getUrlVideoId(videoAddress); const title = titleAEl.textContent.trim(); const insertionPositionEl = el.querySelector('.yt-lockup-view-model__metadata'); list.push({ el, title, view, durationTxt, duration, videoAddress, userName, videoId, compilationId, insertionPositionEl, explicitSubjectEl: insertionPositionEl, userNameList }); } return list; }; const checkRightVideoListBlock = async () => { const list = await getRightVideoList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }; const getCommentList$1 = async () => { const elList = await elUtil.findElements('#comments>#sections>#contents>.style-scope.ytd-item-section-renderer'); return pageCommon.extractCommentList(elList); }; const intervalCheckPlayerVideoList = new IntervalExecutor(checkRightVideoListBlock, {processTips: true, intervalName: '播放页右侧视频列表'}); const intervalCheckCommentList = new IntervalExecutor(async () => { if (await playLivePage.isLivePage()) { intervalCheckCommentList.stop('播放页为直播状态,取消检测评论区操作'); return; } const commentCount = getCommentCount(); if (commentCount !== -1 && commentCount === 0) { intervalCheckCommentList.stop('评论区为空,取消检测评论区操作'); return } const list = await getCommentList$1(); for (const contentData of list) { comments_shielding.shieldingCommentDecorated(contentData).catch((list) => { list.forEach(v => eventEmitter.send('event:插入屏蔽按钮', v)); }); } }, {processTips: true, intervalName: '评论区'}); const getPlayUserInfo = async () => { const userAel = await elUtil.findElement('ytd-watch-metadata #text>a'); const userUrl = decodeURI(userAel.href); const userName = userAel.textContent.trim(); const userId = urlUtil.getUrlUserId(userUrl); const parseUrl = urlUtil.parseUrl(location.href); const videoId = parseUrl.queryParams['v']; return {userId, userName, userUrl, videoId} }; const addShieldButton$2 = () => { if (document.querySelector('#top-row.style-scope.ytd-watch-metadata button[gz_type]')) return; elUtil.findElement('#top-row.style-scope.ytd-watch-metadata').then(el => { if (el.querySelector('button[gz_type]#user-shield-button')) return; const but = document.createElement('button'); but.textContent = '屏蔽'; but.setAttribute('gz_type', ''); but.id = 'user-shield-button'; el.appendChild(but); but.addEventListener('click', () => { getPlayUserInfo().then(data => { eventEmitter.emit('event:mask_options_dialog_box', data); }); }); }); }; const checkRightVideoListAd = () => { if (!isDelVideoPageSponsoredAdsGm()) return; elUtil.findElement('#player-ads').then(el => { el.remove(); eventEmitter.send('event:print-msg', '已删除视频页右侧视频列表上方赞助商广告'); }); }; var playerPage = { isUrlPage: isUrlPage$d, getRightVideoList, intervalCheckPlayerVideoList, addShieldButton: addShieldButton$2, intervalCheckCommentList, checkRightVideoListAd }; var script$3 = { data() { return { isDelSponsoredAdsV: isDelSponsoredAdsGm(), isDelHomeShortsItemV: isDelHomeShortsItemGm(), isDelLiveHomeTopBannerV: isDelLiveHomeTopBannerGm(), isDelVideoPageSponsoredAdsV: isDelVideoPageSponsoredAdsGm() } }, watch: { isDelSponsoredAdsV(n) { GM_setValue('is_del_sponsored_ads_gm', n); }, isDelHomeShortsItemV(n) { GM_setValue('is_del_home_shorts_item_gm', n); homePage.startHomeShortsItemDisplay(); }, isDelLiveHomeTopBannerV(n) { GM_setValue('is_del_live_home_top_banner_gm', n); liveHomePage.removeLiveHomeTopBanner(); }, isDelVideoPageSponsoredAdsV(n) { GM_setValue('is_del_video_page_sponsored_ads_gm', n); if (n && playerPage.isUrlPage()) { playerPage.checkRightVideoListAd(); } } } }; const __vue_script__$3 = script$3; var __vue_render__$3 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("首页")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("el-switch", { attrs: { "active-text": "屏蔽赞助商广告" }, model: { value: _vm.isDelSponsoredAdsV, callback: function ($$v) { _vm.isDelSponsoredAdsV = $$v; }, expression: "isDelSponsoredAdsV", }, }), _vm._v(" "), _c("el-switch", { attrs: { "active-text": "移除shorts" }, model: { value: _vm.isDelHomeShortsItemV, callback: function ($$v) { _vm.isDelHomeShortsItemV = $$v; }, expression: "isDelHomeShortsItemV", }, }), ], 1 ), _vm._v(" "), _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("直播(首页)")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("el-switch", { attrs: { "active-text": "屏蔽顶部大横幅推广直播" }, model: { value: _vm.isDelLiveHomeTopBannerV, callback: function ($$v) { _vm.isDelLiveHomeTopBannerV = $$v; }, expression: "isDelLiveHomeTopBannerV", }, }), ], 1 ), _vm._v(" "), _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("视频页(直播页)")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("el-switch", { attrs: { "active-text": "屏蔽视频列表广告", title: "右侧视频列表上方的赞助商广告", }, model: { value: _vm.isDelVideoPageSponsoredAdsV, callback: function ($$v) { _vm.isDelVideoPageSponsoredAdsV = $$v; }, expression: "isDelVideoPageSponsoredAdsV", }, }), ], 1 ), ], 1 ) }; var __vue_staticRenderFns__$3 = []; __vue_render__$3._withStripped = true; const __vue_inject_styles__$3 = undefined; const __vue_component__$3 = normalizeComponent( { render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 }, __vue_inject_styles__$3, __vue_script__$3); var script$2 = { data() { return { intervalExecutorStatus: [] } }, methods: { setIntervalExecutorStatusBut(row, status) { IntervalExecutor.setIntervalExecutorStatus(row.key, status); } }, created() { eventEmitter.on('event:update:intervalExecutorStatus', (data) => { const {status, key} = data; const find = this.intervalExecutorStatus.find(item => item.key === key); if (find) { find.status = status; } else { this.intervalExecutorStatus.push(data); } }); elUtil.installStyle(` #detectionStatusView .el-button { margin-left: 0!important }`); } }; const __vue_script__$2 = script$2; var __vue_render__$2 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", { attrs: { id: "detectionStatusView" } }, [ _c( "el-table", { attrs: { data: _vm.intervalExecutorStatus, border: "", stripe: "" } }, [ _c("el-table-column", { attrs: { label: "检测名", prop: "name" } }), _vm._v(" "), _c("el-table-column", { attrs: { label: "状态", width: "100" }, scopedSlots: _vm._u([ { key: "default", fn: function (scope) { return [ _c( "el-tag", { directives: [ { name: "show", rawName: "v-show", value: scope.row.status, expression: "scope.row.status", }, ], attrs: { type: "success" }, }, [_vm._v("运行中")] ), _vm._v(" "), _c( "el-tag", { directives: [ { name: "show", rawName: "v-show", value: !scope.row.status, expression: "!scope.row.status", }, ], attrs: { type: "danger" }, }, [_vm._v("已停止")] ), ] }, }, ]), }), _vm._v(" "), _c("el-table-column", { attrs: { label: "操作", width: "100" }, scopedSlots: _vm._u([ { key: "default", fn: function (scope) { return [ _c( "div", [ _c( "el-button", { directives: [ { name: "show", rawName: "v-show", value: scope.row.status, expression: "scope.row.status", }, ], on: { click: function ($event) { return _vm.setIntervalExecutorStatusBut( scope.row, false ) }, }, }, [_vm._v("停止")] ), _vm._v(" "), _c( "el-button", { directives: [ { name: "show", rawName: "v-show", value: !scope.row.status, expression: "!scope.row.status", }, ], on: { click: function ($event) { return _vm.setIntervalExecutorStatusBut( scope.row, true ) }, }, }, [_vm._v("启动")] ), ], 1 ), ] }, }, ]), }), ], 1 ), ], 1 ) }; var __vue_staticRenderFns__$2 = []; __vue_render__$2._withStripped = true; const __vue_inject_styles__$2 = undefined; const __vue_component__$2 = normalizeComponent( { render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 }, __vue_inject_styles__$2, __vue_script__$2); var script$1 = { data() { return { outputInfoArr: [] } }, methods: { clearInfoBut() { this.$confirm('是否清空信息', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.outputInfoArr = []; this.$notify({message: '已清空信息', type: 'success'}); }); }, lookDataBut(row) { console.log(row); }, addOutInfo(data) { const find = this.outputInfoArr.find(item => item.msg === data.msg); const date = new Date(); const showTime = date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds(); if (find) { find.showTime = showTime; if (find.msg === data.msg) { return; } find.data = data.data; } else { data.showTime = showTime; this.outputInfoArr.unshift(data); } } }, created() { eventEmitter.on('event:print-msg', (msgData) => { if (typeof msgData === 'string') { this.addOutInfo({msg: msgData, data: null}); return; } this.addOutInfo(msgData); }); } }; const __vue_script__$1 = script$1; var __vue_render__$1 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-table", { attrs: { data: _vm.outputInfoArr, border: "", stripe: "" } }, [ _c("el-table-column", { attrs: { label: "显示时间", prop: "showTime", width: "80" }, }), _vm._v(" "), _c("el-table-column", { attrs: { prop: "msg" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [ _c( "el-button", { attrs: { type: "info" }, on: { click: _vm.clearInfoBut }, }, [_vm._v("清空消息")] ), ] }, proxy: true, }, ]), }), _vm._v(" "), _c("el-table-column", { attrs: { label: "操作", width: "85" }, scopedSlots: _vm._u([ { key: "default", fn: function (scope) { return [ _c( "el-tooltip", { attrs: { content: "内容打印在控制台中" } }, [ _c( "el-button", { attrs: { type: "primary" }, on: { click: function ($event) { return _vm.lookDataBut(scope.row) }, }, }, [_vm._v("print")] ), ], 1 ), ] }, }, ]), }), ], 1 ), ], 1 ) }; var __vue_staticRenderFns__$1 = []; __vue_render__$1._withStripped = true; const __vue_inject_styles__$1 = undefined; const __vue_component__$1 = normalizeComponent( { render: __vue_render__$1, staticRenderFns: __vue_staticRenderFns__$1 }, __vue_inject_styles__$1, __vue_script__$1); var script = { components: { OutputInformationView: __vue_component__$1, ShowImgDialog: __vue_component__$6, RuleManagementView: __vue_component__$9, PanelSettingsView: __vue_component__$8, AboutAndFeedbackView: __vue_component__$7, DonateLayoutView: __vue_component__$5, SheetDialog: __vue_component__$4, PageProcessingView: __vue_component__$3, DetectionStatusView: __vue_component__$2 }, data() { return { drawer: false, dev: false } }, methods: { testBut() { }, test1But() { }, test2But() { }, test3But() { } }, created() { eventEmitter.on('event-drawer-show', (bool) => { this.drawer = bool === null ? !this.drawer : bool; }); 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.handler('el-prompt', (...options) => { return this.$prompt(...options) }); } }; const __vue_script__ = script; var __vue_render__ = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-drawer", { staticStyle: { position: "fixed" }, attrs: { visible: _vm.drawer, title: "油管内容屏蔽器", "z-index": 2050, size: "35%", }, on: { "update:visible": function ($event) { _vm.drawer = $event; }, }, }, [ _c( "el-tabs", { attrs: { "tab-position": "left", type: "card" } }, [ _c( "el-tab-pane", { attrs: { lazy: "", label: "规则管理" } }, [_c("RuleManagementView")], 1 ), _vm._v(" "), _c( "el-tab-pane", { attrs: { lazy: "", label: "页面处理" } }, [_c("PageProcessingView")], 1 ), _vm._v(" "), _c( "el-tab-pane", { attrs: { label: "输出信息", lazy: "" } }, [_c("OutputInformationView")], 1 ), _vm._v(" "), _c( "el-tab-pane", { attrs: { label: "检测状态", lazy: "" } }, [_c("DetectionStatusView")], 1 ), _vm._v(" "), _c( "el-tab-pane", { attrs: { lazy: "", label: "面板设置" } }, [_c("PanelSettingsView")], 1 ), _vm._v(" "), _c( "el-tab-pane", { attrs: { lazy: "", label: "关于与反馈" } }, [ _c("AboutAndFeedbackView"), _vm._v(" "), _c("DonateLayoutView"), ], 1 ), ], 1 ), _vm._v(" "), _vm.dev ? _c( "div", [ _c("el-button", { on: { click: _vm.testBut } }, [ _vm._v("测试1"), ]), _vm._v(" "), _c("el-button", { on: { click: _vm.test1But } }, [ _vm._v("测试获取直播页弹幕列表"), ]), _vm._v(" "), _c("el-button", { on: { click: _vm.test2But } }, [ _vm._v("测试获取播放页右侧视频列表"), ]), _vm._v(" "), _c("el-button", { on: { click: _vm.test3But } }, [ _vm._v("检查播放页聊天弹幕屏蔽"), ]), ], 1 ) : _vm._e(), ], 1 ), _vm._v(" "), _c("ShowImgDialog"), _vm._v(" "), _c("SheetDialog"), ], 1 ) }; var __vue_staticRenderFns__ = []; __vue_render__._withStripped = true; const __vue_inject_styles__ = undefined; const __vue_component__ = normalizeComponent( { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }, __vue_inject_styles__, __vue_script__); function initVueApp(el, App, props = {}) { return new Vue({ render: h => h(App, {props}) }).$mount(el); } const init = () => { if (document.head.querySelector('#element-ui-css') === null) { const linkElement = document.createElement('link'); linkElement.rel = 'stylesheet'; linkElement.href = 'https://unpkg.com/element-ui@2.15.14/lib/theme-chalk/index.css'; linkElement.id = 'element-ui-css'; document.head.appendChild(linkElement); linkElement.addEventListener('load', () => { console.log('element-ui样式加载完成'); }); } const parentEl = document.createElement('div'); const vueDiv = document.createElement('div'); parentEl.appendChild(vueDiv); document.body.appendChild(parentEl); window.mk_vue_app = initVueApp(vueDiv, __vue_component__); console.log('注入演示'); const styleEl = document.createElement('style'); styleEl.innerHTML = `.v-modal{ z-index: auto !important; }`; document.head.appendChild(styleEl); }; var layout_init = {init}; GM_registerMenuCommand('打开/关闭屏蔽器', () => { eventEmitter.send('event-drawer-show', null); }); const isUrlPage$c = (url) => { return url.includes('://www.youtube.com/results?search_query=') }; const getVideoList$3 = async () => { const elList = await elUtil.findElements('#page-manager>ytd-search #contents>ytd-video-renderer.style-scope.ytd-item-section-renderer'); const list = []; for (const el of elList) { const metaEl = el.querySelector('#meta'); const titleAEl = metaEl.querySelector('#video-title'); const viewEl = metaEl.querySelector('#separator+span'); const userAEl = el.querySelector('ytd-channel-name a'); const durationEl = el.querySelector('.yt-badge-shape__text'); let duration = -1; if (durationEl) { duration = strUtil.timeStringToSeconds(durationEl.textContent.trim()); } const view = strUtil.parseView(viewEl.textContent.trim()); const title = titleAEl.textContent.trim(); const videoAddress = titleAEl.href; const videoId = urlUtil.getUrlVideoId(videoAddress); const userName = userAEl.textContent.trim(); const userUrl = decodeURI(userAEl.href); const userId = urlUtil.getUrlUserId(userUrl); const insertionPositionEl = el.querySelector('#channel-info'); const explicitSubjectEl = el.querySelector('.text-wrapper.style-scope.ytd-video-renderer'); list.push({ el, title, videoAddress, videoId, userName, userUrl, duration, view, userId, insertionPositionEl, explicitSubjectEl }); } return list; }; const intervalCheckSearchVideoList = new IntervalExecutor(async () => { const list = await getVideoList$3(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: '搜索页视频列表'}); const addShieldButton$1 = () => { const selector = '#page-manager>ytd-search #contents ytd-channel-renderer'; elUtil.findElements(selector).then(els => { const shieldButSelector = selector + ' #subscribe-button>button[gz_type]'; if (document.querySelector(shieldButSelector)) return; for (const el of els) { const insertionPositionEl = el.querySelector('#subscribe-button'); const but = document.createElement('button'); but.textContent = '屏蔽'; but.setAttribute('gz_type', ''); but.addEventListener('click', () => { console.log('点击了屏蔽按钮'); const userAEl = el.querySelector('#main-link'); const userNameEl = el.querySelector('#channel-title #text'); const userUrl = decodeURI(userAEl.href); const userId = urlUtil.getUrlUserId(userUrl); const userName = userNameEl.textContent.trim(); eventEmitter.send('event:mask_options_dialog_box', {userId, userName, userUrl}); }); insertionPositionEl.appendChild(but); } }); }; var searchPage = { isUrlPage: isUrlPage$c, intervalCheckSearchVideoList, addShieldButton: addShieldButton$1 }; const getChannelPageCurrentTab = (parseUrl) => { const pathSegments = parseUrl.pathSegments; if (pathSegments.length <= 1) { return null; } return pathSegments[2]; }; const isUrlPage$b = (url = location.href) => { return url.includes('://www.youtube.com/channel/'); }; const isUserChannelPage = (url = location.href) => { if (!isUrlPage$b(url)) { return false; } const el = document.querySelector( 'yt-decorated-avatar-view-model+div>yt-content-metadata-view-model>div:first-child'); return el !== null; }; const getVideoAndLiveDataList = async () => { const elList = await elUtil.findElements('ytd-browse[page-subtype="channels"] #items>ytd-grid-video-renderer'); return pageCommon.getMetaVideoList(elList); }; const intervalChannelPageVideoAndLiveListExecutor = new IntervalExecutor(async () => { const list = await getVideoAndLiveDataList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: '频道页视频和直播列表'}); const getLetsPlayVideoList = async () => { const elList = await elUtil.findElements('.ytd-grid-renderer.lockup.yt-lockup-view-model--wrapper'); const list = []; for (const el of elList) { const metadataEl = el.querySelector('.yt-lockup-view-model__metadata'); const titleAel = metadataEl.querySelector('a.yt-lockup-metadata-view-model__title'); const bottomInfoEl = metadataEl.querySelector('.yt-content-metadata-view-model'); const userAEl = bottomInfoEl.querySelector('a[href^="/@"]'); const title = titleAel.textContent.trim(); const videoAddress = titleAel.href; const videoId = urlUtil.getUrlVideoId(videoAddress); const userUrl = decodeURI(userAEl.href); const userId = urlUtil.getUrlUserId(userUrl); const userName = userAEl.textContent.trim(); const playListAEl = bottomInfoEl.querySelector('a[href^="/playlist?list="]'); const playListUrl = playListAEl.href; list.push({ insertionPositionEl: metadataEl, explicitSubjectEl: metadataEl, el, title, videoAddress, videoId, userId, userName, userUrl, playListUrl }); } return list; }; const checkLetsPlayVideoList = async () => { const list = await getLetsPlayVideoList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }; const dynamicRun = (parseUrl) => { const currentTab = getChannelPageCurrentTab(parseUrl); if (currentTab === 'letsplay') { console.log('频道页中的一起玩'); intervalChannelPageVideoAndLiveListExecutor.stop(); checkLetsPlayVideoList(); return; } if (currentTab === null) { intervalChannelPageVideoAndLiveListExecutor.stop(); return; } console.log('测试频道页'); intervalChannelPageVideoAndLiveListExecutor.start(); }; var channelPage = { isUrlPage: isUrlPage$b, getVideoAndLiveDataList, getChannelPageCurrentTab, dynamicRun, isUserChannelPage, intervalChannelPageVideoAndLiveListExecutor }; const isUserSpacePage = (url = location.href) => { return url.includes('://www.youtube.com/@') }; const getUserInfo = async () => { const winHref = location.href; const info = {}; if (channelPage.isUrlPage(winHref)) { info.channelId = urlUtil.getUrlChannelId(winHref); } if (winHref.includes('://www.youtube.com/@')) { info.userId = urlUtil.getUrlUserId(winHref); } else { const el = await elUtil.findElement('yt-decorated-avatar-view-model+div>yt-content-metadata-view-model>div:first-child'); const text = el.textContent; if (text.startsWith('@')) { info.userId = text; } else { info.userId = null; } } info.userUrl = winHref; const nameEl = await elUtil.findElement('.dynamicTextViewModelH1>span'); info.userName = nameEl.textContent; return info; }; const addShieldButton = () => { if (!isUserSpacePage() && !channelPage.isUrlPage()) return; if (document.querySelector('ytd-browse[page-subtype="channels"] yt-flexible-actions-view-model button[gz_type]#user-shield-button')) return; elUtil.findElement('ytd-browse[page-subtype="channels"] yt-flexible-actions-view-model').then(el => { if (el.querySelector('button[gz_type]#user-shield-button')) return; const but = document.createElement('button'); but.textContent = '屏蔽'; but.setAttribute('gz_type', ''); but.id = 'user-shield-button'; but.addEventListener('click', async () => { const data = await getUserInfo(); eventEmitter.emit('event:mask_options_dialog_box', data); }); el.appendChild(but); }); }; const run = () => { addShieldButton(); }; var userSpacePage = { isUserSpacePage, run, addShieldButton }; const isUrlPage$a = (url = location.href) => { if (url.includes('://www.youtube.com/gaming/games')) { return false;//排除,该页不处理 } return url.includes('://www.youtube.com/gaming') }; const intervalCheckGamingVideoList = new IntervalExecutor(async () => { const list = await channelPage.getVideoAndLiveDataList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: '游戏页视频列表'}); var gamingPage = { isUrlPage: isUrlPage$a, intervalCheckGamingVideoList }; const isUrlPage$9 = (url = location.href) => { return url.endsWith('//www.youtube.com/learning') || url.endsWith('//www.youtube.com/channel/UCtFRv9O2AHqOZjjynzrv-xg'); }; const getLearningList = async () => { const els = await elUtil.findElements('ytd-two-column-browse-results-renderer[page-subtype="learning"] #contents>.style-scope.ytd-rich-shelf-renderer'); return pageCommon.getMetaVideoList(els); }; const intervalLearningListExecutor = new IntervalExecutor(async () => { const list = await getLearningList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: '学习列表检测'}); var learningPage = { isUrlPage: isUrlPage$9, intervalLearningListExecutor }; 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(); const isUrlPage$8 = (url = location.href) => { return url.endsWith('//www.youtube.com/channel/UCEgdi0XIXXZ-qJOFPf4JSKw') }; const getSportsList = async () => { let rootEl = valueCache.get('rootEl:sports'); if (rootEl === null) { rootEl = await elUtil.findElement('ytd-two-column-browse-results-renderer[page-subtype="sports"]'); valueCache.set('rootEl:sports', rootEl); } const els = await elUtil.findElements('#contents>ytd-rich-item-renderer', {doc: rootEl}); return pageCommon.getMetaVideoList(els); }; const intervalSportsListExecutor = new IntervalExecutor(async () => { const list = await getSportsList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: '体育内容列表'}); var sportsPage = { isUrlPage: isUrlPage$8, intervalSportsListExecutor }; const isUrlPage$7 = (url = location.href) => { return url.endsWith('//www.youtube.com/channel/UCrpQ4p1Ql_hG8rKXIKM1MOQ') }; const getFashionList = async () => { let rootEl = valueCache.get('rootEl:fashion'); if (rootEl === null) { rootEl = await elUtil.findElement('ytd-two-column-browse-results-renderer[page-subtype="fashion"]'); } const els = await elUtil.findElements('#contents>ytd-rich-item-renderer', {doc: rootEl}); return pageCommon.getMetaVideoList(els); }; const intervalFashionListExecutor = new IntervalExecutor(async () => { const list = await getFashionList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: '时尚与美容内容列表'}); var fashionPage = { isUrlPage: isUrlPage$7, intervalFashionListExecutor }; const isUrlPage$6 = (url = location.href) => { return url.includes('//www.youtube.com/podcasts') }; const isHotBlogPage = (url = location.href) => { return url.includes('//www.youtube.com/podcasts/popularshows') }; const isHotBlogItemPage = (url = location.href) => { return url.includes('//www.youtube.com/podcasts/popularepisodes') }; const emptyList = []; const getBlogHomepageRootEl = async () => { let rootEl = valueCache.get('rootEl:podcastsHome'); if (rootEl === null) { const els = await elUtil.findElements('ytd-two-column-browse-results-renderer.style-scope.ytd-browse.grid.grid-disabled[disable-grid-state-aware]'); for (const el of els) { if (el.querySelector('a[href^="/podcasts"]')) { rootEl = el; valueCache.set('rootEl:podcastsHome', rootEl); } } } return rootEl; }; const getPartialBlogRootEl = async () => { let rootEl = valueCache.get('rootEl:podcasts'); if (rootEl === null) { rootEl = await elUtil.findElement('ytd-two-column-browse-results-renderer.style-scope.ytd-browse.grid.grid-disabled[disable-grid-state-aware]'); valueCache.set('rootEl:podcasts', rootEl); } return rootEl; }; const getHotBlogItem = (el) => { const metadataEl = el.querySelector('.yt-lockup-view-model__metadata'); const titleAEl = metadataEl.querySelector('a.yt-lockup-metadata-view-model__title'); const userAEl = metadataEl.querySelector('a[href^="/@"]'); const title = titleAEl.textContent.trim(); const videoUrl = titleAEl.href; const videoId = urlUtil.getUrlVideoId(videoUrl); const userUrl = userAEl.href; const userId = urlUtil.getUrlUserId(userUrl); const userName = userAEl.textContent.trim(); const playListAEl = metadataEl.querySelector('a[href^="/playlist?list="]'); const playListUrl = playListAEl.href; return { insertionPositionEl: metadataEl, explicitSubjectEl: metadataEl, el, title, videoUrl, videoId, userId, userName, userUrl, playListUrl } }; const intervalPodcastsListExecutor = new IntervalExecutor(async () => { const isHotBlogPageV = isHotBlogPage(); const isHotBlogItemPageV = isHotBlogItemPage(); const list = []; if (isHotBlogPageV) { const els = document.querySelectorAll('#spinner-container+#contents>.style-scope.ytd-rich-grid-renderer'); for (const el of els) { list.push(getHotBlogItem(el)); } } else { let rootEl; if (isHotBlogItemPageV) { rootEl = await getPartialBlogRootEl(); } else { rootEl = await getBlogHomepageRootEl(); } if (rootEl === null) return emptyList; const els = await elUtil.findElements('#contents>ytd-rich-item-renderer', {doc: rootEl}); const otherElList = []; for (const el of els) { if (el.hidden) { continue; } const isResponsiveGrid = el.getAttribute('is-responsive-grid'); if (isResponsiveGrid !== 'EXTRA_COMPACT') { otherElList.push(el); continue; } if (el.querySelector('#channel')) { continue; } list.push(getHotBlogItem(el)); } list.push(...pageCommon.getMetaVideoList(otherElList)); } for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: '博客内容列表'}); var podcastsPage = { isUrlPage: isUrlPage$6, intervalPodcastsListExecutor }; const isUrlPage$5 = (url = location.href) => { return url.includes('://www.youtube.com/hashtag/') }; const getVideoList$2 = async () => { let RootEl = valueCache.get('rootEl:hashtag-landing-page'); if (RootEl === null) { RootEl = await elUtil.findElement('ytd-two-column-browse-results-renderer[page-subtype="hashtag-landing-page"]'); valueCache.set('rootEl:hashtag-landing-page', RootEl); } return pageCommon.getMetaVideoList(await elUtil.findElements('#contents>ytd-rich-item-renderer', {doc: RootEl})); }; window.getVideoList = getVideoList$2; const intervalTagVideoListExecutor = new IntervalExecutor(async () => { const list = await getVideoList$2(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: 'tag标签页面视频列表'}); var hashTagPage = { isUrlPage: isUrlPage$5, intervalTagVideoListExecutor }; const isUrlPage$4 = (url = location.href) => { return url.includes('://www.youtube.com/feed/courses_destination'); }; const getCourseList = async () => { const els = await elUtil.findElements('#page-manager>ytd-browse:not([page-subtype]) #contents>.style-scope.ytd-rich-shelf-renderer'); const list = []; for (const el of els) { const bottomContainerEl = el.querySelector('.yt-lockup-metadata-view-model__text-container'); const titleAel = bottomContainerEl.querySelector('h3>.yt-lockup-metadata-view-model__title'); const userAEl = bottomContainerEl.querySelector('a[href^="/@"]'); const title = titleAel.textContent.trim(); const videUrl = titleAel.href; const videoId = urlUtil.getUrlVideoId(videUrl); const userName = userAEl.textContent.trim(); const userUrl = decodeURI(userAEl.href); const userId = urlUtil.getUrlUserId(userUrl); list.push({ title, videUrl, videoId, userName, userUrl, userId, el, insertionPositionEl: bottomContainerEl, explicitSubjectEl: bottomContainerEl }); } return list }; const checkCourseList = async () => { const list = await getCourseList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }; const intervalCheckCourseList = new IntervalExecutor(checkCourseList, {processTips: true, intervalName: '课程列表'}); var coursesPage = { isUrlPage: isUrlPage$4, intervalCheckCourseList }; const isUrlPage$3 = (url = location.href) => { return url.includes('//www.youtube.com/channel/UCYfdidRxbB8Qhf0Nx7ioOYw') || url.includes('/www.youtube.com/feed/news_destination'); }; const getNewsVideoList = async () => { let rootEl = valueCache.get('rootEl:news'); if (rootEl === null) { rootEl = await elUtil.findElement('ytd-two-column-browse-results-renderer[page-subtype="news"]'); valueCache.set('rootEl:news', rootEl); } const els = await elUtil.findElements('#contents ytd-rich-item-renderer', {doc: rootEl}); return pageCommon.getMetaVideoList(els); }; const intervalNewsVideoListExecutor = new IntervalExecutor(async () => { const list = await getNewsVideoList(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: '新闻'}); var newsPage = { isUrlPage: isUrlPage$3, intervalNewsVideoListExecutor }; const isUrlPage$2 = (url = location.href) => { return url.includes('//www.youtube.com/feed/history'); }; const getVideoList$1 = async () => { let rootEl = valueCache.get('rootEl:history'); if (rootEl === null) { rootEl = await elUtil.findElement('ytd-two-column-browse-results-renderer[page-subtype="history"]'); } const els = await elUtil.findElements('#contents>yt-lockup-view-model', {doc: rootEl}); const list = []; for (const el of els) { const metadataEl = el.querySelector('.yt-lockup-view-model__metadata'); const titleAel = metadataEl.querySelector('a.yt-lockup-metadata-view-model__title'); const nameEl = metadataEl.querySelector('.yt-content-metadata-view-model span'); const title = titleAel.textContent.trim(); const videoUrl = titleAel.href; const videoId = urlUtil.getUrlVideoId(videoUrl); const userName = nameEl.textContent.trim(); const insertionPositionEl = metadataEl.querySelector( '.yt-content-metadata-view-model__metadata-row.yt-content-metadata-view-model__metadata-row--metadata-row-padding'); list.push({ title, insertionPositionEl, explicitSubjectEl: metadataEl, videoUrl, videoId, userName, el }); } return list; }; const intervalHistoryVideoListExecutor = new IntervalExecutor(async () => { const list = await getVideoList$1(); for (const v of list) { video_shielding.shieldingVideoDecorated(v).catch(() => { eventEmitter.send('event:插入屏蔽按钮', v); }); } }, {processTips: true, intervalName: '历史记录列表'}); var historyPage = { isUrlPage: isUrlPage$2, intervalHistoryVideoListExecutor }; const isUrlPage$1 = (url) => { return url.includes('/www.youtube.com/playlist?list=') }; const getVideoList = async () => { let rootEl = valueCache.get('rootEl:watchLater'); if (rootEl === null) { rootEl = await elUtil.findElement('ytd-two-column-browse-results-renderer[page-subtype="playlist"]'); valueCache.set('rootEl:watchLater', rootEl); } const els = await elUtil.findElements('ytd-playlist-video-list-renderer #contents>ytd-playlist-video-renderer', {doc: rootEl}); const list = []; for (const el of els) { const explicitSubjectEl = el.querySelector('#meta'); const insertionPositionEl = el.querySelector('#metadata'); const titleAEl = explicitSubjectEl.querySelector('a#video-title-link,a#video-title'); const bylineContainerEl = explicitSubjectEl.querySelector('#byline-container'); const userAEl = bylineContainerEl.querySelector('a'); const viewEl = bylineContainerEl.querySelector('#video-info>span'); const durationEl = el.querySelector('.ytd-thumbnail-overlay-time-status-renderer .yt-badge-shape__text'); const videoUrl = titleAEl.href; const videoId = urlUtil.getUrlVideoId(videoUrl); const title = titleAEl.textContent.trim(); const userName = userAEl.textContent.trim(); const userUrl = userAEl.href; const userId = urlUtil.getUrlUserId(userUrl); let channelId = null; if (userId == null) { channelId = urlUtil.getUrlChannelId(userUrl); } const viewTxt = viewEl.textContent.trim(); const view = strUtil.parseView(viewTxt); const durationTxt = durationEl ? durationEl.textContent.trim() : null; const duration = durationTxt ? strUtil.timeStringToSeconds(durationTxt) : -1; list.push({ el, title, view, userId, videoUrl, userName, videoId, duration, durationTxt, insertionPositionEl, explicitSubjectEl, userUrl, channelId }); } return list; }; const checkVideoList = async () => { const list = await getVideoList(); for (const v of list) { eventEmitter.send('event:插入屏蔽按钮', v); } }; const intervalExecutor = new IntervalExecutor(checkVideoList, { processTips: true, intervalName: '稍后再看or赞过的视频列表' }); var watchLaterAwesomeVideoPage = { isUrlPage: isUrlPage$1, intervalExecutor }; const isUrlPage = (url) => { return url.includes('//www.youtube.com/post/') }; const getCommentList = async () => { const elList = await elUtil.findElements('#contents>ytd-comment-thread-renderer.style-scope.ytd-item-section-renderer'); return pageCommon.extractCommentList(elList); }; const intervalPostCommentsListExecutor = new IntervalExecutor(async () => { const list = await getCommentList(); for (const contentData of list) { comments_shielding.shieldingCommentDecorated(contentData).catch((list) => { list.forEach(v => eventEmitter.send('event:插入屏蔽按钮', v)); }); } }, {processTips: true, intervalName: '帖子页'}); var postPage = { isUrlPage, intervalPostCommentsListExecutor }; const staticRoute = (title, url) => { const parseUrl = urlUtil.parseUrl(url); console.log('静态路由', title, url, parseUrl); if (homePage.isHomeUrlPage()) { console.log('youtube首页'); homePage.intervalCheckHomeVideoList.start(); } if (playerPage.isUrlPage(url)) { console.log('播放页or直播页'); playerPage.intervalCheckPlayerVideoList.start(); playerPage.intervalCheckCommentList.start(); playLivePage.intervalChatMsgListBlockExecutor.start(); playerPage.addShieldButton(); playerPage.checkRightVideoListAd(); } if (userSpacePage.isUserSpacePage(url)) { console.log('用户空间主页'); userSpacePage.run(); } if (searchPage.isUrlPage(url)) { console.log('搜索页'); searchPage.intervalCheckSearchVideoList.start(); searchPage.addShieldButton(); } if (gamingPage.isUrlPage(url)) { console.log('游戏页'); gamingPage.intervalCheckGamingVideoList.start(); } if (liveHomePage.isUrlPage(url)) { console.log('直播首页'); liveHomePage.removeLiveHomeTopBanner(); liveHomePage.intervalLiveListExecutor.start(); } if (channelPage.isUrlPage(url)) { console.log('频道页'); channelPage.dynamicRun(parseUrl); } if (learningPage.isUrlPage(url)) { console.log('学习页'); learningPage.intervalLearningListExecutor.start(); } if (sportsPage.isUrlPage(url)) { console.log('体育页'); sportsPage.intervalSportsListExecutor.start(); } if (fashionPage.isUrlPage(url)) { console.log('时尚与美容页'); fashionPage.intervalFashionListExecutor.start(); } if (podcastsPage.isUrlPage(url)) { console.log('博客页'); podcastsPage.intervalPodcastsListExecutor.start(); } if (hashTagPage.isUrlPage(url)) { console.log('hashtag页面'); hashTagPage.intervalTagVideoListExecutor.start(); } if (coursesPage.isUrlPage(url)) { console.log('课程页面'); coursesPage.intervalCheckCourseList.start(); } if (newsPage.isUrlPage(url)) { console.log('新闻页面'); newsPage.intervalNewsVideoListExecutor.start(); } if (historyPage.isUrlPage(url)) { historyPage.intervalHistoryVideoListExecutor.start(); } if (watchLaterAwesomeVideoPage.isUrlPage(url)) { watchLaterAwesomeVideoPage.intervalExecutor.start(); } if (postPage.isUrlPage(url)) { console.log('帖子页'); postPage.intervalPostCommentsListExecutor.start(); } userSpacePage.addShieldButton(); }; const dynamicRoute = (title, url) => { const parseUrl = urlUtil.parseUrl(url); console.log('动态路由', title, url, parseUrl); if (playerPage.isUrlPage(url)) { playerPage.intervalCheckPlayerVideoList.start(); playerPage.intervalCheckCommentList.start(); playLivePage.intervalChatMsgListBlockExecutor.start(); playerPage.addShieldButton(); playerPage.checkRightVideoListAd(); } else { playerPage.intervalCheckPlayerVideoList.stop(); playerPage.intervalCheckCommentList.stop(); playLivePage.intervalChatMsgListBlockExecutor.stop(); } if (homePage.isHomeUrlPage()) { homePage.intervalCheckHomeVideoList.start(); homePage.startHomeShortsItemDisplay(); } else { homePage.intervalCheckHomeVideoList.stop(); } if (searchPage.isUrlPage(url)) { searchPage.intervalCheckSearchVideoList.start(); searchPage.addShieldButton(); } else { searchPage.intervalCheckSearchVideoList.stop(); } if (userSpacePage.isUserSpacePage(url)) { console.log('用户空间主页'); userSpacePage.addShieldButton(); } if (gamingPage.isUrlPage(url)) { gamingPage.intervalCheckGamingVideoList.start(); } else { gamingPage.intervalCheckGamingVideoList.stop(); } if (liveHomePage.isUrlPage(url)) { liveHomePage.removeLiveHomeTopBanner(); liveHomePage.intervalLiveListExecutor.start(); } else { liveHomePage.intervalLiveListExecutor.stop(); } if (channelPage.isUrlPage(url)) { channelPage.dynamicRun(parseUrl); userSpacePage.addShieldButton(); } else { channelPage.intervalChannelPageVideoAndLiveListExecutor.stop(); } if (learningPage.isUrlPage(url)) { learningPage.intervalLearningListExecutor.start(); } else { learningPage.intervalLearningListExecutor.stop(); } if (sportsPage.isUrlPage(url)) { sportsPage.intervalSportsListExecutor.start(); } else { sportsPage.intervalSportsListExecutor.stop(); } if (fashionPage.isUrlPage(url)) { fashionPage.intervalFashionListExecutor.start(); } else { fashionPage.intervalFashionListExecutor.stop(); } if (podcastsPage.isUrlPage(url)) { podcastsPage.intervalPodcastsListExecutor.start(); } else { podcastsPage.intervalPodcastsListExecutor.stop(); } if (hashTagPage.isUrlPage(url)) { hashTagPage.intervalTagVideoListExecutor.start(); } else { hashTagPage.intervalTagVideoListExecutor.stop(); } if (coursesPage.isUrlPage(url)) { coursesPage.intervalCheckCourseList.start(); } else { coursesPage.intervalCheckCourseList.stop(); } if (newsPage.isUrlPage(url)) { newsPage.intervalNewsVideoListExecutor.start(); } else { newsPage.intervalNewsVideoListExecutor.stop(); } if (historyPage.isUrlPage(url)) { historyPage.intervalHistoryVideoListExecutor.start(); } else { historyPage.intervalHistoryVideoListExecutor.stop(); } if (watchLaterAwesomeVideoPage.isUrlPage(url)) { watchLaterAwesomeVideoPage.intervalExecutor.start(); } else { watchLaterAwesomeVideoPage.intervalExecutor.stop(); } if (postPage.isUrlPage(url)) { postPage.intervalPostCommentsListExecutor.start(); } else { postPage.intervalPostCommentsListExecutor.stop(); } }; const staticRoutePageAfterLoad = (title, url) => { const parseUrl = urlUtil.parseUrl(url); console.log('页面加载完之后的静态路由', title, url, parseUrl); if (channelPage.isUrlPage(url)) { if (channelPage.isUserChannelPage(url)) { console.log('用户页的频道页'); return; } channelPage.dynamicRun(parseUrl); } }; var router = { staticRoute, dynamicRoute, staticRoutePageAfterLoad }; const addEventListenerUrlChange = (callback,timeout = 1000) => { let oldUrl = window.location.href; setInterval(() => { const newUrl = window.location.href; if (oldUrl === newUrl) return; oldUrl = newUrl; callback(newUrl, oldUrl, document.title); }, timeout); }; var watch = { addEventListenerUrlChange }; 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; } .el-horizontal-outside { display: flex; justify-content: space-between; align-items: center; }`; eventEmitter.on('event:mask_options_dialog_box', (data) => { const {userId, channelId, userName, videoId, userNameList} = data; const showList = []; if (userId) { showList.push({label: `用户id精确屏蔽=${userId}`, value: 'userId_precise'}); showList.push({label: '新标签跳转到用户主页', value: 'to_userId_precise'}); } if (channelId) { showList.push({label: `频道id精确屏蔽=${channelId}`, value: 'channelId_precise'}); showList.push({label: '新标签跳转到频道主页', value: 'to_channelId_precise'}); } if (userName) { showList.push({label: `用户名精确屏蔽=${userName}`, value: 'username_precise'}); } if (userNameList) { for (const n of userNameList) { showList.push({label: `用户名模糊屏蔽=${n}`, value: 'username_precise'}); } } if (videoId) { showList.push({label: `视频id精确屏蔽=${videoId}`, value: 'videoId_precise'}); showList.push({label: '新标签跳转到视频主页', value: 'to_videoId_precise'}); } if (userId && userName) { showList.push({label: `用户Id关联用户名屏蔽=${userId}|${userName}`, value: 'userIdAndUserName'}); } if (userId && channelId) { showList.push({label: `用户Id关联频道Id屏蔽=${userId}|${channelId}`, value: 'userIdAndChannelId'}); } if (userName && channelId) { showList.push({label: `用户名关联频道Id屏蔽=${userName}|${channelId}`, value: 'userNameAndChannelId'}); } eventEmitter.send('sheet-dialog', { title: "屏蔽选项", list: showList, optionsClick: (item) => { const {value} = item; let results; if (value === 'videoId_precise') { results = ruleUtil.addRule(videoId, value); } else if (value === 'userId_precise') { results = ruleUtil.addRule(userId, value); } else if (value === 'username_precise') { results = ruleUtil.addRule(userName, value); } else if (value === 'to_userId_precise') { GM_openInTab('https://www.youtube.com/' + userId); } else if (value === 'to_videoId_precise') { GM_openInTab('https://www.youtube.com/watch?v=' + videoId); } else if (value === 'to_channelId_precise') { GM_openInTab('https://www.youtube.com/channel/' + channelId); } else if (value === 'userIdAndUserName') { results = ruleUtil.addRelationRule('userId_username', `${userName}|${userName}`); } else if (value === 'userIdAndChannelId') { results = ruleUtil.addRelationRule('userId_channelId', `${userId}|${channelId}`); } else if (value === 'userNameAndChannelId') { results = ruleUtil.addRelationRule('username_channelId', `${userName}|${channelId}`); } else { eventEmitter.send('el-msg', "出现意外的选项值"); return } if (results) { const msg = results.res ? results.res : results.msg; eventEmitter.send('el-msg', msg).emit('event:刷新规则信息', false); } } }); }); console.log('油管内容屏蔽器脚本加载成功!'); router.staticRoute(document.title, document.location.href); window.addEventListener('DOMContentLoaded', () => { GM_addStyle(defCss); GM_addStyle(gzStyleCss); console.log('网页元素加载完成'); layout_init.init(); }); watch.addEventListenerUrlChange((newUrl, _oldUrl, title) => { router.dynamicRoute(title, newUrl); }); window.addEventListener('load', () => { console.log('页面加载完成'); router.staticRoutePageAfterLoad(document.title, location.href); }); document.addEventListener('keydown', function (event) { eventEmitter.emit('event-keydownEvent', event); if (event.key === getDrawerShortcutKeyGm()) { eventEmitter.send('event-drawer-show', null); } }); })(Vue);