torn-crack

Simple Cracking Helper

// ==UserScript==
// @name         torn-crack
// @namespace    torn-crack
// @version      0.0.8.1
// @description  Simple Cracking Helper
// @author       SirAua [3785905]
// @match        *://www.torn.com/page.php?sid=crimes*
// @grant        GM_xmlhttpRequest
// @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 SUPABASE_GET_WORDS_URL = "https://mthndavliqfbtaplgfau.supabase.co/functions/v1/get-words";
    const SUPABASE_ADD_WORD_URL  = "https://mthndavliqfbtaplgfau.supabase.co/functions/v1/add-word";

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

    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: 10px; 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);
        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 loadSupabaseWords() {
        return new Promise((resolve) => {
            if (!SUPABASE_GET_WORDS_URL) return resolve([]);
            GM_xmlhttpRequest({
                method: 'GET',
                url: SUPABASE_GET_WORDS_URL,
                timeout: 15000,
                onload: (res) => {
                    try {
                        if (res.status === 200) {
                            const arr = JSON.parse(res.responseText);
                            const up = arr.map(w => (typeof w === 'string' ? w.toUpperCase() : '')).filter(Boolean);
                            supabaseWords = new Set(up);
                            crackLog(`Loaded ${supabaseWords.size} words from Supabase`);
                            resolve(up);
                        } else {
                            crackLog('Supabase get-words returned:', res.status);
                            resolve([]);
                        }
                    } catch (e) {
                        crackLog('Error parsing supabase words', e);
                        resolve([]);
                    }
                },
                onerror: () => {
                    crackLog('Error fetching supabase words');
                    resolve([]);
                }
            });
        });
    }

    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…)');
        loadSupabaseWords()
            .then(mergeSupabaseIntoCache)
            .then((added) => {
            if (added && added > 0) {
                setStatus(`Ready (+${added} words)`);
            } else {
                setStatus('Ready');
            }
        })
            .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 worker = new Worker(URL.createObjectURL(new Blob([`
            self.onmessage = function(e) {
                const { dictChunk, pattern, max } = e.data;
                const regex = new RegExp('^' + pattern.replace(/[*]/g, '.') + '$');
                const out = [];
                for (const word of dictChunk) {
                    if (regex.test(word)) out.push(word);
                    if (out.length >= max) break;
                }
                self.postMessage(out);
            };
        `], { type: 'application/javascript' })));
        return new Promise((resolve) => {
            worker.onmessage = (e) => {
                worker.terminate();
                resolve([...new Set(e.data)]);
            };
            worker.postMessage({ dictChunk: dict[len], pattern: pat.toUpperCase(), max: MAX_SUG });
        });
    }

    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 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 charSlot of charSlots) {
                let char = charSlot.textContent.trim();
                patText += char ? char.toUpperCase() : '*';
            }

            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}`);
            }
            wordCountDiv.textContent = `Stored words per length → ${stats.join(' | ')}`;
        })();

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