// ==UserScript==
// @name NGA UserInfo Enhance
// @name:zh-CN NGA 用户信息增强
// @namespace https://greasyfork.org/users/263018
// @version 2.0.9
// @author snyssss
// @license MIT
// @description 隐藏不需要的用户信息,或显示额外的用户信息,包括被点赞和粉丝数量,以及坛龄、离线天数、发帖数量、属地、曾用名、游戏档案、刀塔段位
// @description:zh-CN 隐藏不需要的用户信息,或显示额外的用户信息,包括被点赞和粉丝数量,以及坛龄、离线天数、发帖数量、属地、曾用名、游戏档案、刀塔段位
// @match *://bbs.nga.cn/*
// @match *://ngabbs.com/*
// @match *://nga.178.com/*
// @require https://update.greasyfork.org/scripts/486070/1414880/NGA%20Library.js
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant unsafeWindow
// @run-at document-start
// @noframes
// ==/UserScript==
(() => {
// 声明泥潭主模块
let commonui;
// 声明缓存和 API
let cache, api;
// 系统标签
const SYSTEM_LABEL_MAP = {
头像: "",
头衔: "",
声望: "",
威望: "",
级别: "",
注册: "",
发帖: "泥潭默认仅版主可见,普通用户可在增强里打开",
财富: "",
徽章: "",
版面: "",
备注: "",
签名: "",
};
// 自定义标签
const CUSTOM_LABEL_MAP = {
点赞: "需要占用额外的资源",
粉丝: "需要占用额外的资源",
坛龄: "",
离线: "",
发帖: "",
属地: "需要占用额外的资源",
曾用名: "需要占用额外的资源",
游戏档案: "需要占用额外的资源,目前支持 Steam、PSN、NS、原神、深空之眼",
刀塔段位:
"需要占用额外的资源,需要可以访问 Opendota 和 Stratz<br/>免费接口为每天 2000 次,每分钟 60 次",
};
// STYLE
GM_addStyle(`
.s-table-wrapper {
max-height: 80vh;
overflow-y: auto;
}
.s-table {
margin: 0;
}
.s-table th,
.s-table td {
position: relative;
white-space: nowrap;
}
.s-table th {
position: sticky;
top: 2px;
z-index: 1;
}
.s-table input:not([type]), .s-table input[type="text"] {
margin: 0;
box-sizing: border-box;
height: 100%;
width: 100%;
}
.s-input-wrapper {
position: absolute;
top: 6px;
right: 6px;
bottom: 6px;
left: 6px;
}
.s-text-ellipsis {
display: flex;
}
.s-text-ellipsis > * {
flex: 1;
width: 1px;
overflow: hidden;
text-overflow: ellipsis;
}
.s-button-group {
margin: -.1em -.2em;
}
.s-user-enhance [s-user-enhance-visible="true"].usercol::after {
content: ' · ';
}
.s-user-enhance [s-user-enhance-visible="false"] {
display: none;
}
`);
/**
* UI
*/
class UI {
/**
* 标签
*/
static label = "用户信息增强";
/**
* 弹出窗
*/
window;
/**
* 视图元素
*/
views = {};
/**
* 初始化
*/
constructor() {
this.init();
}
/**
* 初始化,创建基础视图,初始化通用设置
*/
init() {
const tabs = this.createTabs({
className: "right_",
});
const content = this.createElement("DIV", [], {
style: "width: 600px;",
});
const container = this.createElement("DIV", [tabs, content]);
this.views = {
tabs,
content,
container,
};
}
/**
* 创建元素
* @param {String} tagName 标签
* @param {HTMLElement | HTMLElement[] | String} content 内容,元素或者 innerHTML
* @param {*} properties 额外属性
* @returns {HTMLElement} 元素
*/
createElement(tagName, content, properties = {}) {
const element = document.createElement(tagName);
// 写入内容
if (typeof content === "string") {
element.innerHTML = content;
} else {
if (Array.isArray(content) === false) {
content = [content];
}
content.forEach((item) => {
if (item === null) {
return;
}
if (typeof item === "string") {
element.append(item);
return;
}
element.appendChild(item);
});
}
// 对 A 标签的额外处理
if (tagName.toUpperCase() === "A") {
if (Object.hasOwn(properties, "href") === false) {
properties.href = "javascript: void(0);";
}
}
// 附加属性
Object.entries(properties).forEach(([key, value]) => {
element[key] = value;
});
return element;
}
/**
* 创建按钮
* @param {String} text 文字
* @param {Function} onclick 点击事件
* @param {*} properties 额外属性
*/
createButton(text, onclick, properties = {}) {
return this.createElement("BUTTON", text, {
...properties,
onclick,
});
}
/**
* 创建按钮组
* @param {Array} buttons 按钮集合
*/
createButtonGroup(...buttons) {
return this.createElement("DIV", buttons, {
className: "s-button-group",
});
}
/**
* 创建表格
* @param {Array} headers 表头集合
* @param {*} properties 额外属性
* @returns {HTMLElement} 元素和相关函数
*/
createTable(headers, properties = {}) {
const rows = [];
const ths = headers.map((item, index) =>
this.createElement("TH", item.label, {
...item,
className: `c${index + 1}`,
})
);
const tr =
ths.length > 0
? this.createElement("TR", ths, {
className: "block_txt_c0",
})
: null;
const thead = tr !== null ? this.createElement("THEAD", tr) : null;
const tbody = this.createElement("TBODY", []);
const table = this.createElement("TABLE", [thead, tbody], {
...properties,
className: "s-table forumbox",
});
const wrapper = this.createElement("DIV", table, {
className: "s-table-wrapper",
});
const intersectionObserver = new IntersectionObserver((entries) => {
if (entries[0].intersectionRatio <= 0) return;
const list = rows.splice(0, 10);
if (list.length === 0) {
return;
}
intersectionObserver.disconnect();
tbody.append(...list);
intersectionObserver.observe(tbody.lastElementChild);
});
const add = (...columns) => {
const tds = columns.map((column, index) => {
if (ths[index]) {
const { center, ellipsis } = ths[index];
const properties = {};
if (center) {
properties.style = "text-align: center;";
}
if (ellipsis) {
properties.className = "s-text-ellipsis";
}
column = this.createElement("DIV", column, properties);
}
return this.createElement("TD", column, {
className: `c${index + 1}`,
});
});
const tr = this.createElement("TR", tds, {
className: `row${(rows.length % 2) + 1}`,
});
intersectionObserver.disconnect();
rows.push(tr);
intersectionObserver.observe(tbody.lastElementChild || tbody);
};
const update = (e, ...columns) => {
const row = e.target.closest("TR");
if (row) {
const tds = row.querySelectorAll("TD");
columns.map((column, index) => {
if (ths[index]) {
const { center, ellipsis } = ths[index];
const properties = {};
if (center) {
properties.style = "text-align: center;";
}
if (ellipsis) {
properties.className = "s-text-ellipsis";
}
column = this.createElement("DIV", column, properties);
}
if (tds[index]) {
tds[index].innerHTML = "";
tds[index].append(column);
}
});
}
};
const remove = (e) => {
const row = e.target.closest("TR");
if (row) {
tbody.removeChild(row);
}
};
const clear = () => {
rows.splice(0);
intersectionObserver.disconnect();
tbody.innerHTML = "";
};
Object.assign(wrapper, {
add,
update,
remove,
clear,
});
return wrapper;
}
/**
* 创建标签组
* @param {*} properties 额外属性
*/
createTabs(properties = {}) {
const tabs = this.createElement(
"DIV",
`<table class="stdbtn" cellspacing="0">
<tbody>
<tr></tr>
</tbody>
</table>`,
properties
);
return this.createElement(
"DIV",
[
tabs,
this.createElement("DIV", [], {
className: "clear",
}),
],
{
style: "display: none; margin-bottom: 5px;",
}
);
}
/**
* 创建标签
* @param {Element} tabs 标签组
* @param {String} label 标签名称
* @param {Number} order 标签顺序,重复则跳过
* @param {*} properties 额外属性
*/
createTab(tabs, label, order, properties = {}) {
const group = tabs.querySelector("TR");
const items = [...group.childNodes];
if (items.find((item) => item.order === order)) {
return;
}
if (items.length > 0) {
tabs.style.removeProperty("display");
}
const tab = this.createElement("A", label, {
...properties,
className: "nobr silver",
onclick: () => {
if (tab.className === "nobr") {
return;
}
group.querySelectorAll("A").forEach((item) => {
if (item === tab) {
item.className = "nobr";
} else {
item.className = "nobr silver";
}
});
if (properties.onclick) {
properties.onclick();
}
},
});
const wrapper = this.createElement("TD", tab, {
order,
});
const anchor = items.find((item) => item.order > order);
group.insertBefore(wrapper, anchor || null);
return wrapper;
}
/**
* 创建对话框
* @param {HTMLElement | null} anchor 要绑定的元素,如果为空,直接弹出
* @param {String} title 对话框的标题
* @param {HTMLElement} content 对话框的内容
*/
createDialog(anchor, title, content) {
let window;
const show = () => {
if (window === undefined) {
window = commonui.createCommmonWindow();
}
window._.addContent(null);
window._.addTitle(title);
window._.addContent(content);
window._.show();
};
if (anchor) {
anchor.onclick = show;
} else {
show();
}
return window;
}
/**
* 渲染视图
*/
renderView() {
// 创建或打开弹出窗
if (this.window === undefined) {
this.window = this.createDialog(
this.views.anchor,
this.constructor.label,
this.views.container
);
} else {
this.window._.show();
}
// 启用第一个模块
this.views.tabs.querySelector("A").click();
}
/**
* 渲染
*/
render() {
this.renderView();
}
}
/**
* 基础模块
*/
class Module {
/**
* 模块名称
*/
static name;
/**
* 模块标签
*/
static label;
/**
* 顺序
*/
static order;
/**
* UI
*/
ui;
/**
* 视图元素
*/
views = {};
/**
* 初始化并绑定UI,注册 UI
* @param {UI} ui UI
*/
constructor(ui) {
this.ui = ui;
this.init();
}
/**
* 获取列表
*/
get list() {
return GM_getValue(this.constructor.name, []);
}
/**
* 写入列表
*/
set list(value) {
GM_setValue(this.constructor.name, value);
}
/**
* 切换启用状态
* @param {String} label 标签
*/
toggle(label) {
const list = this.list;
if (this.list.includes(label)) {
this.list = list.filter((i) => i !== label);
} else {
this.list = list.concat(label);
}
rerender();
}
/**
* 初始化,创建基础视图和组件
*/
init() {
if (this.views.container) {
this.destroy();
}
const { ui } = this;
const container = ui.createElement("DIV", []);
this.views = {
container,
};
this.initComponents();
}
/**
* 初始化组件
*/
initComponents() {}
/**
* 销毁
*/
destroy() {
Object.values(this.views).forEach((view) => {
if (view.parentNode) {
view.parentNode.removeChild(view);
}
});
this.views = {};
}
/**
* 渲染
* @param {HTMLElement} container 容器
*/
render(container) {
container.innerHTML = "";
container.appendChild(this.views.container);
}
}
/**
* 系统模块
*/
class SystemModule extends Module {
/**
* 模块名称
*/
static name = "system";
/**
* 模块标签
*/
static label = "系统";
/**
* 顺序
*/
static order = 10;
/**
* 表格列
* @returns {Array} 表格列集合
*/
columns() {
return [
{ label: "标题" },
{ label: "注释" },
{ label: "是否启用", center: true, width: 1 },
];
}
/**
* 表格项
* @param {String} label 标签
* @param {String} description 注释
* @returns {Array} 表格项集合
*/
column(label, description) {
const { ui, list } = this;
// 标题
const labelElement = ui.createElement("SPAN", label, {
className: "nobr",
});
// 注释
const descriptionElement = ui.createElement("SPAN", description, {
className: "nobr",
});
// 是否启用
const enabled = ui.createElement("INPUT", [], {
type: "checkbox",
checked: list.includes(label) === false,
onchange: () => {
this.toggle(label);
},
});
return [labelElement, descriptionElement, enabled];
}
/**
* 初始化组件
*/
initComponents() {
super.initComponents();
const { tabs, content } = this.ui.views;
const table = this.ui.createTable(this.columns());
const tab = this.ui.createTab(
tabs,
this.constructor.label,
this.constructor.order,
{
onclick: () => {
this.render(content);
},
}
);
Object.assign(this.views, {
tab,
table,
});
this.views.container.appendChild(table);
}
/**
* 渲染
* @param {HTMLElement} container 容器
*/
render(container) {
super.render(container);
const { table } = this.views;
if (table) {
const { add, clear } = table;
clear();
Object.entries(SYSTEM_LABEL_MAP).forEach(([label, description]) => {
const column = this.column(label, description);
add(...column);
});
}
}
}
/**
* 自定义模块
*/
class CustomModule extends Module {
/**
* 模块名称
*/
static name = "custom";
/**
* 模块标签
*/
static label = "增强";
/**
* 顺序
*/
static order = 20;
/**
* 表格列
* @returns {Array} 表格列集合
*/
columns() {
return [
{ label: "标题" },
{ label: "注释" },
{ label: "是否启用", center: true, width: 1 },
];
}
/**
* 表格项
* @param {String} label 标签
* @param {String} description 注释
* @returns {Array} 表格项集合
*/
column(label, description) {
const { ui, list } = this;
// 标题
const labelElement = ui.createElement("SPAN", label, {
className: "nobr",
});
// 注释
const descriptionElement = ui.createElement("SPAN", description, {
className: "nobr",
});
// 是否启用
const enabled = ui.createElement("INPUT", [], {
type: "checkbox",
checked: list.includes(label),
onchange: () => {
this.toggle(label);
},
});
return [labelElement, descriptionElement, enabled];
}
/**
* 初始化组件
*/
initComponents() {
super.initComponents();
const { tabs, content } = this.ui.views;
const table = this.ui.createTable(this.columns());
const tab = this.ui.createTab(
tabs,
this.constructor.label,
this.constructor.order,
{
onclick: () => {
this.render(content);
},
}
);
Object.assign(this.views, {
tab,
table,
});
this.views.container.appendChild(table);
}
/**
* 渲染
* @param {HTMLElement} container 容器
*/
render(container) {
super.render(container);
const { table } = this.views;
if (table) {
const { add, clear } = table;
clear();
Object.entries(CUSTOM_LABEL_MAP).forEach(([label, description]) => {
const column = this.column(label, description);
add(...column);
});
}
}
}
/**
* 处理 commonui 模块
* @param {*} value commonui
*/
const handleCommonui = (value) => {
// 绑定主模块
commonui = value;
// 拦截 postDisp 事件,这是泥潭的楼层渲染
Tools.interceptProperty(commonui, "postDisp", {
afterSet: () => {
rerender();
},
afterGet: (_, args) => {
rerender(...args);
},
});
};
/**
* 注册脚本菜单
*/
const registerMenu = () => {
let ui;
GM_registerMenuCommand(`设置`, () => {
if (commonui && commonui.mainMenuItems) {
if (ui === undefined) {
ui = new UI();
new SystemModule(ui);
new CustomModule(ui);
}
ui.render();
}
});
};
/**
* 重新渲染
* @param {Number | undefined} index 重新渲染的楼层,为空时重新渲染全部
*/
const rerender = (index) => {
if (commonui === undefined || commonui.postArg === undefined) {
return;
}
if (index === undefined) {
Object.keys(commonui.postArg.data).forEach((item) => {
rerender(item);
});
return;
}
const argid = parseInt(index, 10);
if (Number.isNaN(argid) || argid < 0) {
return;
}
// TODO 需要优化
const system = GM_getValue("system", []);
const custom = GM_getValue("custom", []);
const item = commonui.postArg.data[argid];
const lite = item.lite;
const uid = parseInt(item.pAid, 10) || 0;
const posterInfo = lite
? item.uInfoC.closest("tr").querySelector(".posterInfoLine")
: item.uInfoC;
const container = item.pC.closest(".postbox");
// 主容器样式
container.classList.add("s-user-enhance");
// 头像
{
const element = posterInfo.querySelector(".avatar");
if (element) {
element.setAttribute(
"s-user-enhance-visible",
system.includes("头像") === false
);
}
}
// 头衔
{
const element = posterInfo.querySelector("[name='honor']");
if (element) {
element.setAttribute(
"s-user-enhance-visible",
system.includes("头衔") === false
);
}
}
// 声望进度条
{
const element = posterInfo.querySelector(".r_container");
if (element) {
element.setAttribute(
"s-user-enhance-visible",
system.includes("声望") === false
);
}
}
// 声望、威望、级别、注册、发帖、财富
{
const elements = lite
? posterInfo.querySelectorAll(".usercol")
: posterInfo.querySelectorAll(".stat NOBR");
[...elements].forEach((element) => {
if (lite) {
["声望", "威望", "级别", "注册", "发帖", "财富"].forEach((label) => {
if (element.innerText.indexOf(label) >= 0) {
element.innerHTML = element.innerHTML.replace(" · ", "");
element.setAttribute(
"s-user-enhance-visible",
system.includes(label) === false
);
}
});
} else {
const container = element.closest("DIV");
container.style = "float: left; min-width: 50%;";
["声望", "威望", "级别", "注册", "发帖", "财富"].forEach((label) => {
if (element.innerText.indexOf(label) >= 0) {
container.setAttribute(
"s-user-enhance-visible",
system.includes(label) === false
);
}
});
}
});
}
// 徽章
{
const anchor = posterInfo.querySelector("[name='medal']");
if (anchor) {
const br = anchor.nextElementSibling;
const text = (() => {
const previous =
anchor.previousElementSibling || anchor.previousSibling;
if (previous.nodeName === "SPAN") {
return previous;
}
const span = document.createElement("SPAN");
span.appendChild(previous);
insertBefore(span, anchor);
return span;
})();
const visible = system.includes("徽章") === false;
if (lite) {
text.innerHTML = text.innerHTML.replace(" · ", "");
anchor
.closest(".usercol")
.setAttribute("s-user-enhance-visible", visible);
} else {
[text, anchor, br].forEach((element) => {
element.setAttribute("s-user-enhance-visible", visible);
});
}
}
}
// 版面
{
const anchor = posterInfo.querySelector("[name='site']");
if (anchor) {
const container = anchor.closest("SPAN");
const br = container.nextElementSibling;
const visible = system.includes("版面") === false;
if (lite) {
anchor
.closest(".usercol")
.setAttribute("s-user-enhance-visible", visible);
} else {
[container, br].forEach((element) => {
if (element) {
element.setAttribute("s-user-enhance-visible", visible);
}
});
}
}
}
// 备注
{
const elements = [
...posterInfo.querySelectorAll("SPAN[title^='公开备注']"),
...posterInfo.querySelectorAll("SPAN[title^='版主可见']"),
];
[...elements].forEach((element) => {
const container = element.closest("SPAN");
container.setAttribute(
"s-user-enhance-visible",
system.includes("备注") === false
);
});
}
// 签名
{
const signC = item.signC;
if (signC) {
signC.setAttribute(
"s-user-enhance-visible",
system.includes("签名") === false
);
}
}
if (uid <= 0) {
return;
}
// 粉丝
{
const element = (() => {
const anchor = posterInfo.querySelector(
"[name='s-user-enhance-follows']"
);
if (anchor) {
return anchor;
}
const span = document.createElement("SPAN");
span.setAttribute("name", `s-user-enhance-follows`);
span.className = "small_colored_text_btn stxt block_txt_c2 vertmod";
span.style.cursor = "default";
span.style.margin = "0 0 0 4px";
span.innerHTML = `
<span class="white">
<span style="font-family: comm_glyphs; -webkit-font-smoothing: antialiased; line-height: 1em;">★</span>
<span name="s-user-enhance-follows-value"></span>
</span>`;
const uid = posterInfo.querySelector("[name='uid']");
insertAfter(span, uid);
return span;
})();
const value = element.querySelector(
"[name='s-user-enhance-follows-value']"
);
const visible = custom.includes("粉丝");
if (visible) {
api.getUserInfo(uid).then(({ follow_by_num }) => {
value.innerHTML = follow_by_num || 0;
element.setAttribute("s-user-enhance-visible", true);
});
}
element.setAttribute("s-user-enhance-visible", false);
}
// 点赞
{
const element = (() => {
const anchor = posterInfo.querySelector(
"[name='s-user-enhance-likes']"
);
if (anchor) {
return anchor;
}
const span = document.createElement("SPAN");
span.setAttribute("name", `s-user-enhance-likes`);
span.className = "small_colored_text_btn stxt block_txt_c2 vertmod";
span.style.cursor = "default";
span.style.margin = "0 0 0 4px";
span.innerHTML = `
<span class="white">
<span style="font-family: comm_glyphs; -webkit-font-smoothing: antialiased; line-height: 1em;">⯅</span>
<span name="s-user-enhance-likes-value"></span>
</span>`;
const uid = posterInfo.querySelector("[name='uid']");
insertAfter(span, uid);
return span;
})();
const value = element.querySelector(
"[name='s-user-enhance-likes-value']"
);
const visible = custom.includes("点赞");
if (visible) {
api.getUserInfo(uid).then(({ more_info }) => {
const likes = Object.values(more_info || {}).find(
(item) => item.type === 8
);
value.innerHTML = likes ? likes.data : 0;
element.setAttribute("s-user-enhance-visible", true);
});
}
element.setAttribute("s-user-enhance-visible", false);
}
// 坛龄
{
const element = (() => {
const anchor = posterInfo.querySelector(
"[name='s-user-enhance-regdays']"
);
if (anchor) {
return anchor;
}
if (lite) {
const span = document.createElement("SPAN");
span.setAttribute("name", `s-user-enhance-regdays`);
span.className = "usercol nobr";
span.innerHTML = `坛龄 <span class="userval" name="s-user-enhance-regdays-value"></span>`;
const lastChild = [...posterInfo.querySelectorAll(".usercol")].pop();
insertAfter(span, lastChild);
return span;
}
const div = document.createElement("DIV");
div.setAttribute("name", `s-user-enhance-regdays`);
div.style = "float: left; min-width: 50%";
div.innerHTML = `
<nobr>
<span>坛龄: <span class="userval numericl" name="s-user-enhance-regdays-value"></span></span>
</nobr>`;
const lastChild = posterInfo.querySelector('.stat DIV[class="clear"]');
insertBefore(div, lastChild);
return div;
})();
const value = element.querySelector(
"[name='s-user-enhance-regdays-value']"
);
const visible = custom.includes("坛龄");
if (visible) {
const { regdate } = commonui.userInfo.users[uid];
const { years, months, days } = Tools.dateDiff(
new Date(regdate * 1000)
);
value.title = ``;
value.innerHTML = ``;
[
[years, "年"],
[months, "月"],
[days, "天"],
].forEach(([item, unit]) => {
if (item > 0) {
value.title += `${item}${unit}`;
if (value.innerHTML.length === 0) {
value.innerHTML = `${item}${unit}`;
}
}
});
if (value.innerHTML.length === 0) {
value.innerHTML = `0天`;
}
}
element.setAttribute("s-user-enhance-visible", visible);
}
// 离线
{
const element = (() => {
const anchor = posterInfo.querySelector(
"[name='s-user-enhance-offdays']"
);
if (anchor) {
return anchor;
}
if (lite) {
const span = document.createElement("SPAN");
span.setAttribute("name", `s-user-enhance-offdays`);
span.className = "usercol nobr";
span.innerHTML = `离线 <span class="userval" name="s-user-enhance-offdays-value"></span>`;
const lastChild = [...posterInfo.querySelectorAll(".usercol")].pop();
insertAfter(span, lastChild);
return span;
}
const div = document.createElement("DIV");
div.setAttribute("name", `s-user-enhance-offdays`);
div.style = "float: left; min-width: 50%";
div.innerHTML = `
<nobr>
<span>离线: <span class="userval numericl" name="s-user-enhance-offdays-value"></span></span>
</nobr>`;
const lastChild = posterInfo.querySelector('.stat DIV[class="clear"]');
insertBefore(div, lastChild);
return div;
})();
const value = element.querySelector(
"[name='s-user-enhance-offdays-value']"
);
const visible = custom.includes("离线");
if (visible) {
const thisvisit = commonui.userInfo.users[uid].thisvisit;
const postTime = item.postTime;
const time = Math.max(thisvisit, postTime) * 1000;
const diff = new Date() - new Date(time);
const start = new Date(2000, 0, 1);
const end = new Date();
end.setTime(start.getTime() + diff);
const { years, months, days } = Tools.dateDiff(start, end);
value.title = ``;
value.innerHTML = ``;
[
[years, "年"],
[months, "月"],
[days, "天"],
].forEach(([item, unit]) => {
if (item > 0) {
value.title += `${item}${unit}`;
if (value.innerHTML.length === 0) {
value.innerHTML = `${item}${unit}`;
}
}
});
} else {
value.innerHTML = ``;
}
element.setAttribute(
"s-user-enhance-visible",
value.innerHTML.length > 0
);
}
// 发帖
{
const element = (() => {
const anchor = posterInfo.querySelector(
"[name='s-user-enhance-postnum']"
);
if (anchor) {
return anchor;
}
if (lite) {
const span = document.createElement("SPAN");
span.setAttribute("name", `s-user-enhance-postnum`);
span.className = "usercol nobr";
span.innerHTML = `发帖 <span class="userval" name="s-user-enhance-postnum-value"></span>`;
const lastChild = [...posterInfo.querySelectorAll(".usercol")].pop();
insertAfter(span, lastChild);
return span;
}
const div = document.createElement("DIV");
div.setAttribute("name", `s-user-enhance-postnum`);
div.style = "float: left; min-width: 50%";
div.innerHTML = `
<nobr>
<span>发帖: <span class="userval numericl" name="s-user-enhance-postnum-value"></span></span>
</nobr>`;
const lastChild = posterInfo.querySelector('.stat DIV[class="clear"]');
insertBefore(div, lastChild);
return div;
})();
const value = element.querySelector(
"[name='s-user-enhance-postnum-value']"
);
const visible = custom.includes("发帖");
if (visible) {
const { postnum, regdate } = commonui.userInfo.users[uid];
const days = Math.ceil((Date.now() / 1000 - regdate) / (24 * 60 * 60));
const postnumPerDay = postnum / days;
value.title = `日均: ${postnumPerDay.toFixed(1)}`;
value.innerHTML = postnum;
}
element.setAttribute("s-user-enhance-visible", visible);
}
// 属地
{
const element = (() => {
const anchor = posterInfo.querySelector(
"[name='s-user-enhance-ipLoc']"
);
if (anchor) {
return anchor;
}
if (lite) {
const span = document.createElement("SPAN");
span.setAttribute("name", `s-user-enhance-ipLoc`);
span.className = "usercol nobr";
span.innerHTML = `<span class="userval" name="s-user-enhance-ipLoc-value"></span>`;
const lastChild = [...posterInfo.querySelectorAll(".usercol")].pop();
insertAfter(span, lastChild);
return span;
}
const div = document.createElement("DIV");
div.setAttribute("name", `s-user-enhance-ipLoc`);
div.style = "float: left; min-width: 50%";
div.innerHTML = `<span name="s-user-enhance-ipLoc-value"></span>`;
const lastChild = posterInfo.querySelector('.stat DIV[class="clear"]');
insertBefore(div, lastChild);
return div;
})();
const value = element.querySelector(
"[name='s-user-enhance-ipLoc-value']"
);
const visible = custom.includes("属地");
if (visible) {
api.getIpLocations(uid).then((data) => {
if (data.length) {
value.innerHTML = `${lite ? "属地 " : "属地: "}${data
.map(
({ ipLoc, timestamp }) =>
`<span class="userval" title="${
timestamp ? commonui.time2dis(timestamp / 1000) : ""
}">${ipLoc}</span>`
)
.join(", ")}`;
element.setAttribute("s-user-enhance-visible", true);
}
});
}
element.setAttribute("s-user-enhance-visible", false);
}
// 曾用名
{
const element = (() => {
const anchor = posterInfo.querySelector(
"[name='s-user-enhance-oldname']"
);
if (anchor) {
return anchor;
}
if (lite) {
const span = document.createElement("SPAN");
span.setAttribute("name", `s-user-enhance-oldname`);
span.className = "usercol nobr";
span.innerHTML = `<span class="userval" name="s-user-enhance-oldname-value"></span>`;
const lastChild = [...posterInfo.querySelectorAll(".usercol")].pop();
insertAfter(span, lastChild);
return span;
}
const div = document.createElement("DIV");
div.setAttribute("name", `s-user-enhance-oldname`);
div.style = "float: left; width: 100%";
div.innerHTML = `<span name="s-user-enhance-oldname-value"></span>`;
const lastChild = posterInfo.querySelector('.stat DIV[class="clear"]');
insertBefore(div, lastChild);
return div;
})();
const value = element.querySelector(
"[name='s-user-enhance-oldname-value']"
);
const visible = custom.includes("曾用名");
if (visible) {
api.getUsernameChanged(uid).then((data) => {
const values = Object.values(data || {});
if (values.length) {
value.innerHTML = `${lite ? "曾用名 " : "曾用名: "}${values
.map(
({ username, time }) =>
`<span class="userval" title="${commonui.time2dis(
time
)}">${username}</span>`
)
.join(", ")}`;
element.setAttribute("s-user-enhance-visible", true);
}
});
}
element.setAttribute("s-user-enhance-visible", false);
}
// 游戏档案
{
const element = (() => {
const anchor = posterInfo.querySelector(
"[name='s-user-enhance-games']"
);
if (anchor) {
return anchor;
}
const div = document.createElement("DIV");
div.setAttribute("name", `s-user-enhance-games`);
div.style = "margin: 0 -2px;";
div.innerHTML = ``;
if (lite) {
const lastChild = [...posterInfo.querySelectorAll(".usercol")].pop();
insertAfter(div, lastChild);
} else {
const lastChild = posterInfo.querySelector(".stat").lastChild;
insertBefore(div, lastChild);
}
return div;
})();
const visible = custom.includes("游戏档案");
if (visible) {
element.innerHTML = ``;
api.getUserGameInfo(uid).then((info) => {
// Steam
if (info.steam) {
const { steam_user_id, steam_user_name } = info.steam;
const steam = (() => {
if (steam_user_id) {
const element = document.createElement("A");
element.href = `https://steamcommunity.com/profiles/${steam_user_id}`;
element.style = `
background-image: url(${unsafeWindow.__IMG_BASE}/misc/fid414/headline/icon03.png);
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
width: 20px;
height: 20px;
display: inline-block;
cursor: pointer;
outline: none;`;
element.title = `${steam_user_name}[${steam_user_id}]`;
return element;
}
return null;
})();
if (steam) {
steam.style.margin = "2px";
element.appendChild(steam);
}
element.setAttribute("s-user-enhance-visible", true);
}
// PSN
if (info.psn) {
const { psn_user_id, psn_user_name } = info.psn;
const psn = (() => {
if (psn_user_name) {
const element = document.createElement("A");
element.href = `https://psnprofiles.com/${psn_user_name}`;
element.style = `
background-image: url(${unsafeWindow.__IMG_BASE}/misc/fid414/headline/icon05.png);
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
width: 20px;
height: 20px;
display: inline-block;
cursor: pointer;
outline: none;`;
element.title = `${psn_user_name}[${psn_user_id}]`;
return element;
}
return null;
})();
if (psn) {
psn.style.margin = "2px";
element.appendChild(psn);
}
element.setAttribute("s-user-enhance-visible", true);
}
// NS
if (info.nintendo) {
const { user_info } = info.nintendo;
const nintendo = (() => {
if (user_info) {
const { ns_nickname, ns_friendcode } = user_info.user;
const element = document.createElement("A");
element.style = `
background-image: url(${unsafeWindow.__IMG_BASE}/misc/fid414/headline/icon01.png);
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
width: 20px;
height: 20px;
display: inline-block;
cursor: pointer;
outline: none;`;
element.title = `${ns_nickname}[${
ns_friendcode === "SW-XXXX-XXXX-XXXX" ? "-" : ns_friendcode
}]`;
return element;
}
return null;
})();
if (nintendo) {
nintendo.style.margin = "2px";
element.appendChild(nintendo);
}
element.setAttribute("s-user-enhance-visible", true);
}
// 刀塔
if (info.steam) {
const { steam_user_id } = info.steam;
const stratz = (() => {
if (steam_user_id && unsafeWindow.__CURRENT_GFID === 321) {
const shortID = Number.isSafeInteger(steam_user_id)
? steam_user_id
: Number(steam_user_id.substr(-16, 16)) - 6561197960265728;
const element = document.createElement("A");
element.href = `https://stratz.com/players/${shortID}`;
element.style = `
background-image: url(${unsafeWindow.__IMG_BASE}/proxy/cache_attach/ficon/321u.png);
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
width: 20px;
height: 20px;
display: inline-block;
cursor: pointer;
outline: none;`;
element.title = shortID;
return element;
}
return null;
})();
if (stratz) {
stratz.style.margin = "2px";
element.appendChild(stratz);
}
element.setAttribute("s-user-enhance-visible", true);
}
// 原神
if (info.genshin) {
const { userInfo } = info.genshin;
const genshin = (() => {
if (userInfo.ys_id) {
const element = document.createElement("A");
element.style = `
background-image: url(${unsafeWindow.__IMG_BASE}/proxy/cache_attach/ficon/650u.png);
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
width: 20px;
height: 20px;
display: inline-block;
cursor: pointer;
outline: none;`;
element.title = `${userInfo.nickname}[${userInfo.ys_id}]`;
return element;
}
return null;
})();
if (genshin) {
genshin.style.margin = "2px";
element.appendChild(genshin);
}
element.setAttribute("s-user-enhance-visible", true);
}
// 深空之眼
if (info.skzy) {
const { skzy_uid, nick_name } = info.skzy;
const skzy = (() => {
if (skzy_uid) {
const element = document.createElement("A");
element.style = `
background-image: url(${unsafeWindow.__IMG_BASE}/proxy/cache_attach/ficon/848u.png);
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
width: 20px;
height: 20px;
display: inline-block;
cursor: pointer;
outline: none;`;
element.title = `${nick_name}[${skzy_uid}]`;
return element;
}
return null;
})();
if (skzy) {
skzy.style.margin = "2px";
element.appendChild(skzy);
}
element.setAttribute("s-user-enhance-visible", true);
}
});
}
element.setAttribute("s-user-enhance-visible", false);
}
// 刀塔段位
{
const element = (() => {
const anchor = posterInfo.querySelector(
"[name='s-user-enhance-dota-rank']"
);
if (anchor) {
return anchor;
}
const div = document.createElement("DIV");
div.setAttribute("name", `s-user-enhance-dota-rank`);
div.style = "margin: 2px 0";
div.innerHTML = ``;
if (lite) {
return null;
}
const lastChild = posterInfo.querySelector(".stat");
insertAfter(div, lastChild);
return div;
})();
if (element) {
const visible = custom.includes("刀塔段位");
if (visible) {
element.innerHTML = ``;
api.getSteamInfo(uid).then(async ({ steam_user_id }) => {
if (steam_user_id) {
const shortID = Number.isSafeInteger(steam_user_id)
? steam_user_id
: Number(steam_user_id.substr(-16, 16)) - 6561197960265728;
// TODO 代码优化
// 简单的缓存,同一个人每天只请求一次
const data = (await cache.get("DotaRank")) || {};
const info = await new Promise((resolve) => {
if (data[shortID]) {
const { timestamp } = data[shortID];
const date = new Date(timestamp);
const isToday = Tools.dateIsToday(date);
if (isToday) {
resolve(data[shortID]);
return;
}
delete data[shortID];
}
fetch(`https://api.opendota.com/api/players/${shortID}`)
.then((res) => res.json())
.then((res) => {
if (res) {
data[shortID] = {
...res,
timestamp: new Date().getTime(),
};
cache.put("DotaRank", data);
resolve(res);
return;
}
resolve(null);
})
.catch(() => {
resolve(null);
});
});
if (info.profile) {
const { rank_tier, leaderboard_rank } = info;
const medals = [
"先锋",
"卫士",
"中军",
"统帅",
"传奇",
"万古流芳",
"超凡入圣",
"冠绝一世",
];
const medal = Math.floor(rank_tier / 10);
const star = rank_tier % 10;
element.innerHTML = `
<div style="
width: 64px;
height: 64px;
display: inline-flex;
-webkit-box-pack: center;
justify-content: center;
-webkit-box-align: center;
align-items: center;
position: relative;
font-size: 10px;
overflow: hidden;
" title="${
medals[medal - 1]
? `${medals[medal - 1]}[${leaderboard_rank || star}]`
: ""
}">
<svg viewBox="0 0 256 256" style="max-width: 256px; max-height: 256px">
<image href="https://cdn.stratz.com/images/dota2/seasonal_rank/medal_${medal}.png" height="100%" width="100%"></image>
${
star > 0
? `<image href="https://cdn.stratz.com/images/dota2/seasonal_rank/star_${star}.png" height="100%" width="100%"></image>`
: ""
}
</svg>
${
leaderboard_rank
? `<div style="
background-color: rgba(0, 0, 0, 0.7);
border-radius: 4px;
color: rgba(255, 255, 255, 0.8);
padding: 0.2em 0.3em 0.3em;
position: absolute;
line-height: normal;
bottom: 0;
">${leaderboard_rank}</div>`
: ""
}
</div>`;
element.setAttribute("s-user-enhance-visible", true);
}
}
});
}
element.setAttribute("s-user-enhance-visible", false);
}
}
};
/**
* 插入至元素之前
* @param {HTMLElement} element 新元素
* @param {HTMLElement} target 目标元素
*/
const insertBefore = (element, target) => {
const parentNode = target.parentNode;
parentNode.insertBefore(element, target);
};
/**
* 插入至元素之后
* @param {HTMLElement} element 新元素
* @param {HTMLElement} target 目标元素
*/
const insertAfter = (element, target) => {
const parentNode = target.parentNode;
if (parentNode.lastChild === target) {
parentNode.appendChild(element);
return;
}
parentNode.insertBefore(element, target.nextSibling);
};
// 主函数
(async () => {
// 初始化缓存和 API 并绑定
const libs = initCacheAndAPI();
cache = libs.cache;
api = libs.api;
// 注册脚本菜单
registerMenu();
// 处理 commonui 模块
if (unsafeWindow.commonui) {
handleCommonui(unsafeWindow.commonui);
return;
}
Tools.interceptProperty(unsafeWindow, "commonui", {
afterSet: (value) => {
handleCommonui(value);
},
});
})();
})();