csmith1865 revised this gist 2 months ago. Go to revision
1 file changed, 91 insertions, 80 deletions
CompleteDiscordQuest.md
| @@ -16,19 +16,19 @@ How to use this script: | |||
| 16 | 16 | ||
| 17 | 17 | ```js | |
| 18 | 18 | delete window.$; | |
| 19 | - | let wpRequire; | |
| 20 | - | window.webpackChunkdiscord_app.push([[ Math.random() ], {}, (req) => { wpRequire = req; }]); | |
| 19 | + | let wpRequire = webpackChunkdiscord_app.push([[Symbol()], {}, r => r]); | |
| 20 | + | webpackChunkdiscord_app.pop(); | |
| 21 | 21 | ||
| 22 | - | let ApplicationStreamingStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.getStreamerActiveStreamMetadata).exports.Z; | |
| 22 | + | let ApplicationStreamingStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getStreamerActiveStreamMetadata).exports.Z; | |
| 23 | 23 | let RunningGameStore = Object.values(wpRequire.c).find(x => x?.exports?.ZP?.getRunningGames).exports.ZP; | |
| 24 | - | let QuestsStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.getQuest).exports.Z; | |
| 25 | - | let ChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.getAllThreadsForParent).exports.Z; | |
| 24 | + | let QuestsStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getQuest).exports.Z; | |
| 25 | + | let ChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getAllThreadsForParent).exports.Z; | |
| 26 | 26 | let GuildChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.ZP?.getSFWDefaultChannel).exports.ZP; | |
| 27 | - | let FluxDispatcher = Object.values(wpRequire.c).find(x => x?.exports?.Z?.flushWaitQueue).exports.Z; | |
| 27 | + | let FluxDispatcher = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.flushWaitQueue).exports.Z; | |
| 28 | 28 | let api = Object.values(wpRequire.c).find(x => x?.exports?.tn?.get).exports.tn; | |
| 29 | 29 | ||
| 30 | 30 | let quest = [...QuestsStore.quests.values()].find(x => x.id !== "1248385850622869556" && x.userStatus?.enrolledAt && !x.userStatus?.completedAt && new Date(x.config.expiresAt).getTime() > Date.now()) | |
| 31 | - | let isApp = navigator.userAgent.includes("Electron/") | |
| 31 | + | let isApp = typeof DiscordNative !== "undefined" | |
| 32 | 32 | if(!quest) { | |
| 33 | 33 | console.log("You don't have any uncompleted quests!") | |
| 34 | 34 | } else { | |
| @@ -36,108 +36,119 @@ if(!quest) { | |||
| 36 | 36 | ||
| 37 | 37 | const applicationId = quest.config.application.id | |
| 38 | 38 | const applicationName = quest.config.application.name | |
| 39 | - | const taskName = ["WATCH_VIDEO", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY"].find(x => quest.config.taskConfig.tasks[x] != null) | |
| 40 | - | const secondsNeeded = quest.config.taskConfig.tasks[taskName].target | |
| 41 | - | const secondsDone = quest.userStatus?.progress?.[taskName]?.value ?? 0 | |
| 42 | - | ||
| 43 | - | if(taskName === "WATCH_VIDEO") { | |
| 44 | - | const tolerance = 2, speed = 10 | |
| 45 | - | const diff = Math.floor((Date.now() - new Date(quest.userStatus.enrolledAt).getTime())/1000) | |
| 46 | - | const startingPoint = Math.min(Math.max(Math.ceil(secondsDone), diff), secondsNeeded) | |
| 47 | - | let fn = async () => { | |
| 48 | - | for(let i=startingPoint;i<=secondsNeeded;i+=speed) { | |
| 49 | - | try { | |
| 50 | - | await api.post({url: `/quests/${quest.id}/video-progress`, body: {timestamp: Math.min(secondsNeeded, i + Math.random())}}) | |
| 51 | - | } catch(ex) { | |
| 52 | - | console.log("Failed to send increment of", i, ex.message) | |
| 39 | + | const questName = quest.config.messages.questName | |
| 40 | + | const taskConfig = quest.config.taskConfig ?? quest.config.taskConfigV2 | |
| 41 | + | const taskName = ["WATCH_VIDEO", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY", "WATCH_VIDEO_ON_MOBILE"].find(x => taskConfig.tasks[x] != null) | |
| 42 | + | const secondsNeeded = taskConfig.tasks[taskName].target | |
| 43 | + | let secondsDone = quest.userStatus?.progress?.[taskName]?.value ?? 0 | |
| 44 | + | ||
| 45 | + | if(taskName === "WATCH_VIDEO" || taskName === "WATCH_VIDEO_ON_MOBILE") { | |
| 46 | + | const maxFuture = 10, speed = 7, interval = 1 | |
| 47 | + | const enrolledAt = new Date(quest.userStatus.enrolledAt).getTime() | |
| 48 | + | let completed = false | |
| 49 | + | let fn = async () => { | |
| 50 | + | while(true) { | |
| 51 | + | const maxAllowed = Math.floor((Date.now() - enrolledAt)/1000) + maxFuture | |
| 52 | + | const diff = maxAllowed - secondsDone | |
| 53 | + | const timestamp = secondsDone + speed | |
| 54 | + | if(diff >= speed) { | |
| 55 | + | const res = await api.post({url: `/quests/${quest.id}/video-progress`, body: {timestamp: Math.min(secondsNeeded, timestamp + Math.random())}}) | |
| 56 | + | completed = res.body.completed_at != null | |
| 57 | + | secondsDone = Math.min(secondsNeeded, timestamp) | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | if(timestamp >= secondsNeeded) { | |
| 61 | + | break | |
| 53 | 62 | } | |
| 54 | - | await new Promise(resolve => setTimeout(resolve, tolerance * 1000)) | |
| 63 | + | await new Promise(resolve => setTimeout(resolve, interval * 1000)) | |
| 55 | 64 | } | |
| 56 | - | if((secondsNeeded-secondsDone)%speed !== 0) { | |
| 65 | + | if(!completed) { | |
| 57 | 66 | await api.post({url: `/quests/${quest.id}/video-progress`, body: {timestamp: secondsNeeded}}) | |
| 58 | 67 | } | |
| 59 | 68 | console.log("Quest completed!") | |
| 60 | 69 | } | |
| 61 | 70 | fn() | |
| 62 | - | console.log(`Spoofing video for ${applicationName}. Wait for ${Math.ceil((secondsNeeded - startingPoint)/speed*tolerance)} more seconds.`) | |
| 71 | + | console.log(`Spoofing video for ${questName}.`) | |
| 63 | 72 | } else if(taskName === "PLAY_ON_DESKTOP") { | |
| 64 | 73 | if(!isApp) { | |
| 65 | - | console.log("This no longer works in browser for non-video quests. Use the desktop app to complete the", applicationName, "quest!") | |
| 74 | + | console.log("This no longer works in browser for non-video quests. Use the discord desktop app to complete the", questName, "quest!") | |
| 75 | + | } else { | |
| 76 | + | api.get({url: `/applications/public?application_ids=${applicationId}`}).then(res => { | |
| 77 | + | const appData = res.body[0] | |
| 78 | + | const exeName = appData.executables.find(x => x.os === "win32").name.replace(">","") | |
| 79 | + | ||
| 80 | + | const fakeGame = { | |
| 81 | + | cmdLine: `C:\\Program Files\\${appData.name}\\${exeName}`, | |
| 82 | + | exeName, | |
| 83 | + | exePath: `c:/program files/${appData.name.toLowerCase()}/${exeName}`, | |
| 84 | + | hidden: false, | |
| 85 | + | isLauncher: false, | |
| 86 | + | id: applicationId, | |
| 87 | + | name: appData.name, | |
| 88 | + | pid: pid, | |
| 89 | + | pidPath: [pid], | |
| 90 | + | processName: appData.name, | |
| 91 | + | start: Date.now(), | |
| 92 | + | } | |
| 93 | + | const realGames = RunningGameStore.getRunningGames() | |
| 94 | + | const fakeGames = [fakeGame] | |
| 95 | + | const realGetRunningGames = RunningGameStore.getRunningGames | |
| 96 | + | const realGetGameForPID = RunningGameStore.getGameForPID | |
| 97 | + | RunningGameStore.getRunningGames = () => fakeGames | |
| 98 | + | RunningGameStore.getGameForPID = (pid) => fakeGames.find(x => x.pid === pid) | |
| 99 | + | FluxDispatcher.dispatch({type: "RUNNING_GAMES_CHANGE", removed: realGames, added: [fakeGame], games: fakeGames}) | |
| 100 | + | ||
| 101 | + | let fn = data => { | |
| 102 | + | let progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.PLAY_ON_DESKTOP.value) | |
| 103 | + | console.log(`Quest progress: ${progress}/${secondsNeeded}`) | |
| 104 | + | ||
| 105 | + | if(progress >= secondsNeeded) { | |
| 106 | + | console.log("Quest completed!") | |
| 107 | + | ||
| 108 | + | RunningGameStore.getRunningGames = realGetRunningGames | |
| 109 | + | RunningGameStore.getGameForPID = realGetGameForPID | |
| 110 | + | FluxDispatcher.dispatch({type: "RUNNING_GAMES_CHANGE", removed: [fakeGame], added: [], games: []}) | |
| 111 | + | FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn) | |
| 112 | + | } | |
| 113 | + | } | |
| 114 | + | FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn) | |
| 115 | + | ||
| 116 | + | console.log(`Spoofed your game to ${applicationName}. Wait for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`) | |
| 117 | + | }) | |
| 66 | 118 | } | |
| 67 | - | ||
| 68 | - | api.get({url: `/applications/public?application_ids=${applicationId}`}).then(res => { | |
| 69 | - | const appData = res.body[0] | |
| 70 | - | const exeName = appData.executables.find(x => x.os === "win32").name.replace(">","") | |
| 71 | - | ||
| 72 | - | const games = RunningGameStore.getRunningGames() | |
| 73 | - | const fakeGame = { | |
| 74 | - | cmdLine: `C:\\Program Files\\${appData.name}\\${exeName}`, | |
| 75 | - | exeName, | |
| 76 | - | exePath: `c:/program files/${appData.name.toLowerCase()}/${exeName}`, | |
| 77 | - | hidden: false, | |
| 78 | - | isLauncher: false, | |
| 119 | + | } else if(taskName === "STREAM_ON_DESKTOP") { | |
| 120 | + | if(!isApp) { | |
| 121 | + | console.log("This no longer works in browser for non-video quests. Use the discord desktop app to complete the", questName, "quest!") | |
| 122 | + | } else { | |
| 123 | + | let realFunc = ApplicationStreamingStore.getStreamerActiveStreamMetadata | |
| 124 | + | ApplicationStreamingStore.getStreamerActiveStreamMetadata = () => ({ | |
| 79 | 125 | id: applicationId, | |
| 80 | - | name: appData.name, | |
| 81 | - | pid: pid, | |
| 82 | - | pidPath: [pid], | |
| 83 | - | processName: appData.name, | |
| 84 | - | start: Date.now(), | |
| 85 | - | } | |
| 86 | - | games.push(fakeGame) | |
| 87 | - | FluxDispatcher.dispatch({type: "RUNNING_GAMES_CHANGE", removed: [], added: [fakeGame], games: games}) | |
| 126 | + | pid, | |
| 127 | + | sourceName: null | |
| 128 | + | }) | |
| 88 | 129 | ||
| 89 | 130 | let fn = data => { | |
| 90 | - | let progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.PLAY_ON_DESKTOP.value) | |
| 131 | + | let progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.STREAM_ON_DESKTOP.value) | |
| 91 | 132 | console.log(`Quest progress: ${progress}/${secondsNeeded}`) | |
| 92 | 133 | ||
| 93 | 134 | if(progress >= secondsNeeded) { | |
| 94 | 135 | console.log("Quest completed!") | |
| 95 | 136 | ||
| 96 | - | const idx = games.indexOf(fakeGame) | |
| 97 | - | if(idx > -1) { | |
| 98 | - | games.splice(idx, 1) | |
| 99 | - | FluxDispatcher.dispatch({type: "RUNNING_GAMES_CHANGE", removed: [fakeGame], added: [], games: []}) | |
| 100 | - | } | |
| 137 | + | ApplicationStreamingStore.getStreamerActiveStreamMetadata = realFunc | |
| 101 | 138 | FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn) | |
| 102 | 139 | } | |
| 103 | 140 | } | |
| 104 | 141 | FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn) | |
| 105 | 142 | ||
| 106 | - | console.log(`Spoofed your game to ${applicationName}. Wait for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`) | |
| 107 | - | }) | |
| 108 | - | } else if(taskName === "STREAM_ON_DESKTOP") { | |
| 109 | - | if(!isApp) { | |
| 110 | - | console.log("This no longer works in browser for non-video quests. Use the desktop app to complete the", applicationName, "quest!") | |
| 111 | - | } | |
| 112 | - | ||
| 113 | - | let realFunc = ApplicationStreamingStore.getStreamerActiveStreamMetadata | |
| 114 | - | ApplicationStreamingStore.getStreamerActiveStreamMetadata = () => ({ | |
| 115 | - | id: applicationId, | |
| 116 | - | pid, | |
| 117 | - | sourceName: null | |
| 118 | - | }) | |
| 119 | - | ||
| 120 | - | let fn = data => { | |
| 121 | - | let progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.STREAM_ON_DESKTOP.value) | |
| 122 | - | console.log(`Quest progress: ${progress}/${secondsNeeded}`) | |
| 123 | - | ||
| 124 | - | if(progress >= secondsNeeded) { | |
| 125 | - | console.log("Quest completed!") | |
| 126 | - | ||
| 127 | - | ApplicationStreamingStore.getStreamerActiveStreamMetadata = realFunc | |
| 128 | - | FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn) | |
| 129 | - | } | |
| 143 | + | console.log(`Spoofed your stream to ${applicationName}. Stream any window in vc for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`) | |
| 144 | + | console.log("Remember that you need at least 1 other person to be in the vc!") | |
| 130 | 145 | } | |
| 131 | - | FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn) | |
| 132 | - | ||
| 133 | - | console.log(`Spoofed your stream to ${applicationName}. Stream any window in vc for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`) | |
| 134 | - | console.log("Remember that you need at least 1 other person to be in the vc!") | |
| 135 | 146 | } else if(taskName === "PLAY_ACTIVITY") { | |
| 136 | 147 | const channelId = ChannelStore.getSortedPrivateChannels()[0]?.id ?? Object.values(GuildChannelStore.getAllGuilds()).find(x => x != null && x.VOCAL.length > 0).VOCAL[0].channel.id | |
| 137 | 148 | const streamKey = `call:${channelId}:1` | |
| 138 | 149 | ||
| 139 | 150 | let fn = async () => { | |
| 140 | - | console.log("Completing quest", applicationName, "-", quest.config.messages.questName) | |
| 151 | + | console.log("Completing quest", questName, "-", quest.config.messages.questName) | |
| 141 | 152 | ||
| 142 | 153 | while(true) { | |
| 143 | 154 | const res = await api.post({url: `/quests/${quest.id}/heartbeat`, body: {stream_key: streamKey, terminal: false}}) | |
csmith1865 revised this gist 10 months ago. Go to revision
1 file changed, 206 insertions
CompleteDiscordQuest.md(file created)
| @@ -0,0 +1,206 @@ | |||
| 1 | + | ## Complete Recent Discord Quest | |
| 2 | + | ||
| 3 | + | > [!NOTE] | |
| 4 | + | > This does not works in browser for non-video, non-activity quests! For stream/play quests use the desktop app! | |
| 5 | + | ||
| 6 | + | > [!NOTE] | |
| 7 | + | > When doing stream quests, you need at least 1 other account in the vc! | |
| 8 | + | ||
| 9 | + | How to use this script: | |
| 10 | + | 1. Accept a quest under User Settings -> Gift Inventory | |
| 11 | + | 2. Press <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>I</kbd> to open DevTools | |
| 12 | + | 3. Go to the `Console` tab | |
| 13 | + | 4. Paste the following code and hit enter: | |
| 14 | + | <details> | |
| 15 | + | <summary>Click to expand</summary> | |
| 16 | + | ||
| 17 | + | ```js | |
| 18 | + | delete window.$; | |
| 19 | + | let wpRequire; | |
| 20 | + | window.webpackChunkdiscord_app.push([[ Math.random() ], {}, (req) => { wpRequire = req; }]); | |
| 21 | + | ||
| 22 | + | let ApplicationStreamingStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.getStreamerActiveStreamMetadata).exports.Z; | |
| 23 | + | let RunningGameStore = Object.values(wpRequire.c).find(x => x?.exports?.ZP?.getRunningGames).exports.ZP; | |
| 24 | + | let QuestsStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.getQuest).exports.Z; | |
| 25 | + | let ChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.getAllThreadsForParent).exports.Z; | |
| 26 | + | let GuildChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.ZP?.getSFWDefaultChannel).exports.ZP; | |
| 27 | + | let FluxDispatcher = Object.values(wpRequire.c).find(x => x?.exports?.Z?.flushWaitQueue).exports.Z; | |
| 28 | + | let api = Object.values(wpRequire.c).find(x => x?.exports?.tn?.get).exports.tn; | |
| 29 | + | ||
| 30 | + | let quest = [...QuestsStore.quests.values()].find(x => x.id !== "1248385850622869556" && x.userStatus?.enrolledAt && !x.userStatus?.completedAt && new Date(x.config.expiresAt).getTime() > Date.now()) | |
| 31 | + | let isApp = navigator.userAgent.includes("Electron/") | |
| 32 | + | if(!quest) { | |
| 33 | + | console.log("You don't have any uncompleted quests!") | |
| 34 | + | } else { | |
| 35 | + | const pid = Math.floor(Math.random() * 30000) + 1000 | |
| 36 | + | ||
| 37 | + | const applicationId = quest.config.application.id | |
| 38 | + | const applicationName = quest.config.application.name | |
| 39 | + | const taskName = ["WATCH_VIDEO", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY"].find(x => quest.config.taskConfig.tasks[x] != null) | |
| 40 | + | const secondsNeeded = quest.config.taskConfig.tasks[taskName].target | |
| 41 | + | const secondsDone = quest.userStatus?.progress?.[taskName]?.value ?? 0 | |
| 42 | + | ||
| 43 | + | if(taskName === "WATCH_VIDEO") { | |
| 44 | + | const tolerance = 2, speed = 10 | |
| 45 | + | const diff = Math.floor((Date.now() - new Date(quest.userStatus.enrolledAt).getTime())/1000) | |
| 46 | + | const startingPoint = Math.min(Math.max(Math.ceil(secondsDone), diff), secondsNeeded) | |
| 47 | + | let fn = async () => { | |
| 48 | + | for(let i=startingPoint;i<=secondsNeeded;i+=speed) { | |
| 49 | + | try { | |
| 50 | + | await api.post({url: `/quests/${quest.id}/video-progress`, body: {timestamp: Math.min(secondsNeeded, i + Math.random())}}) | |
| 51 | + | } catch(ex) { | |
| 52 | + | console.log("Failed to send increment of", i, ex.message) | |
| 53 | + | } | |
| 54 | + | await new Promise(resolve => setTimeout(resolve, tolerance * 1000)) | |
| 55 | + | } | |
| 56 | + | if((secondsNeeded-secondsDone)%speed !== 0) { | |
| 57 | + | await api.post({url: `/quests/${quest.id}/video-progress`, body: {timestamp: secondsNeeded}}) | |
| 58 | + | } | |
| 59 | + | console.log("Quest completed!") | |
| 60 | + | } | |
| 61 | + | fn() | |
| 62 | + | console.log(`Spoofing video for ${applicationName}. Wait for ${Math.ceil((secondsNeeded - startingPoint)/speed*tolerance)} more seconds.`) | |
| 63 | + | } else if(taskName === "PLAY_ON_DESKTOP") { | |
| 64 | + | if(!isApp) { | |
| 65 | + | console.log("This no longer works in browser for non-video quests. Use the desktop app to complete the", applicationName, "quest!") | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | api.get({url: `/applications/public?application_ids=${applicationId}`}).then(res => { | |
| 69 | + | const appData = res.body[0] | |
| 70 | + | const exeName = appData.executables.find(x => x.os === "win32").name.replace(">","") | |
| 71 | + | ||
| 72 | + | const games = RunningGameStore.getRunningGames() | |
| 73 | + | const fakeGame = { | |
| 74 | + | cmdLine: `C:\\Program Files\\${appData.name}\\${exeName}`, | |
| 75 | + | exeName, | |
| 76 | + | exePath: `c:/program files/${appData.name.toLowerCase()}/${exeName}`, | |
| 77 | + | hidden: false, | |
| 78 | + | isLauncher: false, | |
| 79 | + | id: applicationId, | |
| 80 | + | name: appData.name, | |
| 81 | + | pid: pid, | |
| 82 | + | pidPath: [pid], | |
| 83 | + | processName: appData.name, | |
| 84 | + | start: Date.now(), | |
| 85 | + | } | |
| 86 | + | games.push(fakeGame) | |
| 87 | + | FluxDispatcher.dispatch({type: "RUNNING_GAMES_CHANGE", removed: [], added: [fakeGame], games: games}) | |
| 88 | + | ||
| 89 | + | let fn = data => { | |
| 90 | + | let progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.PLAY_ON_DESKTOP.value) | |
| 91 | + | console.log(`Quest progress: ${progress}/${secondsNeeded}`) | |
| 92 | + | ||
| 93 | + | if(progress >= secondsNeeded) { | |
| 94 | + | console.log("Quest completed!") | |
| 95 | + | ||
| 96 | + | const idx = games.indexOf(fakeGame) | |
| 97 | + | if(idx > -1) { | |
| 98 | + | games.splice(idx, 1) | |
| 99 | + | FluxDispatcher.dispatch({type: "RUNNING_GAMES_CHANGE", removed: [fakeGame], added: [], games: []}) | |
| 100 | + | } | |
| 101 | + | FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn) | |
| 102 | + | } | |
| 103 | + | } | |
| 104 | + | FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn) | |
| 105 | + | ||
| 106 | + | console.log(`Spoofed your game to ${applicationName}. Wait for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`) | |
| 107 | + | }) | |
| 108 | + | } else if(taskName === "STREAM_ON_DESKTOP") { | |
| 109 | + | if(!isApp) { | |
| 110 | + | console.log("This no longer works in browser for non-video quests. Use the desktop app to complete the", applicationName, "quest!") | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | let realFunc = ApplicationStreamingStore.getStreamerActiveStreamMetadata | |
| 114 | + | ApplicationStreamingStore.getStreamerActiveStreamMetadata = () => ({ | |
| 115 | + | id: applicationId, | |
| 116 | + | pid, | |
| 117 | + | sourceName: null | |
| 118 | + | }) | |
| 119 | + | ||
| 120 | + | let fn = data => { | |
| 121 | + | let progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.STREAM_ON_DESKTOP.value) | |
| 122 | + | console.log(`Quest progress: ${progress}/${secondsNeeded}`) | |
| 123 | + | ||
| 124 | + | if(progress >= secondsNeeded) { | |
| 125 | + | console.log("Quest completed!") | |
| 126 | + | ||
| 127 | + | ApplicationStreamingStore.getStreamerActiveStreamMetadata = realFunc | |
| 128 | + | FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn) | |
| 129 | + | } | |
| 130 | + | } | |
| 131 | + | FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn) | |
| 132 | + | ||
| 133 | + | console.log(`Spoofed your stream to ${applicationName}. Stream any window in vc for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`) | |
| 134 | + | console.log("Remember that you need at least 1 other person to be in the vc!") | |
| 135 | + | } else if(taskName === "PLAY_ACTIVITY") { | |
| 136 | + | const channelId = ChannelStore.getSortedPrivateChannels()[0]?.id ?? Object.values(GuildChannelStore.getAllGuilds()).find(x => x != null && x.VOCAL.length > 0).VOCAL[0].channel.id | |
| 137 | + | const streamKey = `call:${channelId}:1` | |
| 138 | + | ||
| 139 | + | let fn = async () => { | |
| 140 | + | console.log("Completing quest", applicationName, "-", quest.config.messages.questName) | |
| 141 | + | ||
| 142 | + | while(true) { | |
| 143 | + | const res = await api.post({url: `/quests/${quest.id}/heartbeat`, body: {stream_key: streamKey, terminal: false}}) | |
| 144 | + | const progress = res.body.progress.PLAY_ACTIVITY.value | |
| 145 | + | console.log(`Quest progress: ${progress}/${secondsNeeded}`) | |
| 146 | + | ||
| 147 | + | await new Promise(resolve => setTimeout(resolve, 20 * 1000)) | |
| 148 | + | ||
| 149 | + | if(progress >= secondsNeeded) { | |
| 150 | + | await api.post({url: `/quests/${quest.id}/heartbeat`, body: {stream_key: streamKey, terminal: true}}) | |
| 151 | + | break | |
| 152 | + | } | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | console.log("Quest completed!") | |
| 156 | + | } | |
| 157 | + | fn() | |
| 158 | + | } | |
| 159 | + | } | |
| 160 | + | ``` | |
| 161 | + | </details> | |
| 162 | + | ||
| 163 | + | 5. Follow the printed instructions depending on what type of quest you have | |
| 164 | + | - If your quest says to "play" the game, you can just wait and do nothing | |
| 165 | + | - If your quest says to "stream" the game, join a vc with a friend or alt and stream any window | |
| 166 | + | 7. Wait for 15 minutes | |
| 167 | + | 8. You can now claim the reward in User Settings -> Gift Inventory! | |
| 168 | + | ||
| 169 | + | You can track the progress by looking at the `Quest progress:` prints in the Console tab, or by reopening the Gift Inventory tab in settings. | |
| 170 | + | ||
| 171 | + | ## FAQ | |
| 172 | + | ||
| 173 | + | **Q: Ctrl + Shift + I doesn't work** | |
| 174 | + | ||
| 175 | + | A: Either download the [ptb client](https://discord.com/api/downloads/distributions/app/installers/latest?channel=ptb&platform=win&arch=x64), or use [this](https://www.reddit.com/r/discordapp/comments/sc61n3/comment/hu4fw5x/) to enable DevTools on stable | |
| 176 | + | ||
| 177 | + | ||
| 178 | + | **Q: I get an error saying "Unauthorized"** | |
| 179 | + | ||
| 180 | + | A: Discord has patched the script from working in browsers. Use the desktop app, or alternatively find some extension which lets you change your User-Agent and append the string `Electron/` anywhere in it. | |
| 181 | + | ||
| 182 | + | They have also started checking how many people are in the vc, so make sure you join it on at least 1 other account. | |
| 183 | + | ||
| 184 | + | ||
| 185 | + | **Q: I get a syntax error/unexpected token error** | |
| 186 | + | ||
| 187 | + | A: Make sure your browser isn't auto-translating this website before copying the script. Turn off any translator extensions and try again. | |
| 188 | + | ||
| 189 | + | ||
| 190 | + | **Q: I get the error `Cannot read properties of undefined (reading 'nativeModules')`** | |
| 191 | + | ||
| 192 | + | A: You're on a modded client which is a browser wrapper (i.e. Vesktop). Use an actual desktop app. | |
| 193 | + | ||
| 194 | + | ||
| 195 | + | **Q: I get a different error** | |
| 196 | + | ||
| 197 | + | A: Make sure you're copy/pasting the script correctly and that you've have done all the steps. | |
| 198 | + | ||
| 199 | + | ||
| 200 | + | **Q: Can you make the script auto accept the quest/reward?** | |
| 201 | + | ||
| 202 | + | A: No. Both of those actions may show a captcha, so automating them is not a good idea. Just do the two clicks yourself. | |
| 203 | + | ||
| 204 | + | ___ | |
| 205 | + | ||
| 206 | + | <sup>Side note: Please don't post your "fixed" or "improved" versions of the script in the comments. It creates unnecessary confusion, often doesn't fix anything, and sometimes puts other people's accounts at risk. I might redact/delete such comments without notice. Thank you for understanding.</sup> | |