Skip to content

Instantly share code, notes, and snippets.

@Rakise
Last active January 9, 2026 05:14
Show Gist options
  • Select an option

  • Save Rakise/517c617753bd0876086b59c66c447790 to your computer and use it in GitHub Desktop.

Select an option

Save Rakise/517c617753bd0876086b59c66c447790 to your computer and use it in GitHub Desktop.

Since the original creator aamiaa does not want new features or improvements added to their code, I created this gist with some quality-of-life improvements instead.

My version still fully respects the Discord API and does not bypass or abuse it.


Patch Notes

Patch 0.0

  • Added a simple and intuitive UI
  • Implemented quest queueing
  • Enabled multitasking between different quests

Patch 0.1

  • Added proper handling for HTTP 429 (rate limiting)
  • Added safeguards to prevent multitasking a lot when quests are of the same type

Patch 0.1.1

  • Added support for starting individual quests manually

Patch 0.1.2

  • Added multi-language support (i18n)
  • Changed cursor to blocked icon when buttons are disabled

How to use this script:

  1. Accept a quest under Discover -> Quests
  2. Press Ctrl+Shift+I to open DevTools
  3. Go to the Console tab
  4. Paste the following code and hit enter:
Click to expand
{
    // --- 0. Cleanup Previous Instances ---
    const existingUI = document.getElementById('autoquest-overlay');
    if (existingUI) existingUI.remove();
    const existingCanvas = document.getElementById('aq-confetti');
    if (existingCanvas) existingCanvas.remove();
    
    // --- 1. Discord Internals Setup ---
    delete window.$;
    let wpRequire = webpackChunkdiscord_app.push([[Symbol()], {}, r => r]);
    webpackChunkdiscord_app.pop();

    let ApplicationStreamingStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getStreamerActiveStreamMetadata).exports.Z;
    let RunningGameStore = Object.values(wpRequire.c).find(x => x?.exports?.ZP?.getRunningGames).exports.ZP;
    let QuestsStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getQuest).exports.Z;
    let ChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getAllThreadsForParent).exports.Z;
    let GuildChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.ZP?.getSFWDefaultChannel).exports.ZP;
    let FluxDispatcher = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.flushWaitQueue).exports.Z;
    let api = Object.values(wpRequire.c).find(x => x?.exports?.tn?.get).exports.tn;

    const isApp = typeof DiscordNative !== "undefined";
    const SLEEP_BUFFER = 5000; 

    const AllTranslations = {
        "en-US": {
            name: "English (US)",
            headerTitle: "AutoQuest",
            scanning: "Scanning quests...",
            noQuests: "No active quests found.<br>Make sure you accepted them!",
            startBtnReady: "Start All Quests",
            startBtnRunning: "Tasks Running...",
            startBtnQueued: "Tasks Queued...",
            startBtnDone: "All Quests Completed",
            statusReady: "Ready to start",
            statusQueued: "Queued...",
            statusWatching: "Watching...",
            statusActivity: "Active...",
            statusInActivity: "In Activity...",
            statusGame: "Playing Game...",
            statusStream: "Streaming...",
            statusRetrying: "Retrying...",
            statusCompleted: "Completed!",
            statusError: "Error Occurred",
            statusDone: "Done",
            errorVoice: "Error: No Voice Channel found",
            errorDesktop: "Error: Desktop App Required",
            refreshTooltip: "Refresh Quests",
            minimizeTooltip: "Minimize",
            closeTooltip: "Close",
            playTooltip: "Start This Quest",
            langTooltip: "Change Language",
            creditsTooltip: "Credits & Patch Notes"
        },
        "en-GB": {
            name: "English (UK)",
            headerTitle: "AutoQuest",
            scanning: "Scanning quests...",
            noQuests: "No active quests found.<br>Ensure you have accepted them!",
            startBtnReady: "Start All Quests",
            startBtnRunning: "Tasks Running...",
            startBtnQueued: "Tasks Queued...",
            startBtnDone: "All Quests Completed",
            statusReady: "Ready to start",
            statusQueued: "Queued...",
            statusWatching: "Watching...",
            statusActivity: "Active...",
            statusInActivity: "In Activity...",
            statusGame: "Playing Game...",
            statusStream: "Streaming...",
            statusRetrying: "Retrying...",
            statusCompleted: "Completed!",
            statusError: "Error Occurred",
            statusDone: "Done",
            errorVoice: "Error: No Voice Channel found",
            errorDesktop: "Error: Desktop App Required",
            refreshTooltip: "Refresh Quests",
            minimizeTooltip: "Minimize",
            closeTooltip: "Close",
            playTooltip: "Start This Quest",
            langTooltip: "Change Language",
            creditsTooltip: "Credits & Patch Notes"
        },
        "pt-BR": {
            name: "Português (Brasil)",
            headerTitle: "AutoQuest",
            scanning: "Escaneando missões...",
            noQuests: "Nenhuma missão ativa encontrada.<br>Certifique-se de tê-las aceito!",
            startBtnReady: "Iniciar Todas",
            startBtnRunning: "Tarefas em Andamento...",
            startBtnQueued: "Tarefas na Fila...",
            startBtnDone: "Todas Missões Completas",
            statusReady: "Pronto para iniciar",
            statusQueued: "Na fila...",
            statusWatching: "Assistindo...",
            statusActivity: "Ativo...",
            statusInActivity: "Em Atividade...",
            statusGame: "Jogando...",
            statusStream: "Transmitindo...",
            statusRetrying: "Tentando novamente...",
            statusCompleted: "Concluído!",
            statusError: "Ocorreu um Erro",
            statusDone: "Feito",
            errorVoice: "Erro: Nenhum Canal de Voz encontrado",
            errorDesktop: "Erro: App Desktop Necessário",
            refreshTooltip: "Atualizar Missões",
            minimizeTooltip: "Minimizar",
            closeTooltip: "Fechar",
            playTooltip: "Iniciar Esta Missão",
            langTooltip: "Mudar Idioma",
            creditsTooltip: "Créditos e Notas"
        },
        "es-ES": {
            name: "Español",
            headerTitle: "AutoQuest",
            scanning: "Escaneando misiones...",
            noQuests: "No se encontraron misiones activas.<br>¡Asegúrate de haberlas aceptado!",
            startBtnReady: "Iniciar Todas",
            startBtnRunning: "Tareas en curso...",
            startBtnQueued: "Tareas en cola...",
            startBtnDone: "Todas las misiones completadas",
            statusReady: "Listo para iniciar",
            statusQueued: "En cola...",
            statusWatching: "Viendo...",
            statusActivity: "Activo...",
            statusInActivity: "En actividad...",
            statusGame: "Jugando...",
            statusStream: "Transmitiendo...",
            statusRetrying: "Reintentando...",
            statusCompleted: "¡Completado!",
            statusError: "Ocurrió un error",
            statusDone: "Hecho",
            errorVoice: "Error: No se encontró canal de voz",
            errorDesktop: "Error: Se requiere App de Escritorio",
            refreshTooltip: "Actualizar Misiones",
            minimizeTooltip: "Minimizar",
            closeTooltip: "Cerrar",
            playTooltip: "Iniciar Esta Misión",
            langTooltip: "Cambiar Idioma",
            creditsTooltip: "Créditos y Notas"
        },
        "de-DE": {
            name: "Deutsch",
            headerTitle: "AutoQuest",
            scanning: "Scanne Quests...",
            noQuests: "Keine aktiven Quests gefunden.<br>Stelle sicher, dass du sie angenommen hast!",
            startBtnReady: "Alle starten",
            startBtnRunning: "Aufgaben laufen...",
            startBtnQueued: "Aufgaben in Warteschlange...",
            startBtnDone: "Alle Quests abgeschlossen",
            statusReady: "Bereit zum Starten",
            statusQueued: "In Warteschlange...",
            statusWatching: "Schaue zu...",
            statusActivity: "Aktiv...",
            statusInActivity: "In Aktivität...",
            statusGame: "Spiele...",
            statusStream: "Streame...",
            statusRetrying: "Wiederhole...",
            statusCompleted: "Abgeschlossen!",
            statusError: "Fehler aufgetreten",
            statusDone: "Fertig",
            errorVoice: "Fehler: Kein Sprachkanal gefunden",
            errorDesktop: "Fehler: Desktop-App erforderlich",
            refreshTooltip: "Quests aktualisieren",
            minimizeTooltip: "Minimieren",
            closeTooltip: "Schließen",
            playTooltip: "Diese Quest starten",
            langTooltip: "Sprache ändern",
            creditsTooltip: "Credits & Hinweise"
        },
        "fr-FR": {
            name: "Français",
            headerTitle: "AutoQuest",
            scanning: "Recherche de quêtes...",
            noQuests: "Aucune quête active trouvée.<br>Assurez-vous de les avoir acceptées !",
            startBtnReady: "Tout Démarrer",
            startBtnRunning: "Tâches en cours...",
            startBtnQueued: "Tâches en file d'attente...",
            startBtnDone: "Toutes les quêtes terminées",
            statusReady: "Prêt à démarrer",
            statusQueued: "En file d'attente...",
            statusWatching: "Regarde...",
            statusActivity: "Actif...",
            statusInActivity: "En activité...",
            statusGame: "Joue...",
            statusStream: "Stream...",
            statusRetrying: "Nouvelle tentative...",
            statusCompleted: "Terminé !",
            statusError: "Erreur survenue",
            statusDone: "Fait",
            errorVoice: "Erreur : Aucun salon vocal trouvé",
            errorDesktop: "Erreur : Application de bureau requise",
            refreshTooltip: "Actualiser les quêtes",
            minimizeTooltip: "Réduire",
            closeTooltip: "Fermer",
            playTooltip: "Démarrer cette quête",
            langTooltip: "Changer de langue",
            creditsTooltip: "Crédits et Notes"
        },
        "it-IT": {
            name: "Italiano",
            headerTitle: "AutoQuest",
            scanning: "Scansione missioni...",
            noQuests: "Nessuna missione attiva trovata.<br>Assicurati di averle accettate!",
            startBtnReady: "Avvia Tutto",
            startBtnRunning: "Compiti in corso...",
            startBtnQueued: "Compiti in coda...",
            startBtnDone: "Tutte le missioni completate",
            statusReady: "Pronto per iniziare",
            statusQueued: "In coda...",
            statusWatching: "Guardando...",
            statusActivity: "Attivo...",
            statusInActivity: "In attività...",
            statusGame: "Giocando...",
            statusStream: "In streaming...",
            statusRetrying: "Riprovo...",
            statusCompleted: "Completato!",
            statusError: "Si è verificato un errore",
            statusDone: "Fatto",
            errorVoice: "Errore: Nessun canale vocale trovato",
            errorDesktop: "Errore: App Desktop richiesta",
            refreshTooltip: "Aggiorna missioni",
            minimizeTooltip: "Minimizza",
            closeTooltip: "Chiudi",
            playTooltip: "Avvia questa missione",
            langTooltip: "Cambia lingua",
            creditsTooltip: "Crediti e Note"
        },
        "zh-CN": {
            name: "简体中文",
            headerTitle: "AutoQuest",
            scanning: "正在扫描任务...",
            noQuests: "未找到活动任务。<br>请确保您已接受它们!",
            startBtnReady: "全部开始",
            startBtnRunning: "任务运行中...",
            startBtnQueued: "任务排队中...",
            startBtnDone: "所有任务已完成",
            statusReady: "准备开始",
            statusQueued: "排队中...",
            statusWatching: "观看中...",
            statusActivity: "活跃中...",
            statusInActivity: "活动进行中...",
            statusGame: "正在游戏...",
            statusStream: "正在直播...",
            statusRetrying: "正在重试...",
            statusCompleted: "已完成!",
            statusError: "发生错误",
            statusDone: "完成",
            errorVoice: "错误:未找到语音频道",
            errorDesktop: "错误:需要桌面应用程序",
            refreshTooltip: "刷新任务",
            minimizeTooltip: "最小化",
            closeTooltip: "关闭",
            playTooltip: "开始此任务",
            langTooltip: "更改语言",
            creditsTooltip: "致谢与更新"
        },
        "zh-TW": {
            name: "繁體中文",
            headerTitle: "AutoQuest",
            scanning: "正在掃描任務...",
            noQuests: "未找到活動任務。<br>請確保您已接受它們!",
            startBtnReady: "全部開始",
            startBtnRunning: "任務執行中...",
            startBtnQueued: "任務排隊中...",
            startBtnDone: "所有任務已完成",
            statusReady: "準備開始",
            statusQueued: "排隊中...",
            statusWatching: "觀看中...",
            statusActivity: "活躍中...",
            statusInActivity: "活動進行中...",
            statusGame: "正在遊戲...",
            statusStream: "正在直播...",
            statusRetrying: "正在重試...",
            statusCompleted: "已完成!",
            statusError: "發生錯誤",
            statusDone: "完成",
            errorVoice: "錯誤:未找到語音頻道",
            errorDesktop: "錯誤:需要桌面應用程序",
            refreshTooltip: "刷新任務",
            minimizeTooltip: "最小化",
            closeTooltip: "關閉",
            playTooltip: "開始此任務",
            langTooltip: "更改語言",
            creditsTooltip: "致謝與更新"
        },
        "ja-JP": {
            name: "日本語",
            headerTitle: "AutoQuest",
            scanning: "クエストをスキャン中...",
            noQuests: "アクティブなクエストが見つかりません。<br>承認していることを確認してください!",
            startBtnReady: "すべて開始",
            startBtnRunning: "タスク実行中...",
            startBtnQueued: "タスク待機中...",
            startBtnDone: "全クエスト完了",
            statusReady: "開始準備完了",
            statusQueued: "待機中...",
            statusWatching: "視聴中...",
            statusActivity: "アクティブ...",
            statusInActivity: "アクティビティ中...",
            statusGame: "ゲーム中...",
            statusStream: "配信中...",
            statusRetrying: "再試行中...",
            statusCompleted: "完了!",
            statusError: "エラー発生",
            statusDone: "終了",
            errorVoice: "エラー:ボイスチャンネルが見つかりません",
            errorDesktop: "エラー:デスクトップアプリが必要です",
            refreshTooltip: "クエストを更新",
            minimizeTooltip: "最小化",
            closeTooltip: "閉じる",
            playTooltip: "このクエストを開始",
            langTooltip: "言語を変更",
            creditsTooltip: "クレジットとメモ"
        },
        "ko-KR": {
            name: "한국어",
            headerTitle: "AutoQuest",
            scanning: "퀘스트 스캔 중...",
            noQuests: "진행 중인 퀘스트가 없습니다.<br>수락했는지 확인하세요!",
            startBtnReady: "모두 시작",
            startBtnRunning: "작업 진행 중...",
            startBtnQueued: "대기열에 추가됨...",
            startBtnDone: "모든 퀘스트 완료",
            statusReady: "시작 준비 완료",
            statusQueued: "대기 중...",
            statusWatching: "시청 중...",
            statusActivity: "활동 중...",
            statusInActivity: "활동 수행 중...",
            statusGame: "게임 중...",
            statusStream: "방송 중...",
            statusRetrying: "재시도 중...",
            statusCompleted: "완료!",
            statusError: "오류 발생",
            statusDone: "완료",
            errorVoice: "오류: 음성 채널을 찾을 수 없음",
            errorDesktop: "오류: 데스크톱 앱 필요",
            refreshTooltip: "퀘스트 새로 고침",
            minimizeTooltip: "최소화",
            closeTooltip: "닫기",
            playTooltip: "이 퀘스트 시작",
            langTooltip: "언어 변경",
            creditsTooltip: "크레딧 및 노트"
        },
        "ru-RU": {
            name: "Русский",
            headerTitle: "AutoQuest",
            scanning: "Сканирование квестов...",
            noQuests: "Активные квесты не найдены.<br>Убедитесь, что вы их приняли!",
            startBtnReady: "Запустить все",
            startBtnRunning: "Выполнение задач...",
            startBtnQueued: "В очереди...",
            startBtnDone: "Все квесты завершены",
            statusReady: "Готово к запуску",
            statusQueued: "В очереди...",
            statusWatching: "Просмотр...",
            statusActivity: "Активность...",
            statusInActivity: "В активности...",
            statusGame: "В игре...",
            statusStream: "Стриминг...",
            statusRetrying: "Повторная попытка...",
            statusCompleted: "Завершено!",
            statusError: "Ошибка",
            statusDone: "Готово",
            errorVoice: "Ошибка: Голосовой канал не найден",
            errorDesktop: "Ошибка: Требуется приложение для ПК",
            refreshTooltip: "Обновить квесты",
            minimizeTooltip: "Свернуть",
            closeTooltip: "Закрыть",
            playTooltip: "Запустить этот квест",
            langTooltip: "Изменить язык",
            creditsTooltip: "Кредиты и заметки"
        },
        "tr-TR": {
            name: "Türkçe",
            headerTitle: "AutoQuest",
            scanning: "Görevler taranıyor...",
            noQuests: "Aktif görev bulunamadı.<br>Kabul ettiğinizden emin olun!",
            startBtnReady: "Hepsini Başlat",
            startBtnRunning: "Görevler Çalışıyor...",
            startBtnQueued: "Sırada...",
            startBtnDone: "Tüm Görevler Tamamlandı",
            statusReady: "Başlamaya Hazır",
            statusQueued: "Sırada...",
            statusWatching: "İzleniyor...",
            statusActivity: "Aktif...",
            statusInActivity: "Etkinlikte...",
            statusGame: "Oynanıyor...",
            statusStream: "Yayınlanıyor...",
            statusRetrying: "Tekrar deneniyor...",
            statusCompleted: "Tamamlandı!",
            statusError: "Hata Oluştu",
            statusDone: "Bitti",
            errorVoice: "Hata: Ses Kanalı bulunamadı",
            errorDesktop: "Hata: Masaüstü Uygulaması Gerekli",
            refreshTooltip: "Görevleri Yenile",
            minimizeTooltip: "Küçült",
            closeTooltip: "Kapat",
            playTooltip: "Bu Görevi Başlat",
            langTooltip: "Dili Değiştir",
            creditsTooltip: "Krediler ve Notlar"
        },
        "pl-PL": {
            name: "Polski",
            headerTitle: "AutoQuest",
            scanning: "Skanowanie zadań...",
            noQuests: "Nie znaleziono aktywnych zadań.<br>Upewnij się, że je zaakceptowałeś!",
            startBtnReady: "Rozpocznij wszystkie",
            startBtnRunning: "Zadania w toku...",
            startBtnQueued: "Zadania w kolejce...",
            startBtnDone: "Wszystkie zadania ukończone",
            statusReady: "Gotowy do startu",
            statusQueued: "W kolejce...",
            statusWatching: "Oglądanie...",
            statusActivity: "Aktywny...",
            statusInActivity: "W trakcie aktywności...",
            statusGame: "W grze...",
            statusStream: "Streamowanie...",
            statusRetrying: "Ponawianie...",
            statusCompleted: "Ukończono!",
            statusError: "Wystąpił błąd",
            statusDone: "Gotowe",
            errorVoice: "Błąd: Nie znaleziono kanału głosowego",
            errorDesktop: "Błąd: Wymagana aplikacja desktopowa",
            refreshTooltip: "Odśwież zadania",
            minimizeTooltip: "Zminimalizuj",
            closeTooltip: "Zamknij",
            playTooltip: "Rozpocznij to zadanie",
            langTooltip: "Zmień język",
            creditsTooltip: "Twórcy i Notatki"
        }
    };

    let Strings = AllTranslations["en-US"]; // Default

    // --- 2. VFX: Confetti Engine ---
    const fireConfetti = (duration = 2000, amount = 100) => {
        const canvas = document.createElement('canvas');
        canvas.id = 'aq-confetti';
        canvas.style.position = 'fixed';
        canvas.style.top = '0';
        canvas.style.left = '0';
        canvas.style.width = '100vw';
        canvas.style.height = '100vh';
        canvas.style.pointerEvents = 'none';
        canvas.style.zIndex = '100000';
        document.body.appendChild(canvas);

        const ctx = canvas.getContext('2d');
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        const particles = [];
        const colors = ['#5865F2', '#57F287', '#EB459E', '#FEE75C', '#ffffff'];

        for (let i = 0; i < amount; i++) {
            particles.push({
                x: Math.random() * canvas.width,
                y: Math.random() * canvas.height - canvas.height,
                vx: Math.random() * 4 - 2,
                vy: Math.random() * 4 + 2,
                color: colors[Math.floor(Math.random() * colors.length)],
                size: Math.random() * 8 + 4,
                rotation: Math.random() * 360
            });
        }

        let start = null;
        const animate = (timestamp) => {
            if (!start) start = timestamp;
            const progress = timestamp - start;
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            particles.forEach(p => {
                p.x += p.vx;
                p.y += p.vy;
                p.rotation += 2;
                
                ctx.save();
                ctx.translate(p.x, p.y);
                ctx.rotate(p.rotation * Math.PI / 180);
                ctx.fillStyle = p.color;
                ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
                ctx.restore();

                if (p.y > canvas.height) p.y = -20;
            });

            if (progress < duration) {
                requestAnimationFrame(animate);
            } else {
                canvas.remove();
            }
        };
        requestAnimationFrame(animate);
    };

    // --- 3. UI Builder & State Management ---
    
    const colors = {
        bg: '#2b2d31', 
        header: '#1e1f22',
        primary: '#5865F2',
        success: '#23a559',
        text: '#dbdee1',
        subtext: '#949ba4',
        barBg: '#3f4147'
    };

    const styles = `
        #autoquest-overlay {
            position: fixed;
            top: 100px;
            left: 100px;
            width: 400px;
            height: auto;
            min-width: 300px;
            min-height: 100px;
            max-height: 80vh;
            background: rgba(43, 45, 49, 0.98);
            border-radius: 12px;
            box-shadow: 0 12px 32px rgba(0,0,0,0.4);
            font-family: 'gg sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
            color: ${colors.text};
            z-index: 99999;
            overflow: hidden;
            display: flex;
            flex-direction: column;
            border: 1px solid rgba(255,255,255,0.08);
            backdrop-filter: blur(12px);
            transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1), 
                        height 0.3s cubic-bezier(0.4, 0, 0.2, 1),
                        border-radius 0.3s,
                        opacity 0.2s,
                        transform 0.2s;
        }
        
        /* Minimized State */
        #autoquest-overlay.minimized {
            width: 60px !important;
            height: 60px !important;
            min-width: 0 !important; 
            min-height: 0 !important;
            border-radius: 50% !important;
            cursor: pointer;
            overflow: hidden;
            background: ${colors.primary};
            box-shadow: 0 4px 12px rgba(88, 101, 242, 0.4);
            border: 2px solid rgba(255,255,255,0.2);
        }
        #autoquest-overlay.minimized #autoquest-header,
        #autoquest-overlay.minimized #autoquest-content,
        #autoquest-overlay.minimized #autoquest-footer,
        #autoquest-overlay.minimized .aq-resize-handle,
        #autoquest-overlay.minimized #aq-credits-modal {
            display: none;
        }
        #autoquest-overlay.minimized::after {
            content: "AQ";
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-weight: 900;
            font-size: 20px;
            pointer-events: none;
        }
        #autoquest-overlay.minimized:hover {
            transform: scale(1.1);
        }

        #autoquest-header {
            background: rgba(30, 31, 34, 0.8);
            padding: 14px 16px;
            font-weight: 700;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-bottom: 1px solid rgba(255,255,255,0.05);
            cursor: default;
            user-select: none;
            flex-shrink: 0;
        }
        .header-controls { display: flex; gap: 8px; position: relative; }
        
        /* Language Dropdown */
        .aq-dropdown {
            position: absolute;
            top: 100%;
            right: 0;
            margin-top: 5px;
            background: #1e1f22;
            border: 1px solid rgba(255,255,255,0.1);
            border-radius: 4px;
            padding: 5px;
            z-index: 100000;
            display: none;
            max-height: 200px;
            width: 150px;
            overflow-y: auto;
            box-shadow: 0 4px 12px rgba(0,0,0,0.5);
        }
        .aq-dropdown.visible { display: block; }
        .aq-lang-item {
            padding: 6px 12px;
            cursor: pointer;
            color: #dbdee1;
            font-size: 13px;
            border-radius: 3px;
            text-align: left;
        }
        .aq-lang-item:hover { background: #5865F2; color: #fff; }
        .aq-lang-item.active { background: rgba(88, 101, 242, 0.3); color: #fff; font-weight: bold; }

        #autoquest-content {
            padding: 16px;
            flex: 1;
            overflow-y: auto;
            min-height: 0; 
        }
        #autoquest-content::-webkit-scrollbar { width: 6px; }
        #autoquest-content::-webkit-scrollbar-track { background: transparent; }
        #autoquest-content::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.4); border-radius: 4px; }
        
        .quest-card {
            position: relative;
            background: rgba(30, 31, 34, 0.4);
            border-radius: 8px;
            padding: 16px;
            margin-bottom: 10px;
            border: 1px solid rgba(255,255,255,0.05);
            transition: transform 0.2s, border-color 0.2s;
            display: flex;
            gap: 12px;
            align-items: center;
            overflow: hidden;
        }
        .quest-card:hover {
            border-color: rgba(255,255,255,0.2);
            background: rgba(40, 42, 45, 0.6);
            transform: translateY(-2px);
        }
        
        .quest-info {
            flex: 1;
            min-width: 0; 
            z-index: 1;
        }
        .quest-actions {
            z-index: 2;
            flex-shrink: 0;
        }
        .quest-title {
            font-weight: 700;
            font-size: 15px;
            margin-bottom: 8px;
            color: #fff;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .quest-meta {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
        }
        .quest-type {
            font-size: 10px;
            color: #fff;
            text-transform: uppercase;
            font-weight: 800;
            letter-spacing: 0.5px;
            background: rgba(255,255,255,0.1);
            padding: 4px 8px;
            border-radius: 4px;
        }
        .quest-timer {
            font-size: 12px;
            font-family: 'Consolas', 'Monaco', monospace;
            color: ${colors.text};
            font-variant-numeric: tabular-nums;
            background: rgba(0,0,0,0.4);
            padding: 2px 6px;
            border-radius: 4px;
        }
        .progress-track {
            height: 8px;
            background: rgba(0,0,0,0.4);
            border-radius: 4px;
            overflow: hidden;
            margin-top: 8px;
        }
        .progress-fill {
            height: 100%;
            background: ${colors.primary};
            width: 0%;
            transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
            position: relative;
            overflow: hidden;
        }
        .progress-fill::after {
            content: '';
            position: absolute;
            top: 0; left: 0; bottom: 0; right: 0;
            background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
            transform: translateX(-100%);
            animation: shimmer 2s infinite;
        }
        @keyframes shimmer { 100% { transform: translateX(100%); } }

        .status-text {
            font-size: 12px;
            color: #efefef;
            margin-top: 6px;
            display: flex;
            justify-content: space-between;
            font-weight: 500;
        }
        #autoquest-footer {
            padding: 16px;
            background: rgba(30, 31, 34, 0.8);
            display: flex;
            gap: 10px;
            border-top: 1px solid rgba(255,255,255,0.05);
            flex-shrink: 0;
        }
        .aq-btn {
            flex: 1;
            padding: 10px;
            border: none;
            border-radius: 6px;
            color: #fff;
            font-weight: 600;
            font-size: 13px;
            cursor: pointer;
            transition: all 0.2s;
            position: relative;
            overflow: hidden;
        }
        .aq-btn:active { transform: scale(0.97); }
        .aq-btn:hover { opacity: 0.9; }
        .aq-btn-start { 
            background: linear-gradient(90deg, ${colors.primary}, #4752C4); 
            box-shadow: 0 4px 12px rgba(88, 101, 242, 0.3);
        }
        .aq-btn-icon {
            width: 28px; 
            height: 28px; 
            padding: 0; 
            display: flex; 
            align-items: center; 
            justify-content: center; 
            border-radius: 6px;
            cursor: pointer;
            flex: 0 0 auto;
            background: rgba(255,255,255,0.1);
            color: #dbdee1;
            border: none;
            font-family: monospace;
            font-size: 16px;
        }
        .aq-btn-icon:hover { background: rgba(255,255,255,0.2); color: #fff; }
        .aq-btn-close:hover { background: #da373c; }
        .aq-btn-play { 
            background: rgba(88, 101, 242, 0.2); 
            color: #5865F2; 
            width: 32px; 
            height: 32px; 
            border: 1px solid rgba(88, 101, 242, 0.3);
        }
        .aq-btn-play:hover:not(:disabled) { 
            background: #5865F2; 
            color: white; 
            transform: scale(1.05);
        }
        .aq-btn-play:disabled {
            opacity: 0.3;
            cursor: not-allowed;
            filter: grayscale(1);
        }
        
        /* Credits Modal */
        #aq-credits-modal {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(43, 45, 49, 0.98);
            z-index: 2000;
            padding: 24px;
            display: none;
            flex-direction: column;
            backdrop-filter: blur(16px);
            overflow-y: auto;
        }
        #aq-credits-modal.visible { display: flex; }
        .credits-section { margin-bottom: 20px; }
        .credits-header { font-size: 18px; font-weight: bold; margin-bottom: 12px; color: #fff; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 8px; }
        .credits-text { font-size: 14px; color: #dbdee1; line-height: 1.6; }
        .credits-text strong { color: #fff; }
        
        details {
            background: rgba(0,0,0,0.2);
            border-radius: 6px;
            margin-bottom: 8px;
            border: 1px solid rgba(255,255,255,0.05);
        }
        summary {
            padding: 10px;
            cursor: pointer;
            font-weight: 600;
            color: #fff;
            list-style: none;
        }
        summary::-webkit-details-marker { display: none; }
        summary:hover { background: rgba(255,255,255,0.05); }
        .patch-content { padding: 10px; border-top: 1px solid rgba(255,255,255,0.05); font-size: 13px; color: #dbdee1; }
        .patch-content ul { margin: 0; padding-left: 20px; }
        .patch-content li { margin-bottom: 4px; }

        .aq-resize-handle {
            position: absolute;
            bottom: 0;
            right: 0;
            width: 20px;
            height: 20px;
            cursor: nwse-resize;
            background: radial-gradient(circle at bottom right, rgba(255,255,255,0.2) 0%, transparent 50%);
            border-bottom-right-radius: 12px;
            z-index: 10;
        }
    `;

    // Inject Styles
    const styleEl = document.createElement('style');
    styleEl.textContent = styles;
    document.head.appendChild(styleEl);

    // Create Main Container
    const container = document.createElement('div');
    container.id = 'autoquest-overlay';
    container.innerHTML = `
        <div id="autoquest-header">
            <span style="display:flex; align-items:center; gap:10px;">
                <img src="https://cdn.discordapp.com/embed/avatars/0.png" style="width:24px; height:24px; border-radius:50%;" id="aq-header-icon">
                <span id="aq-title">${Strings.headerTitle}</span>
            </span>
            <div class="header-controls">
                <button class="aq-btn-icon" id="aq-credits-btn" title="${Strings.creditsTooltip || 'Credits'}">ℹ</button>
                <button class="aq-btn-icon" id="aq-lang-btn" title="${Strings.langTooltip}">🌐</button>
                <div id="aq-lang-menu" class="aq-dropdown"></div>
                
                <button class="aq-btn-icon" id="aq-refresh-btn" title="${Strings.refreshTooltip}">↻</button>
                <button class="aq-btn-icon" id="aq-min-btn" title="${Strings.minimizeTooltip}">_</button>
                <button class="aq-btn-icon aq-btn-close" title="${Strings.closeTooltip}">×</button>
            </div>
        </div>
        <div id="autoquest-content">
            <div style="text-align: center; color: #949ba4; padding: 40px 20px;">
                <div style="margin-bottom:10px; font-size:24px;">🔍</div>
                <span id="aq-scanning">${Strings.scanning}</span>
            </div>
        </div>
        <div id="autoquest-footer">
            <button id="aq-start-btn" class="aq-btn aq-btn-start">${Strings.startBtnReady}</button>
        </div>
        
        <!-- Credits Modal -->
        <div id="aq-credits-modal">
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
                <h2 style="margin:0; color:#fff; font-size: 20px;">Credits</h2>
                <button class="aq-btn-icon" id="aq-credits-close">×</button>
            </div>
            
            <div class="credits-section">
                <div class="credits-text">
                    Original creator: <strong>aamiaa</strong><br>
                    Edited by: <strong>Rakise</strong>
                </div>
            </div>

            <div class="credits-header">Patch Notes</div>
            
            <details>
                <summary>Patch 0.1.2</summary>
                <div class="patch-content">
                    <ul>
                        <li>Added multi-language support (i18n)</li>
                        <li>Changed cursor to blocked icon when buttons are disabled</li>
                    </ul>
                </div>
            </details>

            <details>
                <summary>Patch 0.1.1</summary>
                <div class="patch-content">
                    <ul>
                        <li>Added support for starting individual quests manually</li>
                    </ul>
                </div>
            </details>

            <details>
                <summary>Patch 0.1</summary>
                <div class="patch-content">
                    <ul>
                        <li>Added proper handling for HTTP 429 (rate limiting)</li>
                        <li>Added safeguards to prevent multitasking a lot when quests are of the same type</li>
                    </ul>
                </div>
            </details>

            <details>
                <summary>Patch 0.0</summary>
                <div class="patch-content">
                    <ul>
                        <li>Added a simple and intuitive UI</li>
                        <li>Implemented quest queueing</li>
                        <li>Enabled multitasking between different quests</li>
                    </ul>
                </div>
            </details>
        </div>

        <div class="aq-resize-handle"></div>
    `;
    document.body.appendChild(container);

    // --- State & Interaction Logic ---
    const header = document.getElementById('autoquest-header');
    const resizeHandle = container.querySelector('.aq-resize-handle');
    const minBtn = document.getElementById('aq-min-btn');
    const refreshBtn = document.getElementById('aq-refresh-btn');
    const langBtn = document.getElementById('aq-lang-btn');
    const langMenu = document.getElementById('aq-lang-menu');
    const creditsBtn = document.getElementById('aq-credits-btn');
    const creditsModal = document.getElementById('aq-credits-modal');
    const creditsClose = document.getElementById('aq-credits-close');
    
    let isDragging = false, isResizing = false, isMinimized = false;
    let dragOffset = { x: 0, y: 0 };
    let resizeStart = { w: 0, h: 0, x: 0, y: 0 };
    let rafId = null;
    let savedSize = { w: 380, h: 500 }; 
    let hasMoved = false; 

    // Credits Logic
    creditsBtn.onclick = (e) => {
        e.stopPropagation();
        creditsModal.classList.add('visible');
    };
    creditsClose.onclick = (e) => {
        e.stopPropagation();
        creditsModal.classList.remove('visible');
    };

    // Language Menu Toggle
    langBtn.onclick = (e) => {
        e.stopPropagation();
        langMenu.classList.toggle('visible');
    };
    
    // Close menu when clicking elsewhere
    document.addEventListener('click', (e) => {
        if (!e.target.closest('#aq-lang-btn') && !e.target.closest('#aq-lang-menu')) {
            langMenu.classList.remove('visible');
        }
    });

    const toggleMinimize = () => {
        isMinimized = !isMinimized;
        container.classList.toggle('minimized', isMinimized);
        
        if (isMinimized) {
            savedSize = { w: container.offsetWidth, h: container.offsetHeight };
        } else {
            container.style.width = `${savedSize.w}px`;
            container.style.height = `${savedSize.h}px`;
        }
    };

    minBtn.onclick = (e) => { e.stopPropagation(); toggleMinimize(); };
    
    container.onmousedown = (e) => {
        if (!isMinimized) return; 
        e.preventDefault(); 
        isDragging = true;
        hasMoved = false;
        
        const rect = container.getBoundingClientRect();
        dragOffset.x = e.clientX - rect.left;
        dragOffset.y = e.clientY - rect.top;
        container.style.cursor = 'grabbing';
    };

    container.onclick = (e) => {
        if(isMinimized && e.target === container && !hasMoved) toggleMinimize();
    };

    header.onmousedown = (e) => {
        if(e.target.closest('button') || e.target.closest('.aq-dropdown')) return;
        isDragging = true;
        hasMoved = false;
        const rect = container.getBoundingClientRect();
        dragOffset.x = e.clientX - rect.left;
        dragOffset.y = e.clientY - rect.top;
        header.style.cursor = 'grabbing';
    };

    resizeHandle.onmousedown = (e) => {
        e.stopPropagation();
        isResizing = true;
        const rect = container.getBoundingClientRect();
        resizeStart = { w: rect.width, h: rect.height, x: e.clientX, y: e.clientY };
    };

    document.onmousemove = (e) => {
        if (!isDragging && !isResizing) return;
        if (isDragging) hasMoved = true;
        if (rafId) cancelAnimationFrame(rafId);
        rafId = requestAnimationFrame(() => {
            if (isDragging) {
                const newLeft = e.clientX - dragOffset.x;
                const newTop = e.clientY - dragOffset.y;
                container.style.left = `${newLeft}px`;
                container.style.top = `${newTop}px`;
            } 
            else if (isResizing && !isMinimized) {
                const deltaX = e.clientX - resizeStart.x;
                const deltaY = e.clientY - resizeStart.y;
                container.style.width = `${Math.max(300, resizeStart.w + deltaX)}px`;
                container.style.height = `${Math.max(200, resizeStart.h + deltaY)}px`;
            }
        });
    };

    document.onmouseup = () => {
        isDragging = false;
        isResizing = false;
        header.style.cursor = 'default';
        container.style.cursor = isMinimized ? 'pointer' : 'default';
        if (rafId) cancelAnimationFrame(rafId);
    };

    container.querySelector('.aq-btn-close').onclick = (e) => {
        e.stopPropagation();
        container.remove();
        styleEl.remove();
        if(autoRefreshInterval) clearInterval(autoRefreshInterval);
    };
    
    // --- UI Helpers ---
    const contentArea = document.getElementById('autoquest-content');
    const timers = new Map();
    let autoRefreshInterval = null;

    const formatTime = (seconds) => {
        if (seconds <= 0) return "00:00";
        const m = Math.floor(seconds / 60);
        const s = Math.floor(seconds % 60);
        return `${m}:${s.toString().padStart(2, '0')}`;
    };

    const updateUI = (questId, percent, status, remainingSecs = null, isDone = false) => {
        const card = document.getElementById(`quest-card-${questId}`);
        if (!card) return;
        const bar = card.querySelector('.progress-fill');
        const statusEl = card.querySelector('.status-detail');
        const timerEl = card.querySelector('.quest-timer');
        const playBtn = card.querySelector('.aq-btn-play');

        if(bar) bar.style.width = `${Math.min(100, percent)}%`;
        if(statusEl) statusEl.innerText = status;
        if (remainingSecs !== null && timerEl) timerEl.innerText = formatTime(remainingSecs);
        
        // If status implies running/queued, disable button
        if(playBtn && !isDone && (status.includes("Running") || status.includes("Queued") || status.includes("Starting") || status.includes("Active") || status.includes("Watching"))) {
            playBtn.disabled = true;
            playBtn.innerHTML = `
                <div style="width:12px;height:12px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:aq-spin 1s linear infinite;"></div>
                <style>@keyframes aq-spin{to{transform:rotate(360deg)}}</style>
            `;
        }

        if (isDone) {
            if(bar) { bar.style.background = colors.success; bar.style.boxShadow = "none"; }
            if(statusEl) { statusEl.style.color = colors.success; statusEl.innerText = Strings.statusCompleted; }
            if(timerEl) timerEl.innerText = Strings.statusDone;
            if(playBtn) { playBtn.innerText = "✓"; playBtn.disabled = true; playBtn.style.color = colors.success; playBtn.style.borderColor = colors.success; }
            if (Math.random() > 0.7) fireConfetti(1000, 30); 
            if (timers.has(questId)) { clearInterval(timers.get(questId)); timers.delete(questId); }
        }
    };

    const startLocalTimer = (questId, initialSeconds) => {
        if (timers.has(questId)) clearInterval(timers.get(questId));
        let remaining = initialSeconds;
        const card = document.getElementById(`quest-card-${questId}`);
        const timerEl = card?.querySelector('.quest-timer');
        if(timerEl) timerEl.innerText = formatTime(remaining);
        const interval = setInterval(() => {
            remaining--;
            if (remaining < 0) remaining = 0;
            if(timerEl) timerEl.innerText = formatTime(remaining);
            if(!document.getElementById(`quest-card-${questId}`)) clearInterval(interval);
        }, 1000);
        timers.set(questId, interval);
    };

    // --- Task Queue Scheduler ---
    const Scheduler = {
        video: { active: 0, max: 2, queue: [] },
        game: { active: 0, max: 1, queue: [] },
        stream: { active: 0, max: 1, queue: [] },
        activity: { active: 0, max: 1, queue: [] }, 

        enqueue: (task) => {
            let type = 'video';
            if(task.type.includes('PLAY_ON_DESKTOP')) type = 'game';
            else if(task.type.includes('STREAM')) type = 'stream';
            else if(task.type.includes('ACTIVITY')) type = 'activity';

            Scheduler[type].queue.push(task);
            updateUI(task.q.id, (task.done/task.needed)*100, Strings.statusQueued);
            Scheduler.process(type);
        },

        process: async (type) => {
            const pool = Scheduler[type];
            if (pool.active >= pool.max || pool.queue.length === 0) return;

            pool.active++;
            const task = pool.queue.shift();
            
            try {
                // Execute based on type
                if (type === 'video') {
                    await handleVideoTask(task.q, task.type, task.needed, task.done);
                } else if (type === 'activity') {
                    await handleActivityTask(task.q, task.type, task.needed, task.done);
                } else if (type === 'game') {
                    await handleDesktopPlayTask(task.q, task.appId, task.appName, task.needed, task.pid);
                } else if (type === 'stream') {
                    await handleDesktopStreamTask(task.q, task.appId, task.appName, task.needed, task.pid);
                }
            } catch (e) {
                console.error(e);
            } finally {
                pool.active--;
                // Add delay before next to avoid rate limits
                setTimeout(() => Scheduler.process(type), 1500); 
            }
        }
    };

    const createQuestCard = (q, type) => {
        const div = document.createElement('div');
        div.className = 'quest-card';
        div.id = `quest-card-${q.id}`;
        let typeClass = 'badge-game';
        if(type.includes("VIDEO")) typeClass = 'badge-video';
        if(type.includes("STREAM")) typeClass = 'badge-stream';
        if(type.includes("ACTIVITY")) typeClass = 'badge-activity';
        
        div.innerHTML = `
            <div class="quest-info">
                <div class="quest-meta">
                    <span class="quest-type ${typeClass}">${type.replace(/_/g, ' ')}</span>
                    <span class="quest-timer">--:--</span>
                </div>
                <div class="quest-title">${q.config.messages.questName}</div>
                <div class="progress-track">
                    <div class="progress-fill" style="width: 0%"></div>
                </div>
                <div class="status-text">
                    <span class="status-detail">${Strings.statusReady}</span>
                </div>
            </div>
            <div class="quest-actions">
                <button class="aq-btn-icon aq-btn-play" title="${Strings.playTooltip}">▶</button>
            </div>
        `;
        
        // Attach individual start handler
        div.querySelector('.aq-btn-play').onclick = function() {
            this.disabled = true;
            const pid = Math.floor(Math.random() * 30000) + 1000;
            const taskConfig = q.config.taskConfig ?? q.config.taskConfigV2;
            const secondsNeeded = taskConfig.tasks[type].target;
            const secondsDone = q.userStatus?.progress?.[type]?.value ?? 0;
            
            Scheduler.enqueue({
                q: q,
                type: type,
                needed: secondsNeeded,
                done: secondsDone,
                appId: q.config.application.id,
                appName: q.config.application.name,
                pid: pid
            });
        };

        return div;
    };

    // --- 4. Task Logic (Smart Throttle) ---

    // Retry Helper
    const safePost = async (url, body) => {
        try {
            return await api.post({ url, body });
        } catch (e) {
            // Check for 429
            if (e.status === 429 || (e.body && e.body.retry_after)) {
                const retryAfter = (e.body?.retry_after || 5) * 1000;
                console.log(`[Rate Limit] Waiting ${retryAfter}ms...`);
                await new Promise(r => setTimeout(r, retryAfter + 1000));
                return await api.post({ url, body });
            }
            throw e;
        }
    };

    const handleVideoTask = async (quest, taskName, secondsNeeded, secondsDone) => {
        let remaining = secondsNeeded - secondsDone;
        updateUI(quest.id, (secondsDone/secondsNeeded)*100, Strings.statusWatching, remaining);
        startLocalTimer(quest.id, remaining);
        
        const maxFuture = 10, speed = 7, interval = 2; // Slower interval
        const enrolledAt = new Date(quest.userStatus.enrolledAt).getTime();
        
        await new Promise(r => setTimeout(r, Math.random() * 2000));

        while (true) {
            const maxAllowed = Math.floor((Date.now() - enrolledAt) / 1000) + maxFuture;
            const diff = maxAllowed - secondsDone;
            const timestamp = secondsDone + speed;

            if (diff >= speed) {
                try {
                    const res = await safePost(`/quests/${quest.id}/video-progress`, { timestamp: Math.min(secondsNeeded, timestamp + Math.random()) });
                    secondsDone = Math.min(secondsNeeded, timestamp);
                    updateUI(quest.id, (secondsDone/secondsNeeded)*100, Strings.statusWatching);
                    if (res.body.completed_at != null || secondsDone >= secondsNeeded) break;
                } catch(e) {
                    updateUI(quest.id, (secondsDone/secondsNeeded)*100, Strings.statusRetrying);
                }
            }
            await new Promise(r => setTimeout(r, interval * 1000));
        }

        await safePost(`/quests/${quest.id}/video-progress`, { timestamp: secondsNeeded });
        updateUI(quest.id, 100, Strings.statusCompleted, 0, true);
    };

    const handleActivityTask = async (quest, taskName, secondsNeeded, secondsDone) => {
        const channelId = ChannelStore.getSortedPrivateChannels()[0]?.id 
            ?? Object.values(GuildChannelStore.getAllGuilds()).find(x => x != null && x.VOCAL.length > 0)?.VOCAL[0].channel.id;
        if (!channelId) {
            updateUI(quest.id, 0, Strings.errorVoice);
            return;
        }
        let remaining = secondsNeeded - secondsDone;
        const streamKey = `call:${channelId}:1`;
        updateUI(quest.id, 0, Strings.statusActivity, remaining);
        startLocalTimer(quest.id, remaining);
        while (true) {
            try {
                const res = await safePost(`/quests/${quest.id}/heartbeat`, { stream_key: streamKey, terminal: false });
                const progress = res.body.progress.PLAY_ACTIVITY.value;
                updateUI(quest.id, (progress/secondsNeeded)*100, Strings.statusInActivity);
                if (progress >= secondsNeeded) {
                    await safePost(`/quests/${quest.id}/heartbeat`, { stream_key: streamKey, terminal: true });
                    break;
                }
            } catch(e) {
                updateUI(quest.id, 0, Strings.statusRetrying);
            }
            await new Promise(r => setTimeout(r, 20 * 1000));
        }
        updateUI(quest.id, 100, Strings.statusCompleted, 0, true);
    };

    const handleDesktopPlayTask = (quest, applicationId, applicationName, secondsNeeded, pid) => {
        return new Promise((resolve) => {
            if (!isApp) { updateUI(quest.id, 0, Strings.errorDesktop); return resolve(); }
            updateUI(quest.id, 0, Strings.statusGame, secondsNeeded);
            api.get({ url: `/applications/public?application_ids=${applicationId}` }).then(res => {
                const appData = res.body[0];
                const exeName = appData.executables.find(x => x.os === "win32").name.replace(">", "");
                const fakeGame = { cmdLine: `C:\\Program Files\\${appData.name}\\${exeName}`, exeName, exePath: `c:/program files/${appData.name.toLowerCase()}/${exeName}`, hidden: false, isLauncher: false, id: applicationId, name: appData.name, pid: pid, pidPath: [pid], processName: appData.name, start: Date.now() };
                const realGetRunningGames = RunningGameStore.getRunningGames;
                const realGetGameForPID = RunningGameStore.getGameForPID;
                const realGames = RunningGameStore.getRunningGames();
                RunningGameStore.getRunningGames = () => [fakeGame];
                RunningGameStore.getGameForPID = (pid) => pid === fakeGame.pid ? fakeGame : null;
                FluxDispatcher.dispatch({ type: "RUNNING_GAMES_CHANGE", removed: realGames, added: [fakeGame], games: [fakeGame] });
                const fn = (data) => {
                    let progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.PLAY_ON_DESKTOP.value);
                    const rem = Math.max(0, secondsNeeded - progress);
                    if (!timers.has(quest.id)) startLocalTimer(quest.id, rem);
                    updateUI(quest.id, (progress/secondsNeeded)*100, Strings.statusGame, rem);
                    if (progress >= secondsNeeded) {
                        RunningGameStore.getRunningGames = realGetRunningGames;
                        RunningGameStore.getGameForPID = realGetGameForPID;
                        FluxDispatcher.dispatch({ type: "RUNNING_GAMES_CHANGE", removed: [fakeGame], added: [], games: [] });
                        FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
                        updateUI(quest.id, 100, Strings.statusCompleted, 0, true);
                        resolve();
                    }
                };
                FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
            });
        });
    };

    const handleDesktopStreamTask = (quest, applicationId, applicationName, secondsNeeded, pid) => {
        return new Promise((resolve) => {
            if (!isApp) { updateUI(quest.id, 0, Strings.errorDesktop); return resolve(); }
            updateUI(quest.id, 0, Strings.statusStream, secondsNeeded);
            const realFunc = ApplicationStreamingStore.getStreamerActiveStreamMetadata;
            ApplicationStreamingStore.getStreamerActiveStreamMetadata = () => ({ id: applicationId, pid, sourceName: null });
            const fn = (data) => {
                let progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.STREAM_ON_DESKTOP.value);
                const rem = Math.max(0, secondsNeeded - progress);
                if (!timers.has(quest.id)) startLocalTimer(quest.id, rem);
                updateUI(quest.id, (progress/secondsNeeded)*100, Strings.statusStream, rem);
                if (progress >= secondsNeeded) {
                    ApplicationStreamingStore.getStreamerActiveStreamMetadata = realFunc;
                    FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
                    updateUI(quest.id, 100, Strings.statusCompleted, 0, true);
                    resolve();
                }
            };
            FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
        });
    };

    // --- 5. Initialization ---

    const scanQuests = () => {
        const rawQuests = QuestsStore.quests;
        const questsList = (rawQuests instanceof Map) ? [...rawQuests.values()] : Object.values(rawQuests);
        contentArea.innerHTML = '';
        let count = 0;
        
        for (const q of questsList) {
            const isEnrolled = !!q.userStatus?.enrolledAt;
            const isCompleted = !!q.userStatus?.completedAt;
            const isExpired = new Date(q.config.expiresAt).getTime() <= Date.now();
            if (q.id === "1412491570820812933") continue; 
            if (isCompleted || isExpired || !isEnrolled) continue;
            const taskConfig = q.config.taskConfig ?? q.config.taskConfigV2;
            const taskName = ["WATCH_VIDEO", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY", "WATCH_VIDEO_ON_MOBILE"].find(x => taskConfig.tasks[x] != null);
            if (!taskName) continue;
            const secondsNeeded = taskConfig.tasks[taskName].target;
            const secondsDone = q.userStatus?.progress?.[taskName]?.value ?? 0;
            if (secondsDone >= secondsNeeded) continue;
            
            contentArea.appendChild(createQuestCard(q, taskName));
            
            const rem = Math.max(0, secondsNeeded - secondsDone);
            const tEl = document.getElementById(`quest-card-${q.id}`).querySelector('.quest-timer');
            if(tEl) tEl.innerText = formatTime(rem);
            count++;
        }
        if(count === 0) { contentArea.innerHTML = `<div style="text-align:center; padding: 40px; color: #949ba4;">${Strings.noQuests}</div>`; return; }
    };

    const startAllQuests = async (btn) => {
        btn.innerText = Strings.startBtnQueued;
        btn.disabled = true;
        btn.style.opacity = '0.5';
        btn.style.cursor = 'not-allowed';
        
        // Trigger clicks on all visible play buttons
        const playBtns = document.querySelectorAll('.aq-btn-play');
        playBtns.forEach(b => {
            if(!b.disabled) b.click();
        });
    };

    // Apply a specific language
    const applyLanguage = (langCode) => {
        if (!AllTranslations[langCode]) return;
        Strings = AllTranslations[langCode];
        
        // Update Static Elements
        document.getElementById('aq-title').innerText = Strings.headerTitle;
        
        // Check if scanning text element still exists
        const scanningEl = document.getElementById('aq-scanning');
        if(scanningEl) scanningEl.innerHTML = Strings.scanning;
        
        document.getElementById('aq-start-btn').innerText = Strings.startBtnReady;
        document.getElementById('aq-refresh-btn').title = Strings.refreshTooltip;
        document.getElementById('aq-min-btn').title = Strings.minimizeTooltip;
        document.querySelector('.aq-btn-close').title = Strings.closeTooltip;
        document.getElementById('aq-credits-btn').title = Strings.creditsTooltip;
        document.getElementById('aq-lang-btn').title = Strings.langTooltip;

        scanQuests();
        
        document.getElementById('aq-lang-menu').classList.remove('visible');
    };

    const buildLangMenu = () => {
        const menu = document.getElementById('aq-lang-menu');
        menu.innerHTML = '';
        Object.keys(AllTranslations).forEach(code => {
            const item = document.createElement('div');
            item.className = 'aq-lang-item';
            item.innerText = AllTranslations[code].name;
            item.onclick = () => applyLanguage(code);
            menu.appendChild(item);
        });
    };

    const init = async () => {
        // Load Translations first
        try {
            const resp = await fetch(TRANSLATION_URL);
            if (resp.ok) {
                const data = await resp.json();
                AllTranslations = { ...AllTranslations, ...data };
                console.log(`AutoQuest: Loaded remote translations`);
            }
        } catch (e) {
            console.log("AutoQuest: Remote translations unavailable, using built-in");
        }

        // Initialize UI with detected language
        const userLang = (navigator.language || navigator.userLanguage);
        let targetLang = "en-US";
        if (AllTranslations[userLang]) targetLang = userLang;
        else {
            const shortLang = userLang.split('-')[0]; 
            const found = Object.keys(AllTranslations).find(k => k.startsWith(shortLang));
            if (found) targetLang = found;
        }
        
        Strings = AllTranslations[targetLang];

        // Build UI elements
        document.getElementById('aq-title').innerText = Strings.headerTitle;
        const scanningEl = document.getElementById('aq-scanning');
        if(scanningEl) scanningEl.innerHTML = Strings.scanning;
        
        document.getElementById('aq-start-btn').innerText = Strings.startBtnReady;
        document.getElementById('aq-refresh-btn').title = Strings.refreshTooltip;
        document.getElementById('aq-min-btn').title = Strings.minimizeTooltip;
        document.querySelector('.aq-btn-close').title = Strings.closeTooltip;
        document.getElementById('aq-credits-btn').title = Strings.creditsTooltip;
        document.getElementById('aq-lang-btn').title = Strings.langTooltip;

        buildLangMenu();
        scanQuests();
        
        const startBtn = document.getElementById('aq-start-btn');
        if(startBtn) startBtn.onclick = () => startAllQuests(startBtn);
        if(refreshBtn) {
            refreshBtn.onclick = () => {
                refreshBtn.style.transform = 'rotate(360deg)';
                refreshBtn.style.transition = 'transform 0.5s';
                scanQuests();
                setTimeout(() => { refreshBtn.style.transform = 'none'; }, 500);
            };
        }
        autoRefreshInterval = setInterval(() => {}, 30000);
    };

    init();
    console.log("AutoQuest UI Loaded");
}
@svenbledt
Copy link

