Note
This does not works in browser for quests which require you to play a game! Use the desktop app to complete those.
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
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, QuestsStore, ChannelStore, GuildChannelStore, FluxDispatcher, api
if(!ApplicationStreamingStore) {
ApplicationStreamingStore = Object.values(wpRequire.c).find(x => x?.exports?.A?.__proto__?.getStreamerActiveStreamMetadata).exports.A;
RunningGameStore = Object.values(wpRequire.c).find(x => x?.exports?.Ay?.getRunningGames).exports.Ay;
QuestsStore = Object.values(wpRequire.c).find(x => x?.exports?.A?.__proto__?.getQuest).exports.A;
ChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.A?.__proto__?.getAllThreadsForParent).exports.A;
GuildChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.Ay?.getSFWDefaultChannel).exports.Ay;
FluxDispatcher = Object.values(wpRequire.c).find(x => x?.exports?.h?.__proto__?.flushWaitQueue).exports.h;
api = Object.values(wpRequire.c).find(x => x?.exports?.Bo?.get).exports.Bo;
} else {
RunningGameStore = Object.values(wpRequire.c).find(x => x?.exports?.ZP?.getRunningGames).exports.ZP;
QuestsStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getQuest).exports.Z;
ChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getAllThreadsForParent).exports.Z;
GuildChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.ZP?.getSFWDefaultChannel).exports.ZP;
FluxDispatcher = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.flushWaitQueue).exports.Z;
api = Object.values(wpRequire.c).find(x => x?.exports?.tn?.get).exports.tn;
}
const supportedTasks = ["WATCH_VIDEO", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY", "WATCH_VIDEO_ON_MOBILE"]
let quests = [...QuestsStore.quests.values()].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)))
let isApp = typeof DiscordNative !== "undefined"
if(quests.length === 0) {
console.log("You don't have any uncompleted quests!")
} else {
let doJob = function() {
const quest = quests.pop()
if(!quest) return
const pid = Math.floor(Math.random() * 30000) + 1000
const applicationId = quest.config.application.id
const applicationName = quest.config.application.name
const questName = quest.config.messages.questName
const taskConfig = quest.config.taskConfig ?? quest.config.taskConfigV2
const taskName = supportedTasks.find(x => taskConfig.tasks[x] != null)
const secondsNeeded = taskConfig.tasks[taskName].target
let secondsDone = quest.userStatus?.progress?.[taskName]?.value ?? 0
if(taskName === "WATCH_VIDEO" || taskName === "WATCH_VIDEO_ON_MOBILE") {
const maxFuture = 10, speed = 7, interval = 1
const enrolledAt = new Date(quest.userStatus.enrolledAt).getTime()
let completed = false
let fn = async () => {
while(true) {
const maxAllowed = Math.floor((Date.now() - enrolledAt)/1000) + maxFuture
const diff = maxAllowed - secondsDone
const timestamp = secondsDone + speed
if(diff >= speed) {
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
secondsDone = Math.min(secondsNeeded, timestamp)
}
if(timestamp >= secondsNeeded) {
break
}
await new Promise(resolve => setTimeout(resolve, interval * 1000))
}
if(!completed) {
await api.post({url: `/quests/${quest.id}/video-progress`, body: {timestamp: secondsNeeded}})
}
console.log("Quest completed!")
doJob()
}
fn()
console.log(`Spoofing video for ${questName}.`)
} else if(taskName === "PLAY_ON_DESKTOP") {
if(!isApp) {
console.log("This no longer works in browser for non-video quests. Use the discord desktop app to complete the", questName, "quest!")
} else {
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 realGames = 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)
console.log(`Quest progress: ${progress}/${secondsNeeded}`)
if(progress >= secondsNeeded) {
console.log("Quest completed!")
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)
console.log(`Spoofed your game to ${applicationName}. Wait for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`)
})
}
} else if(taskName === "STREAM_ON_DESKTOP") {
if(!isApp) {
console.log("This no longer works in browser for non-video quests. Use the discord desktop app to complete the", questName, "quest!")
} else {
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)
console.log(`Quest progress: ${progress}/${secondsNeeded}`)
if(progress >= secondsNeeded) {
console.log("Quest completed!")
ApplicationStreamingStore.getStreamerActiveStreamMetadata = realFunc
FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn)
doJob()
}
}
FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn)
console.log(`Spoofed your stream to ${applicationName}. Stream any window in vc for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`)
console.log("Remember that you need at least 1 other person to be in the vc!")
}
} 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
const streamKey = `call:${channelId}:1`
let fn = async () => {
console.log("Completing quest", questName, "-", quest.config.messages.questName)
while(true) {
const res = await api.post({url: `/quests/${quest.id}/heartbeat`, body: {stream_key: streamKey, terminal: false}})
const progress = res.body.progress.PLAY_ACTIVITY.value
console.log(`Quest progress: ${progress}/${secondsNeeded}`)
await new Promise(resolve => setTimeout(resolve, 20 * 1000))
if(progress >= secondsNeeded) {
await api.post({url: `/quests/${quest.id}/heartbeat`, body: {stream_key: streamKey, terminal: true}})
break
}
}
console.log("Quest completed!")
doJob()
}
fn()
}
}
doJob()
}(If you're unable to paste into the console, you might have to type allow pasting and hit enter)
- Follow the printed instructions depending on what type of quest you have
- If your quest says to "play" the game or watch a video, you can just wait and do nothing
- If your quest says to "stream" the game, join a vc with a friend or alt and stream any window
- Wait a bit for it to complete the quest
- You can now claim the reward!
You can track the progress by looking at the Quest progress: prints in the Console tab, or by looking at the progress bar in the quests tab.
Q: Running the script does nothing besides printing "undefined", and makes chat messages not go through
A: This is a random bug with opening devtools, something that Chromium/Electron must fix. It's not the script's fault. Restart discord and try again.
Q: Can I get banned for using this?
A: There is always a risk, though so far nobody has been banned for this or other similar things like client mods.
Q: Ctrl + Shift + I doesn't work
A: Either download the ptb client, or use this to enable DevTools on stable.
Q: Ctrl + Shift + I takes a screenshot
A: Disable the keybind in your AMD Radeon app.
Q: I get a syntax error/unexpected token error
A: Make sure your browser isn't auto-translating this website before copying the script. Turn off any translator extensions and try again.
Q: I'm on Vesktop but it tells me that I'm using a browser
A: Vesktop is not a true desktop client, it's a fancy browser wrapper. Download the actual desktop app instead.
Q: I get a different error
A: Make sure you're copy/pasting the script correctly and that you've have done all the steps.
Q: Can I complete expired quests with this?
A: No, there is no way to do that.
Q: Can you make the script auto accept the quest/reward?
A: No. Both of those actions may show a captcha, so automating them is not a good idea. Just do the two clicks yourself.
Q: Can you make this a Vencord plugin?
A: No. The script sometimes requires immediate updates for Discord's changes, and Vencord's update cycle and code review would be too slow for that. There are some Vencord forks which have implemented this script or their own quest completers if you really want one.
Q: Can you upload the standalone script to a repo and make this gist's code a one line fetch()?
A: No. Doing that would put you at risk because I (or someone in my account) could change the underlying code to be malicious at any time, then forcepush it away later, and you'd never know.