Linux.do OAuth Auto Allow

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

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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