Linux.do OAuth Auto Allow

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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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