diff --git a/src/equicordplugins/animalese/README.md b/src/equicordplugins/animalese/README.md deleted file mode 100644 index 02a59c96b..000000000 --- a/src/equicordplugins/animalese/README.md +++ /dev/null @@ -1,21 +0,0 @@ -## vencord-animalese -animalese yapping in your discord client - -> [!WARNING] -> don't go to Vencord support if you can't install this. - -### demo - -https://github.com/user-attachments/assets/3f1fa925-bbd2-451d-b63e-ea14dbe4be44 - -### settings - -##### settings used in the video -![CleanShot 2024-12-26 at 22 11 49@2x](https://github.com/user-attachments/assets/35895fcc-d703-42cb-b0ef-bdef39ab15be) - - -you can change the settings in the settings but otherwise, enjoy the yap - -### credits - -- [sounds: animalese-generator by equalo-official](https://github.com/equalo-official/animalese-generator/tree/master) diff --git a/src/equicordplugins/demonstration/README.md b/src/equicordplugins/demonstration/README.md deleted file mode 100644 index 431fcf8ab..000000000 --- a/src/equicordplugins/demonstration/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Demonstration - -Helps for taking theme screenshots diff --git a/src/equicordplugins/demonstration/index.tsx b/src/equicordplugins/demonstration/index.tsx deleted file mode 100644 index 54c2b459e..000000000 --- a/src/equicordplugins/demonstration/index.tsx +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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 { classNameFactory } from "@api/Styles"; -import { Devs, EquicordDevs } from "@utils/constants"; -import { closeModal, ModalCloseButton, ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal"; -import definePlugin, { OptionType } from "@utils/types"; -import { Button, Forms, Switch, Text } from "@webpack/common"; - -// definitely not stolen from glide :P -async function injectCSS() { - var elementToRemove = document.getElementById("DemonstrationStyle"); - if (elementToRemove) { - elementToRemove.remove(); - } - const styleElement = document.createElement("style"); - styleElement.id = "DemonstrationStyle"; - const content = await fetch("https://minidiscordthemes.github.io/Demonstration/Demonstration.theme.css").then(e => e.text()); - styleElement.textContent = content; - document.documentElement.appendChild(styleElement); -} - -const validKeycodes = [ - "Backspace", "Tab", "Enter", "ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", "AltLeft", "AltRight", "Pause", "CapsLock", - "Escape", "Space", "PageUp", "PageDown", "End", "Home", "ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown", "PrintScreen", "Insert", - "Delete", "Digit0", "Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9", "KeyA", "KeyB", "KeyC", - "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", - "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ", "MetaLeft", "MetaRight", "ContextMenu", "Numpad0", "Numpad1", "Numpad2", "Numpad3", - "Numpad4", "Numpad5", "Numpad6", "Numpad7", "Numpad8", "Numpad9", "NumpadMultiply", "NumpadAdd", "NumpadSubtract", "NumpadDecimal", - "NumpadDivide", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "NumLock", "ScrollLock" -]; - -const settings = definePluginSettings({ - keyBind: { - description: "The key to toggle the theme when pressed", - type: OptionType.STRING, - default: "F6", - isValid: (value: string) => { - if (validKeycodes.includes(value)) { - return true; - } - return false; - } - }, - soundVolume: { - description: "How loud the toggle sound is (0 to disable)", - type: OptionType.SLIDER, - default: 0.5, - markers: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] - }, - showConfirmationModal: { - description: "Show modal to remind shortcut", - type: OptionType.BOOLEAN, - default: true, - } -}); - -function ToggleModal() { - const value = !settings.use(["showConfirmationModal"]).showConfirmationModal; - return ( - { settings.store.showConfirmationModal = !v; }}> - Disable modal? - - ); -} - -function handleToggle() { - const style = document.getElementById("DemonstrationStyle"); - if (style != null) { - style.remove(); - playSound("https://github.com/Equicord/Equibored/raw/main/sounds/demonstration/wp5rpz.wav"); - } - else { - if (settings.store.showConfirmationModal) { - const cl = classNameFactory("vc-demonstration-modal"); - - const key = openModal(props => ( - - - Demonstration - closeModal(key)}> - - - - This will censor all text! To disable this, remember the shortcut: - - - {settings.store.keyBind} - - - - - - )); - } else { - injectCSS(); - playSound("https://github.com/Equicord/Equibored/raw/main/sounds/demonstration/ckz46t.wav"); - } - } -} - -function handleKeydown(event: KeyboardEvent) { - if (event.code !== settings.store.keyBind) { return; } - handleToggle(); -} - -async function playSound(url: string) { - const audio = new Audio(url); - audio.volume = settings.store.soundVolume; - await audio.play().catch(error => { - console.error("Error playing sound:", error); - }); - audio.remove(); -} - -export default definePlugin({ - name: "Demonstration", - description: "Plugin for taking theme screenshots - censors identifying images and text.", - authors: [Devs.Samwich, EquicordDevs.Panniku], - settings, - toolboxActions: { - "Toggle Demonstration": (() => handleToggle()) - }, - settingsAboutComponent: () => { - return ( - <> - To change your keycode, check out this tool! - - ); - }, - start() { - document.addEventListener("keydown", handleKeydown); - }, - stop() { - document.removeEventListener("keydown", handleKeydown); - } -}); diff --git a/src/equicordplugins/equicordHelper/index.tsx b/src/equicordplugins/equicordHelper/index.tsx index bd9672f90..8c56cb418 100644 --- a/src/equicordplugins/equicordHelper/index.tsx +++ b/src/equicordplugins/equicordHelper/index.tsx @@ -18,19 +18,27 @@ import "@equicordplugins/_misc/styles.css"; +import { definePluginSettings, Settings } from "@api/Settings"; import { Devs, EQUICORD_HELPERS, EquicordDevs, GUILD_ID } from "@utils/constants"; import { Logger } from "@utils/Logger"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; import { Button, ChannelStore, Flex, Forms, GuildMemberStore, showToast, Toasts } from "@webpack/common"; import { JSX } from "react"; import { toggleEnabled } from "./utils"; +export const settings = definePluginSettings({ + noMirroredCamera: { + type: OptionType.BOOLEAN, + description: "Prevents the camera from being mirrored on your screen", + default: false, + }, +}); export default definePlugin({ name: "EquicordHelper", description: "Fixes some misc issues with discord", - authors: [Devs.thororen, EquicordDevs.nyx, EquicordDevs.Naibuu], + authors: [Devs.thororen, Devs.nyx, EquicordDevs.Naibuu], settingsAboutComponent: () => <> This plugin was created to allow us as the Equicord Team & Contributors @@ -58,8 +66,41 @@ export default definePlugin({ replace: "return $1;" } ] + }, + // When focused on voice channel or group chat voice call + { + find: /\i\?\i.\i.SELF_VIDEO/, + replacement: { + match: /mirror:\i/, + replace: "mirror:!1" + }, + predicate: () => settings.store.noMirroredCamera + }, + // Popout camera when not focused on voice channel + { + find: ".mirror]:", + replacement: { + match: /\[(\i).mirror]:\i/, + replace: "[$1.mirror]:!1" + }, + predicate: () => settings.store.noMirroredCamera + }, + // Overriding css on Preview Camera/Change Video Background popup + { + find: ".cameraPreview,", + replacement: { + match: /className:\i.camera,/, + replace: "$&style:{transform: \"scalex(1)\"}," + }, + predicate: () => settings.store.noMirroredCamera } ], + start() { + if (Settings.plugins?.NoMirroredCamera?.enabled) { + Settings.plugins.NoMirroredCamera.enabled = false; + settings.store.noMirroredCamera = true; + } + }, renderMessageAccessory(props) { const buttons = [] as JSX.Element[]; diff --git a/src/equicordplugins/equissant/README.md b/src/equicordplugins/equissant/README.md deleted file mode 100644 index d6922399f..000000000 --- a/src/equicordplugins/equissant/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Equissant - -Plays crossant every 10 clicks diff --git a/src/equicordplugins/equissant/index.ts b/src/equicordplugins/equissant/index.ts deleted file mode 100644 index 1975caf77..000000000 --- a/src/equicordplugins/equissant/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and Megumin - * - * 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 { Devs, EquicordDevs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -let clickCount = 0; - -function play() { - clickCount++; - - if (clickCount % 10 === 0) { - const audio = new Audio("https://github.com/Equicord/Equibored/raw/main/sounds/equissant/croissant.mp3"); - audio.play(); - } -} - -export default definePlugin({ - name: "Equissant", - description: "Crossant every 10 clicks :trolley:", - authors: [EquicordDevs.SomeAspy, Devs.thororen], - start() { - document.addEventListener("click", play); - }, - stop() { - document.removeEventListener("click", play); - } -}); diff --git a/src/equicordplugins/exportContacts/README.md b/src/equicordplugins/exportContacts/README.md deleted file mode 100644 index 620b8a296..000000000 --- a/src/equicordplugins/exportContacts/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# ExportContacts - -Exports your friends list onto your clipboard diff --git a/src/equicordplugins/exportContacts/index.tsx b/src/equicordplugins/exportContacts/index.tsx deleted file mode 100644 index 6a5df39f5..000000000 --- a/src/equicordplugins/exportContacts/index.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2024 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 "./styles.css"; - -import ErrorBoundary from "@components/ErrorBoundary"; -import { copyToClipboard } from "@utils/clipboard"; -import { EquicordDevs } from "@utils/constants"; -import definePlugin from "@utils/types"; -import { Toasts } from "@webpack/common"; - -interface User { - id: string; - username: string; - avatar: string; - discriminator: string; - publicFlags: number; - avatarDecorationData?: any; - globalName: string; -} - -interface ContactsList { - id: string; - type: number; - nickname?: any; - user: User; - since: string; -} - -// for type parameter, it takes in a number that determines the type of the contact -// 1 is friends added -// 2 is blocked users -// 3 is incoming friend requests -// 4 is outgoing friend requests -function getUsernames(contacts: ContactsList[], type: number): string[] { - return contacts - // only select contacts that are the specified type - .filter(e => e.type === type) - // return the username, and discriminator if necessary - .map(e => e.user.discriminator === "0" ? e.user.username : e.user.username + "#" + e.user.discriminator); -} - -export default definePlugin({ - name: "ExportContacts", - description: "Export a list of friends to your clipboard. Adds a new button to the menu bar for the friends tab.", - authors: [EquicordDevs.dat_insanity], - patches: [ - { - find: "fetchRelationships(){", - replacement: { - match: /(\.then\(\i)=>(\i\.\i\.dispatch\({type:"LOAD_RELATIONSHIPS_SUCCESS",relationships:(\i\.body)}\))/, - replace: "$1=>{$2; $self.getContacts($3)}" - } - }, - { - find: "[role=\"tab\"][aria-disabled=\"false\"]", - replacement: { - match: /("aria-label":(\i).{0,25})(\i)\.Children\.map\((\i),this\.renderChildren\)/, - replace: - "$1($3 && $3.Children" + - "? ($2 === 'Friends'" + - "? [...$3.Children.map($4, this.renderChildren), $self.addExportButton()]" + - ": [...$3.Children.map($4, this.renderChildren)])" + - ": $3.map($4, this.renderChildren))" - } - } - ], - - getContacts(contacts: ContactsList[]) { - this.contactList = { - friendsAdded: [...getUsernames(contacts, 1)], - blockedUsers: [...getUsernames(contacts, 2)], - incomingFriendRequests: [...getUsernames(contacts, 3)], - outgoingFriendRequests: [...getUsernames(contacts, 4)] - }; - }, - - addExportButton() { - return - - ; - }, - - copyContactToClipboard() { - if (this.contactList) { - copyToClipboard(JSON.stringify(this.contactList)); - Toasts.show({ - message: "Contacts copied to clipboard successfully.", - type: Toasts.Type.SUCCESS, - id: Toasts.genId(), - options: { - duration: 3000, - position: Toasts.Position.BOTTOM - } - }); - return; - } - // reason why you need to click the all tab is because the data is extracted during - // the request itself when you fetch all your friends. this is done to avoid sending a - // manual request to discord, which may raise suspicion and might even get you terminated. - Toasts.show({ - message: "Contact list is undefined. Click on the \"All\" tab before exporting.", - type: Toasts.Type.FAILURE, - id: Toasts.genId(), - options: { - duration: 3000, - position: Toasts.Position.BOTTOM - } - }); - } -}); diff --git a/src/equicordplugins/exportMessages/index.tsx b/src/equicordplugins/exportMessages/index.tsx index 79c978630..41739fe4d 100644 --- a/src/equicordplugins/exportMessages/index.tsx +++ b/src/equicordplugins/exportMessages/index.tsx @@ -4,20 +4,31 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import "./styles.css"; + import { showNotification } from "@api/Notifications"; -import { definePluginSettings } from "@api/Settings"; +import { definePluginSettings, Settings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { copyToClipboard } from "@utils/clipboard"; import { EquicordDevs } from "@utils/constants"; import { showItemInFolder } from "@utils/native"; import definePlugin, { OptionType } from "@utils/types"; import { saveFile } from "@utils/web"; import { Message } from "@vencord/discord-types"; -import { Menu } from "@webpack/common"; +import { Menu, Toasts } from "@webpack/common"; + +import { ContactsList } from "./types"; const settings = definePluginSettings({ openFileAfterExport: { type: OptionType.BOOLEAN, description: "Open the exported file in the default file handler after export", default: true + }, + exportContacts: { + type: OptionType.BOOLEAN, + description: "Export a list of friends to your clipboard. Adds a new button to the menu bar for the friends tab.", + default: false } }); @@ -102,12 +113,92 @@ const messageContextMenuPatch = (children: Array | null> ); }; +// for type parameter, it takes in a number that determines the type of the contact +// 1 is friends added +// 2 is blocked users +// 3 is incoming friend requests +// 4 is outgoing friend requests +function getUsernames(contacts: ContactsList[], type: number): string[] { + return contacts + // only select contacts that are the specified type + .filter(e => e.type === type) + // return the username, and discriminator if necessary + .map(e => e.user.discriminator === "0" ? e.user.username : e.user.username + "#" + e.user.discriminator); +} + export default definePlugin({ name: "ExportMessages", description: "Allows you to export any message to a file", - authors: [EquicordDevs.veygax], + authors: [EquicordDevs.veygax, EquicordDevs.dat_insanity], settings, contextMenus: { "message": messageContextMenuPatch + }, + patches: [ + { + find: "fetchRelationships(){", + replacement: { + match: /(\.then\(\i)=>(\i\.\i\.dispatch\({type:"LOAD_RELATIONSHIPS_SUCCESS",relationships:(\i\.body)}\))/, + replace: "$1=>{$2; $self.getContacts($3)}" + } + }, + { + find: "[role=\"tab\"][aria-disabled=\"false\"]", + replacement: { + match: /("aria-label":(\i).{0,25})(\i)\.Children\.map\((\i),this\.renderChildren\)/, + replace: + "$1($3 && $3.Children" + + "? ($2 === 'Friends'" + + "? [...$3.Children.map($4, this.renderChildren), $self.addExportButton()]" + + ": [...$3.Children.map($4, this.renderChildren)])" + + ": $3.map($4, this.renderChildren))" + } + } + ], + start() { + if (Settings.plugins?.ExportContacts?.enabled) { + Settings.plugins.ExportContacts.enabled = false; + settings.store.exportContacts = true; + } + }, + getContacts(contacts: ContactsList[]) { + this.contactList = { + friendsAdded: [...getUsernames(contacts, 1)], + blockedUsers: [...getUsernames(contacts, 2)], + incomingFriendRequests: [...getUsernames(contacts, 3)], + outgoingFriendRequests: [...getUsernames(contacts, 4)] + }; + }, + addExportButton() { + return + + ; + }, + copyContactToClipboard() { + if (this.contactList) { + copyToClipboard(JSON.stringify(this.contactList)); + Toasts.show({ + message: "Contacts copied to clipboard successfully.", + type: Toasts.Type.SUCCESS, + id: Toasts.genId(), + options: { + duration: 3000, + position: Toasts.Position.BOTTOM + } + }); + return; + } + // reason why you need to click the all tab is because the data is extracted during + // the request itself when you fetch all your friends. this is done to avoid sending a + // manual request to discord, which may raise suspicion and might even get you terminated. + Toasts.show({ + message: "Contact list is undefined. Click on the \"All\" tab before exporting.", + type: Toasts.Type.FAILURE, + id: Toasts.genId(), + options: { + duration: 3000, + position: Toasts.Position.BOTTOM + } + }); } }); diff --git a/src/equicordplugins/exportContacts/styles.css b/src/equicordplugins/exportMessages/styles.css similarity index 99% rename from src/equicordplugins/exportContacts/styles.css rename to src/equicordplugins/exportMessages/styles.css index 3769882ac..06387ea8a 100644 --- a/src/equicordplugins/exportContacts/styles.css +++ b/src/equicordplugins/exportMessages/styles.css @@ -24,4 +24,4 @@ .export-contacts-button:hover { background-color: #454fbf; -} +} \ No newline at end of file diff --git a/src/equicordplugins/exportMessages/types.ts b/src/equicordplugins/exportMessages/types.ts new file mode 100644 index 000000000..d13db0e0e --- /dev/null +++ b/src/equicordplugins/exportMessages/types.ts @@ -0,0 +1,23 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export interface User { + id: string; + username: string; + avatar: string; + discriminator: string; + publicFlags: number; + avatarDecorationData?: any; + globalName: string; +} + +export interface ContactsList { + id: string; + type: number; + nickname?: any; + user: User; + since: string; +} diff --git a/src/equicordplugins/keyboardSounds/index.ts b/src/equicordplugins/keyboardSounds/index.ts deleted file mode 100644 index 2b6d292aa..000000000 --- a/src/equicordplugins/keyboardSounds/index.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; - -let click1, click2, click3, backspace; -let sounds = { - click1, - click2, - click3, - backspace -}; - -const ignoredKeys = ["CapsLock", "ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", "AltLeft", "AltRight", "MetaLeft", "MetaRight", "ArrowUp", "ArrowRight", "ArrowLeft", "ArrowDown", "MediaPlayPause", "MediaStop", "MediaTrackNext", "MediaTrackPrevious", "MediaSelect", "MediaEject", "MediaVolumeUp", "MediaVolumeDown", "AudioVolumeUp", "AudioVolumeDown"]; - -const keydown = (e: KeyboardEvent) => { - if (ignoredKeys.includes(e.code)) return; - for (const sound of Object.values(sounds)) sound.pause(); - if (e.code === "Backspace") { - sounds.backspace.currentTime = 0; - sounds.backspace.play(); - } else { - const click = sounds[`click${Math.floor(Math.random() * 3) + 1}`]; - click.currentTime = 0; - click.play(); - } -}; - -export default definePlugin({ - name: "KeyboardSounds", - description: "Adds the Opera GX Keyboard Sounds to Discord", - authors: [Devs.HypedDomi], - start: () => { - click1 = new Audio("https://github.com/Equicord/Equibored/raw/main/sounds/keyboard/click1.wav"); - click2 = new Audio("https://github.com/Equicord/Equibored/raw/main/sounds/keyboard/click2.wav"); - click3 = new Audio("https://github.com/Equicord/Equibored/raw/main/sounds/keyboard/click3.wav"); - backspace = new Audio("https://github.com/Equicord/Equibored/raw/main/sounds/keyboard/backspace.wav"); - sounds = { - click1, - click2, - click3, - backspace, - }; - document.addEventListener("keydown", keydown); - }, - stop: () => document.removeEventListener("keydown", keydown), - options: { - volume: { - description: "Volume", - type: OptionType.SLIDER, - markers: [0, 100], - stickToMarkers: false, - default: 100, - onChange: value => { for (const sound of Object.values(sounds)) sound.volume = value / 100; } - } - } -}); diff --git a/src/equicordplugins/moreCommands/commands/misc.ts b/src/equicordplugins/moreCommands/commands/misc.ts index 3a29f3cea..f72cc901c 100644 --- a/src/equicordplugins/moreCommands/commands/misc.ts +++ b/src/equicordplugins/moreCommands/commands/misc.ts @@ -5,10 +5,10 @@ */ import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, OptionalMessageOption, RequiredMessageOption, sendBotMessage } from "@api/Commands"; +import { sendMessage } from "@utils/discord"; import { UserStore } from "@webpack/common"; -import { fromMorse, getCuteAnimeBoys, getCuteNeko, getCutePats, isMorse, makeFreaky, mock, toMorse } from "../utils"; -import { sendMessage } from "@utils/discord"; +import { fromMorse, getCuteAnimeBoys, getCuteNeko, getCutePats, isMorse, makeFreaky, mock, toMorse, uwuify } from "../utils"; export default [ { @@ -130,5 +130,14 @@ export default [ content: `${output}` }; }, - } + }, + { + name: "uwuify", + description: "uwuifies your messages", + options: [RequiredMessageOption], + + execute: opts => ({ + content: uwuify(findOption(opts, "message", "")), + }), + }, ]; diff --git a/src/equicordplugins/moreCommands/index.ts b/src/equicordplugins/moreCommands/index.ts index c3cb2baf7..cd93a11b0 100644 --- a/src/equicordplugins/moreCommands/index.ts +++ b/src/equicordplugins/moreCommands/index.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { addMessagePreEditListener, addMessagePreSendListener, MessageObject, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents"; import { migratePluginSettings } from "@api/Settings"; import { Devs, EquicordDevs } from "@utils/constants"; import definePlugin from "@utils/types"; @@ -25,13 +26,13 @@ import misc from "./commands/misc"; import system from "./commands/system"; import text from "./commands/text"; import time from "./commands/time"; -import { settings } from "./utils"; +import { settings, uwuify, uwuifyArray } from "./utils"; migratePluginSettings("MoreCommands", "CuteAnimeBoys", "CuteNekos", "CutePats", "Slap"); export default definePlugin({ name: "MoreCommands", description: "Adds various fun and useful commands", - authors: [Devs.Arjix, Devs.amy, Devs.Samu, EquicordDevs.zyqunix, EquicordDevs.ShadyGoat, Devs.thororen, Devs.Korbo], + authors: [Devs.Arjix, Devs.amy, Devs.Samu, EquicordDevs.zyqunix, EquicordDevs.ShadyGoat, Devs.thororen, Devs.Korbo, Devs.nyx, Devs.amy], settings, commands: [ ...choice, @@ -39,5 +40,40 @@ export default definePlugin({ ...text, ...time, ...misc, - ] + ], + patches: [ + { + find: ".isPureReactComponent=!0;", + predicate: () => settings.store.uwuEverything && settings.store.uwuify, + replacement: { + match: /(?<=.defaultProps\)void 0.{0,60})(\i)\)/, + replace: "$self.uwuifyProps($1))" + } + } + ], + uwuifyProps(props: any) { + if (!props.children) return props; + if (typeof props.children === "string") props.children = uwuify(props.children); + else if (Array.isArray(props.children)) props.children = uwuifyArray(props.children); + return props; + }, + + onSend(msg: MessageObject) { + // Only run when it's enabled + if (settings.store.uwuEveryMessage && settings.store.uwuify) { + msg.content = uwuify(msg.content); + } + }, + + start() { + this.preSend = addMessagePreSendListener((_, msg) => this.onSend(msg)); + this.preEdit = addMessagePreEditListener((_cid, _mid, msg) => + this.onSend(msg) + ); + }, + + stop() { + removeMessagePreSendListener(this.preSend); + removeMessagePreEditListener(this.preEdit); + }, }); diff --git a/src/equicordplugins/moreCommands/utils.ts b/src/equicordplugins/moreCommands/utils.ts index f539d9636..e5d0b3068 100644 --- a/src/equicordplugins/moreCommands/utils.ts +++ b/src/equicordplugins/moreCommands/utils.ts @@ -12,6 +12,18 @@ export const settings = definePluginSettings({ type: OptionType.BOOLEAN, description: "Add ๐Ÿ‘… or โค๏ธ at the end", default: true + }, + uwuEveryMessage: { + description: "Make every single message uwuified", + type: OptionType.BOOLEAN, + default: false, + restartNeeded: false + }, + uwuEverything: { + description: "Makes *all* text uwuified - really bad idea", + type: OptionType.BOOLEAN, + default: false, + restartNeeded: true } }); @@ -87,3 +99,112 @@ export const fromMorse = (text: string) => { }; export const isMorse = (text: string) => /^[.\-/ ]+$/.test(text); + +export const endings = [ + "rawr x3", + "OwO", + "UwU", + "o.O", + "-.-", + ">w<", + "(โ‘…ห˜๊’ณห˜)", + "(๊ˆแด—๊ˆ)", + "(ห˜ฯ‰ห˜)", + "(U แต• Uโ)", + "ฯƒฯ‰ฯƒ", + "รฒฯ‰รณ", + "(///หฌ///โœฟ)", + "(U ๏น U)", + "( อกo ฯ‰ อกo )", + "ส˜wส˜", + ":3", + ":3", // important enough to have twice + ":3", // important enough to have thrice + "XD", + "nyaa~~", + "mya", + ">_<", + "๐Ÿ˜ณ", + "๐Ÿฅบ", + "๐Ÿ˜ณ๐Ÿ˜ณ๐Ÿ˜ณ", + "rawr", + "^^", + "^^;;", + "(ห† ๏ปŒ ห†)โ™ก", + "^โ€ข๏ปŒโ€ข^", + "/(^โ€ขฯ‰โ€ข^)", + "(โœฟoฯ‰o)" +]; + +export const replacements = [ + ["small", "smol"], + ["cute", "kawaii"], + ["fluff", "floof"], + ["love", "luv"], + ["stupid", "baka"], + ["what", "nani"], + ["meow", "nya"], + ["hello", "hewwo"], +]; + +export function selectRandomElement(arr) { + // generate a random index based on the length of the array + const randomIndex = Math.floor(Math.random() * arr.length); + + // return the element at the randomly generated index + return arr[randomIndex]; +} +export const isOneCharacterString = (str: string): boolean => { + return str.split("").every((char: string) => char === str[0]); +}; + +export function replaceString(inputString) { + let replaced = false; + for (const replacement of replacements) { + const regex = new RegExp(`\\b${replacement[0]}\\b`, "gi"); + if (regex.test(inputString)) { + inputString = inputString.replace(regex, replacement[1]); + replaced = true; + } + } + return replaced ? inputString : false; +} + +export function uwuify(message: string): string { + const rule = /\S+|\s+/g; + const words: string[] | null = message.match(rule); + let answer = ""; + + if (words === null) return ""; + + for (let i = 0; i < words.length; i++) { + if (isOneCharacterString(words[i]) || words[i].startsWith("https://")) { + answer += words[i]; + continue; + } + + if (!replaceString(words[i])) { + answer += words[i] + .replace(/n(?=[aeo])/g, "ny") + .replace(/l|r/g, "w"); + } else answer += replaceString(words[i]); + + } + + answer += " " + selectRandomElement(endings); + return answer; +} + +export function uwuifyArray(arr) { + const newArr = [...arr]; + + newArr.forEach((item, index) => { + if (Array.isArray(item)) { + newArr[index] = uwuifyArray(item); + } else if (typeof item === "string") { + newArr[index] = uwuify(item); + } + }); + + return newArr; +} diff --git a/src/equicordplugins/moyai/index.ts b/src/equicordplugins/moyai/index.ts deleted file mode 100644 index 2a5ad52d1..000000000 --- a/src/equicordplugins/moyai/index.ts +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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 { definePluginSettings } from "@api/Settings"; -import { Devs } from "@utils/constants"; -import { sleep } from "@utils/misc"; -import definePlugin, { makeRange, OptionType } from "@utils/types"; -import { Message, ReactionEmoji } from "@vencord/discord-types"; -import { RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common"; - -interface IMessageCreate { - type: "MESSAGE_CREATE"; - optimistic: boolean; - isPushNotification: boolean; - channelId: string; - message: Message; -} - -interface IReactionAdd { - type: "MESSAGE_REACTION_ADD"; - optimistic: boolean; - channelId: string; - messageId: string; - messageAuthorId: string; - userId: "195136840355807232"; - emoji: ReactionEmoji; -} - -interface IVoiceChannelEffectSendEvent { - type: string; - emoji?: ReactionEmoji; // Just in case... - channelId: string; - userId: string; - animationType: number; - animationId: number; -} - -const MOYAI = "๐Ÿ—ฟ"; -const MOYAI_URL = "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.mp3"; -const MOYAI_URL_HD = "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.wav"; - -const settings = definePluginSettings({ - volume: { - description: "Volume of the ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ", - type: OptionType.SLIDER, - markers: makeRange(0, 1, 0.1), - default: 0.5, - stickToMarkers: false - }, - quality: { - description: "Quality of the ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ", - type: OptionType.SELECT, - options: [ - { label: "Normal", value: "Normal", default: true }, - { label: "HD", value: "HD" } - ], - }, - triggerWhenUnfocused: { - description: "Trigger the ๐Ÿ—ฟ even when the window is unfocused", - type: OptionType.BOOLEAN, - default: true - }, - ignoreBots: { - description: "Ignore bots", - type: OptionType.BOOLEAN, - default: true - }, - ignoreBlocked: { - description: "Ignore blocked users", - type: OptionType.BOOLEAN, - default: true - } -}); - -export default definePlugin({ - name: "Moyai", - authors: [Devs.Megu, Devs.Nuckyz], - description: "๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ", - settings, - - flux: { - async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) { - if (optimistic || type !== "MESSAGE_CREATE") return; - if (message.state === "SENDING") return; - if (settings.store.ignoreBots && message.author?.bot) return; - if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(message.author?.id)) return; - if (!message.content) return; - if (channelId !== SelectedChannelStore.getChannelId()) return; - - const moyaiCount = getMoyaiCount(message.content); - - for (let i = 0; i < moyaiCount; i++) { - boom(); - await sleep(300); - } - }, - - MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, messageAuthorId, emoji }: IReactionAdd) { - if (optimistic || type !== "MESSAGE_REACTION_ADD") return; - if (settings.store.ignoreBots && UserStore.getUser(userId)?.bot) return; - if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(messageAuthorId)) return; - if (channelId !== SelectedChannelStore.getChannelId()) return; - - const name = emoji.name.toLowerCase(); - if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; - - boom(); - }, - - VOICE_CHANNEL_EFFECT_SEND({ emoji }: IVoiceChannelEffectSendEvent) { - if (!emoji?.name) return; - const name = emoji.name.toLowerCase(); - if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; - - boom(); - } - } -}); - -function countOccurrences(sourceString: string, subString: string) { - let i = 0; - let lastIdx = 0; - while ((lastIdx = sourceString.indexOf(subString, lastIdx) + 1) !== 0) - i++; - - return i; -} - -function countMatches(sourceString: string, pattern: RegExp) { - if (!pattern.global) - throw new Error("pattern must be global"); - - let i = 0; - while (pattern.test(sourceString)) - i++; - - return i; -} - -const customMoyaiRe = //gi; - -function getMoyaiCount(message: string) { - const count = countOccurrences(message, MOYAI) - + countMatches(message, customMoyaiRe); - - return Math.min(count, 10); -} - -function boom() { - if (!settings.store.triggerWhenUnfocused && !document.hasFocus()) return; - const audioElement = document.createElement("audio"); - - audioElement.src = settings.store.quality === "HD" - ? MOYAI_URL_HD - : MOYAI_URL; - - audioElement.volume = settings.store.volume; - audioElement.play(); -} diff --git a/src/equicordplugins/noDeleteSafety/index.tsx b/src/equicordplugins/noDeleteSafety/index.tsx deleted file mode 100644 index d9d6247b6..000000000 --- a/src/equicordplugins/noDeleteSafety/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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"; -import { findByPropsLazy } from "@webpack"; -import { Alerts, Button, GuildStore } from "@webpack/common"; -const DeleteGuild = findByPropsLazy("deleteGuild", "sendTransferOwnershipPincode").deleteGuild; - -function GetPropsAndDeleteGuild(id) { - const GotGuild = GuildStore.getGuild(id); - if (!GotGuild) return; - - DeleteGuild(id, GotGuild.name); -} - -const settings = definePluginSettings( - { - confirmModal: { - type: OptionType.BOOLEAN, - description: "Should a \"are you sure you want to delete\" modal be shown?", - default: true - }, - }); - -export default definePlugin({ - name: "NoDeleteSafety", - description: "Removes the \"enter server name\" requirement when deleting a server", - authors: [Devs.Samwich], - settings, - async HandleGuildDeleteModal(server) { - if (settings.store.confirmModal) { - return Alerts.show({ title: "Delete server?", body:

