linux.do.level

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         linux.do.level
// @namespace    https://linux.do/u/io.oi/s/level
// @version      1.4.6
// @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 .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;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="#times"></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 getLoadingSvg(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>`;
  }
  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() {
      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 = getLoadingSvg();
              let result = await getLevelFromConnect();
              this.loading = false;
              icon.innerHTML = defaultIcon;
              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();
          }
        });
        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();
    }
    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 emojiPicker = document.querySelector("div.emoji-picker.opened");
        if (emojiPicker) {
          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);
          });
        }
      }));
    }
    moveElementToFirstBySelector(selector, root) {
      let node = root.querySelector(selector);
      if (node) {
        root.insertBefore(node, root.children[0].nextSibling);
      }
    }
    init() {
      observeDom("div#reply-control", (replay) => {
        this.onReplayOpen(replay);
      });
    }
    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();
      }
    }
  }
  function init() {
    window.addEventListener("load", () => {
      new Level().init();
      new Floor().init();
      new Emoji().init();
    });
  }
  init();

})();