mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-08 22:23:02 -04:00
Remove deprecated plugins: UnitConverter, UwUifier, WhitelistedEmojis, WigglyText, and WriteUpperCase
This commit is contained in:
parent
19104b9a76
commit
cdeccda3df
11 changed files with 12 additions and 1552 deletions
|
@ -54,6 +54,8 @@ interface IVoiceChannelEffectSendEvent {
|
||||||
const MOYAI = "🗿";
|
const MOYAI = "🗿";
|
||||||
const MOYAI_URL = "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.mp3";
|
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 MOYAI_URL_HD = "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.wav";
|
||||||
|
const MOYAI_URL_ULTRA = "https://pub-e77fd37d275f481896833bda931f1d70.r2.dev/moyai.WAV";
|
||||||
|
const MOYAI_URL_ULTRA_HD = "https://pub-e77fd37d275f481896833bda931f1d70.r2.dev/moyai.WAV";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
volume: {
|
volume: {
|
||||||
|
@ -76,6 +78,11 @@ const settings = definePluginSettings({
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
ultraMode: {
|
||||||
|
description: "ryncord's special 🗿 feature!!",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
ignoreBots: {
|
ignoreBots: {
|
||||||
description: "Ignore bots",
|
description: "Ignore bots",
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
|
@ -90,8 +97,8 @@ const settings = definePluginSettings({
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Moyai",
|
name: "Moyai",
|
||||||
authors: [Devs.Megu, Devs.Nuckyz],
|
authors: [Devs.Megu, Devs.Nuckyz, Devs.rayanzay],
|
||||||
description: "🗿🗿🗿🗿🗿🗿🗿🗿",
|
description: "🗿 but with something else inside the settings...",
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
flux: {
|
flux: {
|
||||||
|
@ -166,9 +173,9 @@ function boom() {
|
||||||
if (!settings.store.triggerWhenUnfocused && !document.hasFocus()) return;
|
if (!settings.store.triggerWhenUnfocused && !document.hasFocus()) return;
|
||||||
const audioElement = document.createElement("audio");
|
const audioElement = document.createElement("audio");
|
||||||
|
|
||||||
audioElement.src = settings.store.quality === "HD"
|
audioElement.src = settings.store.ultraMode
|
||||||
? MOYAI_URL_HD
|
? (settings.store.quality === "HD" ? MOYAI_URL_ULTRA_HD : MOYAI_URL_ULTRA)
|
||||||
: MOYAI_URL;
|
: (settings.store.quality === "HD" ? MOYAI_URL_HD : MOYAI_URL);
|
||||||
|
|
||||||
audioElement.volume = settings.store.volume;
|
audioElement.volume = settings.store.volume;
|
||||||
audioElement.play();
|
audioElement.play();
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { classNameFactory } from "@api/Styles";
|
|
||||||
import { useState } from "@webpack/common";
|
|
||||||
import { Message } from "discord-types/general";
|
|
||||||
|
|
||||||
export const conversions = new Map<string, (conv: string) => void>();
|
|
||||||
const cl = classNameFactory("vc-converter-");
|
|
||||||
function Dismiss({ onDismiss }: { onDismiss: () => void; }) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={onDismiss}
|
|
||||||
className={cl("dismiss")}
|
|
||||||
>
|
|
||||||
Dismiss
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// thanks <@408047304864432139>
|
|
||||||
export function ConvertIcon({ height = 24, width = 24, className }: {
|
|
||||||
height?: number,
|
|
||||||
width?: number,
|
|
||||||
className?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 98 98"
|
|
||||||
height={height}
|
|
||||||
width={width}
|
|
||||||
className={[cl("icon"), className].join(" ")}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="m50 16.668v-7.4609c0-1.875-2.25-2.7891-3.543-1.457l-11.664 11.625c-0.83594 0.83203-0.83594 2.125 0 2.957l11.625 11.625c1.332 1.293 3.582 0.375 3.582-1.5v-7.457c13.793 0 25 11.207 25 25 0 3.293-0.625 6.5-1.832 9.375-0.625 1.5-0.16797 3.207 0.95703 4.332 2.125 2.125 5.707 1.375 6.832-1.4141 1.543-3.793 2.375-7.9609 2.375-12.293 0-18.418-14.914-33.332-33.332-33.332zm0 58.332c-13.793 0-25-11.207-25-25 0-3.293 0.625-6.5 1.832-9.375 0.625-1.5 0.16797-3.207-0.95703-4.332-2.125-2.125-5.707-1.375-6.832 1.4141-1.543 3.793-2.375 7.9609-2.375 12.293 0 18.418 14.914 33.332 33.332 33.332v7.4609c0 1.875 2.25 2.7891 3.543 1.457l11.625-11.625c0.83203-0.83203 0.83203-2.125 0-2.957l-11.625-11.625c-1.293-1.293-3.543-0.375-3.543 1.5z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export function ConverterAccessory({ message }: { message: Message; }) {
|
|
||||||
const [conversion, setConversion] = useState<string>("");
|
|
||||||
conversions.set(message.id, setConversion);
|
|
||||||
if (!conversion) return null;
|
|
||||||
return (
|
|
||||||
<span className={cl("accessory")}>
|
|
||||||
<ConvertIcon width={16} height={16} />
|
|
||||||
{conversion}
|
|
||||||
{" - "}
|
|
||||||
<Dismiss onDismiss={() => setConversion("")} />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { settings } from ".";
|
|
||||||
|
|
||||||
interface regexes {
|
|
||||||
imperial: {
|
|
||||||
[key: string]: {
|
|
||||||
regex: RegExp,
|
|
||||||
convert: (...groups: string[]) => string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
metric: {
|
|
||||||
[key: string]: {
|
|
||||||
regex: RegExp,
|
|
||||||
convert: (...groups: string[]) => string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// TODO: add grams, kilograms, ounces, and pounds
|
|
||||||
const regexes: regexes = {
|
|
||||||
// matches imperial units, converts them to metric
|
|
||||||
imperial: {
|
|
||||||
farenheight: {
|
|
||||||
regex: /(-?\d+(?:\.\d+)?)°?(f)(?!\w)/ig,
|
|
||||||
convert(...groups) {
|
|
||||||
const c = ((parseFloat(groups[1]) - 32) * (5 / 9)).toFixed(2);
|
|
||||||
return `${c}°C`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
feetInchesMark: {
|
|
||||||
regex: /(\d+)(') ?(\d+(?:\.\d+)?)("|'')?/g,
|
|
||||||
convert(...groups) {
|
|
||||||
let ftin = parseFloat(groups[1]) / 3.281;
|
|
||||||
ftin += parseFloat(groups[3]) / 39.37;
|
|
||||||
return `${ftin.toFixed(2)}m`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
feetWord: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) *(f(ee)?t)(?! *\d)/ig,
|
|
||||||
convert(...groups) {
|
|
||||||
const ft = (parseFloat(groups[1]) / 3.281).toFixed(2);
|
|
||||||
return `${ft}m`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inchesWord: {
|
|
||||||
regex: /(?<!\d+ *(?:f(?:ee|oo)?t) *)(\d+(?:\.\d+)?) *(in(?:ches?)?)/ig,
|
|
||||||
convert(...groups) {
|
|
||||||
const inches = (parseFloat(groups[1]) / 2.54).toFixed(2);
|
|
||||||
return `${inches}cm`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
feetInchesWord: {
|
|
||||||
regex: /(\d+) *(f(?:ee|oo)?t) *(\d+(?:\.\d+)?) *(in(?:ches?)?)/ig,
|
|
||||||
convert(...groups) {
|
|
||||||
let ftin = parseFloat(groups[1]) / 3.281;
|
|
||||||
ftin += parseFloat(groups[3]) / 39.37;
|
|
||||||
return `${ftin.toFixed(2)}m`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
poundWord: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) *(lbs?|pounds?)(?! ?\d)/ig,
|
|
||||||
convert(...groups: string[]) {
|
|
||||||
const lbs = (parseFloat(groups[1]) / 2.205).toFixed(2);
|
|
||||||
return `${lbs}kg`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
poundOunceWord: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) *(lbs?|pounds?) *(\d+(?:\.\d+)?) *(ozs?|ounces?)/ig,
|
|
||||||
convert(...groups) {
|
|
||||||
let lbs = (parseInt(groups[1]) / 2.205);
|
|
||||||
lbs += (parseFloat(groups[2]) / 35.274);
|
|
||||||
return `${lbs.toFixed(2)}kg`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ounceWord: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) ?(ounces?|oz)/gi,
|
|
||||||
convert(...groups) {
|
|
||||||
const ozs = (parseFloat(groups[1]) * 28.35).toFixed(2);
|
|
||||||
return `${ozs}g`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
milesPerHour: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) ?(m(?:p|\/)h)/gi,
|
|
||||||
convert(...groups) {
|
|
||||||
const mph = (parseFloat(groups[1]) * 1.609).toFixed(2);
|
|
||||||
return `${mph}km/h`;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// matches metric untis, converts them into imperial
|
|
||||||
metric: {
|
|
||||||
// i dont think people ever write metric units as 1m3cm or something like that
|
|
||||||
celcius: {
|
|
||||||
regex: /(-?\d+(?:\.\d+)?)\s?°?c(?!\w)/ig,
|
|
||||||
convert(...groups) {
|
|
||||||
const f = ((parseFloat(groups[1]) * (9 / 5)) + 32).toFixed(2);
|
|
||||||
return `${f}°F`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// convert to inches
|
|
||||||
centimeters: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) ?(cm|centimeters?)(?!\w)/gi,
|
|
||||||
convert(...groups) {
|
|
||||||
const cm = (parseFloat(groups[1]) / 2.54).toFixed(2);
|
|
||||||
return `${cm}in`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// convert to feet
|
|
||||||
meters: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) ?(m|meters?)(?!\w)/gi,
|
|
||||||
convert(...groups) {
|
|
||||||
const m = parseFloat((parseFloat(groups[1]) * 3.821).toFixed(2));
|
|
||||||
if (Number.isInteger(m))
|
|
||||||
return `${m}ft`;
|
|
||||||
return `${m.toFixed(0)}ft${((m % 1) * 12).toFixed(2)}in`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// covnert to miles
|
|
||||||
kilometers: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) ?(km|kilometers?|kms?)(?!\w)/gi,
|
|
||||||
convert(...groups) {
|
|
||||||
const m = (parseFloat(groups[1]) / 1.609).toFixed(2);
|
|
||||||
return `${m}mi`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
grams: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) ?(grams?|g)/gi,
|
|
||||||
convert(...groups) {
|
|
||||||
const g = (parseFloat(groups[1]) / 28.35).toFixed(2);
|
|
||||||
return `${g}oz(s)`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
kilograms: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) ?(kg|kilo(?:gram)?s?)/gi,
|
|
||||||
convert(...groups) {
|
|
||||||
const kg = (parseFloat(groups[1]) * 2.205).toFixed(2);
|
|
||||||
return `${kg}lb(s)`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
kilometersPerHour: {
|
|
||||||
regex: /(\d+(?:\.\d+)?) ?(km\/h|kmph|kph|kilometers?\/?h)/gi,
|
|
||||||
convert(...groups) {
|
|
||||||
const kph = (parseFloat(groups[1]) / 1.609).toFixed(2);
|
|
||||||
return `${kph}mph`;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
export function convert(message: string): string {
|
|
||||||
let newMessage = message;
|
|
||||||
if (settings.store.myUnits === "imperial") {
|
|
||||||
for (const unit in regexes.metric) {
|
|
||||||
newMessage = newMessage.replaceAll(regexes.metric[unit].regex, regexes.metric[unit].convert);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const unit in regexes.imperial) {
|
|
||||||
newMessage = newMessage.replaceAll(regexes.imperial[unit].regex, regexes.imperial[unit].convert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newMessage;
|
|
||||||
}
|
|
|
@ -1,77 +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 "./style.css";
|
|
||||||
|
|
||||||
import { addMessageAccessory } from "@api/MessageAccessories";
|
|
||||||
import { addMessagePopoverButton } from "@api/MessagePopover";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
import { ChannelStore } from "@webpack/common";
|
|
||||||
|
|
||||||
import { convert } from "./converter";
|
|
||||||
import { conversions, ConverterAccessory, ConvertIcon } from "./ConverterAccessory";
|
|
||||||
|
|
||||||
|
|
||||||
export const settings = definePluginSettings({
|
|
||||||
myUnits: {
|
|
||||||
type: OptionType.SELECT,
|
|
||||||
description: "the units you use and want things converted to. defaults to imperial",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
default: true,
|
|
||||||
label: "Imperial",
|
|
||||||
value: "imperial",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Metric",
|
|
||||||
value: "metric"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// invert: {
|
|
||||||
// type: OptionType.BOOLEAN,
|
|
||||||
// default: false,
|
|
||||||
// // is there a better way to word this?
|
|
||||||
// description: "If this option is set, ignore the units you set and invert every conversion."
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
export default definePlugin({
|
|
||||||
name: "UnitConverter",
|
|
||||||
description: "Converts metric units to Imperal units and vice versa",
|
|
||||||
authors: [Devs.sadan],
|
|
||||||
start() {
|
|
||||||
addMessageAccessory("vc-converter", props => <ConverterAccessory message={props.message} />);
|
|
||||||
addMessagePopoverButton("vc-converter", message => {
|
|
||||||
if (!message.content) return null;
|
|
||||||
return {
|
|
||||||
label: "Convert Units",
|
|
||||||
icon: ConvertIcon,
|
|
||||||
message,
|
|
||||||
channel: ChannelStore.getChannel(message.channel_id),
|
|
||||||
onClick: async () => {
|
|
||||||
const setConversion = conversions.get(message.id);
|
|
||||||
if (!setConversion) return;
|
|
||||||
setConversion(convert(message.content));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
settings,
|
|
||||||
});
|
|
|
@ -1,24 +0,0 @@
|
||||||
.vc-converter-dismiss {
|
|
||||||
all: unset;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--text-link);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-converter-dismiss:is(:hover, :focus) {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-converter-accessory {
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-top: 0.5em;
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-converter-accessory svg {
|
|
||||||
margin-right: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-converter-chat-button {
|
|
||||||
scale: 1.085;
|
|
||||||
}
|
|
|
@ -1,206 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 exhq
|
|
||||||
*
|
|
||||||
* 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 { addMessagePreEditListener, addMessagePreSendListener, MessageObject, removeMessagePreEditListener, removeMessagePreSendListener } 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
|
|
||||||
":3", // important enough to have thrice
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
uwuEverything: {
|
|
||||||
description: "Makes *all* text uwuified - really bad idea",
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
default: false,
|
|
||||||
restartNeeded: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
const isOneCharacterString = (str: string): boolean => {
|
|
||||||
return str.split("").every((char: string) => char === str[0]);
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// actual command declaration
|
|
||||||
export default definePlugin({
|
|
||||||
name: "UwUifier",
|
|
||||||
description: "Make everything uwu",
|
|
||||||
authors: [Devs.echo],
|
|
||||||
dependencies: ["MessageEventsAPI"],
|
|
||||||
settings,
|
|
||||||
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
name: "uwuify",
|
|
||||||
description: "uwuifies your messages",
|
|
||||||
options: [RequiredMessageOption],
|
|
||||||
|
|
||||||
execute: opts => ({
|
|
||||||
content: uwuify(findOption(opts, "message", "")),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: ".isPureReactComponent=!0;",
|
|
||||||
predicate: () => settings.store.uwuEverything,
|
|
||||||
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) {
|
|
||||||
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);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,636 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import "./style.css";
|
|
||||||
|
|
||||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
|
||||||
import { DataStore } from "@api/index";
|
|
||||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
|
||||||
import { EquicordDevs } from "@utils/constants";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
import { Alerts, Button, EmojiStore, GuildStore, Menu, Toasts, useEffect, useState } from "@webpack/common";
|
|
||||||
import { CustomEmoji, UnicodeEmoji } from "@webpack/types";
|
|
||||||
import { JSX } from "react";
|
|
||||||
|
|
||||||
interface ContextMenuEmoji {
|
|
||||||
type: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
surrogates?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Target {
|
|
||||||
dataset: ContextMenuEmoji;
|
|
||||||
firstChild: HTMLImageElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface customSaveEmoji {
|
|
||||||
type: string;
|
|
||||||
id: string;
|
|
||||||
guildId?: string;
|
|
||||||
name: string;
|
|
||||||
surrogates?: string;
|
|
||||||
url?: string;
|
|
||||||
animated?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DATA_COLLECTION_NAME = "whitelisted-emojis";
|
|
||||||
|
|
||||||
let cache_allowedList: ContextMenuEmoji[] = [];
|
|
||||||
const getAllowedList = async (): Promise<ContextMenuEmoji[]> => (await DataStore.get<ContextMenuEmoji[]>(DATA_COLLECTION_NAME)) ?? [];
|
|
||||||
|
|
||||||
function isItemAllowed(item: (CustomEmoji | UnicodeEmoji)) {
|
|
||||||
if ("uniqueName" in item) {
|
|
||||||
return cache_allowedList.some(emoji => emoji.name === item.uniqueName);
|
|
||||||
}
|
|
||||||
return cache_allowedList.some(emoji => emoji.name === item.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function itemAlreadyInList(item: ContextMenuEmoji) {
|
|
||||||
return cache_allowedList.some(emoji => emoji.name === item.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addBulkToAllowedList(items: ContextMenuEmoji[]) {
|
|
||||||
const itemsToAdd = await Promise.all(items.map(async item => {
|
|
||||||
if (!itemAlreadyInList(item)) {
|
|
||||||
let emojiData: CustomEmoji | null = null;
|
|
||||||
|
|
||||||
if (!item.surrogates) {
|
|
||||||
emojiData = EmojiStore.getCustomEmojiById(item.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveData: customSaveEmoji = {
|
|
||||||
type: "emoji",
|
|
||||||
id: item.id,
|
|
||||||
name: item.name,
|
|
||||||
surrogates: item.surrogates,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (emojiData && emojiData.guildId) {
|
|
||||||
saveData.url = `https://cdn.discordapp.com/emojis/${emojiData.id}.${emojiData.animated ? "gif" : "png"}`;
|
|
||||||
saveData.guildId = emojiData.guildId;
|
|
||||||
saveData.animated = emojiData.animated;
|
|
||||||
}
|
|
||||||
|
|
||||||
return saveData;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}));
|
|
||||||
|
|
||||||
const validItemsToAdd = itemsToAdd.filter(item => item !== null);
|
|
||||||
await DataStore.set(DATA_COLLECTION_NAME, [...cache_allowedList, ...validItemsToAdd]);
|
|
||||||
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: `Added ${validItemsToAdd.length} emojis to the list, ${items.length - validItemsToAdd.length} already in the list`,
|
|
||||||
type: Toasts.Type.SUCCESS,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_allowedList = await getAllowedList();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeBulkFromAllowedList(items: ContextMenuEmoji[]) {
|
|
||||||
const itemsToRemove = items.filter(item => itemAlreadyInList(item));
|
|
||||||
await DataStore.set(DATA_COLLECTION_NAME, cache_allowedList.filter(emoji => {
|
|
||||||
return !itemsToRemove.some(item => item.name === emoji.name);
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: `Removed ${itemsToRemove.length} emojis from the list`,
|
|
||||||
type: Toasts.Type.SUCCESS,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_allowedList = await getAllowedList();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addToAllowedList(item: ContextMenuEmoji) {
|
|
||||||
if (!itemAlreadyInList(item)) {
|
|
||||||
let emojiData: CustomEmoji | null = null;
|
|
||||||
|
|
||||||
if (!item.surrogates) {
|
|
||||||
emojiData = EmojiStore.getCustomEmojiById(item.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveData: customSaveEmoji = {
|
|
||||||
type: "emoji",
|
|
||||||
id: item.id,
|
|
||||||
name: item.name,
|
|
||||||
surrogates: item.surrogates,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (emojiData && emojiData.guildId) {
|
|
||||||
saveData.url = `https://cdn.discordapp.com/emojis/${emojiData.id}.${emojiData.animated ? "gif" : "png"}`;
|
|
||||||
saveData.guildId = emojiData.guildId;
|
|
||||||
saveData.animated = emojiData.animated;
|
|
||||||
}
|
|
||||||
|
|
||||||
await DataStore.set(DATA_COLLECTION_NAME, [...cache_allowedList, { ...saveData }]);
|
|
||||||
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: `Added "${item.name}" to the list`,
|
|
||||||
type: Toasts.Type.SUCCESS,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: `"${item.name}" is already in the list`,
|
|
||||||
type: Toasts.Type.FAILURE,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_allowedList = await getAllowedList();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeFromAllowedList(item: ContextMenuEmoji) {
|
|
||||||
if (itemAlreadyInList(item)) {
|
|
||||||
await DataStore.set(DATA_COLLECTION_NAME, cache_allowedList.filter(emoji => {
|
|
||||||
return emoji.name !== item.name;
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: `Removed "${item.name}" from the list`,
|
|
||||||
type: Toasts.Type.SUCCESS,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: `"${item.name}" is not in the list`,
|
|
||||||
type: Toasts.Type.FAILURE,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_allowedList = await getAllowedList();
|
|
||||||
}
|
|
||||||
|
|
||||||
const expressionPickerPatch: NavContextMenuPatchCallback = (children, { target }: { target: Target; }) => {
|
|
||||||
const { dataset } = target;
|
|
||||||
|
|
||||||
if (!dataset) return;
|
|
||||||
if (dataset.type !== "emoji") return;
|
|
||||||
|
|
||||||
const emoji = dataset as ContextMenuEmoji;
|
|
||||||
|
|
||||||
if ("name" in emoji) {
|
|
||||||
children.push(buildMenuItems(emoji));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const guildContextPatch: NavContextMenuPatchCallback = (children, { guild }: { guild: { id: string; name: string; }; }) => {
|
|
||||||
children.push(buildGuildContextPatch(guild));
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildGuildContextPatch = (guild: { id: string; name: string; }) => {
|
|
||||||
return (
|
|
||||||
<Menu.MenuGroup>
|
|
||||||
<Menu.MenuItem
|
|
||||||
id="add-white-list-guild-emojis"
|
|
||||||
key="add-white-list-guild-emojis"
|
|
||||||
label="Add All Guild Emojis"
|
|
||||||
action={() => {
|
|
||||||
const { id, name } = guild;
|
|
||||||
const emojis = EmojiStore.getGuildEmoji(id);
|
|
||||||
addBulkToAllowedList(emojis.map(emoji => ({
|
|
||||||
type: "emoji",
|
|
||||||
id: emoji.id,
|
|
||||||
name: emoji.name
|
|
||||||
})));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Menu.MenuItem
|
|
||||||
id="remove-white-list-guild-emojis"
|
|
||||||
key="remove-white-list-guild-emojis"
|
|
||||||
label="Remove All Guild Emojis"
|
|
||||||
action={() => {
|
|
||||||
const { id, name } = guild;
|
|
||||||
const emojis = EmojiStore.getGuildEmoji(id);
|
|
||||||
removeBulkFromAllowedList(emojis.map(emoji => ({
|
|
||||||
type: "emoji",
|
|
||||||
id: emoji.id,
|
|
||||||
name: emoji.name
|
|
||||||
})));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Menu.MenuGroup>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildMenuItems(emoji: ContextMenuEmoji) {
|
|
||||||
const typeString = itemAlreadyInList(emoji) ? "Remove" : "Add";
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Menu.MenuSeparator />
|
|
||||||
<Menu.MenuItem
|
|
||||||
id={`white-list-emoji-${typeString}`}
|
|
||||||
key={`white-list-emoji-${typeString}`}
|
|
||||||
label={`${typeString} to Whitelist`}
|
|
||||||
action={() => {
|
|
||||||
if (typeString === "Add") {
|
|
||||||
addToAllowedList(emoji);
|
|
||||||
} else {
|
|
||||||
removeFromAllowedList(emoji);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const WhiteListedEmojisComponent = (): JSX.Element => {
|
|
||||||
const [whitelistedEmojis, setWhitelistedEmojis] = useState<customSaveEmoji[]>([]);
|
|
||||||
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchAllowedList = async () => {
|
|
||||||
const allowedList = await getAllowedList() as customSaveEmoji[];
|
|
||||||
setWhitelistedEmojis(allowedList);
|
|
||||||
};
|
|
||||||
fetchAllowedList();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleRemoveEmoji = async (emoji: customSaveEmoji) => {
|
|
||||||
await removeFromAllowedList(emoji);
|
|
||||||
setWhitelistedEmojis(await getAllowedList() as customSaveEmoji[]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveAllEmojis = async (guildId: string) => {
|
|
||||||
const emojisToRemove = whitelistedEmojis.filter(emoji => emoji.guildId === guildId || (guildId === "default" && !emoji.guildId));
|
|
||||||
for (const emoji of emojisToRemove) {
|
|
||||||
await removeFromAllowedList(emoji);
|
|
||||||
}
|
|
||||||
setWhitelistedEmojis(await getAllowedList() as customSaveEmoji[]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleGroupCollapse = (guildId: string) => {
|
|
||||||
setCollapsedGroups(prev => ({
|
|
||||||
...prev,
|
|
||||||
[guildId]: !prev[guildId]
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const groupedEmojis = whitelistedEmojis.reduce((groups, emoji) => {
|
|
||||||
const groupKey = emoji.guildId || "default";
|
|
||||||
if (!groups[groupKey]) {
|
|
||||||
groups[groupKey] = [];
|
|
||||||
}
|
|
||||||
groups[groupKey].push(emoji);
|
|
||||||
return groups;
|
|
||||||
}, {} as Record<string, customSaveEmoji[]>);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="emoji-container">
|
|
||||||
{Object.entries(groupedEmojis).map(([guildId, emojis]) => (
|
|
||||||
<div key={guildId} className="guild-section">
|
|
||||||
<div className="guild-header">
|
|
||||||
<h3 className="guild-name" onClick={() => toggleGroupCollapse(guildId)}>
|
|
||||||
{guildId === "default" ? "Default Emojis" : `${GuildStore.getGuild(guildId)?.name || `Guild ${guildId}`} Emojis`}
|
|
||||||
</h3>
|
|
||||||
<Button
|
|
||||||
className="remove-all-button"
|
|
||||||
size="small"
|
|
||||||
look="outlined"
|
|
||||||
color="red"
|
|
||||||
onClick={() => handleRemoveAllEmojis(guildId)}
|
|
||||||
>
|
|
||||||
Remove All
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{!collapsedGroups[guildId] && (
|
|
||||||
<div className="guild-emojis">
|
|
||||||
{emojis.map(emoji => (
|
|
||||||
<div key={emoji.id || emoji.name} className="emoji-item">
|
|
||||||
<span className="emoji-name">{emoji.name}</span>
|
|
||||||
{emoji.surrogates ? (
|
|
||||||
<span className="emoji-surrogate">{emoji.surrogates}</span>
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
className="emoji-image"
|
|
||||||
src={`https://cdn.discordapp.com/emojis/${emoji.id}.${emoji.animated ? "gif" : "png"}`}
|
|
||||||
alt={emoji.name}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
className="remove-button"
|
|
||||||
size="small"
|
|
||||||
look="outlined"
|
|
||||||
color="red"
|
|
||||||
onClick={() => handleRemoveEmoji(emoji)}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{whitelistedEmojis.length === 0 && (
|
|
||||||
<span className="no-emoji-message">No emojis in the whitelist.</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const exportEmojis = async () => {
|
|
||||||
const fileName = "whitelisted-emojis.json";
|
|
||||||
const exportData = await exportEmojisToJson();
|
|
||||||
const data = new TextEncoder().encode(exportData);
|
|
||||||
|
|
||||||
if (IS_WEB || IS_EQUIBOP || IS_VESKTOP) {
|
|
||||||
const file = new File([data], fileName, { type: "application/json" });
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.href = URL.createObjectURL(file);
|
|
||||||
a.download = fileName;
|
|
||||||
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
setImmediate(() => {
|
|
||||||
URL.revokeObjectURL(a.href);
|
|
||||||
document.body.removeChild(a);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
DiscordNative.fileManager.saveWithDialog(data, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: "Successfully exported emojis",
|
|
||||||
type: Toasts.Type.SUCCESS,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function exportEmojisToJson() {
|
|
||||||
const emojis = await getAllowedList();
|
|
||||||
return JSON.stringify({ emojis }, null, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadEmojis = async () => {
|
|
||||||
if (IS_WEB || IS_EQUIBOP || IS_VESKTOP) {
|
|
||||||
const input = document.createElement("input");
|
|
||||||
input.type = "file";
|
|
||||||
input.style.display = "none";
|
|
||||||
input.accept = "application/json";
|
|
||||||
input.onchange = async () => {
|
|
||||||
const file = input.files?.[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = async () => {
|
|
||||||
const data = reader.result as string;
|
|
||||||
await importEmojis(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsText(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.body.appendChild(input);
|
|
||||||
input.click();
|
|
||||||
setImmediate(() => {
|
|
||||||
document.body.removeChild(input);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const [file] = await DiscordNative.fileManager.openFiles({
|
|
||||||
filters: [
|
|
||||||
{ name: "Whitelisted Emojis", extensions: ["json"] },
|
|
||||||
{ name: "all", extensions: ["*"] }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
try {
|
|
||||||
await importEmojis(new TextDecoder().decode(file.data));
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: `Failed to import emojis: ${err}`,
|
|
||||||
type: Toasts.Type.FAILURE,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const importEmojis = async (data: string) => {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(data);
|
|
||||||
if (parsed && typeof parsed === "object" && Array.isArray(parsed.emojis)) {
|
|
||||||
await DataStore.set(DATA_COLLECTION_NAME, parsed.emojis);
|
|
||||||
cache_allowedList = await getAllowedList();
|
|
||||||
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: "Successfully imported emojis",
|
|
||||||
type: Toasts.Type.SUCCESS,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: "Invalid JSON data",
|
|
||||||
type: Toasts.Type.FAILURE,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: `Failed to import emojis: ${err}`,
|
|
||||||
type: Toasts.Type.FAILURE,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetEmojis = async () => {
|
|
||||||
await DataStore.set(DATA_COLLECTION_NAME, []);
|
|
||||||
cache_allowedList = await getAllowedList();
|
|
||||||
|
|
||||||
if (!settings.store.disableToasts) {
|
|
||||||
Toasts.show({
|
|
||||||
message: "Reset emojis",
|
|
||||||
type: Toasts.Type.SUCCESS,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
duration: 3000,
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
defaultEmojis: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Hide default emojis",
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
serverEmojis: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Hide server emojis",
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
disableToasts: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Disable toasts",
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
whiteListedEmojis: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
description: "Whitelisted Emojis",
|
|
||||||
component: WhiteListedEmojisComponent
|
|
||||||
},
|
|
||||||
exportEmojis: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
description: "Export Emojis",
|
|
||||||
component: () => (
|
|
||||||
<Button onClick={exportEmojis}>Export Emojis</Button>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
importEmojis: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
description: "Import Emojis",
|
|
||||||
component: () => (
|
|
||||||
<Button onClick={() =>
|
|
||||||
Alerts.show({
|
|
||||||
title: "Are you sure?",
|
|
||||||
body: "This will overwrite your current whitelist.",
|
|
||||||
confirmText: "Import",
|
|
||||||
confirmColor: Button.Colors.RED,
|
|
||||||
cancelText: "Cancel",
|
|
||||||
onConfirm: async () => {
|
|
||||||
await DataStore.set(DATA_COLLECTION_NAME, []);
|
|
||||||
await uploadEmojis();
|
|
||||||
}
|
|
||||||
})}>
|
|
||||||
Import Emojis
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
resetEmojis: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
description: "Reset Emojis",
|
|
||||||
component: () => (
|
|
||||||
<Button onClick={() =>
|
|
||||||
Alerts.show({
|
|
||||||
title: "Are you sure?",
|
|
||||||
body: "This will remove all emojis from your whitelist.",
|
|
||||||
confirmText: "Reset",
|
|
||||||
confirmColor: Button.Colors.RED,
|
|
||||||
cancelText: "Cancel",
|
|
||||||
onConfirm: resetEmojis
|
|
||||||
})}>
|
|
||||||
Reset Emojis
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
migratePluginSettings("WhitelistedEmojis", "NoDefaultEmojis");
|
|
||||||
export default definePlugin({
|
|
||||||
name: "WhitelistedEmojis",
|
|
||||||
description: "Adds the ability to disable all message emojis except for a whitelisted set.",
|
|
||||||
authors: [EquicordDevs.creations],
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: "#{intl::EMOJI_MATCHING}",
|
|
||||||
replacement: {
|
|
||||||
match: /renderResults\((\i)\){/,
|
|
||||||
replace: "$&$1.results.emojis=$self.filterEmojis($1);if($1.results.emojis.length===0)return;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
settings: settings,
|
|
||||||
async start() {
|
|
||||||
cache_allowedList = await getAllowedList();
|
|
||||||
addContextMenuPatch("expression-picker", expressionPickerPatch);
|
|
||||||
addContextMenuPatch("guild-context", guildContextPatch);
|
|
||||||
},
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch("expression-picker", expressionPickerPatch);
|
|
||||||
removeContextMenuPatch("guild-context", guildContextPatch);
|
|
||||||
},
|
|
||||||
filterEmojis: (data: { results: { emojis: (CustomEmoji | UnicodeEmoji)[]; }; }) => {
|
|
||||||
const { emojis } = data.results;
|
|
||||||
let modifiedEmojis = emojis;
|
|
||||||
|
|
||||||
if (settings.store.defaultEmojis) {
|
|
||||||
modifiedEmojis = modifiedEmojis.filter(emoji => !("uniqueName" in emoji) || isItemAllowed(emoji));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.store.serverEmojis) {
|
|
||||||
modifiedEmojis = modifiedEmojis.filter(emoji => "uniqueName" in emoji || isItemAllowed(emoji));
|
|
||||||
}
|
|
||||||
|
|
||||||
return modifiedEmojis;
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,105 +0,0 @@
|
||||||
.emoji-container {
|
|
||||||
display: grid;
|
|
||||||
gap: 10px;
|
|
||||||
background-color: var(--background-secondary);
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guild-section {
|
|
||||||
border: 1px solid var(--background-tertiary);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guild-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: var(--background-tertiary);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guild-name {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--white-500);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-all-button {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: var(--button-danger-background);
|
|
||||||
color: var(--white-500);
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-all-button:hover {
|
|
||||||
filter: saturate(75%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.guild-emojis {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px;
|
|
||||||
background-color: var(--background-secondary-alt);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-image {
|
|
||||||
max-height: 32px;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-surrogate {
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-name {
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--white-500);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-button {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: var(--button-danger-background);
|
|
||||||
color: var(--white-500);
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-button:hover {
|
|
||||||
filter: saturate(75%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-container::-webkit-scrollbar {
|
|
||||||
background-color: #fff1;
|
|
||||||
width: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-container::-webkit-scrollbar-thumb {
|
|
||||||
background-color: #fff3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-emoji-message {
|
|
||||||
font-size: 25px;
|
|
||||||
color: var(--white-500);
|
|
||||||
text-align: center;
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
|
@ -1,200 +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 { makeRange } from "@components/PluginSettings/components";
|
|
||||||
import { EquicordDevs } from "@utils/constants";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
import { Text } from "@webpack/common";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
import ExampleWiggle from "./ui/components/ExampleWiggle";
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
intensity: {
|
|
||||||
type: OptionType.SLIDER,
|
|
||||||
description: "Animation intensity in px",
|
|
||||||
markers: makeRange(1, 10, 1),
|
|
||||||
default: 4,
|
|
||||||
stickToMarkers: true,
|
|
||||||
onChange: () => updateStyles()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const dirMap = {
|
|
||||||
x: "0.6s wiggle-wavy-x alternate ease-in-out infinite",
|
|
||||||
y: "1.2s wiggle-wavy-y linear infinite"
|
|
||||||
};
|
|
||||||
|
|
||||||
const classMap = [
|
|
||||||
{
|
|
||||||
chars: ["<", ">"],
|
|
||||||
className: "wiggle-inner-x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chars: ["^", "^"],
|
|
||||||
className: "wiggle-inner-y",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chars: [")", "("],
|
|
||||||
className: "wiggle-inner-xy"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
let styles: HTMLStyleElement;
|
|
||||||
const updateStyles = () => {
|
|
||||||
const inten = Vencord.Settings.plugins.WigglyText.intensity + "px";
|
|
||||||
styles.textContent = `
|
|
||||||
.wiggle-example {
|
|
||||||
list-style-type: disc;
|
|
||||||
list-style-position: outside;
|
|
||||||
margin: 4px 0 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiggle-example li {
|
|
||||||
white-space: break-spaces;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiggle-inner {
|
|
||||||
position: relative;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
&.wiggle-inner-x {
|
|
||||||
animation: ${dirMap.x};
|
|
||||||
}
|
|
||||||
|
|
||||||
&.wiggle-inner-y {
|
|
||||||
animation: ${dirMap.y};
|
|
||||||
}
|
|
||||||
|
|
||||||
&.wiggle-inner-xy {
|
|
||||||
animation: ${dirMap.x}, ${dirMap.y};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes wiggle-wavy-x {
|
|
||||||
from {
|
|
||||||
left: -${inten};
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
left: ${inten};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes wiggle-wavy-y {
|
|
||||||
0% {
|
|
||||||
top: 0;
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
25% {
|
|
||||||
top: -${inten};
|
|
||||||
animation-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
top: 0;
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
75% {
|
|
||||||
top: ${inten};
|
|
||||||
animation-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "WigglyText",
|
|
||||||
description: "Adds a new markdown formatting that makes text wiggly.",
|
|
||||||
authors: [EquicordDevs.nexpid],
|
|
||||||
settings,
|
|
||||||
settingsAboutComponent: () => (
|
|
||||||
<Text>
|
|
||||||
You can make text wiggle with the following:<br />
|
|
||||||
<ul className="wiggle-example">
|
|
||||||
<li><ExampleWiggle wiggle="x">left and right</ExampleWiggle> by typing <code><~text~></code></li>
|
|
||||||
<li><ExampleWiggle wiggle="y">up and down</ExampleWiggle> by typing <code>^~text~^</code></li>
|
|
||||||
<li><ExampleWiggle wiggle="xy">in a circle</ExampleWiggle> by typing <code>)~text~(</code></li>
|
|
||||||
</ul>
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: "parseToAST:",
|
|
||||||
replacement: {
|
|
||||||
match: /(parse[\w]*):(.*?)\((\i)\),/g,
|
|
||||||
replace: "$1:$2({...$3,wiggly:$self.wigglyRule}),",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
wigglyRule: {
|
|
||||||
order: 24,
|
|
||||||
match: (source: string) => classMap.map(({ chars }) => source.match(new RegExp(`^(\\${chars[0]})~([\\s\\S]+?)~(\\${chars[1]})(?!_)`))).find(x => x !== null),
|
|
||||||
parse: (
|
|
||||||
capture: RegExpMatchArray,
|
|
||||||
transform: (...args: any[]) => any,
|
|
||||||
state: any
|
|
||||||
) => {
|
|
||||||
const className = classMap.find(({ chars }) => chars[0] === capture[1] && chars[1] === capture[3])?.className ?? "";
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: transform(capture[2], state),
|
|
||||||
className
|
|
||||||
};
|
|
||||||
},
|
|
||||||
react: (
|
|
||||||
data: { content: any[]; className: string; },
|
|
||||||
output: (...args: any[]) => ReactNode[]
|
|
||||||
) => {
|
|
||||||
let offset = 0;
|
|
||||||
const traverse = (raw: any) => {
|
|
||||||
const children = !Array.isArray(raw) ? [raw] : raw;
|
|
||||||
let modified = false;
|
|
||||||
|
|
||||||
let j = -1;
|
|
||||||
for (const child of children) {
|
|
||||||
j++;
|
|
||||||
if (typeof child === "string") {
|
|
||||||
modified = true;
|
|
||||||
children[j] = child.split("").map((x, i) => (
|
|
||||||
<span key={i}>
|
|
||||||
<span
|
|
||||||
className={`wiggle-inner ${data.className}`}
|
|
||||||
style={{
|
|
||||||
animationDelay: `${((offset++) * 25) % 1200}ms`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{x}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
));
|
|
||||||
} else if (child?.props?.children)
|
|
||||||
child.props.children = traverse(child.props.children);
|
|
||||||
}
|
|
||||||
|
|
||||||
return modified ? children : raw;
|
|
||||||
};
|
|
||||||
|
|
||||||
return traverse(output(data.content));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
start: () => {
|
|
||||||
styles = document.createElement("style");
|
|
||||||
styles.id = "WigglyText";
|
|
||||||
document.head.appendChild(styles);
|
|
||||||
|
|
||||||
updateStyles();
|
|
||||||
},
|
|
||||||
|
|
||||||
stop: () => styles.remove()
|
|
||||||
});
|
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function ExampleWiggle({ wiggle, children }: { wiggle: "x" | "y" | "xy", children: string; }) {
|
|
||||||
return children.split("").map((x, i) => (
|
|
||||||
<span key={i}>
|
|
||||||
<span
|
|
||||||
className={`wiggle-inner wiggle-inner-${wiggle}`}
|
|
||||||
style={{
|
|
||||||
animationDelay: `${(i * 25) % 1200}ms`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{x}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
));
|
|
||||||
}
|
|
|
@ -1,47 +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 { definePluginSettings } from "@api/Settings";
|
|
||||||
import { Devs, EquicordDevs } from "@utils/constants";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
|
|
||||||
const settings = definePluginSettings(
|
|
||||||
{
|
|
||||||
blockedWords: {
|
|
||||||
type: OptionType.STRING,
|
|
||||||
description: "Strings not to capitilise (seperate with a comma)",
|
|
||||||
default: "http, https, ok"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const presendObject: MessageSendListener = (_, msg) => {
|
|
||||||
const sentences = msg.content.split(/(?<=\w\.)\s/);
|
|
||||||
const blockedWordsArray: string[] = settings.store.blockedWords.split(", ");
|
|
||||||
|
|
||||||
msg.content = sentences.map(element => {
|
|
||||||
if (!blockedWordsArray.some(word => element.toLowerCase().startsWith(word.toLocaleLowerCase()))) {
|
|
||||||
return element.charAt(0).toUpperCase() + element.slice(1);
|
|
||||||
} else {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}).join(" ");
|
|
||||||
};
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "WriteUpperCase",
|
|
||||||
description: "Changes the first Letter of each Sentence in Message Inputs to Uppercase",
|
|
||||||
authors: [Devs.Samwich, EquicordDevs.KrystalSkull],
|
|
||||||
settings,
|
|
||||||
|
|
||||||
start() {
|
|
||||||
addMessagePreSendListener(presendObject);
|
|
||||||
},
|
|
||||||
stop() {
|
|
||||||
removeMessagePreSendListener(presendObject);
|
|
||||||
}
|
|
||||||
});
|
|
Loading…
Add table
Add a link
Reference in a new issue