// ==UserScript== // @name 全网内容屏蔽 // @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 // @match *://*.bilibili.com/* // @exclude *://weibo.com/tv* // @grant GM.getValue // @grant GM.setValue // @version 4.0.7 // @author no one // @description 屏蔽特定用户的帖子和评论 // @description:zh 屏蔽特定用户的帖子和评论 // @require https://update.greasyfork.icu/scripts/472943/1320613/Itsnotlupus%27%20MiddleMan.js // @downloadURL none // ==/UserScript== (() => { var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/utils/store.ts var DomainStore = class _DomainStore { static NgListKey = "NgList"; static domainKeyPrefix; static listener(user) { const store = new _DomainStore(); store.addUser(user); } static { const domain = document.location.host; let segs = domain.split("."); if (segs.length > 2) segs = segs.slice(1); this.domainKeyPrefix = `${segs.join(".")}:${this.NgListKey}`; } static async init() { let blockedUsers = []; const value = await GM.getValue(_DomainStore.blockUsersKey()); if (value === void 0) { const list = await this.loadBlackList(); blockedUsers = list.map((name) => { if (this.domainKeyPrefix.includes("zhihu")) { return { id: name, name: "", blockedDate: /* @__PURE__ */ new Date() }; } else { return { name, id: "", blockedDate: /* @__PURE__ */ new Date() }; } }); const value2 = JSON.stringify(blockedUsers); await GM.setValue(_DomainStore.blockUsersKey(), value2); } else { blockedUsers = JSON.parse(value); blockedUsers.forEach((user) => { user.name = user.name.trim(); }); } const val = await GM.getValue(_DomainStore.patternKey()); const patterns = JSON.parse(val || "[]"); return new _DomainStore(blockedUsers, patterns); } nameMap = /* @__PURE__ */ new Map(); // private idMap: Map = new Map(); excludePatterns = []; get userList() { return this.nameMap.keys(); } get patternList() { return structuredClone(this.excludePatterns); } matchPattern(text) { text = text.trim(); return this.excludePatterns.some((pattern) => { return text.toLocaleLowerCase().includes(pattern.toLocaleLowerCase()); }); } addPattern(text) { const s = new Set(this.excludePatterns).add(text.trim()); this.excludePatterns = [...s]; this.flush(); } removePattern(text) { text = text.trim(); this.excludePatterns = this.excludePatterns.filter((pattern) => { return pattern !== text; }); this.flush(); } hasUser({ name, id }) { if (!name) { return false; } return this.nameMap.has(name.trim()); } addUser({ name, id }) { name = name.trim(); const user = { name, id, blockedDate: /* @__PURE__ */ new Date() }; if (user.name) this.nameMap.set(name, user); this.flush(); } removeUser({ name, id }) { if (name) this.nameMap.delete(name.trim()); this.flush(); } static blockUsersKey() { return `${_DomainStore.domainKeyPrefix}:blockedusers`; } static patternKey() { return `${_DomainStore.domainKeyPrefix}:patterns`; } async flush() { const blockedUsers = [...this.nameMap.values()]; const val = JSON.stringify(blockedUsers); await GM.setValue(_DomainStore.blockUsersKey(), val); const pv = JSON.stringify(this.excludePatterns); await GM.setValue(_DomainStore.patternKey(), pv); } static singleton; constructor(users, patterns) { if (_DomainStore.singleton) { return _DomainStore.singleton; } for (const user of users) { this.nameMap.set(user.name, user); } this.excludePatterns = patterns; _DomainStore.singleton = this; } // 获取屏蔽词列表 static async loadBlackList() { const value = await GM.getValue(_DomainStore.domainKeyPrefix); if (!value) return []; const ret = JSON.parse(value.toString()); if (!Array.isArray(ret)) return []; console.log(`gm value: ${ret}`); return ret; } }; // src/utils/css.ts var BlockButtonClass = "block-button"; var HoverButtonClass = "hover-button"; var cssByCls = (cls) => `.${cls}`; var ButtonCSSText = ` ${cssByCls(BlockButtonClass)} { cursor: pointer; height: 12px; width: 12px; margin-left: 1px; float: inherit; background: inherit; #color: transparent; mix-blend-mode: difference; border-width: 0; padding: 0; display: inline-block; line-height:0px; transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1); transform-origin: bottom; } ${cssByCls(BlockButtonClass)}:hover { transform: translateY(0) scale(1.5); }`; var 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; } ${ButtonCSSText} .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; } .ng_pattern { background-color: #ff704d; display: inline-flex; align-items: center; padding: 0 10px; font-size: 12px; color: #000000; text-decoration: line-through; 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; } .${HoverButtonClass}:hover { filter: opacity(50); transition: filter 0.1s linear 0.1s; } .${HoverButtonClass} { background-color: #fff; background-color: rgba(255,255,255,0.2); height: 16px; width: 16px; filter: opacity(5); z-index: 10; position: absolute; top: 12px; left: 12px; transition: filter 0.1s linear 0s; } `; var svgIcon = ` `; function createBlockButton({ name, id, subclass, listeners, css: css2 }) { const wrapper = document.createElement("div"); wrapper.innerHTML = svgIcon; wrapper.className = BlockButtonClass; if (subclass) wrapper.classList.add(subclass); wrapper.setAttribute("user-name", name); wrapper.setAttribute("user-id", id); if (css2) { const style = document.createElement("style"); style.textContent = css2; wrapper.appendChild(style); } wrapper.addEventListener("click", (ev) => { ev.preventDefault(); ev.stopPropagation(); if (typeof listeners === "function") { listeners({ name, id }); } else { listeners.forEach((listener) => { listener({ name, id }); }); } }); return wrapper; } 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); } // src/filters/base.ts var defaultNameFn = (elem) => { const href = elem.getAttribute("href"); const id = href?.split("/").at(-1); let name = elem?.textContent || elem?.getAttribute("aria-label"); name = name.trim().replace(/^@/, "").replace("\uFF1A", "").trim(); return { name, id }; }; var defaultContentFn = (elem) => { return elem ? elem.textContent : ""; }; function newListAndUser(list, user, content) { return { listSelector: list, userSelector: user, contentSelector: content ? content : "" }; } function RegisterSubclass(target) { WebsiteFilter.registerClass(target); } var WebsiteFilter = class _WebsiteFilter { hardDelete = false; static subclasses; static registerClass(FilterClass) { if (!_WebsiteFilter.subclasses) { _WebsiteFilter.subclasses = []; } _WebsiteFilter.subclasses.push(FilterClass); } static fromHost() { const host = document.location.host; for (const cls of _WebsiteFilter.subclasses) { if (host.includes(cls.host)) { return new cls(); } } } renderers = []; proxyOpt = { requestRouters: [], responseRouters: [] }; registered = false; removeElements(selectors, root = document, nameFn = defaultNameFn, contentFn = defaultContentFn) { if (selectors.length < 1) { return; } const selector = selectors[0]; const list = root.querySelectorAll(selector.listSelector); list.forEach((element) => { if (element.style.display === "none") { return; } const user = element.querySelector(selector.userSelector); if (!user) { return; } let content; if (selector.contentSelector) { const contentElement = element.querySelector(selector.contentSelector); content = contentFn(contentElement); } else { content = ""; } if (nameFn === void 0) nameFn = defaultNameFn; const poster = nameFn(user); if (this.inBlackList(poster, content)) { if (this.hardDelete) { element?.parentNode?.removeChild(element); } else { element.style.display = "none"; } } else { this.removeElements(selectors.slice(1), element, nameFn, contentFn); } }); } inBlackList(name, content = "") { const store = new DomainStore(); return store.hasUser(name) || store.matchPattern(content); } registerHooks() { if (!this.registered) { const opt = this.proxyOpt; opt.requestRouters.forEach((router) => { middleMan.addHook(router.hookMeta.pattern, this.requestHandler(router)); }); opt.responseRouters.forEach((router) => { middleMan.addHook( router.hookMeta.pattern, this.responseHandler(router) ); }); this.registered = true; } } constructor() { this.addHooks( ...Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter((name) => Object.getPrototypeOf(this)[name].hookMeta).reduce((hooks, name) => { const pd = Object.getOwnPropertyDescriptor( Object.getPrototypeOf(this), name ); const fn = pd.value.bind(this); fn.hookMeta = pd.value.hookMeta; hooks.push(fn); return hooks; }, []) ); this.renderers = Object.getOwnPropertyNames( Object.getPrototypeOf(this) ).reduce((fns, name) => { const method = Object.getOwnPropertyDescriptor( Object.getPrototypeOf(this), name ).value; if (method.filterMeta) { const fn = method.bind(this); fn.filterMeta = method.filterMeta; fns.push(fn); } return fns; }, []); this.registerHooks(); } addHooks(...hooks) { hooks.forEach((hook) => { switch (hook.hookMeta.type) { case "request": this.proxyOpt.requestRouters.push(hook); break; case "response": this.proxyOpt.responseRouters.push(hook); break; } }); } requestHandler(hook) { return { async requestHandler(request) { const h = hook; return h(request); } }; } responseHandler(hook) { return { async responseHandler(request, response, error) { if (error) { throw error; } const res = await response.json(); const url = request.url; const h = hook; h(url, res); return Response.json(res); } }; } defaultButtonFunc({ element, user }) { if (element.querySelector(`${cssByCls(BlockButtonClass)}`)) { return; } const { name, id } = user; return createBlockButton({ name, id, listeners: [DomainStore.listener, this.render.bind(this)] }); } injectButton({ listSelector, userSelector, elementButtonFunc, doc = document, subListSelector, subUserSelector, nameFn = defaultNameFn, buttonWrapper, subButtonWrapper }) { const list = doc.querySelectorAll(listSelector); const defaultWrapperFunc = (userNode, root) => { while (userNode && userNode.parentNode !== root) { if (userNode.parentNode.childElementCount > 1) { return userNode.parentNode; } userNode = userNode.parentNode; } return userNode.parentNode; }; list.forEach((element) => { let ue = element.querySelector(userSelector); if (!ue) { return; } const user = nameFn(ue); if (!elementButtonFunc) elementButtonFunc = this.defaultButtonFunc.bind(this); const btn = elementButtonFunc({ element, user }); if (btn === void 0) { return; } if (!buttonWrapper) { buttonWrapper = defaultWrapperFunc; } let wrapper; if (typeof buttonWrapper === "function") { wrapper = buttonWrapper(ue, element); } else { wrapper = element.querySelector(buttonWrapper); } wrapper.appendChild(btn); }); if (subListSelector && subUserSelector) { list.forEach((element) => { this.injectButton({ listSelector: subListSelector, userSelector: subUserSelector, doc: element, nameFn, buttonWrapper: subButtonWrapper ? subButtonWrapper : buttonWrapper }); }); } } render() { this.renderers.forEach((f) => { if (typeof f.filterMeta === "boolean") { f(); } else { const href = document.location.href; if (f.filterMeta.pattern.exec(href)) { f(); } } }); } }; function filterFunc(target, name, descriptor) { const fn = descriptor.value; fn.filterMeta = true; } function patternFilterFunc(pattern, once = false) { return (target, name, descriptor) => { descriptor.value.filterMeta = { pattern, once }; }; } function HookDecorator(type) { return (pattern) => { return (target, name, descriptor) => { descriptor.value.hookMeta = { type, pattern }; }; }; } var reqHook = HookDecorator("request"); var respHook = HookDecorator("response"); // src/filters/zhihu.ts var userFunc = (element) => { const href = element.getAttribute("href"); return { id: href.split("/").at(-1), name: element.textContent }; }; var feedUserFunc = (e) => { const metaText = e.dataset.zaExtraModule; let id = ""; if (metaText) { const meta = JSON.parse(metaText); id = meta?.card?.content?.author_member_hash_id; } const zop = e.dataset.zop; const name = JSON.parse(zop).authorName; return { id, name }; }; var ZhihuFilter = class extends WebsiteFilter { interceptAnalytics() { return Response.json({}, { status: 200 }); } interceptTouch() { return Response.json({ success: true }, { status: 201 }); } 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: "a.css-10u695f", replyContent: "div.CommentContent.css-1jpzztt" }; this.removeElements( [ newListAndUser( selector.comments, selector.commentUser, selector.commentContent ), newListAndUser( selector.replies, selector.replyUser, selector.replyContent ) ], document, userFunc, ZhihuFilter.contentFunc ); this.injectButton({ listSelector: selector.comments, userSelector: selector.commentUser, subListSelector: selector.replies, subUserSelector: selector.replyUser, nameFn: userFunc }); } static contentFunc(element) { return element ? element.textContent + element.querySelector("img")?.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" }; this.removeElements( [ newListAndUser( selector.comments, selector.commentUser, selector.commentContent ) ], document, userFunc, ZhihuFilter.contentFunc ); this.injectButton({ listSelector: selector.comments, userSelector: selector.commentUser, nameFn: userFunc }); } async filterAnswers() { const cardSelector = { card: "div.Card.AnswerCard.css-0", user: "div.ContentItem.AnswerItem", content: ".RichText.ztext.CopyrightRichText-richText.css-1ygg4xu" }; const buttonWrapper = "div.AuthorInfo.AnswerItem-authorInfo.AnswerItem-authorInfo--related div.AuthorInfo"; this.removeElements( [ newListAndUser( cardSelector.card, cardSelector.user, cardSelector.content ) ], document, feedUserFunc ); this.injectButton({ listSelector: cardSelector.card, userSelector: cardSelector.user, nameFn: feedUserFunc, buttonWrapper }); const selector = { answers: "div.List-item", answerUser: "div.ContentItem.AnswerItem" }; this.removeElements( [ newListAndUser( selector.answers, selector.answerUser, cardSelector.content ) ], document, feedUserFunc ); this.injectButton({ listSelector: selector.answers, userSelector: selector.answerUser, nameFn: feedUserFunc, buttonWrapper }); } async filterRecommends() { const selector = { listSelector: "div.Card.TopstoryItem.TopstoryItem-isRecommend", userSelector: "div.ContentItem.AnswerItem", content: "h2 div a", wrapper: "h2 div" }; this.removeElements( [ newListAndUser( selector.listSelector, selector.userSelector, selector.content ) ], document, feedUserFunc ); this.injectButton({ listSelector: selector.listSelector, userSelector: selector.userSelector, nameFn: feedUserFunc, elementButtonFunc: ({ element, user }) => { const btn = this.defaultButtonFunc({ element, user }); btn.style.display = "block"; return btn; }, buttonWrapper: selector.wrapper }); } hookComments(url, res) { const comments = res.data; res.data = comments.filter((comment) => { const id = comment.author.id; const name = comment.author.name; const content = comment.content; return !this.inBlackList({ id, name }, content); }); res.data.forEach((comment) => { comment.childComments = comment.childComments?.filter((childComment) => { const { id, name } = childComment.author; const content = childComment.content; return !this.inBlackList({ id, name }, content); }); }); } hookAnswers(url, res) { const answers = res.data; res.data = answers.filter((answer) => { const id = answer.target?.author?.id; const name = answer.target?.author?.name; const content = answer.target?.content + answer.target.title ? answer.target.title : answer.target.question.title; return !this.inBlackList({ name, id }, content); }); } hookRecommends(url, res) { const answers = res.data; res.data = answers.filter((answer) => { const id = answer.target?.author?.id; const name = answer.target.author.name; const content = answer.target?.content + answer.target.title ? answer.target.title : answer.target.question.title; return !this.inBlackList({ name, id }, content); }); } interceptTrends(url, res) { res.preset_words.words = []; } interceptHotList(url, res) { res.data = []; res.display_num = 0; res.display_first = false; } interceptRecommends(url, res) { res.data = []; } }; __publicField(ZhihuFilter, "host", "zhihu.com"); __decorateClass([ reqHook(/zhihu-web-analytics|datahub|apm/) ], ZhihuFilter.prototype, "interceptAnalytics", 1); __decorateClass([ reqHook(/lastread\/touch/) ], ZhihuFilter.prototype, "interceptTouch", 1); __decorateClass([ filterFunc ], ZhihuFilter.prototype, "filterComments", 1); __decorateClass([ patternFilterFunc(/question\/\d+/) ], ZhihuFilter.prototype, "filterAnswerComments", 1); __decorateClass([ patternFilterFunc(/question\/\d+/) ], ZhihuFilter.prototype, "filterAnswers", 1); __decorateClass([ patternFilterFunc(/zhihu\.com\/$/) ], ZhihuFilter.prototype, "filterRecommends", 1); __decorateClass([ respHook(/\/root_comment|\/child_comment/) ], ZhihuFilter.prototype, "hookComments", 1); __decorateClass([ respHook(/questions\/\d+\/feeds/) ], ZhihuFilter.prototype, "hookAnswers", 1); __decorateClass([ respHook(/feed\/topstory\/recommend/) ], ZhihuFilter.prototype, "hookRecommends", 1); __decorateClass([ respHook(/search\/preset_words/) ], ZhihuFilter.prototype, "interceptTrends", 1); __decorateClass([ respHook(/feed\/topstory\/hot-lists/) ], ZhihuFilter.prototype, "interceptHotList", 1); __decorateClass([ respHook(/recommend_follow_people/) ], ZhihuFilter.prototype, "interceptRecommends", 1); ZhihuFilter = __decorateClass([ RegisterSubclass ], ZhihuFilter); // src/filters/weibo.ts var WeiboFilter = class extends WebsiteFilter { apiBlackList = ["/female_version.mp3", "/intake/v2/rum/events"]; mockRequests() { return Response.json({}, { status: 200 }); } interceptRead() { const data = { data: [ { act: "PC_real_read", duration: 1902, read_duration: 1902, itemid: "5067162012353224", type: "adMblog", rid: "0_0_1_5135480043505157444_0_0_0", page: "", root_id: "5058972623834652", uicode: 20000390, groupid: 20000390, fid: 232150, analysis_extra: "", __date: 1723590632, ext: "module:02", PC_real_read: 1 } ], ok: 1 }; return Response.json(data); } constructor() { super(); this.hardDelete = true; this.hideTrends(); } async filterSearchResults() { const selector = { cards: "div.card-wrap", cardUser: "div.info a.name", cardContent: "div.feed_list_content", retweetedCards: "div.card-wrap div.card-comment", retweetedCardUser: "div.con a.name", comments: "div.card-review", commentUser: "div.content a.name", commentContent: "div.content div.txt", commentWrapper: "div.content div.txt" }; this.injectButton({ listSelector: selector.cards, userSelector: selector.cardUser, subListSelector: selector.comments, subUserSelector: selector.commentUser, subButtonWrapper: selector.commentWrapper }); this.injectButton({ listSelector: selector.retweetedCards, userSelector: selector.retweetedCardUser }); this.removeElements([ newListAndUser(selector.cards, selector.cardUser, selector.cardContent), newListAndUser( selector.comments, selector.commentUser, selector.commentContent ) ]); this.removeElements([ newListAndUser(selector.cards, 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) ]); this.injectButton({ listSelector: selector.cardListSelector, userSelector: selector.cardUserSelector }); const feedWrappers = document.querySelectorAll(selector.cardListSelector); feedWrappers.forEach((feed) => { this.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( [ newListAndUser( selector.commentListSelector, selector.commentUserSelector ), newListAndUser(selector.replyListSelector, selector.replyUserSelector) ], reply ); this.injectButton({ listSelector: selector.commentListSelector, userSelector: selector.commentUserSelector, doc: reply, subListSelector: selector.replyListSelector, subUserSelector: selector.replyUserSelector }); } createCommentButton(card, user) { this.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([ newListAndUser( selector.commentListSelector, selector.commentUserSelector ), newListAndUser(selector.replyListSelector, selector.replyUserSelector) ]); this.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" ) ]); this.injectButton({ listSelector: "div.retweet.Feed_retweet_JqZJb", userSelector: "a.ALink_default_2ibt1" }); } filterComments(comments) { return comments.reduce((filtered, comment) => { const myText = comment.text || ""; const ngWordInMyText = this.inBlackList( { name: comment.user?.screen_name }, myText ); if (!ngWordInMyText) { filtered.push(comment); } return filtered; }, []); } filterStatuses(statuses) { return statuses.reduce((acc, cur) => { if (cur.user.following) { const myText = cur.text || ""; const ngWordInMyText = this.inBlackList( { name: cur.user?.screen_name }, myText ); if (!ngWordInMyText) { if (cur.retweeted_status) { const oriText = cur.retweeted_status.text || ""; const ngWordInOriText = this.inBlackList( { name: cur.retweeted_status?.user?.screen_name }, oriText ); if (ngWordInOriText) return acc; } acc.push(cur); } } return acc; }, []); } filterSearchBand(searchBands) { return searchBands.reduce((acc, cur) => { if (!this.inBlackList({ name: "" }, cur.word)) { acc.push(cur); } return acc; }, []); } onFriendTimeline(url, res) { if (url.includes("m.weibo.cn")) { res = res; res.data.statuses = this.filterStatuses(res.data.statuses); } else { res = res; 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); } onTrendsBand(url, res) { res.data.list = []; } hook_buildComments(url, res) { res.data = this.filterComments(res.data); } }; __publicField(WeiboFilter, "host", "weibo.com"); __decorateClass([ reqHook(/female_version|rum\/events/) ], WeiboFilter.prototype, "mockRequests", 1); __decorateClass([ reqHook(/log\/read/) ], WeiboFilter.prototype, "interceptRead", 1); __decorateClass([ patternFilterFunc(/s\.weibo\.com/) ], WeiboFilter.prototype, "filterSearchResults", 1); __decorateClass([ patternFilterFunc(/weibo\.com\/mygroups|(weibo\.com$)/) ], WeiboFilter.prototype, "filterFeeds", 1); __decorateClass([ patternFilterFunc(/weibo\.com\/\d+\//) ], WeiboFilter.prototype, "filterReplies", 1); __decorateClass([ patternFilterFunc(/weibo\.com\/\d+\//) ], WeiboFilter.prototype, "filterDetailComments", 1); __decorateClass([ filterFunc ], WeiboFilter.prototype, "hideTrends", 1); __decorateClass([ filterFunc ], WeiboFilter.prototype, "createRetweetButton", 1); __decorateClass([ respHook(/friendstimeline/) ], WeiboFilter.prototype, "onFriendTimeline", 1); __decorateClass([ respHook(/\/searchBand/) ], WeiboFilter.prototype, "onSearchBand", 1); __decorateClass([ respHook(/getIndexBand/) ], WeiboFilter.prototype, "onTrendsBand", 1); __decorateClass([ respHook(/\/buildComments/) ], WeiboFilter.prototype, "hook_buildComments", 1); WeiboFilter = __decorateClass([ RegisterSubclass ], WeiboFilter); // src/filters/hupu.ts var HupuFilter = class extends WebsiteFilter { async filterPosts() { const contentFunc = (element) => { const title = element.querySelector( "p.bbs-header-title.hupu-font-title1" ); const content = element.querySelector( "div.bbs-content-font.hupu-font-body5" ); return title.textContent + content.textContent; }; const selector = { post: ".hupu-bbs-post", user: "p.first-line-user-info a span", content: ".hupu-bbs-post" }; this.injectButton({ listSelector: selector.post, userSelector: selector.user }); this.removeElements( [newListAndUser(selector.post, selector.user, selector.content)], document, void 0, contentFunc ); } async filterHupuComments() { const selector = { comments: "div.index_discuss-card__Nd4MK.hp-m-discuss-card.post-card", commentUser: "p.discuss-card__user span.discuss-card__username", comment: "p.discuss-card__content", replies: "div.index_discuss-card__Nd4MK.hp-m-discuss-card.discuss-card", quotes: "div.discuss-card__quote-container-quote", quoteUser: "div.discuss-card__quote-container-quote span.discuss-card__quote-container-discusser", quote: "span.discuss-card__quote-content" }; const wrapper = "p.discuss-card__user"; this.removeElements([ newListAndUser(selector.comments, selector.commentUser, selector.comment) ]); this.injectButton({ listSelector: selector.comments, userSelector: selector.commentUser, buttonWrapper: wrapper }); this.removeElements([ newListAndUser(selector.comments, selector.quoteUser, selector.quote) ]); this.injectButton({ listSelector: selector.quotes, userSelector: selector.quoteUser, buttonWrapper: ".discuss-card__quote-container-discusser" }); this.removeElements([ newListAndUser(selector.replies, selector.commentUser, selector.comment) ]); this.injectButton({ listSelector: selector.replies, userSelector: selector.commentUser, buttonWrapper: wrapper }); } hupuRouter(url, res) { function filterHupuReplies(replies) { return replies.reduce((filtered, reply) => { const user = reply.user.username; const content = reply.content; if (!this.inBlackList({ name: user }, content)) { filtered.push(reply); } return filtered; }, []); } res.data.replies = filterHupuReplies(res.data.replies); } }; __publicField(HupuFilter, "host", "hupu.com"); __decorateClass([ filterFunc ], HupuFilter.prototype, "filterHupuComments", 1); __decorateClass([ respHook(/\/bbs-reply-detail/) ], HupuFilter.prototype, "hupuRouter", 1); HupuFilter = __decorateClass([ RegisterSubclass ], HupuFilter); // src/utils/time.ts function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } // src/filters/tieba.ts var TiebaFilter = class extends WebsiteFilter { constructor() { super(); 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 popup = document.querySelector("div.tieba-custom-pass-login"); const closeButton = document.querySelector("span.close-btn"); if (closeButton) { closeButton.click(); } else { popup.style.display = "none"; } }); ob.observe(login, { attributeFilter: ["style"], subtree: true }); break; } await sleep(10); } } async filterTiebaThreadListComments() { const selector = { threadsSelector: "li.j_thread_list.clearfix.thread_item_box", threadUserSelector: "span.tb_icon_author", content: "div.col2_right.j_threadlist_li_right" }; const contentFn = (element) => { const title = element.querySelector( "div.threadlist_abs.threadlist_abs_onlyline" ); const preview = element.querySelector( "div.threadlist_title.pull_left.j_th_tit" ); return title?.textContent + preview?.textContent; }; const fn = (user) => { const field = user.dataset.field; const id = JSON.parse(field)?.user_id; return { name: user.getAttribute("title")?.replace("\u4E3B\u9898\u4F5C\u8005: ", ""), id }; }; this.removeElements( [ newListAndUser( selector.threadsSelector, selector.threadUserSelector, selector.content ) ], document, fn, contentFn ); this.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", commentContent: "div.p_content div.d_post_content.j_d_post_content", 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", replyContent: "div.lzl_cnt span.lzl_content_main" }; this.removeElements([ newListAndUser( selector.commentsSelector, selector.commentUserSelector, selector.commentContent ), newListAndUser( selector.repliesSelector, selector.replyUserSelector, selector.replyContent ) ]); this.injectButton({ listSelector: selector.commentsSelector, userSelector: selector.commentUserSelector, buttonWrapper: "li.d_name" }); document.querySelectorAll(selector.commentsSelector).forEach((comment) => { this.injectButton({ listSelector: selector.repliesSelector, userSelector: selector.replyUserSelector, buttonWrapper: ".lzl_content_main" }); }); } async hideRightBar() { const rightBar = document.querySelector("div.right_section.right_bright"); if (!rightBar) { return; } rightBar.parentNode.removeChild(rightBar); } filterTiebaReplies(data) { const userMap = /* @__PURE__ */ 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({ name: reply.show_nickname }, reply.content) ); comment.comment_list_num = replies.length; comment.comment_info = replies; if (comment.comment_list_num > 0) { filtered = { ...filtered, [postId]: comment }; } return filtered; }, {} ); return data; } hookReplies(url, res) { res.data = this.filterTiebaReplies(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 = []; } hookSuggestion(url, res) { res.hottopic_list.search_data = []; res.query_tips.search_data = []; } }; __publicField(TiebaFilter, "host", "tieba.baidu.com"); __decorateClass([ filterFunc ], TiebaFilter.prototype, "hideLoginPopup", 1); __decorateClass([ patternFilterFunc(/f\?kw=/) ], TiebaFilter.prototype, "filterTiebaThreadListComments", 1); __decorateClass([ patternFilterFunc(/p\/\d+/) ], TiebaFilter.prototype, "filterTiebaThreadComments", 1); __decorateClass([ filterFunc ], TiebaFilter.prototype, "hideRightBar", 1); __decorateClass([ respHook(/\/p\/totalComment/) ], TiebaFilter.prototype, "hookReplies", 1); __decorateClass([ respHook(/\/topicList/) ], TiebaFilter.prototype, "hookTopicList", 1); __decorateClass([ respHook(/\/suggestion/) ], TiebaFilter.prototype, "hookSuggestion", 1); TiebaFilter = __decorateClass([ RegisterSubclass ], TiebaFilter); // src/filters/bilibili.ts var BilibiliFilter = class extends WebsiteFilter { render() { super.render(); } commentsButton(name, id) { return createBlockButton({ name, id, css: ButtonCSSText, listeners: [DomainStore.listener, this.render.bind(this)] }); } hoverButtonFunc({ element, user }) { if (element.querySelector(cssByCls(BlockButtonClass))) { return; } return createBlockButton({ name: user.name, id: user.id, subclass: HoverButtonClass, listeners: [DomainStore.listener, this.render.bind(this)] }); } filterReplies(e) { let replies = e.shadowRoot?.querySelector("div#replies")?.querySelector("bili-comment-replies-renderer")?.shadowRoot; let replyRenderers = replies?.querySelectorAll( "bili-comment-reply-renderer" ); replyRenderers?.forEach((replyShadowRoot) => { let reply = replyShadowRoot?.shadowRoot; let replyUser = reply?.querySelector("bili-comment-user-info")?.shadowRoot?.querySelector("#user-name"); let name = replyUser?.textContent || ""; let id = replyUser?.getAttribute("data-user-profile-id") || ""; if (this.inBlackList({ name, id })) { replyShadowRoot.parentElement?.removeChild(replyShadowRoot); } else { if (replyUser.parentNode.querySelector(cssByCls(BlockButtonClass))) { return; } const button = this.commentsButton(name, id); replyUser.parentNode.appendChild(button); } }); } filterComments() { const srw = document.querySelector( "#comment > div > bili-comments" )?.shadowRoot; if (!srw) { return; } const comments = srw.querySelectorAll( "#feed > bili-comment-thread-renderer" ); comments?.forEach((e) => { this.filterReplies(e); const comment = e.shadowRoot?.querySelector( "bili-comment-renderer" )?.shadowRoot; let commentUser = comment?.querySelector( "#header > bili-comment-user-info" )?.shadowRoot; const userInfo = commentUser.querySelector("#user-name"); const buttonWrapper = commentUser.querySelector("div#info"); let userName = userInfo?.textContent; let userId = userInfo?.getAttribute("data-user-profile-id") || ""; if (this.inBlackList({ name: userName, id: userId })) { e.parentNode.removeChild(e); } else { if (buttonWrapper.querySelector(cssByCls(BlockButtonClass))) { return; } const button = this.commentsButton(userName, userId); buttonWrapper.appendChild(button); } }); } filterVideoSearch() { const selector = { videos: ".col_3.col_xs_1_5.col_md_2.col_xl_1_7.mb_x40", author: ".bili-video-card__info--owner", title: ".bili-video-card__info--tit" }; const nameFn = (element) => { const author = element.querySelector(".bili-video-card__info--author"); const href = element.getAttribute("href"); return { name: author.textContent, id: href.split("/").at(-1) }; }; this.removeElements( [newListAndUser(selector.videos, selector.author, selector.title)], document, nameFn ); this.injectButton({ listSelector: selector.videos, userSelector: selector.author, elementButtonFunc: this.hoverButtonFunc.bind(this), buttonWrapper: ".bili-video-card__image--wrap", nameFn }); } filterVideoList() { const selector = { videos: ".feed-card", author: ".bili-video-card__info--owner", title: ".bili-video-card__info--tit" }; const nameFn = (element) => { const author = element.querySelector(".bili-video-card__info--author"); const href = element.getAttribute("href"); return { name: author.textContent, id: href.split("/").at(-1) }; }; this.removeElements( [newListAndUser(selector.videos, selector.author, selector.title)], document, nameFn ); this.injectButton({ listSelector: selector.videos, userSelector: selector.author, elementButtonFunc: this.hoverButtonFunc.bind(this), nameFn, buttonWrapper: ".bili-video-card__image--wrap" }); } filterVideoPlaylist() { const selector = { videos: ".video-page-card-small", videoAuthor: ".upname a", title: "div.info a p.title" }; const nameFn = (element) => { return { name: element.querySelector("span.name").textContent, id: element.getAttribute("href").split("/").at(-2) }; }; this.removeElements( [newListAndUser(selector.videos, selector.videoAuthor, selector.title)], document, nameFn ); setTimeout(() => { this.injectButton({ listSelector: selector.videos, userSelector: selector.videoAuthor, nameFn, // elementButtonFunc: this.hoverButtonFunc.bind(this), buttonWrapper: "div.playinfo" }); }, 5e3); } hookMain(url, res) { return this.hookComments(url, res); } hookReplies(url, res) { return this.hookComments(url, res); } hookComments(url, res) { const filterReplyFunc = (reply) => { const id = reply.mid_str; const name = reply.member.uname; const content = reply.content.message; return !this.inBlackList({ name, id }, content); }; let { replies } = res.data; replies = replies.filter(filterReplyFunc); replies.forEach((reply) => { const subReplies = reply.replies; reply.replies = subReplies.filter(filterReplyFunc); }); res.data.replies = replies; } hookSearchVideo(url, res) { res.data.result = res.data.result.filter((item) => { const name = item.author; const id = item.mid; const desc = item.description; return !this.inBlackList({ name, id }, desc); }); } hookSearchTrends(url, res) { res.data.trending.list = [ { keyword: "fsd", show_name: "FSD", icon: "http://i0.hdslb.com/bfs/activity-plat/static/20221213/eaf2dd702d7cc14d8d9511190245d057/lrx9rnKo24.png", uri: "", goto: "" } ]; } }; __publicField(BilibiliFilter, "host", "bilibili.com"); __decorateClass([ patternFilterFunc(/com\/video\/\w+/) ], BilibiliFilter.prototype, "filterComments", 1); __decorateClass([ patternFilterFunc(/search\.bilibili\.com/) ], BilibiliFilter.prototype, "filterVideoSearch", 1); __decorateClass([ filterFunc ], BilibiliFilter.prototype, "filterVideoList", 1); __decorateClass([ patternFilterFunc(/com\/video\/\w+/, true) ], BilibiliFilter.prototype, "filterVideoPlaylist", 1); __decorateClass([ respHook(/reply\/wbi\/main/) ], BilibiliFilter.prototype, "hookMain", 1); __decorateClass([ respHook(/\/reply\/reply/) ], BilibiliFilter.prototype, "hookReplies", 1); __decorateClass([ respHook(/search\/type/) ], BilibiliFilter.prototype, "hookSearchVideo", 1); __decorateClass([ respHook(/search\/square/) ], BilibiliFilter.prototype, "hookSearchTrends", 1); BilibiliFilter = __decorateClass([ RegisterSubclass ], BilibiliFilter); // src/views/panel.ts var MainView = class _MainView { store; filter; static instance; constructor() { if (_MainView.instance) { return _MainView.instance; } this.filter = WebsiteFilter.fromHost(); this.store = new DomainStore(); addStyle(css); window.addEventListener("load", () => { appObserverInit(); }); _MainView.instance = this; } static DialogSelector = ".my-dialog__wrapper"; dialogElement() { return document.querySelector(_MainView.DialogSelector); } freshPage() { this.filter.render(); } render() { setInterval(() => { this.renderSettingButton(); this.renderSettingPanel(); this.filter.render(); }, 1e3); } /* 生成添加屏蔽关键词的按钮 */ async renderSettingButton() { if (!document.body) { return; } if (document.body.querySelector("#add_ngList_btn")) { return; } const btn = document.createElement("div"); btn.title = "\u6DFB\u52A0\u5C4F\u853D\u5173\u952E\u8BCD"; 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() { const dialogTemplate = `
\u5C4F\u853D\u8BCD\u5217\u8868

