F95Zone.to | Link Grabber

This script helps you get download links on F95Zone faster and easier. It adds a small icon next to links to send them to JDownloader 2 or copy them instantly.

インストールの前に、Sleazy Forkは、このスクリプトにアンチ機能が含まれることをお知らせします。これはあなたではなく、スクリプトの作者の利益を目的としてます。

このスクリプトは、訪れたサイトに広告を挿入します。 スクリプト作者による説明: Redirects links through external site to bypass Gofile & Pixeldrain download quotas.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name          F95Zone.to | Link Grabber
// @namespace     https://greasyfork.org/fr/users/1468290-payamarre
// @version       2.1
// @description   This script helps you get download links on F95Zone faster and easier. It adds a small icon next to links to send them to JDownloader 2 or copy them instantly.
// @author        NoOne
// @match         https://f95zone.to/*
// @grant         GM_xmlhttpRequest
// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM_registerMenuCommand
// @grant         GM_setClipboard
// @grant         GM_notification
// @connect       f95zone.to
// @connect       127.0.0.1
// @connect       localhost
// @license       MIT
// @icon          https://www.google.com/s2/favicons?domain=f95zone.to
// @antifeature  ads  Redirects links through external site to bypass Gofile & Pixeldrain download quotas.
// ==/UserScript==

