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");
}
@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