SplitMessages: add remote block-check (fail-closed) and hard chunk cap

This commit is contained in:
Ally Founder 2026-08-20 11:56:17 -04:00
parent 4d06a05abc
commit a48b49f54f

View file

@ -1,22 +1,29 @@
import { findByProps, findByStoreName } from "@vendetta/metro"; import { findByProps, findByStoreName } from "@vendetta/metro";
import { before, instead } from "@vendetta/patcher"; import { instead } from "@vendetta/patcher";
import { storage } from "@vendetta/plugin"; import { id, storage } from "@vendetta/plugin";
import { getAssetIDByName } from "@vendetta/ui/assets"; import { getAssetIDByName } from "@vendetta/ui/assets";
import { showToast } from "@vendetta/ui/toasts"; import { showToast } from "@vendetta/ui/toasts";
import { checkPluginStatus } from "@shared/lib/backend";
import { checkForUpdate } from "@shared/lib/reload";
import { intoChunks } from "./lib/split"; import { intoChunks } from "./lib/split";
import Settings from "./ui/Settings"; import Settings from "./ui/Settings";
const MAX_CHUNKS = 20;
function sleep(ms: number): Promise<void> { function sleep(ms: number): Promise<void> {
return new Promise((r) => setTimeout(r, ms)); return new Promise((r) => setTimeout(r, ms));
} }
let unpatchMaxLength: (() => boolean) | undefined; let unpatchMaxLength: (() => boolean) | undefined;
let unpatchSend: (() => boolean) | undefined; let unpatchSend: (() => boolean) | undefined;
let unpatchEdit: (() => boolean) | undefined;
export default { export default {
onLoad() { onLoad() {
storage.splitOnWords ??= false; storage.splitOnWords ??= false;
checkForUpdate(id).catch(() => {});
const MaxLengthModule = findByProps("getMaxMessageLength"); const MaxLengthModule = findByProps("getMaxMessageLength");
const MessageActions = findByProps("sendMessage", "editMessage"); const MessageActions = findByProps("sendMessage", "editMessage");
const UserStore = findByStoreName("UserStore"); const UserStore = findByStoreName("UserStore");
@ -24,44 +31,91 @@ export default {
unpatchMaxLength = instead("getMaxMessageLength", MaxLengthModule, () => 2 ** 30); unpatchMaxLength = instead("getMaxMessageLength", MaxLengthModule, () => 2 ** 30);
unpatchSend = before("sendMessage", MessageActions, (args: any[]) => { const maxLength = () => (UserStore.getCurrentUser()?.premiumType === 2 ? 4000 : 2000);
const [channelId, message] = args;
const content: string = message?.content ?? "";
const maxLength = UserStore.getCurrentUser()?.premiumType === 2 ? 4000 : 2000;
if (content.length <= maxLength) return;
const chunks = intoChunks(content, maxLength, storage.splitOnWords);
if (!chunks) {
message.content = "";
showToast("Message too long to split", getAssetIDByName("Small"));
return;
}
message.content = chunks.shift();
const delayFor = (channelId: string) => {
const channel = ChannelStore.getChannel(channelId); const channel = ChannelStore.getChannel(channelId);
const delay = Math.max((channel?.rateLimitPerUser ?? 0) * 1000, 1000); return Math.max((channel?.rateLimitPerUser ?? 0) * 1000, 1000);
};
(async () => { const sendChunks = async (channelId: string, chunks: string[], template: any) => {
for (const chunk of chunks) { for (const chunk of chunks) {
await sleep(delay); await sleep(delayFor(channelId));
await MessageActions._sendMessage( await MessageActions._sendMessage(
channelId, channelId,
{ {
invalidEmojis: message.invalidEmojis, invalidEmojis: template.invalidEmojis,
validNonShortcutEmojis: message.validNonShortcutEmojis, validNonShortcutEmojis: template.validNonShortcutEmojis,
tts: false, tts: false,
content: chunk, content: chunk,
}, },
{} {}
); );
} }
};
unpatchSend = instead("sendMessage", MessageActions, ([channelId, message, promise, options]: any[], orig: (a: any[]) => any) => {
const content: string = message?.content ?? "";
const limit = maxLength();
const hasAttachments = !!options?.attachmentsToUpload?.length;
if (content.length <= limit && !hasAttachments) return orig([channelId, message, promise, options]);
const chunks = content ? intoChunks(content, limit, storage.splitOnWords) : [];
if (chunks === false || chunks.length > MAX_CHUNKS) {
showToast("Message too long to split", getAssetIDByName("Small"));
return;
}
return (async () => {
const status = await checkPluginStatus(UserStore.getCurrentUser()?.id, id);
if (status.blocked) {
showToast("Split Messages is unavailable right now", getAssetIDByName("Small"));
return orig([channelId, message, promise, options]);
}
if (hasAttachments) {
await sendChunks(channelId, chunks, message);
if (chunks.length) await sleep(delayFor(channelId));
await orig([channelId, { ...message, content: "" }, promise, options]);
return;
}
const first = { ...message, content: chunks.shift() };
await orig([channelId, first, promise, options]);
await sendChunks(channelId, chunks, message);
})();
});
unpatchEdit = instead("editMessage", MessageActions, ([channelId, messageId, message]: any[], orig: (a: any[]) => any) => {
const content: string = message?.content ?? "";
const limit = maxLength();
if (content.length <= limit) return orig([channelId, messageId, message]);
const chunks = intoChunks(content, limit, storage.splitOnWords);
if (!chunks || chunks.length > MAX_CHUNKS) {
showToast("Message too long to split", getAssetIDByName("Small"));
return;
}
return (async () => {
const status = await checkPluginStatus(UserStore.getCurrentUser()?.id, id);
if (status.blocked) {
showToast("Split Messages is unavailable right now", getAssetIDByName("Small"));
return orig([channelId, messageId, message]);
}
const result = await orig([channelId, messageId, { ...message, content: chunks.shift() }]);
await sendChunks(channelId, chunks, message);
return result;
})(); })();
}); });
}, },
onUnload() { onUnload() {
unpatchMaxLength?.(); unpatchMaxLength?.();
unpatchSend?.(); unpatchSend?.();
unpatchEdit?.();
}, },
settings: Settings, settings: Settings,
}; };