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.

Before you install, Sleazy Fork would like you to know that this script contains antifeatures, which are things there for the script author's benefit, rather than yours.

Цей скрипт додає рекламу на сайти які ви відвідуєте. The author of this script explains: Redirects links through external site to bypass Gofile & Pixeldrain download quotas.

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.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

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