diff --git a/README.md b/README.md index 649d4e31..c2dca9fd 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,10 @@ Visit [1_INSTALLING.md](/docs/1_INSTALLING.md) - - SlashCommandMentionOptions - - TalkInReverse +- Plugins by [happyendermangit](https://github.com/happyendermangit/) +- - CopyEmojiAsFormattedString (added from vishnyanetchereshnya's [pull request](https://github.com/Vendicated/Vencord/pull/2266)) +- - QuestsCompleter (added from vishnyanetchereshnya's [pull request](https://github.com/Vendicated/Vencord/pull/2393)) + - PurgeMessages (by [bhop](https://github.com/prettylittlelies)) - PlatformSpoofer (by [drag](https://github.com/dragdotpng)) - Timezones (by [mantikafasi](https://github.com/mantikafasi) & [ArjixWasTaken](https://github.com/ArjixWasTaken)) diff --git a/src/suncordplugins/questCompleter/index.tsx b/src/suncordplugins/questCompleter/index.tsx new file mode 100644 index 00000000..8d232460 --- /dev/null +++ b/src/suncordplugins/questCompleter/index.tsx @@ -0,0 +1,197 @@ +/* + * 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 } from "@utils/constants"; +import { localStorage } from "@utils/localStorage"; +import definePlugin from "@utils/types"; +import { findByProps } from "@webpack"; +import { Text } from "@webpack/common"; + +interface Stream { + streamType: string; + state: string; + ownerId: string; + guildId: string; + channelId: string; +} + +function getLeftQuests() { + const QuestsStore = findByProps("getQuest"); + // check if user still has incompleted quests + const quest = [...QuestsStore.quests.values()].find(quest => quest.userStatus?.enrolledAt && !quest?.userStatus?.completedAt && new Date(quest?.config?.expiresAt) >= new Date()); + return quest; +} + +let interval; +let quest; +let ImagesConfig = {}; + +export default definePlugin({ + name: "QuestCompleter", + description: "A plugin to complete quests without having the game.", + authors: [Devs.HAPPY_ENDERMAN, Devs.SerStars], + patches: [ + { + find: "\"invite-button\"", + replacement: { + match: /(function .+?\(.+?\){let{inPopout:.+allowIdle.+?}=.+?\.usePreventIdle\)\("popup"\),(.+?)=\[\];if\(.+?\){.+"chat-spacer"\)\)\),\(\d,.+?\.jsx\)\(.+?,{children:).+?}}/, + replace: "$1[$self.renderQuestButton(),...$2]})}}" + } + } + ], + settingsAboutComponent() { + const isDesktop = navigator.userAgent.includes("discord/"); + const hasQuestsExtensionEnabled = localStorage.getItem("QUESTS_EXT_ENABLED"); + + return (<> + { + isDesktop || hasQuestsExtensionEnabled ? + + The plugin should work properly because you {isDesktop ? "are on the Desktop Client." : "installed our extension."} + + : + + This plugin won't work right now. Please download + our extension + to make the plugin work on web. + + } + ); + }, + start() { + const currentUserId: string = findByProps("getCurrentUser").getCurrentUser().id; + window.currentUserId = currentUserId; // this is here because discord will lag if we get the current user id every time + }, + renderQuestButton() { + const currentStream: Stream | null = findByProps("getCurrentUserActiveStream").getCurrentUserActiveStream(); + let shouldDisable = !!interval; + const { Divider } = findByProps("Divider", "Icon"); + + if (!currentStream) { + shouldDisable = true; + } + if (currentStream) { + if (!findByProps("getParticipants").getParticipants(currentStream.channelId).filter(participent => participent.user.id !== window.currentUserId).length) { + shouldDisable = true; + } + if (currentStream?.ownerId !== window.currentUserId) { + shouldDisable = true; + } + } + if (!getLeftQuests()) { + shouldDisable = true; + } + + + + const ToolTipButton = findByProps("CenterControlButton").default; + const QuestsIcon = () => props => ( + + + + + ); + + return ( + <> + + + + + + ); + }, + openCompleteQuestUI() { + // check if user is sharing screen and there is someone that is watching the stream + + const currentStream: Stream | null = findByProps("getCurrentUserActiveStream").getCurrentUserActiveStream(); + const encodedStreamKey = findByProps("encodeStreamKey").encodeStreamKey(currentStream); + quest = getLeftQuests(); + ImagesConfig = { + icon: findByProps("getQuestBarHeroAssetUrl").getQuestBarHeroAssetUrl(quest), + image: findByProps("getHeroAssetUrl").getHeroAssetUrl(quest) + }; + + const heartBeat = async () => { + findByProps("HTTP", "getAPIBaseURL"); // rest api module + findByProps("sendHeartbeat").sendHeartbeat({ questId: quest.id, streamKey: encodedStreamKey }); + }; + + heartBeat(); + interval = setInterval(heartBeat, 60500); // send the heartbeat each minute + + return; + }, + flux: { + STREAM_STOP: event => { + const stream: Stream = findByProps("encodeStreamKey").decodeStreamKey(event.streamKey); + // we check if the stream is by the current user id so we do not clear the interval without any reason. + if (stream.ownerId === window.currentUserId && interval) { + clearInterval(interval); + interval = null; + } + }, + QUESTS_SEND_HEARTBEAT_FAILURE: () => { + showNotification( + { + title: "Couldn't start Completing Quest", + body: "You are probally using web, please check the plugin settings for help.", + ...ImagesConfig + } + ); + clearInterval(interval); + interval = null; + }, + QUESTS_SEND_HEARTBEAT_SUCCESS: event => { + + const a = event.userStatus.streamProgressSeconds * 100; + const b = quest.config.streamDurationRequirementMinutes * 60; + showNotification({ + title: `${quest.config.applicationName} - Quests Completer`, + body: `Current progress: ${Math.floor(a / b)}% (${Math.floor(event.userStatus.streamProgressSeconds / 60)} minutes.)`, + ...ImagesConfig + }); + + if (event.userStatus.streamProgressSeconds === quest.config.streamDurationRequirementMinutes * 60) { + showNotification({ + title: `${quest.config.applicationName} - Quests Completer`, + body: "Quest Completed", + ...ImagesConfig + }); + clearInterval(interval); + interval = null; + } + } + } +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index e621a03d..a63d9e81 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -530,6 +530,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "DaBluLite", id: 582170007505731594n, }, + SerStars: { + name: "SerStars", + id: 861631850681729045n, + }, } satisfies Record); export const SuncordDevs = /* #__PURE__*/ Object.freeze({