From 0d09c083c6a5089c7a81a9b9a064877985c8cb06 Mon Sep 17 00:00:00 2001 From: Reycko <78082869+Reycko@users.noreply.github.com> Date: Thu, 1 May 2025 18:29:47 +0200 Subject: [PATCH] plugin: SplitLargeMessages (#249) * add(SplitLargeMessages) * Fixes * Update README.md --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- README.md | 5 +- .../splitLargeMessages/index.ts | 156 ++++++++++++++++++ src/utils/constants.ts | 4 + 3 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/equicordplugins/splitLargeMessages/index.ts diff --git a/README.md b/README.md index e8af6a0e..946a646e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch ### Extra included plugins
-169 additional plugins +170 additional plugins ### All Platforms @@ -96,6 +96,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - JumpToStart by Samwich - KeyboardSounds by HypedDomi - KeywordNotify by camila314 & x3rt +- - LastActive by Crxa - LimitMiddleClickPaste by no dev listed - LoginWithQR by nexpid - MediaPlaybackSpeed by D3SOX @@ -142,6 +143,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - Signature by Ven, Rini, ImBanana, KrystalSkull - Slap by Korbo - SoundBoardLogger by Moxxie, fres, echo, maintained by thororen +- - SplitLargeMessages by Reycko - SpotifyLyrics by Joona - StatsfmPresence by Crxa - StatusPresets by iamme @@ -177,7 +179,6 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - Woof by Samwich - WriteUpperCase by Samwich & KrystalSkull - YoutubeDescription by arHSM -- LastActive by Crxa ### Web Only diff --git a/src/equicordplugins/splitLargeMessages/index.ts b/src/equicordplugins/splitLargeMessages/index.ts new file mode 100644 index 00000000..88b4e426 --- /dev/null +++ b/src/equicordplugins/splitLargeMessages/index.ts @@ -0,0 +1,156 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents"; +import { definePluginSettings } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import { getCurrentChannel, sendMessage } from "@utils/discord"; +import definePlugin, { OptionType } from "@utils/types"; +import { ChannelStore, ComponentDispatch, PermissionsBits, UserStore } from "@webpack/common"; + +let maxLength: number = 0; + +const canSplit: () => boolean = () => { + const slowmode = getCurrentChannel()?.rateLimitPerUser ?? 0; + return (settings.store.splitInSlowmode ? slowmode < settings.store.slowmodeMax : slowmode <= 0) && settings.store.disableFileConversion; +}; + +const autoMaxLength = () => { + const hasNitro = UserStore.getCurrentUser().premiumType === 2; + return hasNitro ? 4000 : 2000; +}; + +const split = async (channelId: string, chunks: string[], delayInMs: number) => { + const sendChunk = async (chunk: string) => { + await sendMessage(channelId, { content: chunk }, true); + }; + + // Send the chunks + for (let i = 0; i < chunks.length; i++) { + await sendChunk(chunks[i]); + if (i < chunks.length - 1) // Not the last chunk + await new Promise(resolve => setTimeout(resolve, delayInMs)); // Wait for `delayInMs` + } +}; + +const listener: MessageSendListener = async (channelId, msg) => { + if (msg.content.trim().length < maxLength || !canSplit()) return; // Nothing to split + + const channel = ChannelStore.getChannel(channelId); + + // Check for slowmode + let isSlowmode = channel.rateLimitPerUser > 0; + if ((channel.accessPermissions & PermissionsBits.MANAGE_MESSAGES) === PermissionsBits.MANAGE_MESSAGES + || (channel.accessPermissions & PermissionsBits.MANAGE_CHANNELS) === PermissionsBits.MANAGE_CHANNELS) + isSlowmode = false; + + // Not slowmode or splitInSlowmode is on and less than slowmodeMax + if (!isSlowmode || (settings.store.splitInSlowmode && channel.rateLimitPerUser < settings.store.slowmodeMax)) { + const chunks: string[] = []; + const { hardSplit } = settings.store; + while (msg.content.length > maxLength) { + msg.content = msg.content.trim(); + + // Get last space or newline + const splitIndex = Math.max(msg.content.lastIndexOf(" ", maxLength), msg.content.lastIndexOf("\n", maxLength)); + + // If hard split is on or neither newline or space found, split at maxLength + if (hardSplit || splitIndex === -1) { + chunks.push(msg.content.slice(0, maxLength)); + msg.content = msg.content.slice(maxLength); + } + else { + chunks.push(msg.content.slice(0, splitIndex)); + msg.content = msg.content.slice(splitIndex); + } + } + + ComponentDispatch.dispatchToLastSubscribed("CLEAR_TEXT"); + await split(channelId, [...chunks, msg.content], settings.store.sendDelay * 1000); + } + return { cancel: true }; +}; + +const settings = definePluginSettings({ + maxLength: { + type: OptionType.NUMBER, + description: "Maximum length of a message before it is split. Set to 0 to automatically detect.", + default: 0, + max: 4000, + onChange(newValue) { + if (newValue === 0) + maxLength = autoMaxLength(); + }, + }, + + disableFileConversion: { + type: OptionType.BOOLEAN, + description: "If true, disables file conversion for large messages.", + default: true, + }, + + sendDelay: { + type: OptionType.SLIDER, + description: "Delay between each chunk in seconds.", + default: 1, + markers: [1, 2, 3, 5, 10], + }, + + hardSplit: { + type: OptionType.BOOLEAN, + description: "If true, splits on the last character instead of the last space/newline.", + default: false, + }, + + splitInSlowmode: { + type: OptionType.BOOLEAN, + description: "Should messages be split if the channel has slowmode enabled?", + }, + + slowmodeMax: { + type: OptionType.NUMBER, + description: "Maximum slowmode time if splitting in slowmode.", + default: 5, + min: 1, + max: 30, + } +}); + +export default definePlugin({ + name: "SplitLargeMessages", + description: "Splits large messages into multiple to fit Discord's message limit.", + authors: [EquicordDevs.Reycko], + dependencies: ["MessageEventsAPI"], + settings, + + start() { + if (settings.store.maxLength === 0) + maxLength = autoMaxLength(); + addMessagePreSendListener(listener); + }, + + stop() { + removeMessagePreSendListener(listener); + }, + + patches: [ + { + find: 'type:"MESSAGE_LENGTH_UPSELL"', // bypass message length check + replacement: { + match: /if\(\i.length>\i/, + replace: "if(false", + } + }, + + { + find: '(this,"hideAutocomplete"', // disable file conversion + replacement: { + match: /if\(\i.length>\i\)/, + replace: "if(false)", + }, + } + ] +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 6baba55e..b798c88e 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1058,6 +1058,10 @@ export const EquicordDevs = Object.freeze({ name: "Buzzy", id: 1273353654644117585n }, + Reycko: { + name: "Reycko", + id: 1123725368004726794n, + } } satisfies Record); // iife so #__PURE__ works correctly