\u6CE8\uFF1A1. \u53EF\u8FC7\u6EE4\u5305\u542B\u5C4F\u853D\u8BCD\u7684\u7528\u6237\u3001\u5FAE\u535A\u3001\u8BC4\u8BBA\u3001\u70ED\u641C\u3002 2. \u5173\u952E\u8BCD\u4FDD\u5B58\u5728\u672C\u5730\u7684local storage\u4E2D\u3002 3. \u66F4\u6539\u5173\u952E\u8BCD\u540E\u5237\u65B0\u9875\u9762\u751F\u6548\uFF08\u4E0D\u5237\u65B0\u9875\u9762\u7684\u60C5\u51B5\u4E0B\uFF0C\u53EA\u6709\u4E4B\u540E\u52A0\u8F7D\u7684\u5FAE\u535A\u624D\u4F1A\u751F\u6548\uFF09\u3002

`; 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) { this.store.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 = [...this.store.userList]; for (const [i, pattern] of this.store.patternList.reverse().entries()) { blockedUsersHTML += `${pattern}`; } for (const [i, item] of users.reverse().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": this.store.removeUser({ name, id: name }); break; case "pattern": this.store.removePattern(name); break; } this.renderBlockedUsers(); }); } } } }; 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("\u79FB\u9664\u4E86\u5F31\u667A\u4E09\u8FDE"); } }; const observer = new MutationObserver(callback); observer.observe(targetNode, config); } // src/main.ts async function main() { await DomainStore.init(); const controller = new MainView(); controller.render(); } main(); })();