/* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { showNotification } from "@api/Notifications"; import { Devs, EquicordDevs } from "@utils/constants"; import { getTheme, Theme } from "@utils/discord"; import definePlugin from "@utils/types"; import { findByCode, findByProps } from "@webpack"; import { FluxDispatcher, Forms, RestAPI, Text, UserStore } from "@webpack/common"; export default definePlugin({ name: "QuestCompleter", description: "A plugin to complete quests without having the game installed.", authors: [Devs.HappyEnderman, EquicordDevs.SerStars, EquicordDevs.thororen], patches: [ { find: "\"invite-button\"", replacement: { match: /(function .+?\(.+?\){let{inPopout:.+allowIdle.+?}=.+?\.\i\)\("popup"\),(.+?)=\[\];if\(.+?\){.+"chat-spacer"\)\)\),\(\d,.+?\.jsx\)\(.+?,{children:).+?}}/, replace: "$1[$self.renderQuestButton(),...$2]})}}" } } ], settingsAboutComponent() { const isDesktop = navigator.userAgent.includes("discord/"); return (<> { isDesktop ? The plugin should work properly because you are on the Desktop Client. : This plugin won't work because you are not on the Desktop Client. } ); }, start() { const currentUserId: string = UserStore.getCurrentUser().id; window.currentUserId = currentUserId; // this is here because discord will lag if we get the current user id every time }, renderQuestButton() { const ToolTipButton = findByCode("}),color:\"currentColor\"})})}}"); const QuestsIcon = () => props => ( ); return ( <> ); }, async openCompleteQuestUI() { // check if user is sharing screen and there is someone that is watching the stream const ApplicationStreamingStore = findByProps("getStreamerActiveStreamMetadata"); const RunningGameStore = findByProps("getRunningGames"); const ExperimentStore = findByProps("getGuildExperiments"); const QuestsStore = findByProps("getQuest"); const quest = [...QuestsStore.quests.values()].find(quest => quest.userStatus?.enrolledAt && !quest?.userStatus?.completedAt && new Date(quest?.config?.expiresAt) >= new Date()); const isApp = navigator.userAgent.includes("Electron/"); if (!isApp) { showNotification({ title: "Quests Completer", body: "This no longer works in browser. Use the desktop app!", }); } else if (!quest) { showNotification({ title: "Quests Completer", body: "No Quests To Complete", }); } else { const pid = Math.floor(Math.random() * 30000) + 1000; const theme = getTheme() === Theme.Light ? "light" : "dark"; let applicationId, applicationName, secondsNeeded, secondsDone, canPlay, icon, questId; if (quest.config.configVersion === 1) { questId = quest.id; applicationId = quest.config.applicationId; applicationName = quest.config.applicationName; secondsNeeded = quest.config.streamDurationRequirementMinutes * 60; secondsDone = quest.userStatus?.streamProgressSeconds ?? 0; icon = `https://cdn.discordapp.com/assets/quests/${questId}/${theme}/${quest.config.assets.gameTile}`; canPlay = quest.config.variants.includes(2); } else if (quest.config.configVersion === 2) { questId = quest.id; applicationId = quest.config.application.id; applicationName = quest.config.application.name; icon = `https://cdn.discordapp.com/assets/quests/${questId}/${theme}/${quest.config.assets.gameTile}`; canPlay = ExperimentStore.getUserExperimentBucket("2024-04_quest_playtime_task") > 0 && quest.config.taskConfig.tasks.PLAY_ON_DESKTOP; const taskName = canPlay ? "PLAY_ON_DESKTOP" : "STREAM_ON_DESKTOP"; secondsNeeded = quest.config.taskConfig.tasks[taskName]?.target; secondsDone = quest.userStatus?.progress?.[taskName]?.value ?? 0; } if (canPlay) { await RestAPI.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 games = RunningGameStore.getRunningGames(); 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(), }; games.push(fakeGame); FluxDispatcher.dispatch({ type: "RUNNING_GAMES_CHANGE", removed: [], added: [fakeGame], games: games }); const fn = data => { const progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.PLAY_ON_DESKTOP.value); showNotification({ title: `${applicationName} - Quests Completer`, body: `Current progress: ${progress}/${secondsNeeded} minutes.`, icon: icon, }); if (progress >= secondsNeeded) { showNotification({ title: `${applicationName} - Quests Completer`, body: "Quest Completed", icon: icon, }); const idx = games.indexOf(fakeGame); if (idx > -1) { games.splice(idx, 1); FluxDispatcher.dispatch({ type: "RUNNING_GAMES_CHANGE", removed: [fakeGame], added: [], games: [] }); } FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn); } }; FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn); }); } else { const realFunc = ApplicationStreamingStore.getStreamerActiveStreamMetadata; ApplicationStreamingStore.getStreamerActiveStreamMetadata = () => ({ id: applicationId, pid, sourceName: null }); const fn = data => { const progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.STREAM_ON_DESKTOP.value); showNotification({ title: `${applicationName} - Quests Completer`, body: `Current progress: ${progress}/${secondsNeeded} minutes.`, icon: icon, }); if (progress >= secondsNeeded) { showNotification({ title: `${applicationName} - Quests Completer`, body: "Quest Completed", icon: icon, }); ApplicationStreamingStore.getStreamerActiveStreamMetadata = realFunc; FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn); } }; FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn); } return; } } });