RSS+ : Show Site All RSS

View Available RSS Feeds (If Any)

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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         RSS+ : Show Site All RSS
// @name:zh      RSS+ : 显示当前网站所有的 RSS
// @name:zh-CN   RSS+ : 显示当前网站所有的 RSS
// @name:zh-TW   RSS+ : 顯示當前網站所有的 RSS
// @name:ja      RSS+ : 現在のサイトのRSSを表示
// @name:ko      RSS+ : 현재 사이트의 RSS 표시
// @name:pt-PT   RSS+ : Mostrar todos os RSS do site
// @name:pt-BR   RSS+ : Mostrar todos os RSS do site
// @name:fr      RSS+ : Afficher tous les flux RSS du site
// @description         View Available RSS Feeds (If Any)
// @description:zh      显示当前网站的所有 RSS(如果有的话)
// @description:zh-CN   显示当前网站的所有 RSS(如果有的话)
// @description:zh-TW   顯示當前網站的所有 RSS(如果有的话)
// @description:ja      サイトのすべてのRSSを表示します (あれば)
// @description:ko      웹 사이트의 모든 RSS 를 표시합니다 (있는 경우)
// @description:pt-PT   Mostra todos os feeds RSS do site (se houver)
// @description:pt-BR   Mostra todos os feeds RSS do site (se houver)
// @description:fr      Montre tous les flux RSS du site (s'il y en a)
// @license      GPL3.0
// @version      1.2.1

// @icon         data:image/png;base64,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
// @author       Wizos
// @namespace    https://blog.wizos.me
// @supportURL   [email protected]
// @match        http://*/*
// @match        https://*/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/qrcode-generator/1.4.4/qrcode.min.js
// @resource     RulerJs  https://fastly.jsdelivr.net/gh/wizos/rssplus/Ruler.js
// @grant        GM_xmlhttpRequest
// @grant        GM_getResourceText
// @grant        GM_setClipboard
// @grant        GM_notification
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @connect      rssplus.vercel.app
// @connect      rssplus.pages.dev
// @connect      rssfind.val.run
// @connect      rssplus.deno.dev
// @connect      rssplus.mdict.workers.dev
// @noframes
// @run-at     document-idle
// ==/UserScript==

// 2025.10.09_1.2.1  增加适应浅色、深色主题,去掉整体阴影效果。
// 2025.10.06_1.2.0  1.将iframe框架改为shadow DOM,防止主网页的CSS干扰本控件。2.计数按钮支持长按后拖动位置,释放后自动贴边。3.在RSS列表关闭按钮的左侧增加设置按钮,点击后弹出对话框。4.整体保持响应式设计。5.根据按钮位置调整展开方向,始终保证容器在可视区域内。
// 2025.09.23_1.1.5  1.调整匹配 RSS 网址的正则,使其兼容性强、更容易理解;2.所有发现的 RSS 源都进行有效性检测,并处理重定向。
// 2025.06.29_1.1.4  1.由于流量过大,增加 rssplus.pages.dev, rssfind.val.run, rssplus.deno.dev, rssplus.mdict.workers.dev 域名。2.修复无法使用自定义 RSSHub 域名。3.增加 Folo 订阅。
// 2025.04.05_1.1.3  1.修复火狐136版本无法查看 RSS 问题。2. 支持 NewsBlur 和 The Old Reader。3. 全屏时隐藏图标。4. 保持订阅源 URL 大小写。
// 2025.01.16_1.1.2  1.使用 TTP 给元素增加 innerHTML。2、增加识别 feed.json。
// 2024.10.20_1.1.1  1.修复未开启远程规则的问题。2.增加更多的网站适配。
// 2024.10.08_1.0.9  1.增加支持葡萄牙语和法语(感谢Filipe Mota (BlackSpirits)提供的翻译)。2.支持 RSSHub 访问密钥。3.重构代码。
// 2022.10.18_1.0.8  1.修复 TinyTinyRSS 订阅地址错误问题。2.修复 Bug。
// 2022.07.04_1.0.7  支持用Miniflux订阅(感谢Sevichecc提供的代码https://gist.github.com/Sevichecc/f5608c4ad52e71d98f6fcf74110369df)
// 2022.07.04_1.0.6  修复火狐上因为GSAP库导致无法使用问题
// 2022.05.20_1.0.5  1.解决 jsdelivr 在中国被墙的问题。2.修复bilibili video页面获取feed标题异常问题。
// 2022.04.18_1.0.4  修复导致网页加载卡顿的问题。
// 2022.04.05_1.0.2  1.受unsafe-eval影响,无法本地执行规则时使用远程规则,改用 rssplus.vercel.app 接口。2.调整 UI,每次获取到新 RSS 都同步到 UI 中。3.精简设置项。4.监听 URL 变化,同步获取新的 RSS。
// 2022.04.02_1.0.0  1.将远程规则放到 GitHub。2.修复订阅 RSS 网址的转义问题。
// 2021.12.17_0.9.2  1.支持设置 FreshRSS 一键订阅。2.支持设置带端口的网址。
// 2021.02.24_0.9.1  1.支持开启/关闭二维码。
// 2021.02.19_0.9.0  1.支持鼠标悬停在订阅链接上时展示其二维码,方便扫码订阅。
// 2021.02.05_0.8.1  1.url参数用base64编码,防止服务端获取url参数时,漏掉query部分的数据。
// 2021.02.03_0.8.0  1.支持小屏幕展示。2.支持设置 TinyTinyRSS 服务的域名。
// 2021.01.05_0.7.3  1.改用 GM_xmlhttpRequest。2.改用 rssfinder.vercel.app 接口。
// 2020.12.16_0.7.2  1.修复 bug。
// 2020.12.06_0.7.1  1.调整搞定。2.优化代码。
// 2020.11.16_0.7.0  1.支持设置 InoReader 服务的域名。2.在打印页面时隐藏。3.修复影响页面样式的问题。
// 2020.09.11_0.6.4  1.支持 RSSHub 服务器为 IP 地址。2.被识别的 RSS 地址不再转换为小写(因为 news.google.com 的小写地址打不开)
// 2020.04.28_0.6.3  修复改了脚本name导致无法更新的bug。
// 2020.04.27_0.6.2  修复rsshub domain默认为undefined的bug。
// 2020.04.26_0.6.1  支持设置 RSSHub 服务的域名。
// 2020.03.01_0.6    1.可设置点击"订阅"时打开的rss服务商(feedly,inoreadly)。2.修复火狐浏览器下无法展示的问题。
// 2019.09.29_0.5    增加hexo站点的rss嗅探规则。
// 2019.04.26_0.4.2  1.修复默认圆圈状态下宽度太宽,导致遮挡下层页面事件触发的问题。2.将icon由字体改为svg形式,修复部分站点无法显示icon的问题。3.优化RSS没有title时的默认名称。
// 2018.11.10_0.4.1  关闭发现RSS后的h5通知
// 2018.10.29_0.4    1.在无法链接服务器时也能展示本地的RSS;2.针对开启 Content-Security-Policy 的网站直接展示本地的RSS;3.发现RSS后,进行h5通知
// 2018.10.23_0.4    1.增加识别为 wordpress 站点时,尝试使用/feed后缀;2.增加多语言支持
// 2018.10.16_0.3    1.改为iframe方式显示,兼容性更好;2.改为post方式传递页面地址;
// 2018.10.14_0.2    第一版 RSS+ 成型;
// 2018.09.16_0.1    在 RSS+Atom Feed Subscribe Button Generator 脚本基础上增加连接后端获取feed的方式;