Thanks for doing that! Improvements are welcome! i rly like what u did with the project!

@TheDumbWolfy
Copy link

Thanks for making this! Works perfectly!

@BeanoWellesley
Copy link

image_2025-12-21_025513973

It seems that Discord went to work and patched it, I tried running it 3 times with 20 mins of runtime each for tests.

They probably saw your post and smited it down before anyone can bypass

@BeanoWellesley
Copy link

image_2025-12-21_025513973 It seems that Discord went to work and patched it, I tried running it 3 times with 20 mins of runtime each for tests.

They probably saw your post and smited it down before anyone can bypass

nvm, they didnt patch it..

my 2gb laptop just having an aneurysm my bad twin

@qaz1qazlol2
Copy link

qaz1qazlol2 commented Dec 20, 2025

There is a small problem that the refresh button doesn't take place immediately
I have to wait for some time to see the time change
Is it able to make the button take place immediately?

@Rakise
Copy link
Author

Rakise commented Dec 20, 2025

There is a small problem that the refresh button doesn't take place immediately I have to wait for some time to see the time change Is it able to make the button take place immediately?

refresh button is meant to refresh quest list without re-running the code. will add a new functionality to start each one individually

@qaz1qazlol2
Copy link

There is a small problem that the refresh button doesn't take place immediately I have to wait for some time to see the time change Is it able to make the button take place immediately?

