From f25a108ac04359eec2c3965b8374b335bf714eb9 Mon Sep 17 00:00:00 2001 From: Hugo Clemente Date: Sat, 4 Apr 2026 15:55:27 +0200 Subject: [PATCH] feat: add date-based filtering to discord_read_messages Add before/after/around params that accept either snowflake IDs or ISO 8601 dates, converting dates to synthetic snowflakes internally. Only one of the three can be specified at a time. --- README.md | 2 +- src/schemas.ts | 12 ++++++++---- src/toolList.ts | 7 +++++-- src/tools/channel.ts | 11 +++++++++-- src/utils/snowflake.ts | 18 ++++++++++++++++++ 5 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 src/utils/snowflake.ts diff --git a/README.md b/README.md index 75ad2f5..0784712 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ Important notes: ### Messages and Reactions - `discord_search_messages`: Search messages in a server -- `discord_read_messages`: Read channel messages +- `discord_read_messages`: Read channel messages (supports `before`, `after`, `around` params — accepts snowflake IDs or ISO 8601 dates like `"2025-03-01T00:00:00Z"`) - `discord_edit_message`: Edit a bot-authored message - `discord_add_reaction`: Add a reaction to a message - `discord_add_multiple_reactions`: Add multiple reactions to a message diff --git a/src/schemas.ts b/src/schemas.ts index bf94c61..9542440 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -112,10 +112,14 @@ export const DeleteChannelSchema = z.object({ export const ReadMessagesSchema = z.object({ channelId: z.string({ description: "The ID of the channel to read messages from." }), - limit: z.number({ description: "How many recent messages to fetch (1-100)." }).min(1).max(100).optional().default(50) -}, { - description: "Read recent messages from a specified channel." -}); + limit: z.number({ description: "How many recent messages to fetch (1-100)." }).min(1).max(100).optional().default(50), + before: z.string({ description: "Snowflake ID or ISO 8601 date (e.g. '2025-03-01T00:00:00Z'). Get messages before this point." }).optional(), + after: z.string({ description: "Snowflake ID or ISO 8601 date (e.g. '2025-03-01T00:00:00Z'). Get messages after this point." }).optional(), + around: z.string({ description: "Snowflake ID or ISO 8601 date (e.g. '2025-03-01T00:00:00Z'). Get messages around this point." }).optional(), +}).refine( + (data) => [data.before, data.after, data.around].filter(Boolean).length <= 1, + { message: "Only one of 'before', 'after', or 'around' can be specified." } +); export const GetServerInfoSchema = z.object({ guildId: z.string({ description: "The ID of the server (guild) to get information for." }) diff --git a/src/toolList.ts b/src/toolList.ts index 68eaf2a..d3e4303 100644 --- a/src/toolList.ts +++ b/src/toolList.ts @@ -185,7 +185,7 @@ export const toolList = [ }, { name: "discord_read_messages", - description: "Retrieves messages from a Discord text channel with a configurable limit", + description: "Retrieves messages from a Discord text channel. Supports date-based filtering via before/after/around params (accepts snowflake IDs or ISO 8601 dates).", inputSchema: { type: "object", properties: { @@ -195,7 +195,10 @@ export const toolList = [ minimum: 1, maximum: 100, default: 50 - } + }, + before: { type: "string", description: "Snowflake ID or ISO 8601 date (e.g. '2025-03-01T00:00:00Z'). Get messages before this point." }, + after: { type: "string", description: "Snowflake ID or ISO 8601 date (e.g. '2025-03-01T00:00:00Z'). Get messages after this point." }, + around: { type: "string", description: "Snowflake ID or ISO 8601 date (e.g. '2025-03-01T00:00:00Z'). Get messages around this point." } }, required: ["channelId"] } diff --git a/src/tools/channel.ts b/src/tools/channel.ts index 7e20437..c90f676 100644 --- a/src/tools/channel.ts +++ b/src/tools/channel.ts @@ -15,6 +15,7 @@ import { CreateVoiceChannelSchema } from "../schemas.js"; import { handleDiscordError } from "../errorHandler.js"; +import { resolveSnowflakeOrDate } from "../utils/snowflake.js"; // Category creation handler export async function createCategoryHandler( @@ -292,7 +293,7 @@ export async function readMessagesHandler( args: unknown, context: ToolContext ): Promise { - const { channelId, limit } = ReadMessagesSchema.parse(args); + const { channelId, limit, before, after, around } = ReadMessagesSchema.parse(args); try { if (!context.client.isReady()) { return { @@ -317,8 +318,14 @@ export async function readMessagesHandler( }; } + // Build fetch options + const fetchOptions: { limit: number; before?: string; after?: string; around?: string } = { limit }; + if (before) fetchOptions.before = resolveSnowflakeOrDate(before); + if (after) fetchOptions.after = resolveSnowflakeOrDate(after); + if (around) fetchOptions.around = resolveSnowflakeOrDate(around); + // Fetch messages - const messages = await channel.messages.fetch({ limit }); + const messages = await channel.messages.fetch(fetchOptions); if (messages.size === 0) { return { diff --git a/src/utils/snowflake.ts b/src/utils/snowflake.ts new file mode 100644 index 0000000..79529be --- /dev/null +++ b/src/utils/snowflake.ts @@ -0,0 +1,18 @@ +const DISCORD_EPOCH = 1420070400000n; + +export function dateToSnowflake(date: Date): string { + return ((BigInt(date.getTime()) - DISCORD_EPOCH) << 22n).toString(); +} + +export function isSnowflake(value: string): boolean { + return /^\d{17,20}$/.test(value); +} + +export function resolveSnowflakeOrDate(value: string): string { + if (isSnowflake(value)) return value; + const date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error(`Invalid snowflake or date: ${value}`); + } + return dateToSnowflake(date); +}