(function() {
  'use strict';

  // 过滤掉明确不包含 RSS 源的URL
  if (location.href.match(/(api\.wizos\.me)|(feedly\.com\/i\/subscription)|(inoreader\.com\/feed\/http)/i)) {
    return;
  }

  // 常量定义
  const CONSTANTS = {
    // 国际化文本
    I18N: {
      zh: {
        noTitle: "无标题",
        copied: "已复制",
        copy: "复制",
        copySucceeded: "复制成功",
        follow: "订阅",
        found: "发现 ",
        feed: "订阅源数量:",
        clickToView: "点击右下角的数字查看",
        subscriptionMode: "订阅方式",
        settingRSShubDomain: "设置 RSSHub 的域名",
        settingRSShubAccessKey: "设置 RSSHub 的访问密钥",
        settingInoreaderDomain: "设置 Inoreader 的域名",
        settingTinytinyrssDomain: "设置 Tiny Tiny RSS 的域名",
        settingFreshrssDomain: "设置 FreshRSS 的域名",
        settingMinifluxDomain: "设置 Miniflux 的域名",
        domainIsWrong: "服务器地址格式有误,请检查",
        enableQRCode: "启用二维码",
        enabled: "已启用",
        disabled: "已禁用",
        setting: "设置",
        confirm: "确认",
        close: "关闭"
      },
      en: {
        noTitle: "Untitled",
        copied: "Copied",
        copy: "Copy",
        copySucceeded: "Copy succeeded",
        follow: "Subscribe",
        found: "Found ",
        feed: "Number of feeds: ",
        clickToView: "Click the number in the bottom right to view",
        subscriptionMode: "Subscription mode",
        settingRSShubDomain: "Set RSSHub domain",
        settingRSShubAccessKey: "Set RSSHub access key",
        settingInoreaderDomain: "Set Inoreader domain",
        settingTinytinyrssDomain: "Set Tiny Tiny RSS domain",
        settingFreshrssDomain: "Set FreshRSS domain",
        settingMinifluxDomain: "Set Miniflux domain",
        domainIsWrong: "Error in domain name format. Please check",
        enableQRCode: "Enable QR code",
        enabled: "Enabled",
        disabled: "Disabled",
        setting: "Setting",
        confirm: "Confirm",
        close: "Close"
      }
    },

    // RSS内容类型
    FEED_CONTENT_TYPES: [
      'application/rss+xml',
      'application/atom+xml',
      'application/rdf+xml',
      'application/xml',
      'application/feed+json',
      'application/json',
      'text/xml'
    ],

    // RSS后缀
    FEED_SUFFIXES: [
      '/feed', '/rss', '/rss.xml', '/atom.xml', '/feed.xml', '/rss.json', '/atom.json', '/feed.json',
      '/?feed=rss', '/?feed=rss2', '/blog/feed', '/blog/rss', '/latest/rss',
      '/news/atom', '/feed/index.xml'
    ],

    // 代理列表
    PROXY_LIST: [
      "https://rssplus.vercel.app/",
      "https://rssplus.pages.dev/",
      "https://rssfind.val.run/",
      "https://rssplus.deno.dev/",
      "https://rssplus.mdict.workers.dev/"
    ],

    // RSS模式 - 借鉴附件中的正则表达式
    RSS_PATTERNS: [
      // 子域名风格
      /(?:https?:\/\/)?(?:feeds?|rss2?|atom2?)\.[a-z0-9-]+\.[a-z]{2,}(?::\d{2,5})?(?:\/[^\s?#]*)?(?:[\s?#][^\s]*)?/i,
      // 路径/文件名风格(相对/绝对均可)
      /(^|[\/.])(feeds?|rss2?|atom2?)(?=[\/?#]|\.(json|xml|rdf|jsp|aspx|php)|$)/i,
      // 有些站点写的比较特殊,会用 index.xml, index.json 作为 rss 源
      /(^|[\/.])index(?=\.(json|xml|rdf)([\/?#]|$)|[\/?#])/i,
      // 查询参数风格
      /[?&](feeds?|format)=(rss2?|atom2?|rdf)(?:&|$)/i
    ],

    // RSS内容模式
    RSS_CONTENT_PATTERNS: [
      /<rss\s+version=/i,
      /<feed\s+xmlns=/i,
      /<rss\s+xmlns=/i,
      /{"version":\s*"https:\/\/jsonfeed\.org\/version\/1/i,
      /<channel>/i
    ]
  };

  // RSS检测器主类
  class RSSPlusDetector {
    constructor() {
      this.state = {
        foundRSS: new Set(),
        validRSS: new Map(),
        invalidRSS: new Set(),
        settings: {
          subscriptionService: 'feedly',
          enableQRCode: true,
          rsshubBase: 'https://rsshub.app',
          rsshubAccessKey: '',
          inoreaderDomain: 'https://www.inoreader.com',
          tinytinyrssDomain: 'https://tt-rss.org',
          freshrssDomain: 'https://freshrss.org',
          minifluxDomain: 'https://miniflux.app'
        },
        maxZIndex: 0,
        isDragging: false,
        isContainerDragging: false,
        dragStartX: 0,
        dragStartY: 0,
        hasMoved: false
      };

      // 获取当前语言 - 修复i18n问题
      const browserLang = navigator.language || navigator.userLanguage || 'en';
      this.lang = browserLang.toLowerCase().replace('-', "");
      this.t = CONSTANTS.I18N[this.lang] || CONSTANTS.I18N[browserLang.split('-')[0]] || CONSTANTS.I18N.en;

      this.init();
    }

    init() {
      this.loadSettings();
      this.calculateMaxZIndex();
      this.createUI();
      this.detectRSS();
      this.setupMutationObserver();
      this.setupUrlObserver();
    }

    // 计算页面最大z-index
    calculateMaxZIndex() {
      let max = 0;
      document.querySelectorAll('*').forEach(el => {
        const z = parseInt(window.getComputedStyle(el).zIndex) || 0;
        if (z > max) max = z;
      });
      this.state.maxZIndex = max;
    }

    // 创建UI
    createUI() {
      // 创建Shadow DOM
      this.shadowHost = document.createElement('rss-plus');
      this.shadowHost.id = 'rss-plus-shadow-host';
      document.body.appendChild(this.shadowHost);
      this.shadowRoot = this.shadowHost.attachShadow({ mode: 'open' });  // 兼容性更好

      // 添加样式
      const style = document.createElement('style');
      style.textContent = `
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
:host {
  all: initial;
  /* 1. 让影子根支持两套配色,浏览器会先换好默认 Canvas/CanvasText */
  color-scheme: light dark;

  /* 2. 核心语义变量 —— 全部用 light-dark() 定义 */
  --fg:               light-dark(#1f2937, #f3f4f6);          /* 主文字 */
  --bg:               light-dark(#ffffff, #111827);          /* 主背景 */
  --sub-fg:           light-dark(#6b7280, #9ca3af);          /* 次要文字 */
  --accent:           light-dark(#4f46e5, #818cf8);          /* 主强调色 */
  --accent-hover:     light-dark(#4338ca, #a5b4fc);          /* 强调色-hover */
  --border:           light-dark(rgba(0,0,0,.08), rgba(255,255,255,.08));
  --dash-border: light-dark(#d1d9e0, #737d87);
  --header-bg:        light-dark(rgba(249,250,251,.8), rgba(31,41,55,.8));
  --item-hover:       light-dark(rgba(249,250,251,.8), rgba(55,65,81,.8));
  --input-bg:         light-dark(#ffffff, #1f2937);
  --input-border:     light-dark(#d1d5db, #4b5563);
  --input-focus-ring: light-dark(0 0 0 3px rgba(79,70,229,.1), 0 0 0 3px rgba(165,180,252,.3));
  --modal-bg:         light-dark(#ffffff, #1f2937);
  --overlay-bg:       light-dark(rgba(0,0,0,.6), rgba(0,0,0,.75));
  --btn-secondary-bg: light-dark(#f3f4f6, #374151);
  --btn-secondary-hover: light-dark(#e5e7eb, #4b5563);
  --scroll-thumb:     light-dark(rgba(156,163,175,.3), rgba(75,85,99,.5));
  --scroll-thumb-hover: light-dark(rgba(156,163,175,.5), rgba(75,85,99,.7));
}
body {
  font-family: -system-ui;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
/* ---------- 通用文字色 ---------- */
body,
#rss-counter,
#rss-container,
#rss-list,
.rss-item,
.rss-item-title,
.rss-item-url,
.setting-label,
.setting-input,
.setting-select,
.btn-secondary {
  color: var(--fg);
  background-color: transparent;   /* 背景由父级统一给 */
}

/* ---------- 悬浮球 ---------- */
#rss-counter {
  position: fixed;
  bottom: 20px;
  right: 20px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--bg);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 2px solid var(--border);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  cursor: pointer;
  box-shadow: var(--shadow);
  z-index: ${this.state.maxZIndex + 1};
  transition: all .2s ease;
  user-select: none;
}
#rss-counter:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-hover);
  background: var(--bg);
}
#rss-counter:active {
  transform: translateY(0);
}

/* ---------- 主面板 ---------- border: 1px solid var(--border); box-shadow: var(--shadow); */
#rss-container {
  position: fixed;
  width: 380px;
  max-height: 70vh;
  background: var(--bg);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-radius: 12px;

  border: 2px dashed var(--dash-border);

  z-index: ${this.state.maxZIndex + 1};
  display: none;
  overflow: hidden;
}

#rss-header {
  padding: 10px 18px;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: var(--header-bg);
  border-radius: 12px 12px 0 0;
}

#rss-title {
  font-size: 15px;
}

#rss-title-count {
  font-weight: bold;
  color: #ef4444;          /* 固定醒目红,不随主题变 */
}

/* ---------- 按钮组 ---------- */
.rss-buttons {
  display: flex;
  gap: 6px;
}
.rss-btn {
  width: 32px;
  height: 32px;
  border: none;
  background: transparent;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  transition: background .2s;
}
.rss-btn:hover {
  background: var(--item-hover);
}

/* ---------- 列表 ---------- */
#rss-list {
  max-height: calc(70vh - 56px);
  overflow-y: auto;
  padding: 0;
  background: var(--bg);
}
.rss-item {
  padding: 10px 18px;
  position: relative;
  transition: background .2s;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  min-height: 50px;
}
.rss-item:last-child {
  border-bottom: none;
}
.rss-item:hover {
  background: var(--item-hover);
}
.rss-item.invalid {
  opacity: .6;
}
.rss-item.invalid .rss-item-title,
.rss-item.invalid .rss-item-url {
  text-decoration: line-through;
}

.rss-item-content {
  flex: 1;
  min-width: 0;
  margin-right: 10px;
}
.rss-item-title {
  font-weight: 500;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rss-item-url {
  font-size: 12px;
  white-space: nowrap;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--sub-fg);
}
.rss-item-url:hover {
  color: var(--accent);
  text-decoration: underline;
}

.rss-item-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

/* ---------- QR 弹窗 ---------- */
.qr-code {
  position: fixed;
  background: var(--bg);
  border-radius: 8px;
  box-shadow: var(--shadow);
  z-index: ${this.state.maxZIndex + 2};
  display: none;
  border: 1px solid var(--border);
}

/* ---------- 设置模态框 ---------- */
#settings-modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--modal-bg);
  padding: 24px;
  border-radius: 12px;
  box-shadow: 0 15px 35px rgba(0,0,0,.2);
  z-index: ${this.state.maxZIndex + 3};
  display: none;
  width: 420px;
  border: 1px solid var(--border);
}
.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--overlay-bg);
  z-index: ${this.state.maxZIndex + 2};
  display: none;
  backdrop-filter: blur(4px);
}

/* ---------- 表单 ---------- */
.setting-group {
  margin-bottom: 10px;
}
.setting-label {
  display: block;
  margin-bottom: 8px;
  font-weight: 500;
  font-size: 14px;
}
.setting-input,
.setting-select {
  width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--input-border);
  border-radius: 8px;
  font-size: 14px;
  background: var(--input-bg);
  transition: border .2s, box-shadow .2s;
}
.setting-input:focus,
.setting-select:focus {
  border-color: var(--accent);
  outline: none;
  box-shadow: var(--input-focus-ring);
}
.required-star {
  color: #e53e3e;
  margin-right: 4px;
}
.setting-input.error {
  border-color: #e53e3e;
}
.error-tip {
  color: #e53e3e;
  font-size: 12px;
  margin-top: 4px;
  display: none;
}
.checkbox-wrapper {
  display: flex;
  align-items: center;
  gap: 10px;
}
.checkbox-wrapper input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
}

/* ---------- 按钮 ---------- */
.modal-buttons {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
  margin-top: 10px;
}
.btn-primary {
  padding: 10px 18px;
  background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 500;
  transition: all .2s;
  box-shadow: 0 2px 4px rgba(59,130,246,.2);
}
.btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 8px rgba(59,130,246,.3);
}
.btn-secondary {
  padding: 10px 18px;
  background: var(--btn-secondary-bg);
  color: var(--fg);
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 500;
  transition: all .2s;
  box-shadow: 0 1px 2px rgba(0,0,0,.05);
}
.btn-secondary:hover {
  background: var(--btn-secondary-hover);
  transform: translateY(-1px);
}

.domain-setting {
  display: none;
}
.domain-setting.active {
  display: block;
}

/* ---------- 滚动条 ---------- */
#rss-list::-webkit-scrollbar {
  width: 6px;
}
#rss-list::-webkit-scrollbar-track {
  background: transparent;
}
#rss-list::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  border-radius: 3px;
}
#rss-list::-webkit-scrollbar-thumb:hover {
  background: var(--scroll-thumb-hover);
}

/* ---------- 响应式 ---------- */
@media (max-width: 480px) {
  #rss-container {
    width: calc(100vw - 40px);
    max-width: 380px;
  }
  #settings-modal {
    width: calc(100vw - 40px);
    max-width: 380px;
  }
}

/* ---------- 打印隐藏 ---------- */
@media print {
  :host {
    display: none;
  }
}
            `;
      this.shadowRoot.appendChild(style);

      // 创建计数按钮
      this.counter = document.createElement('div');
      this.counter.id = 'rss-counter';
      this.counter.textContent = '0';
      this.counter.style.display = 'none';
      this.shadowRoot.appendChild(this.counter);

      // 创建容器
      this.container = document.createElement('div');
      this.container.id = 'rss-container';
      this.container.innerHTML = `
                <div id="rss-header">
                    <div id="rss-title">${this.t.feed}0</div>
                    <div class="rss-buttons">
                        <button class="rss-btn" id="settings-btn" title="${this.t.setting}">
                            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <circle cx="12" cy="12" r="3"></circle>
                                <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
                            </svg>
                        </button>
                        <button class="rss-btn" id="close-btn" title="${this.t.close}">
                            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <line x1="18" y1="6" x2="6" y2="18"></line>
                                <line x1="6" y1="6" x2="18" y2="18"></line>
                            </svg>
                        </button>
                    </div>
                </div>
                <div id="rss-list"></div>
            `;
      this.shadowRoot.appendChild(this.container);

      this.rssList = this.shadowRoot.getElementById('rss-list');

      // 创建二维码容器
      this.qrCode = document.createElement('div');
      this.qrCode.className = 'qr-code';
      this.shadowRoot.appendChild(this.qrCode);

      // 创建设置模态框
      this.modalOverlay = document.createElement('div');
      this.modalOverlay.className = 'modal-overlay';
      this.shadowRoot.appendChild(this.modalOverlay);

      this.settingsModal = document.createElement('div');
      this.settingsModal.id = 'settings-modal';
      this.settingsModal.innerHTML = `
                <h3 style="margin-bottom: 10px; font-size: 18px; color: #1f2937;">${this.t.setting}</h3>
                <div class="setting-group">
                    <label class="setting-label">${this.t.subscriptionMode}</label>
                    <select class="setting-select" id="subscription-service">
                        <option value="feedly">Feedly</option>
                        <option value="inoreader">Inoreader</option>
                        <option value="tinytinyrss">Tiny Tiny RSS</option>
                        <option value="freshrss">Fresh RSS</option>
                        <option value="miniflux">Miniflux</option>
                        <option value="folo">Folo</option>
                        <option value="newsblur">NewsBlur</option>
                        <option value="theoldreader">The Old Reader</option>
                    </select>
                </div>

                <div class="setting-group domain-setting" id="inoreader-domain-setting">
                    <label class="setting-label">${this.t.settingInoreaderDomain}</label>
                    <input type="text" class="setting-input" id="inoreader-domain" placeholder="https://www.inoreader.com">
                </div>

                <div class="setting-group domain-setting required-domain" id="tinytinyrss-domain-setting">
                    <label class="setting-label"><span class="required-star">*</span>${this.t.settingTinytinyrssDomain}</label>
                    <input type="text" class="setting-input validate-url" id="tinytinyrss-domain" placeholder="https://tt-rss.org">
                    <div class="error-tip"></div>
                </div>

                <div class="setting-group domain-setting required-domain" id="freshrss-domain-setting">
                    <label class="setting-label"><span class="required-star">*</span>${this.t.settingFreshrssDomain}</label>
                    <input type="text" class="setting-input validate-url" id="freshrss-domain" placeholder="https://freshrss.org">
                    <div class="error-tip"></div>
                </div>

                <div class="setting-group domain-setting required-domain" id="miniflux-domain-setting">
                    <label class="setting-label"><span class="required-star">*</span>${this.t.settingMinifluxDomain}</label>
                    <input type="text" class="setting-input validate-url" id="miniflux-domain" placeholder="https://miniflux.app">
                    <div class="error-tip"></div>
                </div>

                <div class="setting-group">
                    <label class="setting-label">${this.t.settingRSShubDomain}</label>
                    <input type="text" class="setting-input" id="rsshub-base" placeholder="https://rsshub.app">
                </div>

                <div class="setting-group">
                    <label class="setting-label">${this.t.settingRSShubAccessKey}</label>
                    <input type="text" class="setting-input" id="rsshub-access-key" placeholder="">
                </div>

                <div class="setting-group">
                    <div class="checkbox-wrapper">
                        <input type="checkbox" id="enable-qrcode">
                        <label for="enable-qrcode">${this.t.enableQRCode}</label>
                    </div>
                </div>

                <div class="modal-buttons">
                    <button class="btn-secondary" id="cancel-settings">${this.t.close}</button>
                    <button class="btn-primary" id="save-settings">${this.t.confirm}</button>
                </div>
            `;
      this.shadowRoot.appendChild(this.settingsModal);

      // 绑定事件
      this.bindEvents();
    }

    // 绑定事件
    bindEvents() {
      // 拖动功能
      this.setupDrag();

      // 关闭按钮
      this.container.querySelector('#close-btn').addEventListener('click', () => {
        this.hideContainer();
      });

      // 设置按钮
      this.container.querySelector('#settings-btn').addEventListener('click', () => {
        this.showSettings();
      });

      // 设置模态框
      this.modalOverlay.addEventListener('click', () => {
        this.hideSettings();
      });

      this.settingsModal.addEventListener('click', (e) => {
        e.stopPropagation();
      });

      this.settingsModal.querySelector('#cancel-settings').addEventListener('click', () => {
        this.hideSettings();
      });

      // this.settingsModal.querySelector('#save-settings').addEventListener('click', () => {
      //   this.saveSettingsFromModal();
      // });
      this.settingsModal.querySelector('#save-settings').addEventListener('click', () => {
        if (!this.validateRequiredDomains()) return;   // 阻断非法输入
        this.saveSettingsFromModal();
      });

      // 订阅服务切换事件
      const subscriptionSelect = this.settingsModal.querySelector('#subscription-service');
      subscriptionSelect.addEventListener('change', (e) => {
        this.toggleDomainSettings(e.target.value);
        this.validateRequiredDomains();   // 实时把非必填的报错清掉
      });
    }

    // 切换域名设置显示
    toggleDomainSettings(service) {
      // 隐藏所有域名设置
      this.settingsModal.querySelectorAll('.domain-setting').forEach(el => {
        el.classList.remove('active');
      });

      // 显示对应的域名设置
      const domainSettings = {
        // 'inoreader': 'inoreader-domain-setting',
        'tinytinyrss': 'tinytinyrss-domain-setting',
        'freshrss': 'freshrss-domain-setting',
        'miniflux': 'miniflux-domain-setting'
      };

      if (domainSettings[service]) {
        this.settingsModal.querySelector(`#${domainSettings[service]}`).classList.add('active');
      }
    }

    // 设置计数按钮拖动
    setupDrag() {
      const dragThreshold = 5;          // 像素阈值
      let pressTimer       = null;      // 长按定时器
      let dragStartedHere = false;   // ★ 新增:标记拖动是否由按钮发起

      /* ---------- 启动拖动 ---------- */
      const startDrag = (x, y) => {
        dragStartedHere = true;
        this.state.isDragging = true;
        this.state.hasMoved    = false;
        this.state.dragStartX  = x;
        this.state.dragStartY  = y;
        const rect = this.counter.getBoundingClientRect();
        this.state.initialX = rect.left;
        this.state.initialY = rect.top;
        this.counter.style.cursor      = 'grabbing';
        this.counter.style.transition  = 'none';
        // console.log("开始拖动:" + dragStartedHere + " - " + handled);
      };

      /* ---------- 移动处理 ---------- */
      const moveHandler = (x, y) => {
        if (!this.state.isDragging) return;

        const dx = x - this.state.dragStartX;
        const dy = y - this.state.dragStartY;
        if (Math.abs(dx) > dragThreshold || Math.abs(dy) > dragThreshold) {
          this.state.hasMoved = true;
        }
        if (this.state.hasMoved) {
          this.counter.style.left = `${this.state.initialX + dx}px`;
          this.counter.style.top  = `${this.state.initialY + dy}px`;
          this.counter.style.right = 'auto';
          this.counter.style.bottom = 'auto';
        }
      };

      /* ---------- 结束处理 ---------- */
      const endHandler = (e) => {
        // console.log("结束处理器:" + dragStartedHere + " - " + handled);
        clearTimeout(pressTimer);
        document.removeEventListener('mousemove', onMoveMouse);
        document.removeEventListener('mouseup', onEndMouse);
        document.removeEventListener('touchmove', onMoveTouch);
        document.removeEventListener('touchend', onEndTouch);
        // if(!dragStartedHere || handled) return;
        // handled = true;
        // cleanup();
        if (this.state.hasMoved) {
          this.snapToEdge();
        } else {
          this.showContainer();
        }

        this.state.isDragging = false;
        this.state.hasMoved = false;
        dragStartedHere = false;
        // handled = false;
        this.counter.style.cursor = 'pointer';
        this.counter.style.transition = 'all .2s ease';
      };
      /* ========== 鼠标事件 ========== */
      const onMoveMouse = (e) => moveHandler(e.clientX, e.clientY);
      const onEndMouse = (e) => {
        // console.log("鼠标结束");
        e.preventDefault();
        endHandler();
      };
      this.counter.addEventListener('mousedown', (e) => {
        e.preventDefault();
        pressTimer = setTimeout(() => {
          startDrag(e.clientX, e.clientY);
          document.addEventListener('mousemove', onMoveMouse);
        }, 200);
        document.addEventListener('mouseup', onEndMouse);
      });

      /* ========== 触屏事件 ========== */
      const onMoveTouch = (e) => {
        e.preventDefault();
        if (e.touches.length > 0) {
          moveHandler(e.touches[0].clientX, e.touches[0].clientY);
        }
      };
      const onEndTouch = (e) => {
        e.preventDefault();
        endHandler();
      };
      this.counter.addEventListener('touchstart', (e) => {
        e.preventDefault();
        const t = e.touches[0];
        pressTimer = setTimeout(() => startDrag(t.clientX, t.clientY), 200);
        document.addEventListener('touchmove', onMoveTouch, { passive: false });
        document.addEventListener('touchend', onEndTouch);
      });

      /* ========== 意外中断 ========== */
      // this.counter.addEventListener('touchcancel', cleanup);
      this.counter.addEventListener('touchcancel', () => {
        // console.log("cleanup" + onMoveMouse + " moved" + onMoveTouch);
        clearTimeout(pressTimer);
        document.removeEventListener('touchmove', onTouchMove);
        document.removeEventListener('touchend', onTouchEnd);
        this.state.isDragging = false;
        this.state.hasMoved = false;
        dragStartedHere = false;
        this.counter.style.cursor = 'pointer';
        this.counter.style.transition = 'all 0.2s ease';
      });
    }

    // 修复后的贴边方法
    snapToEdge() {
      const pad = 20;
      const counterRect = this.counter.getBoundingClientRect();
      const viewportWidth = window.innerWidth;
      const viewportHeight = window.innerHeight;

      const centerX = counterRect.left + counterRect.width / 2;

      let newLeft, newTop;

      // 水平方向贴边
      if (centerX < viewportWidth / 2) {
        newLeft = pad; // 左边贴边
      } else {
        newLeft = viewportWidth - counterRect.width - pad; // 右边贴边
      }

      // 垂直方向处理(保持当前位置,但确保不超出边界)
      newTop = counterRect.top;

      // 检查上边界
      if (newTop < pad) {
        newTop = pad;
      }

      // 检查下边界
      if (newTop + counterRect.height > viewportHeight - pad) {
        newTop = viewportHeight - counterRect.height - pad;
      }

      // 应用新位置
      this.counter.style.left = `${newLeft}px`;
      this.counter.style.top = `${newTop}px`;
      this.counter.style.right = 'auto';
      this.counter.style.bottom = 'auto';
    }

    // 显示容器
    showContainer() {
      const pad = 20;
      // 获取计数按钮位置
      const counterRect = this.counter.getBoundingClientRect();
      const viewportWidth = window.innerWidth;
      const viewportHeight = window.innerHeight;

      // 判断计数按钮在左侧还是右侧
      const isOnLeft = (counterRect.left + (counterRect.width / 2)) < (viewportWidth / 2);
      const isOnTop = (counterRect.top + counterRect.height / 2) < (viewportHeight / 2);

      // 重置容器位置
      this.container.style.height = '';

      // 设置容器位置
      if (isOnLeft) {
        // 在左侧,以左下角为基准展开
        this.container.style.left = counterRect.left + 'px';
        this.container.style.right = '';
      } else {
        // 在右侧,以右下角为基准展开
        this.container.style.left = '';
        this.container.style.right = (viewportWidth - counterRect.right) + 'px';
      }

      if (isOnTop){
        this.container.style.top = counterRect.top + 'px';
        this.container.style.bottom = '';
        this.container.style.maxHeight = viewportHeight - counterRect.height - pad;
        this.rssList.style.maxHeight = viewportHeight - counterRect.bottom - pad - 52;
      } else {
        this.container.style.top = '';
        this.container.style.bottom = (viewportHeight - counterRect.bottom) + 'px';
        this.container.style.maxHeight = counterRect.bottom;
        this.rssList.style.maxHeight = counterRect.top - pad - 52;
      }

      // 显示容器
      this.counter.style.display = 'none';
      this.container.style.display = 'block';

      // 确保容器在可视区域内
      // setTimeout(() => {
      //   this.ensureContainerInViewport();
      // }, 10);
    }

    // 隐藏容器
    hideContainer() {
      this.container.style.display = 'none';
      this.counter.style.display = 'flex';
    }

    // 调整容器高度
    adjustContainerHeight() {
      const listContainer = this.container.querySelector('#rss-list');
      const headerHeight = this.container.querySelector('#rss-header').offsetHeight;
      const viewportHeight = window.innerHeight;
      const containerBottom = parseInt(this.container.style.bottom) || 0;

      // 计算可用高度
      const availableHeight = viewportHeight - containerBottom - headerHeight - 40; // 40为边距

      // 获取列表内容高度
      const listContentHeight = listContainer.scrollHeight;

      // 设置容器高度
      if (listContentHeight < availableHeight) {
        this.container.style.height = (listContentHeight + headerHeight) + 'px';
        this.container.style.maxHeight = '';
      } else {
        this.container.style.height = '';
        this.container.style.maxHeight = availableHeight + 'px';
      }
    }

    // 加载设置
    loadSettings() {
      // const saved = localStorage.getItem('rssDetectorSettings');
      // const saved = GM_getValue("rssDetectorSettings");
      // if (saved) {
      //   this.state.settings = { ...this.state.settings, ...JSON.parse(saved) };
      // }
      this.state.settings.subscriptionService = GM_getValue('rss_service', "feedly");
      this.state.settings.rsshubBase = GM_getValue("rsshub_domain", "https://rsshub.app");
      this.state.settings.rsshubAccessKey = GM_getValue("rsshub_access_key", "");
      // this.state.settings.inoreaderDomain = GM_getValue("inoreader_domain", "https://www.inoreader.com");
      this.state.settings.tinytinyrssDomain = GM_getValue("tinytinyrss_domain", "");
      this.state.settings.freshrssDomain = GM_getValue("freshrss_domain", "");
      this.state.settings.minifluxDomain = GM_getValue("miniflux_domain", "");
      this.state.settings.enableQRCode = GM_getValue('enable_qr_code', true);
    }

    // 保存设置
    saveSettings() {
      // localStorage.setItem('rssDetectorSettings', JSON.stringify(this.state.settings));
      // GM_setValue("rssDetectorSettings", JSON.stringify(this.state.settings));
      GM_setValue('rss_service', this.state.settings.subscriptionService);
      GM_setValue("rsshub_domain", this.state.settings.rsshubBase);
      GM_setValue("rsshub_access_key", this.state.settings.rsshubAccessKey);
      // GM_setValue("inoreader_domain", this.state.settings.inoreaderDomain);
      GM_setValue("tinytinyrss_domain", this.state.settings.tinytinyrssDomain);
      GM_setValue("freshrss_domain", this.state.settings.freshrssDomain);
      GM_setValue("miniflux_domain", this.state.settings.minifluxDomain);
      GM_setValue('enable_qr_code', this.state.settings.enableQRCode);
    }

    // 显示设置
    showSettings() {
      // 填充当前设置
      this.settingsModal.querySelector('#subscription-service').value = this.state.settings.subscriptionService;
      this.settingsModal.querySelector('#rsshub-base').value = this.state.settings.rsshubBase;
      this.settingsModal.querySelector('#rsshub-access-key').value = this.state.settings.rsshubAccessKey;
      // this.settingsModal.querySelector('#inoreader-domain').value = this.state.settings.inoreaderDomain;
      this.settingsModal.querySelector('#tinytinyrss-domain').value = this.state.settings.tinytinyrssDomain;
      this.settingsModal.querySelector('#freshrss-domain').value = this.state.settings.freshrssDomain;
      this.settingsModal.querySelector('#miniflux-domain').value = this.state.settings.minifluxDomain;
      this.settingsModal.querySelector('#enable-qrcode').checked = this.state.settings.enableQRCode;

      // 切换域名设置显示
      this.toggleDomainSettings(this.state.settings.subscriptionService);

      this.modalOverlay.style.display = 'block';
      this.settingsModal.style.display = 'block';
    }

    // 隐藏设置
    hideSettings() {
      this.modalOverlay.style.display = 'none';
      this.settingsModal.style.display = 'none';
    }

    // 保存设置
    saveSettingsFromModal() {
      this.state.settings.subscriptionService = this.settingsModal.querySelector('#subscription-service').value;
      this.state.settings.rsshubBase = this.settingsModal.querySelector('#rsshub-base').value;
      this.state.settings.rsshubAccessKey = this.settingsModal.querySelector('#rsshub-access-key').value;
      // this.state.settings.inoreaderDomain = this.settingsModal.querySelector('#inoreader-domain').value;
      this.state.settings.tinytinyrssDomain = this.settingsModal.querySelector('#tinytinyrss-domain').value;
      this.state.settings.freshrssDomain = this.settingsModal.querySelector('#freshrss-domain').value;
      this.state.settings.minifluxDomain = this.settingsModal.querySelector('#miniflux-domain').value;
      this.state.settings.enableQRCode = this.settingsModal.querySelector('#enable-qrcode').checked;

      this.saveSettings();
      this.hideSettings();
    }
    // 简单的 URL 合法性检查
    isValidUrl(str) {
      try {
        const u = new URL(str);
        return u.protocol === 'http:' || u.protocol === 'https:';
      } catch {
        return false;
      }
    }

    // 统一校验必填 + 格式
    validateRequiredDomains() {
      const required = [
        { id: 'tinytinyrss-domain',  service: 'tinytinyrss' },
        { id: 'freshrss-domain',     service: 'freshrss' },
        { id: 'miniflux-domain',     service: 'miniflux' }
      ];
      let ok = true;

      required.forEach(({ id, service }) => {
        const input  = this.settingsModal.querySelector(`#${id}`);
        const tip    = input.parentElement.querySelector('.error-tip');
        const value  = input.value.trim();

        // 如果当前选中的服务就是它,则必填
        const currentService = this.settingsModal.querySelector('#subscription-service').value;
        const isRequired = currentService === service;

        input.classList.remove('error');
        tip.style.display = 'none';

        if (isRequired) {
          if (!value) {
            tip.textContent = '此项为必填';
            tip.style.display = 'block';
            input.classList.add('error');
            ok = false;
          } else if (!this.isValidUrl(value)) {
            tip.textContent = '请输入正确的 URL(带 http/https)';
            tip.style.display = 'block';
            input.classList.add('error');
            ok = false;
          }
        }
      });

      return ok;
    }

    // 1. 查找已声明的RSS - 借鉴用户代码中的逻辑
    findFeedsFromMeta() {
      const links = document.getElementsByTagName("link");
      const rssLinks = [];

      for (const link of links) {
        const { href, type, title } = link;
        if (type && (type.match(/.+\/(rss|rdf|atom|feed\+json)/i) || type.match(/^text\/xml$/i))) {
          rssLinks.push({
            url: href,
            title: title || document.title || this.t.noTitle,
            reliable: true
          });
        }
      }

      return rssLinks;
    }

    // 2. 查找页面上链接中带有rss特征
    findFeedsFromLinks(links = []) {
      const rssLinks = [];

      for (const link of links) {
        const href = link.href;
        // 非url的舍去
        if(!/^(?:(?:https?:)?\/\/)?(?:[^\/\s:@]+(?::[^\/\s:@]+)?@)?(?:\[[0-9a-f:]+\]|(?:\d{1,3}\.){3}\d{1,3}|(?:[a-z0-9-]+\.)+[a-z0-9-]+)(?::\d{2,5})?(?:\/[^\s?#]*)?(?:\?[^\s#]*)?(?:#\S*)?$/i.test(href)
          && !/^\/[^\s#]*$/i.test(href)
        ){
          continue;
        }

        // 使用附件中的正则表达式
        if (CONSTANTS.RSS_PATTERNS.some(pattern => pattern.test(href))) {
          rssLinks.push({
            url: href,
            title: link.title || link.textContent || document.title || this.t.noTitle,
            reliable: true
          });
        }
      }

      return rssLinks;
    }

    // 3. 检查特定平台
    checkFeedForPlatform(selector, regex) {
      const links = document.querySelectorAll(selector);
      for (const link of links) {
        if (regex.test(link.href)) {
          return true;
        }
      }
      return false;
    }

    // 4. 根据页面特征猜测可能的RSS源
    findFeedsFromGuess() {
      const origin = location.origin || `${location.protocol}//${location.host}`;
      const guessedRSS = [];

      // 检查特定平台
      if (this.checkFeedForPlatform("html > head > link", /(wp-content)/i)) { // WordPress
        CONSTANTS.FEED_SUFFIXES.forEach(suffix => {
          guessedRSS.push({
            url: origin + suffix,
            title:  document.title || this.t.noTitle,
            reliable: false
          });
        });
      }

      if (this.checkFeedForPlatform("html > body footer a", /(bitcron\.com|typecho\.org|hexo\.io)/i)) { // Blog platforms
        CONSTANTS.FEED_SUFFIXES.forEach(suffix => {
          guessedRSS.push({
            url: origin + suffix,
            title: document.title || this.t.noTitle,
            reliable: false
          });
        });
      }

      if (document.querySelectorAll("a.footer-copyright-halo, head > meta[name=generator][content*='Halo']").length > 0) { // Halo
        guessedRSS.push({
          url: `${origin}/rss.xml`,
          title: document.title || this.t.noTitle,
          reliable: false
        });
      }

      return guessedRSS;
    }

    // 5. 请求远程的RSS规则本地执行
    async findFeedsFromCloud() {
      const url = location.href;
      const res = document.documentElement.outerHTML;

      try {
        // 示例:豆瓣
        try {
          return this.findFeedsByEval(url, res);
        } catch (e) {
          console.error("无法通过 Eval 执行:", e);
          return await this.findFeedsFromAPI(url);
        }
      } catch (e) {
        console.error("无法通过远程规则执行:", e);
      }
      return [];
    }
    findFeedsByEval(url, res) {
      const rssLinks = [];
      return function(jsStr, url, res) {
        if (jsStr.trim().length === 0) {
          throw new Error("未获取到可执行脚本");
        }
        const Ruler = Function(`"use strict";return (${jsStr})`)();

        const list = Ruler.find(url, res);
        if (!list) {
          return rssLinks;
        }
        list.forEach(element => {
          rssLinks.push({url: element.link, title: element.title || document.title, reliable: true});
        });
        return rssLinks;
      }.call(window, GM_getResourceText('RulerJs'), url, res);
    }

    // 6. 请求API获取RSS源 - 注释掉
    async findFeedsFromAPI(url) {
      const proxy = CONSTANTS.PROXY_LIST[Math.floor(Math.random() * CONSTANTS.PROXY_LIST.length)];

      try {
        const response = await fetch(`${proxy}api/find?url=${encodeURIComponent(url)}`, {
          method: 'get',
          timeout: 5000
        });

        if (response.status === 200) {
          const obj = JSON.parse(await response.text());
          if (obj && Array.isArray(obj)) {
            return obj.map(item => ({
              url: item.link,
              title: item.title || document.title || this.t.noTitle,
              reliable: true
            }));
          }
        }
      } catch (err) {
        console.error('rss 检测 feed 异常:', err);
      }
      return [];
    }

    // 检测RSS
    async detectRSS() {
      const candidates = new Set();

      // 1. 查找已声明的RSS(高可信度)
      const credibleFeeds = this.findFeedsFromMeta();
      credibleFeeds.forEach(feed => candidates.add(JSON.stringify(feed)));

      // 2. 查找页面上链接中带有rss特征(低可信度)
      const dubiousFeeds = this.findFeedsFromLinks(document.links || document.getElementsByTagName("a"));
      dubiousFeeds.forEach(feed => candidates.add(JSON.stringify(feed)));

      // 3. 请求远程的RSS规则本地执行
      const cloudFeeds = await this.findFeedsFromCloud();
      cloudFeeds.forEach(feed => candidates.add(JSON.stringify(feed)));

      // 4. 根据页面特征猜测可能的RSS源
      const guessedFeeds = this.findFeedsFromGuess();
      guessedFeeds.forEach(feed => candidates.add(JSON.stringify(feed)));

      // 验证RSS
      const validationPromises = Array.from(candidates).map(async candidate => {
        const feed = JSON.parse(candidate);
        await this.validateRSS(feed);
        // 更新UI
        this.updateUI();
      });

      await Promise.all(validationPromises);
    }

    // 检查新增链接是否为RSS源
    async checkNewLinksForRSS(links) {
      const dubiousFeeds = this.findFeedsFromLinks(links);
      // 验证新增的RSS
      for (const feed of dubiousFeeds) {
        if (!this.state.foundRSS.has(feed.url)) {
          await this.validateRSS(feed);
          // 更新UI
          this.updateUI();
        }
      }
    }

    // 验证RSS有效性
    async validateRSS(feedInfo) {
      const { url, title, reliable } = feedInfo;

      if (this.state.foundRSS.has(url)) return;
      this.state.foundRSS.add(url);
      console.log("准备验证:" + url);
      try {
        const result = await this.verifyFeedUrl(url);

        // console.log("验证结果:" + JSON.stringify(result));
        if (result.valid) {
          // 处理RSSHub URL
          let finalUrl = result.targetUrl;
          if (finalUrl.match(/^https*:\/\/rsshub.app/i)) {
            const rsshubDomain = this.state.settings.rsshubBase;
            if (rsshubDomain && rsshubDomain !== "") {
              finalUrl = finalUrl.replace(/^https*:\/\/rsshub.app/i, rsshubDomain);
            }

            const rsshubAccessKey = this.state.settings.rsshubAccessKey;
            if (rsshubAccessKey && rsshubAccessKey !== "") {
              const uri = new URL(finalUrl);
              uri.searchParams.set('key', rsshubAccessKey);
              finalUrl = uri.href;
            }
          }

          // const feedTitle = await this.getFeedTitle(finalUrl);
          this.state.validRSS.set(finalUrl, {
            title: title || document.title,
            originalUrl: url
          });
        } else if (reliable) {
          // 对于非猜测的源,即使无效也显示但置灰
          this.state.invalidRSS.add({
            url: url,
            title: title || document.title
          });
        }
      } catch (error) {
        console.error('验证RSS失败:', error);
        // 对于非猜测的源,即使验证失败也显示但置灰
        if (reliable) {
          this.state.invalidRSS.add({
            url: url,
            title: title || document.title
          });
        }
      }
    }

    // RSS Url有效性校验
    async verifyFeedUrl(url){
      try {
        const response = await fetch(url, {
          method: 'HEAD',
          timeout: 3000,
          headers: {
            'Referer': location.href,
            'Accept-Encoding': 'deflate',
            "Accept": "application/feed+json, application/json;q=0.9,application/rss+xml;q=0.8, application/atom+xml;q=0.7, application/xml;q=0.6, */*;q=0.1",
            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
          }
        });
        const contentType = response.headers.get('content-type') || response.headers.get('Content-Type') || '';
        const valid = (response.ok || response.status === 206) && this.isValidContentType(contentType);
        if (valid) return {valid: valid, targetUrl: response.url};
      } catch (e) {
        // Range 不支持或报错 → 继续走 fallback
      }
      // 先尝试 Range 请求
      try {
        const response = await fetch(url, {
          method: "GET",
          timeout: 3000,
          headers: {
            "Range": "bytes=0-0",
            'Referer': location.href,
            'Accept-Encoding': 'deflate',
            "Accept": "application/feed+json, application/json;q=0.9,application/rss+xml;q=0.8, application/atom+xml;q=0.7, application/xml;q=0.6, */*;q=0.1",
            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
          }
        });
        const contentType = response.headers.get('content-type') || response.headers.get('Content-Type') || '';
        const valid = (response.ok || response.status === 206) && this.isValidContentType(contentType);
        if (valid) return {valid: valid, targetUrl: response.url};
      } catch (e) {
        // Range 不支持或报错 → 继续走 fallback
      }

      // fallback: 普通 GET,但拿到响应头就中止
      const controller = new AbortController();
      let bodyConsumed = false;

      try {
        const fetchPromise = fetch(url, {
          signal: controller.signal,
          timeout: 5000,
          headers: {
            "Range": "bytes=0-0",
            'Referer': location.href,
            'Accept-Encoding': 'deflate',
            "Accept": "application/feed+json, application/json;q=0.9,application/rss+xml;q=0.8, application/atom+xml;q=0.7, application/xml;q=0.6, */*;q=0.1",
            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
          }
        })
          .then(async response => {
            // 拿到响应头就直接 abort
            if (!bodyConsumed) {
              controller.abort();
            }
            const contentType = response.headers.get('content-type') || response.headers.get('Content-Type') || '';
            return {valid: response.ok && this.isValidContentType(contentType), targetUrl: response.url};
          })
          .catch(() => {
            return {valid: false, targetUrl: url}
          });

        // 限时防止卡死(例如无响应)
        const timeoutPromise = new Promise(resolve =>
          setTimeout(() => {
            controller.abort();
            resolve({valid: false, targetUrl: url} );
          }, 5000) // 最多等 5 秒
        );

        return await Promise.race([fetchPromise, timeoutPromise]);
      } catch (e) {
        return { valid: false, targetUrl: url };
      }
    }

    // 检查内容类型是否有效
    isValidContentType(contentType) {
      return CONSTANTS.FEED_CONTENT_TYPES.some(type => contentType.includes(type));
    }

    // 检查内容是否为RSS
    // isRSSContent(content) {
    //   return CONSTANTS.RSS_CONTENT_PATTERNS.some(pattern => pattern.test(content));
    // }
    //
    // // 获取Feed标题
    // async getFeedTitle(responseText, defaultValue = null) {
    //   try {
    //     // RSS 2.0
    //     const rssTitleMatch = responseText.match(/<title>([^<]+)<\/title>/i);
    //     if (rssTitleMatch) {
    //       return rssTitleMatch[1].trim();
    //     }
    //
    //     // Atom
    //     const atomTitleMatch = responseText.match(/<title[^>]*>([^<]+)<\/title>/i);
    //     if (atomTitleMatch) {
    //       return atomTitleMatch[1].trim();
    //     }
    //
    //     // JSON Feed
    //     try {
    //       const json = JSON.parse(responseText);
    //       if (json.title) {
    //         return json.title;
    //       }
    //     } catch (e) {
    //       // 不是JSON
    //     }
    //
    //     return defaultValue || document.title || this.t.noTitle;
    //   } catch (error) {
    //     return defaultValue || document.title || this.t.noTitle;
    //   }
    // }

    // 更新UI
    updateUI() {
      const totalRSS = this.state.validRSS.size + this.state.invalidRSS.size;

      // 如果没有RSS源,隐藏计数按钮
      if (totalRSS === 0) {
        this.counter.style.display = 'none';
        return;
      } else if (this.counter.style.display === 'none') {
        this.counter.style.display = 'flex';
      }

      this.updateCounter();
      this.updateList();
    }

    // 更新计数器
    updateCounter() {
      this.counter.textContent = this.state.validRSS.size + this.state.invalidRSS.size;
    }

    // 更新列表
    updateList() {
      const listContainer = this.container.querySelector('#rss-list');
      const titleElement = this.container.querySelector('#rss-title');

      const totalSize = this.state.validRSS.size + this.state.invalidRSS.size;
      titleElement.innerHTML = `${this.t.feed}<span id="rss-title-count">${totalSize}</span>`;

      listContainer.innerHTML = '';

      // 先添加有效的RSS
      this.state.validRSS.forEach((info, url) => {
        const item = this.createRSSItem(url, info.title, info.originalUrl, false);
        listContainer.appendChild(item);
      });

      // 再添加无效的RSS
      this.state.invalidRSS.forEach(rssInfo => {
        const item = this.createRSSItem(rssInfo.url, rssInfo.title, rssInfo.url, true);
        listContainer.appendChild(item);
      });

      // 调整容器高度
      this.adjustContainerHeight();
    }

    // 创建RSS列表项
    createRSSItem(url, title, originalUrl, isInvalid) {
      const item = document.createElement('div');
      item.className = 'rss-item' + (isInvalid ? ' invalid' : '');

      // 创建内容区域(用于显示二维码)
      const contentArea = document.createElement('div');
      contentArea.className = 'rss-item-content';

      // 创建标题和链接
      const titleDiv = document.createElement('div');
      titleDiv.className = 'rss-item-title';
      titleDiv.textContent = this.escapeHtml(title || document.title);

      const linkElement = document.createElement('a');
      linkElement.className = 'rss-item-url';
      linkElement.href = url;
      linkElement.target = '_blank';
      linkElement.rel = 'noopener noreferrer';
      linkElement.textContent = url;

      // 将标题和链接添加到内容区域
      contentArea.appendChild(titleDiv);
      contentArea.appendChild(linkElement);

      // 创建按钮区域
      const actionsDiv = document.createElement('div');
      actionsDiv.className = 'rss-item-actions';
      actionsDiv.innerHTML = `
                <button class="rss-btn copy-btn" data-url="${url}" title="${this.t.copy}">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                        <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                    </svg>
                </button>
                <button class="rss-btn subscribe-btn" data-url="${url}" title="${this.t.follow}">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
                    </svg>
                </button>
            `;

      // 将内容区域和按钮区域添加到项目
      item.appendChild(contentArea);
      item.appendChild(actionsDiv);

      // 绑定事件
      const copyBtn = actionsDiv.querySelector('.copy-btn');
      copyBtn.addEventListener('click', () => {
        this.copyToClipboard(url);
      });

      const subscribeBtn = actionsDiv.querySelector('.subscribe-btn');
      subscribeBtn.addEventListener('click', () => {
        this.subscribeToRSS(url);
      });

      // 二维码显示 - 只在内容区域显示
      if (this.state.settings.enableQRCode) {
        contentArea.addEventListener('mouseenter', (e) => {
          this.showQRCode(e, url);
        });

        contentArea.addEventListener('mouseleave', () => {
          this.hideQRCode();
        });

        contentArea.addEventListener('mousemove', (e) => {
          this.updateQRCodePosition(e);
        });
      }

      return item;
    }

    // 复制到剪贴板
    async copyToClipboard(text) {
      try {
        await navigator.clipboard.writeText(text);
        this.showToast(this.t.copied);
      } catch (error) {
        console.error('复制失败:', error);
      }
    }

    // 订阅RSS
    subscribeToRSS(url) {
      const serviceUrls = {
        feedly: `https://feedly.com/i/subscription/feed/${encodeURIComponent(url)}`,
        inoreader: `${this.state.settings.inoreaderDomain}/?add_feed=${encodeURIComponent(url)}`,
        tinytinyrss: `${this.state.settings.tinytinyrssDomain}/public.php?op=bookmarklets--subscribe&feed_url=${url}`,
        freshrss: `${this.state.settings.freshrssDomain}/i/?c=feed&a=add&url_rss=${encodeURIComponent(url)}`,
        miniflux: `${this.state.settings.minifluxDomain}/bookmarklet?uri=${encodeURIComponent(url)}`,
        folo: `follow://add?url=${encodeURIComponent(url)}`,
        newsblur: `http://www.newsblur.com/?url=${encodeURIComponent(url)}`,
        theoldreader: `https://theoldreader.com/feeds/subscribe?url=${encodeURIComponent(url)}`
      };

      const subscribeUrl = serviceUrls[this.state.settings.subscriptionService];
      if (subscribeUrl) {
        window.open(subscribeUrl, '_blank');
      }
    }

    // 生成二维码
    async generateQRCode(text) {
      const qr = qrcode(0, 'L');
      qr.addData(text);
      qr.make();
      return qr.createDataURL();
    }

    // 显示二维码
    async showQRCode(e, url) {
      try {
        const dataURL = await this.generateQRCode(url);
        this.qrCode.innerHTML = `<img src="${dataURL}">`;
        //this.qrCode.innerHTML = qrCode;
        this.qrCode.style.display = 'block';
        this.updateQRCodePosition(e);
      } catch (error) {
        console.error('生成二维码失败:', error);
      }
    }

    // 隐藏二维码
    hideQRCode() {
      this.qrCode.style.display = 'none';
    }

    // 更新二维码位置
    updateQRCodePosition(e) {
      const qrWidth = 90; // 二维码宽度
      const qrHeight = 90; // 二维码高度
      const padding = 2; // 边距

      let x = e.clientX + 2;
      let y = e.clientY + 2;

      // 确保不超出右边界
      if (x + qrWidth + padding > window.innerWidth) {
        x = e.clientX - qrWidth - 2;
      }

      // 确保不超出下边界
      if (y + qrHeight + padding > window.innerHeight) {
        y = e.clientY - qrHeight - 2;
      }

      // 确保不超出左边界
      if (x < padding) {
        x = padding;
      }

      // 确保不超出上边界
      if (y < padding) {
        y = padding;
      }

      this.qrCode.style.left = x + 'px';
      this.qrCode.style.top = y + 'px';
    }

    // 设置MutationObserver防止控件被删除
    setupMutationObserver() {
      const observer = new MutationObserver(async (mutations) => {
        const newLinks = [];
        for (const mutation of mutations) {
          mutation.addedNodes.forEach((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              if (node.tagName === 'A') {
                newLinks.push(node);
              } else if (node.querySelectorAll){
                node.querySelectorAll('a').forEach(a => newLinks.push(a));
              }
            }
          });

          mutation.removedNodes.forEach((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              if (node.tagName === 'rss-plus') {
                // 检查控件是否被删除
                if (!document.getElementById('rss-plus-shadow-host')) {
                  document.body.appendChild(this.shadowHost);
                }
              }
            }
          });

          // 处理属性变化(href变化)
          if (mutation.type === 'attributes' &&
            mutation.target.tagName === 'A' &&
            mutation.attributeName === 'href') {
            newLinks.push(mutation.target);
          }
        }

        if (newLinks.length > 0) {
          await this.checkNewLinksForRSS(newLinks);
        }
      });

      observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ["href"],
        attributeOldValue: true
      });

      // 定时检查确保控件存在
      // setInterval(() => {
      //   if (!document.getElementById('rss-plus-shadow-host')) {
      //     document.body.appendChild(this.shadowHost);
      //   }
      // }, 1000);
    }

    setupUrlObserver() {
      const _historyWrap = function(type) {
        const orig = history[type];
        const e = new Event(type);
        return function() {
          const rv = orig.apply(this, arguments);
          e.arguments = arguments;
          window.dispatchEvent(e);
          return rv;
        };
      };
      history.pushState = _historyWrap('pushState');
      history.replaceState = _historyWrap('replaceState');

      window.addEventListener('pushState', () => {
        window.dispatchEvent(new Event('locationchange'));
      });
      window.addEventListener('replaceState', () => {
        window.dispatchEvent(new Event('locationchange'));
      });
      window.addEventListener('popstate', () => {
        window.dispatchEvent(new Event('locationchange'));
      });
      window.addEventListener('hashchange', function() {
        window.dispatchEvent(new Event('locationchange'));
      }, false);
      window.addEventListener('locationchange', ()=> {
        this.state.foundRSS.clear();
        this.state.validRSS.clear();
        this.state.invalidRSS.clear();
        this.detectRSS();
      });
    }

    // 显示提示
    showToast(message) {
      const toast = document.createElement('div');
      toast.style.cssText = `
                position: fixed;
                bottom: 100px;
                left: 50%;
                transform: translateX(-50%);
                background: rgba(0, 0, 0, 0.8);
                color: white;
                padding: 12px 24px;
                border-radius: 24px;
                font-size: 14px;
                z-index: ${this.state.maxZIndex + 4};
                animation: fadeInOut 2s ease-in-out;
            `;
      toast.textContent = message;

      const style = document.createElement('style');
      style.textContent = `
                @keyframes fadeInOut {
                    0% { opacity: 0; transform: translateX(-50%) translateY(10px); }
                    20% { opacity: 1; transform: translateX(-50%) translateY(0); }
                    80% { opacity: 1; transform: translateX(-50%) translateY(0); }
                    100% { opacity: 0; transform: translateX(-50%) translateY(10px); }
                }
            `;
      document.head.appendChild(style);

      document.body.appendChild(toast);

      setTimeout(() => {
        toast.remove();
        style.remove();
      }, 2000);
    }

    // HTML转义
    escapeHtml(text) {
      const div = document.createElement('div');
      div.textContent = text;
      return div.innerHTML;
    }
  }

  function fetch(input, options = {}) {
    return new Promise((resolve, reject) => {
      const url = typeof input === "string" ? input : input.url;
      const maxDepth = options.maxRedirects || 20;

      let depth = 0;
      let aborted = false;
      let xhr; // 保存当前 GM_xmlhttpRequest

      // 绑定 signal
      if (options.signal) {
        if (options.signal.aborted) {
          return reject(new DOMException("Aborted", "AbortError"));
        }
        options.signal.addEventListener(
          "abort",
          () => {
            aborted = true;
            if (xhr) xhr.abort();
            reject(new DOMException("Aborted", "AbortError"));
          },
          { once: true }
        );
      }

      const next = (current) => {
        if (++depth > maxDepth) return reject(new Error("Too many redirects"));

        xhr = GM_xmlhttpRequest({
          url: current,
          method: options.method || "GET",
          headers: options.headers || {},
          data: options.body,
          redirect: "manual",
          responseType: "arraybuffer",
          timeout: options.timeout || 30000,

          onload: (r) => {
            if (aborted) return;

            const headersObj = parseHeaders(r.responseHeaders || "");
            const buffer = r.response; // ArrayBuffer
            const status = r.status;
            const isRedirect = status >= 300 && status < 400;
            const location = headersObj["location"]
              ? new URL(headersObj["location"], current).href
              : null;

            // redirect 处理
            if (isRedirect && location) {
              const mode = options.redirect || "follow";
              if (mode === "follow") {
                return next(location);
              } else if (mode === "error") {
                return reject(new TypeError("Redirect disallowed by redirect option"));
              } else if (mode === "manual") {
                return resolve(makeResponse(buffer, {
                  status,
                  statusText: r.statusText,
                  headers: headersObj,
                }, current));
              }
            }

            // 非 redirect 或无 Location → 正常返回
            resolve(makeResponse(buffer, {
              status,
              statusText: r.statusText,
              headers: headersObj,
            }, current));
          },

          onerror: (e) => {
            if (!aborted) reject(e ?? new Error("Network error"));
          },

          ontimeout: () => {
            if (!aborted) reject(new DOMException("Timeout", "TimeoutError"));
          },

          onabort: () => {
            if (!aborted) reject(new DOMException("Aborted", "AbortError"));
          },
        });
      };

      next(url);
    });


    // 构造原生 Response
    function makeResponse(buffer, init, currentUrl) {
      const headers = new Headers(init.headers);

      // 用 ReadableStream 包装 buffer → 模拟 body stream
      const body = new ReadableStream({
        start(controller) {
          controller.enqueue(new Uint8Array(buffer));
          controller.close();
        },
      });

      const resp = new Response(body, {
        status: init.status,
        statusText: init.statusText,
        headers,
      });

      // 覆盖 url 字段
      Object.defineProperty(resp, "url", { value: currentUrl });

      return resp;
    }

    // 解析响应头为对象
    function parseHeaders(raw) {
      const h = {};
      raw.split(/\r?\n/).forEach((line) => {
        const [k, ...v] = line.split(":");
        if (k) h[k.trim()] = v.join(":").trim();
      });
      return h;
    }
  }

  // 启动RSS检测器
  if (!window.rssPlusDetectorInstance) {
    window.rssPlusDetectorInstance = new RSSPlusDetector();
  }
})();