Greasy Fork

来自缓存

Greasy Fork is available in English.

Magnet Hash Autolinker

Converts 40-char hex hashes into clickable magnet links

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Magnet Hash Autolinker
// @namespace   Violentmonkey Scripts
// @match       *://*/*
// @grant       none
// @version     1.0
// @author      chimez
// @description Converts 40-char hex hashes into clickable magnet links
// @run-at       document-end
// @license MIT
// ==/UserScript==


(function() {
    'use strict';

    // 1. The Regex: Matches exactly 40 hexadecimal characters (case insensitive)
    // \b ensures we don't cut off longer strings or match inside css/js code
    const hashRegex = /\b[a-fA-F0-9]{40}\b/g;

    // 2. Tags to ignore (to avoid breaking scripts, styles, or existing links)
    const ignoredTags = new Set(['A', 'SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'SELECT', 'BUTTON', 'CODE', 'PRE']);

    /**
     * scans a specific DOM node (and its children) for hashes
     */
    function processNode(rootNode) {
        // We only care about Elements (1) to traverse, but we actually modify Text Nodes (3)
        if (!rootNode || !rootNode.nodeType) return;

        // Create a TreeWalker to find all text nodes under the rootNode
        const walker = document.createTreeWalker(
            rootNode,
            NodeFilter.SHOW_TEXT,
            {
                acceptNode: function(node) {
                    // Skip if parent is a forbidden tag (e.g. don't link inside a <script> or existing <a>)
                    if (node.parentNode && ignoredTags.has(node.parentNode.tagName)) {
                        return NodeFilter.FILTER_REJECT;
                    }
                    // Skip if text doesn't contain a hash (optimization)
                    if (!hashRegex.test(node.nodeValue)) {
                        return NodeFilter.FILTER_SKIP;
                    }
                    return NodeFilter.FILTER_ACCEPT;
                }
            },
            false
        );

        const nodesToReplace = [];

        // Collect nodes first to avoid messing up the walker while modifying the DOM
        while (walker.nextNode()) {
            nodesToReplace.push(walker.currentNode);
        }

        // Process the collected nodes
        nodesToReplace.forEach(textNode => {
            replaceTextWithLinks(textNode);
        });
    }

    /**
     * Replaces the text node with a DocumentFragment containing the text and the new links
     */
    function replaceTextWithLinks(textNode) {
        const text = textNode.nodeValue;
        let match;
        let lastIndex = 0;
        const fragment = document.createDocumentFragment();
        let found = false;

        // Reset regex state
        hashRegex.lastIndex = 0;

        while ((match = hashRegex.exec(text)) !== null) {
            found = true;

            // 1. Append text before the match
            const before = text.substring(lastIndex, match.index);
            if (before) fragment.appendChild(document.createTextNode(before));

            // 2. Create the link
            const a = document.createElement('a');
            const hash = match[0];
            a.href = `magnet:?xt=urn:btih:${hash}`;
            a.textContent = hash;
            a.style.color = 'green'; // Optional: make it distinct
            a.style.fontWeight = 'bold';
            a.title = "Click to open Magnet Link";
            fragment.appendChild(a);

            lastIndex = hashRegex.lastIndex;
        }

        // If we found matches, append the rest of the text and replace the original node
        if (found) {
            const after = text.substring(lastIndex);
            if (after) fragment.appendChild(document.createTextNode(after));
            textNode.parentNode.replaceChild(fragment, textNode);
        }
    }

    // --- Execution ---

    // 1. Run on initial page load
    processNode(document.body);

    // 2. Run on dynamic content changes (AJAX/Infinite Scroll)
    const observer = new MutationObserver(mutations => {
        // We debounce or just limit scope to avoid freezing browser on huge updates
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                // If an element was added, scan it
                if (node.nodeType === Node.ELEMENT_NODE) {
                    processNode(node);
                }
                // If a text node was added directly (rare but possible)
                else if (node.nodeType === Node.TEXT_NODE) {
                    // Check parent before processing
                    if (node.parentNode && !ignoredTags.has(node.parentNode.tagName)) {
                        replaceTextWithLinks(node);
                    }
                }
            }
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

})();