linux.do.level

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

当前为 2024-12-11 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();

})();