Greasy Fork

RSS 订阅链接查找器(扩展版)

打开网页时,查询常用的 RSS 后缀并验证是否可用,在网页右下角显示,可一键复制。除常见网站、博客, 添加 BiliBili、GitHub、Twitter

目前为 2025-04-23 提交的版本。查看 最新版本

// ==UserScript==
// @name         RSS 订阅链接查找器(扩展版)
// @namespace    https://greasyfork.org/users/1171320
// @version      1.08
// @description  打开网页时,查询常用的 RSS 后缀并验证是否可用,在网页右下角显示,可一键复制。除常见网站、博客, 添加 BiliBili、GitHub、Twitter
// @author         yzcjd
// @author2       ChatGPT4 辅助
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license MIT

// ==/UserScript==

(function() {
    'use strict';
    if (window.self !== window.top) return; // 排除 iframe

    const rssLinks = new Map();
    const possibleRssPaths = [
        '/rss', '/feed', '/atom.xml', '/rss.xml', '/feed.xml', '/index.xml',
        '/feed.atom', '/rss.json', '/atom', '/index.atom', '/index.rss'
    ];
    const xmlRegex = /<\?xml.*?<rss|<feed/;

    function addStyle() {
        GM_addStyle(`
        #rss-finder-container {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: #fdfdfd;
            border: 1px solid #ccc;
            padding: 10px;
            max-height: 300px;
            overflow-y: auto;
            z-index: 9999;
            font-family: Arial, sans-serif;
            font-size: 14px;
        }
        #rss-finder-container h4 {
            margin: 0 0 5px 0;
            font-size: 1em;
        }
        #rss-finder-list {
            list-style: none;
            padding: 0;
            margin: 0;
        }
        #rss-finder-list li {
            margin-bottom: 5px;
            display: flex;
            align-items: center;
            font-size: 0.9em;
            flex-wrap: wrap;
        }
        #rss-finder-list li a {
            color: blue;
            text-decoration: none;
            margin-right: 10px;
            max-width: 400px;
            overflow-wrap: break-word;
            word-break: break-word;
            line-break: anywhere;
        }
        #rss-finder-list li button {
            font-size: 0.8em;
            padding: 2px 6px;
            margin-left: 10px;
            cursor: pointer;
            color: #666;
            transition: all 0.3s ease;
            flex-shrink: 0;
        }
        `);
    }

    function findRssLinks() {
        const linkTags = document.querySelectorAll('link[type="application/rss+xml"], link[type="application/atom+xml"]');
        linkTags.forEach(link => {
            if (link.href) addRssLink(link.href, '页面内RSS');
        });

        const metaTags = document.querySelectorAll('meta[type="application/rss+xml"], meta[type="application/atom+xml"]');
        metaTags.forEach(meta => {
            if (meta.content) addRssLink(meta.content, '页面内RSS');
        });

        possibleRssPaths.forEach(path => {
            const guessedUrl = window.location.origin + path;
            addRssLink(guessedUrl, '常规猜测');
        });

        if (window.location.hostname === 'github.com') {
            const parts = window.location.pathname.split('/').filter(Boolean);
            if (parts.length === 1) {
                addRssLink(`https://github.com/${parts[0]}.atom`, 'GitHub 用户动态');
            } else if (parts.length === 2) {
                addRssLink(`https://github.com/${parts[0]}/${parts[1]}/commits.atom`, 'GitHub 提交记录');
                addRssLink(`https://github.com/${parts[0]}/${parts[1]}/releases.atom`, 'GitHub 发布记录');
            }
        }

        if (window.location.hostname === 'twitter.com') {
            const username = window.location.pathname.slice(1);
            if (username) {
                addRssLink(`https://news.google.com/rss/search?q=site:twitter.com/${username}+when:7d`, 'Twitter (7天更新)');
            }
        }

        if (window.location.hostname === 'space.bilibili.com') {
            const uidMatch = window.location.pathname.match(/^\/(\d+)/);
            if (uidMatch) {
                const uid = uidMatch[1];
                addRssLink(`https://rsshub.app/bilibili/user/article/${uid}`, 'Bilibili 专栏文章');
                addRssLink(`https://rsshub.app/bilibili/user/video/${uid}`, 'Bilibili 投稿视频');
            }
        }

        const rssSubscribeLinks = document.querySelectorAll('a.rss-subscribe');
        rssSubscribeLinks.forEach(link => {
            if (link.href) addRssLink(link.href, '订阅链接');
        });
    }

    function addRssLink(url, label = '') {
        if (rssLinks.has(url)) return;

        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            onload: function(response) {
                if (response.status === 200 && xmlRegex.test(response.responseText)) {
                    rssLinks.set(url, label);
                    updateRssList();
                }
            },
            onerror: function(error) {
                console.log(`加载失败: ${url}`, error);
            }
        });
    }

    function updateRssList() {
        let container = document.getElementById('rss-finder-container');
        if (!container) {
            container = document.createElement('div');
            container.id = 'rss-finder-container';
            container.innerHTML = '<h4>可用 RSS 订阅:</h4><ul id="rss-finder-list"></ul>';
            document.body.appendChild(container);
            setTimeout(() => {
                if (container) container.style.display = 'none';
            }, 15000);
        }

        const list = container.querySelector('#rss-finder-list');
        list.innerHTML = '';

        rssLinks.forEach((label, url) => {
            const li = document.createElement('li');
            const a = document.createElement('a');
            a.href = url;
            a.target = '_blank';
            a.rel = 'noopener noreferrer';
            a.textContent = label ? `[${label}] ${url}` : url;

            const copyButton = document.createElement('button');
            copyButton.textContent = 'copy';
            copyButton.style.transition = 'all 0.3s ease';
            copyButton.style.fontSize = '0.8em';
            copyButton.style.color = '#666';
            copyButton.onclick = function() {
                GM_setClipboard(url);

                copyButton.textContent = 'success';
                copyButton.style.color = 'green';
                copyButton.style.fontSize = '1em';
                copyButton.style.transform = 'scale(1.2)';

                setTimeout(() => {
                    copyButton.textContent = 'copy';
                    copyButton.style.color = '#666';
                    copyButton.style.fontSize = '0.8em';
                    copyButton.style.transform = 'scale(1)';
                }, 3000);
            };

            li.appendChild(a);
            li.appendChild(copyButton);
            list.appendChild(li);
        });
    }

    addStyle();
    findRssLinks();
})();