GoFile Download Bypass (with banner)

Redirect gofile share pages (e.g. /d/QGjyK2) to gf.1drv.eu.org (maps /d/<id> -> /<id>). Shows a brief banner "redirected to gf.1drv.eu.org" before redirecting. Opt-out with ?noredirect=1. Preserves trailing path/query/hash and avoids loops.

// ==UserScript==
// @name         GoFile Download Bypass (with banner)
// @namespace    https://greasyfork.org/en/users/1522706-squiggly6279
// @version      1.2
// @description  Redirect gofile share pages (e.g. /d/QGjyK2) to gf.1drv.eu.org (maps /d/<id> -> /<id>). Shows a brief banner "redirected to gf.1drv.eu.org" before redirecting. Opt-out with ?noredirect=1. Preserves trailing path/query/hash and avoids loops.
// @author       you
// @icon         https://www.google.com/s2/favicons?domain=gofile.io
// @match        *://gofile.io/*
// @match        *://www.gofile.io/*
// @match        *://gofile.co/*
// @match        *://www.gofile.co/*
// @match        *://*.gofile.*/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ---------- CONFIG ----------
    const TARGET_HOST = 'gf.1drv.eu.org'; // destination host
    const IGNORE_PREFIXES = ['/api/', '/public/', '/static/', '/cdn/', '/download?']; // do not redirect these
    const OPT_OUT_PARAM = 'noredirect'; // add ?noredirect=1 to opt out
    const ID_RE = /^[A-Za-z0-9_-]{4,40}$/; // allowed chars for id
    const BANNER_SHOW_MS = 1100; // how long banner is visible before redirect (ms)
    const BANNER_FADE_MS = 300;   // CSS fade duration (ms)
    // --------------------------------

    try {
        if (window.top !== window) return; // run only in top window

        const host = location.hostname;
        if (host === TARGET_HOST) return; // already on target

        // basic allowed host check
        const allowedHosts = ['gofile.io', 'www.gofile.io', 'gofile.co', 'www.gofile.co'];
        if (!allowedHosts.some(h => host === h || host.endsWith('.' + h))) return;

        const url = new URL(location.href);
        const path = location.pathname || '/';
        const lowerPath = path.toLowerCase();

        // Respect opt-out
        if (url.searchParams.has(OPT_OUT_PARAM)) {
            console.debug('GoFile bypass: opt-out param present, skipping redirect');
            return;
        }

        // Ignore common API/static endpoints
        for (const pfx of IGNORE_PREFIXES) {
            if (lowerPath.startsWith(pfx)) {
                console.debug('GoFile bypass: ignoring path prefix', pfx);
                return;
            }
        }

        // helper to build target URL, preserving protocol/query/hash and optional trailingPath
        function buildTargetUrl(id, trailing = '') {
            if (trailing && !trailing.startsWith('/')) trailing = '/' + trailing;
            const q = location.search || '';
            const h = location.hash || '';
            return location.protocol + '//' + TARGET_HOST + '/' + id + trailing + q + h;
        }

        // Prevent looping: sessionStorage per-id marker
        function alreadyRedirected(key) {
            try { return !!sessionStorage.getItem('gofile_redirected_' + key); } catch (e) { return false; }
        }
        function markRedirected(key) {
            try { sessionStorage.setItem('gofile_redirected_' + key, Date.now().toString()); } catch (e) { /* ignore */ }
        }

        // Create and show banner, then call onDone() after timing; returns a Promise
        function showBannerThen(delayMs = BANNER_SHOW_MS) {
            return new Promise((resolve) => {
                try {
                    // banner container
                    const banner = document.createElement('div');
                    banner.setAttribute('id', 'gofile-redirect-banner');
                    banner.textContent = `Redirected to ${TARGET_HOST}`;
                    // minimal inline styles (kept small & unobtrusive)
                    const style = banner.style;
                    style.position = 'fixed';
                    style.top = '16px';
                    style.left = '50%';
                    style.transform = 'translateX(-50%)';
                    style.zIndex = 2147483647; // max z-index to be visible
                    style.padding = '10px 14px';
                    style.fontFamily = 'Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial';
                    style.fontSize = '13px';
                    style.borderRadius = '10px';
                    style.boxShadow = '0 6px 18px rgba(0,0,0,0.28)';
                    style.background = 'linear-gradient(180deg, rgba(255,255,255,0.96), rgba(245,245,245,0.96))';
                    style.color = '#111';
                    style.opacity = '0';
                    style.transition = `opacity ${BANNER_FADE_MS}ms ease, transform ${BANNER_FADE_MS}ms ease`;
                    style.pointerEvents = 'none';
                    // append and trigger fade-in
                    document.documentElement.appendChild(banner);
                    // force reflow then show
                    // eslint-disable-next-line no-unused-expressions
                    banner.offsetHeight;
                    style.opacity = '1';
                    style.transform = 'translateX(-50%) translateY(0)';

                    // after delay, fade out then remove
                    setTimeout(() => {
                        style.opacity = '0';
                        style.transform = 'translateX(-50%) translateY(-6px)';
                        setTimeout(() => {
                            try { banner.remove(); } catch (e) { /* ignore */ }
                            resolve();
                        }, BANNER_FADE_MS + 10);
                    }, delayMs);
                } catch (e) {
                    // If anything fails drawing banner, just resolve immediately
                    resolve();
                }
            });
        }

        // Patterns to detect share id + optional trailing path
        const shareRegexes = [
            /\/d\/([A-Za-z0-9_-]{4,40})(\/.*)?$/i,
            /\/f\/([A-Za-z0-9_-]{4,40})(\/.*)?$/i,
            /\/file\/d\/([A-Za-z0-9_-]{4,40})(\/.*)?$/i,
            /[?&]file=([A-Za-z0-9_-]{4,40})/i,
            /\/([A-Za-z0-9_-]{4,40})(?:\/.*)?$/i
        ];

        for (const re of shareRegexes) {
            const m = location.pathname.match(re) || location.href.match(re);
            if (m && m[1]) {
                const id = m[1];
                const trailing = (m[2] && m[2] !== '/') ? m[2] : '';
                if (!ID_RE.test(id)) continue;
                if (alreadyRedirected(id)) {
                    console.debug('GoFile bypass: already redirected id', id, '- skipping');
                    return;
                }
                const target = buildTargetUrl(id, trailing);
                markRedirected(id);
                console.debug('GoFile bypass: redirecting to', target);
                // show banner then redirect
                showBannerThen().then(() => location.replace(target));
                return;
            }
        }

        // Root redirect (show banner then redirect)
        if (path === '/' || path === '' || path === '/index.html') {
            if (document.referrer && (new URL(document.referrer).hostname === TARGET_HOST)) {
                console.debug('GoFile bypass: referrer is target host, skipping root redirect');
                return;
            }
            const rootTarget = location.protocol + '//' + TARGET_HOST + '/' + (location.search || '') + (location.hash || '');
            console.debug('GoFile bypass: redirecting root to', rootTarget);
            showBannerThen().then(() => location.replace(rootTarget));
            return;
        }

        console.debug('GoFile bypass: no redirect rule matched for', location.href);
    } catch (err) {
        console.error('GoFile bypass script error:', err);
    }
})();