It's permanent, if that wasn't obvious.

, confirmColor: Button.Colors.RED, confirmText: "Delete", onConfirm: () => GetPropsAndDeleteGuild(server.id), cancelText: "Cancel" }); - } else { - return GetPropsAndDeleteGuild(server.id); - } - }, - patches: [ - { - find: ".DELETE,onClick(){let", - replacement: { - match: /let\{name:\i\}=\i\.guild/, - replace: "$self.HandleGuildDeleteModal($1);$&" - } - } - ] -}); diff --git a/src/equicordplugins/noMirroredCamera/index.tsx b/src/equicordplugins/noMirroredCamera/index.tsx deleted file mode 100644 index 821c3efeb..000000000 --- a/src/equicordplugins/noMirroredCamera/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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"; - -export default definePlugin({ - name: "NoMirroredCamera", - description: "Prevents the camera from being mirrored on your screen", - authors: [Devs.nyx], - - patches: [ - // When focused on voice channel or group chat voice call - { - find: /\i\?\i.\i.SELF_VIDEO/, - replacement: { - match: /mirror:\i/, - replace: "mirror:!1" - } - }, - // Popout camera when not focused on voice channel - { - find: ".mirror]:", - replacement: { - match: /\[(\i).mirror]:\i/, - replace: "[$1.mirror]:!1" - } - }, - // Overriding css on Preview Camera/Change Video Background popup - { - find: ".cameraPreview,", - replacement: { - match: /className:\i.camera,/, - replace: "$&style:{transform: \"scalex(1)\"}," - } - } - ] -}); diff --git a/src/equicordplugins/remix/index.tsx b/src/equicordplugins/remix/index.tsx index 13c2dbdca..c45a34b2c 100644 --- a/src/equicordplugins/remix/index.tsx +++ b/src/equicordplugins/remix/index.tsx @@ -5,7 +5,8 @@ */ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; -import { definePluginSettings } from "@api/Settings"; +import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents"; +import { definePluginSettings, Settings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import { PaintbrushIcon } from "@components/Icons"; import { EquicordDevs } from "@utils/constants"; @@ -106,12 +107,20 @@ const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: true, }, + remixMe: { + description: "Turns every single message with attachment to have remix tag", + type: OptionType.BOOLEAN, + default: false, + } }); +const handleMessage: MessageSendListener = (_, __, ex) => ex.uploads && ex.uploads.forEach(att => att.isRemix = true); + + export default definePlugin({ name: "Remix", description: "Adds Remix to Desktop", - authors: [EquicordDevs.MrDiamond], + authors: [EquicordDevs.MrDiamond, EquicordDevs.meowabyte], settings, contextMenus: { "channel-attach": UploadContextMenuPatch, @@ -124,9 +133,19 @@ export default definePlugin({ await requireSettingsMenu(); enableStyle(css); + if (Settings.plugins?.RemixMe?.enabled) { + Settings.plugins.RemixMe.enabled = false; + settings.store.remixMe = true; + } + if (settings.store.remixMe) { + addMessagePreSendListener(handleMessage); + } }, stop() { disableStyle(css); + if (settings.store.remixMe) { + removeMessagePreSendListener(handleMessage); + } }, }); diff --git a/src/equicordplugins/remixMe/index.tsx b/src/equicordplugins/remixMe/index.tsx deleted file mode 100644 index 08d4916ae..000000000 --- a/src/equicordplugins/remixMe/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2023 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents"; -import { EquicordDevs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -const handleMessage: MessageSendListener = (_, __, ex) => ex.uploads && ex.uploads.forEach(att => att.isRemix = true); - -export default definePlugin({ - name: "RemixMe", - description: "Turns every single message with attachment to have remix tag", - authors: [EquicordDevs.meowabyte], - start() { - addMessagePreSendListener(handleMessage); - }, - stop() { - removeMessagePreSendListener(handleMessage); - } -}); diff --git a/src/equicordplugins/animalese/index.ts b/src/equicordplugins/soundTroll/animalese.ts similarity index 50% rename from src/equicordplugins/animalese/index.ts rename to src/equicordplugins/soundTroll/animalese.ts index 265c2e0d6..1def30cc4 100644 --- a/src/equicordplugins/animalese/index.ts +++ b/src/equicordplugins/soundTroll/animalese.ts @@ -1,80 +1,38 @@ /* * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors + * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ -import { definePluginSettings } from "@api/Settings"; -import { EquicordDevs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; -import { SelectedChannelStore, UserStore } from "@webpack/common"; +import { settings } from "./settings"; -const settings = definePluginSettings({ - volume: { - type: OptionType.SLIDER, - description: "Volume of the animalese sound", - default: 0.5, - markers: [0, 0.1, 0.25, 0.5, 0.6, 0.75, 1], - }, - speed: { - type: OptionType.SLIDER, - description: "Speed of the animalese sound", - default: 1, - markers: [0.5, 0.75, 1, 1.25, 1.5], - }, - pitch: { - type: OptionType.SLIDER, - description: "Pitch multiplier", - default: 1, - markers: [0.75, 0.8, 0.85, 1, 1.15, 1.25, 1.35, 1.5], - }, - messageLengthLimit: { - type: OptionType.NUMBER, - description: "Maximum length of message to process", - default: 50, - }, - processOwnMessages: { - type: OptionType.BOOLEAN, - description: "Enable to yap your own messages too", - default: true, - }, - soundQuality: { - type: OptionType.SELECT, - description: "Quality of sound to use", - options: [ - { - label: "High", - value: "high", - default: true - }, - { - label: "Medium", - value: "med" - }, - { - label: "Low", - value: "low" - }, - { - label: "Lowest", - value: "low" - } - ] +export let audioContext: AudioContext | null = null; + +export async function init() { + if (!audioContext) { + audioContext = new AudioContext(); + await initSoundBuffers(); } -}); + init(); +} -let audioContext: AudioContext | null = null; +export async function kill() { + if (audioContext) { + audioContext.close(); + audioContext = null; + } +} // better than my old hardcoded garbage -const highSounds = Array.from( +export const highSounds = Array.from( { length: 30 }, (_, i) => `sound${String(i + 1).padStart(2, "0")}.wav` ); -const soundBuffers: Record = {}; +export const soundBuffers: Record = {}; -const BASE_URL_HIGH = "https://raw.githubusercontent.com/Equicord/Equibored/main/sounds/animalese"; +export const BASE_URL_HIGH = "https://raw.githubusercontent.com/Equicord/Equibored/main/sounds/animalese"; -async function initSoundBuffers() { +export async function initSoundBuffers() { if (!audioContext) audioContext = new AudioContext(); const quality = settings.store.soundQuality; for (const file of highSounds) { @@ -85,7 +43,7 @@ async function initSoundBuffers() { } } -async function loadSound(url: string): Promise { +export async function loadSound(url: string): Promise { if (!audioContext) audioContext = new AudioContext(); const response = await fetch(url); if (!response.ok) throw new Error("Network response was not OK"); @@ -93,7 +51,7 @@ async function loadSound(url: string): Promise { return audioContext.decodeAudioData(arrayBuffer); } -async function generateAnimalese(text: string): Promise { +export async function generateAnimalese(text: string): Promise { if (!audioContext) audioContext = new AudioContext(); const soundIndices: string[] = []; @@ -175,7 +133,7 @@ async function generateAnimalese(text: string): Promise { return outputBuffer; } -async function playSound(buffer: AudioBuffer, volume: number) { +export async function playSound(buffer: AudioBuffer, volume: number) { if (!audioContext) audioContext = new AudioContext(); const source = audioContext.createBufferSource(); const gainNode = audioContext.createGain(); @@ -189,55 +147,3 @@ async function playSound(buffer: AudioBuffer, volume: number) { source.start(); } - -export default definePlugin({ - name: "Animalese", - description: "Plays animal crossing animalese for every message sent (they yap a lot)", - authors: [EquicordDevs.ryanamay, EquicordDevs.Mocha], - settings, - - flux: { - async MESSAGE_CREATE({ optimistic, type, message, channelId }) { - if (optimistic || type !== "MESSAGE_CREATE") return; - if (message.state === "SENDING") return; - if (!message.content || message.author?.bot || channelId !== SelectedChannelStore.getChannelId()) return; - - const urlPattern = /https?:\/\/[^\s]+/; - const maxLength = settings.store.messageLengthLimit || 100; - const processOwnMessages = settings.store.processOwnMessages ?? true; - - if ( - urlPattern.test(message.content) - || message.content.length > maxLength - || !processOwnMessages - && String(message.author.id) === String(UserStore.getCurrentUser().id) - ) return; - - try { - const buffer = await generateAnimalese(message.content); - if (buffer) await playSound(buffer, settings.store.volume); - } catch (err) { - console.error("[Animalese]", err); - } - } - }, - - start() { - // Only subscribe once! - const init = async () => { - if (!audioContext) { - audioContext = new AudioContext(); - await initSoundBuffers(); - } - init(); - }; - init(); - }, - - stop() { - if (audioContext) { - audioContext.close(); - audioContext = null; - } - }, -}); diff --git a/src/equicordplugins/soundTroll/equissant.ts b/src/equicordplugins/soundTroll/equissant.ts new file mode 100644 index 000000000..fc45702da --- /dev/null +++ b/src/equicordplugins/soundTroll/equissant.ts @@ -0,0 +1,16 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +let clickCount = 0; + +export function croissant() { + clickCount++; + + if (clickCount % 10 === 0) { + const audio = new Audio("https://github.com/Equicord/Equibored/raw/main/sounds/equissant/croissant.mp3"); + audio.play(); + } +} diff --git a/src/equicordplugins/soundTroll/index.ts b/src/equicordplugins/soundTroll/index.ts new file mode 100644 index 000000000..4c4d540cb --- /dev/null +++ b/src/equicordplugins/soundTroll/index.ts @@ -0,0 +1,147 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs, EquicordDevs } from "@utils/constants"; +import { sleep } from "@utils/misc"; +import definePlugin from "@utils/types"; +import { RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common"; + +import { croissant } from "./equissant"; +import { boom, getMoyaiCount, MOYAI } from "./moyai"; +import { settings } from "./settings"; +import { IMessageCreate, IReactionAdd, IVoiceChannelEffectSendEvent } from "./types"; +import { generateAnimalese, playSound, init, kill } from "./animalese"; +import { keydown } from "./keyboardSounds"; +import { Settings } from "@api/Settings"; + +export let click1, click2, click3, backspace; +export let sounds = { + click1, + click2, + click3, + backspace +}; + +export default definePlugin({ + name: "SoundTroll", + description: "Random sound plugin", + authors: [EquicordDevs.SomeAspy, Devs.thororen, Devs.Megu, Devs.Nuckyz, EquicordDevs.ryanamay, EquicordDevs.Mocha, Devs.HypedDomi], + settings, + start() { + if (settings.store.equissant) { + document.addEventListener("click", croissant); + } + if (settings.store.animalese) { + init(); + } + if (settings.store.keyboardSounds) { + click1 = new Audio("https://github.com/Equicord/Equibored/raw/main/sounds/keyboard/click1.wav"); + click2 = new Audio("https://github.com/Equicord/Equibored/raw/main/sounds/keyboard/click2.wav"); + click3 = new Audio("https://github.com/Equicord/Equibored/raw/main/sounds/keyboard/click3.wav"); + backspace = new Audio("https://github.com/Equicord/Equibored/raw/main/sounds/keyboard/backspace.wav"); + sounds = { + click1, + click2, + click3, + backspace, + }; + document.addEventListener("keydown", keydown); + } + if (Settings.plugins?.Animalese?.enabled) { + Settings.plugins.Animalese.enabled = false; + Settings.plugins.SoundTroll.enabled = true; + settings.store.animalese = true; + } + if (Settings.plugins?.Moyai?.enabled) { + Settings.plugins.Moyai.enabled = false; + Settings.plugins.SoundTroll.enabled = true; + settings.store.moyai = true; + } + if (Settings.plugins?.Equissant?.enabled) { + Settings.plugins.Equissant.enabled = false; + Settings.plugins.SoundTroll.enabled = true; + settings.store.equissant = true; + } + if (Settings.plugins?.KeyboardSounds?.enabled) { + Settings.plugins.KeyboardSounds.enabled = false; + Settings.plugins.SoundTroll.enabled = true; + settings.store.keyboardSounds = true; + } + }, + stop() { + if (settings.store.equissant) { + document.removeEventListener("click", croissant); + } + if (settings.store.animalese) { + kill(); + } + if (settings.store.keyboardSounds) { + document.removeEventListener("keydown", keydown); + } + }, + flux: { + async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) { + if (optimistic || type !== "MESSAGE_CREATE") return; + if (message.state === "SENDING") return; + if (settings.store.ignoreBots && message.author?.bot) return; + if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(message.author?.id)) return; + if (!message.content) return; + if (channelId !== SelectedChannelStore.getChannelId()) return; + + if (settings.store.moyai) { + const moyaiCount = getMoyaiCount(message.content); + + for (let i = 0; i < moyaiCount; i++) { + boom(); + await sleep(300); + } + } + + if (settings.store.animalese) { + const urlPattern = /https?:\/\/[^\s]+/; + const maxLength = settings.store.messageLengthLimit || 100; + const processOwnMessages = settings.store.processOwnMessages ?? true; + if ( + urlPattern.test(message.content) + || message.content.length > maxLength + || !processOwnMessages + && String(message.author.id) === String(UserStore.getCurrentUser().id) + ) return; + + try { + const buffer = await generateAnimalese(message.content); + if (buffer) await playSound(buffer, settings.store.volume); + } catch (err) { + console.error("[Animalese]", err); + } + } + }, + + MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, messageAuthorId, emoji }: IReactionAdd) { + if (optimistic || type !== "MESSAGE_REACTION_ADD") return; + if (settings.store.ignoreBots && UserStore.getUser(userId)?.bot) return; + if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(messageAuthorId)) return; + if (channelId !== SelectedChannelStore.getChannelId()) return; + + if (settings.store.moyai) { + const name = emoji.name.toLowerCase(); + if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; + + boom(); + } + }, + + VOICE_CHANNEL_EFFECT_SEND({ emoji }: IVoiceChannelEffectSendEvent) { + if (settings.store.moyai) { + if (!emoji?.name) return; + const name = emoji.name.toLowerCase(); + if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; + + boom(); + } + } + } +}); diff --git a/src/equicordplugins/soundTroll/keyboardSounds.ts b/src/equicordplugins/soundTroll/keyboardSounds.ts new file mode 100644 index 000000000..c3fe70867 --- /dev/null +++ b/src/equicordplugins/soundTroll/keyboardSounds.ts @@ -0,0 +1,22 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { sounds } from "."; + +export const ignoredKeys = ["CapsLock", "ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", "AltLeft", "AltRight", "MetaLeft", "MetaRight", "ArrowUp", "ArrowRight", "ArrowLeft", "ArrowDown", "MediaPlayPause", "MediaStop", "MediaTrackNext", "MediaTrackPrevious", "MediaSelect", "MediaEject", "MediaVolumeUp", "MediaVolumeDown", "AudioVolumeUp", "AudioVolumeDown"]; + +export const keydown = (e: KeyboardEvent) => { + if (ignoredKeys.includes(e.code)) return; + for (const sound of Object.values(sounds)) sound.pause(); + if (e.code === "Backspace") { + sounds.backspace.currentTime = 0; + sounds.backspace.play(); + } else { + const click = sounds[`click${Math.floor(Math.random() * 3) + 1}`]; + click.currentTime = 0; + click.play(); + } +}; diff --git a/src/equicordplugins/soundTroll/moyai.ts b/src/equicordplugins/soundTroll/moyai.ts new file mode 100644 index 000000000..26ec9c55c --- /dev/null +++ b/src/equicordplugins/soundTroll/moyai.ts @@ -0,0 +1,52 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { settings } from "./settings"; + +export const MOYAI = "๐Ÿ—ฟ"; +export const MOYAI_URL = "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.mp3"; +export const MOYAI_URL_HD = "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.wav"; + +export function countOccurrences(sourceString: string, subString: string) { + let i = 0; + let lastIdx = 0; + while ((lastIdx = sourceString.indexOf(subString, lastIdx) + 1) !== 0) + i++; + + return i; +} + +export function countMatches(sourceString: string, pattern: RegExp) { + if (!pattern.global) + throw new Error("pattern must be global"); + + let i = 0; + while (pattern.test(sourceString)) + i++; + + return i; +} + +export const customMoyaiRe = //gi; + +export function getMoyaiCount(message: string) { + const count = countOccurrences(message, MOYAI) + + countMatches(message, customMoyaiRe); + + return Math.min(count, 10); +} + +export function boom() { + if (!settings.store.triggerWhenUnfocused && !document.hasFocus()) return; + const audioElement = document.createElement("audio"); + + audioElement.src = settings.store.soundQuality !== "low" + ? MOYAI_URL_HD + : MOYAI_URL; + + audioElement.volume = settings.store.volume; + audioElement.play(); +} diff --git a/src/equicordplugins/soundTroll/settings.tsx b/src/equicordplugins/soundTroll/settings.tsx new file mode 100644 index 000000000..16b55e247 --- /dev/null +++ b/src/equicordplugins/soundTroll/settings.tsx @@ -0,0 +1,110 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { makeRange, OptionType } from "@utils/types"; + +import { sounds } from "."; +import { croissant } from "./equissant"; + +export const settings = definePluginSettings({ + equissant: { + type: OptionType.BOOLEAN, + description: "Crossant every 10 clicks :trolley:", + default: false, + onChange: v => { + if (v) { + document.addEventListener("click", croissant); + } else { + document.removeEventListener("click", croissant); + } + } + }, + moyai: { + type: OptionType.BOOLEAN, + description: "๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ", + default: false, + }, + animalese: { + type: OptionType.BOOLEAN, + description: "Plays animal crossing animalese for every message sent (they yap a lot)", + default: false, + }, + keyboardSounds: { + type: OptionType.BOOLEAN, + description: "Adds the Opera GX Keyboard Sounds to Discord", + default: false, + }, + speed: { + type: OptionType.SLIDER, + description: "Speed of the animalese sound", + default: 1, + markers: [0.5, 0.75, 1, 1.25, 1.5], + }, + pitch: { + type: OptionType.SLIDER, + description: "Pitch multiplier", + default: 1, + markers: [0.75, 0.8, 0.85, 1, 1.15, 1.25, 1.35, 1.5], + }, + volume: { + description: "Volume of the ๐Ÿ—ฟ๐Ÿ—ฟ๐Ÿ—ฟ", + type: OptionType.SLIDER, + markers: makeRange(0, 1, 0.1), + default: 0.5, + stickToMarkers: false, + onChange: value => { for (const sound of Object.values(sounds)) sound.volume = value / 100; } + + }, + soundQuality: { + type: OptionType.SELECT, + description: "Quality of sound to use", + options: [ + { + label: "High", + value: "high", + default: true + }, + { + label: "Medium", + value: "med" + }, + { + label: "Low", + value: "low" + }, + { + label: "Lowest", + value: "low" + } + ] + }, + triggerWhenUnfocused: { + description: "Trigger the ๐Ÿ—ฟ even when the window is unfocused", + type: OptionType.BOOLEAN, + default: true + }, + ignoreBots: { + description: "Ignore bots", + type: OptionType.BOOLEAN, + default: true + }, + ignoreBlocked: { + description: "Ignore blocked users", + type: OptionType.BOOLEAN, + default: true + }, + messageLengthLimit: { + type: OptionType.NUMBER, + description: "Maximum length of message to process", + default: 50, + }, + processOwnMessages: { + type: OptionType.BOOLEAN, + description: "Enable to yap your own messages too", + default: true, + }, +}); diff --git a/src/equicordplugins/soundTroll/types.ts b/src/equicordplugins/soundTroll/types.ts new file mode 100644 index 000000000..307abfc4d --- /dev/null +++ b/src/equicordplugins/soundTroll/types.ts @@ -0,0 +1,34 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Message, ReactionEmoji } from "@vencord/discord-types"; + +export interface IMessageCreate { + type: "MESSAGE_CREATE"; + optimistic: boolean; + isPushNotification: boolean; + channelId: string; + message: Message; +} + +export interface IReactionAdd { + type: "MESSAGE_REACTION_ADD"; + optimistic: boolean; + channelId: string; + messageId: string; + messageAuthorId: string; + userId: "195136840355807232"; + emoji: ReactionEmoji; +} + +export interface IVoiceChannelEffectSendEvent { + type: string; + emoji?: ReactionEmoji; // Just in case... + channelId: string; + userId: string; + animationType: number; + animationId: number; +} diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 47601741a..d5f490f90 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -123,8 +123,8 @@ async function generateDebugInfoMessage() { } const potentiallyProblematicPlugins = ([ - "NoRPC", "NoProfileThemes", "NoMosaic", "NoRoleHeaders", "NoSystemBadge", "NoDeleteSafety", - "Moyai", "AlwaysAnimate", "ClientTheme", "Equissant", "Ingtoninator", "KeyboardSounds", "NeverPausePreviews", + "NoRPC", "NoProfileThemes", "NoMosaic", "NoRoleHeaders", "NoSystemBadge", + "AlwaysAnimate", "ClientTheme", "SoundTroll", "Ingtoninator", "NeverPausePreviews", ].filter(Vencord.Plugins.isPluginEnabled) ?? []).sort(); if (Vencord.Plugins.isPluginEnabled("CustomIdle") && Vencord.Settings.plugins.CustomIdle.idleTimeout === 0) { diff --git a/src/plugins/alwaysTrust/index.ts b/src/plugins/alwaysTrust/index.tsx similarity index 52% rename from src/plugins/alwaysTrust/index.ts rename to src/plugins/alwaysTrust/index.tsx index 0c89ae116..83d61a8bf 100644 --- a/src/plugins/alwaysTrust/index.ts +++ b/src/plugins/alwaysTrust/index.tsx @@ -16,9 +16,20 @@ * along with this program. If not, see . */ -import { definePluginSettings } from "@api/Settings"; +import { definePluginSettings, Settings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { Alerts, Button, GuildStore } from "@webpack/common"; + +const DeleteGuild = findByPropsLazy("deleteGuild", "sendTransferOwnershipPincode").deleteGuild; + +function GetPropsAndDeleteGuild(id) { + const GotGuild = GuildStore.getGuild(id); + if (!GotGuild) return; + + DeleteGuild(id, GotGuild.name); +} const settings = definePluginSettings({ domain: { @@ -32,13 +43,25 @@ const settings = definePluginSettings({ default: true, description: "Remove the 'Potentially Dangerous Download' popup when opening links", restartNeeded: true - } + }, + noDeleteSafety: { + type: OptionType.BOOLEAN, + default: true, + description: "Remove the 'Potentially Dangerous Download' popup when opening links", + restartNeeded: true + }, + confirmModal: { + type: OptionType.BOOLEAN, + description: "Should a \"are you sure you want to delete\" modal be shown?", + default: true + }, }); export default definePlugin({ name: "AlwaysTrust", description: "Removes the annoying untrusted domain and suspicious file popup", authors: [Devs.zt, Devs.Trwy], + settings, patches: [ { find: '="MaskedLinkStore",', @@ -55,7 +78,34 @@ export default definePlugin({ replace: "$&return null;" }, predicate: () => settings.store.file + }, + { + find: ".DELETE,onClick(){let", + replacement: { + match: /let\{name:\i\}=\i\.guild/, + replace: "$self.HandleGuildDeleteModal($1);$&" + }, + predicate: () => settings.store.noDeleteSafety } ], - settings + start() { + if (Settings.plugins?.NoDeleteSafety?.enabled) { + Settings.plugins.NoDeleteSafety.enabled = false; + settings.store.noDeleteSafety = true; + } + }, + async HandleGuildDeleteModal(server) { + if (settings.store.confirmModal) { + return Alerts.show({ + title: "Delete server?", + body:

It's permanent, if that wasn't obvious.

, + confirmColor: Button.Colors.RED, + confirmText: "Delete", + onConfirm: () => GetPropsAndDeleteGuild(server.id), + cancelText: "Cancel" + }); + } else { + return GetPropsAndDeleteGuild(server.id); + } + }, });