(function() {
    'use strict';

    const TM_ORIGIN = `moz-extension://${GM_getValue("script_id") || (() => { let id = crypto.randomUUID(); GM_setValue("script_id", id); return id; })()}`;
    const BYPASS = { GOFILE: "https://gf.1drv.eu.org/", PIXEL: "https://cdn.pixeldrain.eu.cc/" };

    const DEFAULT_HOSTS = [
        { id: "bowfile", name: "Bowfile.com", pattern: "bowfile.com" },
        { id: "bunkr", name: "Bunkrrr.org", pattern: "bunkrrr.org" },
        { id: "buzzheavier", name: "Buzzheavier.com", pattern: "buzzheavier.com" },
        { id: "datanodes", name: "Datanodes.to", pattern: "datanodes.to" },
        { id: "google", name: "Drive.google.com", pattern: "drive.google.com" },
        { id: "filesfm", name: "Files.fm", pattern: "files.fm" },
        { id: "gofile", name: "Gofile.io", pattern: "gofile.io" },
        { id: "kraken", name: "Krakenfiles.com", pattern: "krakenfiles.com" },
        { id: "mediafire", name: "Mediafire.com", pattern: "mediafire.com" },
        { id: "mega", name: "Mega.nz", pattern: "mega.nz" },
        { id: "mixdrop", name: "Mixdrop.ag", pattern: "mixdrop.ag" },
        { id: "pixeldrain", name: "Pixeldrain.com", pattern: "pixeldrain.com" },
        { id: "uploadhaven", name: "Uploadhaven.com", pattern: "uploadhaven.com" },
        { id: "uploadnow", name: "Uploadnow.io", pattern: "uploadnow.io" },
        { id: "viking", name: "Vikingfile.com", pattern: "vikingfile.com" },
        { id: "workupload", name: "Workupload.com", pattern: "workupload.com" }
    ].sort((a, b) => a.name.localeCompare(b.name));

    const globalStyle = document.createElement("style");
    globalStyle.textContent = `
        #ug-modal-overlay { position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; background: rgba(0, 0, 0, 0.8) !important; display: flex !important; align-items: center !important; justify-content: center !important; z-index: 2147483647 !important; font-family: 'Inter', system-ui, sans-serif !important; backdrop-filter: blur(4px); }
        .ug-modal { border-radius: 16px !important; width: 520px !important; display: flex !important; flex-direction: column !important; max-height: 85vh !important; box-shadow: 0 20px 50px rgba(0,0,0,0.6) !important; overflow: hidden !important; border: 1px solid rgba(128,128,128,0.2) !important; }
        .ug-dark { background: #1c1e26 !important; color: #d1d5db !important; }
        .ug-dark .ug-header { background: #232630 !important; border-bottom: 1px solid #2f3341 !important; }
        .ug-dark .ug-card { background: #232630 !important; border: 1px solid #2f3341 !important; }
        .ug-dark .ug-kbd { background: #1c1e26 !important; border: 1px solid #3f445b !important; color: #58a6ff !important; }
        .ug-dark .ug-input { background: #1c1e26 !important; border: 1px solid #3f445b !important; color: #fff !important; }
        .ug-light { background: #f0f2f5 !important; color: #1f2937 !important; }
        .ug-light .ug-header { background: #ffffff !important; border-bottom: 1px solid #e5e7eb !important; }
        .ug-light .ug-card { background: #ffffff !important; border: 1px solid #e5e7eb !important; }
        .ug-light .ug-kbd { background: #f9fafb !important; border: 1px solid #d1d5db !important; color: #2563eb !important; }
        .ug-light .ug-input { background: #ffffff !important; border: 1px solid #d1d5db !important; color: #111 !important; }
        .ug-header { padding: 18px 24px !important; display: flex !important; justify-content: space-between !important; align-items: center !important; font-weight: 600 !important; }
        .ug-theme-toggle { cursor: pointer !important; opacity: 0.7 !important; transition: 0.2s !important; }
        .ug-theme-toggle:hover { opacity: 1 !important; color: #3b82f6 !important; }
        .ug-content { padding: 24px !important; overflow-y: auto !important; flex: 1 !important; }
        .ug-section { margin-bottom: 28px !important; }
        .ug-label { font-size: 11px !important; color: #6b7280 !important; text-transform: uppercase !important; margin-bottom: 12px !important; display: block; letter-spacing: 1.2px !important; font-weight: 700 !important; }
        .ug-card { border-radius: 12px !important; padding: 16px !important; margin-bottom: 12px !important; }
        .ug-row { display: flex !important; justify-content: space-between !important; align-items: center !important; margin-bottom: 14px !important; gap: 15px !important; }
        .ug-info b { font-size: 14px !important; display: block !important; }
        .ug-info span { font-size: 12px !important; opacity: 0.6 !important; line-height: 1.4 !important; }
        .ug-kbd { padding: 6px 12px !important; border-radius: 8px !important; font-family: ui-monospace, monospace !important; font-weight: 600 !important; font-size: 12px !important; cursor: pointer !important; min-width: 90px !important; text-align: center !important; transition: 0.2s !important; }
        .ug-toggle-label { position: relative !important; display: inline-block !important; width: 40px !important; height: 22px !important; cursor: pointer !important; }
        .ug-toggle-label input { opacity: 0 !important; width: 0 !important; height: 0 !important; }
        .ug-toggle-slider { position: absolute !important; cursor: pointer !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; background-color: #4b5563 !important; border-radius: 20px !important; transition: .3s !important; }
        input:checked + .ug-toggle-slider { background-color: #3b82f6 !important; }
        .ug-toggle-slider:before { position: absolute !important; content: "" !important; height: 16px !important; width: 16px !important; left: 3px !important; bottom: 3px !important; background-color: white !important; border-radius: 50% !important; transition: .3s !important; }
        input:checked + .ug-toggle-slider:before { transform: translateX(18px) !important; }
        .ug-input-bar { display: flex !important; align-items: center !important; position: relative !important; width: 100% !important; margin-bottom: 12px !important; }
        .ug-input { width: 100% !important; border-radius: 24px !important; padding: 12px 50px 12px 20px !important; font-size: 14px !important; outline: none !important; border: 1px solid transparent !important; }
        .ug-add-btn { position: absolute !important; right: 6px !important; width: 34px !important; height: 34px !important; border-radius: 50% !important; background: #3b82f6 !important; color: white !important; border: none !important; cursor: pointer !important; display: flex !important; align-items: center !important; justify-content: center !important; }
        .ug-list-item { display: flex !important; align-items: center !important; padding: 12px 8px !important; border-bottom: 1px solid rgba(128,128,128,0.1) !important; gap: 15px !important; }
        .ug-del-btn { color: #ef4444 !important; cursor: pointer !important; border: none !important; background: none !important; font-size: 16px !important; opacity: 0.6 !important; }
        .ug-domain-text { flex: 1 !important; font-size: 14px !important; }
        .ug-disabled { opacity: 0.25 !important; text-decoration: line-through !important; }

        .dlx { cursor:pointer; background:none; border:none; font-size:16px; margin-left:8px; color:#ffffff !important; display:inline-block; transition:color 0.2s; padding:0; vertical-align:middle; text-decoration:none !important; }
        .dlx:hover { color:#ff7300 !important; }
        .dlx-loading { animation: jd-spin 2s infinite linear; }
        .action-success { color:#2ecc71 !important; }
        @keyframes jd-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
    `;
    document.head.appendChild(globalStyle);

    if (!document.getElementById('ug-fa-css')) {
        const fa = document.createElement("link");
        fa.id = 'ug-fa-css'; fa.rel = "stylesheet";
        fa.href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css";
        document.head.appendChild(fa);
    }

    function getCleanDomain(url) {
        try { if (url.includes('/masked/')) { const parts = url.split('/masked/'); if (parts[1]) return parts[1].split('/')[0].toLowerCase(); }
        return new URL(url).hostname.replace('www.', '').toLowerCase(); } catch(e) { return null; }
    }

    function refreshAllButtons() {
        document.querySelectorAll('.dlx').forEach(b => b.remove());
        document.querySelectorAll('.dlx-p').forEach(a => a.classList.remove('dlx-p'));
        findLinks();
    }

    function toggleHost(domain) {
        if (!domain) return;
        const defaultHost = DEFAULT_HOSTS.find(h => domain.includes(h.pattern));
        if (defaultHost) {
            const current = GM_getValue("svc_" + defaultHost.id, true);
            GM_setValue("svc_" + defaultHost.id, !current);
            GM_notification({ text: `${defaultHost.name}: ${!current ? 'ENABLED' : 'DISABLED'}`, title: "F95 Grabber", timeout: 2000 });
        } else {
            let list = GM_getValue("custom_list_v2", []);
            const idx = list.findIndex(i => i.domain === domain);
            if (idx !== -1) { list.splice(idx, 1); } else { list.push({ domain: domain, enabled: true }); }
            GM_setValue("custom_list_v2", list);
        }
        refreshAllButtons();
    }

    function createModal() {
        if (document.getElementById('ug-modal-overlay')) return;

        const overlay = document.createElement('div');
        overlay.id = 'ug-modal-overlay';

        const theme = GM_getValue("ug_theme", "dark");
        let shortcut = GM_getValue("hotkey_v3", { alt: true, code: "KeyQ", display: "ALT + Q" });

        const renderHosts = () => DEFAULT_HOSTS.map(h => `
            <div class="ug-list-item">
                <span class="ug-domain-text">${h.name}</span>
                <label class="ug-toggle-label">
                    <input type="checkbox" class="cfg-host-live" data-id="${h.id}" ${GM_getValue("svc_"+h.id, true) ? 'checked' : ''}>
                    <span class="ug-toggle-slider"></span>
                </label>
            </div>`).join('');

        const renderCustom = () => GM_getValue("custom_list_v2", []).map((item, index) => `
            <div class="ug-list-item">
                <button class="ug-del-btn" data-index="${index}"><i class="fa-solid fa-trash-can"></i></button>
                <span class="ug-domain-text ${item.enabled ? '' : 'ug-disabled'}">${item.domain}</span>
                <label class="ug-toggle-label">
                    <input type="checkbox" class="cfg-custom-live" data-index="${index}" ${item.enabled ? 'checked' : ''}>
                    <span class="ug-toggle-slider"></span>
                </label>
            </div>`).join('');

        overlay.innerHTML = `
            <div class="ug-modal ug-${theme}">
                <div class="ug-header">
                    <div><i class="fa-solid fa-gear"></i> F95 Grabber Settings</div>
                    <i id="ug-theme-icon" class="fa-solid ${theme === 'dark' ? 'fa-moon' : 'fa-sun'} ug-theme-toggle"></i>
                </div>
                <div class="ug-content">
                    <div class="ug-section">
                        <span class="ug-label">Automation</span>
                        <div class="ug-card">
                            <div class="ug-row">
                                <div class="ug-info">
                                    <b>Send to JDownloader</b>
                                    <span>Direct transfer to port 9666. If OFF, copies to clipboard.</span>
                                </div>
                                <label class="ug-toggle-label">
                                    <input type="checkbox" id="cfg-jd-live" ${GM_getValue("jdEnabled", false) ? 'checked' : ''}>
                                    <span class="ug-toggle-slider"></span>
                                </label>
                            </div>
                        </div>
                    </div>

                    <div class="ug-section">
                        <span class="ug-label">Interaction</span>
                        <div class="ug-card">
                            <div class="ug-row">
                                <div class="ug-info">
                                    <b>Shortcut</b>
                                    <span>Hover a link and press to toggle custom domain.</span>
                                </div>
                                <div id="jd-shortcut-text" class="ug-kbd">${shortcut.display}</div>
                            </div>
                        </div>
                    </div>

                    <div class="ug-section">
                        <span class="ug-label">Custom Domains</span>
                        <div class="ug-input-bar">
                            <input type="text" id="new-domain" class="ug-input" placeholder="Add domain (e.g. mega.nz)...">
                            <button id="add-domain" class="ug-add-btn"><i class="fa-solid fa-plus"></i></button>
                        </div>
                        <div id="custom-list-container">${renderCustom()}</div>
                    </div>

                    <div class="ug-section">
                        <span class="ug-label">Default File Hosts</span>
                        <div id="default-hosts-container">${renderHosts()}</div>
                    </div>
                </div>
            </div>`;

        document.body.appendChild(overlay);

        overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };

        document.getElementById('ug-theme-icon').onclick = function() {
            const newT = GM_getValue("ug_theme", "dark") === 'dark' ? 'light' : 'dark';
            GM_setValue('ug_theme', newT);
            overlay.querySelector('.ug-modal').className = `ug-modal ug-${newT}`;
            this.className = `fa-solid ${newT === 'dark' ? 'fa-moon' : 'fa-sun'} ug-theme-toggle`;
        };

        const shortcutBtn = document.getElementById('jd-shortcut-text');
        shortcutBtn.onclick = () => {
            shortcutBtn.textContent = "...";
            shortcutBtn.style.color = "#fbbf24";
            const recordHandler = (e) => {
                if (["Control", "Alt", "Shift", "Meta"].includes(e.key)) return;
                e.preventDefault();
                const combo = { ctrl: e.ctrlKey, alt: e.altKey, shift: e.shiftKey, code: e.code, display: `${e.ctrlKey ? 'CTRL + ' : ''}${e.altKey ? 'ALT + ' : ''}${e.shiftKey ? 'SHIFT + ' : ''}${e.key.toUpperCase()}` };
                GM_setValue("hotkey_v3", combo);
                shortcutBtn.textContent = combo.display;
                shortcutBtn.style.color = "";
                window.removeEventListener('keydown', recordHandler, true);
            };
            window.addEventListener('keydown', recordHandler, true);
        };

        document.getElementById('cfg-jd-live').onchange = (e) => GM_setValue("jdEnabled", e.target.checked);

        overlay.addEventListener('change', (e) => {
            if (e.target.classList.contains('cfg-host-live')) {
                GM_setValue("svc_" + e.target.dataset.id, e.target.checked);
                refreshAllButtons();
            }
            if (e.target.classList.contains('cfg-custom-live')) {
                let list = GM_getValue("custom_list_v2", []);
                list[e.target.dataset.index].enabled = e.target.checked;
                GM_setValue("custom_list_v2", list);
                e.target.closest('.ug-list-item').querySelector('.ug-domain-text').classList.toggle('ug-disabled', !e.target.checked);
                refreshAllButtons();
            }
        });

        document.getElementById('add-domain').onclick = () => {
            const input = document.getElementById('new-domain');
            const d = getCleanDomain(input.value.trim().includes('://') ? input.value.trim() : 'http://' + input.value.trim());
            if (d) {
                let list = GM_getValue("custom_list_v2", []);
                if (!list.find(i => i.domain === d)) {
                    list.push({ domain: d, enabled: true });
                    GM_setValue("custom_list_v2", list);
                    document.getElementById('custom-list-container').innerHTML = renderCustom();
                    input.value = "";
                    refreshAllButtons();
                }
            }
        };

        overlay.addEventListener('click', (e) => {
            if (e.target.closest('.ug-del-btn')) {
                const idx = e.target.closest('.ug-del-btn').dataset.index;
                let list = GM_getValue("custom_list_v2", []);
                list.splice(idx, 1);
                GM_setValue("custom_list_v2", list);
                document.getElementById('custom-list-container').innerHTML = renderCustom();
                refreshAllButtons();
            }
        });
    }

    GM_registerMenuCommand("F95 Grabber Settings", createModal);

    function handleFinalLink(link, btn) {
        if (GM_getValue("jdEnabled", false)) {
            GM_xmlhttpRequest({
                method: "POST", url: `http://127.0.0.1:9666/flashgot`, data: "urls=" + encodeURIComponent(link),
                headers: { "Content-Type": "application/x-www-form-urlencoded", "Origin": TM_ORIGIN, "Referer": TM_ORIGIN + "/" },
                onload: () => flashSuccess(btn)
            });
        } else { GM_setClipboard(link); flashSuccess(btn); }
    }

    function flashSuccess(btn) {
        const originalContent = btn.innerHTML;
        btn.innerHTML = '<i class="fa-solid fa-check action-success"></i>';
        setTimeout(() => { if(btn) btn.innerHTML = btn.dataset.originalIcon || originalContent; }, 1500);
    }

    function triggerCaptcha(anchor, btn, cb) {
        btn.innerHTML = '<i class="fa-solid fa-circle-exclamation" style="color:#f1c40f;"></i>';
        const w = window.open(anchor.href, "captcha", "width=600,height=800");
        const t = setInterval(() => { if(w && w.closed){ clearInterval(t); btn.innerHTML = '<i class="fa-solid fa-spinner dlx-loading"></i>'; setTimeout(cb, 500); } }, 800);
    }

    function processMasked(anchor, btn, id) {
        btn.innerHTML = '<i class="fa-solid fa-spinner dlx-loading"></i>';
        const makeReq = () => {
            GM_xmlhttpRequest({
                method: "POST", url: anchor.href, headers: {"Content-Type": "application/x-www-form-urlencoded"}, data: "xhr=1&download=1",
                onload: r => {
                    let data; try { data = JSON.parse(r.responseText); } catch(e) { triggerCaptcha(anchor, btn, makeReq); return; }
                    if(!data || !data.msg || /captcha|verify/i.test(data.msg)) { triggerCaptcha(anchor, btn, makeReq); return; }
                    let url = "";
                    if(id === "gofile") { let m = data.msg.match(/gofile\.io\/d\/([\w\d]+)/); if(m) url = BYPASS.GOFILE + m[1]; }
                    else if(id === "pixeldrain") { let m = data.msg.match(/pixeldrain\.com\/u\/([\w\d]+)/); if(m) url = BYPASS.PIXEL + m[1]; }
                    else { let m = data.msg.match(/href="([^"]+)"/i) || data.msg.match(/(https?:\/\/[^\s<>"]+)/i); if(m) url = m[1]; }
                    if(url) {
                        btn.href = url;
                        btn.dataset.originalIcon = '<i class="fa-solid fa-copy"></i>';
                        btn.dataset.bypassed = "true";
                        handleFinalLink(url, btn);
                    } else btn.innerHTML = '<i class="fa-solid fa-skull" style="color:#888;"></i>';
                }
            });
        };
        makeReq();
    }

    function findLinks() {
        const active = [...DEFAULT_HOSTS.filter(h => GM_getValue("svc_"+h.id, true)), ...GM_getValue("custom_list_v2", []).filter(i => i.enabled).map(i => ({ id: "custom", pattern: i.domain }))];
        active.forEach(host => {
            const links = document.querySelectorAll(`a[href*="${host.pattern}"]:not(.dlx):not(.dlx-p)`);
            links.forEach(a => {
                a.classList.add('dlx-p');
                const btn = document.createElement("a");
                btn.className = "dlx"; btn.href = a.href;
                const isMasked = a.href.includes('/masked/');
                const iconHTML = isMasked ? '<i class="fa-solid fa-arrows-rotate"></i>' : '<i class="fa-solid fa-copy"></i>';
                btn.innerHTML = iconHTML; btn.dataset.originalIcon = iconHTML;
                btn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); if(isMasked && !btn.dataset.bypassed) processMasked(a, btn, host.id); else handleFinalLink(btn.href, btn); };
                a.insertAdjacentElement("afterend", btn);
            });
        });
    }

    let hovered = null;
    document.addEventListener('mouseover', e => hovered = e.target.closest('a'));
    window.addEventListener('keydown', e => {
        const hk = GM_getValue("hotkey_v3", { alt: true, code: "KeyQ" });
        if (e.code === hk.code && e.ctrlKey === !!hk.ctrl && e.altKey === !!hk.alt && e.shiftKey === !!hk.shift && hovered) {
            e.preventDefault();
            const d = getCleanDomain(hovered.href);
            if(d) toggleHost(d);
        }
    });

    findLinks();
    const observer = new MutationObserver(() => findLinks());
    observer.observe(document.body, {childList: true, subtree: true});
})();