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/search* // @exclude *://weibo.com/tv* // @grant GM.getValue // @grant GM.setValue // @grant GM.deleteValue // @version 3.8 // @author fbz // @description block users' posts and comments whom you blocked // @description:zh 屏蔽特定用户的帖子和评论 // @run-at document-start // @license MIT // @require https://unpkg.com/ajax-hook@3.0.3/dist/ajaxhook.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, _class, _WeiboFilter, _apiBlackList, _dec4, _class2, _HupuFilter, _dec5, _dec6, _dec7, _class3, _TiebaFilter, _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) { return { listSelector: list, userSelector: user }; } function injectButton({ listSelector, userSelector, elementButtonFunc, doc = document, subListSelector, subUserSelector, nameFn = defaultNameFn }) { const list = doc.querySelectorAll(listSelector); list.forEach(element => { if (subListSelector && subUserSelector) { injectButton({ listSelector: subListSelector, userSelector: subUserSelector, doc: 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) { if (user.parentNode.childElementCount > 1) { user.parentNode.appendChild(btn); break; } user = user.parentNode; } }); } var _filters = /*#__PURE__*/new WeakMap(); var _proxyOpt = /*#__PURE__*/new WeakMap(); var _registered = /*#__PURE__*/new WeakMap(); class WebsiteFilter { /** * @param {Array.<{listSelector: String, userSelector:String}>} selectors * @param {Document | Element} root * @param nameFn */ removeElements(selectors, root = document, nameFn = defaultNameFn) { 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 poster = nameFn(user); if (this.inBlackList(poster)) { element.parentNode.removeChild(element); } else { this.removeElements(selectors.slice(1), element); } }); } inBlackList(name) { return this.store.hasUser(name); } /** * * @param {DomainStore} store */ constructor(store) { _classPrivateFieldInitSpec(this, _filters, []); _classPrivateFieldInitSpec(this, _proxyOpt, {}); _classPrivateFieldInitSpec(this, _registered, false); /** * @type DomainStore */ _defineProperty(this, "store", void 0); this.store = store; this.addHook(...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; }, [])); } addHook(...hooks) { _classPrivateFieldSet(_proxyOpt, this, { ..._classPrivateFieldGet(_proxyOpt, this), onError: (err, handler) => { console.log(err); handler.next(err); }, onResponse: this.handleResponseFunc(...hooks) }); } handleResponseFunc(...hooks) { return (response, handler) => { const { url } = response.config; if (typeof url !== 'string') { handler.next(response); return; } for (const hooker of hooks) { const pattern = hooker.hookMeta; if (url.includes(pattern)) { let res = response.response; if (res) { try { res = JSON.parse(res); const ret = hooker(url, res); res = ret || res; response.response = JSON.stringify(res); } catch (err) { /* empty */ } } break; } } handler.next(response); }; } withProxyOpt(o) { _classPrivateFieldSet(_proxyOpt, this, { ..._classPrivateFieldGet(_proxyOpt, this), ...o }); } render() { if (!_classPrivateFieldGet(_registered, this)) { const opt = _classPrivateFieldGet(_proxyOpt, this); ah.proxy(opt, unsafeWindow); _classPrivateFieldSet(_registered, this, true); } _classPrivateFieldGet(_filters, this).forEach(f => { f(); }); } } function filterFunc(target, name, descriptor) { const fn = descriptor.value; fn.filterMeta = true; return descriptor; } function hookFunc(pattern) { return (target, name, descriptor) => { descriptor.value.hookMeta = pattern; return descriptor; }; } let WeiboFilter = (_dec = hookFunc('/friendstimeline'), _dec2 = hookFunc('/searchBand'), _dec3 = hookFunc('/buildComments'), (_class = (_apiBlackList = /*#__PURE__*/new WeakMap(), (_WeiboFilter = class WeiboFilter extends WebsiteFilter { // 接口黑名单 constructor(store) { super(store); _classPrivateFieldInitSpec(this, _apiBlackList, ['/female_version.mp3', '/intake/v2/rum/events']); this.withProxyOpt({ onRequest: (config, handler) => { if (!_classPrivateFieldGet(_apiBlackList, this).some(item => config.url.toString().includes(item))) { // 不在接口黑名单里的请求才放行 handler.next(config); } } }); 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(myText) || ((_comment$user = comment.user) === null || _comment$user === void 0 ? void 0 : _comment$user.screen_name) && this.inBlackList(comment.user.screen_name); 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(myText) || ((_cur$user = cur.user) === null || _cur$user === void 0 ? void 0 : _cur$user.screen_name) && this.inBlackList(cur.user.screen_name); if (!ngWordInMyText) { if (cur.retweeted_status) { var _cur$retweeted_status; const oriText = cur.retweeted_status.text || ''; const ngWordInOriText = this.inBlackList(oriText) || ((_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) && this.inBlackList(cur.retweeted_status.user.screen_name); 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, "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", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "onFriendTimeline"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "onSearchBand", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, "onSearchBand"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "hook_buildComments", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, "hook_buildComments"), _class.prototype)), _class)); let HupuFilter = (_dec4 = hookFunc('/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", [_dec4], Object.getOwnPropertyDescriptor(_class2.prototype, "hupuRouter"), _class2.prototype)), _class2)); let TiebaFilter = (_dec5 = hookFunc('/p/totalComment'), _dec6 = hookFunc('/topicList'), _dec7 = hookFunc('/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", [_dec5], Object.getOwnPropertyDescriptor(_class3.prototype, "hookReplies"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookTopicList", [_dec6], Object.getOwnPropertyDescriptor(_class3.prototype, "hookTopicList"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookSuggestion", [_dec7], Object.getOwnPropertyDescriptor(_class3.prototype, "hookSuggestion"), _class3.prototype)), _class3)); /** * @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 _DomainStore_brand = /*#__PURE__*/new WeakSet(); class DomainStore { static async init() { let list = await this.loadBlackList(); return new DomainStore(list); } /** * @type {Map.} * */ get userList() { return _classPrivateFieldGet(_blackUsers, this).keys(); } 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); } /** * @param {string[]} list * */ constructor(list) { _classPrivateMethodInitSpec(this, _DomainStore_brand); _classPrivateFieldInitSpec(this, _blackUsers, void 0); _classPrivateFieldSet(_blackUsers, this, list.reduce((map, user) => { return map.set(user, true); }, new Map())); } // 获取屏蔽词列表 static async loadBlackList() { let value = await GM.getValue(DomainStore.domainKeyPrefix); if (!value) return []; const ret = JSON.parse(String(value)); console.log(`gm value: ${ret}`); return ret; } } _DomainStore = DomainStore; async function _flush() { await GM.setValue(_DomainStore.domainKeyPrefix, JSON.stringify(_classPrivateFieldGet(_blackUsers, this).keys())); } _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); }); }); } /* 生成添加屏蔽关键词的按钮 */ 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).addUser(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, 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.textContent; _classPrivateFieldGet(_store, this).removeUser(name); 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; filter = host.includes(WeiboFilter.host) ? new WeiboFilter(store) : host.includes(HupuFilter.host) ? new HupuFilter(store) : host.includes(TiebaFilter.host) ? new TiebaFilter(store) : null; const controller = new MainView({ store, filter }); controller.render(); })();
注:1. 可过滤包含屏蔽词的用户、微博、评论、热搜。 2. 关键词保存在本地的local storage中。 3. 更改关键词后刷新页面生效(不刷新页面的情况下,只有之后加载的微博才会生效)。