refresh button is meant to refresh quest list without re-running the code. will add a new functionality to start each one individually

Great,looking forward to the next update

@Rakise
Copy link
Author

Rakise commented Dec 20, 2025

There is a small problem that the refresh button doesn't take place immediately I have to wait for some time to see the time change Is it able to make the button take place immediately?

refresh button is meant to refresh quest list without re-running the code. will add a new functionality to start each one individually

Great,looking forward to the next update

give it a try! ^-^

@qaz1qazlol2
Copy link

qaz1qazlol2 commented Dec 20, 2025

There is a small problem that the refresh button doesn't take place immediately I have to wait for some time to see the time change Is it able to make the button take place immediately?

refresh button is meant to refresh quest list without re-running the code. will add a new functionality to start each one individually

Great,looking forward to the next update

give it a try! ^-^

wow,update so fast
though i have no more task left to try
maybe next time
by the way,is it able to edit the code to i18n the gui and edit the “Tasks Running” button cursor to 🚫 one?

@Rakise
Copy link
Author

Rakise commented Dec 20, 2025

wow,update so fast though i have no more task left to try maybe next time by the way,is it able to edit the code to i18n the gui and edit the “Tasks Running” button cursor to 🚫 one?

i can try later on yeah

@qaz1qazlol2
Copy link

qaz1qazlol2 commented Dec 20, 2025

i can try later on yeah

Cool

@qaz1qazlol2
Copy link

qaz1qazlol2 commented Dec 24, 2025

Oh here is another advice: Is it able to show All tasks Completed Button replacing Start All Quests button When all tasks completed?
and i wonder there will be an auto-task-accepter in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment