From c731a87d07a65c9cd511350806528716a2bc7896 Mon Sep 17 00:00:00 2001 From: Shadow Date: Sat, 10 Jan 2026 16:38:02 -0600 Subject: [PATCH] Discord: add fetch message action --- skills/discord/SKILL.md | 20 ++++++++++ src/agents/tools/discord-actions-messaging.ts | 40 +++++++++++++++++++ src/agents/tools/discord-actions.ts | 1 + src/agents/tools/discord-schema.ts | 15 +++++++ src/discord/send.ts | 11 +++++ 5 files changed, 87 insertions(+) diff --git a/skills/discord/SKILL.md b/skills/discord/SKILL.md index 7d7d1de35..b1f55e768 100644 --- a/skills/discord/SKILL.md +++ b/skills/discord/SKILL.md @@ -12,6 +12,7 @@ Use `discord` to manage messages, reactions, threads, polls, and moderation. You ## Inputs to collect - For reactions: `channelId`, `messageId`, and an `emoji`. +- For fetchMessage: `guildId`, `channelId`, `messageId`, or a `messageLink` like `https://discord.com/channels///`. - For stickers/polls/sendMessage: a `to` target (`channel:` or `user:`). Optional `content` text. - Polls also need a `question` plus 2–10 `answers`. - For media: `mediaUrl` with `file:///path` for local files or `https://...` for remote. @@ -21,6 +22,7 @@ Use `discord` to manage messages, reactions, threads, polls, and moderation. You Message context lines include `discord message id` and `channel` fields you can reuse directly. **Note:** `sendMessage` uses `to: "channel:"` format, not `channelId`. Other actions like `react`, `readMessages`, `editMessage` use `channelId` directly. +**Note:** `fetchMessage` accepts message IDs or full links like `https://discord.com/channels///`. ## Actions @@ -144,6 +146,24 @@ Use `discord.actions.*` to disable action groups: } ``` +### Fetch a single message + +```json +{ + "action": "fetchMessage", + "guildId": "999", + "channelId": "123", + "messageId": "456" +} +``` + +```json +{ + "action": "fetchMessage", + "messageLink": "https://discord.com/channels/999/123/456" +} +``` + ### Send/edit/delete a message ```json diff --git a/src/agents/tools/discord-actions-messaging.ts b/src/agents/tools/discord-actions-messaging.ts index d45d8f4a0..7fcae8c68 100644 --- a/src/agents/tools/discord-actions-messaging.ts +++ b/src/agents/tools/discord-actions-messaging.ts @@ -5,6 +5,7 @@ import { deleteMessageDiscord, editMessageDiscord, fetchChannelPermissionsDiscord, + fetchMessageDiscord, fetchReactionsDiscord, listPinsDiscord, listThreadsDiscord, @@ -53,6 +54,23 @@ function formatDiscordTimestamp(ts?: string | null): string | undefined { return `${yyyy}-${mm}-${dd}T${hh}:${min}${sign}${offsetH}:${offsetM}${tzSuffix}`; } +function parseDiscordMessageLink(link: string) { + const normalized = link.trim(); + const match = normalized.match( + /^(?:https?:\/\/)?(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(\d+)\/(\d+)\/(\d+)(?:\/?|\?.*)$/i, + ); + if (!match) { + throw new Error( + "Invalid Discord message link. Expected https://discord.com/channels///.", + ); + } + return { + guildId: match[1], + channelId: match[2], + messageId: match[3], + }; +} + export async function handleDiscordMessagingAction( action: string, params: Record, @@ -157,6 +175,28 @@ export async function handleDiscordMessagingAction( const permissions = await fetchChannelPermissionsDiscord(channelId); return jsonResult({ ok: true, permissions }); } + case "fetchMessage": { + if (!isActionEnabled("messages")) { + throw new Error("Discord message reads are disabled."); + } + const messageLink = readStringParam(params, "messageLink"); + let guildId = readStringParam(params, "guildId"); + let channelId = readStringParam(params, "channelId"); + let messageId = readStringParam(params, "messageId"); + if (messageLink) { + const parsed = parseDiscordMessageLink(messageLink); + guildId = parsed.guildId; + channelId = parsed.channelId; + messageId = parsed.messageId; + } + if (!guildId || !channelId || !messageId) { + throw new Error( + "Discord message fetch requires guildId, channelId, and messageId (or a valid messageLink).", + ); + } + const message = await fetchMessageDiscord(channelId, messageId); + return jsonResult({ ok: true, message, guildId, channelId, messageId }); + } case "readMessages": { if (!isActionEnabled("messages")) { throw new Error("Discord message reads are disabled."); diff --git a/src/agents/tools/discord-actions.ts b/src/agents/tools/discord-actions.ts index e3104f53b..751653c4c 100644 --- a/src/agents/tools/discord-actions.ts +++ b/src/agents/tools/discord-actions.ts @@ -11,6 +11,7 @@ const messagingActions = new Set([ "sticker", "poll", "permissions", + "fetchMessage", "readMessages", "sendMessage", "editMessage", diff --git a/src/agents/tools/discord-schema.ts b/src/agents/tools/discord-schema.ts index 7bc055298..8d2b48c89 100644 --- a/src/agents/tools/discord-schema.ts +++ b/src/agents/tools/discord-schema.ts @@ -35,6 +35,21 @@ export const DiscordToolSchema = Type.Union([ action: Type.Literal("permissions"), channelId: Type.String(), }), + Type.Union([ + Type.Object({ + action: Type.Literal("fetchMessage"), + messageLink: Type.String(), + guildId: Type.Optional(Type.String()), + channelId: Type.Optional(Type.String()), + messageId: Type.Optional(Type.String()), + }), + Type.Object({ + action: Type.Literal("fetchMessage"), + guildId: Type.String(), + channelId: Type.String(), + messageId: Type.String(), + }), + ]), Type.Object({ action: Type.Literal("readMessages"), channelId: Type.String(), diff --git a/src/discord/send.ts b/src/discord/send.ts index d54811af7..34cc50b82 100644 --- a/src/discord/send.ts +++ b/src/discord/send.ts @@ -903,6 +903,17 @@ export async function readMessagesDiscord( )) as APIMessage[]; } +export async function fetchMessageDiscord( + channelId: string, + messageId: string, + opts: DiscordReactOpts = {}, +): Promise { + const rest = resolveDiscordRest(opts); + return (await rest.get( + Routes.channelMessage(channelId, messageId), + )) as APIMessage; +} + export async function editMessageDiscord( channelId: string, messageId: string,