torn-crack

Simple Cracking Helper

当前为 2025-09-07 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         torn-crack
// @namespace    torn-crack
// @version      0.0.8.7
// @description  Simple Cracking Helper
// @author       SirAua [3785905]
// @match        *://www.torn.com/page.php?sid=crimes*
// @grant        GM_xmlhttpRequest
// @connect      gitlab.com
// @connect      *.supabase.co
// @license      mit
// ==/UserScript==

// 🔴Clear the Wordlist Cache🔴

(function () {
    'use strict';

    if (window.CRACK_INJECTED) return;
    window.CRACK_INJECTED = true;

    const debug = true;
    const UPDATE_INTERVAL = 800;
    const MAX_SUG = 8;
    const MIN_LENGTH = 4;
    const MAX_LENGTH = 10;

    const wordlistUrl = 'https://gitlab.com/kalilinux/packages/seclists/-/raw/kali/master/Passwords/Common-Credentials/Pwdb_top-1000000.txt?ref_type=heads';

    const DOWNLOAD_MIN_DELTA = 20;

    const SUPABASE_COUNT_URL = "https://mthndavliqfbtaplgfau.supabase.co/functions/v1/get-words/count";
    const SUPABASE_WORDS_URL = "https://mthndavliqfbtaplgfau.supabase.co/functions/v1/get-words/words";

    const SUPABASE_ADD_WORD_URL = "https://mthndavliqfbtaplgfau.supabase.co/functions/v1/add-word";

    const DB_NAME = 'crack';
    const STORE_NAME = 'dictionary';

    const STATUS_PREF_KEY = 'crack_show_badge';
    function getBoolPref(key, def = true) {
        const v = localStorage.getItem(key);
        return v === null ? def : v === '1';
    }
    function setBoolPref(key, val) {
        localStorage.setItem(key, val ? '1' : '0');
    }

    let dict = [];
    let dictLoaded = false;
    let dictLoading = false;
    let supabaseWords = new Set();
    let statusEl = null;

    function crackLog(...args) {
        if (debug) console.log('[Crack]', ...args);
    }

    function ensureStatusBadge() {
        if (statusEl) return statusEl;
        statusEl = document.createElement('div');
        statusEl.id = '__crack_status';
        statusEl.style.cssText = `
              position: fixed; right: 10px; bottom: 40px; z-index: 10000;
              background:#000; color:#0f0; border:1px solid #0f0; border-radius:6px;
              padding:6px 8px; font-size:11px; font-family:monospace; opacity:0.9;
            `;
        statusEl.textContent = 'Dictionary: Idle';
        document.body.appendChild(statusEl);

        const show = getBoolPref(STATUS_PREF_KEY, true);
        statusEl.style.display = show ? 'block' : 'none';

        return statusEl;
    }
    function setStatus(msg) {
        ensureStatusBadge().textContent = `Dictionary: ${msg}`;
        crackLog('STATUS →', msg);
    }

    function openDB() {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(DB_NAME, 1);
            request.onupgradeneeded = () => {
                const db = request.result;
                if (!db.objectStoreNames.contains(STORE_NAME)) {
                    db.createObjectStore(STORE_NAME);
                }
            };
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }

    async function idbSet(key, value) {
        const db = await openDB();
        return new Promise((resolve, reject) => {
            const tx = db.transaction(STORE_NAME, 'readwrite');
            tx.objectStore(STORE_NAME).put(value, key);
            tx.oncomplete = resolve;
            tx.onerror = () => reject(tx.error);
        });
    }

    async function idbGet(key) {
        const db = await openDB();
        return new Promise((resolve, reject) => {
            const tx = db.transaction(STORE_NAME, 'readonly');
            const req = tx.objectStore(STORE_NAME).get(key);
            req.onsuccess = () => resolve(req.result);
            req.onerror = () => reject(req.error);
        });
    }

    async function idbClear() {
        const db = await openDB();
        return new Promise((resolve, reject) => {
            const tx = db.transaction(STORE_NAME, 'readwrite');
            tx.objectStore(STORE_NAME).clear();
            tx.oncomplete = resolve;
            tx.onerror = () => reject(tx.error);
        });
    }

    async function clearLocalDictCache() {
        await idbClear();
        crackLog('Cleared cached dictionary from IndexedDB');
        setStatus('Cleared cache — reload');
    }

    function getHeader(headers, name) {
        const re = new RegExp('^' + name + ':\\s*(.*)$', 'mi');
        const m = headers && headers.match ? headers.match(re) : null;
        return m ? m[1].trim() : null;
    }

    async function fetchRemoteMeta() {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: 'HEAD',
                url: SUPABASE_COUNT_URL,
                timeout: 10000,
                onload: async (res) => {
                    const count = Number(getHeader(res.responseHeaders, 'X-Total-Count') || 0);
                    const etag = getHeader(res.responseHeaders, 'ETag') || '';
                    await idbSet('sb_remote_count', count);
                    await idbSet('sb_remote_etag', etag);
                    resolve({ count, etag });
                },
                onerror: () => resolve({ count: 0, etag: '' }),
            });
        });
    }

    async function downloadCommunityWordlist(ifNoneMatchEtag) {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: SUPABASE_WORDS_URL,
                headers: ifNoneMatchEtag ? { 'If-None-Match': ifNoneMatchEtag } : {},
                timeout: 20000,
                onload: async (res) => {
                    const etag = getHeader(res.responseHeaders, 'ETag') || '';
                    const count = Number(getHeader(res.responseHeaders, 'X-Total-Count') || 0);
                    if (etag) await idbSet('sb_remote_etag', etag);
                    if (count) await idbSet('sb_remote_count', count);

                    if (res.status === 304) {
                        crackLog('Community word-list unchanged (304).');
                        resolve(0);
                        return;
                    }
                    if (res.status !== 200) {
                        crackLog('Download failed, status:', res.status);
                        resolve(0);
                        return;
                    }

                    const arr = JSON.parse(res.responseText);
                    const up = arr.map(w => (typeof w === 'string' ? w.toUpperCase() : '')).filter(Boolean);
                    supabaseWords = new Set(up);
                    const added = await mergeSupabaseIntoCache(up);

                    await idbSet('sb_last_downloaded_count', count || up.length);
                    resolve(added);
                },
                onerror: () => resolve(0),
            });
        });
    }

    async function checkRemoteAndMaybeDownload(force = false) {
        const { count: remoteCount, etag } = await fetchRemoteMeta();
        const lastDownloaded = (await idbGet('sb_last_downloaded_count')) || 0;
        const delta = Math.max(0, remoteCount - lastDownloaded);

        if (!force && delta < DOWNLOAD_MIN_DELTA) {
            crackLog(`Skip download: delta=${delta} < ${DOWNLOAD_MIN_DELTA}`);
            await idbSet('sb_pending_delta', delta);
            return 0;
        }

        setStatus(force ? 'Manual sync…' : `Syncing (+${delta})…`);
        const added = await downloadCommunityWordlist(etag);
        await idbSet('sb_pending_delta', 0);
        return added;
    }

    async function sendWordToSupabase(word) {
        if (!SUPABASE_ADD_WORD_URL) {
            crackLog('SUPABASE_ADD_WORD_URL not set. skipping send:', word);
            return;
        }
        const payload = { word: word.toUpperCase() };
        GM_xmlhttpRequest({
            method: 'POST',
            url: SUPABASE_ADD_WORD_URL,
            headers: { 'Content-Type': 'application/json' },
            data: JSON.stringify(payload),
            onload: (res) => {
                crackLog('sent to supabase', payload, 'status', res.status);
                supabaseWords.add(payload.word);
            },
            onerror: (err) => {
                crackLog('failed to send to supabase', err);
            }
        });
    }

    function fetchWordlistAndCache() {
        setStatus('Downloading base wordlist…');
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'get',
                url: wordlistUrl,
                timeout: 30000,
                ontimeout: (err) => reject(new Error(`Timeout: ${JSON.stringify(err)}`)),
                onerror: (err) => reject(new Error(`Request failed: ${JSON.stringify(err)}`)),
                onload: async (res) => {
                    setStatus('Indexing…');
                    const lines = res.responseText.split(/\r?\n/).map(w => (w || '').trim().toUpperCase());
                    dict = [];
                    for (const word of lines) {
                        if (!/^[A-Z0-9_.]+$/.test(word)) continue;
                        const len = word.length;
                        if (len < MIN_LENGTH || len > MAX_LENGTH) continue;
                        if (!dict[len]) dict[len] = [];
                        dict[len].push(word);
                    }
                    for (let len = MIN_LENGTH; len <= MAX_LENGTH; len++) {
                        if (dict[len]) {
                            await idbSet(`len_${len}`, dict[len]);
                        }
                    }
                    crackLog('Base dictionary downloaded and cached in IndexedDB');
                    resolve();
                },
            });
        });
    }

    async function mergeSupabaseIntoCache(words) {
        if (!words || !words.length) return 0;
        let added = 0;
        for (const w of words) {
            const len = w.length;
            if (len < MIN_LENGTH || len > MAX_LENGTH) continue;
            let chunk = await idbGet(`len_${len}`);
            if (!chunk) chunk = [];
            if (!chunk.includes(w)) {
                chunk.push(w);
                await idbSet(`len_${len}`, chunk);
                if (!dict[len]) dict[len] = [];
                if (!dict[len].includes(w)) dict[len].push(w);
                added++;
            }
        }
        return added;
    }

    async function loadDict() {
        if (dictLoaded || dictLoading) return;
        dictLoading = true;
        setStatus('Loading from cache…');

        let hasData = false;
        dict = [];
        for (let len = MIN_LENGTH; len <= MAX_LENGTH; len++) {
            const chunk = await idbGet(`len_${len}`);
            if (chunk && chunk.length) {
                dict[len] = chunk;
                hasData = true;
            }
        }

        if (!hasData) {
            crackLog('No cache found. Downloading dictionary…');
            try {
                await fetchWordlistAndCache();
            } catch (e) {
                crackLog('Failed to download base wordlist:', e);
            }
        } else {
            crackLog('Dictionary loaded from IndexedDB');
        }

        dictLoaded = true;
        dictLoading = false;
        setStatus('Ready');

        setStatus('Ready (database sync…)');
        checkRemoteAndMaybeDownload(false)
            .then(async (added) => {
            const remoteCount = await idbGet('sb_remote_count');
            const delta = await idbGet('sb_pending_delta');
            if (added && added > 0) {
                setStatus(`Ready (+${added})`);
            } else if (delta && delta > 0) {
                setStatus(`Ready (+${delta} pending)`);
            } else {
                setStatus(`Ready (${remoteCount ?? 'n/a'})`);
            }
        })
            .catch(() => setStatus('Ready'));
    }

    async function suggest(pat) {
        const len = pat.length;
        if (len < MIN_LENGTH || len > MAX_LENGTH) return [];
        if (!dict[len]) {
            const chunk = await idbGet(`len_${len}`);
            if (!chunk) return [];
            dict[len] = chunk;
        }

        const rxSource = pat.toUpperCase();
        const syncFilter = () => {
            const rx = toRegexFromPattern(rxSource);
            const out = [];
            for (const word of dict[len]) {
                if (rx.test(word)) { out.push(word); if (out.length >= MAX_SUG) break; }
            }
            return [...new Set(out)];
        };

        try {
            const blob = new Blob([`
      self.onmessage = e => {
        const { dictChunk, pattern, max } = e.data;
        const rx = new RegExp('^' + pattern
          .replace(/[-\\/^$+?.()|[\\]{}]/g, '\\\\$&')
          .replace(/\\*/g, '.') + '$');
        const out = [];
        for (const w of dictChunk) { if (rx.test(w)) { out.push(w); if (out.length >= max) break; } }
        self.postMessage(out);
      };
    `], { type: 'application/javascript' });

            const url = URL.createObjectURL(blob);
            const worker = new Worker(url);
            URL.revokeObjectURL(url);

            return await new Promise(resolve => {
                worker.onmessage = e => { worker.terminate(); resolve([...new Set(e.data)]); };
                worker.onerror = () => { worker.terminate(); resolve(syncFilter()); };
                worker.postMessage({ dictChunk: dict[len], pattern: rxSource, max: MAX_SUG });
            });
        } catch {
            return syncFilter();
        }
    }


    function prependPanelToRow(row, pat) {
        const existing = row.querySelector('.__crackhelp_panel');
        if (existing && existing.dataset.pattern === pat && existing.querySelector('span')) return;
        if (existing) existing.remove();

        const panel = document.createElement('div');
        panel.className = '__crackhelp_panel';
        panel.dataset.pattern = pat;
        panel.style.cssText = 'background: #000; font-size: 10px; text-align: center; position: absolute; z-index: 9999;';
        const listDiv = document.createElement('div');
        listDiv.style.cssText = 'margin-top: 2px;';
        panel.appendChild(listDiv);
        row.prepend(panel);

        async function updateSuggestions() {
            listDiv.innerHTML = '';
            if (dictLoading && !dictLoaded) {
                const sp = document.createElement('span');
                sp.style.cssText = 'padding:2px; color:#ff0;';
                sp.textContent = '(loading dictionary…)';
                listDiv.appendChild(sp);
                return;
            }
            const sugs = await suggest(pat);
            listDiv.innerHTML = '';
            if (sugs.length > 0) {
                sugs.forEach(word => {
                    const sp = document.createElement('span');
                    sp.style.cssText = 'padding:2px; color: #00ff00;';
                    sp.textContent = word;
                    listDiv.appendChild(sp);
                });
            } else {
                const none = document.createElement('span');
                none.textContent = dictLoaded ? '(no matches)' : '(loading dictionary…)';
                none.style.color = dictLoaded ? '#a00' : '#ff0';
                listDiv.appendChild(none);
            }
        }
        loadDict().then(updateSuggestions);
    }

    async function isWordInLocalDict(word) {
        const len = word.length;
        if (!dict[len]) {
            const chunk = await idbGet(`len_${len}`);
            if (!chunk) return false;
            dict[len] = chunk;
        }
        return dict[len].includes(word);
    }

    async function addWordToLocalCache(word) {
        const len = word.length;
        if (len < MIN_LENGTH || len > MAX_LENGTH) return;
        let chunk = await idbGet(`len_${len}`);
        if (!chunk) chunk = [];
        if (!chunk.includes(word)) {
            chunk.push(word);
            await idbSet(`len_${len}`, chunk);
            if (!dict[len]) dict[len] = [];
            if (!dict[len].includes(word)) dict[len].push(word);
            crackLog('Added to local cache:', word);
        }
    }

    function hasClassPrefix(el, prefix) {
        return Array.from(el.classList || []).some(c => c.startsWith(prefix));
    }

    function extractCharFromSlot(slot) {
        if (hasClassPrefix(slot, 'hasEncryption') || slot.querySelector('[class^="dot___"]')) {
            return '*';
        }

        const inp = slot.querySelector('input[type="text"]');
        if (inp) {
            const v = (inp.value || '').trim();
            if (/^[A-Za-z0-9_.]$/.test(v)) return v.toUpperCase();
            return '*';
        }

        const t = (slot.textContent || '').trim();
        if (t && /^[A-Za-z0-9_.]$/.test(t)) return t.toUpperCase();

        return '*';
    }

    function toRegexFromPattern(pat) {
        const esc = s => s.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&');
        return new RegExp('^' + esc(pat).replace(/\\\*/g, '.') + '$');
    }

    function scanCrimePage() {
        if (!location.href.endsWith('cracking')) return;

        const currentCrime = document.querySelector('[class^="currentCrime"]');
        if (!currentCrime) return;

        const container = currentCrime.querySelector('[class^="virtualList"]');
        if (!container) return;

        const crimeOptions = container.querySelectorAll('[class^="crimeOptionWrapper"]');
        crackLog('Scanning crime options:', crimeOptions.length);

        for (const crimeOption of crimeOptions) {
            let patText = '';
            const charSlots = crimeOption.querySelectorAll('[class^="charSlot"]:not([class*="charSlotDummy"])');
            for (const slot of charSlots) {
                patText += extractCharFromSlot(slot);
            }

            if (!/[*]/.test(patText)) {
                const newWord = patText.toUpperCase();
                if (!/^[A-Z0-9_.]+$/.test(newWord)) {
                    crackLog('Revealed word contains invalid chars. skippin:', newWord);
                } else {
                    (async () => {
                        const localHas = await isWordInLocalDict(newWord);
                        const supHas = supabaseWords.has(newWord);
                        if (!localHas && !supHas) {
                            crackLog('New fully cracked word sendin:', newWord);
                            await addWordToLocalCache(newWord);
                            sendWordToSupabase(newWord);
                        } else {
                            crackLog('Fully cracked but known not sendin: ', newWord);
                            if (supHas && !localHas) {
                                await addWordToLocalCache(newWord);
                            }
                        }
                    })();
                }
            }

            if (!/^[*]+$/.test(patText)) prependPanelToRow(crimeOption, patText);
        }
    }

    async function showMenuOverlay() {
        const overlay = document.createElement('div');
        overlay.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.7); color: #fff;
            display: flex; align-items: center; justify-content: center;
            z-index: 10000; font-size: 14px;
        `;
        const box = document.createElement('div');
        box.style.cssText = `
            background: #111; padding: 20px; border: 1px solid #0f0;
            border-radius: 6px; text-align: center; min-width: 320px;
        `;
        box.innerHTML = `<div style="margin-bottom: 12px; font-size: 20px; color: #0f0;">Settings</div>`;

        const statusLine = document.createElement('div');
        statusLine.style.cssText = 'color:#0f0; font-size:12px; margin-bottom:8px;';
        statusLine.textContent = ensureStatusBadge().textContent;
        box.appendChild(statusLine);

        const wordCountDiv = document.createElement('div');
        wordCountDiv.style.cssText = 'color: #0f0; font-size: 12px; margin-bottom: 10px;';
        wordCountDiv.textContent = 'Loading dictionary stats...';
        box.appendChild(wordCountDiv);

        (async () => {
            let stats = [];
            for (let len = MIN_LENGTH; len <= MAX_LENGTH; len++) {
                const chunk = await idbGet(`len_${len}`);
                stats.push(`${len}: ${chunk ? chunk.length : 0}`);
            }
            const remoteCount = await idbGet('sb_remote_count');
            const delta = await idbGet('sb_pending_delta');
            wordCountDiv.textContent =
                `Stored words per length → ${stats.join(' | ')}  |  Remote cracked words: ${remoteCount ?? 'n/a'}${delta ? ` ( +${delta} pending )` : ''}`;
        })();

        const badgeRow = document.createElement('div');
        badgeRow.style.cssText = 'margin: 8px 0; font-size:12px; color:#0f0; display:flex; align-items:center; justify-content:center; gap:8px;';
        const badgeLabel = document.createElement('label');
        badgeLabel.style.cssText = 'cursor:pointer; display:flex; align-items:center; gap:6px;';
        const badgeChk = document.createElement('input');
        badgeChk.type = 'checkbox';
        badgeChk.checked = getBoolPref(STATUS_PREF_KEY, true);
        badgeChk.onchange = () => {
            const show = badgeChk.checked;
            setBoolPref(STATUS_PREF_KEY, show);
            ensureStatusBadge().style.display = show ? 'block' : 'none';
        };
        const badgeText = document.createElement('span');
        badgeText.textContent = 'Show status badge';
        badgeLabel.appendChild(badgeChk);
        badgeLabel.appendChild(badgeText);
        badgeRow.appendChild(badgeLabel);
        box.appendChild(badgeRow);

        const syncBtn = document.createElement('button');
        syncBtn.textContent = 'Sync now (manual)';
        syncBtn.style.cssText = 'margin: 4px; padding: 4px 8px; background: #0a0; color: #fff; cursor: pointer;';
        syncBtn.onclick = async () => {
            syncBtn.disabled = true;
            const added = await checkRemoteAndMaybeDownload(true);
            const remoteCount = await idbGet('sb_remote_count');
            const delta = await idbGet('sb_pending_delta');
            setStatus(added ? `Ready (+${added}, remote: ${remoteCount})`
                : `Ready (remote ${remoteCount}${delta ? `, +${delta} pending` : ''})`);
            let stats = [];
            for (let len = MIN_LENGTH; len <= MAX_LENGTH; len++) {
                const chunk = await idbGet(`len_${len}`);
                stats.push(`${len}: ${chunk ? chunk.length : 0}`);
            }
            wordCountDiv.textContent = `Stored words per length → ${stats.join(' | ')}  |  Remote cracked words: ${remoteCount ?? 'n/a'}`;
            syncBtn.disabled = false;
        };
        box.appendChild(syncBtn);

        const btnCache = document.createElement('button');
        btnCache.textContent = 'Clear Wordlist Cache';
        btnCache.style.cssText = 'margin: 4px; padding: 4px 8px; background: #a00; color: #fff; cursor: pointer;';
        btnCache.onclick = async () => { await clearLocalDictCache(); location.reload(); };

        const cancelBtn = document.createElement('button');
        cancelBtn.textContent = 'Close';
        cancelBtn.style.cssText = 'margin: 4px; padding: 4px 8px; background: #222; color: #fff; cursor: pointer;';
        cancelBtn.onclick = () => { document.body.removeChild(overlay); };

        box.appendChild(btnCache);
        box.appendChild(cancelBtn);

        const line = document.createElement('hr');
        line.style.cssText = 'border: none; border-top: 1px solid #0f0; margin: 10px 0;';
        box.appendChild(line);

        const pwrdByMsg = document.createElement('div');
        pwrdByMsg.style.cssText = 'color: #0f0; font-size: 12px; margin-bottom: 10px;';
        pwrdByMsg.textContent = 'Powered by Supabase / IndexedDB - Made with Love ❤ by SirAua [3785905] (and friends)';
        box.appendChild(pwrdByMsg);

        const psMsg = document.createElement('div');
        psMsg.style.cssText = 'color: #0f0; font-size: 9px; margin-bottom: 10px;';
        psMsg.textContent = 'Ps: Clear the cache after updates';
        box.appendChild(psMsg);

        overlay.appendChild(box);
        document.body.appendChild(overlay);
    }

    function injectMenuButton() {
        if (!location.href.endsWith('cracking')) return;
        if (document.getElementById('__crack_menu_btn')) return;
        const appHeader = document.querySelector('[class^="appHeaderDelimiter"]');
        if (!appHeader) return;
        const btn = document.createElement('button');
        btn.id = '__crack_menu_btn';
        btn.textContent = 'Bruteforce characters to show suggestions! (Click this to open a menu)';
        btn.style.cssText = 'background: #000; color: #0f0; font-size: 10px; text-align: left; z-index: 9999; cursor: pointer;';
        btn.onclick = showMenuOverlay;
        appHeader.appendChild(btn);

        ensureStatusBadge();
    }

    (async function init() {
        ensureStatusBadge();
        setStatus('Initializing…');
        loadDict();
        scanCrimePage();
        setInterval(scanCrimePage, UPDATE_INTERVAL);
        setInterval(injectMenuButton, UPDATE_INTERVAL);
    })();
})();