Filecrypt Unlocker

Automatically decrypt links from a Filecrypt container.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Filecrypt Unlocker
// @description  Automatically decrypt links from a Filecrypt container.
// @icon         https://icons.duckduckgo.com/ip3/filecrypt.to.ico
// @version      1.2
// @author       afkarxyz
// @namespace    https://github.com/afkarxyz/userscripts
// @supportURL   https://github.com/afkarxyz/userscripts/issues
// @license      MIT
// @match        https://*.filecrypt.cc/Container/*
// @match        https://*.filecrypt.to/Container/*
// @grant        GM_xmlhttpRequest
// @connect      dcrypt.it
// @run-at       document-idle
// ==/UserScript==

(() => {
    'use strict';

    const DECRYPT_URL = 'http://dcrypt.it/decrypt/paste';

    const faviconUrl = domain => `https://icons.duckduckgo.com/ip3/${domain}.ico`;

    const COPY_ICON = '<svg viewBox="0 0 640 640" aria-hidden="true"><path fill="currentColor" d="M360 160L280 160C266.7 160 256 149.3 256 136C256 122.7 266.7 112 280 112L360 112C373.3 112 384 122.7 384 136C384 149.3 373.3 160 360 160zM360 208C397.1 208 427.6 180 431.6 144L448 144C456.8 144 464 151.2 464 160L464 512C464 520.8 456.8 528 448 528L192 528C183.2 528 176 520.8 176 512L176 160C176 151.2 183.2 144 192 144L208.4 144C212.4 180 242.9 208 280 208L360 208zM419.9 96C407 76.7 385 64 360 64L280 64C255 64 233 76.7 220.1 96L192 96C156.7 96 128 124.7 128 160L128 512C128 547.3 156.7 576 192 576L448 576C483.3 576 512 547.3 512 512L512 160C512 124.7 483.3 96 448 96L419.9 96z"/></svg>';
    const SUCCESS_ICON = '<svg viewBox="0 0 640 640" aria-hidden="true"><path fill="currentColor" d="M360 64C385 64 407 76.7 419.9 96L448 96C483.3 96 512 124.7 512 160L512 512C512 547.3 483.3 576 448 576L192 576C156.7 576 128 547.3 128 512L128 160C128 124.7 156.7 96 192 96L220.1 96C233 76.7 255 64 280 64L360 64zM192 144C183.2 144 176 151.2 176 160L176 512C176 520.8 183.2 528 192 528L448 528C456.8 528 464 520.8 464 512L464 160C464 151.2 456.8 144 448 144L431.6 144C427.6 180 397.1 208 360 208L280 208C242.9 208 212.4 180 208.4 144L192 144zM356.7 303.9C364.5 293.2 379.5 290.8 390.2 298.6C400.9 306.4 403.3 321.4 395.5 332.1L319.4 436.8C315.2 442.5 308.7 446.1 301.6 446.6C294.5 447.1 287.6 444.4 282.7 439.3L246.8 402.1C237.6 392.6 237.9 377.4 247.4 368.2C256.9 359 272.1 359.2 281.3 368.8L297.4 385.4L356.7 303.8zM280 112C266.7 112 256 122.7 256 136C256 149.3 266.7 160 280 160L360 160C373.3 160 384 149.3 384 136C384 122.7 373.3 112 360 112L280 112z"/></svg>';
    const OPEN_ICON = '<svg viewBox="0 0 640 640" aria-hidden="true"><path fill="currentColor" d="M384 64C366.3 64 352 78.3 352 96C352 113.7 366.3 128 384 128L466.7 128L265.3 329.4C252.8 341.9 252.8 362.2 265.3 374.7C277.8 387.2 298.1 387.2 310.6 374.7L512 173.3L512 256C512 273.7 526.3 288 544 288C561.7 288 576 273.7 576 256L576 96C576 78.3 561.7 64 544 64L384 64zM144 160C99.8 160 64 195.8 64 240L64 496C64 540.2 99.8 576 144 576L400 576C444.2 576 480 540.2 480 496L480 416C480 398.3 465.7 384 448 384C430.3 384 416 398.3 416 416L416 496C416 504.8 408.8 512 400 512L144 512C135.2 512 128 504.8 128 496L128 240C128 231.2 135.2 224 144 224L224 224C241.7 224 256 209.7 256 192C256 174.3 241.7 160 224 160L144 160z"/></svg>';

    const getDomain = url => {
        try { return new URL(url).hostname.replace('www.', ''); }
        catch { return ''; }
    };

    const request = details => new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            timeout: 30000,
            ...details,
            onload: resolve,
            onerror: () => reject(new Error('Request failed')),
            ontimeout: () => reject(new Error('Request timed out')),
        });
    });

    const decryptDlc = async dlcContent => {
        const response = await request({
            method: 'POST',
            url: DECRYPT_URL,
            headers: {
                'Accept': 'application/json, text/javascript, */*',
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                'X-Requested-With': 'XMLHttpRequest',
                'Origin': 'http://dcrypt.it',
                'Referer': 'http://dcrypt.it/',
            },
            data: new URLSearchParams({ content: dlcContent }).toString(),
        });

        if (response.status < 200 || response.status >= 300) {
            throw new Error(`dcrypt.it responded with ${response.status}`);
        }

        let json;
        try {
            json = JSON.parse(response.responseText);
        } catch {
            throw new Error('Invalid response from dcrypt.it');
        }

        const links = json?.success?.links;
        if (!Array.isArray(links) || !links.length) {
            throw new Error('No links returned by dcrypt.it');
        }

        return links;
    };

    const injectStyles = () => {
        const style = document.createElement('style');
        style.textContent = `
            #czx_decryptor,
            #czx_decryptor * { box-sizing: border-box; }
            #czx_decryptor {
                text-align: left;
                background: #ffffff !important;
                border-color: #9fcfe4 !important;
                color: #183247 !important;
                box-shadow: 0 8px 24px rgba(30, 100, 130, .12) !important;
            }
            #czx_decryptor button,
            #czx_decryptor a.czx_btn {
                cursor: pointer;
                font: inherit;
            }
            #czx_decryptor .czx_btn {
                display: inline-flex;
                align-items: center;
                justify-content: center;
                gap: 6px;
                min-height: var(--czx-height, 32px);
                padding: var(--czx-padding, 7px 14px);
                border-radius: 4px !important;
                border: 1px solid var(--czx-border, transparent);
                background: var(--czx-bg, #1c1c44);
                color: var(--czx-color, #ffffff);
                font-size: 12px;
                font-weight: 600;
                line-height: 1.2;
                text-decoration: none;
                white-space: nowrap;
                flex-shrink: 0;
                box-shadow: none !important;
                transition: background-color .18s ease, border-color .18s ease, color .18s ease;
            }
            #czx_decryptor .czx_btn:hover {
                background: var(--czx-bg-hover, var(--czx-bg, #26265a));
                border-color: var(--czx-border-hover, var(--czx-border, transparent));
                color: var(--czx-color-hover, var(--czx-color, #ffffff));
                text-decoration: none;
            }
            #czx_decryptor .czx_btn svg {
                width: 14px;
                height: 14px;
                flex-shrink: 0;
            }
            #czx_decryptor .czx_icon_btn {
                width: var(--czx-height, 32px);
                padding: 0 !important;
            }
            #czx_decryptor .czx_row {
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 8px 10px;
                border: 1px solid #cbdfea !important;
                border-radius: 3px;
                background: #f7fbfd !important;
                min-width: 0;
                text-align: left;
                transition: background-color .18s ease, border-color .18s ease;
            }
            #czx_decryptor .czx_row:hover {
                background: rgba(39, 168, 224, .08) !important;
                border-color: #27a8e0 !important;
            }
            #czx_decryptor #czx_link_list {
                background: transparent !important;
                border-radius: 4px;
                padding: 6px !important;
            }
            #czx_decryptor .czx_link_text {
                flex: 1;
                min-width: 0;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                color: #183247 !important;
                font-size: 11px;
                text-align: left;
            }
            #czx_decryptor .czx_check {
                appearance: none;
                -webkit-appearance: none;
                display: grid;
                place-content: center;
                width: 18px;
                height: 18px;
                margin: 0;
                border: 1.5px solid #53b121;
                border-radius: 3px;
                background: #ffffff !important;
                cursor: pointer;
                flex-shrink: 0;
                transition: background-color .18s ease, border-color .18s ease, transform .12s ease;
            }
            #czx_decryptor .czx_check::before {
                content: '';
                width: 9px;
                height: 5px;
                border-left: 2px solid #ffffff;
                border-bottom: 2px solid #ffffff;
                opacity: 0;
                transform: translateY(-1px) rotate(-45deg) scale(.7);
                transition: opacity .12s ease, transform .12s ease;
            }
            #czx_decryptor .czx_check:checked {
                border-color: #76ce42 !important;
                background: #53b121 !important;
            }
            #czx_decryptor .czx_check:checked::before {
                opacity: 1;
                transform: translateY(-1px) rotate(-45deg) scale(1);
            }
            #czx_decryptor .czx_check:hover {
                border-color: #76ce42 !important;
                transform: scale(1.06);
            }
            #czx_decryptor #czx_header span {
                color: #183247 !important;
            }
            #czx_decryptor .czx_row > span:not(.czx_link_text) {
                color: #277da3 !important;
            }
            #czx_decryptor.czx_dark {
                background: rgba(5, 5, 5, .78) !important;
                border-color: #343434 !important;
                color: #ffffff !important;
                box-shadow: 0 8px 24px rgba(0, 0, 0, .28) !important;
            }
            #czx_decryptor.czx_dark .czx_row {
                background: rgba(12, 12, 12, .72) !important;
                border-color: #303030 !important;
            }
            #czx_decryptor.czx_dark .czx_row:hover {
                background: rgba(32, 32, 32, .82) !important;
                border-color: #505050 !important;
            }
            #czx_decryptor.czx_dark #czx_link_list {
                background: transparent !important;
            }
            #czx_decryptor.czx_dark .czx_link_text,
            #czx_decryptor.czx_dark #czx_header span {
                color: #ffffff !important;
            }
            #czx_decryptor.czx_dark .czx_row > span:not(.czx_link_text) {
                color: #c9c9c9 !important;
            }
            #czx_decryptor.czx_dark .czx_check:not(:checked) {
                background: #111111 !important;
            }
            #czx_decryptor.czx_dark #czx_link_list::-webkit-scrollbar-track {
                background: #101010;
            }
            #czx_decryptor.czx_dark #czx_link_list::-webkit-scrollbar-thumb {
                background: #555555;
            }
            #czx_decryptor.czx_dark .czx_check:focus-visible {
                outline-color: #777777;
            }
            #czx_decryptor .czx_check:focus-visible {
                outline: 1px solid #1fa1e1;
                outline-offset: 1px;
            }
            #czx_link_list::-webkit-scrollbar { width: 5px; }
            #czx_link_list::-webkit-scrollbar-track { background: #e5f0f5; border-radius: 4px; }
            #czx_link_list::-webkit-scrollbar-thumb { background: #1fa1e1; border-radius: 4px; }
        `;
        document.head.appendChild(style);
    };

    const waitFor = (selector, timeout = 10000) => new Promise((resolve, reject) => {
        const el = document.querySelector(selector);
        if (el) return resolve(el);
        const obs = new MutationObserver(() => {
            const found = document.querySelector(selector);
            if (found) { obs.disconnect(); resolve(found); }
        });
        obs.observe(document, { childList: true, subtree: true });
        setTimeout(() => { obs.disconnect(); reject('timeout'); }, timeout);
    });

    const findTableWidthSource = box => {
        const scope = box?.parentElement ?? document;
        const row = scope.querySelector('tbody tr.kwj3, tr.kwj3')
            || document.querySelector('tbody tr.kwj3, tr.kwj3');

        return row?.closest('table')
            || scope.querySelector('table')
            || document.querySelector('table');
    };

    const syncUIWidth = box => {
        const table = findTableWidthSource(box);
        const tableWidth = table?.getBoundingClientRect?.().width;

        if (tableWidth && tableWidth > 0) {
            const width = `${Math.round(tableWidth)}px`;
            box.style.left = '0';
            box.style.width = width;
            box.style.maxWidth = 'none';

            const tableLeft = table.getBoundingClientRect().left;
            const boxLeft = box.getBoundingClientRect().left;
            box.style.left = `${Math.round(tableLeft - boxLeft)}px`;
            return;
        }

        box.style.left = '0';
        box.style.width = '100%';
        box.style.maxWidth = '100%';
    };

    const backgroundIsDark = startElement => {
        let element = startElement;

        while (element) {
            const color = getComputedStyle(element).backgroundColor;
            const match = color.match(/rgba?\((\d+)[, ]+(\d+)[, ]+(\d+)(?:[, /]+([\d.]+))?\)/);

            if (match && (match[4] === undefined || Number(match[4]) > 0.05)) {
                const red = Number(match[1]);
                const green = Number(match[2]);
                const blue = Number(match[3]);
                const luminance = (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 255;
                return luminance < 0.5;
            }

            element = element.parentElement;
        }

        return null;
    };

    const downloadTextUsesDarkTheme = () => {
        const row = document.querySelector('tr.kwj3');
        const textElement = row?.querySelector('a, td, span') || row;
        if (!textElement) return null;

        const color = getComputedStyle(textElement).color;
        const match = color.match(/rgba?\((\d+)[, ]+(\d+)[, ]+(\d+)/);
        if (!match) return null;

        const red = Number(match[1]);
        const green = Number(match[2]);
        const blue = Number(match[3]);
        const luminance = (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 255;

        return luminance > 0.62;
    };

    const pageUsesDarkTheme = () => {
        const textTheme = downloadTextUsesDarkTheme();
        if (textTheme !== null) return textTheme;

        const downloadSurface = document.querySelector('tr.kwj3 td, tr.kwj3')
            || findTableWidthSource(document.body);
        const surfaceTheme = backgroundIsDark(downloadSurface);

        if (surfaceTheme !== null) return surfaceTheme;
        if (document.querySelector('link[href*="/simplyblack.css"]')) return true;

        const pageTheme = backgroundIsDark(document.body)
            ?? backgroundIsDark(document.documentElement);
        return pageTheme ?? window.matchMedia('(prefers-color-scheme: dark)').matches;
    };

    const setCompactState = box => {
        const header = box.querySelector('#czx_header');
        const actions = box.querySelector('#czx_actions');
        const listDiv = box.querySelector('#czx_link_list');

        box.style.padding = '12px 18px';
        if (header) header.style.display = 'none';
        if (actions) actions.style.display = 'none';

        listDiv.style.display = 'flex';
        listDiv.style.alignItems = 'center';
        listDiv.style.justifyContent = 'center';
        listDiv.style.flexDirection = 'column';
        listDiv.style.gap = '0';
        listDiv.style.maxHeight = 'none';
        listDiv.style.minHeight = '40px';
        listDiv.style.overflowY = 'visible';
        listDiv.style.paddingRight = '0';
        listDiv.style.textAlign = 'center';
    };

    const setExpandedState = box => {
        const header = box.querySelector('#czx_header');
        const actions = box.querySelector('#czx_actions');
        const listDiv = box.querySelector('#czx_link_list');

        box.style.padding = '20px';
        if (header) header.style.display = 'flex';
        if (actions) actions.style.display = 'flex';

        listDiv.style.display = 'flex';
        listDiv.style.flexDirection = 'column';
        listDiv.style.alignItems = 'stretch';
        listDiv.style.justifyContent = 'flex-start';
        listDiv.style.gap = '5px';
        listDiv.style.maxHeight = '360px';
        listDiv.style.minHeight = '0';
        listDiv.style.overflowY = 'auto';
        listDiv.style.paddingRight = '4px';
        listDiv.style.textAlign = 'left';
    };

    const buildUI = () => {
        const box = document.createElement('div');
        box.id = 'czx_decryptor';
        box.classList.toggle('czx_dark', pageUsesDarkTheme());
        box.style.cssText = `
            position: relative;
            background: #ffffff;
            border: 1px solid #9fcfe4;
            border-radius: 14px;
            padding: 12px 18px;
            margin: 18px auto;
            width: 100%;
            min-width: 0;
            max-width: 100%;
            font-family: inherit;
            color: #183247;
            box-shadow: 0 8px 24px rgba(30, 100, 130, .12);
            z-index: 999999;
        `;
        box.innerHTML = `
            <div id="czx_header" style="display:none;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap;">
                <div style="display:flex;align-items:center;min-width:0;">
                    <span style="font-size:15px;font-weight:700;color:#183247;letter-spacing:.3px;">Filecrypt Unlocker</span>
                </div>

                <div id="czx_actions" style="display:none;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;margin-left:auto;">
                    <button
                        id="czx_check_all"
                        class="czx_btn"
                        style="--czx-bg:#239edf;--czx-bg-hover:#2aaae8;--czx-border:#2baeea;--czx-border-hover:#66d8ff;--czx-color:#ffffff;--czx-color-hover:#ffffff;--czx-height:32px;--czx-padding:5px 14px;font-size:11px;">
                        Check All
                    </button>
                    <button
                        id="czx_uncheck_all"
                        class="czx_btn"
                        style="--czx-bg:#239edf;--czx-bg-hover:#2aaae8;--czx-border:#2baeea;--czx-border-hover:#66d8ff;--czx-color:#ffffff;--czx-color-hover:#ffffff;--czx-height:32px;--czx-padding:5px 14px;font-size:11px;">
                        Uncheck All
                    </button>
                    <button
                        id="czx_copy_all"
                        class="czx_btn"
                        style="--czx-bg:#52c928;--czx-bg-hover:#47b620;--czx-border:#45ad1e;--czx-border-hover:#3b9819;--czx-color:#ffffff;--czx-color-hover:#ffffff;--czx-height:32px;--czx-padding:5px 14px;font-size:11px;">
                        Copy All Links
                    </button>
                    <button
                        id="czx_open_selected"
                        class="czx_btn"
                        style="--czx-bg:#52c928;--czx-bg-hover:#47b620;--czx-border:#45ad1e;--czx-border-hover:#3b9819;--czx-color:#ffffff;--czx-color-hover:#ffffff;--czx-height:32px;--czx-padding:5px 14px;font-size:11px;">
                        Open Selected URLs
                    </button>
                </div>
            </div>

            <div id="czx_link_list" style="
                display:flex;align-items:center;justify-content:center;
                min-height:40px;padding:6px 0;text-align:center;">
                <div style="color:#527083;font-size:12px;">
                    Decrypting...
                </div>
            </div>
        `;
        return box;
    };

    const renderLinks = (box, links) => {
        const listDiv  = box.querySelector('#czx_link_list');
        const checkAllBtn = box.querySelector('#czx_check_all');
        const uncheckAllBtn = box.querySelector('#czx_uncheck_all');
        const openSelectedBtn = box.querySelector('#czx_open_selected');

        setExpandedState(box);
        listDiv.innerHTML = '';

        const updateOpenSelectedButton = () => {
            const checkedLinks = listDiv.querySelectorAll('.czx_check:checked').length;
            openSelectedBtn.disabled = checkedLinks === 0;
            openSelectedBtn.style.opacity = checkedLinks === 0 ? '0.55' : '1';
            openSelectedBtn.style.cursor = checkedLinks === 0 ? 'not-allowed' : 'pointer';
            const label = `Open Selected URL${checkedLinks !== 1 ? 's' : ''}${checkedLinks ? ` (${checkedLinks})` : ''}`;
            openSelectedBtn.textContent = label;
            openSelectedBtn.setAttribute('aria-label', label);
        };

        links.forEach((link, i) => {
            const domain = getDomain(link);
            const row = document.createElement('div');
            row.className = 'czx_row';
            row.style.fontSize = '12px';

            row.innerHTML = `
                <input type="checkbox" class="czx_check" data-link="${link}" checked aria-label="Select link ${i + 1}">
                <span style="color:#277da3;min-width:22px;font-size:11px;">${String(i+1).padStart(2,'0')}</span>
                ${domain
                    ? `<img src="${faviconUrl(domain)}" width="16" height="16"
                            style="border-radius:3px;flex-shrink:0;"
                            onerror="this.style.display='none'">`
                    : ``
                }
                <span class="czx_link_text">${link}</span>
                <button
                    data-link="${link}"
                    class="czx_btn czx_icon_btn czx_open_one"
                    aria-label="Open URL"
                    style="--czx-bg:#52c928;--czx-bg-hover:#47b620;--czx-border:#45ad1e;--czx-border-hover:#3b9819;--czx-color:#ffffff;--czx-color-hover:#ffffff;--czx-height:28px;--czx-padding:4px 12px;font-size:11px;">
                    ${OPEN_ICON}
                </button>
                <button
                    data-link="${link}"
                    class="czx_btn czx_icon_btn czx_copy_one"
                    aria-label="Copy URL"
                    style="--czx-bg:#239edf;--czx-bg-hover:#2aaae8;--czx-border:#2baeea;--czx-border-hover:#66d8ff;--czx-color:#ffffff;--czx-color-hover:#ffffff;--czx-height:28px;--czx-padding:4px 12px;font-size:11px;">
                    ${COPY_ICON}
                </button>
            `;

            listDiv.appendChild(row);
        });

        updateOpenSelectedButton();

        checkAllBtn.onclick = () => {
            listDiv.querySelectorAll('.czx_check').forEach(input => { input.checked = true; });
            updateOpenSelectedButton();
        };

        uncheckAllBtn.onclick = () => {
            listDiv.querySelectorAll('.czx_check').forEach(input => { input.checked = false; });
            updateOpenSelectedButton();
        };

        box.querySelector('#czx_copy_all').onclick = () => {
            navigator.clipboard.writeText(links.join('\n')).then(() => {
                const btn = box.querySelector('#czx_copy_all');
                btn.textContent = 'Copied!';
                btn.setAttribute('aria-label', 'Copied!');
                btn.style.setProperty('--czx-bg', '#5fca21');
                btn.style.setProperty('--czx-bg-hover', '#67d52a');
                btn.style.setProperty('--czx-border', '#63c729');
                btn.style.setProperty('--czx-border-hover', '#84ea43');
                btn.style.setProperty('--czx-color', '#ffffff');
                btn.style.setProperty('--czx-color-hover', '#ffffff');
                setTimeout(() => {
                    btn.textContent = 'Copy All Links';
                    btn.setAttribute('aria-label', 'Copy All Links');
                    btn.style.setProperty('--czx-bg', '#52c928');
                    btn.style.setProperty('--czx-bg-hover', '#47b620');
                    btn.style.setProperty('--czx-border', '#45ad1e');
                    btn.style.setProperty('--czx-border-hover', '#3b9819');
                    btn.style.setProperty('--czx-color', '#ffffff');
                    btn.style.setProperty('--czx-color-hover', '#ffffff');
                }, 500);
            });
        };

        openSelectedBtn.onclick = () => {
            const checkedLinks = Array.from(listDiv.querySelectorAll('.czx_check:checked')).map(input => input.dataset.link);
            if (!checkedLinks.length) return;
            checkedLinks.forEach((link, i) => setTimeout(() => window.open(link, '_blank'), i * 160));
        };

        listDiv.addEventListener('click', e => {
            const check = e.target.closest('.czx_check');
            if (check) {
                e.stopPropagation();
                updateOpenSelectedButton();
                return;
            }

            const openBtn = e.target.closest('.czx_open_one');
            if (openBtn) {
                window.open(openBtn.dataset.link, '_blank');
                return;
            }

            const btn = e.target.closest('.czx_copy_one');
            if (!btn) return;
            navigator.clipboard.writeText(btn.dataset.link).then(() => {
                const orig = btn.innerHTML;
                btn.innerHTML = SUCCESS_ICON;
                btn.setAttribute('aria-label', 'Copied');
                btn.style.setProperty('--czx-bg', '#5fca21');
                btn.style.setProperty('--czx-bg-hover', '#67d52a');
                btn.style.setProperty('--czx-border', '#63c729');
                btn.style.setProperty('--czx-border-hover', '#84ea43');
                btn.style.setProperty('--czx-color', '#ffffff');
                btn.style.setProperty('--czx-color-hover', '#ffffff');
                setTimeout(() => {
                    btn.innerHTML = orig;
                    btn.setAttribute('aria-label', 'Copy URL');
                    btn.style.setProperty('--czx-bg', '#239edf');
                    btn.style.setProperty('--czx-bg-hover', '#2aaae8');
                    btn.style.setProperty('--czx-border', '#2baeea');
                    btn.style.setProperty('--czx-border-hover', '#66d8ff');
                    btn.style.setProperty('--czx-color', '#ffffff');
                    btn.style.setProperty('--czx-color-hover', '#ffffff');
                }, 500);
            });
        });

        listDiv.addEventListener('change', e => {
            if (!e.target.matches('.czx_check')) return;
            updateOpenSelectedButton();
        });
    };

    const renderError = (box, msg) => {
        const listDiv = box.querySelector('#czx_link_list');
        setCompactState(box);
        listDiv.innerHTML = `
            <div style="color:#cc4444;font-size:12px;padding:6px 0;">
                ${msg}
            </div>`;
    };

    injectStyles();

    waitFor('.dlcdownload').then(async dlcButton => {
        const attrs = dlcButton.getAttributeNames();
        const id    = dlcButton.getAttribute(attrs[attrs.length - 1]);
        const dlcUrl = `${window.location.origin}/DLC/${id}.html`;

        const ui = buildUI();
        const btnsDiv = document.querySelector('.butt1ns');
        btnsDiv ? btnsDiv.insertAdjacentElement('afterend', ui) : document.body.prepend(ui);
        document.querySelector('.fiIItheadblockqueue4')?.remove();
        setCompactState(ui);
        syncUIWidth(ui);
        window.addEventListener('resize', () => syncUIWidth(ui), { passive: true });

        try {
            const dlcResponse = await fetch(dlcUrl, { credentials: 'include' });
            if (!dlcResponse.ok) throw new Error(`Filecrypt responded with ${dlcResponse.status}`);

            const dlcContent = await dlcResponse.text();
            if (!dlcContent.trim()) throw new Error('Empty DLC content returned by Filecrypt');

            const links = await decryptDlc(dlcContent);
            renderLinks(ui, links);
        } catch (err) {
            renderError(ui, err.message);
        }

    }).catch(() => {});

})();