Caution
Use at your own risk. As of April 2026, Discord has expressed intent to monitor and potentially flag accounts automating quest completion. While this script uses native telemetry hooks and official client actions, you should proceed with caution.
Note
This script works best on the Discord Desktop App. While it can process Video Quests in the browser, Game Quests ("Play" or "Stream") require native telemetry only found in the desktop client.
| Feature | Description |
|---|---|
| 🛡️ Safe Property Resolution | Resolves application IDs across both standard and V2 quest formats (taskConfigV2, applications[]) without crashing. |
| 🎯 100% Faithful Engine | Uses authentic store spoofing (RunningGameStore, ApplicationStreamingStore) so Discord natively sends verified heartbeats. |
| 🔒 Anti-Captcha Safety | Eliminates auto-claim and auto-enroll calls to avoid triggering automated captcha challenges or security flags. |
| 💡 Smart Detection Alerts | Notifies you in the console if there are available quests waiting to be accepted before running. |
| 🌐 Multi-Task Compatibility | Handles Play on Desktop, Stream on Desktop, Play Activity, and Watch Video / Mobile Video. |
Depending on the quest type, here is what happens when you run the script:
| Quest Type | What the Script Does | What You Need to Do |
|---|---|---|
📺 Watch Video (WATCH_VIDEO) |
Simulates periodic video progress updates directly with Discord's API. | Just wait until it reaches 100%, then claim the reward. |
🎮 Play Game (PLAY_ON_DESKTOP) |
Spoofs the game process in Discord's memory (RunningGameStore). |
Keep Discord Desktop open and wait for the timer to finish. |
📡 Stream Game (STREAM_ON_DESKTOP) |
Spoofs your active screen share to match the required game title. | Join a Voice Channel with at least 1 other person and stream any window. |
🕹️ Activity (PLAY_ACTIVITY) |
Emits periodic heartbeat pings via voice channel stream keys. | Leave Discord open while the script completes the progress loops. |
If pressing Ctrl + Shift + I does not open Developer Tools in your Discord Desktop app, follow these steps:
- Close Discord completely.
- Press
Win+R, type%appdata%, and pressEnter. - Navigate to the
discordfolder and opensettings.jsonwith Notepad. - Add the following line before the closing
}:(Note: Ensure there is a comma"DANGEROUS_ENABLE_DEVTOOLS_ONLY_ENABLE_IF_YOU_KNOW_WHAT_YOU_ARE_DOING": true
,at the end of the line preceding it!) - Save the file, restart Discord, and press
Ctrl+Shift+I.
- Download and install the Discord Public Test Build (PTB) (DevTools are enabled by default).
- Accept the Quest(s):
- Open Discord and go to User Settings > Gift Inventory / Quests (or the Discover / Quests tab).
- Click Accept Quest on any quests you want to complete.
- Open Console:
- Press
Ctrl+Shift+Ito open DevTools, then click on the Console tab. - (If prompted with a pasting warning, type
allow pastingand press Enter).
- Press
- Run the Script:
- Copy the script below, paste it into the console, and press
Enter.
- Copy the script below, paste it into the console, and press
- Claim Reward:
- Once the console displays
🎉 All active quests have been processed!, return to the Quests tab in Discord and click Claim Reward.
- Once the console displays
Click to expand/collapse the script code
/**
* 🚀 Discord Quest Automator
* Based on aamiaa's gist (https://gist.github.com/aamiaa/204cd9d42013ded9faf646fae7f89fbb)
* Fixed: Safe application ID resolution + robust module finder
*/
(function() {
"use strict";
delete window.$;
const log = (msg, type = "info") => {
const colors = { info: "#3498db", success: "#2ecc71", error: "#e74c3c", warn: "#f1c40f", progress: "#9b59b6" };
const icon = { info: "ℹ️", success: "✅", error: "❌", warn: "⚠️", progress: "⏳" }[type] || "ℹ️";
console.log(`%c${icon} [QuestBot] ${msg}`, `color: ${colors[type] || "#fff"}; font-weight: bold;`);
};
let wpRequire;
try {
wpRequire = window.webpackChunkdiscord_app.push([[Symbol()], {}, (x) => x]);
window.webpackChunkdiscord_app.pop();
} catch (e) {
log("Could not initialize Webpack. Are you running this in Discord?", "error");
return;
}
// Robust store finder matching aamiaa's method with fallback
const findStore = (predicate) => {
for (let id in wpRequire.c) {
let m = wpRequire.c[id]?.exports;
if (!m) continue;
for (let k of ["A", "Ay", "Bo", "h", "Z", "default", "ZP", "HTTP"]) {
if (m[k] && predicate(m[k])) return m[k];
}
for (let k in m) {
if (m[k] && typeof m[k] === "object" && predicate(m[k])) return m[k];
}
if (predicate(m)) return m;
}
return null;
};
const ApplicationStreamingStore = Object.values(wpRequire.c).find(x => x?.exports?.A?.__proto__?.getStreamerActiveStreamMetadata)?.exports?.A
?? findStore(x => x?.getStreamerActiveStreamMetadata || x?.__proto__?.getStreamerActiveStreamMetadata);
const RunningGameStore = Object.values(wpRequire.c).find(x => x?.exports?.Ay?.getRunningGames)?.exports?.Ay
?? findStore(x => x?.getRunningGames || x?.__proto__?.getRunningGames);
const QuestsStore = Object.values(wpRequire.c).find(x => x?.exports?.A?.__proto__?.getQuest)?.exports?.A
?? findStore(x => x?.getQuest || x?.__proto__?.getQuest);
const ChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.A?.__proto__?.getAllThreadsForParent)?.exports?.A
?? findStore(x => x?.getAllThreadsForParent || x?.__proto__?.getAllThreadsForParent || x?.getSortedPrivateChannels);
const GuildChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.Ay?.getSFWDefaultChannel)?.exports?.Ay
?? findStore(x => x?.getSFWDefaultChannel || x?.__proto__?.getSFWDefaultChannel || x?.getAllGuilds);
const FluxDispatcher = Object.values(wpRequire.c).find(x => x?.exports?.h?.__proto__?.flushWaitQueue)?.exports?.h
?? findStore(x => x?.flushWaitQueue || x?.__proto__?.flushWaitQueue || (x?.dispatch && x?.subscribe && x?.unsubscribe));
const api = Object.values(wpRequire.c).find(x => x?.exports?.Bo?.get)?.exports?.Bo
?? findStore(x => x?.get && x?.post && (x?.patch || x?.delete || x?.put));
if (!QuestsStore || !FluxDispatcher || !api) {
log("Could not find required Discord core stores. Make sure you are inside Discord.", "error");
return;
}
const supportedTasks = ["WATCH_VIDEO", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY", "WATCH_VIDEO_ON_MOBILE"];
const isApp = typeof DiscordNative !== "undefined";
const questList = QuestsStore.quests?.values ? [...QuestsStore.quests.values()] : Object.values(QuestsStore.quests || {});
// Filter quests that are enrolled and not yet completed
let quests = questList.filter(x =>
x.userStatus?.enrolledAt &&
!x.userStatus?.completedAt &&
new Date(x.config.expiresAt).getTime() > Date.now() &&
supportedTasks.find(y => Object.keys((x.config.taskConfig ?? x.config.taskConfigV2).tasks).includes(y))
);
if (quests.length === 0) {
// Check if there are unaccepted quests available
let unaccepted = questList.filter(x =>
!x.userStatus?.completedAt &&
new Date(x.config.expiresAt).getTime() > Date.now() &&
supportedTasks.find(y => Object.keys((x.config.taskConfig ?? x.config.taskConfigV2).tasks).includes(y))
);
if (unaccepted.length > 0) {
log(`Found ${unaccepted.length} quest(s) available in Discord, but you haven't accepted them yet! Please go to your Discord Quests tab and click "Accept Quest" first, then run this script again.`, "warn");
} else {
log("You don't have any uncompleted quests!", "info");
}
return;
}
log(`Found ${quests.length} active quest(s). Starting automator...`, "info");
const doJob = function() {
const quest = quests.pop();
if (!quest) {
log("🎉 All active quests have been processed! You can now claim your rewards in Discord.", "success");
return;
}
const pid = Math.floor(Math.random() * 30000) + 1000;
const questName = quest.config.messages?.questName ?? quest.config.messages?.gameTitle ?? "Unknown Quest";
const taskConfig = quest.config.taskConfig ?? quest.config.taskConfigV2;
const taskName = supportedTasks.find(x => taskConfig.tasks[x] != null);
const taskData = taskConfig.tasks[taskName];
// Fixed: Safe application ID resolution (prevents "Cannot read properties of undefined (reading 'id')")
const applicationId = quest.config.application?.id ?? taskData?.applications?.[0]?.id;
const secondsNeeded = taskData.target;
let secondsDone = quest.userStatus?.progress?.[taskName]?.value ?? quest.userStatus?.streamProgressSeconds ?? 0;
log(`▶️ Starting: "${questName}" (${taskName})`, "progress");
// 1. WATCH VIDEO
if (taskName === "WATCH_VIDEO" || taskName === "WATCH_VIDEO_ON_MOBILE") {
const speed = 7;
let completed = false;
let fn = async () => {
while (true) {
const remaining = Math.min(speed, secondsNeeded - secondsDone);
await new Promise(resolve => setTimeout(resolve, remaining * 1000));
const timestamp = secondsDone + speed;
try {
const res = await api.post({
url: `/quests/${quest.id}/video-progress`,
body: { timestamp: Math.min(secondsNeeded, timestamp + Math.random()) }
});
completed = res.body?.completed_at != null;
} catch (e) {}
secondsDone = Math.min(secondsNeeded, timestamp);
log(`Video progress: ${Math.floor(secondsDone)}/${secondsNeeded}s`, "progress");
if (timestamp >= secondsNeeded) {
break;
}
}
if (!completed) {
try {
await api.post({
url: `/quests/${quest.id}/video-progress`,
body: { timestamp: secondsNeeded }
});
} catch (e) {}
}
log(`Quest "${questName}" completed! Claim your reward in the Quests tab.`, "success");
doJob();
};
fn();
log(`Spoofing video for "${questName}"...`, "info");
}
// 2. PLAY ON DESKTOP
else if (taskName === "PLAY_ON_DESKTOP") {
if (!isApp || !RunningGameStore) {
log(`This quest (${questName}) requires the Discord Desktop App. Use the desktop app to complete it!`, "error");
doJob();
return;
}
if (!applicationId) {
log(`Could not find application ID for "${questName}". Skipping...`, "error");
doJob();
return;
}
api.get({ url: `/applications/public?application_ids=${applicationId}` })
.then(res => {
const appData = res.body?.[0];
if (!appData) {
log(`Failed to fetch game data for "${questName}".`, "error");
doJob();
return;
}
const exeName = appData.executables?.find(x => x.os === "win32")?.name?.replace(">", "") ?? appData.name.replace(/[\/\\:*?"<>|]/g, "");
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 realGames = RunningGameStore.getRunningGames ? RunningGameStore.getRunningGames() : [];
const fakeGames = [fakeGame];
const realGetRunningGames = RunningGameStore.getRunningGames;
const realGetGameForPID = RunningGameStore.getGameForPID;
RunningGameStore.getRunningGames = () => fakeGames;
RunningGameStore.getGameForPID = (pid) => fakeGames.find(x => x.pid === pid);
FluxDispatcher.dispatch({ type: "RUNNING_GAMES_CHANGE", removed: realGames, added: [fakeGame], games: fakeGames });
let fn = data => {
let progress = quest.config.configVersion === 1
? data.userStatus.streamProgressSeconds
: Math.floor(data.userStatus.progress.PLAY_ON_DESKTOP.value);
log(`Quest progress [${appData.name}]: ${progress}/${secondsNeeded}s`, "progress");
if (progress >= secondsNeeded) {
log(`Quest "${questName}" completed! Claim your reward in the Quests tab.`, "success");
RunningGameStore.getRunningGames = realGetRunningGames;
RunningGameStore.getGameForPID = realGetGameForPID;
FluxDispatcher.dispatch({ type: "RUNNING_GAMES_CHANGE", removed: [fakeGame], added: [], games: [] });
FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
doJob();
}
};
FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
log(`Spoofed your game to "${appData.name}". Wait for ~${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes...`, "info");
})
.catch(err => {
log(`Error fetching app details: ${err.message || err}`, "error");
doJob();
});
}
// 3. STREAM ON DESKTOP
else if (taskName === "STREAM_ON_DESKTOP") {
if (!isApp || !ApplicationStreamingStore) {
log(`This quest (${questName}) requires the Discord Desktop App. Use the desktop app to complete it!`, "error");
doJob();
return;
}
let realFunc = ApplicationStreamingStore.getStreamerActiveStreamMetadata;
ApplicationStreamingStore.getStreamerActiveStreamMetadata = () => ({
id: applicationId,
pid,
sourceName: null
});
let fn = data => {
let progress = quest.config.configVersion === 1
? data.userStatus.streamProgressSeconds
: Math.floor(data.userStatus.progress.STREAM_ON_DESKTOP.value);
log(`Quest progress (Stream): ${progress}/${secondsNeeded}s`, "progress");
if (progress >= secondsNeeded) {
log(`Quest "${questName}" completed! Claim your reward in the Quests tab.`, "success");
ApplicationStreamingStore.getStreamerActiveStreamMetadata = realFunc;
FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
doJob();
}
};
FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
log(`Spoofed your stream to the target game. Stream any window in a voice channel (with at least 1 other person) for ~${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`, "info");
}
// 4. PLAY ACTIVITY
else if (taskName === "PLAY_ACTIVITY") {
const channelId = ChannelStore?.getSortedPrivateChannels?.()?.[0]?.id
?? Object.values(GuildChannelStore?.getAllGuilds?.() || {}).find(x => x != null && x.VOCAL?.length > 0)?.VOCAL[0]?.channel?.id;
if (!channelId) {
log(`Could not find an accessible channel for activity quest "${questName}".`, "error");
doJob();
return;
}
const streamKey = `call:${channelId}:1`;
let fn = async () => {
log(`Completing quest "${questName}"...`, "info");
while (true) {
try {
const res = await api.post({ url: `/quests/${quest.id}/heartbeat`, body: { stream_key: streamKey, terminal: false } });
const progress = res.body?.progress?.PLAY_ACTIVITY?.value ?? 0;
log(`Quest progress: ${progress}/${secondsNeeded}s`, "progress");
if (progress >= secondsNeeded) {
await api.post({ url: `/quests/${quest.id}/heartbeat`, body: { stream_key: streamKey, terminal: true } });
break;
}
} catch (e) {
log(`Activity heartbeat failed, retrying...`, "warn");
}
await new Promise(resolve => setTimeout(resolve, 20 * 1000));
}
log(`Quest "${questName}" completed! Claim your reward in the Quests tab.`, "success");
doJob();
};
fn();
}
};
doJob();
})();Q: Running the script outputs undefined and breaks sending messages.
A: This is a known Discord DevTools bug where internal HTTP connections temporarily freeze. Simply wait 2 to 3 minutes or restart Discord.
Q: The script outputs: "You don't have any uncompleted quests!" or warns about unaccepted quests.
A: Make sure you clicked "Accept Quest" in your Discord Quests / Gift Inventory tab before pasting the script.
Q: Ctrl + Shift + I takes a screenshot instead of opening DevTools.
A: A background application (like AMD Radeon Software or GeForce Experience) has bound that shortcut. Disable or remap the screenshot hotkey in their settings.
Q: Why doesn't this work on Vesktop or Web Browser for game quests?
A: Non-video quests require Discord's native C++ client telemetry (
DiscordNative) to send verified heartbeats. Use the official Discord Desktop App or PTB Client.
Q: Why doesn't the script auto-accept quests or auto-claim rewards?
A: Accepting and claiming rewards can trigger automated Cloudflare/hCaptcha prompts. Handling those two clicks manually keeps your account safe from detection.
Q: Can you make this into a Vencord or BetterDiscord plugin?
A: Discord patches their quest architecture frequently. Plugin store approval cycles are too slow, making this direct console snippet the fastest and most reliable method.
- Original Author & Gist: aamiaa/CompleteRecentDiscordQuest
- License: Licensed under the GNU General Public License v3.0 (GPL-3.0).
VM1540:142 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'id')
at processQuests (:142:56)