您需要先安装一个扩展,例如 篡改猴、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();
- })();