function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, ("value" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); } function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } // ==UserScript== // @name Internet Content Filterer // @name:zh 网络内容过滤器 // @namespace Violentmonkey Scripts // @match *://*.weibo.com/* // @match *://*.weibo.cn/* // @match *://weibo.com/* // @match *://m.hupu.com/* // @match *://tieba.baidu.com/* // @match *://www.zhihu.com/* // @match *://www.zhihu.com // @exclude *://weibo.com/tv* // @grant GM.getValue // @grant GM.setValue // @grant GM.deleteValue // @grant GM.listValues // @version 3.8.1 // @author no one // @description block users' posts and comments whom you blocked // @description:zh 屏蔽特定用户的帖子和评论 // @run-at document-start // @require https://update.greasyfork.icu/scripts/472943/1320613/Itsnotlupus%27%20MiddleMan.js // @downloadURL none // ==/UserScript== /* eslint-disable max-classes-per-file,no-param-reassign,no-console */ /* jshint esversion: 6 */ // eslint-disable-next-line func-names (async function (_dec, _dec2, _dec3, _dec4, _class, _WeiboFilter, _apiBlackList, _dec5, _class2, _HupuFilter, _dec6, _dec7, _dec8, _class3, _TiebaFilter, _dec9, _dec10, _dec11, _class4, _ZhihuFilter, _DomainStore) { /* 添加样式 */ const BlockButtonClass = 'block-button'; const cssByCls = cls => `.${cls}`; const css = ` #add_ngList_btn { position: fixed; bottom: 2rem; left: 1rem; width: 2rem; height: 2rem; border-radius: 50%; border: 1px solid rgba(0, 0, 0, 0.5); background: white; display: flex; align-items: center; justify-content: center; cursor: pointer !important; z-index: 100; } #add_ngList_btn::before { content: ''; position: absolute; width: 16px; height: 2px; background: rgba(0, 0, 0, 0.5); top: calc(50% - 1px); left: calc(50% - 8px); } #add_ngList_btn::after { content: ''; position: absolute; height: 16px; width: 2px; background: rgba(0, 0, 0, 0.5); top: calc(50% - 8px); left: calc(50% - 1px); } .my-dialog__wrapper { position: fixed; top: 0; right: 0; bottom: 0; left: 0; overflow: auto; margin: 0; z-index: 10000; background: rgba(0, 0, 0, 0.3); display: none; } .my-dialog { position: relative; background: #FFFFFF; border-radius: 2px; box-shadow: 0 1px 3px rgb(0 0 0 / 30%); box-sizing: border-box; width: 50%; transform: none; left: 0; margin: 0 auto; } .my-dialog .my-dialog__header { border-bottom: 1px solid #e4e4e4; padding: 14px 16px 10px 16px; } .my-dialog__title { line-height: 24px; font-size: 18px; color: #303133; } .my-dialog__headerbtn { position: absolute; top: 20px; right: 20px; padding: 0; background: transparent; border: none; outline: none; cursor: pointer; font-size: 16px; width: 12px; height: 12px; transform: rotateZ(45deg); } .my-dialog .my-dialog__header .my-dialog__headerbtn { right: 16px; top: 16px; } .my-dialog__headerbtn .my-dialog__close::before { content: ''; position: absolute; width: 12px; height: 1.5px; background: #909399; top: calc(50% - 0.75px); left: calc(50% - 6px); border-radius: 2px; } .my-dialog__headerbtn:hover .my-dialog__close::before { background: #1890ff; } .my-dialog__headerbtn .my-dialog__close::after { content: ''; position: absolute; height: 12px; width: 1.5px; background: #909399; top: calc(50% - 6px); left: calc(50% - 0.75px); border-radius: 2px; } .my-dialog__headerbtn:hover .my-dialog__close::after { background: #1890ff; } .my-dialog__body { padding: 30px 20px; color: #606266; font-size: 14px; word-break: break-all; } .my-dialog__footer { padding: 20px; padding-top: 10px; text-align: right; box-sizing: border-box; } .my-dialog .my-dialog__footer { padding: 0px 16px 24px 16px; margin-top: 40px; } #ngList { display: flex; flex-wrap: wrap; justify-content: flex-start; max-height: 480px; overflow-y: scroll; } ${cssByCls(BlockButtonClass)} { cursor: pointer; height: 12px; width: 12px; margin-left: 1px; float: inherit; background: white; border-width: 0; padding: 0; line-height:0px; transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1); transformOrigin: '50% bottom; } ${cssByCls(BlockButtonClass)}:hover { transform: translateY(0) scale(1.5); } .close-icon { width: 12px; height: 12px; border-radius: 50%; display: inline-block; position: relative; transform: rotateZ(45deg); margin-left: 8px; cursor: pointer; } .close-icon:hover { background: #409eff; } .close-icon::before { content: ''; position: absolute; width: 8px; height: 2px; background: #409eff; top: calc(50% - 1px); left: calc(50% - 4px); border-radius: 2px; } .close-icon:hover::before { background: #fff; } .close-icon::after { content: ''; position: absolute; height: 8px; width: 2px; background: #409eff; top: calc(50% - 4px); left: calc(50% - 1px); border-radius: 2px; } .close-icon:hover::after { background: #fff; } .ng_item { background-color: #ecf5ff; display: inline-flex; align-items: center; padding: 0 10px; font-size: 12px; color: #409eff; border: 1px solid #d9ecff; border-radius: 4px; box-sizing: border-box; white-space: nowrap; height: 28px; line-height: 26px; margin-left: 12px; margin-top: 8px; } .input_container { display: flex; align-items: center; margin-bottom: 12px; } .el-input { position: relative; font-size: 14px; display: inline-block; width: 100%; } .el-input__inner { -webkit-appearance: none; background-color: #fff; background-image: none; border-radius: 4px; border: 1px solid #dcdfe6; box-sizing: border-box; color: #606266; display: inline-block; font-size: inherit; height: 40px; line-height: 40px; outline: none; padding: 0 15px; transition: border-color .2s cubic-bezier(.645, .045, .355, 1); width: 100%; cursor: pointer; font-family: inherit; } .el-button { display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; background: #fff; border: 1px solid #dcdfe6; color: #606266; -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: 12px 20px; font-size: 14px; border-radius: 4px; } .el-button:focus, .el-button:hover { color: #409eff; border-color: #c6e2ff; background-color: #ecf5ff; } .el-button:active { color: #3a8ee6; border-color: #3a8ee6; outline: none; } .input_container .el-input { margin-right: 12px; } .tips { margin-top: 24px; font-size: 12px; color: #F56C6C; } `; /* 按钮模板 */ const btnTemp = ` 屏蔽词设置 `; const svgIcon = ` `; /* 添加样式 */ function addStyle(cssStyle) { if (!cssStyle) return; const head = document.querySelector('head'); const style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = cssStyle; head.appendChild(style); } const defaultNameFn = elem => elem.innerText || elem.getAttribute('aria-label'); function createButtonElement(name) { // eslint-disable-next-line name = name.trim().replace(/^@/, '').replace(':', ''); const wrapper = document.createElement('div'); wrapper.innerHTML = svgIcon; wrapper.className = BlockButtonClass; wrapper.setAttribute('user-name', name); return wrapper; } function newListAndUser(list, user, content = undefined) { return { listSelector: list, userSelector: user, contentSelector: content }; } function injectButton({ listSelector, userSelector, elementButtonFunc, doc = document, subListSelector, subUserSelector, nameFn = defaultNameFn }) { const list = doc.querySelectorAll(listSelector); list.forEach(element => { if (elementButtonFunc) elementButtonFunc(element); let user = element.querySelector(userSelector); if (!user) { return; } if (element.querySelector(`${cssByCls(BlockButtonClass)}`)) { return; } const btn = createButtonElement(nameFn(user)); while (user.parentNode !== element) { if (user.parentNode.childElementCount > 1) { user.parentNode.appendChild(btn); break; } user = user.parentNode; } if (user.parentNode === element) { user.parentNode.appendChild(btn); } }); list.forEach(element => { if (subListSelector && subUserSelector) { injectButton({ listSelector: subListSelector, userSelector: subUserSelector, doc: element, nameFn: nameFn }); } }); } var _filters = /*#__PURE__*/new WeakMap(); var _proxyOpt = /*#__PURE__*/new WeakMap(); var _registered = /*#__PURE__*/new WeakMap(); class WebsiteFilter { /** * @param {Array.<{listSelector: String, userSelector: String, contentSelector: String}>} selectors * @param {Document | Element} root * @param nameFn * @param contentFn */ removeElements(selectors, root = document, nameFn = defaultNameFn, contentFn = e => '') { if (selectors.length < 1) { return; } const selector = selectors[0]; const list = root.querySelectorAll(selector.listSelector); list.forEach(element => { const user = element.querySelector(selector.userSelector); if (!user) { return; } const contentElement = element.querySelector(selector.contentSelector); const content = contentFn(contentElement); const poster = nameFn(user); if (this.inBlackList(poster, content)) { element.parentNode.removeChild(element); } else { this.removeElements(selectors.slice(1), element, nameFn); } }); } inBlackList(name, content = '') { return this.store.hasUser(name) || this.store.matchPattern(content); } /** * * @param {DomainStore} store */ constructor(store) { _classPrivateFieldInitSpec(this, _filters, []); _classPrivateFieldInitSpec(this, _proxyOpt, { requestRouters: [], responseRouters: [] }); _classPrivateFieldInitSpec(this, _registered, false); /** * @type DomainStore */ _defineProperty(this, "store", void 0); this.store = store; this.addHooks(...Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter(name => Object.getPrototypeOf(this)[name].hookMeta).reduce((hooks, name) => { const fn = this[name].bind(this); fn.hookMeta = this[name].hookMeta; hooks.push(fn); return hooks; }, [])); _classPrivateFieldSet(_filters, this, Object.getOwnPropertyNames(Object.getPrototypeOf(this)).reduce((fns, name) => { const method = this[name]; if (method.filterMeta) { const fn = method.bind(this); fn.hookMeta = this[name].hookMeta; fns.push(fn); } return fns; }, [])); } /** * @callback onRequest * @param {Request} request * @property {{pattern: RegExp, type: string}} hookMeta */ /** * @callback onResponse * @param {string} url * @param {Object} res * @property {{pattern: RegExp, type: string}} hookMeta */ /** * * @param {onRequest | onResponse} hooks */ addHooks(...hooks) { hooks.forEach(hook => { switch (hook.hookMeta.type) { case 'request': _classPrivateFieldGet(_proxyOpt, this).requestRouters.push(hook); break; case 'response': _classPrivateFieldGet(_proxyOpt, this).responseRouters.push(hook); break; } }); } requestHandler(hook) { return { async requestHandler(request) { return hook(request); } }; } responseHandler(hook) { return { async responseHandler(request, response, error) { if (error) { throw error; } const res = await response.json(); const url = request.url; hook(url, res); return Response.json(res); } }; } render() { _classPrivateFieldGet(_filters, this).forEach(f => { if (!_classPrivateFieldGet(_registered, this)) { const opt = _classPrivateFieldGet(_proxyOpt, this); opt.requestRouters.forEach(router => { middleMan.addHook(router.hookMeta.pattern, this.requestHandler(router)); }); opt.responseRouters.forEach(router => { middleMan.addHook(router.hookMeta.pattern, this.responseHandler(router)); }); _classPrivateFieldSet(_registered, this, true); } f(); }); } } function filterFunc(target, name, descriptor) { const fn = descriptor.value; fn.filterMeta = true; return descriptor; } function reqHook(regex) { return (target, name, descriptor) => { descriptor.value.hookMeta = { type: 'request', pattern: regex }; return descriptor; }; } function respHook(pattern) { return (target, name, descriptor) => { descriptor.value.hookMeta = { type: 'response', pattern: pattern }; return descriptor; }; } let WeiboFilter = (_dec = reqHook(/female_version|rum\/events/), _dec2 = respHook(/\/friendstimeline/), _dec3 = respHook(/\/searchBand/), _dec4 = respHook(/\/buildComments/), (_class = (_apiBlackList = /*#__PURE__*/new WeakMap(), (_WeiboFilter = class WeiboFilter extends WebsiteFilter { // 接口黑名单 mockRequests() { return Response.json({}, { status: 200 }); } constructor(store) { super(store); _classPrivateFieldInitSpec(this, _apiBlackList, ['/female_version.mp3', '/intake/v2/rum/events']); this.hideTrends(); } async filterSearchResults() { const selector = { cards: 'div.card-wrap', cardUser: 'div.info a.name', retweetedCards: 'div.card-wrap div.card-comment', retweetedCardUser: 'div.con a.name', comments: 'div.card-review', commentUser: 'div.content a.name' }; injectButton({ listSelector: selector.cards, userSelector: selector.cardUser, elementButtonFunc: WeiboFilter.createCommentButton }); injectButton({ listSelector: selector.retweetedCards, userSelector: selector.retweetedCardUser }); this.removeElements([{ listSelector: selector.cards, userSelector: selector.cardUser }, { listSelector: selector.comments, userSelector: selector.commentUser }]); this.removeElements([{ listSelector: selector.cards, userSelector: selector.retweetedCardUser }]); } async filterFeeds() { const selector = { cardListSelector: 'div.vue-recycle-scroller__item-view', cardUserSelector: 'div.Feed_body_3R0rO a.ALink_default_2ibt1', commentListSelector: 'div.wbpro-list', commentUserSelector: 'div.con1.woo-box-item-flex div.text a.ALink_default_2ibt1', replyListSelector: 'div.item2', replyUserSelector: 'div.con2 a.ALink_default_2ibt1' }; this.removeElements([newListAndUser(selector.cardListSelector, selector.cardUserSelector), newListAndUser(selector.commentListSelector, selector.commentUserSelector), newListAndUser(selector.replyListSelector, selector.replyUserSelector)]); injectButton({ listSelector: selector.cardListSelector, userSelector: selector.cardUserSelector }); const feedWrappers = document.querySelectorAll(selector.cardListSelector); feedWrappers.forEach(feed => { injectButton({ listSelector: selector.commentListSelector, userSelector: selector.commentUserSelector, doc: feed, subListSelector: selector.replyListSelector, subUserSelector: selector.replyUserSelector }); }); } async filterReplies() { const reply = document.querySelector('div.ReplyModal_scroll3_2kADQ'); if (!reply) { return; } const selector = { commentListSelector: 'div.wbpro-list', commentUserSelector: 'div.con1.woo-box-item-flex a.ALink_default_2ibt1', root: reply, replyListSelector: 'div.vue-recycle-scroller__item-view', replyUserSelector: 'div.con2 a.ALink_default_2ibt1' }; this.removeElements([{ listSelector: selector.commentListSelector, userSelector: selector.commentUserSelector }, { listSelector: selector.replyListSelector, userSelector: selector.replyUserSelector }], reply); injectButton({ listSelector: selector.commentListSelector, userSelector: selector.commentUserSelector, doc: reply, subListSelector: selector.replyListSelector, subUserSelector: selector.replyUserSelector }); } static async createCommentButton(card) { injectButton({ listSelector: 'div.card-together div.list div.card-review', userSelector: 'a.name', doc: card }); } async filterDetailComments() { const selector = { commentListSelector: 'div.vue-recycle-scroller__item-view', commentUserSelector: 'div.con1.woo-box-item-flex div.text a.ALink_default_2ibt1', replyListSelector: 'div.item2', replyUserSelector: 'div.con2 a.ALink_default_2ibt1' }; this.removeElements([{ listSelector: selector.commentListSelector, userSelector: selector.commentUserSelector }, { listSelector: selector.replyListSelector, userSelector: selector.replyUserSelector }]); injectButton({ listSelector: selector.commentListSelector, userSelector: selector.commentUserSelector, subListSelector: selector.replyListSelector, subUserSelector: selector.replyUserSelector }); } async hideTrends() { let trend = document.querySelector('div.main-side'); if (trend) trend.style.display = 'none'; if (trend = document.querySelector('div.Main_side_i7Vti')) trend.style.display = 'none'; } async createRetweetButton() { this.removeElements([newListAndUser('div.vue-recycle-scroller__item-view', 'div.retweet.Feed_retweet_JqZJb a.ALink_default_2ibt1')]); injectButton({ listSelector: 'div.retweet.Feed_retweet_JqZJb', userSelector: 'a.ALink_default_2ibt1' }); } /** *@param {Array.<{user: {screen_name: string}, text: string}>} comments * */ filterComments(comments) { return comments.reduce((filtered, comment) => { var _comment$user; const myText = comment.text || ''; const ngWordInMyText = this.inBlackList((_comment$user = comment.user) === null || _comment$user === void 0 ? void 0 : _comment$user.screen_name, myText); if (!ngWordInMyText) { filtered.push(comment); } return filtered; }, []); } /** * @typedef Status * @type {object} * @property {?User} user * @property {string} text * @property {?Status} retweeted_status * * @typedef User * @type {object} * @property {boolean} following * @property {string} screen_name * * * @param {Status[]} statuses * */ filterStatuses(statuses) { return statuses.reduce((acc, cur) => { if (cur.user.following) { var _cur$user; const myText = cur.text || ''; const ngWordInMyText = this.inBlackList((_cur$user = cur.user) === null || _cur$user === void 0 ? void 0 : _cur$user.screen_name, myText); if (!ngWordInMyText) { if (cur.retweeted_status) { var _cur$retweeted_status; const oriText = cur.retweeted_status.text || ''; const ngWordInOriText = this.inBlackList((_cur$retweeted_status = cur.retweeted_status) === null || _cur$retweeted_status === void 0 || (_cur$retweeted_status = _cur$retweeted_status.user) === null || _cur$retweeted_status === void 0 ? void 0 : _cur$retweeted_status.screen_name, oriText); if (ngWordInOriText) return acc; } acc.push(cur); } } return acc; }, []); } filterSearchBand(searchBands) { return searchBands.reduce((acc, cur) => { if (!this.inBlackList(cur.word)) { acc.push(cur); } return acc; }, []); } onFriendTimeline(url, res) { if (url.includes('m.weibo.cn')) { res.data.statuses = this.filterStatuses(res.data.statuses); } else { res.statuses = this.filterStatuses(res.statuses); console.log(`filtered url: ${url}, users: ${res.statuses.reduce((pre, cur) => { pre.push(cur.user.screen_name); return pre; }, [])}`); } } onSearchBand(url, res) { res.data.realtime = this.filterSearchBand(res.data.realtime); } hook_buildComments(url, res) { res.data = this.filterComments(res.data); } }, _defineProperty(_WeiboFilter, "host", 'weibo.com'), _WeiboFilter)), (_applyDecoratedDescriptor(_class.prototype, "mockRequests", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "mockRequests"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "filterSearchResults", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterSearchResults"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "filterFeeds", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterFeeds"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "filterReplies", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterReplies"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "filterDetailComments", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterDetailComments"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "hideTrends", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "hideTrends"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "createRetweetButton", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "createRetweetButton"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "onFriendTimeline", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, "onFriendTimeline"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "onSearchBand", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, "onSearchBand"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "hook_buildComments", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, "hook_buildComments"), _class.prototype)), _class)); let HupuFilter = (_dec5 = respHook(/\/bbs-reply-detail/), (_class2 = (_HupuFilter = class HupuFilter extends WebsiteFilter { async filterHupuComments() { const selector = { commentsSelector: 'div.index_discuss-card__Nd4MK.hp-m-discuss-card.post-card', userSelector: 'p.discuss-card__user span.discuss-card__username', repliesSelector: 'div.index_discuss-card__Nd4MK.hp-m-discuss-card.discuss-card', quotesSelector: 'div.discuss-card__quote-container-quote', quoteUserSelector: 'div.discuss-card__quote-container-quote span.discuss-card__quote-container-discusser' }; this.removeElements([{ listSelector: selector.commentsSelector, userSelector: selector.userSelector }]); injectButton({ listSelector: selector.commentsSelector, userSelector: selector.userSelector }); this.removeElements([newListAndUser(selector.commentsSelector, selector.quoteUserSelector)]); injectButton({ listSelector: selector.quotesSelector, userSelector: selector.quoteUserSelector }); this.removeElements([newListAndUser(selector.repliesSelector, selector.userSelector)]); injectButton({ listSelector: selector.repliesSelector, userSelector: selector.userSelector }); } hupuRouter(url, res) { function filterHupuReplies(replies) { return replies.reduce((filtered, reply) => { const user = reply.user.username; if (!this.inBlackList(user)) { filtered.push(reply); } return filtered; }, []); } res.data.replies = filterHupuReplies(res.data.replies); } }, _defineProperty(_HupuFilter, "host", 'hupu.com'), _HupuFilter), (_applyDecoratedDescriptor(_class2.prototype, "filterHupuComments", [filterFunc], Object.getOwnPropertyDescriptor(_class2.prototype, "filterHupuComments"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "hupuRouter", [_dec5], Object.getOwnPropertyDescriptor(_class2.prototype, "hupuRouter"), _class2.prototype)), _class2)); let TiebaFilter = (_dec6 = respHook(/\/p\/totalComment/), _dec7 = respHook(/\/topicList/), _dec8 = respHook(/\/suggestion/), (_class3 = (_TiebaFilter = class TiebaFilter extends WebsiteFilter { constructor(store) { super(store); this.hideLoginPopup(); } async hideLoginPopup() { let login = null; while (!login) { login = document.querySelector('div.tieba-custom-pass-login'); if (login) { const ob = new MutationObserver(mutations => { const record = mutations[0]; if (record.target.style.display === 'block') { login.style.display = 'none'; } }); ob.observe(login, { attributeFilter: ['style'] }); break; } await sleep(10); } } async filterTiebaThreadListComments() { const selector = { threadsSelector: 'li.j_thread_list.clearfix.thread_item_box', threadUserSelector: 'span.tb_icon_author' }; const fn = user => { var _user$getAttribute; return (_user$getAttribute = user.getAttribute('title')) === null || _user$getAttribute === void 0 ? void 0 : _user$getAttribute.replace('主题作者: ', ''); }; this.removeElements([newListAndUser(selector.threadsSelector, selector.threadUserSelector)], document, fn); injectButton({ listSelector: selector.threadsSelector, userSelector: selector.threadUserSelector, nameFn: fn }); } async filterTiebaThreadComments() { const selector = { commentsSelector: 'div.l_post.l_post_bright.j_l_post.clearfix', commentUserSelector: 'div.d_author li.d_name a.p_author_name.j_user_card', repliesSelector: 'div.j_lzl_c_b_a.core_reply_content li.lzl_single_post.j_lzl_s_p', replyUserSelector: 'div.lzl_cnt a.at.j_user_card' }; this.removeElements([newListAndUser(selector.commentsSelector, selector.commentUserSelector), newListAndUser(selector.repliesSelector, selector.replyUserSelector)]); injectButton({ listSelector: selector.commentsSelector, userSelector: selector.commentUserSelector, subListSelector: selector.repliesSelector, subUserSelector: selector.replyUserSelector }); } async hideRightBar() { const rightBar = document.querySelector('div.right_section.right_bright'); if (!rightBar) { return; } rightBar.parentNode.removeChild(rightBar); } hookReplies(url, res) { res.data = filterTiebaReplies(res.data); } /** * @typedef Topic * @type {object} * @property {string[]} topic_list * * @param {string} url * @param {object} res * @param {{user_his_topic: Topic, sug_topic: Topic, bang_topic: Topic, manual_topic: Topic}} res.data * * */ hookTopicList(url, res) { res.data.user_his_topic.topic_list = []; res.data.sug_topic.topic_list = []; res.data.bang_topic.topic_list = []; res.data.manual_topic.topic_list = []; } /** * @param {string} url * @param {{hottopic_list: object}} res * */ hookSuggestion(url, res) { res.hottopic_list.search_data = []; } }, _defineProperty(_TiebaFilter, "host", 'tieba.baidu.com'), _TiebaFilter), (_applyDecoratedDescriptor(_class3.prototype, "hideLoginPopup", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "hideLoginPopup"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "filterTiebaThreadListComments", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "filterTiebaThreadListComments"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "filterTiebaThreadComments", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "filterTiebaThreadComments"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hideRightBar", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "hideRightBar"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookReplies", [_dec6], Object.getOwnPropertyDescriptor(_class3.prototype, "hookReplies"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookTopicList", [_dec7], Object.getOwnPropertyDescriptor(_class3.prototype, "hookTopicList"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookSuggestion", [_dec8], Object.getOwnPropertyDescriptor(_class3.prototype, "hookSuggestion"), _class3.prototype)), _class3)); let ZhihuFilter = (_dec9 = reqHook(/zhihu-web-analytics/), _dec10 = respHook(/\/root_comment|\/child_comment/), _dec11 = respHook(/questions\/\d+\/feeds/), (_class4 = (_ZhihuFilter = class ZhihuFilter extends WebsiteFilter { interceptAnalytics() { return Response.json({}, { status: 200 }); } async filterComments() { const selector = { comments: 'div.css-18ld3w0 > div[data-id]', commentUser: 'div.css-jp43l4 a.css-10u695f', commentContent: 'div.css-jp43l4 div.CommentContent.css-1jpzztt', replies: 'div[data-id]', replyUser: 'div.css-1tww9qq a.css-10u695f', replyContent: 'div.css-1tww9qq div.CommentContent.css-1jpzztt' }; const fn = element => { const href = element.getAttribute('href'); return href.split('/').at(-1); }; this.removeElements([newListAndUser(selector.comments, selector.commentUser, selector.commentContent), newListAndUser(selector.replies, selector.replyUser, selector.replyContent)], document, fn, ZhihuFilter.contentFunc); injectButton({ listSelector: selector.comments, userSelector: selector.commentUser, subListSelector: selector.replies, subUserSelector: selector.replyUser, nameFn: fn }); } static contentFunc(element) { var _element$querySelecto; return element ? element.textContent + ((_element$querySelecto = element.querySelector('img')) === null || _element$querySelecto === void 0 ? void 0 : _element$querySelecto.getAttribute('alt')) : ''; } async filterAnswerComments() { const selector = { comments: 'div.css-16zdamy div[data-id]', commentUser: 'div.css-1tww9qq a.css-10u695f', commentContent: 'div.CommentContent.css-1jpzztt' }; const fn = element => { const href = element.getAttribute('href'); return href.split('/').at(-1); }; this.removeElements([newListAndUser(selector.comments, selector.commentUser, selector.commentContent)], document, fn, ZhihuFilter.contentFunc); injectButton({ listSelector: selector.comments, userSelector: selector.commentUser, subListSelector: selector.replies, subUserSelector: selector.replyUser, nameFn: fn }); } async filterAnswers() { const selector = { answers: 'div.List-item', answerUser: 'div.ContentItem.AnswerItem' }; const fn = e => { const metaText = e.getAttribute('data-za-extra-module'); const meta = JSON.parse(metaText); return meta.card.content.author_member_hash_id; }; this.removeElements([{ listSelector: selector.answers, userSelector: selector.answerUser }], document, fn); injectButton({ listSelector: selector.answers, userSelector: selector.answerUser, nameFn: fn }); } async filterRecommends() { const selector = { listSelector: 'div.Card.TopstoryItem.TopstoryItem-isRecommend', userSelector: 'div.ContentItem.AnswerItem' }; const fn = e => { const metaText = e.getAttribute('data-za-extra-module'); const meta = JSON.parse(metaText); return meta.card.content.author_member_hash_id; }; this.removeElements([{ listSelector: selector.listSelector, userSelector: selector.userSelector }], document, fn); injectButton({ listSelector: selector.listSelector, userSelector: selector.userSelector, nameFn: fn }); } async filterQuestions() {} /** * * @param url * @param {{data: Object}} res * @property {{author: Object}[]} data * @property {{name: string, id: string}} author */ hookComments(url, res) { const comments = res.data; res.data = comments.filter(comment => { const name = comment.author.id; const content = comment.content; return !this.inBlackList(name, content); }); } /** * @param url * @param res */ hookAnswers(url, res) { const answers = res.data; res.data = answers.filter(answer => { var _answer$target, _answer$target2; const name = (_answer$target = answer.target) === null || _answer$target === void 0 || (_answer$target = _answer$target.author) === null || _answer$target === void 0 ? void 0 : _answer$target.name; const content = (_answer$target2 = answer.target) === null || _answer$target2 === void 0 ? void 0 : _answer$target2.content; return !this.inBlackList(name, content); }); } }, _defineProperty(_ZhihuFilter, "host", 'zhihu.com'), _ZhihuFilter), (_applyDecoratedDescriptor(_class4.prototype, "interceptAnalytics", [_dec9], Object.getOwnPropertyDescriptor(_class4.prototype, "interceptAnalytics"), _class4.prototype), _applyDecoratedDescriptor(_class4.prototype, "filterComments", [filterFunc], Object.getOwnPropertyDescriptor(_class4.prototype, "filterComments"), _class4.prototype), _applyDecoratedDescriptor(_class4.prototype, "filterAnswerComments", [filterFunc], Object.getOwnPropertyDescriptor(_class4.prototype, "filterAnswerComments"), _class4.prototype), _applyDecoratedDescriptor(_class4.prototype, "filterAnswers", [filterFunc], Object.getOwnPropertyDescriptor(_class4.prototype, "filterAnswers"), _class4.prototype), _applyDecoratedDescriptor(_class4.prototype, "filterRecommends", [filterFunc], Object.getOwnPropertyDescriptor(_class4.prototype, "filterRecommends"), _class4.prototype), _applyDecoratedDescriptor(_class4.prototype, "filterQuestions", [filterFunc], Object.getOwnPropertyDescriptor(_class4.prototype, "filterQuestions"), _class4.prototype), _applyDecoratedDescriptor(_class4.prototype, "hookComments", [_dec10], Object.getOwnPropertyDescriptor(_class4.prototype, "hookComments"), _class4.prototype), _applyDecoratedDescriptor(_class4.prototype, "hookAnswers", [_dec11], Object.getOwnPropertyDescriptor(_class4.prototype, "hookAnswers"), _class4.prototype)), _class4)); /** * @param {Object} data * @property {{user_nickname_v2: string}[]} user_list * @property {{comment_info: Object[]}[]} comment_list * @property {{show_nickname: string}} comment_info * */ function filterTiebaReplies(data) { const userMap = new Map(); for (const user of Object.values(data.user_list)) { userMap.set(user.user_nickname_v2, true); } const comments = data.comment_list; data.comment_list = Object.entries(comments).reduce((filtered, [postId, comment]) => { let replies = comment.comment_info; replies = replies.filter(reply => !this.inBlackList(reply.show_nickname)); comment.comment_list_num = replies.length; comment.comment_info = replies; if (comment.comment_list_num > 0) { filtered = { ...filtered, [postId]: comment }; } return filtered; }, {}); return data; } var _blackUsers = /*#__PURE__*/new WeakMap(); var _excludePatterns = /*#__PURE__*/new WeakMap(); var _DomainStore_brand = /*#__PURE__*/new WeakSet(); class DomainStore { static async init() { let list = await this.loadBlackList(); const val = await GM.getValue(DomainStore.patternKey()); let patterns = []; if (typeof val === 'string') { patterns = JSON.parse(val); } return new DomainStore(list, patterns); } /** * @type {Map.} * */ get userList() { return _classPrivateFieldGet(_blackUsers, this).keys(); } get patternList() { return _classPrivateFieldGet(_excludePatterns, this); } matchPattern(text) { return _classPrivateFieldGet(_excludePatterns, this).some(pattern => { return text.includes(pattern); }); } addPattern(text) { let s = new Set(_classPrivateFieldGet(_excludePatterns, this)).add(text); _classPrivateFieldSet(_excludePatterns, this, [...s]); _assertClassBrand(_DomainStore_brand, this, _flush).call(this); } removePattern(text) { _classPrivateFieldSet(_excludePatterns, this, _classPrivateFieldGet(_excludePatterns, this).filter(pattern => { return pattern !== text; })); _assertClassBrand(_DomainStore_brand, this, _flush).call(this); } hasUser(name) { return _classPrivateFieldGet(_blackUsers, this).has(name); } addUser(name) { _classPrivateFieldGet(_blackUsers, this).set(name, true); _assertClassBrand(_DomainStore_brand, this, _flush).call(this); } removeUser(name) { _classPrivateFieldGet(_blackUsers, this).delete(name); _assertClassBrand(_DomainStore_brand, this, _flush).call(this); } static userKey() { return `${DomainStore.domainKeyPrefix}:users`; } static patternKey() { return `${DomainStore.domainKeyPrefix}:patterns`; } /** * @param {string[]} list * */ constructor(users, patterns) { _classPrivateMethodInitSpec(this, _DomainStore_brand); _classPrivateFieldInitSpec(this, _blackUsers, void 0); /** * @type {string[]} */ _classPrivateFieldInitSpec(this, _excludePatterns, []); _classPrivateFieldSet(_blackUsers, this, users.reduce((map, user) => { return map.set(user, true); }, new Map())); _classPrivateFieldSet(_excludePatterns, this, patterns); } // 获取屏蔽词列表 static async loadBlackList() { let value = await GM.getValue(DomainStore.domainKeyPrefix); if (!value) return []; const ret = JSON.parse(String(value)); if (!Array.isArray(ret)) return []; console.log(`gm value: ${ret}`); return ret; } } _DomainStore = DomainStore; async function _flush() { const val = JSON.stringify([..._classPrivateFieldGet(_blackUsers, this).keys()]); await GM.setValue(_DomainStore.domainKeyPrefix, val); await GM.setValue(_DomainStore.userKey(), val); const pv = JSON.stringify(_classPrivateFieldGet(_excludePatterns, this)); await GM.setValue(_DomainStore.patternKey(), pv); } _defineProperty(DomainStore, "NgListKey", 'NgList'); (() => { const domain = document.location.host; let segs = domain.split('.'); if (segs.length > 2) segs = segs.slice(1); _DomainStore.domainKeyPrefix = `${segs.join('.')}:${_DomainStore.NgListKey}`; })(); var _store = /*#__PURE__*/new WeakMap(); var _filter = /*#__PURE__*/new WeakMap(); class MainView { /** * * @param {DomainStore} store * @param {WebsiteFilter} filter */ constructor({ store, filter }) { /** * @member {DomainStore} #store * */ _classPrivateFieldInitSpec(this, _store, void 0); /** * @member {WebsiteFilter} #filter */ _classPrivateFieldInitSpec(this, _filter, void 0); _classPrivateFieldSet(_store, this, store); _classPrivateFieldSet(_filter, this, filter); addStyle(css); // 添加样式 window.addEventListener('load', () => { // 屏蔽视频播放后的弱智三连语音 appObserverInit(); }); } dialogElement() { return document.querySelector(MainView.DialogSelector); } render() { setInterval(() => { this.renderSettingButton(); this.renderSettingPanel(); _classPrivateFieldGet(_filter, this).render(); this.renderBlockButtons(); }, 1000); } async renderBlockButtons() { document.querySelectorAll(`${cssByCls(BlockButtonClass)}`).forEach(button => { // 点击按钮展示弹窗 button.addEventListener('click', ev => { ev.stopPropagation(); let name = button.getAttribute('user-name'); _classPrivateFieldGet(_store, this).addUser(name); _classPrivateFieldGet(_filter, this).render(); }); }); } /* 生成添加屏蔽关键词的按钮 */ async renderSettingButton() { if (!document.body) { return; } if (document.body.querySelector('#add_ngList_btn')) { return; } const btn = document.createElement('div'); btn.title = '添加屏蔽关键词'; const span = document.createElement('span'); span.innerText = ''; btn.appendChild(span); btn.id = 'add_ngList_btn'; document.body.appendChild(btn); // 点击按钮展示弹窗 btn.addEventListener('click', () => { this.renderBlockedUsers(); this.showDialog(); }); } async renderSettingPanel() { /* dialog模板 */ const dialogTemplate = ` 屏蔽词列表 添加 注:1. 可过滤包含屏蔽词的用户、微博、评论、热搜。 2. 关键词保存在本地的local storage中。 3. 更改关键词后刷新页面生效(不刷新页面的情况下,只有之后加载的微博才会生效)。 `; if (!document.body) { return; } if (document.body.querySelector('.my-dialog__wrapper')) { return; } const wrapper = document.createElement('div'); wrapper.classList.add('my-dialog__wrapper'); wrapper.innerHTML = dialogTemplate; document.body.appendChild(wrapper); /* 初始化事件 */ document.querySelector('.my-dialog__headerbtn').addEventListener('click', () => { // 关闭按钮点击事件 this.hideDialog(); }); document.querySelector('#add_btn').addEventListener('click', () => { // 添加关键词按钮点击事件 const ngWord_input = document.querySelector('#ngWord_input'); if (ngWord_input && ngWord_input.value) { _classPrivateFieldGet(_store, this).addPattern(ngWord_input.value); ngWord_input.value = ''; this.renderBlockedUsers(); } }); } showDialog() { this.dialogElement().style.display = 'initial'; } hideDialog() { this.dialogElement().style.display = 'none'; } renderBlockedUsers() { let blockedUsersHTML = ''; const users = [..._classPrivateFieldGet(_store, this).userList]; for (const [i, pattern] of _classPrivateFieldGet(_store, this).patternList.entries()) { blockedUsersHTML += `${pattern}`; } for (const [i, item] of users.entries()) { blockedUsersHTML += `${item}`; } const ngListNode = document.querySelector('#ngList'); if (ngListNode) { ngListNode.innerHTML = blockedUsersHTML; const buttons = ngListNode.querySelectorAll('.close-icon'); for (const button of buttons) { button.addEventListener('click', () => { const name = button.parentNode.textContent; const type = button.parentElement.dataset.type; switch (type) { case 'user': _classPrivateFieldGet(_store, this).removeUser(name); break; case 'pattern': _classPrivateFieldGet(_store, this).removePattern(name); break; } this.renderBlockedUsers(); }); } } } } // 创建观察器 _defineProperty(MainView, "DialogSelector", '.my-dialog__wrapper'); function appObserverInit() { const targetNode = document.getElementById('app'); if (!targetNode) { return; } // 观察器的配置(需要观察什么变动) const config = { childList: true, subtree: true }; // 当观察到变动时执行的回调函数 const callback = function () { const audioList = document.querySelectorAll('.AfterPatch_bg_34rqc'); for (const audio of audioList) { audio.remove(); console.log('移除了弱智三连'); } }; // 创建一个观察器实例并传入回调函数 const observer = new MutationObserver(callback); // 以上述配置开始观察目标节点 observer.observe(targetNode, config); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } const store = await DomainStore.init(); let filter = null; const { host } = document.location; [WeiboFilter, HupuFilter, TiebaFilter, ZhihuFilter].forEach(f => { if (filter) { return; } if (host.includes(f.host)) { filter = new f(store); } }); const controller = new MainView({ store, filter }); controller.render(); })();
注:1. 可过滤包含屏蔽词的用户、微博、评论、热搜。 2. 关键词保存在本地的local storage中。 3. 更改关键词后刷新页面生效(不刷新页面的情况下,只有之后加载的微博才会生效)。