NGA 用户信息增强

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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址