diff --git a/README.md b/README.md index 2f3313dc..d6a6e156 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend - AltKrispSwitch by newwares - AmITyping by MrDiamond - Anammox by Kyuuhachi +- DecodeBase64 by ThePirateStoner - BetterActivities by D3SOX, Arjix, AutumnVN - BetterBanReasons by Inbestigator - BetterQuickReact by Ven and Sqaaakoi @@ -37,6 +38,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend - CommandPalette by Ethan - CopyUserMention by Cortex and castdrian - CustomAppIcons by Happy Enderman and SerStars +- CustomSounds by ScattrdBlade - DeadMembers by Kyuuhachi - DiscordColorways by DaBluLite - DNDWhilePlaying by thororen @@ -46,10 +48,13 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend - Encryptcord by Inbestigator - EquicordCSS by FoxStorm1 and thororen (and all respective css developers) - ExportContacts by dat_insanity +- FakeProfile by Sampath - FindReply by newwares - FriendshipRanks by Samwich +- GensokyoRadioRPC by RyanCaoDev and Prince527 - GlobalBadges by HypedDomi and Hosted by Wolfie - GodMode by Tolgchu +- GrammarFix by unstream - HideMessage by Hanzy - HolyNotes by Wolfie - Hop On by ImLvna @@ -62,6 +67,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend - MessageLinkTooltip by Kyuuhachi - MessageLoggerEnhanced by Aria - ModalFade by Kyuuhachi +- NewPluginsManager by Sqaaakoi - noAppsAllowed by kvba - NoModalAnimation by AutumnVN - NoNitroUpsell by thororen @@ -76,6 +82,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend - Quest Completer by HappyEnderman, SerStars, thororen - QuestionMarkReplacement by nyx - RepeatMessage by Tolgchu +- ReplaceActivityTypes by Nyako - ReplyPingControl by ant0n and MrDiamond - ScreenRecorder by AutumnVN - SearchFix by Jaxx @@ -89,6 +96,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend - ThemeLibrary by Fafa - Title by Kyuuhachi - TosuRPC by AutumnVN +- Translate+ by Prince527 (Using Translate by Ven) - UnlimitedAccounts by Balaclava and thororen - UserPFP by nexpid and thororen - VCSupport by thororen diff --git a/src/equicordplugins/baseDecoder/index.tsx b/src/equicordplugins/baseDecoder/index.tsx new file mode 100644 index 00000000..87573f0b --- /dev/null +++ b/src/equicordplugins/baseDecoder/index.tsx @@ -0,0 +1,162 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 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 { addButton, removeButton } from "@api/MessagePopover"; +import { definePluginSettings } from "@api/Settings"; +import { CodeBlock } from "@components/CodeBlock"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Flex } from "@components/Flex"; +import { EquicordDevs } from "@utils/constants"; +import { copyWithToast } from "@utils/misc"; +import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import definePlugin, { OptionType } from "@utils/types"; +import { Button, ChannelStore, Forms, Text } from "@webpack/common"; + +const DecodeIcon = () => { + return ( + + + + + ); +}; + + +function isValidUtf8String(str) { + try { + new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(str.split("").map(char => char.charCodeAt(0)))); + return true; + } catch (e) { + return false; + } +} + +function findBase64Strings(content) { + const base64Regex = /\b[A-Za-z0-9+/]{4,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\b/g; + const matches = content.match(base64Regex); + return matches || []; +} + +function decodeBase64Strings(base64Strings) { + return base64Strings.map(base64 => { + try { + const decoded = atob(base64); + return isValidUtf8String(decoded) ? decoded : null; + } catch (e) { + console.error("Failed to decode base64 content:", e); + return null; + } + }).filter(decoded => decoded !== null); +} + + +function openDecodedBase64Modal(decodedContent) { + const key = openModal(props => ( + + + + Decoded Base64 Content + closeModal(key)} /> + + +
+ Decoded Content + {decodedContent.map((content, index) => ( + + ))} +
+
+ + + {decodedContent.map((content, index) => ( + + ))} + + +
+
+ )); +} + +const settings = definePluginSettings({ + clickMethod: { + description: "Change the button to decode base64 content of any message.", + type: OptionType.SELECT, + options: [ + { label: "Left Click to decode the base64 content.", value: "Left", default: true }, + { label: "Right click to decode the base64 content.", value: "Right" } + ] + } +}); + +export default definePlugin({ + name: "DecodeBase64", + description: "Decode base64 content of any message and copy the decoded content.", + authors: [EquicordDevs.ThePirateStoner], + dependencies: ["MessagePopoverAPI"], + settings, + contextMenus: { + }, + + start() { + addButton("DecodeBase64", msg => { + const handleClick = () => { + const base64Strings = findBase64Strings(msg.content); + const decodedContent = decodeBase64Strings(base64Strings); + if (settings.store.clickMethod === "Right") { + decodedContent.forEach(content => copyWithToast(content)); + } else { + openDecodedBase64Modal(decodedContent); + } + }; + + const handleContextMenu = e => { + const base64Strings = findBase64Strings(msg.content); + const decodedContent = decodeBase64Strings(base64Strings); + if (settings.store.clickMethod === "Left") { + e.preventDefault(); + e.stopPropagation(); + decodedContent.forEach(content => copyWithToast(content)); + } else { + e.preventDefault(); + e.stopPropagation(); + openDecodedBase64Modal(decodedContent); + } + }; + + const label = settings.store.clickMethod === "Right" + ? "Copy Decoded (Left Click) / Decode Base64 (Right Click)" + : "Decode Base64 (Left Click) / Copy Decoded (Right Click)"; + + return { + label, + icon: DecodeIcon, + message: msg, + channel: ChannelStore.getChannel(msg.channel_id), + onClick: handleClick, + onContextMenu: handleContextMenu + }; + }); + }, + + stop() { + removeButton("DecodeBase64"); + } +}); diff --git a/src/equicordplugins/betterMicrophone/components/MicrophoneSettingsModal.tsx b/src/equicordplugins/betterMicrophone.desktop/components/MicrophoneSettingsModal.tsx similarity index 100% rename from src/equicordplugins/betterMicrophone/components/MicrophoneSettingsModal.tsx rename to src/equicordplugins/betterMicrophone.desktop/components/MicrophoneSettingsModal.tsx diff --git a/src/equicordplugins/betterMicrophone/components/index.tsx b/src/equicordplugins/betterMicrophone.desktop/components/index.tsx similarity index 100% rename from src/equicordplugins/betterMicrophone/components/index.tsx rename to src/equicordplugins/betterMicrophone.desktop/components/index.tsx diff --git a/src/equicordplugins/betterMicrophone/constants/constants.ts b/src/equicordplugins/betterMicrophone.desktop/constants/constants.ts similarity index 100% rename from src/equicordplugins/betterMicrophone/constants/constants.ts rename to src/equicordplugins/betterMicrophone.desktop/constants/constants.ts diff --git a/src/equicordplugins/betterMicrophone/constants/index.ts b/src/equicordplugins/betterMicrophone.desktop/constants/index.ts similarity index 100% rename from src/equicordplugins/betterMicrophone/constants/index.ts rename to src/equicordplugins/betterMicrophone.desktop/constants/index.ts diff --git a/src/equicordplugins/betterMicrophone/index.tsx b/src/equicordplugins/betterMicrophone.desktop/index.tsx similarity index 100% rename from src/equicordplugins/betterMicrophone/index.tsx rename to src/equicordplugins/betterMicrophone.desktop/index.tsx diff --git a/src/equicordplugins/betterMicrophone/logger/index.ts b/src/equicordplugins/betterMicrophone.desktop/logger/index.ts similarity index 100% rename from src/equicordplugins/betterMicrophone/logger/index.ts rename to src/equicordplugins/betterMicrophone.desktop/logger/index.ts diff --git a/src/equicordplugins/betterMicrophone/modals/index.tsx b/src/equicordplugins/betterMicrophone.desktop/modals/index.tsx similarity index 100% rename from src/equicordplugins/betterMicrophone/modals/index.tsx rename to src/equicordplugins/betterMicrophone.desktop/modals/index.tsx diff --git a/src/equicordplugins/betterMicrophone/patchers/index.ts b/src/equicordplugins/betterMicrophone.desktop/patchers/index.ts similarity index 100% rename from src/equicordplugins/betterMicrophone/patchers/index.ts rename to src/equicordplugins/betterMicrophone.desktop/patchers/index.ts diff --git a/src/equicordplugins/betterMicrophone/patchers/microphone.ts b/src/equicordplugins/betterMicrophone.desktop/patchers/microphone.ts similarity index 100% rename from src/equicordplugins/betterMicrophone/patchers/microphone.ts rename to src/equicordplugins/betterMicrophone.desktop/patchers/microphone.ts diff --git a/src/equicordplugins/betterMicrophone/stores/index.ts b/src/equicordplugins/betterMicrophone.desktop/stores/index.ts similarity index 100% rename from src/equicordplugins/betterMicrophone/stores/index.ts rename to src/equicordplugins/betterMicrophone.desktop/stores/index.ts diff --git a/src/equicordplugins/betterMicrophone/stores/microphoneStore.ts b/src/equicordplugins/betterMicrophone.desktop/stores/microphoneStore.ts similarity index 100% rename from src/equicordplugins/betterMicrophone/stores/microphoneStore.ts rename to src/equicordplugins/betterMicrophone.desktop/stores/microphoneStore.ts diff --git a/src/equicordplugins/betterScreenshare/components/AudioSourceSelect.tsx b/src/equicordplugins/betterScreenshare.desktop/components/AudioSourceSelect.tsx similarity index 100% rename from src/equicordplugins/betterScreenshare/components/AudioSourceSelect.tsx rename to src/equicordplugins/betterScreenshare.desktop/components/AudioSourceSelect.tsx diff --git a/src/equicordplugins/betterScreenshare/components/OpenScreenshareSettingsButton.tsx b/src/equicordplugins/betterScreenshare.desktop/components/OpenScreenshareSettingsButton.tsx similarity index 100% rename from src/equicordplugins/betterScreenshare/components/OpenScreenshareSettingsButton.tsx rename to src/equicordplugins/betterScreenshare.desktop/components/OpenScreenshareSettingsButton.tsx diff --git a/src/equicordplugins/betterScreenshare/components/ScreenshareSettingsModal.tsx b/src/equicordplugins/betterScreenshare.desktop/components/ScreenshareSettingsModal.tsx similarity index 99% rename from src/equicordplugins/betterScreenshare/components/ScreenshareSettingsModal.tsx rename to src/equicordplugins/betterScreenshare.desktop/components/ScreenshareSettingsModal.tsx index c3bb1bc3..ac529445 100644 --- a/src/equicordplugins/betterScreenshare/components/ScreenshareSettingsModal.tsx +++ b/src/equicordplugins/betterScreenshare.desktop/components/ScreenshareSettingsModal.tsx @@ -22,7 +22,7 @@ import { ModalSize, openModalLazy } from "@utils/modal"; import { Button, Card, Forms, React, Select, Slider, TextInput, useEffect, useState } from "@webpack/common"; import { SelectOption } from "@webpack/types"; -import { MicrophoneSettingsModal } from "../../betterMicrophone/components"; +import { MicrophoneSettingsModal } from "../../betterMicrophone.desktop/components"; import { MediaEngineStore, openURL, diff --git a/src/equicordplugins/betterScreenshare/components/index.tsx b/src/equicordplugins/betterScreenshare.desktop/components/index.tsx similarity index 100% rename from src/equicordplugins/betterScreenshare/components/index.tsx rename to src/equicordplugins/betterScreenshare.desktop/components/index.tsx diff --git a/src/equicordplugins/betterScreenshare/constants/constants.ts b/src/equicordplugins/betterScreenshare.desktop/constants/constants.ts similarity index 100% rename from src/equicordplugins/betterScreenshare/constants/constants.ts rename to src/equicordplugins/betterScreenshare.desktop/constants/constants.ts diff --git a/src/equicordplugins/betterScreenshare/constants/index.ts b/src/equicordplugins/betterScreenshare.desktop/constants/index.ts similarity index 100% rename from src/equicordplugins/betterScreenshare/constants/index.ts rename to src/equicordplugins/betterScreenshare.desktop/constants/index.ts diff --git a/src/equicordplugins/betterScreenshare/index.tsx b/src/equicordplugins/betterScreenshare.desktop/index.tsx similarity index 100% rename from src/equicordplugins/betterScreenshare/index.tsx rename to src/equicordplugins/betterScreenshare.desktop/index.tsx diff --git a/src/equicordplugins/betterScreenshare/logger/index.ts b/src/equicordplugins/betterScreenshare.desktop/logger/index.ts similarity index 100% rename from src/equicordplugins/betterScreenshare/logger/index.ts rename to src/equicordplugins/betterScreenshare.desktop/logger/index.ts diff --git a/src/equicordplugins/betterScreenshare/modals/index.tsx b/src/equicordplugins/betterScreenshare.desktop/modals/index.tsx similarity index 100% rename from src/equicordplugins/betterScreenshare/modals/index.tsx rename to src/equicordplugins/betterScreenshare.desktop/modals/index.tsx diff --git a/src/equicordplugins/betterScreenshare/patchers/index.ts b/src/equicordplugins/betterScreenshare.desktop/patchers/index.ts similarity index 100% rename from src/equicordplugins/betterScreenshare/patchers/index.ts rename to src/equicordplugins/betterScreenshare.desktop/patchers/index.ts diff --git a/src/equicordplugins/betterScreenshare/patchers/screenshare.ts b/src/equicordplugins/betterScreenshare.desktop/patchers/screenshare.ts similarity index 100% rename from src/equicordplugins/betterScreenshare/patchers/screenshare.ts rename to src/equicordplugins/betterScreenshare.desktop/patchers/screenshare.ts diff --git a/src/equicordplugins/betterScreenshare/patchers/screenshareAudio.ts b/src/equicordplugins/betterScreenshare.desktop/patchers/screenshareAudio.ts similarity index 100% rename from src/equicordplugins/betterScreenshare/patchers/screenshareAudio.ts rename to src/equicordplugins/betterScreenshare.desktop/patchers/screenshareAudio.ts diff --git a/src/equicordplugins/betterScreenshare/patches/index.ts b/src/equicordplugins/betterScreenshare.desktop/patches/index.ts similarity index 100% rename from src/equicordplugins/betterScreenshare/patches/index.ts rename to src/equicordplugins/betterScreenshare.desktop/patches/index.ts diff --git a/src/equicordplugins/betterScreenshare/patches/screenshareModal.tsx b/src/equicordplugins/betterScreenshare.desktop/patches/screenshareModal.tsx similarity index 100% rename from src/equicordplugins/betterScreenshare/patches/screenshareModal.tsx rename to src/equicordplugins/betterScreenshare.desktop/patches/screenshareModal.tsx diff --git a/src/equicordplugins/betterScreenshare/stores/index.ts b/src/equicordplugins/betterScreenshare.desktop/stores/index.ts similarity index 100% rename from src/equicordplugins/betterScreenshare/stores/index.ts rename to src/equicordplugins/betterScreenshare.desktop/stores/index.ts diff --git a/src/equicordplugins/betterScreenshare/stores/screenshareAudioStore.ts b/src/equicordplugins/betterScreenshare.desktop/stores/screenshareAudioStore.ts similarity index 97% rename from src/equicordplugins/betterScreenshare/stores/screenshareAudioStore.ts rename to src/equicordplugins/betterScreenshare.desktop/stores/screenshareAudioStore.ts index 4a7a4579..685ad6b7 100644 --- a/src/equicordplugins/betterScreenshare/stores/screenshareAudioStore.ts +++ b/src/equicordplugins/betterScreenshare.desktop/stores/screenshareAudioStore.ts @@ -21,7 +21,7 @@ import { MicrophoneProfile as ScreenshareAudioProfile, MicrophoneStore as ScreenshareAudioStore, microphoneStoreDefault as screenshareAudioStoreDefault -} from "../../betterMicrophone/stores"; +} from "../../betterMicrophone.desktop/stores"; import { createPluginStore, ProfilableStore, profileable } from "../../philsPluginLibrary"; import { PluginInfo } from "../constants"; diff --git a/src/equicordplugins/betterScreenshare/stores/screenshareStore.ts b/src/equicordplugins/betterScreenshare.desktop/stores/screenshareStore.ts similarity index 100% rename from src/equicordplugins/betterScreenshare/stores/screenshareStore.ts rename to src/equicordplugins/betterScreenshare.desktop/stores/screenshareStore.ts diff --git a/src/equicordplugins/customSounds/index.ts b/src/equicordplugins/customSounds/index.ts new file mode 100644 index 00000000..cea81ce1 --- /dev/null +++ b/src/equicordplugins/customSounds/index.ts @@ -0,0 +1,402 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +const soundFileMapping: { [key: string]: string[]; } = { + discodoDetuneURL: ["c9bfe03395cf2616891f.mp3", "9b8b7e8c94287d5491a8.mp3"], + activitiesRocketTimeURL: ["cd402df20cddf4d85b4b.mp3"], + activityEndURL: ["37530244cdcd5141095b.mp3"], + activityLaunchURL: ["267f72c6f838aac3be94.mp3"], + activityUserJoinURL: ["74c606872cea9803e310.mp3"], + activityUserLeftURL: ["99bd2585703114d2df64.mp3"], + asmrMessage1URL: ["d04d1ee13ab2d7d04e97.mp3"], + bitMessage1URL: ["fd9f21c60424f7bbe603.mp3"], + bopMessage1URL: ["f9b3c218d2bac00a50a5.mp3"], + callCallingURL: ["11b68eb8f243b5f6c8d7.mp3", "ec09898a0bd65dfaa768.mp3"], + callRingingURL: ["986703daecf955ce3ce3.mp3", "6345bccfecdfa67fdb97.mp3"], + callRingingBeatURL: ["3b3a2f5f29b9cb656efb.mp3"], + callRingingHalloweenURL: ["feb12b25f1200b97c4eb.mp3"], + callRingingSnowHalationURL: ["99b1d8a6fe0b95e99827.mp3"], + callRingingSnowsgivingURL: ["54527e70cf0ddaeff76f.mp3"], + clipErrorURL: ["4185e05ac87668c95db7.mp3"], + clipSaveURL: ["f96b272b4140be6ce8a9.mp3"], + ddrDownURL: ["60b2fa578027733f07b2.mp3"], + ddrLeftURL: ["6a2283291b8468b5dcbc.mp3"], + ddrRightURL: ["ede3b86253bb4aa1615b.mp3"], + ddrUpURL: ["89547833e1e1ebb138a4.mp3"], + deafenURL: ["763976e8bc7c745f1bbb.mp3", "1e63dc2f54bef5c5003c.mp3"], + disconnectDetuneURL: ["752b32be8f41b5ef55c4.mp3"], + duckyMessage1URL: ["7732a4c7760789c64269.mp3"], + hangStatusSelectURL: ["6f82a1ced41ffba7e474.mp3"], + highfiveClapURL: ["b6765c41e5305ed3ccbf.mp3"], + highfiveWhistleURL: ["ea499ca7cb948b4e89f3.mp3"], + humanManURL: ["ba335c8e058cf0edef0c.mp3"], + lofiMessage1URL: ["8fd01840800c5b8d5d40.mp3"], + mention1URL: ["72d6195de7af6f6c522f.mp3"], + mention2URL: ["5746c97822ddd998ecaa.mp3"], + mention3URL: ["90268f54ea2962dd9a9d.mp3"], + message1DetuneURL: ["70ae9d5bc54e4e0954f9.mp3", "ed256a76f3fe748177de.mp3"], + message2URL: ["94d97da9d3ca65ca5c48.mp3"], + message3URL: ["647a0cfe7a004fa8b20d.mp3"], + muteDetuneURL: ["ad1365b07daf20cf62d5.mp3", "e8ffe6892e47d655e796.mp3"], + overlayunlockURL: ["cf5424f20c2a9c65b6bc.mp3"], + poggermodeAchievementUnlockURL: ["156fff4a60f8bd215dd9.mp3"], + poggermodeApplauseURL: ["07ff9c560f9ba1c99cc8.mp3"], + poggermodeEnabledURL: ["73eaa6460d4bdb9dcd4d.mp3"], + poggermodeMessageSendURL: ["7e01b5cb50d9d1941f16.mp3"], + pttStartDetuneURL: ["369f4aaf35687b9f986d.mp3", "3d1f7316482d3b37e947.mp3"], + pttStopDetuneURL: ["b843a42563e5f3144483.mp3", "1e1af3535e94d2143b69.mp3"], + reconnectURL: ["da2dc3239ecd9ab74be1.mp3"], + robotManURL: ["76ff191944dd58f4835f.mp3"], + stageWaitingURL: ["e23b4d3cf753989097f4.mp3"], + streamEndedDetuneURL: ["9b12ba39365fff66dd95.mp3", "8f4bc39481c815b02e4c.mp3"], + streamStartedDetuneURL: ["d954d7bfa51d58a25610.mp3", "7868133f107297d09719.mp3"], + streamUserJoinedDetuneURL: ["e9fa25653b507623acbd.mp3", "5320b9eae726f7c7b3f5.mp3"], + streamUserLeftDetuneURL: ["0fdb31ebfdaf7e86e7ff.mp3", "b45e91a78a4377b853b8.mp3"], + successURL: ["1cc608397adb2bb151de.mp3"], + undeafenDetuneURL: ["2f7089b0d3e66e7d6d67.mp3", "c87a93fd4b6918f21b8d.mp3"], + unmuteDetuneURL: ["80f50a0752b5cd6028ac.mp3", "1bf3e7ad8588f290f1d8.mp3"], + userJoinDetuneURL: ["93034ac8d9eba50e3354.mp3", "376a8b2b79947dabac6d.mp3"], + userLeaveDetuneURL: ["fea7a918aecf33a04116.mp3", "9ea3b5ecbcad19a243e6.mp3"], + userMovedDetuneURL: ["e490f52f12a18334ae94.mp3", "af380400eb22d2fa80dc.mp3"], + vibingWumpusURL: ["38b1c58d275e828aa9b6.mp3"] +}; + +const settings = definePluginSettings({ + discodoDetuneURL: { + description: "Audio URL for discodo sound", + type: OptionType.STRING, + default: "https://www.myinstants.com/media/sounds/explosion-m.mp3", + restartNeeded: true + }, + activitiesRocketTimeURL: { + description: "Audio URL for activities rocket time sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + activityEndURL: { + description: "Audio URL for activity end sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + activityLaunchURL: { + description: "Audio URL for activity launch sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + activityUserJoinURL: { + description: "Audio URL for activity user join sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + activityUserLeftURL: { + description: "Audio URL for activity user left sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + asmrMessage1URL: { + description: "Audio URL for asmr message1 sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + bitMessage1URL: { + description: "Audio URL for bit message1 sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + bopMessage1URL: { + description: "Audio URL for bop message1 sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + callCallingURL: { + description: "Audio URL for call calling sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + callRingingURL: { + description: "Audio URL for call ringing sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + callRingingBeatURL: { + description: "Audio URL for call ringing beat sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + callRingingHalloweenURL: { + description: "Audio URL for call ringing halloween sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + callRingingSnowHalationURL: { + description: "Audio URL for call ringing snow halation sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + callRingingSnowsgivingURL: { + description: "Audio URL for call ringing snowsgiving sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + clipErrorURL: { + description: "Audio URL for clip error sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + clipSaveURL: { + description: "Audio URL for clip save sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + ddrDownURL: { + description: "Audio URL for ddr down sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + ddrLeftURL: { + description: "Audio URL for ddr left sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + ddrRightURL: { + description: "Audio URL for ddr right sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + ddrUpURL: { + description: "Audio URL for ddr up sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + deafenURL: { + description: "Audio URL for deafen sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + disconnectDetuneURL: { + description: "Audio URL for disconnect sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + duckyMessage1URL: { + description: "Audio URL for ducky message1 sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + hangStatusSelectURL: { + description: "Audio URL for hang status select sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + highfiveClapURL: { + description: "Audio URL for highfive clap sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + highfiveWhistleURL: { + description: "Audio URL for highfive whistle sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + humanManURL: { + description: "Audio URL for human man sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + lofiMessage1URL: { + description: "Audio URL for lofi message1 sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + mention1URL: { + description: "Audio URL for mention1 sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + mention2URL: { + description: "Audio URL for mention2 sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + mention3URL: { + description: "Audio URL for mention3 sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + message2URL: { + description: "Audio URL for message2 sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + message3URL: { + description: "Audio URL for message3 sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + muteDetuneURL: { + description: "Audio URL for mute sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + overlayunlockURL: { + description: "Audio URL for overlay unlock sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + poggermodeAchievementUnlockURL: { + description: "Audio URL for poggermode achievement unlock sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + poggermodeApplauseURL: { + description: "Audio URL for poggermode applause sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + poggermodeEnabledURL: { + description: "Audio URL for poggermode enabled sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + poggermodeMessageSendURL: { + description: "Audio URL for poggermode message send sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + pttStartDetuneURL: { + description: "Audio URL for ptt start sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + pttStopDetuneURL: { + description: "Audio URL for ptt stop sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + reconnectURL: { + description: "Audio URL for reconnect sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + robotManURL: { + description: "Audio URL for robot man sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + stageWaitingURL: { + description: "Audio URL for stage waiting sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + successURL: { + description: "Audio URL for success sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + }, + vibingWumpusURL: { + description: "Audio URL for vibing wumpus sound", + type: OptionType.STRING, + default: "", + restartNeeded: true + } +}); + +function getSoundURL(settingKey: string) { + const url = settings.store[settingKey]; + const knownAudioExtensions = [".mp3", ".wav", ".ogg", ".flac", ".aac", ".m4a"]; + + const isValidURL = (url: string) => { + try { + new URL(url); + return true; + } catch (_) { + return false; + } + }; + + const hasValidExtension = (url: string) => { + return knownAudioExtensions.some(ext => url.toLowerCase().endsWith(ext)); + }; + + const addHttpsIfMissing = (url: string) => { + if (!/^https?:\/\//i.test(url)) { + return `https://${url}`; + } + return url; + }; + + const correctedURL = addHttpsIfMissing(url); + + if (correctedURL && isValidURL(correctedURL) && hasValidExtension(correctedURL)) { + return correctedURL; + } + return url; +} + +export default definePlugin({ + name: "CustomSounds", + description: "Replace Discord sounds with a custom ones", + authors: [Devs.ScattrdBlade], + settings, + patches: Object.keys(soundFileMapping).flatMap(settingKey => + soundFileMapping[settingKey].map(file => ({ + find: file, + replacement: { + match: /e\.exports\s*=\s*n\.p\s*\+\s*"[a-zA-Z0-9]+\.(mp3|wav|ogg|flac|aac|m4a)"/, + replace: () => `e.exports="${getSoundURL(settingKey)}"` + } + })) + ) +}); diff --git a/src/equicordplugins/fakeProfile/constants.ts b/src/equicordplugins/fakeProfile/constants.ts new file mode 100644 index 00000000..17b465f2 --- /dev/null +++ b/src/equicordplugins/fakeProfile/constants.ts @@ -0,0 +1,13 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export const BASE_URL = "https://i.sampath.tech"; +export const API_URL = BASE_URL + "/v3/users"; +export const SKU_ID_DISCORD = "100101099222222"; +export const SKU_ID = "100101099222224"; +export const GUILD_ID = "1117373291095662623"; +export const INVITE_KEY = "ffmkewQ4R7"; +export const VERSION = "v2.17"; diff --git a/src/equicordplugins/fakeProfile/index.tsx b/src/equicordplugins/fakeProfile/index.tsx new file mode 100644 index 00000000..56366a7a --- /dev/null +++ b/src/equicordplugins/fakeProfile/index.tsx @@ -0,0 +1,644 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges"; +import { addDecoration, removeDecoration } from "@api/MessageDecorations"; +import { definePluginSettings } from "@api/Settings"; +import { classNameFactory, enableStyle } from "@api/Styles"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Flex } from "@components/Flex"; +import { Link } from "@components/Link"; +import { Devs, EquicordDevs } from "@utils/constants"; +import { Margins } from "@utils/margins"; +import { copyWithToast } from "@utils/misc"; +import { closeModal, Modals, openModal } from "@utils/modal"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByCodeLazy } from "@webpack"; +import { Button, Forms, Toasts, Tooltip, useEffect, useState } from "@webpack/common"; +import { User } from "discord-types/general"; +import virtualMerge from "virtual-merge"; + +import { API_URL, BASE_URL, SKU_ID, SKU_ID_DISCORD, VERSION } from "./constants"; +const CustomizationSection = findByCodeLazy(".customizationSectionBackground"); +const cl = classNameFactory("vc-decoration-"); + +import style from "./style.css?managed"; +import { AvatarDecoration, Colors, fakeProfileSectionProps, UserProfile, UserProfileData } from "./types"; + + + + +let UsersData = {} as Record; +const UserBadges: Record = {}; +const updateBadgesForAllUsers = () => { + Object.keys(UsersData).forEach(userId => { + const newBadges = UsersData[userId].badges; + const existingBadges = UserBadges[userId] || []; + if (newBadges) { + newBadges.forEach((badge, index) => { + const existingBadge = existingBadges[index]; + + if (!existingBadge) { + const newBadge = { + image: badge.icon, + position: BadgePosition.START, + description: badge.description, + props: { + style: { + borderRadius: "15%", + transform: "scale(0.9)" + } + }, + shouldShow: userInfo => userInfo.userId === userId, + onClick() { + const modalKey = openModal(props => ( + { + closeModal(modalKey); + VencordNative.native.openExternal("https://github.com/sampathgujarathi/fakeProfile"); + }}> + + + + + fakeProfile + + + + +
+ +
+
+ + Disclaimer: This badge is generated by the fakeProfile plugin. Please be aware that it may not represent genuine credentials or affiliations. Thank you for your understanding. + +
+
+ +
+ +
+ )); + }, + }; + + addBadge(newBadge); + + if (!UserBadges[userId]) { + UserBadges[userId] = []; + } + + UserBadges[userId].splice(index, 0, newBadge); + } + }); + } + existingBadges.forEach((existingBadge, index) => { + const badgeStillExists = newBadges && newBadges[index]; + + if (!badgeStillExists) { + removeBadge(existingBadge); + UserBadges[userId].splice(index, 1); + } + }); + }); +}; + + +async function loadfakeProfile(noCache = false) { + try { + const init = {} as RequestInit; + if (noCache) + init.cache = "no-cache"; + + const response = await fetch(API_URL + "/fakeProfile", init); + const data = await response.json(); + UsersData = data; + } catch (error) { + console.error("Error loading fake profile:", error); + } +} + +function getUserEffect(profileId: string) { + return UsersData[profileId] ? UsersData[profileId].profile_effect : null; +} + + +function encode(primary: number, accent: number): string { + const message = `[#${primary.toString(16).padStart(6, "0")},#${accent.toString(16).padStart(6, "0")}]`; + const padding = ""; + const encoded = Array.from(message) + .map(x => x.codePointAt(0)) + .filter(x => x! >= 0x20 && x! <= 0x7f) + .map(x => String.fromCodePoint(x! + 0xe0000)) + .join(""); + + return (padding || "") + " " + encoded; +} + +function decode(bio: string): Array | null { + if (bio == null) return null; + const colorString = bio.match( + /\u{e005b}\u{e0023}([\u{e0061}-\u{e0066}\u{e0041}-\u{e0046}\u{e0030}-\u{e0039}]+?)\u{e002c}\u{e0023}([\u{e0061}-\u{e0066}\u{e0041}-\u{e0046}\u{e0030}-\u{e0039}]+?)\u{e005d}/u, + ); + if (colorString != null) { + const parsed = [...colorString[0]] + .map(x => String.fromCodePoint(x.codePointAt(0)! - 0xe0000)) + .join(""); + const colors = parsed + .substring(1, parsed.length - 1) + .split(",") + .map(x => parseInt(x.replace("#", "0x"), 16)); + + return colors; + } else { + return null; + } +} + + +const settings = definePluginSettings({ + enableProfileEffects: { + description: "Allows you to use profile effects", + type: OptionType.BOOLEAN, + default: false + }, + enableProfileThemes: { + description: "Allows you to use profile themes", + type: OptionType.BOOLEAN, + default: false + }, + enableCustomBadges: { + description: "Allows you to use custom badges", + type: OptionType.BOOLEAN, + default: false, + restartNeeded: true + }, + enableAvatarDecorations: { + description: "Allows you to use discord avatar decorations", + type: OptionType.BOOLEAN, + default: false + }, + showCustomBadgesinmessage: { + description: "Show custom badges in message", + type: OptionType.BOOLEAN, + default: false, + restartNeeded: true + }, + nitroFirst: { + description: "Banner/Avatar to use if both Nitro and fakeProfile Banner/Avatar are present", + type: OptionType.SELECT, + options: [ + { label: "Nitro", value: true, default: true }, + { label: "fakeProfile", value: false }, + ] + }, + voiceBackground: { + description: "Use fakeProfile banners as voice chat backgrounds", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + } +}); + + +function fakeProfileSection({ hideTitle = false, hideDivider = false, noMargin = false }: fakeProfileSectionProps) { + return + + + + ; +} + +const openModalOnClick = () => { + const modalKey = openModal(props => ( + { + closeModal(modalKey); + VencordNative.native.openExternal("https://github.com/sampathgujarathi/fakeProfile"); + }}> + + + + + fakeProfile + + + + +
+ +
+
+ + Disclaimer: This badge is generated by the fakeProfile plugin. Please be aware that it may not represent genuine credentials or affiliations. Thank you for your understanding. + +
+
+
+
+ )); +}; + +function ImageIcon(path: string) { + return ({ tooltip }: { tooltip: string; }) => ( + + {(tooltipProps: any) => ( + + )} + + ); +} +const BadgeIcon = ({ user, badgeImg, badgeText }: { user: User, badgeImg: string, badgeText: string; }) => { + if (UsersData[user.id]?.badges) { + const Icon = ImageIcon(badgeImg); + const tooltip = badgeText; + return ; + } else { + return null; + } +}; + + + +const BadgeMain = ({ user, wantMargin = true, wantTopMargin = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; }) => { + + const validBadges = UsersData[user.id]?.badges; + if (!validBadges || validBadges.length === 0) return null; + + const icons = validBadges.map((badge, index) => ( +
+ +
+ )); + + return ( + + {icons} + + ); +}; + + + +export default definePlugin({ + name: "fakeProfile", + description: "Unlock Discord profile effects, themes, avatar decorations, and custom badges without the need for Nitro.", + authors: [EquicordDevs.Sampath, Devs.Alyxia, Devs.Remty, Devs.AutumnVN, Devs.pylix, Devs.TheKodeToad], + dependencies: ["MessageDecorationsAPI"], + start: async () => { + enableStyle(style); + await loadfakeProfile(); + if (settings.store.enableCustomBadges) { + updateBadgesForAllUsers(); + } + if (settings.store.showCustomBadgesinmessage) { + addDecoration("custom-badge", props => + + + + ); + } + const response = await fetch(BASE_URL + "/fakeProfile"); + const data = await response.json(); + if (data.version !== VERSION) { + Toasts.show({ + message: "There is an update available for the fakeProfile plugin.", + id: Toasts.genId(), + type: Toasts.Type.MESSAGE, + options: { + position: Toasts.Position.BOTTOM + } + }); + } + setInterval(async () => { + await loadfakeProfile(true); + if (settings.store.enableCustomBadges) { + updateBadgesForAllUsers(); + } + }, data.reloadInterval); + }, + stop: () => { + if (settings.store.showCustomBadgesinmessage) { + removeDecoration("custom-badge"); + } + }, + patches: [ + { + find: "UserProfileStore", + replacement: { + match: /(?<=getUserProfile\(\i\){return )(\i\[\i\])/, + replace: "$self.profileDecodeHook($1)" + } + }, + { + find: "getAvatarDecorationURL:", + replacement: { + match: /(?<=function \i\(\i\){)(?=let{avatarDecoration)/, + replace: "const vcDecoration = (() => { return $self.getAvatarDecorationURL(arguments[0]); })(); if (vcDecoration) return vcDecoration;" + } + }, + { + find: ".USER_SETTINGS_RESET_PROFILE_THEME", + replacement: { + match: /RESET_PROFILE_THEME}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/, + replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})" + } + }, + { + find: "DefaultCustomizationSections", + replacement: { + match: /(?<=USER_SETTINGS_AVATAR_DECORATION},"decoration"\),)/, + replace: "$self.fakeProfileSection()," + } + }, + { + find: ".NITRO_BANNER,", + replacement: { + match: /\?\(0,\i\.jsx\)\(\i,{type:\i,shown/, + replace: "&&$self.shouldShowBadge(arguments[0])$&" + } + }, + { + find: "=!1,canUsePremiumCustomization:", + replacement: { + match: /(\i)\.premiumType/, + replace: "$self.premiumHook($1)||$&" + } + }, + { + find: ".banner)==null", + replacement: { + match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/, + replace: "$self.useBannerHook(arguments[0])||$&" + + } + }, + { + find: "\"data-selenium-video-tile\":", + predicate: () => settings.store.voiceBackground, + replacement: [ + { + match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/, + replace: "$1.style=$self.voiceBackgroundHook($1);" + } + ] + }, + { + find: "getUserAvatarURL:", + replacement: [ + { + match: /(getUserAvatarURL:)(\i),/, + replace: "$1$self.getAvatarHook($2)," + }, + { + match: /(getUserAvatarURL:\i\(\){return )(\i)}/, + replace: "$1$self.getAvatarHook($2)}" + } + ] + }, + { + find: "isAvatarDecorationAnimating:", + group: true, + replacement: [ + { + match: /(?<=TryItOut:\i,guildId:\i}\),)(?<=user:(\i).+?)/, + replace: "vcAvatarDecoration=$self.useUserAvatarDecoration($1)," + }, + { + match: /(?<={avatarDecoration:).{1,20}?(?=,)(?<=avatarDecorationOverride:(\i).+?)/, + replace: "$1??vcAvatarDecoration??($&)" + }, + { + match: /(?<=size:\i}\),\[)/, + replace: "vcAvatarDecoration," + } + ] + }, + { + find: "renderAvatarWithPopout(){", + replacement: [ + { + match: /(?<=\)\({(?:(?:.(?!\)}))*,)?avatarDecoration:)(\i)\.avatarDecoration(?=,|}\))/, + replace: "$self.useUserAvatarDecoration($1)??$&" + } + ] + } + ], + settingsAboutComponent: () => ( + + + Usage + CLICK HERE TO GET PROFILE EFFECTS, CUSTOM BADGES, BANNER OR ANIMATED PFP + + Enable Profile Themes to use fake profile themes.
+ To set your own colors: +
    +
  • โ€ข go to your profile settings
  • +
  • โ€ข choose your own colors in the Nitro preview
  • +
  • โ€ข click the "Copy 3y3" button
  • +
  • โ€ข paste the invisible text anywhere in your bio
  • +

+
+
+ ), + settings, + profileDecodeHook(user: UserProfile) { + if (user) { + if (settings.store.enableProfileEffects || settings.store.enableProfileThemes) { + let mergeData: Partial = {}; + const profileEffect = getUserEffect(user.userId); + const colors = decode(user.bio); + if (settings.store.enableProfileEffects && profileEffect) { + mergeData = { + ...mergeData, + profileEffectId: profileEffect + + }; + } + + if (settings.store.enableProfileThemes && colors) { + mergeData = { + ...mergeData, + premiumType: 2, + themeColors: colors + }; + } + return virtualMerge(user, mergeData as UserProfile); + } + return user; + } + + return user; + }, + SKU_ID_DISCORD, + SKU_ID, + useUserAvatarDecoration(user?: User): { asset: string; skuId: string; animated: boolean; } | null { + const [avatarDecoration, setAvatarDecoration] = useState<{ asset: string; skuId: string; animated: boolean; } | null>(null); + + useEffect(() => { + const fetchUserAssets = async () => { + try { + if (user?.id) { + const userAssetsData = UsersData[user.id]; + if (userAssetsData?.decoration) { + setAvatarDecoration({ + asset: userAssetsData.decoration?.asset, + skuId: userAssetsData.decoration?.skuId, + animated: userAssetsData.decoration?.animated + }); + } + } + } catch (error) { + console.error("Error fetching user assets:", error); + } + }; + fetchUserAssets(); + }, [user, UsersData]); + + if (!user || !settings.store.enableAvatarDecorations) { + return null; + } + + return avatarDecoration ? { + asset: avatarDecoration.asset, + skuId: avatarDecoration.skuId, + animated: avatarDecoration.animated + } : null; + }, + voiceBackgroundHook({ className, participantUserId }: any) { + if (className.includes("tile_")) { + if (UsersData[participantUserId]) { + return { + backgroundImage: `url(${UsersData[participantUserId].banner})`, + backgroundSize: "cover", + backgroundPosition: "center", + backgroundRepeat: "no-repeat" + }; + } + } + }, + + useBannerHook({ displayProfile }: any) { + if (displayProfile?.banner && settings.store.nitroFirst) return; + if (UsersData[displayProfile?.userId] && UsersData[displayProfile?.userId].banner) return UsersData[displayProfile?.userId].banner; + }, + + premiumHook({ userId }: any) { + if (UsersData[userId]) return 2; + }, + + shouldShowBadge({ displayProfile, user }: any) { + return displayProfile?.banner && (UsersData[user.id] || settings.store.nitroFirst); + }, + getAvatarHook: (original: any) => (user: User, animated: boolean, size: number) => { + if (!settings.store.nitroFirst && user.avatar?.startsWith("a_")) return original(user, animated, size); + + return UsersData[user.id]?.avatar ?? original(user, animated, size); + }, + getAvatarDecorationURL({ avatarDecoration, canAnimate }: { avatarDecoration: AvatarDecoration | null; canAnimate?: boolean; }) { + if (!avatarDecoration || !settings.store.enableAvatarDecorations) return; + if (canAnimate && avatarDecoration?.animated !== false) { + if (avatarDecoration?.skuId === SKU_ID) { + const url = new URL(`https://i.sampath.tech/avatar-decoration-presets/a_${avatarDecoration?.asset}.png`); + return url.toString(); + } else { + const url = new URL(`https://cdn.discordapp.com/avatar-decoration-presets/${avatarDecoration?.asset}.png`); + return url.toString(); + } + } else { + if (avatarDecoration?.skuId === SKU_ID) { + const url = new URL(`https://i.sampath.tech/avatar-decoration-presets/${avatarDecoration?.asset}.png`); + return url.toString(); + } else { + const url = new URL(`https://cdn.discordapp.com/avatar-decoration-presets/${avatarDecoration?.asset}.png?passthrough=false`); + return url.toString(); + } + } + }, + fakeProfileSection: ErrorBoundary.wrap(fakeProfileSection), + toolboxActions: { + async "Refetch fakeProfile"() { + await loadfakeProfile(true); + updateBadgesForAllUsers(); + Toasts.show({ + message: "Successfully refetched fakeProfile!", + id: Toasts.genId(), + type: Toasts.Type.SUCCESS + }); + } + }, + addCopy3y3Button: ErrorBoundary.wrap(function ({ primary, accent }: Colors) { + return ; + }, { noop: true }), +}); diff --git a/src/equicordplugins/fakeProfile/style.css b/src/equicordplugins/fakeProfile/style.css new file mode 100644 index 00000000..69c5b185 --- /dev/null +++ b/src/equicordplugins/fakeProfile/style.css @@ -0,0 +1,12 @@ +:is([class*="userProfile"], [class*="userPopout"]) [class*="bannerPremium"] { + background: center / cover no-repeat; +} + +[class*="NonPremium"]:has([class*="bannerPremium"]) [class*="avatarPositionNormal"], +[class*="PremiumWithoutBanner"]:has([class*="bannerPremium"]) [class*="avatarPositionPremiumNoBanner"] { + top: 76px; +} + +[style*="background-image"] [class*="background_"] { + background-color: transparent !important; +} diff --git a/src/equicordplugins/fakeProfile/types.ts b/src/equicordplugins/fakeProfile/types.ts new file mode 100644 index 00000000..b045b305 --- /dev/null +++ b/src/equicordplugins/fakeProfile/types.ts @@ -0,0 +1,49 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { User } from "discord-types/general"; + +export interface Badge { + asset: string; + description: string; + icon: string; + link?: string; +} + +export interface DecorationData { + asset: string; + skuId: string; + animated: boolean; +} +export interface AvatarDecoration { + asset: string; + skuId: string; + animated: boolean; +} +export interface UserProfile extends User { + profileEffectId: string; + userId: string; + themeColors?: Array; + +} +export interface UserProfileData { + profile_effect: string; + banner: string; + avatar: string; + badges: Badge[]; + decoration: DecorationData; +} + +export interface Colors { + primary: number; + accent: number; +} + +export interface fakeProfileSectionProps { + hideTitle?: boolean; + hideDivider?: boolean; + noMargin?: boolean; +} diff --git a/src/equicordplugins/gensokyoRadioRPC/index.tsx b/src/equicordplugins/gensokyoRadioRPC/index.tsx new file mode 100644 index 00000000..4a166406 --- /dev/null +++ b/src/equicordplugins/gensokyoRadioRPC/index.tsx @@ -0,0 +1,102 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { Devs, EquicordDevs } from "@utils/constants"; +import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; +import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common"; + +import { Activity, ActivityFlag, ActivityType } from "./types"; + +const Native = VencordNative.pluginHelpers.GensokyoRadioRPC as PluginNative; + +const applicationId = "1253772057926303804"; + +function setActivity(activity: Activity | null) { + FluxDispatcher.dispatch({ + type: "LOCAL_ACTIVITY_UPDATE", + activity, + socketId: "GensokyoRadio", + }); +} + +function getImageAsset(data: string) { + return ApplicationAssetUtils.fetchAssetIds(applicationId, [data]).then(ids => ids[0]); +} + +const settings = definePluginSettings({ + refreshInterval: { + type: OptionType.SLIDER, + description: "The interval between activity refreshes (seconds)", + markers: [1, 2, 2.5, 3, 5, 10, 15], + default: 15, + restartNeeded: true, + } +}); + +export default definePlugin({ + name: "GensokyoRadioRPC", + description: "Discord rich presence for Gensokyo Radio!", + authors: [Devs.RyanCaoDev, EquicordDevs.Prince527], + reporterTestable: ReporterTestable.None, + + settingsAboutComponent() { + return <> + + Discord rich presence for Gensokyo Radio! + + ; + }, + + settings, + + start() { + this.updatePresence(); + this.updateInterval = setInterval(() => { this.updatePresence(); }, settings.store.refreshInterval * 1000); + }, + + stop() { + clearInterval(this.updateInterval); + FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null }); + }, + + updatePresence() { + this.getActivity().then(activity => { setActivity(activity); }); + }, + + async getActivity(): Promise { + const trackData = await Native.fetchTrackData(); + if (!trackData) return null; + + return { + application_id: applicationId, + + name: "Gensokyo Radio", + details: trackData.title, + state: trackData.artist, + + timestamps: { + // start: Date.now() - (trackData.position * 1000), + start: trackData.position * 1000, + // end: Date.now() - (trackData.position * 1000) + (trackData.duration * 1000), + end: trackData.duration * 1000, + }, + + assets: { + large_image: await getImageAsset(trackData.artwork), + large_text: trackData.album, + small_image: await getImageAsset("logo"), + small_text: "Gensokyo Radio" + }, + + buttons: undefined, + metadata: { button_urls: undefined }, + + type: ActivityType.LISTENING, + flags: ActivityFlag.INSTANCE, + }; + } +}); diff --git a/src/equicordplugins/gensokyoRadioRPC/native.ts b/src/equicordplugins/gensokyoRadioRPC/native.ts new file mode 100644 index 00000000..69dbe37a --- /dev/null +++ b/src/equicordplugins/gensokyoRadioRPC/native.ts @@ -0,0 +1,20 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import type { TrackData } from "./types"; + +export async function fetchTrackData(): Promise { + const song = await (await fetch("https://gensokyoradio.net/api/station/playing/")).json(); + + return { + title: song.SONGINFO.TITLE, + album: song.SONGINFO.ALBUM, + artist: song.SONGINFO.ARTIST, + position: song.SONGTIMES.SONGSTART, + duration: song.SONGTIMES.SONGEND, + artwork: song.MISC.ALBUMART ? `https://gensokyoradio.net/images/albums/500/${song.MISC.ALBUMART}` : "undefined", + }; +} diff --git a/src/equicordplugins/gensokyoRadioRPC/types.ts b/src/equicordplugins/gensokyoRadioRPC/types.ts new file mode 100644 index 00000000..3fa0464b --- /dev/null +++ b/src/equicordplugins/gensokyoRadioRPC/types.ts @@ -0,0 +1,57 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export interface ActivityAssets { + large_image?: string; + large_text?: string; + small_image?: string; + small_text?: string; +} + +export interface Activity { + state: string; + details?: string; + timestamps?: { + start?: number; + end?: number; + }; + assets?: ActivityAssets; + buttons?: Array; + name: string; + application_id: string; + metadata?: { + button_urls?: Array; + }; + type: number; + flags: number; +} + +export interface ActivityAssets { + large_image?: string; + large_text?: string; + small_image?: string; + small_text?: string; +} + +export const enum ActivityType { + PLAYING = 0, + LISTENING = 2, +} + +export const enum ActivityFlag { + INSTANCE = 1 << 0 +} + +export interface TrackData { + title: string; + album: string; + artist: string; + + artwork: string; + + position: number; + duration: number; +} diff --git a/src/equicordplugins/grammarFix/index.ts b/src/equicordplugins/grammarFix/index.ts new file mode 100644 index 00000000..96db13e1 --- /dev/null +++ b/src/equicordplugins/grammarFix/index.ts @@ -0,0 +1,67 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { + addPreSendListener, + removePreSendListener, + SendListener, +} from "@api/MessageEvents"; +import { EquicordDevs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +const getPresend = dictionary => { + const presendObject: SendListener = (_, msg) => { + msg.content = msg.content.trim(); + if (!msg.content.includes("```") && /\w/.test(msg.content.charAt(0))) { + const re = new RegExp( + `(^|(?<=[^A-Z0-9]+))(${Object.keys(dictionary) + .map(k => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) + .join("|")})((?=[^A-Z0-9]+)|$)`, + "gi" + ); + if (re !== null) { + msg.content = msg.content.replace(re, match => { + return dictionary[match.toLowerCase()] || match; + }); + } + + if (/[A-Z0-9]/i.test(msg.content.charAt(msg.content.length - 1))) { + if (!msg.content.startsWith("http", msg.content.lastIndexOf(" ") + 1)) + msg.content += "."; + } + + // Ensure sentences are capitalized after punctuation + msg.content = msg.content.replace(/([.!?])\s*(\w)/g, match => + match.toUpperCase() + ); + + // Ensure the first character of the entire message is capitalized + if (!msg.content.startsWith("http")) { + msg.content = + msg.content.charAt(0).toUpperCase() + msg.content.slice(1); + } + } + }; + return presendObject; +}; + +export default definePlugin({ + name: "GrammarFix", + description: "Automatic punctuation, capitalization, and word replacement.", + authors: [EquicordDevs.unstream], + dependencies: ["MessageEventsAPI"], + async start() { + let dictionary = await fetch( + "https://raw.githubusercontent.com/wont-stream/dictionary/3d52fecd9aca5dfee0fcde0df2c2af357f977df7/index.min.json" + ); + dictionary = await dictionary.json(); + + addPreSendListener(getPresend(dictionary)); + }, + stop() { + removePreSendListener(getPresend({})); + }, +}); diff --git a/src/equicordplugins/newPluginsManager/NewPluginsModal.tsx b/src/equicordplugins/newPluginsManager/NewPluginsModal.tsx new file mode 100644 index 00000000..12a079ed --- /dev/null +++ b/src/equicordplugins/newPluginsManager/NewPluginsModal.tsx @@ -0,0 +1,152 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { useSettings } from "@api/Settings"; +import { classNameFactory } from "@api/Styles"; +import { PluginCard } from "@components/PluginSettings"; +import { ChangeList } from "@utils/ChangeList"; +import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { Alerts, Button, Flex, Forms, Parser, React, Text, Tooltip, useMemo } from "@webpack/common"; + +import Plugins from "~plugins"; + +import { getNewPlugins, writeKnownPlugins } from "./knownPlugins"; + +const cl = classNameFactory("vc-plugins-"); + +let hasSeen = false; + +// Most of this was stolen from PluginSettings directly. + +export function NewPluginsModal({ modalProps, newPlugins }: { modalProps: ModalProps; newPlugins: Set; }) { + const settings = useSettings(); + const changes = React.useMemo(() => new ChangeList(), []); + + React.useEffect(() => { + return () => void (changes.hasChanges && Alerts.show({ + title: "Restart required", + body: ( + <> +

The following plugins require a restart:

+
{changes.map((s, i) => ( + <> + {i > 0 && ", "} + {Parser.parse("`" + s + "`")} + + ))}
+ + ), + confirmText: "Restart now", + cancelText: "Later!", + onConfirm: () => location.reload() + })); + }, []); + + const depMap = React.useMemo(() => { + const o = {} as Record; + for (const plugin in Plugins) { + const deps = Plugins[plugin].dependencies; + if (deps) { + for (const dep of deps) { + o[dep] ??= []; + o[dep].push(plugin); + } + } + } + return o; + }, []); + + const sortedPlugins = useMemo(() => [...newPlugins].map(pn => Plugins[pn]) + .sort((a, b) => a.name.localeCompare(b.name)), []); + + const plugins = [] as JSX.Element[]; + const requiredPlugins = [] as JSX.Element[]; + + for (const p of sortedPlugins) { + if (p.hidden) + continue; + + const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled); + + if (isRequired) { + const tooltipText = p.required + ? "This plugin is required for Vencord to function." + : makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled)); + + requiredPlugins.push( + + {({ onMouseLeave, onMouseEnter }) => ( + changes.handleChange(name)} + disabled={true} + plugin={p} + key={p.name} + /> + )} + + ); + } else { + plugins.push( + changes.handleChange(name)} + disabled={false} + plugin={p} + key={p.name} + /> + ); + } + } + + + return + + New Plugins ({[...plugins, ...requiredPlugins].length}) + + +
+ {[...plugins, ...requiredPlugins]} +
+
+ + + + + +
; +} + + +function makeDependencyList(deps: string[]) { + return ( + + This plugin is required by: + {deps.map((dep: string) => {dep})} + + ); +} + +export async function openNewPluginsModal() { + const newPlugins = await getNewPlugins(); + if (newPlugins.size && !hasSeen) { + hasSeen = true; + openModal(modalProps => ( + + )); + } +} diff --git a/src/equicordplugins/newPluginsManager/index.tsx b/src/equicordplugins/newPluginsManager/index.tsx new file mode 100644 index 00000000..9ab045c9 --- /dev/null +++ b/src/equicordplugins/newPluginsManager/index.tsx @@ -0,0 +1,25 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +import { KNOWN_PLUGINS_DATA_KEY } from "./knownPlugins"; +import { openNewPluginsModal } from "./NewPluginsModal"; + +export default definePlugin({ + name: "NewPluginsManager", + description: "Utility that notifies you when new plugins are added to Vencord", + authors: [Devs.Sqaaakoi], + enabledByDefault: true, + flux: { + async POST_CONNECTION_OPEN() { + openNewPluginsModal(); + } + }, + openNewPluginsModal, + KNOWN_PLUGINS_DATA_KEY +}); diff --git a/src/equicordplugins/newPluginsManager/knownPlugins.ts b/src/equicordplugins/newPluginsManager/knownPlugins.ts new file mode 100644 index 00000000..2a9dea6f --- /dev/null +++ b/src/equicordplugins/newPluginsManager/knownPlugins.ts @@ -0,0 +1,32 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { DataStore } from "@api/index"; + +import plugins from "~plugins"; + +export const KNOWN_PLUGINS_DATA_KEY = "NewPluginsManager_KnownPlugins"; + +export async function getKnownPlugins(): Promise> { + let knownPlugins = await DataStore.get(KNOWN_PLUGINS_DATA_KEY) as string[]; + if (knownPlugins === undefined) { + knownPlugins = Object.keys(plugins); + DataStore.set(KNOWN_PLUGINS_DATA_KEY, knownPlugins); + } + return new Set(knownPlugins); +} + +export async function getNewPlugins(): Promise> { + const currentPlugins = Object.keys(plugins); + const knownPlugins = await getKnownPlugins(); + return new Set(currentPlugins.filter(p => !knownPlugins.has(p))); +} + +export async function writeKnownPlugins(): Promise { + const currentPlugins = Object.keys(plugins); + const knownPlugins = await getKnownPlugins(); + DataStore.set(KNOWN_PLUGINS_DATA_KEY, [...new Set([...currentPlugins, ...knownPlugins])]); +} diff --git a/src/equicordplugins/philsPluginLibrary/patches/audio.ts b/src/equicordplugins/philsPluginLibrary/patches/audio.ts index 636ca251..5910b0a6 100644 --- a/src/equicordplugins/philsPluginLibrary/patches/audio.ts +++ b/src/equicordplugins/philsPluginLibrary/patches/audio.ts @@ -19,7 +19,7 @@ import { Logger } from "@utils/Logger"; import { lodash } from "@webpack/common"; -import { MicrophoneProfile, MicrophoneStore } from "../../betterMicrophone/stores"; +import { MicrophoneProfile, MicrophoneStore } from "../../betterMicrophone.desktop/stores"; import { ProfilableStore, replaceObjectValuesIfExist, types } from ".."; export function getDefaultAudioTransportationOptions(connection: types.Connection) { diff --git a/src/equicordplugins/philsPluginLibrary/patches/video.ts b/src/equicordplugins/philsPluginLibrary/patches/video.ts index 249e5978..b0460dee 100644 --- a/src/equicordplugins/philsPluginLibrary/patches/video.ts +++ b/src/equicordplugins/philsPluginLibrary/patches/video.ts @@ -19,7 +19,7 @@ import { Logger } from "@utils/Logger"; import { lodash } from "@webpack/common"; -import { ScreenshareProfile, ScreenshareStore } from "../../betterScreenshare/stores"; +import { ScreenshareProfile, ScreenshareStore } from "../../betterScreenshare.desktop/stores"; import { ProfilableStore, replaceObjectValuesIfExist, types, utils } from ".."; diff --git a/src/equicordplugins/replaceActivityTypes/ReplaceSettings.tsx b/src/equicordplugins/replaceActivityTypes/ReplaceSettings.tsx new file mode 100644 index 00000000..aadb962b --- /dev/null +++ b/src/equicordplugins/replaceActivityTypes/ReplaceSettings.tsx @@ -0,0 +1,144 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { CheckedTextInput } from "@components/CheckedTextInput"; +import { Margins } from "@utils/margins"; +import { identity } from "@utils/misc"; +import { findByPropsLazy } from "@webpack"; +import { Card, Forms, React, Select, SnowflakeUtils, Switch } from "@webpack/common"; + +import { makeEmptyAppId } from "."; +import { ActivityType, RpcApp, SettingsProps } from "./types"; + + +const RPCUtils = findByPropsLazy("fetchApplicationsRPC", "getRemoteIconURL"); + +const cachedApps: any = {}; +async function lookupApp(appId: string): Promise { + if (cachedApps[appId]) return cachedApps[appId]; + const socket: any = {}; + try { + await RPCUtils.fetchApplicationsRPC(socket, appId); + console.log(`Lookup finished for ${socket.application.name}`); + cachedApps[appId] = socket.application; + return socket.application; + } catch { + console.log(`Lookup failed for ${appId}`); + return null; + } +} + +function isValidSnowflake(v: string) { + const regex = /^\d{17,20}$/; + return regex.test(v) && !Number.isNaN(SnowflakeUtils.extractTimestamp(v)); +} + +export function ReplaceTutorial() { + return ( + <> + How to get an Application ID + + The method of getting an app's id will differ depending on what app it is. If the source code is available you can most likely find it inside the app's repository. + + + Another method is to start the app in question, then open Discord's console and look for a log from RPCServer saying something like + "cmd: 'SET_ACTIVITY'" with your app's name somewhere inside + + + + Note: ActivityTypes other than Playing will only show timestamps on Mobile. It's a Discord issue. + + + ); +} + +export function ReplaceSettings({ appIds, update, save }: SettingsProps) { + async function onChange(val: string | boolean, index: number, key: string) { + if (index === appIds.length - 1) + appIds.push(makeEmptyAppId()); + + appIds[index][key] = val; + + if (val && key === "appId") { + const tempApp = await lookupApp(val.toString()); + appIds[index].appName = tempApp?.name || "Unknown"; + } + + if (appIds[index].appId === "" && index !== appIds.length - 1) + appIds.splice(index, 1); + + save(); + update(); + } + + return ( + <> + { + appIds.map((setting, i) => + + { + onChange(value, i, "enabled"); + }} + className={Margins.bottom16} + hideBorder={true} + > + {setting.appName} + + Application ID + { + onChange(v, i, "appId"); + }} + validate={v => + !v || isValidSnowflake(v) || "Invalid appId, must be a snowflake" + } + /> + {setting.activityType === ActivityType.STREAMING && + <> + Stream URL + { + onChange(v, i, "streamUrl"); + }} + validate={st => !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(st) && "Only Twitch and Youtube urls will work." || true} + /> + } + New activity type +