NGA 用户信息增强

隐藏不需要的用户信息,或显示额外的用户信息,包括被点赞和粉丝数量,以及坛龄、发帖数量、属地、曾用名、游戏档案、刀塔段位

当前为 2024-05-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name NGA UserInfo Enhance
  3. // @name:zh-CN NGA 用户信息增强
  4.  
  5. // @namespace https://greasyfork.org/users/263018
  6. // @version 2.0.2
  7. // @author snyssss
  8. // @license MIT
  9.  
  10. // @description 隐藏不需要的用户信息,或显示额外的用户信息,包括被点赞和粉丝数量,以及坛龄、发帖数量、属地、曾用名、游戏档案、刀塔段位
  11. // @description:zh-CN 隐藏不需要的用户信息,或显示额外的用户信息,包括被点赞和粉丝数量,以及坛龄、发帖数量、属地、曾用名、游戏档案、刀塔段位
  12.  
  13. // @match *://bbs.nga.cn/*
  14. // @match *://ngabbs.com/*
  15. // @match *://nga.178.com/*
  16.  
  17. // @require https://update.greasyfork.org/scripts/486070/1378387/NGA%20Library.js
  18.  
  19. // @grant GM_addStyle
  20. // @grant GM_setValue
  21. // @grant GM_getValue
  22. // @grant GM_registerMenuCommand
  23. // @grant unsafeWindow
  24.  
  25. // @run-at document-start
  26. // @noframes
  27. // ==/UserScript==
  28.  
  29. (() => {
  30. // 声明泥潭主模块
  31. let commonui;
  32.  
  33. // 声明缓存和 API
  34. let cache, api;
  35.  
  36. // 系统标签
  37. const SYSTEM_LABEL_MAP = {
  38. 头像: "",
  39. 头衔: "",
  40. 声望: "",
  41. 威望: "",
  42. 级别: "",
  43. 注册: "",
  44. 发帖: "泥潭默认仅版主可见,普通用户可在增强里打开",
  45. 财富: "",
  46. 徽章: "",
  47. 版面: "",
  48. 备注: "",
  49. };
  50.  
  51. // 自定义标签
  52. const CUSTOM_LABEL_MAP = {
  53. 点赞: "需要占用额外的资源",
  54. 粉丝: "需要占用额外的资源",
  55. 坛龄: "",
  56. 发帖: "",
  57. 属地: "需要占用额外的资源",
  58. 曾用名: "需要占用额外的资源",
  59. 游戏档案: "需要占用额外的资源,目前支持 Steam",
  60. 刀塔段位:
  61. "需要占用额外的资源,需要可以访问 Opendota 和 Stratz<br/>免费接口为每天 2000 次,每分钟 60 次",
  62. };
  63.  
  64. // STYLE
  65. GM_addStyle(`
  66. .s-table-wrapper {
  67. max-height: 80vh;
  68. overflow-y: auto;
  69. }
  70. .s-table {
  71. margin: 0;
  72. }
  73. .s-table th,
  74. .s-table td {
  75. position: relative;
  76. white-space: nowrap;
  77. }
  78. .s-table th {
  79. position: sticky;
  80. top: 2px;
  81. z-index: 1;
  82. }
  83. .s-table input:not([type]), .s-table input[type="text"] {
  84. margin: 0;
  85. box-sizing: border-box;
  86. height: 100%;
  87. width: 100%;
  88. }
  89. .s-input-wrapper {
  90. position: absolute;
  91. top: 6px;
  92. right: 6px;
  93. bottom: 6px;
  94. left: 6px;
  95. }
  96. .s-text-ellipsis {
  97. display: flex;
  98. }
  99. .s-text-ellipsis > * {
  100. flex: 1;
  101. width: 1px;
  102. overflow: hidden;
  103. text-overflow: ellipsis;
  104. }
  105. .s-button-group {
  106. margin: -.1em -.2em;
  107. }
  108. .s-user-enhance [s-user-enhance-visible="true"].usercol::after {
  109. content: ' · ';
  110. }
  111. .s-user-enhance [s-user-enhance-visible="false"] {
  112. display: none;
  113. }
  114. `);
  115.  
  116. /**
  117. * UI
  118. */
  119. class UI {
  120. /**
  121. * 标签
  122. */
  123. static label = "用户信息增强";
  124.  
  125. /**
  126. * 弹出窗
  127. */
  128. window;
  129.  
  130. /**
  131. * 视图元素
  132. */
  133. views = {};
  134.  
  135. /**
  136. * 初始化
  137. */
  138. constructor() {
  139. this.init();
  140. }
  141.  
  142. /**
  143. * 初始化,创建基础视图,初始化通用设置
  144. */
  145. init() {
  146. const tabs = this.createTabs({
  147. className: "right_",
  148. });
  149.  
  150. const content = this.createElement("DIV", [], {
  151. style: "width: 600px;",
  152. });
  153.  
  154. const container = this.createElement("DIV", [tabs, content]);
  155.  
  156. this.views = {
  157. tabs,
  158. content,
  159. container,
  160. };
  161. }
  162.  
  163. /**
  164. * 创建元素
  165. * @param {String} tagName 标签
  166. * @param {HTMLElement | HTMLElement[] | String} content 内容,元素或者 innerHTML
  167. * @param {*} properties 额外属性
  168. * @returns {HTMLElement} 元素
  169. */
  170. createElement(tagName, content, properties = {}) {
  171. const element = document.createElement(tagName);
  172.  
  173. // 写入内容
  174. if (typeof content === "string") {
  175. element.innerHTML = content;
  176. } else {
  177. if (Array.isArray(content) === false) {
  178. content = [content];
  179. }
  180.  
  181. content.forEach((item) => {
  182. if (item === null) {
  183. return;
  184. }
  185.  
  186. if (typeof item === "string") {
  187. element.append(item);
  188. return;
  189. }
  190.  
  191. element.appendChild(item);
  192. });
  193. }
  194.  
  195. // 对 A 标签的额外处理
  196. if (tagName.toUpperCase() === "A") {
  197. if (Object.hasOwn(properties, "href") === false) {
  198. properties.href = "javascript: void(0);";
  199. }
  200. }
  201.  
  202. // 附加属性
  203. Object.entries(properties).forEach(([key, value]) => {
  204. element[key] = value;
  205. });
  206.  
  207. return element;
  208. }
  209.  
  210. /**
  211. * 创建按钮
  212. * @param {String} text 文字
  213. * @param {Function} onclick 点击事件
  214. * @param {*} properties 额外属性
  215. */
  216. createButton(text, onclick, properties = {}) {
  217. return this.createElement("BUTTON", text, {
  218. ...properties,
  219. onclick,
  220. });
  221. }
  222.  
  223. /**
  224. * 创建按钮组
  225. * @param {Array} buttons 按钮集合
  226. */
  227. createButtonGroup(...buttons) {
  228. return this.createElement("DIV", buttons, {
  229. className: "filter-button-group",
  230. });
  231. }
  232.  
  233. /**
  234. * 创建表格
  235. * @param {Array} headers 表头集合
  236. * @param {*} properties 额外属性
  237. * @returns {HTMLElement} 元素和相关函数
  238. */
  239. createTable(headers, properties = {}) {
  240. const rows = [];
  241.  
  242. const ths = headers.map((item, index) =>
  243. this.createElement("TH", item.label, {
  244. ...item,
  245. className: `c${index + 1}`,
  246. })
  247. );
  248.  
  249. const tr =
  250. ths.length > 0
  251. ? this.createElement("TR", ths, {
  252. className: "block_txt_c0",
  253. })
  254. : null;
  255.  
  256. const thead = tr !== null ? this.createElement("THEAD", tr) : null;
  257.  
  258. const tbody = this.createElement("TBODY", []);
  259.  
  260. const table = this.createElement("TABLE", [thead, tbody], {
  261. ...properties,
  262. className: "filter-table forumbox",
  263. });
  264.  
  265. const wrapper = this.createElement("DIV", table, {
  266. className: "filter-table-wrapper",
  267. });
  268.  
  269. const intersectionObserver = new IntersectionObserver((entries) => {
  270. if (entries[0].intersectionRatio <= 0) return;
  271.  
  272. const list = rows.splice(0, 10);
  273.  
  274. if (list.length === 0) {
  275. return;
  276. }
  277.  
  278. intersectionObserver.disconnect();
  279.  
  280. tbody.append(...list);
  281.  
  282. intersectionObserver.observe(tbody.lastElementChild);
  283. });
  284.  
  285. const add = (...columns) => {
  286. const tds = columns.map((column, index) => {
  287. if (ths[index]) {
  288. const { center, ellipsis } = ths[index];
  289.  
  290. const properties = {};
  291.  
  292. if (center) {
  293. properties.style = "text-align: center;";
  294. }
  295.  
  296. if (ellipsis) {
  297. properties.className = "filter-text-ellipsis";
  298. }
  299.  
  300. column = this.createElement("DIV", column, properties);
  301. }
  302.  
  303. return this.createElement("TD", column, {
  304. className: `c${index + 1}`,
  305. });
  306. });
  307.  
  308. const tr = this.createElement("TR", tds, {
  309. className: `row${(rows.length % 2) + 1}`,
  310. });
  311.  
  312. intersectionObserver.disconnect();
  313.  
  314. rows.push(tr);
  315.  
  316. intersectionObserver.observe(tbody.lastElementChild || tbody);
  317. };
  318.  
  319. const update = (e, ...columns) => {
  320. const row = e.target.closest("TR");
  321.  
  322. if (row) {
  323. const tds = row.querySelectorAll("TD");
  324.  
  325. columns.map((column, index) => {
  326. if (ths[index]) {
  327. const { center, ellipsis } = ths[index];
  328.  
  329. const properties = {};
  330.  
  331. if (center) {
  332. properties.style = "text-align: center;";
  333. }
  334.  
  335. if (ellipsis) {
  336. properties.className = "filter-text-ellipsis";
  337. }
  338.  
  339. column = this.createElement("DIV", column, properties);
  340. }
  341.  
  342. if (tds[index]) {
  343. tds[index].innerHTML = "";
  344. tds[index].append(column);
  345. }
  346. });
  347. }
  348. };
  349.  
  350. const remove = (e) => {
  351. const row = e.target.closest("TR");
  352.  
  353. if (row) {
  354. tbody.removeChild(row);
  355. }
  356. };
  357.  
  358. const clear = () => {
  359. rows.splice(0);
  360. intersectionObserver.disconnect();
  361.  
  362. tbody.innerHTML = "";
  363. };
  364.  
  365. Object.assign(wrapper, {
  366. add,
  367. update,
  368. remove,
  369. clear,
  370. });
  371.  
  372. return wrapper;
  373. }
  374.  
  375. /**
  376. * 创建标签组
  377. * @param {*} properties 额外属性
  378. */
  379. createTabs(properties = {}) {
  380. const tabs = this.createElement(
  381. "DIV",
  382. `<table class="stdbtn" cellspacing="0">
  383. <tbody>
  384. <tr></tr>
  385. </tbody>
  386. </table>`,
  387. properties
  388. );
  389.  
  390. return this.createElement(
  391. "DIV",
  392. [
  393. tabs,
  394. this.createElement("DIV", [], {
  395. className: "clear",
  396. }),
  397. ],
  398. {
  399. style: "display: none; margin-bottom: 5px;",
  400. }
  401. );
  402. }
  403.  
  404. /**
  405. * 创建标签
  406. * @param {Element} tabs 标签组
  407. * @param {String} label 标签名称
  408. * @param {Number} order 标签顺序,重复则跳过
  409. * @param {*} properties 额外属性
  410. */
  411. createTab(tabs, label, order, properties = {}) {
  412. const group = tabs.querySelector("TR");
  413.  
  414. const items = [...group.childNodes];
  415.  
  416. if (items.find((item) => item.order === order)) {
  417. return;
  418. }
  419.  
  420. if (items.length > 0) {
  421. tabs.style.removeProperty("display");
  422. }
  423.  
  424. const tab = this.createElement("A", label, {
  425. ...properties,
  426. className: "nobr silver",
  427. onclick: () => {
  428. if (tab.className === "nobr") {
  429. return;
  430. }
  431.  
  432. group.querySelectorAll("A").forEach((item) => {
  433. if (item === tab) {
  434. item.className = "nobr";
  435. } else {
  436. item.className = "nobr silver";
  437. }
  438. });
  439.  
  440. if (properties.onclick) {
  441. properties.onclick();
  442. }
  443. },
  444. });
  445.  
  446. const wrapper = this.createElement("TD", tab, {
  447. order,
  448. });
  449.  
  450. const anchor = items.find((item) => item.order > order);
  451.  
  452. group.insertBefore(wrapper, anchor || null);
  453.  
  454. return wrapper;
  455. }
  456.  
  457. /**
  458. * 创建对话框
  459. * @param {HTMLElement | null} anchor 要绑定的元素,如果为空,直接弹出
  460. * @param {String} title 对话框的标题
  461. * @param {HTMLElement} content 对话框的内容
  462. */
  463. createDialog(anchor, title, content) {
  464. let window;
  465.  
  466. const show = () => {
  467. if (window === undefined) {
  468. window = commonui.createCommmonWindow();
  469. }
  470.  
  471. window._.addContent(null);
  472. window._.addTitle(title);
  473. window._.addContent(content);
  474. window._.show();
  475. };
  476.  
  477. if (anchor) {
  478. anchor.onclick = show;
  479. } else {
  480. show();
  481. }
  482.  
  483. return window;
  484. }
  485.  
  486. /**
  487. * 渲染视图
  488. */
  489. renderView() {
  490. // 创建或打开弹出窗
  491. if (this.window === undefined) {
  492. this.window = this.createDialog(
  493. this.views.anchor,
  494. this.constructor.label,
  495. this.views.container
  496. );
  497. } else {
  498. this.window._.show();
  499. }
  500.  
  501. // 启用第一个模块
  502. this.views.tabs.querySelector("A").click();
  503. }
  504.  
  505. /**
  506. * 渲染
  507. */
  508. render() {
  509. this.renderView();
  510. }
  511. }
  512.  
  513. /**
  514. * 基础模块
  515. */
  516. class Module {
  517. /**
  518. * 模块名称
  519. */
  520. static name;
  521.  
  522. /**
  523. * 模块标签
  524. */
  525. static label;
  526.  
  527. /**
  528. * 顺序
  529. */
  530. static order;
  531.  
  532. /**
  533. * UI
  534. */
  535. ui;
  536.  
  537. /**
  538. * 视图元素
  539. */
  540. views = {};
  541.  
  542. /**
  543. * 初始化并绑定UI,注册 UI
  544. * @param {UI} ui UI
  545. */
  546. constructor(ui) {
  547. this.ui = ui;
  548.  
  549. this.init();
  550. }
  551.  
  552. /**
  553. * 获取列表
  554. */
  555. get list() {
  556. return GM_getValue(this.constructor.name, []);
  557. }
  558.  
  559. /**
  560. * 写入列表
  561. */
  562. set list(value) {
  563. GM_setValue(this.constructor.name, value);
  564. }
  565.  
  566. /**
  567. * 切换启用状态
  568. * @param {String} label 标签
  569. */
  570. toggle(label) {
  571. const list = this.list;
  572.  
  573. if (this.list.includes(label)) {
  574. this.list = list.filter((i) => i !== label);
  575. } else {
  576. this.list = list.concat(label);
  577. }
  578.  
  579. rerender();
  580. }
  581.  
  582. /**
  583. * 初始化,创建基础视图和组件
  584. */
  585. init() {
  586. if (this.views.container) {
  587. this.destroy();
  588. }
  589.  
  590. const { ui } = this;
  591.  
  592. const container = ui.createElement("DIV", []);
  593.  
  594. this.views = {
  595. container,
  596. };
  597.  
  598. this.initComponents();
  599. }
  600.  
  601. /**
  602. * 初始化组件
  603. */
  604. initComponents() {}
  605.  
  606. /**
  607. * 销毁
  608. */
  609. destroy() {
  610. Object.values(this.views).forEach((view) => {
  611. if (view.parentNode) {
  612. view.parentNode.removeChild(view);
  613. }
  614. });
  615.  
  616. this.views = {};
  617. }
  618.  
  619. /**
  620. * 渲染
  621. * @param {HTMLElement} container 容器
  622. */
  623. render(container) {
  624. container.innerHTML = "";
  625. container.appendChild(this.views.container);
  626. }
  627. }
  628.  
  629. /**
  630. * 系统模块
  631. */
  632. class SystemModule extends Module {
  633. /**
  634. * 模块名称
  635. */
  636. static name = "system";
  637.  
  638. /**
  639. * 模块标签
  640. */
  641. static label = "系统";
  642.  
  643. /**
  644. * 顺序
  645. */
  646. static order = 10;
  647.  
  648. /**
  649. * 表格列
  650. * @returns {Array} 表格列集合
  651. */
  652. columns() {
  653. return [
  654. { label: "标题" },
  655. { label: "注释" },
  656. { label: "是否启用", center: true, width: 1 },
  657. ];
  658. }
  659.  
  660. /**
  661. * 表格项
  662. * @param {String} label 标签
  663. * @param {String} description 注释
  664. * @returns {Array} 表格项集合
  665. */
  666. column(label, description) {
  667. const { ui, list } = this;
  668.  
  669. // 标题
  670. const labelElement = ui.createElement("SPAN", label, {
  671. className: "nobr",
  672. });
  673.  
  674. // 注释
  675. const descriptionElement = ui.createElement("SPAN", description, {
  676. className: "nobr",
  677. });
  678.  
  679. // 是否启用
  680. const enabled = ui.createElement("INPUT", [], {
  681. type: "checkbox",
  682. checked: list.includes(label) === false,
  683. onchange: () => {
  684. this.toggle(label);
  685. },
  686. });
  687.  
  688. return [labelElement, descriptionElement, enabled];
  689. }
  690.  
  691. /**
  692. * 初始化组件
  693. */
  694. initComponents() {
  695. super.initComponents();
  696.  
  697. const { tabs, content } = this.ui.views;
  698.  
  699. const table = this.ui.createTable(this.columns());
  700.  
  701. const tab = this.ui.createTab(
  702. tabs,
  703. this.constructor.label,
  704. this.constructor.order,
  705. {
  706. onclick: () => {
  707. this.render(content);
  708. },
  709. }
  710. );
  711.  
  712. Object.assign(this.views, {
  713. tab,
  714. table,
  715. });
  716.  
  717. this.views.container.appendChild(table);
  718. }
  719.  
  720. /**
  721. * 渲染
  722. * @param {HTMLElement} container 容器
  723. */
  724. render(container) {
  725. super.render(container);
  726.  
  727. const { table } = this.views;
  728.  
  729. if (table) {
  730. const { add, clear } = table;
  731.  
  732. clear();
  733.  
  734. Object.entries(SYSTEM_LABEL_MAP).forEach(([label, description]) => {
  735. const column = this.column(label, description);
  736.  
  737. add(...column);
  738. });
  739. }
  740. }
  741. }
  742.  
  743. /**
  744. * 自定义模块
  745. */
  746. class CustomModule extends Module {
  747. /**
  748. * 模块名称
  749. */
  750. static name = "custom";
  751.  
  752. /**
  753. * 模块标签
  754. */
  755. static label = "增强";
  756.  
  757. /**
  758. * 顺序
  759. */
  760. static order = 20;
  761.  
  762. /**
  763. * 表格列
  764. * @returns {Array} 表格列集合
  765. */
  766. columns() {
  767. return [
  768. { label: "标题" },
  769. { label: "注释" },
  770. { label: "是否启用", center: true, width: 1 },
  771. ];
  772. }
  773.  
  774. /**
  775. * 表格项
  776. * @param {String} label 标签
  777. * @param {String} description 注释
  778. * @returns {Array} 表格项集合
  779. */
  780. column(label, description) {
  781. const { ui, list } = this;
  782.  
  783. // 标题
  784. const labelElement = ui.createElement("SPAN", label, {
  785. className: "nobr",
  786. });
  787.  
  788. // 注释
  789. const descriptionElement = ui.createElement("SPAN", description, {
  790. className: "nobr",
  791. });
  792.  
  793. // 是否启用
  794. const enabled = ui.createElement("INPUT", [], {
  795. type: "checkbox",
  796. checked: list.includes(label),
  797. onchange: () => {
  798. this.toggle(label);
  799. },
  800. });
  801.  
  802. return [labelElement, descriptionElement, enabled];
  803. }
  804.  
  805. /**
  806. * 初始化组件
  807. */
  808. initComponents() {
  809. super.initComponents();
  810.  
  811. const { tabs, content } = this.ui.views;
  812.  
  813. const table = this.ui.createTable(this.columns());
  814.  
  815. const tab = this.ui.createTab(
  816. tabs,
  817. this.constructor.label,
  818. this.constructor.order,
  819. {
  820. onclick: () => {
  821. this.render(content);
  822. },
  823. }
  824. );
  825.  
  826. Object.assign(this.views, {
  827. tab,
  828. table,
  829. });
  830.  
  831. this.views.container.appendChild(table);
  832. }
  833.  
  834. /**
  835. * 渲染
  836. * @param {HTMLElement} container 容器
  837. */
  838. render(container) {
  839. super.render(container);
  840.  
  841. const { table } = this.views;
  842.  
  843. if (table) {
  844. const { add, clear } = table;
  845.  
  846. clear();
  847.  
  848. Object.entries(CUSTOM_LABEL_MAP).forEach(([label, description]) => {
  849. const column = this.column(label, description);
  850.  
  851. add(...column);
  852. });
  853. }
  854. }
  855. }
  856.  
  857. /**
  858. * 处理 commonui 模块
  859. * @param {*} value commonui
  860. */
  861. const handleCommonui = (value) => {
  862. // 绑定主模块
  863. commonui = value;
  864.  
  865. // 拦截 postDisp 事件,这是泥潭的楼层渲染
  866. Tools.interceptProperty(commonui, "postDisp", {
  867. afterSet: () => {
  868. rerender();
  869. },
  870. afterGet: (_, args) => {
  871. rerender(...args);
  872. },
  873. });
  874. };
  875.  
  876. /**
  877. * 注册脚本菜单
  878. */
  879. const registerMenu = () => {
  880. let ui;
  881.  
  882. GM_registerMenuCommand(`设置`, () => {
  883. if (commonui && commonui.mainMenuItems) {
  884. if (ui === undefined) {
  885. ui = new UI();
  886.  
  887. new SystemModule(ui);
  888. new CustomModule(ui);
  889. }
  890.  
  891. ui.render();
  892. }
  893. });
  894. };
  895.  
  896. /**
  897. * 重新渲染
  898. * @param {Number | undefined} index 重新渲染的楼层,为空时重新渲染全部
  899. */
  900. const rerender = (index) => {
  901. if (commonui === undefined || commonui.postArg === undefined) {
  902. return;
  903. }
  904.  
  905. if (index === undefined) {
  906. Object.keys(commonui.postArg.data).forEach((item) => {
  907. rerender(item);
  908. });
  909. return;
  910. }
  911.  
  912. const argid = parseInt(index, 10);
  913.  
  914. if (argid >= 0) {
  915. // TODO 需要优化
  916.  
  917. const system = GM_getValue("system", []);
  918. const custom = GM_getValue("custom", []);
  919.  
  920. const item = commonui.postArg.data[argid];
  921.  
  922. const lite = item.lite;
  923.  
  924. const uid = parseInt(item.pAid, 10) || 0;
  925.  
  926. const posterInfo = lite
  927. ? item.uInfoC.closest("tr").querySelector(".posterInfoLine")
  928. : item.uInfoC;
  929.  
  930. // 主容器样式
  931. posterInfo.classList.add("s-user-enhance");
  932.  
  933. // 头像
  934. {
  935. const element = posterInfo.querySelector(".avatar");
  936.  
  937. if (element) {
  938. element.setAttribute(
  939. "s-user-enhance-visible",
  940. system.includes("头像") === false
  941. );
  942. }
  943. }
  944.  
  945. // 头衔
  946. {
  947. const element = posterInfo.querySelector("[name='honor']");
  948.  
  949. if (element) {
  950. element.setAttribute(
  951. "s-user-enhance-visible",
  952. system.includes("头衔") === false
  953. );
  954. }
  955. }
  956.  
  957. // 声望进度条
  958. {
  959. const element = posterInfo.querySelector(".r_container");
  960.  
  961. if (element) {
  962. element.setAttribute(
  963. "s-user-enhance-visible",
  964. system.includes("声望") === false
  965. );
  966. }
  967. }
  968.  
  969. // 声望、威望、级别、注册、发帖、财富
  970. {
  971. const elements = lite
  972. ? posterInfo.querySelectorAll(".usercol")
  973. : posterInfo.querySelectorAll(".stat NOBR");
  974.  
  975. [...elements].forEach((element) => {
  976. if (lite) {
  977. ["声望", "威望", "级别", "注册", "发帖", "财富"].forEach(
  978. (label) => {
  979. if (element.innerText.indexOf(label) >= 0) {
  980. element.innerHTML = element.innerHTML.replace(" · ", "");
  981.  
  982. element.setAttribute(
  983. "s-user-enhance-visible",
  984. system.includes(label) === false
  985. );
  986. }
  987. }
  988. );
  989. } else {
  990. const container = element.closest("DIV");
  991.  
  992. container.style = "float: left; min-width: 50%;";
  993.  
  994. ["声望", "威望", "级别", "注册", "发帖", "财富"].forEach(
  995. (label) => {
  996. if (element.innerText.indexOf(label) >= 0) {
  997. container.setAttribute(
  998. "s-user-enhance-visible",
  999. system.includes(label) === false
  1000. );
  1001. }
  1002. }
  1003. );
  1004. }
  1005. });
  1006. }
  1007.  
  1008. // 徽章
  1009. {
  1010. const anchor = posterInfo.querySelector("[name='medal']");
  1011.  
  1012. if (anchor) {
  1013. const br = anchor.nextElementSibling;
  1014. const text = (() => {
  1015. const previous =
  1016. anchor.previousElementSibling || anchor.previousSibling;
  1017.  
  1018. if (previous.nodeName === "SPAN") {
  1019. return previous;
  1020. }
  1021.  
  1022. const span = document.createElement("SPAN");
  1023.  
  1024. span.appendChild(previous);
  1025.  
  1026. insertBefore(span, anchor);
  1027.  
  1028. return span;
  1029. })();
  1030.  
  1031. const visible = system.includes("徽章") === false;
  1032.  
  1033. if (lite) {
  1034. text.innerHTML = text.innerHTML.replace(" · ", "");
  1035.  
  1036. anchor
  1037. .closest(".usercol")
  1038. .setAttribute("s-user-enhance-visible", visible);
  1039. } else {
  1040. [text, anchor, br].forEach((element) => {
  1041. element.setAttribute("s-user-enhance-visible", visible);
  1042. });
  1043. }
  1044. }
  1045. }
  1046.  
  1047. // 版面
  1048. {
  1049. const anchor = posterInfo.querySelector("[name='site']");
  1050.  
  1051. if (anchor) {
  1052. const container = anchor.closest("SPAN");
  1053. const br = container.nextElementSibling;
  1054.  
  1055. const visible = system.includes("版面") === false;
  1056.  
  1057. if (lite) {
  1058. anchor
  1059. .closest(".usercol")
  1060. .setAttribute("s-user-enhance-visible", visible);
  1061. } else {
  1062. [container, br].forEach((element) => {
  1063. if (element) {
  1064. element.setAttribute("s-user-enhance-visible", visible);
  1065. }
  1066. });
  1067. }
  1068. }
  1069. }
  1070.  
  1071. // 备注
  1072. {
  1073. const elements = [
  1074. ...posterInfo.querySelectorAll("SPAN[title^='公开备注']"),
  1075. ...posterInfo.querySelectorAll("SPAN[title^='版主可见']"),
  1076. ];
  1077.  
  1078. [...elements].forEach((element) => {
  1079. const container = element.closest("SPAN");
  1080.  
  1081. container.setAttribute(
  1082. "s-user-enhance-visible",
  1083. system.includes("备注") === false
  1084. );
  1085. });
  1086. }
  1087.  
  1088. if (uid <= 0) {
  1089. return;
  1090. }
  1091.  
  1092. // 粉丝
  1093. {
  1094. const element = (() => {
  1095. const anchor = posterInfo.querySelector(
  1096. "[name='s-user-enhance-follows']"
  1097. );
  1098.  
  1099. if (anchor) {
  1100. return anchor;
  1101. }
  1102.  
  1103. const span = document.createElement("SPAN");
  1104.  
  1105. span.setAttribute("name", `s-user-enhance-follows`);
  1106. span.className = "small_colored_text_btn stxt block_txt_c2 vertmod";
  1107. span.style.cursor = "default";
  1108. span.style.margin = "0 0 0 4px";
  1109. span.innerHTML = `
  1110. <span class="white">
  1111. <span style="font-family: comm_glyphs; -webkit-font-smoothing: antialiased; line-height: 1em;">★</span>
  1112. <span name="s-user-enhance-follows-value"></span>
  1113. </span>`;
  1114.  
  1115. const uid = posterInfo.querySelector("[name='uid']");
  1116.  
  1117. insertAfter(span, uid);
  1118.  
  1119. return span;
  1120. })();
  1121.  
  1122. const value = element.querySelector(
  1123. "[name='s-user-enhance-follows-value']"
  1124. );
  1125.  
  1126. const visible = custom.includes("粉丝");
  1127.  
  1128. if (visible) {
  1129. api.getUserInfo(uid).then(({ follow_by_num }) => {
  1130. value.innerHTML = follow_by_num || 0;
  1131.  
  1132. element.setAttribute("s-user-enhance-visible", true);
  1133. });
  1134. }
  1135.  
  1136. element.setAttribute("s-user-enhance-visible", false);
  1137. }
  1138.  
  1139. // 点赞
  1140. {
  1141. const element = (() => {
  1142. const anchor = posterInfo.querySelector(
  1143. "[name='s-user-enhance-likes']"
  1144. );
  1145.  
  1146. if (anchor) {
  1147. return anchor;
  1148. }
  1149.  
  1150. const span = document.createElement("SPAN");
  1151.  
  1152. span.setAttribute("name", `s-user-enhance-likes`);
  1153. span.className = "small_colored_text_btn stxt block_txt_c2 vertmod";
  1154. span.style.cursor = "default";
  1155. span.style.margin = "0 0 0 4px";
  1156. span.innerHTML = `
  1157. <span class="white">
  1158. <span style="font-family: comm_glyphs; -webkit-font-smoothing: antialiased; line-height: 1em;">⯅</span>
  1159. <span name="s-user-enhance-likes-value"></span>
  1160. </span>`;
  1161.  
  1162. const uid = posterInfo.querySelector("[name='uid']");
  1163.  
  1164. insertAfter(span, uid);
  1165.  
  1166. return span;
  1167. })();
  1168.  
  1169. const value = element.querySelector(
  1170. "[name='s-user-enhance-likes-value']"
  1171. );
  1172.  
  1173. const visible = custom.includes("点赞");
  1174.  
  1175. if (visible) {
  1176. api.getUserInfo(uid).then(({ more_info }) => {
  1177. const likes = Object.values(more_info || {}).find(
  1178. (item) => item.type === 8
  1179. );
  1180.  
  1181. value.innerHTML = likes ? likes.data : 0;
  1182.  
  1183. element.setAttribute("s-user-enhance-visible", true);
  1184. });
  1185. }
  1186.  
  1187. element.setAttribute("s-user-enhance-visible", false);
  1188. }
  1189.  
  1190. // 坛龄
  1191. {
  1192. const element = (() => {
  1193. const anchor = posterInfo.querySelector(
  1194. "[name='s-user-enhance-regdays']"
  1195. );
  1196.  
  1197. if (anchor) {
  1198. return anchor;
  1199. }
  1200.  
  1201. if (lite) {
  1202. const span = document.createElement("SPAN");
  1203.  
  1204. span.setAttribute("name", `s-user-enhance-regdays`);
  1205. span.className = "usercol nobr";
  1206. span.innerHTML = `坛龄 <span class="userval" name="s-user-enhance-regdays-value"></span>`;
  1207.  
  1208. const lastChild = [
  1209. ...posterInfo.querySelectorAll(".usercol"),
  1210. ].pop();
  1211.  
  1212. insertAfter(span, lastChild);
  1213.  
  1214. return span;
  1215. }
  1216.  
  1217. const div = document.createElement("DIV");
  1218.  
  1219. div.setAttribute("name", `s-user-enhance-regdays`);
  1220. div.style = "float: left; min-width: 50%";
  1221. div.innerHTML = `
  1222. <nobr>
  1223. <span>坛龄: <span class="userval numericl" name="s-user-enhance-regdays-value"></span></span>
  1224. </nobr>`;
  1225.  
  1226. const lastChild = posterInfo.querySelector(
  1227. '.stat DIV[class="clear"]'
  1228. );
  1229.  
  1230. insertBefore(div, lastChild);
  1231.  
  1232. return div;
  1233. })();
  1234.  
  1235. const value = element.querySelector(
  1236. "[name='s-user-enhance-regdays-value']"
  1237. );
  1238.  
  1239. const visible = custom.includes("坛龄");
  1240.  
  1241. const regdate = commonui.userInfo.users[uid].regdate;
  1242.  
  1243. const days =
  1244. (new Date() - new Date(regdate * 1000)) / (24 * 60 * 60 * 1000);
  1245.  
  1246. value.innerHTML = `${days.toFixed(0)}天`;
  1247.  
  1248. element.setAttribute("s-user-enhance-visible", visible);
  1249. }
  1250.  
  1251. // 发帖
  1252. {
  1253. const element = (() => {
  1254. const anchor = posterInfo.querySelector(
  1255. "[name='s-user-enhance-postnum']"
  1256. );
  1257.  
  1258. if (anchor) {
  1259. return anchor;
  1260. }
  1261.  
  1262. if (lite) {
  1263. const span = document.createElement("SPAN");
  1264.  
  1265. span.setAttribute("name", `s-user-enhance-postnum`);
  1266. span.className = "usercol nobr";
  1267. span.innerHTML = `发帖 <span class="userval" name="s-user-enhance-postnum-value"></span>`;
  1268.  
  1269. const lastChild = [
  1270. ...posterInfo.querySelectorAll(".usercol"),
  1271. ].pop();
  1272.  
  1273. insertAfter(span, lastChild);
  1274.  
  1275. return span;
  1276. }
  1277.  
  1278. const div = document.createElement("DIV");
  1279.  
  1280. div.setAttribute("name", `s-user-enhance-postnum`);
  1281. div.style = "float: left; min-width: 50%";
  1282. div.innerHTML = `
  1283. <nobr>
  1284. <span>发帖: <span class="userval numericl" name="s-user-enhance-postnum-value"></span></span>
  1285. </nobr>`;
  1286.  
  1287. const lastChild = posterInfo.querySelector(
  1288. '.stat DIV[class="clear"]'
  1289. );
  1290.  
  1291. insertBefore(div, lastChild);
  1292.  
  1293. return div;
  1294. })();
  1295.  
  1296. const value = element.querySelector(
  1297. "[name='s-user-enhance-postnum-value']"
  1298. );
  1299.  
  1300. const visible = custom.includes("发帖");
  1301.  
  1302. const postnum = commonui.userInfo.users[uid].postnum;
  1303.  
  1304. value.innerHTML = postnum;
  1305.  
  1306. element.setAttribute("s-user-enhance-visible", visible);
  1307. }
  1308.  
  1309. // 属地
  1310. {
  1311. const element = (() => {
  1312. const anchor = posterInfo.querySelector(
  1313. "[name='s-user-enhance-ipLoc']"
  1314. );
  1315.  
  1316. if (anchor) {
  1317. return anchor;
  1318. }
  1319.  
  1320. if (lite) {
  1321. const span = document.createElement("SPAN");
  1322.  
  1323. span.setAttribute("name", `s-user-enhance-ipLoc`);
  1324. span.className = "usercol nobr";
  1325. span.innerHTML = `<span class="userval" name="s-user-enhance-ipLoc-value"></span>`;
  1326.  
  1327. const lastChild = [
  1328. ...posterInfo.querySelectorAll(".usercol"),
  1329. ].pop();
  1330.  
  1331. insertAfter(span, lastChild);
  1332.  
  1333. return span;
  1334. }
  1335.  
  1336. const div = document.createElement("DIV");
  1337.  
  1338. div.setAttribute("name", `s-user-enhance-ipLoc`);
  1339. div.style = "float: left; min-width: 50%";
  1340. div.innerHTML = `<span name="s-user-enhance-ipLoc-value"></span>`;
  1341.  
  1342. const lastChild = posterInfo.querySelector(
  1343. '.stat DIV[class="clear"]'
  1344. );
  1345.  
  1346. insertBefore(div, lastChild);
  1347.  
  1348. return div;
  1349. })();
  1350.  
  1351. const value = element.querySelector(
  1352. "[name='s-user-enhance-ipLoc-value']"
  1353. );
  1354.  
  1355. const visible = custom.includes("属地");
  1356.  
  1357. if (visible) {
  1358. api.getIpLocations(uid).then((data) => {
  1359. if (data.length) {
  1360. value.innerHTML = `${lite ? "属地 " : "属地: "}${data
  1361. .map(
  1362. ({ ipLoc, timestamp }) =>
  1363. `<span class="userval" title="${
  1364. timestamp ? commonui.time2dis(timestamp / 1000) : ""
  1365. }">${ipLoc}</span>`
  1366. )
  1367. .join(", ")}`;
  1368.  
  1369. element.setAttribute("s-user-enhance-visible", true);
  1370. }
  1371. });
  1372. }
  1373.  
  1374. element.setAttribute("s-user-enhance-visible", false);
  1375. }
  1376.  
  1377. // 曾用名
  1378. {
  1379. const element = (() => {
  1380. const anchor = posterInfo.querySelector(
  1381. "[name='s-user-enhance-oldname']"
  1382. );
  1383.  
  1384. if (anchor) {
  1385. return anchor;
  1386. }
  1387.  
  1388. if (lite) {
  1389. const span = document.createElement("SPAN");
  1390.  
  1391. span.setAttribute("name", `s-user-enhance-oldname`);
  1392. span.className = "usercol nobr";
  1393. span.innerHTML = `<span class="userval" name="s-user-enhance-oldname-value"></span>`;
  1394.  
  1395. const lastChild = [
  1396. ...posterInfo.querySelectorAll(".usercol"),
  1397. ].pop();
  1398.  
  1399. insertAfter(span, lastChild);
  1400.  
  1401. return span;
  1402. }
  1403.  
  1404. const div = document.createElement("DIV");
  1405.  
  1406. div.setAttribute("name", `s-user-enhance-oldname`);
  1407. div.style = "float: left; width: 100%";
  1408. div.innerHTML = `<span name="s-user-enhance-oldname-value"></span>`;
  1409.  
  1410. const lastChild = posterInfo.querySelector(
  1411. '.stat DIV[class="clear"]'
  1412. );
  1413.  
  1414. insertBefore(div, lastChild);
  1415.  
  1416. return div;
  1417. })();
  1418.  
  1419. const value = element.querySelector(
  1420. "[name='s-user-enhance-oldname-value']"
  1421. );
  1422.  
  1423. const visible = custom.includes("曾用名");
  1424.  
  1425. if (visible) {
  1426. api.getUsernameChanged(uid).then((data) => {
  1427. const values = Object.values(data || {});
  1428.  
  1429. if (values.length) {
  1430. value.innerHTML = `${lite ? "曾用名 " : "曾用名: "}${values
  1431. .map(
  1432. ({ username, time }) =>
  1433. `<span class="userval" title="${commonui.time2dis(
  1434. time
  1435. )}">${username}</span>`
  1436. )
  1437. .join(", ")}`;
  1438.  
  1439. element.setAttribute("s-user-enhance-visible", true);
  1440. }
  1441. });
  1442. }
  1443.  
  1444. element.setAttribute("s-user-enhance-visible", false);
  1445. }
  1446.  
  1447. // 游戏档案
  1448. {
  1449. const element = (() => {
  1450. const anchor = posterInfo.querySelector(
  1451. "[name='s-user-enhance-games']"
  1452. );
  1453.  
  1454. if (anchor) {
  1455. return anchor;
  1456. }
  1457.  
  1458. const div = document.createElement("DIV");
  1459.  
  1460. div.setAttribute("name", `s-user-enhance-games`);
  1461. div.style = "margin: 0 -2px;";
  1462. div.innerHTML = ``;
  1463.  
  1464. if (lite) {
  1465. const lastChild = [
  1466. ...posterInfo.querySelectorAll(".usercol"),
  1467. ].pop();
  1468.  
  1469. insertAfter(div, lastChild);
  1470. } else {
  1471. const lastChild = posterInfo.querySelector(".stat").lastChild;
  1472.  
  1473. insertBefore(div, lastChild);
  1474. }
  1475.  
  1476. return div;
  1477. })();
  1478.  
  1479. const visible = custom.includes("游戏档案");
  1480.  
  1481. if (visible) {
  1482. element.innerHTML = ``;
  1483.  
  1484. api.getSteamInfo(uid).then(({ steam_user_id }) => {
  1485. if (steam_user_id) {
  1486. const steam = (() => {
  1487. if (steam_user_id) {
  1488. const element = document.createElement("A");
  1489.  
  1490. element.href = `https://steamcommunity.com/profiles/${steam_user_id}`;
  1491. element.style = `
  1492. background-image: url(${unsafeWindow.__IMG_BASE}/misc/fid414/headline/icon03.png);
  1493. background-repeat: no-repeat;
  1494. background-position: 50% 50%;
  1495. background-size: contain;
  1496. width: 20px;
  1497. height: 20px;
  1498. display: inline-block;
  1499. cursor: pointer;
  1500. outline: none;`;
  1501. element.title = steam_user_id;
  1502.  
  1503. return element;
  1504. }
  1505.  
  1506. return null;
  1507. })();
  1508.  
  1509. const stratz = (() => {
  1510. if (steam && unsafeWindow.__CURRENT_GFID === 321) {
  1511. const shortID = Number.isSafeInteger(steam_user_id)
  1512. ? steam_user_id
  1513. : Number(steam_user_id.substr(-16, 16)) - 6561197960265728;
  1514.  
  1515. const element = document.createElement("A");
  1516.  
  1517. element.href = `https://stratz.com/players/${shortID}`;
  1518. element.style = `
  1519. background-image: url(${unsafeWindow.__IMG_BASE}/proxy/cache_attach/ficon/321u.png);
  1520. background-repeat: no-repeat;
  1521. background-position: 50% 50%;
  1522. background-size: contain;
  1523. width: 20px;
  1524. height: 20px;
  1525. display: inline-block;
  1526. cursor: pointer;
  1527. outline: none;`;
  1528. element.title = shortID;
  1529.  
  1530. return element;
  1531. }
  1532.  
  1533. return null;
  1534. })();
  1535.  
  1536. if (steam) {
  1537. steam.style.margin = "2px";
  1538. element.appendChild(steam);
  1539. }
  1540.  
  1541. if (stratz) {
  1542. stratz.style.margin = "2px";
  1543. element.appendChild(stratz);
  1544. }
  1545.  
  1546. element.setAttribute("s-user-enhance-visible", true);
  1547. }
  1548. });
  1549. }
  1550.  
  1551. element.setAttribute("s-user-enhance-visible", false);
  1552. }
  1553.  
  1554. // 刀塔段位
  1555. {
  1556. const element = (() => {
  1557. const anchor = posterInfo.querySelector(
  1558. "[name='s-user-enhance-dota-rank']"
  1559. );
  1560.  
  1561. if (anchor) {
  1562. return anchor;
  1563. }
  1564.  
  1565. const div = document.createElement("DIV");
  1566.  
  1567. div.setAttribute("name", `s-user-enhance-dota-rank`);
  1568. div.style = "margin: 2px 0";
  1569. div.innerHTML = ``;
  1570.  
  1571. if (lite) {
  1572. return null;
  1573. }
  1574.  
  1575. const lastChild = posterInfo.querySelector(".stat");
  1576.  
  1577. insertAfter(div, lastChild);
  1578.  
  1579. return div;
  1580. })();
  1581.  
  1582. const visible = custom.includes("刀塔段位");
  1583.  
  1584. if (visible && element) {
  1585. element.innerHTML = ``;
  1586.  
  1587. api.getSteamInfo(uid).then(async ({ steam_user_id }) => {
  1588. if (steam_user_id) {
  1589. const shortID = Number.isSafeInteger(steam_user_id)
  1590. ? steam_user_id
  1591. : Number(steam_user_id.substr(-16, 16)) - 6561197960265728;
  1592.  
  1593. // TODO 代码优化
  1594. // 简单的缓存,同一个人每天只请求一次
  1595. const data = (await cache.get("DotaRank")) || {};
  1596.  
  1597. const info = await new Promise((resolve) => {
  1598. if (data[shortID]) {
  1599. const { timestamp } = data[shortID];
  1600.  
  1601. const now = new Date();
  1602. const time = new Date(timestamp);
  1603.  
  1604. const isToday =
  1605. now.getDate() === time.getDate() &&
  1606. now.getMonth() === time.getMonth() &&
  1607. now.getFullYear() === time.getFullYear();
  1608.  
  1609. if (isToday) {
  1610. resolve(data[shortID]);
  1611. return;
  1612. }
  1613.  
  1614. delete data[shortID];
  1615. }
  1616.  
  1617. fetch(`https://api.opendota.com/api/players/${shortID}`)
  1618. .then((res) => res.json())
  1619. .then((res) => {
  1620. if (res) {
  1621. data[shortID] = {
  1622. ...res,
  1623. timestamp: new Date().getTime(),
  1624. };
  1625.  
  1626. cache.put("DotaRank", data);
  1627.  
  1628. resolve(res);
  1629. return;
  1630. }
  1631.  
  1632. resolve(null);
  1633. })
  1634. .catch(() => {
  1635. resolve(null);
  1636. });
  1637. });
  1638.  
  1639. if (info.profile) {
  1640. const { rank_tier, leaderboard_rank } = info;
  1641.  
  1642. const medals = [
  1643. "先锋",
  1644. "卫士",
  1645. "中军",
  1646. "统帅",
  1647. "传奇",
  1648. "万古流芳",
  1649. "超凡入圣",
  1650. "冠绝一世",
  1651. ];
  1652.  
  1653. const medal = Math.floor(rank_tier / 10);
  1654.  
  1655. const star = rank_tier % 10;
  1656.  
  1657. element.innerHTML = `
  1658. <div style="
  1659. width: 64px;
  1660. height: 64px;
  1661. display: inline-flex;
  1662. -webkit-box-pack: center;
  1663. justify-content: center;
  1664. -webkit-box-align: center;
  1665. align-items: center;
  1666. position: relative;
  1667. font-size: 10px;
  1668. overflow: hidden;
  1669. " title="${
  1670. medals[medal - 1]
  1671. ? `${medals[medal - 1]}[${leaderboard_rank || star}]`
  1672. : ""
  1673. }">
  1674. <svg viewBox="0 0 256 256" style="max-width: 256px; max-height: 256px">
  1675. <image href="https://cdn.stratz.com/images/dota2/seasonal_rank/medal_${medal}.png" height="100%" width="100%"></image>
  1676. ${
  1677. star > 0
  1678. ? `<image href="https://cdn.stratz.com/images/dota2/seasonal_rank/star_${star}.png" height="100%" width="100%"></image>`
  1679. : ""
  1680. }
  1681. </svg>
  1682. ${
  1683. leaderboard_rank
  1684. ? `<div style="
  1685. background-color: rgba(0, 0, 0, 0.7);
  1686. border-radius: 4px;
  1687. color: rgba(255, 255, 255, 0.8);
  1688. padding: 0.2em 0.3em 0.3em;
  1689. position: absolute;
  1690. line-height: normal;
  1691. bottom: 0;
  1692. ">${leaderboard_rank}</div>`
  1693. : ""
  1694. }
  1695. </div>`.replace("\n", "");
  1696.  
  1697. element.setAttribute("s-user-enhance-visible", true);
  1698. }
  1699. }
  1700. });
  1701. }
  1702.  
  1703. element.setAttribute("s-user-enhance-visible", false);
  1704. }
  1705. }
  1706. };
  1707.  
  1708. /**
  1709. * 插入至元素之前
  1710. * @param {HTMLElement} element 新元素
  1711. * @param {HTMLElement} target 目标元素
  1712. */
  1713. const insertBefore = (element, target) => {
  1714. const parentNode = target.parentNode;
  1715.  
  1716. parentNode.insertBefore(element, target);
  1717. };
  1718.  
  1719. /**
  1720. * 插入至元素之后
  1721. * @param {HTMLElement} element 新元素
  1722. * @param {HTMLElement} target 目标元素
  1723. */
  1724. const insertAfter = (element, target) => {
  1725. const parentNode = target.parentNode;
  1726.  
  1727. if (parentNode.lastChild == target) {
  1728. parentNode.appendChild(element);
  1729. return;
  1730. }
  1731.  
  1732. parentNode.insertBefore(element, target.nextSibling);
  1733. };
  1734.  
  1735. // 主函数
  1736. (async () => {
  1737. // 初始化缓存和 API 并绑定
  1738. const libs = initCacheAndAPI();
  1739.  
  1740. cache = libs.cache;
  1741. api = libs.api;
  1742.  
  1743. // 注册脚本菜单
  1744. registerMenu();
  1745.  
  1746. // 处理 commonui 模块
  1747. if (unsafeWindow.commonui) {
  1748. handleCommonui(unsafeWindow.commonui);
  1749. return;
  1750. }
  1751.  
  1752. Tools.interceptProperty(unsafeWindow, "commonui", {
  1753. afterSet: (value) => {
  1754. handleCommonui(value);
  1755. },
  1756. });
  1757. })();
  1758. })();