This commit is contained in:
Skye 2023-08-08 12:26:00 +09:00
parent 0b611a2911
commit 39fa23cfb7
Signed by: me
GPG key ID: 0104BC05F41B77B8
7 changed files with 788 additions and 0 deletions

43
src/plugins/badge.ts Normal file
View file

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

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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 (
<Tooltip color="black" position="top" text={layers <= 1 ? "🥺" : `Decoded from ${layers} nested bottom messages`}>
{({ onMouseLeave, onMouseEnter }) => (
<span
className={`power-bottom-indicator ${findByPropsLazy("edited").edited}`}
style={{ color: "var(--text-muted)" }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{bottom ? "(bottom)" : "(original)"}
</span>
)}
</Tooltip>
);
}

View file

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

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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<string, Record<string, { originalContent: string; top?: boolean; layers?: number; }>>;
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;

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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: () => (
<svg x="0" y="0" aria-hidden="false" width="22" height="22" viewBox="0 0 36 36" fill="currentColor" className="icon">
<circle fill="#FFCC4D" cx="18" cy="18" r="18" />
<path fill="#65471B" d="M20.996 27c-.103 0-.206-.016-.309-.049-1.76-.571-3.615-.571-5.375 0-.524.169-1.089-.117-1.26-.642-.171-.525.117-1.089.643-1.26 2.162-.702 4.447-.702 6.609 0 .525.171.813.735.643 1.26-.137.421-.529.691-.951.691z" />
<path fill="#FFF" d="M30.335 12.068c-.903 2.745-3.485 4.715-6.494 4.715-.144 0-.289-.005-.435-.014-1.477-.093-2.842-.655-3.95-1.584.036.495.076.997.136 1.54.152 1.388.884 2.482 2.116 3.163.82.454 1.8.688 2.813.752 1.734.109 3.57-.28 4.873-.909 1.377-.665 2.272-1.862 2.456-3.285.183-1.415-.354-2.924-1.515-4.378z" />
<path fill="#65471B" d="M21.351 7.583c-1.297.55-1.947 2.301-1.977 5.289l.039.068c.897 1.319 2.373 2.224 4.088 2.332.114.007.228.011.341.011 2.634 0 4.849-1.937 5.253-4.524-.115-.105-.221-.212-.343-.316-3.715-3.17-6.467-3.257-7.401-2.86z" />
<path fill="#F4900C" d="M23.841 16.783c3.009 0 5.591-1.97 6.494-4.715-.354-.443-.771-.88-1.241-1.309-.404 2.587-2.619 4.524-5.253 4.524-.113 0-.227-.004-.341-.011-1.715-.108-3.191-1.013-4.088-2.332l-.039-.068c-.007.701.021 1.473.083 2.313 1.108.929 2.473 1.491 3.95 1.584.146.01.291.014.435.014z" />
<circle fill="#FFF" cx="21.413" cy="10.705" r="1.107" />
<path fill="#FFF" d="M12.159 16.783c-3.009 0-5.591-1.97-6.494-4.715-1.161 1.454-1.697 2.963-1.515 4.377.185 1.423 1.079 2.621 2.456 3.285 1.303.629 3.138 1.018 4.873.909 1.013-.064 1.993-.297 2.813-.752 1.231-.681 1.963-1.775 2.116-3.163.06-.542.1-1.042.136-1.536-1.103.923-2.47 1.487-3.95 1.58-.146.011-.291.015-.435.015z" />
<path fill="#65471B" d="M12.159 15.283c.113 0 .227-.004.341-.011 1.715-.108 3.191-1.013 4.088-2.332l.039-.068c-.031-2.988-.68-4.739-1.977-5.289-.934-.397-3.687-.31-7.401 2.859-.122.104-.227.211-.343.316.404 2.588 2.619 4.525 5.253 4.525z" />
<path fill="#F4900C" d="M16.626 12.872l-.039.068c-.897 1.319-2.373 2.224-4.088 2.332-.114.007-.228.011-.341.011-2.634 0-4.849-1.937-5.253-4.524-.47.429-.887.866-1.241 1.309.903 2.745 3.485 4.715 6.494 4.715.144 0 .289-.005.435-.014 1.48-.093 2.847-.657 3.95-1.58.062-.841.091-1.614.083-2.317z" />
<path fill="#FFF" d="M9.781 11.81c.61-.038 1.074-.564 1.035-1.174-.038-.61-.564-1.074-1.174-1.036-.61.038-1.074.564-1.036 1.174.039.61.565 1.074 1.175 1.036z" />
</svg>
),
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 (
<ErrorBoundary>
<Indicator layers={Handler.cache[props.message.channel_id][props.message.id].layers ?? 0} bottom={!Handler.isTranslated(props.message)} />
</ErrorBoundary>
);
} 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", "")),
})
}
]
});

View file

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

143
src/plugins/uwuifier.ts Normal file
View file

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