🏠 Home 

NGA UserInfo Enhance

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


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