Some Merges

This commit is contained in:
thororen1234 2025-09-10 06:04:17 -04:00
parent bb071250f6
commit 4411fabdd5
No known key found for this signature in database
29 changed files with 813 additions and 847 deletions

View file

@ -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)

View file

@ -1,3 +0,0 @@
# Demonstration
Helps for taking theme screenshots

View file

@ -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 (
<Switch
note="You can re-enable this setting later"
value={value}
onChange={v => { settings.store.showConfirmationModal = !v; }}>
Disable modal?
</Switch>
);
}
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 => (
<ModalRoot {...props}>
<ModalHeader className={cl("header")}>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }} >Demonstration</Text>
<ModalCloseButton onClick={() => closeModal(key)}></ModalCloseButton>
</ModalHeader>
<ModalContent className={cl("content")}>
<Forms.FormText>
This will censor all text! To disable this, remember the shortcut:
</Forms.FormText>
<Text variant="heading-xl/bold" style={{ textAlign: "center", width: "100%", paddingTop: "20px", paddingBottom: "20px" }}>
{settings.store.keyBind}
</Text>
<ToggleModal />
</ModalContent>
<Button
onClick={() => {
closeModal(key);
injectCSS();
playSound("https://github.com/Equicord/Equibored/raw/main/sounds/demonstration/ckz46t.wav");
}}
>Okay!</Button>
</ModalRoot>
));
} 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 (
<>
<Text>To change your keycode, check out <a href="https://www.toptal.com/developers/keycode" target="_blank" rel="noreferrer noopener">this tool</a>!</Text>
</>
);
},
start() {
document.addEventListener("keydown", handleKeydown);
},
stop() {
document.removeEventListener("keydown", handleKeydown);
}
});

View file

@ -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: () => <>
<Forms.FormText className="plugin-warning" style={{ textAlign: "left" }}>
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[];

View file

@ -1,3 +0,0 @@
# Equissant
Plays crossant every 10 clicks

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
});

View file

@ -1,3 +0,0 @@
# ExportContacts
Exports your friends list onto your clipboard

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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 <ErrorBoundary noop key=".2">
<button className="export-contacts-button" onClick={() => { this.copyContactToClipboard(); console.log("clicked"); }}>Export</button>
</ErrorBoundary>;
},
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
}
});
}
});

View file

@ -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<React.ReactElement<any> | 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 <ErrorBoundary noop key=".2">
<button className="export-contacts-button" onClick={() => { this.copyContactToClipboard(); console.log("clicked"); }}>Export</button>
</ErrorBoundary>;
},
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
}
});
}
});

View file

@ -24,4 +24,4 @@
.export-contacts-button:hover {
background-color: #454fbf;
}
}

View file

@ -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;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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; }
}
}
});

View file

@ -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", "")),
}),
},
];

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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);
},
});

View file

@ -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;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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 = /<a?:\w*moy?ai\w*:\d{17,20}>/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();
}

View file

@ -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: <p>It's permanent, if that wasn't obvious.</p>, 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);$&"
}
}
]
});

View file

@ -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)\"},"
}
}
]
});

View file

@ -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);
}
},
});

View file

@ -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);
}
});

View file

@ -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<string, AudioBuffer> = {};
export const soundBuffers: Record<string, AudioBuffer> = {};
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<AudioBuffer> {
export async function loadSound(url: string): Promise<AudioBuffer> {
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<AudioBuffer> {
return audioContext.decodeAudioData(arrayBuffer);
}
async function generateAnimalese(text: string): Promise<AudioBuffer | null> {
export async function generateAnimalese(text: string): Promise<AudioBuffer | null> {
if (!audioContext) audioContext = new AudioContext();
const soundIndices: string[] = [];
@ -175,7 +133,7 @@ async function generateAnimalese(text: string): Promise<AudioBuffer | null> {
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;
}
},
});

View file

@ -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();
}
}

View file

@ -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();
}
}
}
});

View file

@ -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();
}
};

View file

@ -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 = /<a?:\w*moy?ai\w*:\d{17,20}>/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();
}

View file

@ -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,
},
});

View file

@ -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;
}

View file

@ -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) {

View file

@ -16,9 +16,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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: <p>It's permanent, if that wasn't obvious.</p>,
confirmColor: Button.Colors.RED,
confirmText: "Delete",
onConfirm: () => GetPropsAndDeleteGuild(server.id),
cancelText: "Cancel"
});
} else {
return GetPropsAndDeleteGuild(server.id);
}
},
});