diff --git a/src/plugins/badge.ts b/src/plugins/badge.ts
new file mode 100644
index 00000000..c7876d85
--- /dev/null
+++ b/src/plugins/badge.ts
@@ -0,0 +1,43 @@
+/* eslint-disable header/header */
+import { BadgePosition, ProfileBadge } from "@api/Badges";
+import { Badges } from "@api/index";
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+import { UserStore } from "@webpack/common";
+
+const SHIGGY_BADGE = "https://cdn.discordapp.com/emojis/1101838344146665502.gif?size=240&quality=lossless";
+const BLOBFOXBOX_BADGE = "https://cdn.discordapp.com/emojis/1036216552736952350.webp?size=240&quality=lossless";
+
+const ShiggyBadge: ProfileBadge = {
+ description: "true shiggy fan",
+ image: SHIGGY_BADGE,
+ position: BadgePosition.START,
+ props: {
+ style: { transform: "scale(0.9)" }
+ },
+ shouldShow: ({ user }) => user.id === UserStore.getCurrentUser().id,
+ link: "https://ryanccn.dev/"
+};
+const BlobfoxBoxBadge: ProfileBadge = {
+ description: "blobfox",
+ image: BLOBFOXBOX_BADGE,
+ position: BadgePosition.START,
+ props: {
+ style: { transform: "scale(0.9)" }
+ },
+ shouldShow: ({ user }) => user.id === UserStore.getCurrentUser().id,
+ link: "https://ryanccn.dev/"
+};
+
+export default definePlugin({
+ name: "Ryan's Extra Badges",
+ description: "shiggy",
+ authors: [Devs.RyanCaoDev],
+ dependencies: ["BadgeAPI"],
+
+
+ start() {
+ Badges.addBadge(ShiggyBadge);
+ Badges.addBadge(BlobfoxBoxBadge);
+ },
+});
diff --git a/src/plugins/bottom/components/Indicator.tsx b/src/plugins/bottom/components/Indicator.tsx
new file mode 100644
index 00000000..e8ade6e9
--- /dev/null
+++ b/src/plugins/bottom/components/Indicator.tsx
@@ -0,0 +1,63 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+/*
+ * This plugin was modified from code licensed under the following license:
+ *
+ * MIT License
+ *
+ * Copyright (c) 2021-present Sebastian Law
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+import { findByPropsLazy } from "@webpack";
+import { Tooltip } from "@webpack/common";
+
+export default function Indicator({ layers, bottom }: { layers: number; bottom: boolean; }) {
+ return (
+
+ {({ onMouseLeave, onMouseEnter }) => (
+
+ {bottom ? "(bottom)" : "(original)"}
+
+ )}
+
+ );
+}
diff --git a/src/plugins/bottom/encoding.ts b/src/plugins/bottom/encoding.ts
new file mode 100644
index 00000000..699ffd8b
--- /dev/null
+++ b/src/plugins/bottom/encoding.ts
@@ -0,0 +1,73 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+/*
+ * This file was adapted from https://github.com/bottom-software-foundation/bottom-js
+ * Which is, hopefully, licensed under MIT.
+*/
+
+const CHARACTER_VALUES: [number, string][] = [
+ [200, "🫂"],
+ [50, "💖"],
+ [10, "✨"],
+ [5, "🥺"],
+ [1, ","],
+ [0, "❤️"],
+];
+const SECTION_SEPERATOR = "👉👈";
+const FINAL_TERMINATOR = new RegExp(`(${SECTION_SEPERATOR})?$`);
+
+function encodeChar(charValue: number): string {
+ if (charValue === 0) return "";
+ const [val, currentCase]: [number, string] =
+ CHARACTER_VALUES.find(([val]) => charValue >= val) || CHARACTER_VALUES[-1];
+ return `${currentCase}${encodeChar(charValue - val)}`;
+}
+
+export function encode(value: string): string {
+ return Array.from(new TextEncoder().encode(value))
+ .map((v: number) => encodeChar(v) + SECTION_SEPERATOR)
+ .join("");
+}
+
+export function decode(value: string): string {
+ return new TextDecoder().decode(Uint8Array.from(
+ value
+ .trim()
+ .replace(FINAL_TERMINATOR, "")
+ .split(SECTION_SEPERATOR)
+ .map(letters => {
+ return Array.from(letters)
+ .map(character => {
+ const [value, emoji]: [number, string] = CHARACTER_VALUES.find(
+ ([_, em]) => em === character
+ ) || [-1, ""];
+ if (!emoji) {
+ throw new TypeError(`Invalid bottom text: '${character}'`);
+ }
+ return value;
+ })
+ .reduce((p, c) => p + c);
+ })
+ ));
+}
+
+export default {
+ encode: encode,
+ decode: decode
+};
diff --git a/src/plugins/bottom/handler.ts b/src/plugins/bottom/handler.ts
new file mode 100644
index 00000000..0092c9dd
--- /dev/null
+++ b/src/plugins/bottom/handler.ts
@@ -0,0 +1,158 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+/*
+ * This plugin was modified from code licensed under the following license:
+ *
+ * MIT License
+ *
+ * Copyright (c) 2021-present Sebastian Law
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+import { FluxDispatcher, MessageStore } from "@webpack/common";
+import type { Message } from "discord-types/general";
+
+import Bottom from "./encoding";
+
+class BottomHandler {
+
+ cache: Record>;
+ re: RegExp;
+
+ constructor() {
+ this.cache = {};
+ this.re = /((?:((?:\uD83E\uDEC2)?(?:💖)*(?:✨)*(?:🥺)*(?:,)*(❤️)?)(?:👉👈|\u200b))+)/gm;
+ }
+
+ isTranslated(message) {
+ if (
+ !this.cache[message.channel_id] ||
+ !this.cache[message.channel_id][message.id]
+ ) { return false; }
+
+ return this.cache[message.channel_id][message.id].originalContent !== message.content;
+ }
+
+ translate(text: string, notNested: boolean) {
+ var original = text;
+ var translated = text;
+ var layers = 0;
+ while (original.match(this.re)) {
+ translated = original.replace(this.re, (str, p1, offset, s) => Bottom.decode(p1) || p1);
+
+ // the regex can sometimes pick up invalid bottom in which case we want to return to avoid an infinite loop
+ if (translated === original || notNested) break;
+ else {
+ original = translated;
+ layers++;
+ }
+ }
+ return {
+ translated: translated,
+ layers: layers,
+ };
+ }
+
+ translateMessage(message: Message, decodeLayers: boolean) {
+ if (!message.content || message.content.length === 0) {
+ return "";
+ }
+ // Build cache if it doesn't exist
+ if (!this.cache[message.channel_id]) {
+ this.cache[message.channel_id] = {};
+ }
+ if (!this.cache[message.channel_id][message.id]) {
+ this.cache[message.channel_id][message.id] = {
+ originalContent: message.content,
+ };
+ }
+
+ const cached = this.cache[message.channel_id][message.id];
+
+ if (this.isTranslated(message)) {
+ // if we're reverting back to original, just set the content back to original
+ message.content = cached.originalContent;
+ this.updateMessage(message);
+ } else {
+ // the message hasn't been edited, let's try to decode it
+ const { translated, layers } = this.translate(message.content, !decodeLayers);
+ if (translated === message.content) {
+ // we don't want to do anything if there is no bottom
+ // since the translation fails, mark this message to not show the indicator
+ cached.top = true;
+ throw new Error("No Bottom detected 🥺");
+ } else {
+ // let the indicator show how many layers of decoding we did
+ cached.layers = layers;
+ message.content = translated;
+ this.updateMessage(message);
+ }
+ }
+ }
+
+ updateMessage(message: Message) {
+ console.log({
+ bottomTranslation: true,
+ type: "MESSAGE_UPDATE",
+ message,
+ });
+ FluxDispatcher.dispatch({
+ bottomTranslation: true,
+ type: "MESSAGE_UPDATE",
+ message,
+ });
+ }
+
+ clearCache() {
+ for (const channelID in this.cache) {
+ for (const messageID in this.cache[channelID]) {
+ this.removeMessage(channelID, messageID);
+ }
+ }
+ this.cache = {};
+ }
+
+ removeMessage(channelID: string, messageID: string, reset = true) {
+ const message = MessageStore.getMessage(channelID, messageID);
+ if (reset) {
+ message.content = this.cache[channelID][messageID].originalContent;
+ this.updateMessage(message);
+ }
+ delete this.cache[channelID][messageID];
+ }
+}
+
+export default BottomHandler;
diff --git a/src/plugins/bottom/index.tsx b/src/plugins/bottom/index.tsx
new file mode 100644
index 00000000..d317e5c1
--- /dev/null
+++ b/src/plugins/bottom/index.tsx
@@ -0,0 +1,262 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+/*
+ * This plugin was modified from code licensed under the following license:
+ *
+ * MIT License
+ *
+ * Copyright (c) 2021-present Sebastian Law
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+import { findOption, RequiredMessageOption } from "@api/Commands";
+import { addAccessory, removeAccessory } from "@api/MessageAccessories";
+import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
+import { addButton, removeButton } from "@api/MessagePopover";
+import { definePluginSettings } from "@api/settings";
+import ErrorBoundary from "@components/ErrorBoundary";
+import definePlugin, { OptionType } from "@utils/types";
+import { ChannelStore, Toasts } from "@webpack/common";
+
+import Indicator from "./components/Indicator";
+import Bottom from "./encoding";
+import BottomHandler from "./handler";
+
+const Handler = new BottomHandler();
+
+const settings = definePluginSettings({
+ "decode-layers": {
+ description: "Decode Layers",
+ type: OptionType.BOOLEAN,
+ default: true,
+ },
+ "auto-encode-send": {
+ description: "Automatically encode outgoing messages",
+ type: OptionType.BOOLEAN,
+ default: false,
+ },
+ "encode-send-type": {
+ description: "Automatic Encode Behavior",
+ type: OptionType.SELECT,
+ options:
+ [
+ {
+ label: "All",
+ default: true,
+ value: 0,
+ },
+ {
+ label: "Inline (Greedy)",
+ value: 1,
+ },
+ {
+ label: "Inline (Parsed)",
+ value: 2,
+ }
+ ],
+ },
+ "inline-bottom-prefix": {
+ description: "Inline bottom prefix",
+ type: OptionType.STRING,
+ default: "👉",
+ },
+ "inline-bottom-suffix": {
+ description: "Inline bottom suffix",
+ type: OptionType.STRING,
+ default: "👈",
+ },
+});
+
+const escapeRegex: (string: string) => string = string => string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+
+function count(string: string, subString: string): number {
+ var n = 0;
+ var pos = 0;
+ const step = subString.length;
+
+ while (true) {
+ pos = string.indexOf(subString, pos);
+ if (pos >= 0) {
+ n++;
+ pos += step;
+ } else break;
+ }
+ return n;
+}
+function inlineEncode(p: string, s: string, text: string): string {
+ var np = count(text, p);
+ var ns = count(text, s);
+
+ if (np === 0 || ns === 0) return text;
+
+ var pl = p.length;
+ var sl = s.length;
+ const result: string[] = [];
+ let idx = 0;
+
+ while (true) {
+ var startIndex = text.indexOf(p, idx);
+
+ if (startIndex < 0) {
+ result.push(text.slice(idx));
+ break;
+ }
+
+ var endIndex = text.indexOf(s, startIndex + pl);
+
+ if (endIndex < 0) {
+ result.push(text.slice(idx));
+ break;
+ }
+
+ result.push(text.slice(idx, startIndex));
+ startIndex += pl;
+ result.push(Bottom.encode(text.slice(startIndex, endIndex)));
+ endIndex += sl;
+ idx = endIndex;
+ }
+
+ return result.join("");
+}
+
+export default definePlugin({
+ name: "Bottom",
+ description: "The Vencord plugin for bottom 🥺",
+ authors: [
+ {
+ id: 1038096782963507210n,
+ name: "skyevg",
+ },
+ ],
+ dependencies: ["MessagePopoverAPI", "CommandsAPI", "MessageEventsAPI", "MessageAccessoriesAPI"],
+
+ settings,
+
+ start() {
+ addButton("bottom", msg => {
+ return {
+ label: "Translate Bottom",
+ icon: () => (
+
+ ),
+ message: msg,
+ channel: ChannelStore.getChannel(msg.channel_id),
+ onClick: async () => {
+ try {
+ Handler.translateMessage(msg, settings.store["decode-layers"]);
+ } catch (e) {
+ console.error(e);
+ Toasts.show(
+ {
+ id: Toasts.genId(),
+ message: e.message,
+ type: Toasts.Type.MESSAGE
+ }
+ );
+ }
+ }
+ };
+ });
+
+ addAccessory("bottom", props => {
+ try {
+ if (!Handler.cache[props.message.channel_id][props.message.id].top) {
+ try {
+ return (
+
+
+
+ );
+ } catch { }
+ }
+ }
+ catch { }
+ return null;
+ });
+
+ this.preSend = addPreSendListener((_, msg) => {
+ if (settings.store["auto-encode-send"]) {
+ const sendType = settings.store["encode-send-type"];
+ var { content } = msg;
+
+ switch (sendType) {
+ case 0: // all
+ content = Bottom.encode(content);
+ break;
+ case 1: // inline greedy
+ var prefix = escapeRegex(settings.store["inline-bottom-prefix"]);
+ var suffix = escapeRegex(settings.store["inline-bottom-suffix"]);
+ var reg = new RegExp(`${prefix}(.+)${suffix}`, "gm");
+ content = content.replace(reg, (str, p1, o, s) => Bottom.encode(p1));
+ break;
+ case 2: // inline parsed
+ var prefix = settings.store["inline-bottom-prefix"];
+ var suffix = settings.store["inline-bottom-prefix"];
+ content = inlineEncode(prefix, suffix, content);
+ break;
+ }
+ msg.content = content;
+ }
+ });
+ },
+ stop() {
+ removeButton("bottom");
+ removeAccessory("bottom");
+ removePreSendListener(this.preSend);
+ },
+
+ commands: [
+ {
+ name: "bottom",
+ description: "Translate and send text as bottom 🥺",
+ options: [RequiredMessageOption],
+ execute: opts => ({
+ content: Bottom.encode(findOption(opts, "message", "")),
+ })
+ }
+ ]
+});
diff --git a/src/plugins/useAltSearch.ts b/src/plugins/useAltSearch.ts
new file mode 100644
index 00000000..f72392e3
--- /dev/null
+++ b/src/plugins/useAltSearch.ts
@@ -0,0 +1,46 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import definePlugin, { OptionType } from "@utils/types";
+
+export default definePlugin({
+ name: "UseAlternativeSearch",
+ description: "Use alternative search engine in right click menu",
+ authors: [
+ {
+ id: 1038096782963507210n,
+ name: "skyevg",
+ },
+ ],
+ patches: [
+ {
+ find: "https://www.google.com/search?q=",
+ replacement: {
+ match: /"https:\/\/www.google.com\/search\?q=".concat\(encodeURIComponent\(e\)\)/,
+ replace: "Vencord.Settings.plugins.UseAlternativeSearch.source.replace(\"!QUERY!\", encodeURIComponent(e))"
+ }
+ }
+ ],
+ options: {
+ source: {
+ description: "Search engine's url (use !QUERY! as replacement for the search term)",
+ type: OptionType.STRING,
+ default: "https://duckduckgo.com/?q=!QUERY!",
+ }
+ }
+});
diff --git a/src/plugins/uwuifier.ts b/src/plugins/uwuifier.ts
new file mode 100644
index 00000000..f75dd106
--- /dev/null
+++ b/src/plugins/uwuifier.ts
@@ -0,0 +1,143 @@
+/*
+ * 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 { findOption, RequiredMessageOption } from "@api/Commands";
+import { addPreEditListener, addPreSendListener, MessageObject, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
+import { definePluginSettings } from "@api/Settings";
+import { Devs } from "@utils/constants";
+import definePlugin, { OptionType } from "@utils/types";
+
+const endings = [
+ "rawr x3",
+ "OwO",
+ "UwU",
+ "o.O",
+ "-.-",
+ ">w<",
+ "(⑅˘꒳˘)",
+ "(ꈍᴗꈍ)",
+ "(˘ω˘)",
+ "(U ᵕ U❁)",
+ "σωσ",
+ "òωó",
+ "(///ˬ///✿)",
+ "(U ﹏ U)",
+ "( ͡o ω ͡o )",
+ "ʘwʘ",
+ ":3",
+ ":3", // important enough to have twice
+ "XD",
+ "nyaa~~",
+ "mya",
+ ">_<",
+ "😳",
+ "🥺",
+ "😳😳😳",
+ "rawr",
+ "^^",
+ "^^;;",
+ "(ˆ ﻌ ˆ)♡",
+ "^•ﻌ•^",
+ "/(^•ω•^)",
+ "(✿oωo)"
+];
+
+const replacements = [
+ ["small", "smol"],
+ ["cute", "kawaii~"],
+ ["fluff", "floof"],
+ ["love", "luv"],
+ ["stupid", "baka"],
+ ["what", "nani"],
+ ["meow", "nya~"],
+ ["hello", "hewwo"],
+];
+
+const settings = definePluginSettings({
+ uwuEveryMessage: {
+ description: "Make every single message uwuified",
+ type: OptionType.BOOLEAN,
+ default: false,
+ restartNeeded: false
+ }
+});
+
+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];
+}
+
+
+function uwuify(message: string): string {
+ message = message.toLowerCase();
+ // words
+ for (const pair of replacements) {
+ message = message.replaceAll(pair[0], pair[1]);
+ }
+ message = message
+ .replaceAll(/([ \t\n])n/g, "$1ny") // nyaify
+ .replaceAll(/[lr]/g, "w") // [lr] > w
+ .replaceAll(/([ \t\n])([a-z])/g, (_, p1, p2) => Math.random() < .5 ? `${p1}${p2}-${p2}` : `${p1}${p2}`) // stutter
+ .replaceAll(/([^.,!][.,!])([ \t\n])/g, (_, p1, p2) => `${p1} ${selectRandomElement(endings)}${p2}`); // endings
+ return message;
+}
+
+
+
+// actual command declaration
+export default definePlugin({
+ name: "UwUifier",
+ description: "Simply uwuify commands",
+ authors: [Devs.echo, Devs.skyevg, Devs.PandaNinjas],
+ dependencies: ["CommandsAPI", "MessageEventsAPI"],
+ settings,
+
+ commands: [
+ {
+ name: "uwuify",
+ description: "uwuifies your messages",
+ options: [RequiredMessageOption],
+
+ execute: opts => ({
+ content: uwuify(findOption(opts, "message", "")),
+ }),
+ },
+ ],
+
+ onSend(msg: MessageObject) {
+ // Only run when it's enabled
+ if (settings.store.uwuEveryMessage) {
+ msg.content = uwuify(msg.content);
+ }
+ },
+
+ start() {
+ this.preSend = addPreSendListener((_, msg) => this.onSend(msg));
+ this.preEdit = addPreEditListener((_cid, _mid, msg) =>
+ this.onSend(msg)
+ );
+ },
+
+ stop() {
+ removePreSendListener(this.preSend);
+ removePreEditListener(this.preEdit);
+ },
+});