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, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

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