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
- Added a simple and intuitive UI
- Implemented quest queueing
- Enabled multitasking between different quests
- Added proper handling for HTTP 429 (rate limiting)
- Added safeguards to prevent multitasking a lot when quests are of the same type
- Added support for starting individual quests manually
How to use this script:
- Accept a quest under Discover -> Quests
- Press Ctrl+Shift+I to open DevTools
- Go to the
Consoletab - 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;
// --- 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 {
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; }
#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);
}
.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">
AutoQuest
</span>
<div class="header-controls">
<button class="aq-btn-icon" id="aq-refresh-btn" title="Refresh Quests">↻</button>
<button class="aq-btn-icon" id="aq-min-btn" title="Minimize">_</button>
<button class="aq-btn-icon aq-btn-close" title="Close">×</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>
Scanning quests...
</div>
</div>
<div id="autoquest-footer">
<button id="aq-start-btn" class="aq-btn aq-btn-start">Start All Quests</button>
</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');
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;
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')) 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 = "Completed!"; }
if(timerEl) timerEl.innerText = "Done";
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, "Queued...");
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">Ready to start</span>
</div>
</div>
<div class="quest-actions">
<button class="aq-btn-icon aq-btn-play" title="Start This Quest">▶</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, "Watching...", 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, "Watching...");
if (res.body.completed_at != null || secondsDone >= secondsNeeded) break;
} catch(e) {
updateUI(quest.id, (secondsDone/secondsNeeded)*100, "Retrying...");
}
}
await new Promise(r => setTimeout(r, interval * 1000));
}
await safePost(`/quests/${quest.id}/video-progress`, { timestamp: secondsNeeded });
updateUI(quest.id, 100, "Completed!", 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, "Error: No Voice Channel found");
return;
}
let remaining = secondsNeeded - secondsDone;
const streamKey = `call:${channelId}:1`;
updateUI(quest.id, 0, "Active...", 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, "In Activity...");
if (progress >= secondsNeeded) {
await safePost(`/quests/${quest.id}/heartbeat`, { stream_key: streamKey, terminal: true });
break;
}
} catch(e) {
updateUI(quest.id, 0, "Retrying...");
}
await new Promise(r => setTimeout(r, 20 * 1000));
}
updateUI(quest.id, 100, "Completed!", 0, true);
};
const handleDesktopPlayTask = (quest, applicationId, applicationName, secondsNeeded, pid) => {
return new Promise((resolve) => {
if (!isApp) { updateUI(quest.id, 0, "Error: Desktop App Required"); return resolve(); }
updateUI(quest.id, 0, "Starting Game...", 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, "Playing Game...", 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, "Completed!", 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, "Error: Desktop App Required"); return resolve(); }
updateUI(quest.id, 0, "Starting Stream...", 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, "Streaming...", rem);
if (progress >= secondsNeeded) {
ApplicationStreamingStore.getStreamerActiveStreamMetadata = realFunc;
FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
updateUI(quest.id, 100, "Completed!", 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;
// Reset schedulers if needed or keep queues?
// For simplicity we clear UI and let users re-queue if they refresh manually.
// Active tasks in background continue running (Scheduler doesn't stop on UI refresh).
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;">No active quests found.<br>Make sure you accepted them!</div>'; return; }
};
const startAllQuests = async (btn) => {
btn.innerText = 'Tasks Queued...';
btn.disabled = true;
btn.style.opacity = '0.5';
// Trigger clicks on all visible play buttons
const playBtns = document.querySelectorAll('.aq-btn-play');
playBtns.forEach(b => {
if(!b.disabled) b.click();
});
};
const init = () => {
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");
}

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