Filecrypt Unlocker

Automatically decrypt links from a Filecrypt container.

スクリプトをインストールするには、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         Filecrypt Unlocker
// @description  Automatically decrypt links from a Filecrypt container.
// @icon         https://filecrypt.cc/favicon.ico
// @version      1.0
// @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://www.google.com/s2/favicons?sz=32&domain=${domain}`;

    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; }
            #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: 999px;
                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;
                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_row {
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 8px 10px;
                border: 1px solid #1d4e6c;
                border-radius: 16px;
                background: #0d1a28;
                min-width: 0;
                text-align: left;
                transition: background-color .18s ease, border-color .18s ease;
            }
            #czx_decryptor .czx_row:hover {
                background: #112235;
                border-color: #1fa1e1;
            }
            #czx_decryptor .czx_link_text {
                flex: 1;
                min-width: 0;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                color: #ffffff;
                font-size: 11px;
                text-align: left;
            }
            #czx_decryptor .czx_check {
                appearance: none;
                -webkit-appearance: none;
                width: 17px;
                height: 17px;
                margin: 0;
                border: 1.5px solid #53b121;
                border-radius: 999px;
                background: #0b1521;
                cursor: pointer;
                flex-shrink: 0;
                transition: background-color .18s ease, border-color .18s ease;
            }
            #czx_decryptor .czx_check:checked {
                border-color: #76ce42;
                background: #53b121;
            }
            #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: #09131a; 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.width = width;
            box.style.maxWidth = width;
            return;
        }

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

    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.style.cssText = `
            position: relative;
            background: #0b1624;
            border: 1px solid #256f98;
            border-radius: 14px;
            padding: 12px 18px;
            margin: 18px auto;
            width: 100%;
            min-width: 0;
            max-width: 100%;
            font-family: 'Segoe UI', system-ui, sans-serif;
            color: #ffffff;
            box-shadow: none;
            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:#ffffff;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:#0b1624;--czx-bg-hover:#239edf;--czx-border:#2baeea;--czx-border-hover:#66d8ff;--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:#0b1309;--czx-bg-hover:#5fca21;--czx-border:#63c729;--czx-border-hover:#84ea43;--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:#f2f4ff;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';
            openSelectedBtn.textContent = `Open Selected URL${checkedLinks !== 1 ? 's' : ''}${checkedLinks ? ` (${checkedLinks})` : ''}`;
        };

        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:#bfe9fb;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_open_one"
                    style="--czx-bg:#0b1309;--czx-bg-hover:#5fca21;--czx-border:#63c729;--czx-border-hover:#84ea43;--czx-color:#ffffff;--czx-color-hover:#ffffff;--czx-height:28px;--czx-padding:4px 12px;font-size:11px;">
                    Open
                </button>
                <button
                    data-link="${link}"
                    class="czx_btn czx_copy_one"
                    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
                </button>
            `;

            row.querySelector('.czx_link_text').title = link;

            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.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.style.setProperty('--czx-bg', '#0b1624');
                    btn.style.setProperty('--czx-bg-hover', '#239edf');
                    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');
                }, 2000);
            });
        };

        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.textContent;
                btn.textContent = '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 = orig;
                    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');
                }, 1500);
            });
        });

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

})();