agent-messenger 2.29.1 → 2.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +15 -0
- package/bun.lock +42 -2
- package/dist/package.json +2 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/platforms/channeltalk/cli.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/client.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/bot.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/chat.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/group.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/manager.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/message.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/shared.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/snapshot.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/whoami.js.map +1 -1
- package/dist/src/platforms/channeltalk/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
- package/dist/src/platforms/channeltalk/types.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/client.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/client.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/types.d.ts +1 -1
- package/dist/src/platforms/channeltalkbot/types.d.ts.map +1 -1
- package/dist/src/platforms/discord/cli.d.ts.map +1 -1
- package/dist/src/platforms/discord/client.d.ts +1 -0
- package/dist/src/platforms/discord/client.d.ts.map +1 -1
- package/dist/src/platforms/discord/client.js +39 -17
- package/dist/src/platforms/discord/client.js.map +1 -1
- package/dist/src/platforms/discord/commands/message.d.ts +3 -0
- package/dist/src/platforms/discord/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/message.js +30 -0
- package/dist/src/platforms/discord/commands/message.js.map +1 -1
- package/dist/src/platforms/discord/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/discord/listener.d.ts.map +1 -1
- package/dist/src/platforms/discord/listener.js.map +1 -1
- package/dist/src/platforms/discord/remote-auth.js.map +1 -1
- package/dist/src/platforms/discord/super-properties.js.map +1 -1
- package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/discord/token-extractor.js.map +1 -1
- package/dist/src/platforms/discord/types.d.ts +13 -0
- package/dist/src/platforms/discord/types.d.ts.map +1 -1
- package/dist/src/platforms/discord/types.js +6 -0
- package/dist/src/platforms/discord/types.js.map +1 -1
- package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/client.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/client.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/message.d.ts +1 -1
- package/dist/src/platforms/discordbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/message.js +4 -4
- package/dist/src/platforms/discordbot/commands/message.js.map +1 -1
- package/dist/src/platforms/discordbot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/listener.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/listener.js.map +1 -1
- package/dist/src/platforms/discordbot/types.d.ts.map +1 -1
- package/dist/src/platforms/imessage/cli.d.ts.map +1 -1
- package/dist/src/platforms/imessage/client.d.ts.map +1 -1
- package/dist/src/platforms/imessage/client.js.map +1 -1
- package/dist/src/platforms/imessage/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/imessage/errors.js.map +1 -1
- package/dist/src/platforms/imessage/rpc.d.ts.map +1 -1
- package/dist/src/platforms/imessage/rpc.js.map +1 -1
- package/dist/src/platforms/imessage/types.d.ts.map +1 -1
- package/dist/src/platforms/imessage/types.js.map +1 -1
- package/dist/src/platforms/instagram/cli.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/instagram/hybrid-listener.d.ts.map +1 -1
- package/dist/src/platforms/instagram/hybrid-listener.js.map +1 -1
- package/dist/src/platforms/instagram/listener.d.ts.map +1 -1
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts.map +1 -1
- package/dist/src/platforms/instagram/mqtt/transport.d.ts.map +1 -1
- package/dist/src/platforms/instagram/mqtt/transport.js.map +1 -1
- package/dist/src/platforms/instagram/realtime-listener.d.ts.map +1 -1
- package/dist/src/platforms/instagram/realtime-listener.js.map +1 -1
- package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/instagram/types.d.ts.map +1 -1
- package/dist/src/platforms/instagram/types.js.map +1 -1
- package/dist/src/platforms/kakaotalk/cli.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts +5 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +55 -0
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/chat.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/chat.js +16 -1
- package/dist/src/platforms/kakaotalk/commands/chat.js.map +1 -1
- package/dist/src/platforms/kakaotalk/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/index.d.ts +2 -2
- package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/index.js +1 -1
- package/dist/src/platforms/kakaotalk/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/listener.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/listener.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/crypto.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts +23 -0
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.js +50 -0
- package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
- package/dist/src/platforms/kakaotalk/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +30 -10
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +10 -0
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/dist/src/platforms/line/cli.d.ts.map +1 -1
- package/dist/src/platforms/line/client.d.ts.map +1 -1
- package/dist/src/platforms/line/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/line/listener.d.ts.map +1 -1
- package/dist/src/platforms/line/types.d.ts +6 -6
- package/dist/src/platforms/line/types.d.ts.map +1 -1
- package/dist/src/platforms/slack/cli.d.ts.map +1 -1
- package/dist/src/platforms/slack/client.d.ts.map +1 -1
- package/dist/src/platforms/slack/client.js.map +1 -1
- package/dist/src/platforms/slack/commands/message.js +4 -4
- package/dist/src/platforms/slack/commands/message.js.map +1 -1
- package/dist/src/platforms/slack/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/slack/ensure-auth.js.map +1 -1
- package/dist/src/platforms/slack/listener.d.ts.map +1 -1
- package/dist/src/platforms/slack/qr-http-login.js.map +1 -1
- package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/slack/token-extractor.js.map +1 -1
- package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/client.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/commands/message.js +4 -4
- package/dist/src/platforms/slackbot/commands/message.js.map +1 -1
- package/dist/src/platforms/slackbot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/listener.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/listener.js.map +1 -1
- package/dist/src/platforms/slackbot/types.d.ts.map +1 -1
- package/dist/src/platforms/teams/app-config.js.map +1 -1
- package/dist/src/platforms/teams/cli.d.ts.map +1 -1
- package/dist/src/platforms/teams/client.d.ts +1 -0
- package/dist/src/platforms/teams/client.d.ts.map +1 -1
- package/dist/src/platforms/teams/client.js +19 -0
- package/dist/src/platforms/teams/client.js.map +1 -1
- package/dist/src/platforms/teams/commands/chat.d.ts +3 -0
- package/dist/src/platforms/teams/commands/chat.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/chat.js +34 -1
- package/dist/src/platforms/teams/commands/chat.js.map +1 -1
- package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/teams/device-code.js.map +1 -1
- package/dist/src/platforms/teams/device-login.d.ts.map +1 -1
- package/dist/src/platforms/teams/device-login.js.map +1 -1
- package/dist/src/platforms/teams/listener.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/token-provider.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-provider.js.map +1 -1
- package/dist/src/platforms/teams/types.d.ts +14 -14
- package/dist/src/platforms/teams/types.d.ts.map +1 -1
- package/dist/src/platforms/telegram/cli.d.ts.map +1 -1
- package/dist/src/platforms/telegram/client.d.ts +1 -0
- package/dist/src/platforms/telegram/client.d.ts.map +1 -1
- package/dist/src/platforms/telegram/client.js +21 -0
- package/dist/src/platforms/telegram/client.js.map +1 -1
- package/dist/src/platforms/telegram/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/telegram/commands/message.js +25 -1
- package/dist/src/platforms/telegram/commands/message.js.map +1 -1
- package/dist/src/platforms/telegram/commands/shared.js.map +1 -1
- package/dist/src/platforms/telegram/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/telegram/tdlib.d.ts.map +1 -1
- package/dist/src/platforms/telegram/types.d.ts.map +1 -1
- package/dist/src/platforms/telegram/types.js.map +1 -1
- package/dist/src/platforms/telegrambot/cli.d.ts.map +1 -1
- package/dist/src/platforms/telegrambot/client.d.ts.map +1 -1
- package/dist/src/platforms/telegrambot/commands/message.d.ts +1 -1
- package/dist/src/platforms/telegrambot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/telegrambot/commands/message.js +3 -3
- package/dist/src/platforms/telegrambot/commands/message.js.map +1 -1
- package/dist/src/platforms/telegrambot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/telegrambot/listener.d.ts.map +1 -1
- package/dist/src/platforms/telegrambot/types.d.ts.map +1 -1
- package/dist/src/platforms/webex/cli.d.ts.map +1 -1
- package/dist/src/platforms/webex/client.d.ts.map +1 -1
- package/dist/src/platforms/webex/client.js.map +1 -1
- package/dist/src/platforms/webex/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/webex/encryption.d.ts.map +1 -1
- package/dist/src/platforms/webex/kms-key-provider.d.ts.map +1 -1
- package/dist/src/platforms/webex/listener.d.ts.map +1 -1
- package/dist/src/platforms/webex/listener.js.map +1 -1
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/webex/types.d.ts +4 -4
- package/dist/src/platforms/webex/types.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/client.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/listener.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/types.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/client.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/types.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/cli.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.d.ts +3 -1
- package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.js +19 -3
- package/dist/src/platforms/whatsapp/client.js.map +1 -1
- package/dist/src/platforms/whatsapp/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/commands/message.js +18 -0
- package/dist/src/platforms/whatsapp/commands/message.js.map +1 -1
- package/dist/src/platforms/whatsapp/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/types.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/types.js.map +1 -1
- package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/types.d.ts.map +1 -1
- package/dist/src/shared/chromium/browsers.js.map +1 -1
- package/dist/src/shared/chromium/cli-options.js.map +1 -1
- package/dist/src/shared/chromium/cookie-reader.d.ts.map +1 -1
- package/dist/src/shared/chromium/decryptor.d.ts.map +1 -1
- package/dist/src/shared/sqlite.js.map +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts.map +1 -1
- package/dist/src/tui/adapters/channeltalk-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/discord-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/imessage-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/instagram-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/kakaotalk-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/line-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/slack-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/telegram-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/telegram-adapter.js.map +1 -1
- package/dist/src/tui/adapters/webex-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/whatsapp-adapter.d.ts.map +1 -1
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/views/channel-picker.d.ts.map +1 -1
- package/dist/src/tui/views/workspace-picker.d.ts.map +1 -1
- package/docs/content/docs/cli/discordbot.mdx +2 -2
- package/docs/content/docs/cli/kakaotalk.mdx +16 -1
- package/docs/content/docs/cli/slack.mdx +2 -2
- package/docs/content/docs/cli/slackbot.mdx +2 -2
- package/docs/content/docs/cli/telegrambot.mdx +1 -1
- package/docs/content/docs/sdk/kakaotalk.mdx +39 -0
- package/e2e/README.md +3 -3
- package/e2e/discordbot.e2e.test.ts +2 -2
- package/e2e/slack.e2e.test.ts +2 -2
- package/e2e/slackbot.e2e.test.ts +2 -2
- package/e2e/telegrambot.e2e.test.ts +2 -2
- package/package.json +2 -2
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +5 -1
- package/skills/agent-discordbot/SKILL.md +3 -3
- package/skills/agent-discordbot/references/common-patterns.md +2 -2
- package/skills/agent-imessage/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +19 -2
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +3 -3
- package/skills/agent-slack/references/common-patterns.md +2 -2
- package/skills/agent-slackbot/SKILL.md +3 -3
- package/skills/agent-slackbot/references/common-patterns.md +4 -4
- package/skills/agent-teams/SKILL.md +6 -1
- package/skills/agent-telegram/SKILL.md +4 -1
- package/skills/agent-telegrambot/SKILL.md +2 -2
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +25 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/discord/client.test.ts +143 -0
- package/src/platforms/discord/client.ts +49 -22
- package/src/platforms/discord/commands/message.test.ts +26 -1
- package/src/platforms/discord/commands/message.ts +41 -0
- package/src/platforms/discord/types.ts +16 -0
- package/src/platforms/discordbot/commands/message.test.ts +6 -6
- package/src/platforms/discordbot/commands/message.ts +4 -4
- package/src/platforms/kakaotalk/client.test.ts +194 -1
- package/src/platforms/kakaotalk/client.ts +58 -0
- package/src/platforms/kakaotalk/commands/chat.test.ts +24 -0
- package/src/platforms/kakaotalk/commands/chat.ts +23 -0
- package/src/platforms/kakaotalk/index.test.ts +10 -0
- package/src/platforms/kakaotalk/index.ts +4 -0
- package/src/platforms/kakaotalk/protocol/session.test.ts +113 -0
- package/src/platforms/kakaotalk/protocol/session.ts +56 -0
- package/src/platforms/kakaotalk/types.ts +24 -0
- package/src/platforms/slack/commands/message.test.ts +4 -4
- package/src/platforms/slack/commands/message.ts +4 -4
- package/src/platforms/slackbot/commands/message.ts +4 -4
- package/src/platforms/teams/client.test.ts +24 -0
- package/src/platforms/teams/client.ts +28 -0
- package/src/platforms/teams/commands/chat.test.ts +23 -1
- package/src/platforms/teams/commands/chat.ts +44 -0
- package/src/platforms/telegram/client.test.ts +60 -0
- package/src/platforms/telegram/client.ts +24 -0
- package/src/platforms/telegram/commands/message.test.ts +38 -0
- package/src/platforms/telegram/commands/message.ts +39 -0
- package/src/platforms/telegrambot/commands/message.ts +3 -3
- package/src/platforms/whatsapp/client.test.ts +58 -0
- package/src/platforms/whatsapp/client.ts +23 -3
- package/src/platforms/whatsapp/commands/message.test.ts +43 -0
- package/src/platforms/whatsapp/commands/message.ts +25 -0
- package/tsconfig.json +8 -8
|
@@ -246,6 +246,28 @@ describe('DiscordClient', () => {
|
|
|
246
246
|
})
|
|
247
247
|
})
|
|
248
248
|
|
|
249
|
+
describe('editMessage', () => {
|
|
250
|
+
it('edits message with PATCH', async () => {
|
|
251
|
+
mockResponse({
|
|
252
|
+
id: 'msg1',
|
|
253
|
+
channel_id: 'ch1',
|
|
254
|
+
author: { id: '123', username: 'bot' },
|
|
255
|
+
content: 'Updated content',
|
|
256
|
+
timestamp: '2024-01-01T00:00:00.000Z',
|
|
257
|
+
edited_timestamp: '2024-01-01T00:05:00.000Z',
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
const client = await new DiscordClient().login({ token: 'test-token' })
|
|
261
|
+
const message = await client.editMessage('ch1', 'msg1', 'Updated content')
|
|
262
|
+
|
|
263
|
+
expect(message.content).toBe('Updated content')
|
|
264
|
+
expect(message.edited_timestamp).toBe('2024-01-01T00:05:00.000Z')
|
|
265
|
+
expect(fetchCalls[0].url).toBe('https://discord.com/api/v10/channels/ch1/messages/msg1')
|
|
266
|
+
expect(fetchCalls[0].options?.method).toBe('PATCH')
|
|
267
|
+
expect(fetchCalls[0].options?.body).toBe(JSON.stringify({ content: 'Updated content' }))
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
249
271
|
describe('deleteMessage', () => {
|
|
250
272
|
it('deletes message', async () => {
|
|
251
273
|
mockResponse(null, 204)
|
|
@@ -425,6 +447,127 @@ describe('DiscordClient', () => {
|
|
|
425
447
|
})
|
|
426
448
|
})
|
|
427
449
|
|
|
450
|
+
describe('searchMessages', () => {
|
|
451
|
+
const searchResult = {
|
|
452
|
+
id: 'msg1',
|
|
453
|
+
channel_id: 'ch1',
|
|
454
|
+
guild_id: 'guild1',
|
|
455
|
+
author: { id: 'user1', username: 'user1' },
|
|
456
|
+
content: 'matching message',
|
|
457
|
+
timestamp: '2026-01-01T00:00:00.000Z',
|
|
458
|
+
hit: true,
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
it('flattens grouped search results and keeps hits', async () => {
|
|
462
|
+
mockResponse({
|
|
463
|
+
doing_deep_historical_index: false,
|
|
464
|
+
total_results: 1,
|
|
465
|
+
messages: [[searchResult, { ...searchResult, id: 'context', hit: false }]],
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
const client = await new DiscordClient().login({ token: 'test-token' })
|
|
469
|
+
const result = await client.searchMessages('guild1', 'matching', { limit: 25 })
|
|
470
|
+
|
|
471
|
+
expect(result).toEqual({ results: [searchResult], total: 1 })
|
|
472
|
+
expect(fetchCalls[0].url).toBe(
|
|
473
|
+
'https://discord.com/api/v10/guilds/guild1/messages/search?content=matching&limit=25',
|
|
474
|
+
)
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
it('retries when the search index is not ready', async () => {
|
|
478
|
+
mockResponse(
|
|
479
|
+
{
|
|
480
|
+
message: 'Index not yet available. Try again later',
|
|
481
|
+
code: 110000,
|
|
482
|
+
documents_indexed: 0,
|
|
483
|
+
retry_after: 0,
|
|
484
|
+
},
|
|
485
|
+
202,
|
|
486
|
+
)
|
|
487
|
+
mockResponse({
|
|
488
|
+
doing_deep_historical_index: false,
|
|
489
|
+
total_results: 1,
|
|
490
|
+
messages: [[searchResult]],
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
const client = await new DiscordClient().login({ token: 'test-token' })
|
|
494
|
+
const startTime = Date.now()
|
|
495
|
+
const result = await client.searchMessages('guild1', 'matching')
|
|
496
|
+
const elapsed = Date.now() - startTime
|
|
497
|
+
|
|
498
|
+
expect(result).toEqual({ results: [searchResult], total: 1 })
|
|
499
|
+
expect(elapsed).toBeGreaterThanOrEqual(50)
|
|
500
|
+
expect(fetchCalls).toHaveLength(2)
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
it('throws a clear error when the search index remains unavailable', async () => {
|
|
504
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
505
|
+
mockResponse(
|
|
506
|
+
{
|
|
507
|
+
message: 'Index not yet available. Try again later',
|
|
508
|
+
code: 110000,
|
|
509
|
+
documents_indexed: 0,
|
|
510
|
+
retry_after: 0.001,
|
|
511
|
+
},
|
|
512
|
+
202,
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const client = await new DiscordClient().login({ token: 'test-token' })
|
|
517
|
+
|
|
518
|
+
await expect(client.searchMessages('guild1', 'matching')).rejects.toThrow(
|
|
519
|
+
'Search index is not ready after retries',
|
|
520
|
+
)
|
|
521
|
+
expect(fetchCalls).toHaveLength(4)
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
it('rejects unexpected successful response payloads', async () => {
|
|
525
|
+
mockResponse(
|
|
526
|
+
{
|
|
527
|
+
message: 'Unexpected response',
|
|
528
|
+
code: 999999,
|
|
529
|
+
retry_after: 0,
|
|
530
|
+
},
|
|
531
|
+
202,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
const client = await new DiscordClient().login({ token: 'test-token' })
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
await client.searchMessages('guild1', 'matching')
|
|
538
|
+
throw new Error('Expected searchMessages to throw')
|
|
539
|
+
} catch (error) {
|
|
540
|
+
expect(error).toBeInstanceOf(DiscordError)
|
|
541
|
+
if (!(error instanceof DiscordError)) throw error
|
|
542
|
+
expect(error.code).toBe('invalid_search_response')
|
|
543
|
+
}
|
|
544
|
+
expect(fetchCalls).toHaveLength(1)
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
it('rejects malformed message and retry payloads', async () => {
|
|
548
|
+
mockResponse({ total_results: 1, messages: null })
|
|
549
|
+
mockResponse(
|
|
550
|
+
{
|
|
551
|
+
message: 'Index not yet available. Try again later',
|
|
552
|
+
code: 110000,
|
|
553
|
+
documents_indexed: 0,
|
|
554
|
+
retry_after: -1,
|
|
555
|
+
},
|
|
556
|
+
202,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
const client = await new DiscordClient().login({ token: 'test-token' })
|
|
560
|
+
|
|
561
|
+
await expect(client.searchMessages('guild1', 'matching')).rejects.toThrow(
|
|
562
|
+
'Discord returned an invalid search response',
|
|
563
|
+
)
|
|
564
|
+
await expect(client.searchMessages('guild1', 'matching')).rejects.toThrow(
|
|
565
|
+
'Discord returned an invalid search response',
|
|
566
|
+
)
|
|
567
|
+
expect(fetchCalls).toHaveLength(2)
|
|
568
|
+
})
|
|
569
|
+
})
|
|
570
|
+
|
|
428
571
|
describe('rate limiting', () => {
|
|
429
572
|
it('waits when bucket is exhausted before making request', async () => {
|
|
430
573
|
mockResponse({ id: '1', username: 'user1' }, 200, {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
2
|
|
|
3
3
|
import { getDiscordHeaders } from './super-properties'
|
|
4
|
+
import { DiscordSearchIndexNotReadyResponseSchema, DiscordSearchResponseSchema } from './types'
|
|
4
5
|
import type {
|
|
5
6
|
DiscordChannel,
|
|
6
7
|
DiscordDMChannel,
|
|
@@ -11,7 +12,6 @@ import type {
|
|
|
11
12
|
DiscordMessage,
|
|
12
13
|
DiscordRelationship,
|
|
13
14
|
DiscordSearchOptions,
|
|
14
|
-
DiscordSearchResponse,
|
|
15
15
|
DiscordSearchResult,
|
|
16
16
|
DiscordUser,
|
|
17
17
|
DiscordUserNote,
|
|
@@ -37,6 +37,7 @@ interface RateLimitBucket {
|
|
|
37
37
|
const BASE_URL = 'https://discord.com/api/v10'
|
|
38
38
|
const MAX_RETRIES = 3
|
|
39
39
|
const BASE_BACKOFF_MS = 100
|
|
40
|
+
const MAX_SEARCH_INDEX_RETRY_MS = 30_000
|
|
40
41
|
|
|
41
42
|
export class DiscordClient {
|
|
42
43
|
private token: string | null = null
|
|
@@ -242,6 +243,10 @@ export class DiscordClient {
|
|
|
242
243
|
return this.request<DiscordMessage>('GET', `/channels/${channelId}/messages/${messageId}`)
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
async editMessage(channelId: string, messageId: string, content: string): Promise<DiscordMessage> {
|
|
247
|
+
return this.request<DiscordMessage>('PATCH', `/channels/${channelId}/messages/${messageId}`, { content })
|
|
248
|
+
}
|
|
249
|
+
|
|
245
250
|
async deleteMessage(channelId: string, messageId: string): Promise<void> {
|
|
246
251
|
return this.request<void>('DELETE', `/channels/${channelId}/messages/${messageId}`)
|
|
247
252
|
}
|
|
@@ -389,28 +394,50 @@ export class DiscordClient {
|
|
|
389
394
|
params.set('offset', options.offset.toString())
|
|
390
395
|
}
|
|
391
396
|
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
397
|
+
const path = `/guilds/${guildId}/messages/search?${params.toString()}`
|
|
398
|
+
|
|
399
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
400
|
+
const response: unknown = await this.request('GET', path)
|
|
401
|
+
const searchResponse = DiscordSearchResponseSchema.safeParse(response)
|
|
402
|
+
|
|
403
|
+
if (searchResponse.success) {
|
|
404
|
+
const results = searchResponse.data.messages
|
|
405
|
+
.flat()
|
|
406
|
+
.filter((msg) => msg.hit)
|
|
407
|
+
.map((msg) => ({
|
|
408
|
+
id: msg.id,
|
|
409
|
+
channel_id: msg.channel_id,
|
|
410
|
+
guild_id: msg.guild_id,
|
|
411
|
+
content: msg.content,
|
|
412
|
+
author: msg.author,
|
|
413
|
+
timestamp: msg.timestamp,
|
|
414
|
+
hit: msg.hit,
|
|
415
|
+
}))
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
results,
|
|
419
|
+
total: searchResponse.data.total_results,
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const indexNotReadyResponse = DiscordSearchIndexNotReadyResponseSchema.safeParse(response)
|
|
424
|
+
if (!indexNotReadyResponse.success) {
|
|
425
|
+
throw new DiscordError('Discord returned an invalid search response', 'invalid_search_response')
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (attempt === MAX_RETRIES) {
|
|
429
|
+
throw new DiscordError('Search index is not ready after retries', 'search_index_not_ready')
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const requestedDelayMs = indexNotReadyResponse.data.retry_after * 1000
|
|
433
|
+
const retryDelayMs = Math.min(
|
|
434
|
+
requestedDelayMs === 0 ? BASE_BACKOFF_MS : requestedDelayMs,
|
|
435
|
+
MAX_SEARCH_INDEX_RETRY_MS,
|
|
436
|
+
)
|
|
437
|
+
await this.sleep(retryDelayMs)
|
|
413
438
|
}
|
|
439
|
+
|
|
440
|
+
throw new DiscordError('Search failed after retries', 'max_retries')
|
|
414
441
|
}
|
|
415
442
|
|
|
416
443
|
async getUserProfile(userId: string): Promise<DiscordUserProfile> {
|
|
@@ -2,11 +2,12 @@ import { afterEach, beforeEach, expect, mock, spyOn, it } from 'bun:test'
|
|
|
2
2
|
|
|
3
3
|
import { DiscordClient } from '../client'
|
|
4
4
|
import { DiscordCredentialManager } from '../credential-manager'
|
|
5
|
-
import { ackAction, deleteAction, getAction, listAction, searchAction, sendAction } from './message'
|
|
5
|
+
import { ackAction, deleteAction, editAction, getAction, listAction, searchAction, sendAction } from './message'
|
|
6
6
|
|
|
7
7
|
let clientSendMessageSpy: ReturnType<typeof spyOn>
|
|
8
8
|
let clientGetMessagesSpy: ReturnType<typeof spyOn>
|
|
9
9
|
let clientGetMessageSpy: ReturnType<typeof spyOn>
|
|
10
|
+
let clientEditMessageSpy: ReturnType<typeof spyOn>
|
|
10
11
|
let clientDeleteMessageSpy: ReturnType<typeof spyOn>
|
|
11
12
|
let clientAckMessageSpy: ReturnType<typeof spyOn>
|
|
12
13
|
let clientSearchMessagesSpy: ReturnType<typeof spyOn>
|
|
@@ -47,6 +48,15 @@ beforeEach(() => {
|
|
|
47
48
|
timestamp: '2025-01-29T10:00:00Z',
|
|
48
49
|
})
|
|
49
50
|
|
|
51
|
+
clientEditMessageSpy = spyOn(DiscordClient.prototype, 'editMessage').mockResolvedValue({
|
|
52
|
+
id: 'msg_123',
|
|
53
|
+
channel_id: 'ch_456',
|
|
54
|
+
author: { id: 'user_789', username: 'testuser' },
|
|
55
|
+
content: 'Updated content',
|
|
56
|
+
timestamp: '2025-01-29T10:00:00Z',
|
|
57
|
+
edited_timestamp: '2025-01-29T10:05:00Z',
|
|
58
|
+
})
|
|
59
|
+
|
|
50
60
|
clientDeleteMessageSpy = spyOn(DiscordClient.prototype, 'deleteMessage').mockResolvedValue(undefined)
|
|
51
61
|
|
|
52
62
|
clientAckMessageSpy = spyOn(DiscordClient.prototype, 'ackMessage').mockResolvedValue(undefined)
|
|
@@ -87,6 +97,7 @@ afterEach(() => {
|
|
|
87
97
|
clientSendMessageSpy?.mockRestore()
|
|
88
98
|
clientGetMessagesSpy?.mockRestore()
|
|
89
99
|
clientGetMessageSpy?.mockRestore()
|
|
100
|
+
clientEditMessageSpy?.mockRestore()
|
|
90
101
|
clientDeleteMessageSpy?.mockRestore()
|
|
91
102
|
clientAckMessageSpy?.mockRestore()
|
|
92
103
|
clientSearchMessagesSpy?.mockRestore()
|
|
@@ -127,6 +138,20 @@ it('get: returns single message', async () => {
|
|
|
127
138
|
expect(output).toContain('msg_123')
|
|
128
139
|
})
|
|
129
140
|
|
|
141
|
+
it('edit: returns updated message', async () => {
|
|
142
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
143
|
+
console.log = consoleSpy
|
|
144
|
+
|
|
145
|
+
await editAction('ch_456', 'msg_123', 'Updated content', { pretty: false })
|
|
146
|
+
|
|
147
|
+
expect(clientEditMessageSpy).toHaveBeenCalledWith('ch_456', 'msg_123', 'Updated content')
|
|
148
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
149
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
150
|
+
expect(output).toContain('msg_123')
|
|
151
|
+
expect(output).toContain('Updated content')
|
|
152
|
+
expect(output).toContain('edited_timestamp')
|
|
153
|
+
})
|
|
154
|
+
|
|
130
155
|
it('delete: returns success', async () => {
|
|
131
156
|
const consoleSpy = mock((_msg: string) => {})
|
|
132
157
|
console.log = consoleSpy
|
|
@@ -121,6 +121,38 @@ export async function deleteAction(
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
export async function editAction(
|
|
125
|
+
channelId: string,
|
|
126
|
+
messageId: string,
|
|
127
|
+
content: string,
|
|
128
|
+
options: { pretty?: boolean },
|
|
129
|
+
): Promise<void> {
|
|
130
|
+
try {
|
|
131
|
+
const credManager = new DiscordCredentialManager()
|
|
132
|
+
const config = await credManager.load()
|
|
133
|
+
|
|
134
|
+
if (!config.token) {
|
|
135
|
+
console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
|
|
136
|
+
process.exit(1)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const client = await new DiscordClient().login({ token: config.token })
|
|
140
|
+
const message = await client.editMessage(channelId, messageId, content)
|
|
141
|
+
|
|
142
|
+
const output = {
|
|
143
|
+
id: message.id,
|
|
144
|
+
content: message.content,
|
|
145
|
+
author: message.author.username,
|
|
146
|
+
timestamp: message.timestamp,
|
|
147
|
+
edited_timestamp: message.edited_timestamp,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(formatOutput(output, options.pretty))
|
|
151
|
+
} catch (error) {
|
|
152
|
+
handleError(error as Error)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
124
156
|
export async function ackAction(channelId: string, messageId: string, options: { pretty?: boolean }): Promise<void> {
|
|
125
157
|
try {
|
|
126
158
|
const credManager = new DiscordCredentialManager()
|
|
@@ -244,6 +276,15 @@ export const messageCommand = new Command('message')
|
|
|
244
276
|
.option('--pretty', 'Pretty print JSON output')
|
|
245
277
|
.action(deleteAction),
|
|
246
278
|
)
|
|
279
|
+
.addCommand(
|
|
280
|
+
new Command('edit')
|
|
281
|
+
.description('Edit a message')
|
|
282
|
+
.argument('<channel-id>', 'Channel ID')
|
|
283
|
+
.argument('<message-id>', 'Message ID')
|
|
284
|
+
.argument('<content>', 'New message content')
|
|
285
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
286
|
+
.action(editAction),
|
|
287
|
+
)
|
|
247
288
|
.addCommand(
|
|
248
289
|
new Command('ack')
|
|
249
290
|
.description('Mark message as read (acknowledge)')
|
|
@@ -109,6 +109,15 @@ export interface DiscordSearchResponse {
|
|
|
109
109
|
messages: DiscordSearchResult[][]
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
export interface DiscordSearchIndexNotReadyResponse {
|
|
113
|
+
message: string
|
|
114
|
+
code: 110000
|
|
115
|
+
documents_indexed: number
|
|
116
|
+
retry_after: number
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type DiscordSearchApiResponse = DiscordSearchResponse | DiscordSearchIndexNotReadyResponse
|
|
120
|
+
|
|
112
121
|
export interface DiscordSearchOptions {
|
|
113
122
|
channelId?: string
|
|
114
123
|
authorId?: string
|
|
@@ -267,6 +276,13 @@ export const DiscordSearchResponseSchema = z.object({
|
|
|
267
276
|
messages: z.array(z.array(DiscordSearchResultSchema)),
|
|
268
277
|
})
|
|
269
278
|
|
|
279
|
+
export const DiscordSearchIndexNotReadyResponseSchema = z.object({
|
|
280
|
+
message: z.string(),
|
|
281
|
+
code: z.literal(110000),
|
|
282
|
+
documents_indexed: z.number(),
|
|
283
|
+
retry_after: z.number().nonnegative(),
|
|
284
|
+
})
|
|
285
|
+
|
|
270
286
|
export const DiscordCredentialsSchema = z.object({
|
|
271
287
|
token: z.string(),
|
|
272
288
|
})
|
|
@@ -82,7 +82,7 @@ mock.module('../client', () => ({
|
|
|
82
82
|
}))
|
|
83
83
|
|
|
84
84
|
import { DiscordBotCredentialManager } from '../credential-manager'
|
|
85
|
-
import { deleteAction, getAction, listAction, repliesAction, sendAction
|
|
85
|
+
import { deleteAction, editAction, getAction, listAction, repliesAction, sendAction } from './message'
|
|
86
86
|
|
|
87
87
|
describe('message commands', () => {
|
|
88
88
|
let tempDir: string
|
|
@@ -243,9 +243,9 @@ describe('message commands', () => {
|
|
|
243
243
|
})
|
|
244
244
|
})
|
|
245
245
|
|
|
246
|
-
describe('
|
|
247
|
-
it('
|
|
248
|
-
const result = await
|
|
246
|
+
describe('editAction', () => {
|
|
247
|
+
it('edits a message', async () => {
|
|
248
|
+
const result = await editAction('general', 'msg1', 'updated text', { _credManager: manager })
|
|
249
249
|
|
|
250
250
|
expect(result.id).toBe('msg1')
|
|
251
251
|
expect(result.content).toBe('updated text')
|
|
@@ -254,7 +254,7 @@ describe('message commands', () => {
|
|
|
254
254
|
})
|
|
255
255
|
|
|
256
256
|
it('resolves channel name', async () => {
|
|
257
|
-
await
|
|
257
|
+
await editAction('general', 'msg1', 'new', { _credManager: manager })
|
|
258
258
|
|
|
259
259
|
expect(mockResolveChannel).toHaveBeenCalledWith('guild1', 'general')
|
|
260
260
|
})
|
|
@@ -262,7 +262,7 @@ describe('message commands', () => {
|
|
|
262
262
|
it('returns error on failure', async () => {
|
|
263
263
|
mockEditMessage.mockImplementationOnce(() => Promise.reject(new Error('Cannot edit')))
|
|
264
264
|
|
|
265
|
-
const result = await
|
|
265
|
+
const result = await editAction('general', 'msg1', 'new', { _credManager: manager })
|
|
266
266
|
|
|
267
267
|
expect(result.error).toContain('Cannot edit')
|
|
268
268
|
})
|
|
@@ -95,7 +95,7 @@ export async function getAction(channel: string, messageId: string, options: Bot
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
export async function
|
|
98
|
+
export async function editAction(
|
|
99
99
|
channel: string,
|
|
100
100
|
messageId: string,
|
|
101
101
|
text: string,
|
|
@@ -207,8 +207,8 @@ export const messageCommand = new Command('message')
|
|
|
207
207
|
}),
|
|
208
208
|
)
|
|
209
209
|
.addCommand(
|
|
210
|
-
new Command('
|
|
211
|
-
.description('
|
|
210
|
+
new Command('edit')
|
|
211
|
+
.description('Edit a message')
|
|
212
212
|
.argument('<channel>', 'Channel ID or name')
|
|
213
213
|
.argument('<message-id>', 'Message ID')
|
|
214
214
|
.argument('<text>', 'New message text')
|
|
@@ -216,7 +216,7 @@ export const messageCommand = new Command('message')
|
|
|
216
216
|
.option('--server <id>', 'Server ID')
|
|
217
217
|
.option('--pretty', 'Pretty print JSON output')
|
|
218
218
|
.action(async (channel: string, messageId: string, text: string, opts: BotOption) => {
|
|
219
|
-
cliOutput(await
|
|
219
|
+
cliOutput(await editAction(channel, messageId, text, opts), opts.pretty)
|
|
220
220
|
}),
|
|
221
221
|
)
|
|
222
222
|
.addCommand(
|