您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Linux.Do 查看用户信任级别以及升级条件,数据来源于 https://connect.linux.do
当前为
// ==UserScript== // @name linux.do.level // @namespace https://linux.do/u/io.oi/s/level // @version 1.4.7 // @author LINUX.DO // @description Linux.Do 查看用户信任级别以及升级条件,数据来源于 https://connect.linux.do // @license MIT // @icon https://linux.do/uploads/default/original/3X/9/d/9dd49731091ce8656e94433a26a3ef36062b3994.png // @match https://linux.do/* // @connect connect.linux.do // @grant GM.xmlHttpRequest // @grant GM_addStyle // ==/UserScript== (e=>{if(typeof GM_addStyle=="function"){GM_addStyle(e);return}const o=document.createElement("style");o.textContent=e,document.head.append(o)})(" .level-window{position:fixed;bottom:0;background:var(--secondary);z-index:999;padding:.5em;color:var(--primary);box-shadow:0 0 4px #00000020;border:1px solid var(--primary-low)}.level-window .title .close{width:24px;height:24px;color:#fff;background:red;display:inline-block;text-align:center;line-height:24px;float:right;cursor:pointer;border-radius:4px;font-size:var(--base-font-size-largest)}.level-window .bg-white{background-color:var(--primary-low);border-radius:.5em;padding:.5em;margin-top:.5em}.level-window h1{color:var(--primary);font-size:1.3rem}.level-window h2{font-size:1.25rem}.mb-4 table tr:nth-child(2n){background-color:var(--tertiary-400)}.level-window .text-red-500{color:#ef4444}.level-window .text-green-500{color:#10b981}.level-window .mb-4 table tr td{padding:4px 8px}.language-text{background:var(--primary-very-low);font-family:var(--d-font-family--monospace);font-size:var(--base-font-size-smallest);flex-grow:1}.code-box{display:flex;flex-direction:row;justify-content:space-between}.code-box .language-text{padding:.6em 1em}.code-box .copy{padding:.6em 1em;cursor:pointer;-webkit-user-select:none;user-select:none;font-size:var(--base-font-size-smallest);background:var(--secondary)}.connect-button{width:100%;padding:.5em;margin-top:.5em!important}.emoji-picker-category-buttons,.emoji-picker-emoji-area{justify-content:center;padding-left:initial}.emoji-picker-category-buttons::-webkit-scrollbar,.emoji-picker-emoji-area::-webkit-scrollbar{width:5px;height:auto;background:var(--primary)}.emoji-picker-category-buttons::-webkit-scrollbar-thumb,.emoji-picker-emoji-area::-webkit-scrollbar-thumb{box-shadow:inset 0 0 5px #0003;background:var(--secondary)}.emoji-picker-category-buttons::-webkit-scrollbar-track,.emoji-picker-emoji-area::-webkit-scrollbar-track{box-shadow:inset 0 0 5px #0003;background:var(--primary-low)}.floor-text{color:var(--tertiary)}.modal-container .published-page-content-body td{padding:.5em}.modal-container .d-modal__body::-webkit-scrollbar{width:1em;background:transparent;scrollbar-color:rgba(0,0,0,0) transparent;transition:scrollbar-color .25s ease-in-out;transition-delay:.5s}.modal-container .d-modal__body::-webkit-scrollbar-thumb{background:var(--primary-low)} "); (function () { 'use strict'; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var _GM = /* @__PURE__ */ (() => typeof GM != "undefined" ? GM : void 0)(); async function getLevelFromConnect() { return await new Promise((resolve, reject) => { _GM.xmlHttpRequest({ method: "GET", url: "https://connect.linux.do", onload: (response) => { let regx = /<body[^>]*>([\s\S]+?)<\/body>/i; let contents = regx.exec(response.responseText); if (contents) { resolve({ status: true, content: contents[1], error: "" }); } }, onerror: (e) => { reject({ status: false, error: e.error, content: "" }); } }); }); } function observeDom(selector, onChanged, option) { let dom = typeof selector === "string" ? document.querySelector(selector) : selector; if (dom) { const observer = new MutationObserver(() => { onChanged(dom); }); observer.observe(dom, { childList: true }); return observer; } else { console.error(`query dom error: [${selector}]`); return null; } } function isOnTopicPage() { return window.location.href.includes("https://linux.do/t/topic"); } function loadDomFromString(content) { let parser = new DOMParser(); return parser.parseFromString(content, "text/html").body; } function getHtmlBody(html) { let regx = /<body[^>]*>([\s\S]+?)<\/body>/i; let contents = regx.exec(html); if (contents) { return loadDomFromString(contents[1]); } return null; } const _DomEventBus = class _DomEventBus { constructor() { __publicField(this, "listenerMap"); __publicField(this, "observeMap"); this.listenerMap = {}; this.observeMap = {}; } static getInstance() { if (!this.instance) { this.instance = new _DomEventBus(); } return this.instance; } /** * 监听事件 * @param name 事件名称 * @param listener 事件监听器 * @param dom 如果为 null,使用事件名称查找 dom, 不为空直接使用给定的 dom */ add(name, listener, dom = null) { if (!this.listenerMap[name]) { this.listenerMap[name] = []; } if (this.listenerMap[name].length === 0) { let observe = dom === null ? observeDom(name, () => { this.domEmit(name); }) : observeDom(dom, () => { this.domEmit(name); }); if (observe) { this.observeMap[name] = observe; } } this.listenerMap[name].push(listener); } domEmit(event) { const listeners = this.listenerMap[event]; if (listeners) { for (const listener of listeners) { listener(); } } } emit(name) { this.domEmit(name); } clear(name) { if (!this.listenerMap[name]) { return; } this.listenerMap[name] = []; } }; __publicField(_DomEventBus, "instance"); let DomEventBus = _DomEventBus; function createCodeElement(key) { var _a; let realKey = key; let copied = false; let root = document.createElement("div"); root.className = "bg-white p-6 rounded-lg mb-4 shadow"; root.innerHTML = ` <h2>DeepLX Api Key</h2> <div class="code-box"> <span class="hljs language-text">${key.replace(key.substring(12, 21), "**加密**")}</span> </div> `; let copyButton = document.createElement("span"); copyButton.className = "copy"; copyButton.innerHTML = "复制"; copyButton.addEventListener("click", async () => { if (!copied) { await navigator.clipboard.writeText(realKey); copied = true; copyButton.innerHTML = "已复制"; let timer = setTimeout(() => { copied = false; copyButton.innerHTML = "复制"; clearInterval(timer); }, 2e3); } }); (_a = root.querySelector("div.code-box")) == null ? void 0 : _a.appendChild(copyButton); let connectButton = document.createElement("a"); connectButton.className = "btn btn-primary connect-button"; connectButton.href = "https://connect.linux.do"; connectButton.target = "_blank"; connectButton.innerHTML = "前往 Connect 站"; root.appendChild(connectButton); return root; } function createWindow(title, key, levelTable, onClose) { let root = document.createElement("div"); root.setAttribute("id", "level-window"); root.className = "level-window"; root.style.right = document.querySelector("div.chat-drawer.is-expanded") ? "430px" : "15px"; root.innerHTML = ` <div class="title"> <span class="close" id="close-button"> <svg class="fa d-icon d-icon-times svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"> <use href="#xmark"></use> </svg> </span> <div id="content" class="content"></div> </div>`; let window2 = root.querySelector("div#content"); if (window2) { window2.appendChild(title); window2.appendChild(createCodeElement(key)); window2.appendChild(levelTable); } let close = root.querySelector("span#close-button"); close == null ? void 0 : close.addEventListener("click", onClose); DomEventBus.getInstance().add("div.chat-drawer-outlet-container", () => { let chat = document.querySelector("div.chat-drawer.is-expanded"); root.style.right = chat ? "430px" : "15px"; }); let chatContainer = document.querySelector("div.chat-drawer-outlet-container"); if (chatContainer) { let observer = new MutationObserver((_) => { let chat = document.querySelector("div.chat-drawer.is-expanded"); root.style.right = chat ? "430px" : "15px"; }); observer.observe(chatContainer, { childList: true }); } return root; } class Modal { constructor(title, content, maxWidth = null) { __publicField(this, "element"); __publicField(this, "backdrop"); __publicField(this, "title"); __publicField(this, "container"); __publicField(this, "isShow", false); this.maxWidth = maxWidth; this.title = title; this.element = this.initElement(); this.backdrop = this.createBackdrop(); this.container = this.queryContainer(); this.setContent(content); } initElement() { let root = document.createElement("div"); root.id = "custom-modal"; root.className = "ember-view modal d-modal discard-draft-modal"; root.setAttribute("data-keyboard", "false"); root.setAttribute("aria-modal", "true"); root.setAttribute("role", "dialog"); root.innerHTML = ` <div class="d-modal__container" ${this.maxWidth === null ? "" : `style="max-width:${this.maxWidth}px;"`}> <div class="d-modal__header"> <div class="d-modal__title"> <h1 id="discourse-modal-title" class="d-modal__title-text">${this.title}</h1> </div> <button id="m-close-btn" class="btn no-text btn-icon btn-transparent modal-close" title="关闭" type="button"> <svg class="fa d-icon d-icon-times svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"> <use href="#times"></use> </svg> </button> </div> <div class="d-modal__body" tabindex="-1"> <div class="instructions"> </div> </div> <div class="d-modal__footer"> </div> </div>`; const close = root.querySelector("button#m-close-btn"); close == null ? void 0 : close.addEventListener("click", () => this.close()); return root; } queryContainer() { return document.querySelector("div.modal-container"); } setContent(content) { const root = this.element.querySelector("div.instructions"); const contentElement = typeof content === "string" ? loadDomFromString(content) : content; root == null ? void 0 : root.appendChild(contentElement); } setFooter(footer) { var _a; (_a = this.element.querySelector("div.d-modal__footer")) == null ? void 0 : _a.appendChild(footer); } show() { if (this.container) { this.container.appendChild(this.element); this.container.appendChild(this.backdrop); this.isShow = true; } } close() { if (this.isShow && this.container) { this.container.removeChild(this.element); this.container.removeChild(this.backdrop); this.isShow = false; } } createBackdrop() { const back = document.createElement("div"); back.className = "d-modal__backdrop"; return back; } } class MessageBox { constructor(title, content, buttons = [ { text: "确认", type: "btn-primary", onclick: void 0 } ]) { __publicField(this, "title", "MessageBox"); __publicField(this, "content"); __publicField(this, "buttons"); this.title = title; this.content = content; this.buttons = buttons; } show() { const modal = new Modal(this.title, this.content); function createButton({ onclick, text, type }) { let button = document.createElement("button"); button.className = "btn btn-text " + type; button.setAttribute("type", "button"); button.innerHTML = ` <span class="d-button-label"> ${text} </span>`; button.addEventListener("click", () => { if (onclick) { onclick(); } modal.close(); }); return button; } for (const btn of this.buttons) { modal.setFooter(createButton(btn)); } modal.show(); } } class Icons { static getLoading(size = 60) { return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}px" height="${size}px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="lds-ring"> <circle cx="50" cy="50" r="30" stroke="#B3B5B411" stroke-width="10" fill="none"/> <circle cx="50" cy="50" r="30" stroke="#808281" stroke-width="10" fill="none" transform="rotate(144 50 50)"> <animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"/> <animate attributeName="stroke-dasharray" calcMode="linear" values="18.84955592153876 169.64600329384882;94.2477796076938 94.24777960769377;18.84955592153876 169.64600329384882" keyTimes="0;0.5;1" dur="1" begin="0s" repeatCount="indefinite"/> </circle> </svg>`; } } class Level { constructor() { __publicField(this, "levelWindow"); __publicField(this, "loading", false); this.replaceConnectAnchor(); } showErrorAndGotoConnect(error) { const message = new MessageBox("错误", error, [ { text: "确认", type: "btn-primary" }, { text: "前往 Connect 查看", type: "", onclick: () => { window.open("https://connect.linux.do/", "_blank"); } } ]); message.show(); } getContentsFromDom(dom) { var _a, _b, _c; let title = dom.querySelector("h1.text-2xl"); (_a = dom.querySelector("h1.text-2xl a.text-blue-500")) == null ? void 0 : _a.remove(); let levelTable = (_b = dom.querySelector("div.bg-white.p-6.rounded-lg.mb-4.shadow table")) == null ? void 0 : _b.parentElement; let key = (_c = dom.querySelector("div.bg-white.p-6.rounded-lg.mb-4.shadow p strong")) == null ? void 0 : _c.innerHTML; let status = key !== void 0 && levelTable !== null; return { status, key, title, content: levelTable, error: status ? null : "解析 Connect 数据错误。" }; } replaceConnectAnchor() { let connectAnchor = document.querySelector('a[href="https://connect.linux.do"]'); if (connectAnchor) { connectAnchor.href = "javascript:void(0);"; connectAnchor.addEventListener("click", async () => { if (!this.loading && this.levelWindow === void 0) { this.loading = true; let icon = connectAnchor.querySelector("span.sidebar-section-link-prefix.icon"); if (icon) { let defaultIcon = icon.innerHTML; icon.innerHTML = Icons.getLoading(); let result = await getLevelFromConnect(); this.loading = false; icon.innerHTML = defaultIcon; if (result.status) { let dom = loadDomFromString(result.content); let body = this.getContentsFromDom(dom); if (body.status) { this.levelWindow = createWindow(body.title, body.key, body.content, () => { this.close(); }); document.body.appendChild(this.levelWindow); } else { this.showErrorAndGotoConnect(body.error); } } else { this.showErrorAndGotoConnect(result.error); } } } else { this.close(); } }); return; } console.error("replace connect anchor error."); } close() { this.levelWindow.remove(); this.levelWindow = void 0; } } function createFloor(num) { let button = document.createElement("button"); button.className = "widget-button btn-flat reply create fade-out btn-icon-text"; button.setAttribute("title", `${num}楼`); button.setAttribute("id", "floor-button"); button.innerHTML = `<span class='d-button-label floor-text'>#${num}</span>`; return button; } class Floor { constructor() { __publicField(this, "eventBus"); this.eventBus = DomEventBus.getInstance(); this.observeUrl(); } observeUrl() { const changed = () => { const timer = setInterval(() => { if (isOnTopicPage()) { this.eventBus.add("div.post-stream", () => { if (isOnTopicPage()) { this.fixFloorDom(); } }); this.fixFloorDom(); } else { this.eventBus.clear("div.post-stream"); } clearInterval(timer); }); }; this.eventBus.add("div#main-outlet", changed); if (isOnTopicPage()) { this.eventBus.emit("div#main-outlet"); } } fixFloorDom() { let timer = setInterval(() => { var _a, _b; let floors = Array.from(document.querySelectorAll("div.container.posts section.topic-area div.ember-view div.topic-post")); for (const floor of floors) { if (floor.querySelector("button#floor-button")) { continue; } let article = floor.querySelector("article"); if (article) { let id = (_a = article.getAttribute("id")) == null ? void 0 : _a.replace("post_", ""); let actions = Array.from(floor.querySelectorAll("article section nav div.actions")); const button = createFloor(id ?? "??"); (_b = actions.at(-1)) == null ? void 0 : _b.appendChild(button); } } clearInterval(timer); }); } } class Emoji { constructor() { __publicField(this, "customs", ["飞书", "小红书", "b站", "贴吧"]); __publicField(this, "observe", new MutationObserver(() => { let emojiPicker = document.querySelector("div.emoji-picker.opened"); if (!emojiPicker) { return; } let timer = setInterval(() => { let emojiButtons = emojiPicker.querySelector("div.emoji-picker-category-buttons"); let emojiContainer = emojiPicker.querySelector("div.emojis-container"); if (emojiButtons && emojiContainer) { for (const custom of this.customs) { this.moveElementToFirstBySelector(`button[data-section="custom-${custom}"]`, emojiButtons); this.moveElementToFirstBySelector(`div[data-section="custom-${custom}"]`, emojiContainer); } } clearInterval(timer); }); })); observeDom("div#reply-control", (replay) => { this.onReplayOpen(replay); }); } moveElementToFirstBySelector(selector, root) { let node = root.querySelector(selector); if (node) { root.insertBefore(node, root.children[0].nextSibling); } } onReplayOpen(replay) { if (replay.className.includes("open")) { let editor = replay.querySelector("div.d-editor"); if (editor) { this.observe.observe(editor, { childList: true }); } else { console.error("querySelector:div.d-editor"); } } else { this.observe.disconnect(); } } } class FriendLinks { constructor() { __publicField(this, "loading", false); __publicField(this, "defaultIcon", ""); __publicField(this, "loadingIcon", Icons.getLoading()); __publicField(this, "icon"); __publicField(this, "modal"); this.icon = this.replaceFriendAnchor(); } setLoading(loading) { this.loading = loading; this.icon.innerHTML = this.loading ? this.loadingIcon : this.defaultIcon; } replaceFriendAnchor() { const anchor = document.querySelector('a[href="/pub/friend-links"]'); if (!anchor) { throw new Error("query friend link error."); } anchor.href = "javascript:void(0);"; const icon = anchor.querySelector("span.sidebar-section-link-prefix.icon"); if (!icon) throw new Error("query friend link icon error"); this.defaultIcon = icon.innerHTML; anchor.addEventListener("click", () => this.handleClick()); return icon; } async handleClick() { var _a, _b; if (this.loading) return; this.setLoading(true); if ((_a = this.modal) == null ? void 0 : _a.isShow) { this.modal.close(); this.modal = void 0; this.setLoading(false); return; } try { const response = await fetch("/pub/friend-links"); if (!response.ok) throw new Error(`fetch friend links error: ${response.statusText}`); const text = await response.text(); const body = getHtmlBody(text); if (!body) throw new Error("get html body error"); (_b = body.querySelector("div.published-page-header")) == null ? void 0 : _b.remove(); this.modal = new Modal("友链", body, 900); this.modal.show(); } catch (error) { console.error(error); } finally { this.setLoading(false); } } } function init() { window.addEventListener("load", () => { new Level(); new FriendLinks(); new Floor(); new Emoji(); }); } init(); })();