linux.do.level

Linux.Do 查看用户信任级别以及升级条件,数据来源于 https://connect.linux.do

2025-08-13 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         linux.do.level
// @namespace    https://linux.do/u/io.oi/s/level
// @version      1.4.11
// @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 r=document.createElement("style");r.textContent=e,document.head.append(r)})(" .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:var(--d-button-border-radius);font-size:var(--base-font-size-largest)}.level-window .bg-white{background-color:var(--primary-low);border-radius:var(--d-button-border-radius);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;padding:6px}.code-box{display:flex;flex-direction:row;justify-content:space-between}.code-box .copy{padding:.5em 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;border-radius:var(--d-button-border-radius)!important;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)} ");

(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");
  }
  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;
  }
  function createDivLink(content) {
    const div = document.createElement("div");
    div.className = "submenu-link";
    div.title = "LINUX DO Connect 账号连接系统。";
    div.style.cursor = "pointer";
    div.innerHTML = content;
    return div;
  }
  function getLoadingSvg(size = 60) {
    return `<svg class="fa d-icon d-icon-envelope svg-icon svg-string" xmlns="http://www.w3.org/2000/svg" width="${size}px" height="${size}px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
              <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>`;
  }
  function showMessageBox(message, title, buttons = [
    {
      text: "确认",
      type: "btn-primary",
      onClicked: function() {
      }
    }
  ]) {
    let root = document.querySelector("div.modal-container");
    if (root) {
      let box = document.createElement("div");
      box.id = "message-box";
      box.className = "ember-view modal d-modal discard-draft-modal";
      box.setAttribute("data-keyboard", "false");
      box.setAttribute("aria-modal", "true");
      box.setAttribute("role", "dialog");
      box.innerHTML = `
        <div class="d-modal__container">
            <div class="d-modal__header">${title}</div>
            <div class="d-modal__body" tabindex="-1">
              <div class="instructions">
              ${message}
              </div>
            </div>
            <div class="d-modal__footer">
            </div>
        </div>`;
      let backdrop = document.createElement("div");
      backdrop.className = "d-modal__backdrop";
      root.appendChild(backdrop);
      let footer = box.querySelector("div.d-modal__footer");
      if (footer) {
        for (const button of buttons) {
          let btnElement = document.createElement("button");
          btnElement.className = "btn btn-text " + button.type;
          btnElement.setAttribute("type", "button");
          btnElement.innerHTML = `
               <span class="d-button-label">
                    ${button.text}
               </span>
            `;
          btnElement.addEventListener("click", () => {
            button.onClicked();
            box.remove();
            backdrop.remove();
          });
          footer.appendChild(btnElement);
        }
        root.appendChild(box);
      }
    }
  }
  class Level {
    constructor() {
      __publicField(this, "levelWindow");
      __publicField(this, "loading", false);
    }
    init() {
      this.replaceConnectAnchor();
    }
    loadDomFromString(content) {
      let parser = new DOMParser();
      return parser.parseFromString(content, "text/html").body;
    }
    showErrorAndGotoConnect(error) {
      showMessageBox(error, "错误", [{
        text: "确认",
        type: "btn-primary",
        onClicked: () => {
        }
      }, {
        text: "前往 Connect 查看",
        type: "",
        onClicked: () => {
          window.open("https://connect.linux.do/", "_blank");
        }
      }]);
    }
    getContentsFromDom(dom) {
      var _a, _b, _c;
      let title = dom.querySelector("h1.text-2xl");
      (_a = title == null ? void 0 : title.querySelector('a[href^="/logout/"]')) == 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() {
      var _a;
      let connectAnchor = document.querySelector('a[href="https://connect.linux.do"]');
      if (connectAnchor) {
        const defaultHtml = connectAnchor.innerHTML;
        const link = createDivLink(connectAnchor.innerHTML);
        link.addEventListener("click", async () => {
          if (!this.loading && this.levelWindow === void 0) {
            this.loading = true;
            link.innerHTML = `${getLoadingSvg()} Connect`;
            let result = await getLevelFromConnect();
            this.loading = false;
            link.innerHTML = defaultHtml;
            if (result.status) {
              let dom = this.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();
          }
        });
        (_a = connectAnchor.parentElement) == null ? void 0 : _a.appendChild(link);
        connectAnchor.remove();
        return;
      }
      console.error("replace connect anchor error.");
    }
    close() {
      var _a;
      (_a = this.levelWindow) == null ? void 0 : _a.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();
    }
    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;
        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 = floor.querySelectorAll("article section nav div.actions");
            const button = createFloor(id ? id : "??");
            if (actions.length > 0) {
              const i = actions.length === 2 ? 1 : 0;
              actions[i].appendChild(button);
            } else {
              console.error("query actions error.");
            }
          }
        }
        clearInterval(timer);
      });
    }
    init() {
      this.observeUrl();
    }
  }
  class Emoji {
    constructor() {
      __publicField(this, "customs", ["飞书", "小红书", "b站", "贴吧"]);
      __publicField(this, "observe", new MutationObserver(() => {
        let loadTimes = 0;
        let emojiPicker = document.querySelector("div.emoji-picker");
        if (emojiPicker) {
          let timer = setInterval(() => {
            let emojiButtons = emojiPicker.querySelector("div.emoji-picker__sections-nav");
            let emojiContainer = emojiPicker.querySelector("div.emoji-picker__sections");
            if (emojiButtons && emojiContainer) {
              for (const custom of this.customs) {
                this.moveElementToFirstBySelector(`div[data-section="${custom}"]`, emojiContainer);
                this.moveElementToFirstBySelector(`button[data-section="${custom}"]`, emojiButtons, custom === "贴吧");
              }
              clearInterval(timer);
            }
            loadTimes++;
            if (loadTimes >= 300) {
              console.warn("emoji 加载缓慢,跳过修正,下次打开表情面板即可正常显示。");
              clearInterval(timer);
            }
          });
        }
      }));
    }
    moveElementToFirstBySelector(selector, root, click = false) {
      let node = root.querySelector(selector);
      if (node) {
        root.insertBefore(node, root.children[0].nextSibling);
        if (click && node instanceof HTMLButtonElement) {
          node.click();
        }
      }
    }
    init() {
      observeDom("div#reply-control", (replay) => {
        this.onReplayOpen(replay);
      });
    }
    onReplayOpen(replay) {
      if (replay.className.includes("open")) {
        let menu = document.querySelector("div#d-menu-portals");
        if (menu) {
          this.observe.observe(menu, { childList: true });
        } else {
          console.error("querySelector:div.d-editor");
        }
      } else {
        this.observe.disconnect();
      }
    }
  }
  function init() {
    window.addEventListener("load", (event) => {
      if (event.target === document && !event.bubbles) {
        new Level().init();
        new Floor().init();
        new Emoji().init();
      }
    });
  }
  init();

})();