Linux.do OAuth Auto Allow

自动允许 Linux.do OAuth 授权,记住已允许的网站,优化版

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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 OAuth Auto Allow
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  自动允许 Linux.do OAuth 授权,记住已允许的网站,优化版
// @author       [email protected]
// @match        https://connect.linux.do/oauth2/authorize*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  const STORAGE_KEY = "linux_do_oauth_allowed_sites_v2";
  let currentSiteName = null;
  let currentSiteUrl = null;
  let isProcessing = false;

  function getAllowedSites() {
    const data = GM_getValue(STORAGE_KEY, "[]");
    try {
      return JSON.parse(data);
    } catch (e) {
      return [];
    }
  }

  function saveAllowedSite(siteName) {
    const sites = getAllowedSites();
    const exists = sites.find((s) => s.name === siteName);
    if (!exists) {
      sites.push({
        name: siteName,
        allowedAt: new Date().toISOString(),
      });
      GM_setValue(STORAGE_KEY, JSON.stringify(sites));
      updateStatusPanel(true);
    }
  }

  function removeAllowedSite(siteName) {
    let sites = getAllowedSites();
    sites = sites.filter((s) => s.name !== siteName);
    GM_setValue(STORAGE_KEY, JSON.stringify(sites));
    updateStatusPanel(false);
  }

  function isAllowed(siteName) {
    const sites = getAllowedSites();
    return sites.some((s) => s.name === siteName);
  }

  function getAppInfo() {
    let name = "未知应用";
    let url = "";

    const appLink = document.querySelector('a[href*="://"]');
    if (appLink) {
      url = appLink.href;
      const linkText = appLink.textContent.trim();
      if (linkText && linkText.length < 50) name = linkText;
    }

    if (name === "未知应用") {
      const h2 = document.querySelector("h2");
      if (h2) {
        const text = h2.textContent.trim();
        const match = text.match(/"([^"]+)"/);
        if (match) name = match[1];
        else {
          const firstPart = text.split(/[\n\r]/)[0].trim();
          if (firstPart && firstPart.length < 50) name = firstPart;
        }
      }
    }

    if (!url) {
      const linkEl = document.querySelector('[class*="url"], [class*="link"]');
      if (linkEl) url = linkEl.textContent.trim();
    }

    return { name, url };
  }

  function getSiteName() {
    return getAppInfo().name;
  }

  function createStatusPanel() {
    const existing = document.getElementById("oauth-status-panel");
    if (existing) existing.remove();

    const panel = document.createElement("div");
    panel.id = "oauth-status-panel";
    panel.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: rgba(0,0,0,0.75);
            backdrop-filter: blur(10px);
            border-radius: 8px;
            padding: 12px 14px;
            z-index: 999999;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            color: white;
            font-size: 13px;
            min-width: 180px;
            max-width: 280px;
            cursor: pointer;
            transition: all 0.2s;
        `;

    panel.innerHTML = `
            <div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
                <span id="oauth-status-icon">⏳</span>
                <span id="oauth-status-text">检测中...</span>
            </div>
            <div id="oauth-site-name" style="font-weight:500;font-size:14px;word-break:break-all;line-height:1.3;"></div>
            <div id="oauth-site-url" style="opacity:0.6;font-size:11px;word-break:break-all;margin-top:4px;"></div>
        `;

    panel.addEventListener("click", () => {
      if (isAllowed(currentSiteName)) {
        removeAllowedSite(currentSiteName);
      } else {
        saveAllowedSite(currentSiteName);
      }
    });

    panel.title = "点击切换自动授权";
    document.body.appendChild(panel);
  }

  function updateStatusPanel(allowed) {
    const icon = document.getElementById("oauth-status-icon");
    const text = document.getElementById("oauth-status-text");
    const siteName = document.getElementById("oauth-site-name");
    const siteUrl = document.getElementById("oauth-site-url");
    const panel = document.getElementById("oauth-status-panel");

    if (!icon || !text) return;

    if (allowed) {
      icon.textContent = "✓";
      text.textContent = "自动授权";
      panel.style.background = "rgba(16,185,129,0.85)";
    } else {
      icon.textContent = "⏳";
      text.textContent = "待授权";
      panel.style.background = "rgba(0,0,0,0.75)";
    }

    if (currentSiteName) {
      siteName.textContent = currentSiteName;
    }
    if (currentSiteUrl) {
      siteUrl.textContent = currentSiteUrl;
    }
  }

  function isAllowButton(element) {
    const text = (element.textContent || element.value || "")
      .toLowerCase()
      .trim();
    const allowKeywords = [
      "允许",
      "authorize",
      "approve",
      "同意",
      "授权",
      "确认",
      "授权登录",
      "确认授权",
      "登录",
      "login",
      "登入",
    ];
    return allowKeywords.some((kw) => text.includes(kw));
  }

  function isDenyButton(element) {
    const text = (element.textContent || element.value || "")
      .toLowerCase()
      .trim();
    const denyKeywords = [
      "cancel",
      "取消",
      "拒绝",
      "deny",
      "reject",
      "不同意",
      "decline",
    ];
    return denyKeywords.some((kw) => text.includes(kw));
  }

  function handleButtonClick(e) {
    if (isProcessing) return;

    const target = e.target.closest(
      'button, input[type="submit"], input[type="button"], a.btn, [class*="btn"], [class*="button"]',
    );
    if (!target) return;

    if (isAllowButton(target)) {
      isProcessing = true;
      saveAllowedSite(currentSiteName);

      const panel = document.getElementById("oauth-status-panel");
      if (panel) {
        panel.style.transform = "scale(1.02)";
        setTimeout(() => {
          panel.style.transform = "scale(1)";
        }, 200);
      }

      setTimeout(() => {
        isProcessing = false;
      }, 1000);
    } else if (isDenyButton(target)) {
      if (isAllowed(currentSiteName)) {
        removeAllowedSite(currentSiteName);
      }
    }
  }

  function autoClickAllow() {
    const allowKeywords = [
      "允许",
      "authorize",
      "approve",
      "同意",
      "授权",
      "确认",
      "授权登录",
      "确认授权",
      "登录",
      "login",
      "登入",
    ];
    const denyKeywords = ["cancel", "取消", "拒绝", "deny"];

    const buttons = document.querySelectorAll(
      'button, input[type="submit"], input[type="button"], a.btn, [class*="btn"], [class*="button"]',
    );

    for (const btn of buttons) {
      const text = (btn.textContent || btn.value || "").toLowerCase().trim();

      const isAllow = allowKeywords.some((kw) => text.includes(kw));
      const isDeny = denyKeywords.some((kw) => text.includes(kw));

      if (isAllow && !isDeny) {
        btn.click();
        return true;
      }
    }

    return false;
  }

  function showNotification(message, type = "success") {
    const notification = document.createElement("div");
    const bgColor =
      type === "success" ? "rgba(16,185,129,0.9)" : "rgba(239,68,68,0.9)";
    notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: ${bgColor};
            backdrop-filter: blur(10px);
            color: white;
            padding: 8px 14px;
            border-radius: 6px;
            font-size: 13px;
            z-index: 1000000;
            transform: translateX(120%);
            transition: transform 0.3s ease;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        `;
    notification.textContent = message;
    document.body.appendChild(notification);

    setTimeout(() => (notification.style.transform = "translateX(0)"), 10);
    setTimeout(() => {
      notification.style.transform = "translateX(120%)";
      setTimeout(() => notification.remove(), 300);
    }, 2000);
  }

  function init() {
    const info = getAppInfo();
    currentSiteName = info.name;
    currentSiteUrl = info.url;

    if (!currentSiteName || currentSiteName === "未知应用") {
      setTimeout(init, 500);
      return;
    }

    createStatusPanel();

    const allowed = isAllowed(currentSiteName);
    updateStatusPanel(allowed);

    document.addEventListener("click", handleButtonClick, true);

    if (allowed) {
      setTimeout(() => {
        if (isAllowed(currentSiteName)) {
          autoClickAllow();
          showNotification(`已自动授权: ${currentSiteName}`);
        }
      }, 3000);
    }
  }

  GM_registerMenuCommand("📋 查看已允许的网站", () => {
    const sites = getAllowedSites();

    if (sites.length === 0) {
      showNotification("暂无已允许的网站", "info");
      return;
    }

    const panel = document.getElementById("oauth-status-panel");
    const existingList = document.getElementById("oauth-sites-list");
    if (existingList) {
      existingList.remove();
      return;
    }

    const listDiv = document.createElement("div");
    listDiv.id = "oauth-sites-list";
    listDiv.style.cssText = `
            margin-top: 16px;
            padding-top: 16px;
            border-top: 1px solid rgba(255,255,255,0.2);
            max-height: 200px;
            overflow-y: auto;
        `;

    listDiv.innerHTML = sites
      .map(
        (s, i) => `
            <div style="
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 10px;
                background: rgba(255,255,255,0.1);
                border-radius: 8px;
                margin-bottom: 8px;
                font-size: 12px;
            ">
                <div style="flex: 1; min-width: 0;">
                    <div style="font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${s.name}</div>
                    <div style="opacity: 0.7; font-size: 10px;">${new Date(s.allowedAt).toLocaleDateString()}</div>
                </div>
                <button class="oauth-remove-item" data-index="${i}" style="
                    margin-left: 8px;
                    padding: 6px 12px;
                    background: rgba(239, 68, 68, 0.3);
                    border: 1px solid rgba(239, 68, 68, 0.5);
                    border-radius: 6px;
                    font-size: 11px;
                    color: white;
                    cursor: pointer;
                    transition: all 0.2s;
                ">删除</button>
            </div>
        `,
      )
      .join("");

    panel.appendChild(listDiv);

    listDiv.querySelectorAll(".oauth-remove-item").forEach((btn) => {
      btn.addEventListener("click", (e) => {
        const idx = parseInt(e.target.dataset.index);
        const sites = getAllowedSites();
        const removed = sites.splice(idx, 1)[0];
        GM_setValue(STORAGE_KEY, JSON.stringify(sites));

        if (removed.name === currentSiteName) {
          updateStatusPanel(false);
        }

        listDiv.remove();
        showNotification(`已移除: ${removed.name}`);
      });

      btn.addEventListener("mouseenter", function () {
        this.style.background = "rgba(239, 68, 68, 0.5)";
      });
      btn.addEventListener("mouseleave", function () {
        this.style.background = "rgba(239, 68, 68, 0.3)";
      });
    });
  });

  GM_registerMenuCommand("🗑️ 清除所有记录", () => {
    GM_setValue(STORAGE_KEY, "[]");
    updateStatusPanel(false);
    showNotification("已清除所有记录");
  });

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();