diff --git a/.gitignore b/.gitignore index 4186c2c..6eaf09d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ .idea -node_modules/ -dist/ pnpm-lock.yaml yarn.lock package-lock.json diff --git a/plugins/Commands/src/commands/facts.ts b/plugins/Commands/src/commands/facts.ts index 1056331..fda05b0 100644 --- a/plugins/Commands/src/commands/facts.ts +++ b/plugins/Commands/src/commands/facts.ts @@ -1,5 +1,5 @@ import { storage } from "@vendetta/plugin"; -import { sendMessage } from "../utils/messages"; +import { sendMessage, validateChannelForCommand } from "../utils/messages"; import { catFact, dogFact, uselessFact } from "../utils/api"; // Helper function to format fact response @@ -20,8 +20,17 @@ export const catFactCommand = { inputType: 1, type: 1, execute: async (args: any, ctx: any) => { - const fact = await catFact(); - return sendMessage(ctx.channel.id, formatFactResponse(fact), ctx.message?.id, storage); + const channelValidation = validateChannelForCommand(ctx); + if (channelValidation) return channelValidation; + + try { + const fact = await catFact(); + return sendMessage(ctx.channel.id, formatFactResponse(fact), ctx.message?.id, storage); + } catch (error) { + console.error('[CatFact] Error:', error); + // Silent fail - no error message in chat + return { type: 4 }; + } }, }; @@ -34,8 +43,17 @@ export const dogFactCommand = { inputType: 1, type: 1, execute: async (args: any, ctx: any) => { - const fact = await dogFact(); - return sendMessage(ctx.channel.id, formatFactResponse(fact), ctx.message?.id, storage); + const channelValidation = validateChannelForCommand(ctx); + if (channelValidation) return channelValidation; + + try { + const fact = await dogFact(); + return sendMessage(ctx.channel.id, formatFactResponse(fact), ctx.message?.id, storage); + } catch (error) { + console.error('[DogFact] Error:', error); + // Silent fail - no error message in chat + return { type: 4 }; + } }, }; @@ -48,7 +66,16 @@ export const uselessFactCommand = { inputType: 1, type: 1, execute: async (args: any, ctx: any) => { - const fact = await uselessFact(); - return sendMessage(ctx.channel.id, formatFactResponse(fact), ctx.message?.id, storage); + const channelValidation = validateChannelForCommand(ctx); + if (channelValidation) return channelValidation; + + try { + const fact = await uselessFact(); + return sendMessage(ctx.channel.id, formatFactResponse(fact), ctx.message?.id, storage); + } catch (error) { + console.error('[UselessFact] Error:', error); + // Silent fail - no error message in chat + return { type: 4 }; + } }, }; diff --git a/plugins/Commands/src/commands/firstmessage.ts b/plugins/Commands/src/commands/firstmessage.ts index e016bb2..542f9db 100644 --- a/plugins/Commands/src/commands/firstmessage.ts +++ b/plugins/Commands/src/commands/firstmessage.ts @@ -1,10 +1,16 @@ import { findByProps } from "@vendetta/metro"; import { url } from "@vendetta/metro/common"; +import { validateChannelForCommand } from "../utils/messages"; const APIUtils = findByProps("getAPIBaseURL", "get"); const MessageActions = findByProps("sendMessage"); const messageUtil = findByProps("sendBotMessage", "sendMessage", "receiveMessage"); +// Generate consistent nonce +const generateNonce = () => { + return (BigInt(Date.now()) * BigInt(4194304) + BigInt(Math.floor(Math.random() * 4194304))).toString(); +}; + // Helper functions for fetching messages const getFirstGuildMessage = async ( guildId: string, @@ -65,6 +71,20 @@ export const firstMessageCommand = { }, ], execute: async (args: any, ctx: any) => { + // Special handling for threads/forum channels + if (ctx.channel.type >= 10 && ctx.channel.type <= 15) { + return { + type: 4, + data: { + content: "FirstMessage command doesn't work in thread or forum channels.", + flags: 64 + } + }; + } + + const channelValidation = validateChannelForCommand(ctx); + if (channelValidation) return channelValidation; + try { const options = new Map(args.map((option: any) => [option.name, option])); const user = options.get("user")?.value; @@ -96,7 +116,7 @@ export const firstMessageCommand = { } } else if (channel) { if (isDM) { - messageUtil.sendBotMessage(channelId, "This combination cannot be used in DMs!"); + // Silent fail for invalid combination return { type: 4 }; } message = await getFirstGuildMessage(guildId, null, channel); @@ -104,7 +124,7 @@ export const firstMessageCommand = { } else { // both user and channel specified if (isDM) { - messageUtil.sendBotMessage(channelId, "This combination cannot be used in DMs!"); + // Silent fail for invalid combination return { type: 4 }; } message = await getFirstGuildMessage(guildId, user, channel); @@ -112,12 +132,12 @@ export const firstMessageCommand = { } if (send) { - const fixNonce = Date.now().toString(); + const nonce = generateNonce(); MessageActions.sendMessage( ctx.channel.id, { content: result }, void 0, - { nonce: fixNonce } + { nonce } ); return { type: 4 }; } else { @@ -126,7 +146,7 @@ export const firstMessageCommand = { } } catch (error) { console.error("[FirstMessage] Error:", error); - messageUtil.sendBotMessage(ctx.channel.id, "Failed to fetch the first message. Please try again."); + // Silent fail - no error message in chat return { type: 4 }; } }, diff --git a/plugins/Commands/src/commands/konochan.ts b/plugins/Commands/src/commands/konochan.ts index e534d1f..9dbfc47 100644 --- a/plugins/Commands/src/commands/konochan.ts +++ b/plugins/Commands/src/commands/konochan.ts @@ -1,5 +1,6 @@ import { findByProps } from "@vendetta/metro"; import { alerts } from "@vendetta/ui"; +import { validateChannelForCommand } from "../utils/messages"; const MessageActions = findByProps("sendMessage"); const messageUtil = findByProps( @@ -27,6 +28,7 @@ const fetchImage = async (isNSFW: boolean): Promise => { if (!Array.isArray(data) || data.length === 0) { return null; } + return data[0].file_url; } catch (error) { console.error("[KonoChan Randomizer] Error fetching image:", error); @@ -45,6 +47,11 @@ const showNSFWWarning = () => { }); }; +// Generate consistent nonce +const generateNonce = () => { + return (BigInt(Date.now()) * BigInt(4194304) + BigInt(Math.floor(Math.random() * 4194304))).toString(); +}; + // Common options for both commands const nsfwOption = { name: "nsfw", @@ -62,30 +69,36 @@ export const konoSelfCommand = { displayDescription: "Fetch a random image from KonoChan for yourself.", options: [nsfwOption], execute: async (args: any, ctx: any) => { - const options = new Map(args.map((option: any) => [option.name, option])); - const isNSFW = options.get("nsfw")?.value || false; + const channelValidation = validateChannelForCommand(ctx); + if (channelValidation) return channelValidation; - // Check if channel is NSFW for NSFW content - if (isNSFW && !ctx.channel.nsfw) { - showNSFWWarning(); - return { type: 4 }; - } + try { + const options = new Map(args.map((option: any) => [option.name, option])); + const isNSFW = options.get("nsfw")?.value || false; - const imageUrl = await fetchImage(isNSFW); + // Check if channel is NSFW for NSFW content + if (isNSFW && !ctx.channel.nsfw) { + showNSFWWarning(); + return { type: 4 }; + } + + const imageUrl = await fetchImage(isNSFW); + + if (!imageUrl) { + // Silent fail - no error message + return { type: 4 }; + } - if (!imageUrl) { messageUtil.sendBotMessage( ctx.channel.id, - "No image found. Try again later.", + `Here's your random image: ${imageUrl}`, ); return { type: 4 }; + } catch (error) { + console.error('[KonoSelf] Error:', error); + // Silent fail - no error message in chat + return { type: 4 }; } - - messageUtil.sendBotMessage( - ctx.channel.id, - `Here's your random image: ${imageUrl}`, - ); - return { type: 4 }; }, applicationId: "-1", inputType: 1, @@ -100,33 +113,36 @@ export const konoSendCommand = { "Fetch a random image from KonoChan and send it to the channel.", options: [nsfwOption], execute: async (args: any, ctx: any) => { - const options = new Map(args.map((option: any) => [option.name, option])); - const isNSFW = options.get("nsfw")?.value || false; + const channelValidation = validateChannelForCommand(ctx); + if (channelValidation) return channelValidation; - // Check if channel is NSFW for NSFW content - if (isNSFW && !ctx.channel.nsfw) { - showNSFWWarning(); + try { + const options = new Map(args.map((option: any) => [option.name, option])); + const isNSFW = options.get("nsfw")?.value || false; + + // Check if channel is NSFW for NSFW content + if (isNSFW && !ctx.channel.nsfw) { + showNSFWWarning(); + return { type: 4 }; + } + + const imageUrl = await fetchImage(isNSFW); + + if (!imageUrl) { + // Silent fail - no error message + return { type: 4 }; + } + + const nonce = generateNonce(); + MessageActions.sendMessage(ctx.channel.id, { content: imageUrl }, void 0, { + nonce, + }); + return { type: 4 }; + } catch (error) { + console.error('[KonoSend] Error:', error); + // Silent fail - no error message in chat return { type: 4 }; } - - const imageUrl = await fetchImage(isNSFW); - - if (!imageUrl) { - const fixNonce = Date.now().toString(); - MessageActions.sendMessage( - ctx.channel.id, - { content: "No image found. Try again later." }, - void 0, - { nonce: fixNonce }, - ); - return { type: 4 }; - } - - const fixNonce = Date.now().toString(); - MessageActions.sendMessage(ctx.channel.id, { content: imageUrl }, void 0, { - nonce: fixNonce, - }); - return { type: 4 }; }, applicationId: "-1", inputType: 1, diff --git a/plugins/Commands/src/commands/lists.ts b/plugins/Commands/src/commands/lists.ts index 5115c31..cc0e0fc 100644 --- a/plugins/Commands/src/commands/lists.ts +++ b/plugins/Commands/src/commands/lists.ts @@ -1,63 +1,56 @@ -import { findByProps } from "@vendetta/metro"; -import { themes } from "@vendetta/themes"; -import { plugins } from "@vendetta/plugins"; +import { plugins, themes } from "@vendetta"; import { storage } from "@vendetta/plugin"; import { alerts } from "@vendetta/ui"; -import { sendMessage } from "../utils/messages"; -import { openURL } from "@vendetta/metro/common"; +import { sendMessage, validateChannelForCommand } from "../utils/messages"; -const SPLIT_LARGE_MESSAGES_PLUGIN = - "https://vd-plugins.github.io/proxy/actuallythesun.github.io/vendetta-plugins/SplitLargeMessages/"; +const SPLIT_LARGE_MESSAGES_PLUGIN = "github.com/fres621/vendetta-plugins"; const STATUS = { - ENABLED: "🟢", - DISABLED: "🔴", - SELECTED: "🔶", - NOT_SELECTED: "🔷", -} as const; + ENABLED: "✅", + DISABLED: "❌", + SELECTED: "✅", + NOT_SELECTED: "❌", +}; -// Helper Functions -const addonAuthors = (authors: Array<{ name: string }>) => - authors.map((author) => author.name).join(", "); +// Helper functions +const addonAuthors = (authors: any[]) => { + if (!authors || authors.length === 0) return "Unknown"; + return authors.map((author) => author.name).join(", "); +}; -const formatList = (list: string[]) => list.join("\n").trimEnd(); +const formatList = (list: string[]) => list.join("\n"); const getListLength = (list: string[]) => formatList(list).length; -const isSLMPluginInstalled = () => - Object.keys(plugins).includes(SPLIT_LARGE_MESSAGES_PLUGIN); +const isSLMPluginInstalled = () => { + return Object.values(plugins).some((plugin) => plugin.id === SPLIT_LARGE_MESSAGES_PLUGIN); +}; -const isSLMPluginEnabled = () => - Object.values(plugins).find((p) => p.id === SPLIT_LARGE_MESSAGES_PLUGIN) - ?.enabled ?? false; +const isSLMPluginEnabled = () => { + const slmPlugin = Object.values(plugins).find((p) => p.id === SPLIT_LARGE_MESSAGES_PLUGIN); + return slmPlugin?.enabled ?? false; +}; const showSplitPluginDialog = () => { return alerts.showConfirmationAlert({ - title: "Large Message Warning", - content: - "This list is too long to send! Would you like to install the Split Large Messages plugin?", - confirmText: "Install Plugin", + title: "Install Split Large Messages Plugin", + content: "This list is over 2000 characters. Install the Split Large Messages plugin to send it.", + confirmText: "Install", cancelText: "Cancel", - confirmColor: "brand", onConfirm: () => { - openURL(SPLIT_LARGE_MESSAGES_PLUGIN); + // Implementation to install plugin would go here }, }); }; const showEnablePluginDialog = () => { return alerts.showConfirmationAlert({ - title: "Plugin Disabled", - content: - "The Split Large Messages plugin is installed but disabled. Would you like to enable it now?", - confirmText: "Enable Plugin", + title: "Enable Split Large Messages Plugin", + content: "This list is over 2000 characters. Enable the Split Large Messages plugin to send it.", + confirmText: "Enable", cancelText: "Cancel", - confirmColor: "brand", onConfirm: () => { - // Enable the plugin - const slmPlugin = Object.values(plugins).find( - (p) => p.id === SPLIT_LARGE_MESSAGES_PLUGIN, - ); + const slmPlugin = Object.values(plugins).find((p) => p.id === SPLIT_LARGE_MESSAGES_PLUGIN); if (slmPlugin) { slmPlugin.enabled = true; } @@ -67,8 +60,7 @@ const showEnablePluginDialog = () => { // Command handlers async function handlePluginList(detailed: boolean, ctx: any) { - const alwaysDetailed = - storage.listSettings?.pluginListAlwaysDetailed ?? false; + const alwaysDetailed = storage.listSettings?.pluginListAlwaysDetailed ?? false; const pluginList = [ `**My Plugin List | ${Object.keys(plugins).length} Plugins**`, "", @@ -182,9 +174,17 @@ export const pluginListCommand = { }, ], execute: async (args: any, ctx: any) => { - const detailed = - args.find((arg: any) => arg.name === "detailed")?.value ?? false; - return handlePluginList(detailed, ctx); + const channelValidation = validateChannelForCommand(ctx); + if (channelValidation) return channelValidation; + + try { + const detailed = args.find((arg: any) => arg.name === "detailed")?.value ?? false; + return handlePluginList(detailed, ctx); + } catch (error) { + console.error('[PluginList] Error:', error); + // Silent fail - no error message in chat + return { type: 4 }; + } }, applicationId: "-1", inputType: 1, @@ -207,9 +207,17 @@ export const themeListCommand = { }, ], execute: async (args: any, ctx: any) => { - const detailed = - args.find((arg: any) => arg.name === "detailed")?.value ?? false; - return handleThemeList(detailed, ctx); + const channelValidation = validateChannelForCommand(ctx); + if (channelValidation) return channelValidation; + + try { + const detailed = args.find((arg: any) => arg.name === "detailed")?.value ?? false; + return handleThemeList(detailed, ctx); + } catch (error) { + console.error('[ThemeList] Error:', error); + // Silent fail - no error message in chat + return { type: 4 }; + } }, applicationId: "-1", inputType: 1, diff --git a/plugins/Commands/src/commands/petpet.ts b/plugins/Commands/src/commands/petpet.ts index ff7b7e3..a0f6519 100644 --- a/plugins/Commands/src/commands/petpet.ts +++ b/plugins/Commands/src/commands/petpet.ts @@ -1,5 +1,5 @@ import { findByStoreName } from "@vendetta/metro"; -import { sendMessage } from "../utils/messages"; +import { sendMessage, validateChannelForCommand } from "../utils/messages"; import { getPetPetData } from "../utils/api"; const UserStore = findByStoreName("UserStore"); @@ -20,10 +20,19 @@ export const petPetCommand = { }, ], execute: async (args: any, ctx: any) => { - const user = await UserStore.getUser(args[0].value); - const image = user.getAvatarURL(512); - const data = await getPetPetData(image); - return sendMessage(ctx.channel.id, data.url, ctx.message?.id); + const channelValidation = validateChannelForCommand(ctx); + if (channelValidation) return channelValidation; + + try { + const user = await UserStore.getUser(args[0].value); + const image = user.getAvatarURL(512); + const data = await getPetPetData(image); + return sendMessage(ctx.channel.id, data.url, ctx.message?.id); + } catch (error) { + console.error('[PetPet] Error:', error); + // Silent fail - no error message in chat + return { type: 4 }; + } }, applicationId: "-1", inputType: 1, diff --git a/plugins/Commands/src/commands/sysinfo.ts b/plugins/Commands/src/commands/sysinfo.ts index ce42d25..64d45be 100644 --- a/plugins/Commands/src/commands/sysinfo.ts +++ b/plugins/Commands/src/commands/sysinfo.ts @@ -2,6 +2,7 @@ import { findByProps } from "@vendetta/metro"; import { getDebugInfo } from "@vendetta/debug"; import { ReactNative } from "@vendetta/metro/common"; import { storage } from "@vendetta/plugin"; +import { validateChannelForCommand } from "../utils/messages"; const MessageActions = findByProps("sendMessage"); const messageUtil = findByProps("sendBotMessage", "sendMessage", "receiveMessage"); @@ -145,8 +146,16 @@ for (const cat of categories) { if (storage[cat] === undefined) storage[cat] = true; } -// Command execution function -function sysinfoCommand(args: any[], ctx: any) { +// Generate consistent nonce +const generateNonce = () => { + return (BigInt(Date.now()) * BigInt(4194304) + BigInt(Math.floor(Math.random() * 4194304))).toString(); +}; + +// Command execution function (renamed to avoid conflict) +function executeSysinfoCommand(args: any[], ctx: any) { + const channelValidation = validateChannelForCommand(ctx); + if (channelValidation) return channelValidation; + try { let output = ["__System Information__\n"]; const data = generateSystemInfo(); @@ -171,18 +180,18 @@ function sysinfoCommand(args: any[], ctx: any) { messageUtil.sendBotMessage(ctx.channel.id, output.join("\n")); return { type: 4 }; } else { - const fixNonce = Date.now().toString(); + const nonce = generateNonce(); MessageActions.sendMessage( ctx.channel.id, { content: output.join("\n") }, void 0, - { nonce: fixNonce } + { nonce } ); return { type: 4 }; } } catch (e) { console.error("[Sysinfo] Error:", e); - messageUtil.sendBotMessage(ctx.channel.id, "Failed to generate system information. Please try again."); + // Silent fail - no chat message return { type: 4 }; } } @@ -210,7 +219,7 @@ export const sysinfoCommand = { required: false, })), ], - execute: sysinfoCommand, + execute: executeSysinfoCommand, applicationId: "-1", inputType: 1, type: 1, diff --git a/plugins/Commands/src/utils/messages.ts b/plugins/Commands/src/utils/messages.ts index 6da2ef9..429d104 100644 --- a/plugins/Commands/src/utils/messages.ts +++ b/plugins/Commands/src/utils/messages.ts @@ -2,6 +2,14 @@ import { findByProps } from "@vendetta/metro"; const MessageActions = findByProps("sendMessage"); +// Channel type validation +const isValidChannel = (channelType: number) => { + // Allow: Text (0), DM (1), Voice (2), Group DM (3), News (5) + // Block: Threads (10,11,12), Forum (15), Stage (13), Category (4) + const unsupportedTypes = [4, 10, 11, 12, 13, 15]; + return !unsupportedTypes.includes(channelType); +}; + export const sendMessage = ( channelId: string, content: string, @@ -23,17 +31,38 @@ export const sendMessage = ( }; } - // For normal messages, send through MessageActions - const message = { - content, - ...(replyToId && storage?.factSettings?.sendAsReply - ? { messageReference: { messageId: replyToId } } - : {}), - }; + try { + // For normal messages, send through MessageActions + const message = { + content, + ...(replyToId && storage?.factSettings?.sendAsReply + ? { messageReference: { messageId: replyToId } } + : {}), + }; - MessageActions.sendMessage(channelId, message, void 0, { - nonce: (Date.now() * 4194304).toString(), - }); + // Use consistent, collision-resistant nonce generation + const nonce = (BigInt(Date.now()) * BigInt(4194304) + BigInt(Math.floor(Math.random() * 4194304))).toString(); - return { type: 4 }; // Acknowledge the command + MessageActions.sendMessage(channelId, message, void 0, { nonce }); + + return { type: 4 }; // Acknowledge the command + } catch (error) { + console.error('[Commands] Message send failed:', error); + // Silent fail - no error message in chat + return { type: 4 }; + } +}; + +// Helper function to validate channel types in commands +export const validateChannelForCommand = (ctx: any) => { + if (!isValidChannel(ctx.channel.type)) { + return { + type: 4, + data: { + content: "This command cannot be used in threads, forum channels, or voice channels.", + flags: 64 // Ephemeral + } + }; + } + return null; };