diff --git a/src/equicordplugins/blockKeywords/index.ts b/src/equicordplugins/blockKeywords/index.ts new file mode 100644 index 00000000..f610c0e6 --- /dev/null +++ b/src/equicordplugins/blockKeywords/index.ts @@ -0,0 +1,103 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings, Settings } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { MessageJSON } from "discord-types/general"; + +var blockedKeywords: Array; + +const settings = definePluginSettings({ + blockedWords: { + type: OptionType.STRING, + description: "Comma-seperated list of words to block", + default: "", + restartNeeded: true + }, + useRegex: { + type: OptionType.BOOLEAN, + description: "Use each value as a regular expression when checking message content (advanced)", + default: false, + restartNeeded: true + }, + caseSensitive: { + type: OptionType.BOOLEAN, + description: "Whether to use a case sensitive search or not", + default: false, + restartNeeded: true + } +}); + +export default definePlugin({ + name: "BlockKeywords", + description: "Blocks messages containing specific user-defined keywords, as if the user sending them was blocked.", + authors: [EquicordDevs.catcraft], + patches: [ + { + find: '"_channelMessages",{})', + replacement: { + match: /static commit\((.{1,2})\){/g, + replace: "$&$1=$self.blockMessagesWithKeywords($1);" + } + }, + ], + + settings, + + start() { + const blockedWordsList: Array = Settings.plugins.BlockKeywords.blockedWords.split(","); + const caseSensitiveFlag = Settings.plugins.BlockKeywords.caseSensitive ? "" : "i"; + + if (Settings.plugins.BlockKeywords.useRegex) { + blockedKeywords = blockedWordsList.map(word => { + return new RegExp(word, caseSensitiveFlag); + }); + } + else { + blockedKeywords = blockedWordsList.map(word => { + // escape regex chars in word https://stackoverflow.com/a/6969486 + return new RegExp(`\\b${word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, caseSensitiveFlag); + }); + } + console.log(blockedKeywords); + }, + + containsBlockedKeywords(message: MessageJSON) { + if (blockedKeywords.length === 0) { return false; } + + // can't use forEach because we need to return from inside the loop + // message content loop + for (let wordIndex = 0; wordIndex < blockedKeywords.length; wordIndex++) { + if (blockedKeywords[wordIndex].test(message.content)) { + return true; + } + } + + // embed content loop (e.g. twitter embeds) + for (let embedIndex = 0; embedIndex < message.embeds.length; embedIndex++) { + const embed = message.embeds[embedIndex]; + for (let wordIndex = 0; wordIndex < blockedKeywords.length; wordIndex++) { + // doing this because undefined strings get converted to the string "undefined" in regex tests + // @ts-ignore + const descriptionHasKeywords = embed.rawDescription != null && blockedKeywords[wordIndex].test(embed.rawDescription); + // @ts-ignore + const titleHasKeywords = embed.rawTitle != null && blockedKeywords[wordIndex].test(embed.rawTitle); + if (descriptionHasKeywords || titleHasKeywords) { + return true; + } + } + } + + return false; + }, + + blockMessagesWithKeywords(messageList: any) { + return messageList.reset(messageList.map( + message => message.set("blocked", message.blocked || this.containsBlockedKeywords(message)) + )); + } +}); diff --git a/src/equicordplugins/ircColors/index.ts b/src/equicordplugins/ircColors/index.ts new file mode 100644 index 00000000..21c86381 --- /dev/null +++ b/src/equicordplugins/ircColors/index.ts @@ -0,0 +1,92 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +// Compute a 64-bit FNV-1a hash of the passed data +function hash(data: ArrayBuffer) { + const fnvPrime = 1099511628211n; + const offsetBasis = 14695981039346656037n; + + let result = offsetBasis; + for (const byte of new Uint8Array(data)) { + result ^= BigInt(byte); + result = (result * fnvPrime) % 2n ** 32n; + } + + return result; +} + +// Calculate a CSS color string based on the user ID +function calculateNameColorForUser(id: bigint) { + const idBuffer = new ArrayBuffer(16); + { + const idView = new DataView(idBuffer); + idView.setBigUint64(0, id); + } + const idHash = hash(idBuffer); + + return `hsl(${idHash % 360n}, 100%, ${settings.store.lightness}%)`; +} + +const settings = definePluginSettings({ + lightness: { + description: "Lightness, in %. Change if the colors are too light or too dark.", + restartNeeded: true, + type: OptionType.NUMBER, + default: 70, + }, + memberListColors: { + description: "Replace role colors in the member list", + restartNeeded: true, + type: OptionType.BOOLEAN, + default: false, + }, +}); + +export default definePlugin({ + name: "IrcColors", + description: "Makes username colors in chat unique, like in IRC clients", + authors: [Devs.Grzesiek11], + patches: [ + { + find: "=\"SYSTEM_TAG\"", + replacement: { + match: /(?<=className:\i\.username,style:.{0,50}:void 0,)/, + replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},", + }, + }, + { + find: ".NameWithRole,{roleName:", + replacement: { + match: /(?<=color:)null!=.{0,50}?(?=,)/, + replace: "$self.calculateNameColorForListContext(arguments[0])", + }, + predicate: () => settings.store.memberListColors, + }, + ], + settings, + calculateNameColorForMessageContext(context: any) { + return calculateNameColorForUser(BigInt(context.message.author.id)); + }, + calculateNameColorForListContext(context: any) { + return calculateNameColorForUser(BigInt(context.user.id)); + }, +});