Compare commits
1 commit
09be5b9d06
...
a2251c9066
Author | SHA1 | Date | |
---|---|---|---|
a2251c9066 |
7 changed files with 788 additions and 0 deletions
43
src/plugins/badge.ts
Normal file
43
src/plugins/badge.ts
Normal 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);
|
||||||
|
},
|
||||||
|
});
|
63
src/plugins/bottom/components/Indicator.tsx
Normal file
63
src/plugins/bottom/components/Indicator.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
73
src/plugins/bottom/encoding.ts
Normal file
73
src/plugins/bottom/encoding.ts
Normal 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
|
||||||
|
};
|
158
src/plugins/bottom/handler.ts
Normal file
158
src/plugins/bottom/handler.ts
Normal 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;
|
262
src/plugins/bottom/index.tsx
Normal file
262
src/plugins/bottom/index.tsx
Normal 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", "")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
46
src/plugins/useAltSearch.ts
Normal file
46
src/plugins/useAltSearch.ts
Normal 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
143
src/plugins/uwuifier.ts
Normal 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);
|
||||||
|
},
|
||||||
|
});
|
Loading…
Reference in a new issue