agent-messenger 2.10.2 → 2.11.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/.env.template +4 -1
- package/README.md +77 -27
- package/bun.lock +26 -0
- package/dist/package.json +14 -1
- package/dist/src/platforms/channeltalk/commands/auth.d.ts +2 -1
- package/dist/src/platforms/channeltalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/commands/auth.js +5 -3
- package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts +2 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.js +22 -6
- package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/cli.js +11 -1
- package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/auth.js +1 -5
- package/dist/src/platforms/channeltalkbot/commands/auth.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/bot.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/bot.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/bot.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/chat.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/chat.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/chat.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/group.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/group.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/group.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/manager.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/manager.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/manager.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/message.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/message.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/whoami.js +1 -6
- package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/credential-manager.d.ts +5 -0
- package/dist/src/platforms/channeltalkbot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/credential-manager.js +34 -4
- package/dist/src/platforms/channeltalkbot/credential-manager.js.map +1 -1
- package/dist/src/platforms/discord/commands/auth.d.ts +1 -0
- package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/auth.js +3 -1
- package/dist/src/platforms/discord/commands/auth.js.map +1 -1
- package/dist/src/platforms/discord/listener.d.ts +2 -0
- package/dist/src/platforms/discord/listener.d.ts.map +1 -1
- package/dist/src/platforms/discord/listener.js +51 -21
- package/dist/src/platforms/discord/listener.js.map +1 -1
- package/dist/src/platforms/discord/token-extractor.d.ts +2 -1
- package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/discord/token-extractor.js +21 -6
- package/dist/src/platforms/discord/token-extractor.js.map +1 -1
- package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/cli.js +12 -1
- package/dist/src/platforms/discordbot/cli.js.map +1 -1
- package/dist/src/platforms/discordbot/client.d.ts +3 -0
- package/dist/src/platforms/discordbot/client.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/client.js +3 -0
- package/dist/src/platforms/discordbot/client.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/auth.js +1 -5
- package/dist/src/platforms/discordbot/commands/auth.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/message.js +1 -6
- package/dist/src/platforms/discordbot/commands/message.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/server.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/server.js +1 -4
- package/dist/src/platforms/discordbot/commands/server.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/whoami.js +1 -6
- package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -1
- package/dist/src/platforms/discordbot/index.d.ts +3 -1
- package/dist/src/platforms/discordbot/index.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/index.js +2 -1
- package/dist/src/platforms/discordbot/index.js.map +1 -1
- package/dist/src/platforms/discordbot/listener.d.ts +43 -0
- package/dist/src/platforms/discordbot/listener.d.ts.map +1 -0
- package/dist/src/platforms/discordbot/listener.js +292 -0
- package/dist/src/platforms/discordbot/listener.js.map +1 -0
- package/dist/src/platforms/discordbot/types.d.ts +161 -0
- package/dist/src/platforms/discordbot/types.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/types.js +34 -0
- package/dist/src/platforms/discordbot/types.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +3 -1
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/token-extractor.d.ts +2 -1
- package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/instagram/token-extractor.js +11 -2
- package/dist/src/platforms/instagram/token-extractor.js.map +1 -1
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/auth.js +4 -2
- package/dist/src/platforms/slack/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/token-extractor.d.ts +4 -1
- package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/slack/token-extractor.js +64 -15
- 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/cli.js +15 -3
- package/dist/src/platforms/slackbot/cli.js.map +1 -1
- package/dist/src/platforms/slackbot/client.d.ts +22 -1
- package/dist/src/platforms/slackbot/client.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/client.js +104 -1
- package/dist/src/platforms/slackbot/client.js.map +1 -1
- package/dist/src/platforms/slackbot/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/commands/auth.js +1 -5
- package/dist/src/platforms/slackbot/commands/auth.js.map +1 -1
- package/dist/src/platforms/slackbot/commands/file.d.ts +3 -0
- package/dist/src/platforms/slackbot/commands/file.d.ts.map +1 -0
- package/dist/src/platforms/slackbot/commands/file.js +164 -0
- package/dist/src/platforms/slackbot/commands/file.js.map +1 -0
- package/dist/src/platforms/slackbot/commands/index.d.ts +1 -0
- package/dist/src/platforms/slackbot/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/commands/index.js +1 -0
- package/dist/src/platforms/slackbot/commands/index.js.map +1 -1
- package/dist/src/platforms/slackbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/commands/message.js +19 -0
- package/dist/src/platforms/slackbot/commands/message.js.map +1 -1
- package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/commands/whoami.js +1 -6
- package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -1
- package/dist/src/platforms/slackbot/credential-manager.d.ts +1 -0
- package/dist/src/platforms/slackbot/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/credential-manager.js +30 -2
- package/dist/src/platforms/slackbot/credential-manager.js.map +1 -1
- package/dist/src/platforms/slackbot/index.d.ts +4 -1
- package/dist/src/platforms/slackbot/index.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/index.js +1 -0
- package/dist/src/platforms/slackbot/index.js.map +1 -1
- package/dist/src/platforms/slackbot/listener.d.ts +44 -0
- package/dist/src/platforms/slackbot/listener.d.ts.map +1 -0
- package/dist/src/platforms/slackbot/listener.js +313 -0
- package/dist/src/platforms/slackbot/listener.js.map +1 -0
- package/dist/src/platforms/slackbot/types.d.ts +196 -1
- package/dist/src/platforms/slackbot/types.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/types.js +4 -1
- package/dist/src/platforms/slackbot/types.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts +1 -0
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +37 -6
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +31 -9
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/token-extractor.d.ts +4 -1
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +71 -29
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/webex/commands/auth.d.ts +1 -0
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/auth.js +3 -1
- package/dist/src/platforms/webex/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/token-extractor.d.ts +3 -1
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/webex/token-extractor.js +16 -2
- package/dist/src/platforms/webex/token-extractor.js.map +1 -1
- package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/cli.js +11 -1
- package/dist/src/platforms/wechatbot/cli.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/auth.js +1 -5
- package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/message.js +1 -6
- package/dist/src/platforms/wechatbot/commands/message.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/template.js +1 -6
- package/dist/src/platforms/wechatbot/commands/template.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/user.js +1 -6
- package/dist/src/platforms/wechatbot/commands/user.js.map +1 -1
- package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/wechatbot/commands/whoami.js +1 -6
- package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -1
- package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/cli.js +11 -1
- package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/auth.js +1 -5
- package/dist/src/platforms/whatsappbot/commands/auth.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/message.js +1 -6
- package/dist/src/platforms/whatsappbot/commands/message.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/template.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/template.js +1 -6
- package/dist/src/platforms/whatsappbot/commands/template.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/whoami.js +1 -6
- package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -1
- package/dist/src/shared/chromium/browsers.d.ts +8 -0
- package/dist/src/shared/chromium/browsers.d.ts.map +1 -1
- package/dist/src/shared/chromium/browsers.js +58 -3
- package/dist/src/shared/chromium/browsers.js.map +1 -1
- package/dist/src/shared/chromium/cli-options.d.ts +5 -0
- package/dist/src/shared/chromium/cli-options.d.ts.map +1 -0
- package/dist/src/shared/chromium/cli-options.js +8 -0
- package/dist/src/shared/chromium/cli-options.js.map +1 -0
- package/dist/src/shared/chromium/index.d.ts +3 -1
- package/dist/src/shared/chromium/index.d.ts.map +1 -1
- package/dist/src/shared/chromium/index.js +2 -1
- package/dist/src/shared/chromium/index.js.map +1 -1
- package/dist/src/shared/utils/cli-output.d.ts +7 -0
- package/dist/src/shared/utils/cli-output.d.ts.map +1 -0
- package/dist/src/shared/utils/cli-output.js +7 -0
- package/dist/src/shared/utils/cli-output.js.map +1 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +73 -20
- package/dist/src/tui/app.js.map +1 -1
- package/docs/content/docs/cli/channeltalk.mdx +4 -0
- package/docs/content/docs/cli/discord.mdx +5 -0
- package/docs/content/docs/cli/instagram.mdx +3 -0
- package/docs/content/docs/cli/slack.mdx +5 -0
- package/docs/content/docs/cli/slackbot.mdx +60 -22
- package/docs/content/docs/cli/teams.mdx +5 -0
- package/docs/content/docs/cli/webex.mdx +3 -0
- package/docs/content/docs/sdk/channeltalkbot.mdx +38 -1
- package/docs/content/docs/sdk/discordbot.mdx +501 -0
- package/docs/content/docs/sdk/meta.json +2 -0
- package/docs/content/docs/sdk/slackbot.mdx +576 -0
- package/e2e/README.md +1 -1
- package/e2e/config.ts +9 -4
- package/examples/discordbot-listen.ts +65 -0
- package/examples/slackbot-listen.ts +65 -0
- package/package.json +14 -1
- package/skills/agent-channeltalk/SKILL.md +5 -1
- package/skills/agent-channeltalk/references/authentication.md +5 -1
- package/skills/agent-channeltalkbot/SKILL.md +17 -3
- package/skills/agent-channeltalkbot/references/authentication.md +7 -5
- package/skills/agent-discord/SKILL.md +5 -1
- package/skills/agent-discord/references/authentication.md +7 -1
- package/skills/agent-discordbot/SKILL.md +13 -2
- package/skills/agent-discordbot/references/common-patterns.md +1 -1
- package/skills/agent-instagram/SKILL.md +7 -1
- package/skills/agent-instagram/references/authentication.md +6 -0
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +5 -1
- package/skills/agent-slack/references/authentication.md +7 -1
- package/skills/agent-slackbot/SKILL.md +56 -4
- package/skills/agent-slackbot/references/authentication.md +4 -0
- package/skills/agent-teams/SKILL.md +5 -1
- package/skills/agent-teams/references/authentication.md +7 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +7 -1
- package/skills/agent-webex/references/authentication.md +6 -0
- package/skills/agent-wechatbot/SKILL.md +16 -1
- package/skills/agent-wechatbot/references/authentication.md +219 -0
- package/skills/agent-wechatbot/references/common-patterns.md +358 -0
- package/skills/agent-wechatbot/templates/account-summary.sh +122 -0
- package/skills/agent-wechatbot/templates/post-message.sh +122 -0
- package/skills/agent-wechatbot/templates/send-template.sh +152 -0
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +30 -1
- package/src/platforms/channeltalk/commands/auth.test.ts +15 -3
- package/src/platforms/channeltalk/commands/auth.ts +15 -5
- package/src/platforms/channeltalk/token-extractor.ts +24 -5
- package/src/platforms/channeltalkbot/cli.ts +9 -0
- package/src/platforms/channeltalkbot/commands/auth.ts +1 -5
- package/src/platforms/channeltalkbot/commands/bot.ts +1 -6
- package/src/platforms/channeltalkbot/commands/chat.ts +1 -6
- package/src/platforms/channeltalkbot/commands/group.ts +1 -6
- package/src/platforms/channeltalkbot/commands/manager.ts +1 -6
- package/src/platforms/channeltalkbot/commands/message.ts +1 -6
- package/src/platforms/channeltalkbot/commands/whoami.test.ts +2 -0
- package/src/platforms/channeltalkbot/commands/whoami.ts +1 -6
- package/src/platforms/channeltalkbot/credential-manager.test.ts +96 -2
- package/src/platforms/channeltalkbot/credential-manager.ts +37 -4
- package/src/platforms/discord/commands/auth.ts +13 -2
- package/src/platforms/discord/listener.test.ts +59 -1
- package/src/platforms/discord/listener.ts +43 -19
- package/src/platforms/discord/token-extractor.ts +30 -6
- package/src/platforms/discordbot/cli.ts +10 -0
- package/src/platforms/discordbot/client.ts +4 -0
- package/src/platforms/discordbot/commands/auth.ts +1 -5
- package/src/platforms/discordbot/commands/message.ts +1 -6
- package/src/platforms/discordbot/commands/server.ts +1 -5
- package/src/platforms/discordbot/commands/whoami.ts +1 -6
- package/src/platforms/discordbot/index.test.ts +82 -0
- package/src/platforms/discordbot/index.ts +27 -9
- package/src/platforms/discordbot/listener.test.ts +1002 -0
- package/src/platforms/discordbot/listener.ts +321 -0
- package/src/platforms/discordbot/types.ts +163 -0
- package/src/platforms/instagram/commands/auth.ts +9 -1
- package/src/platforms/instagram/token-extractor.ts +13 -1
- package/src/platforms/slack/commands/auth.ts +11 -2
- package/src/platforms/slack/token-extractor.test.ts +96 -0
- package/src/platforms/slack/token-extractor.ts +76 -13
- package/src/platforms/slackbot/cli.ts +13 -1
- package/src/platforms/slackbot/client.test.ts +274 -0
- package/src/platforms/slackbot/client.ts +130 -2
- package/src/platforms/slackbot/commands/auth.ts +1 -5
- package/src/platforms/slackbot/commands/file.test.ts +201 -0
- package/src/platforms/slackbot/commands/file.ts +212 -0
- package/src/platforms/slackbot/commands/index.ts +1 -0
- package/src/platforms/slackbot/commands/message.ts +22 -0
- package/src/platforms/slackbot/commands/whoami.ts +1 -6
- package/src/platforms/slackbot/credential-manager.test.ts +62 -2
- package/src/platforms/slackbot/credential-manager.ts +32 -2
- package/src/platforms/slackbot/index.test.ts +59 -0
- package/src/platforms/slackbot/index.ts +31 -7
- package/src/platforms/slackbot/listener.test.ts +1012 -0
- package/src/platforms/slackbot/listener.ts +362 -0
- package/src/platforms/slackbot/types.ts +224 -1
- package/src/platforms/teams/commands/auth.test.ts +1 -1
- package/src/platforms/teams/commands/auth.ts +66 -7
- package/src/platforms/teams/ensure-auth.test.ts +56 -5
- package/src/platforms/teams/ensure-auth.ts +39 -11
- package/src/platforms/teams/token-extractor.test.ts +146 -24
- package/src/platforms/teams/token-extractor.ts +87 -29
- package/src/platforms/webex/commands/auth.ts +13 -2
- package/src/platforms/webex/token-extractor.ts +25 -3
- package/src/platforms/wechatbot/cli.ts +9 -0
- package/src/platforms/wechatbot/commands/auth.ts +1 -5
- package/src/platforms/wechatbot/commands/message.ts +1 -6
- package/src/platforms/wechatbot/commands/template.ts +1 -6
- package/src/platforms/wechatbot/commands/user.ts +1 -6
- package/src/platforms/wechatbot/commands/whoami.ts +1 -6
- package/src/platforms/whatsappbot/cli.ts +9 -0
- package/src/platforms/whatsappbot/commands/auth.ts +1 -5
- package/src/platforms/whatsappbot/commands/message.ts +1 -6
- package/src/platforms/whatsappbot/commands/template.ts +1 -6
- package/src/platforms/whatsappbot/commands/whoami.ts +1 -6
- package/src/shared/chromium/browsers.test.ts +80 -0
- package/src/shared/chromium/browsers.ts +72 -3
- package/src/shared/chromium/cli-options.test.ts +22 -0
- package/src/shared/chromium/cli-options.ts +12 -0
- package/src/shared/chromium/index.ts +3 -0
- package/src/shared/utils/cli-output.test.ts +57 -0
- package/src/shared/utils/cli-output.ts +8 -0
- package/src/tui/app.ts +129 -20
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
4
|
|
|
5
5
|
import type { WorkspaceOption } from './shared'
|
|
6
6
|
import { getClient } from './shared'
|
|
@@ -89,11 +89,6 @@ export async function deleteAction(botId: string, options: BotOptions): Promise<
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
function cliOutput(result: BotResult, pretty?: boolean): void {
|
|
93
|
-
console.log(formatOutput(result, pretty))
|
|
94
|
-
if (result.error) process.exit(1)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
92
|
export const botCommand = new Command('bot')
|
|
98
93
|
.description('Bot management commands')
|
|
99
94
|
.addCommand(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
4
|
|
|
5
5
|
import type { WorkspaceOption } from './shared'
|
|
6
6
|
import { getClient, getDefaultBotName } from './shared'
|
|
@@ -120,11 +120,6 @@ export async function deleteAction(chatId: string, options: ChatOptions): Promis
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
function cliOutput(result: ChatResult, pretty?: boolean): void {
|
|
124
|
-
console.log(formatOutput(result, pretty))
|
|
125
|
-
if (result.error) process.exit(1)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
123
|
export const chatCommand = new Command('chat')
|
|
129
124
|
.description('UserChat management commands')
|
|
130
125
|
.addCommand(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
4
|
|
|
5
5
|
import type { WorkspaceOption } from './shared'
|
|
6
6
|
import { getClient } from './shared'
|
|
@@ -93,11 +93,6 @@ export async function messagesAction(group: string, options: GroupOptions): Prom
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
function cliOutput(result: GroupResult, pretty?: boolean): void {
|
|
97
|
-
console.log(formatOutput(result, pretty))
|
|
98
|
-
if (result.error) process.exit(1)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
96
|
export const groupCommand = new Command('group')
|
|
102
97
|
.description('Group management commands')
|
|
103
98
|
.addCommand(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
4
|
|
|
5
5
|
import type { WorkspaceOption } from './shared'
|
|
6
6
|
import { getClient } from './shared'
|
|
@@ -68,11 +68,6 @@ export async function getAction(managerId: string, options: ManagerOptions): Pro
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function cliOutput(result: ManagerResult, pretty?: boolean): void {
|
|
72
|
-
console.log(formatOutput(result, pretty))
|
|
73
|
-
if (result.error) process.exit(1)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
71
|
export const managerCommand = new Command('manager')
|
|
77
72
|
.description('Manager commands')
|
|
78
73
|
.addCommand(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
4
|
|
|
5
5
|
import { wrapTextInBlocks } from '../message-utils'
|
|
6
6
|
import type { WorkspaceOption } from './shared'
|
|
@@ -140,11 +140,6 @@ function detectTargetType(target: string): 'userchat' | 'group' {
|
|
|
140
140
|
return 'userchat'
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
function cliOutput(result: MessageResult, pretty?: boolean): void {
|
|
144
|
-
console.log(formatOutput(result, pretty))
|
|
145
|
-
if (result.error) process.exit(1)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
143
|
export const messageCommand = new Command('message')
|
|
149
144
|
.description('Message commands')
|
|
150
145
|
.addCommand(
|
|
@@ -35,6 +35,8 @@ describe('whoami command', () => {
|
|
|
35
35
|
originalEnv = { ...process.env }
|
|
36
36
|
delete process.env.E2E_CHANNELBOT_ACCESS_KEY
|
|
37
37
|
delete process.env.E2E_CHANNELBOT_ACCESS_SECRET
|
|
38
|
+
delete process.env.E2E_CHANNELTALKBOT_ACCESS_KEY
|
|
39
|
+
delete process.env.E2E_CHANNELTALKBOT_ACCESS_SECRET
|
|
38
40
|
mockGetChannel.mockClear()
|
|
39
41
|
})
|
|
40
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
4
|
|
|
5
5
|
import type { WorkspaceOption } from './shared'
|
|
6
6
|
import { getClient } from './shared'
|
|
@@ -28,11 +28,6 @@ export async function whoamiAction(options: WorkspaceOption): Promise<WhoamiResu
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function cliOutput(result: WhoamiResult, pretty?: boolean): void {
|
|
32
|
-
console.log(formatOutput(result, pretty))
|
|
33
|
-
if (result.error) process.exit(1)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
31
|
export const whoamiCommand = new Command('whoami')
|
|
37
32
|
.description('Show current authenticated bot')
|
|
38
33
|
.option('--workspace <id>', 'Workspace ID to use')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
2
2
|
import { existsSync, rmSync } from 'node:fs'
|
|
3
|
-
import { mkdir, stat } from 'node:fs/promises'
|
|
3
|
+
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'
|
|
4
4
|
import { tmpdir } from 'node:os'
|
|
5
5
|
import { join } from 'node:path'
|
|
6
6
|
|
|
@@ -36,6 +36,8 @@ describe('ChannelBotCredentialManager', () => {
|
|
|
36
36
|
}
|
|
37
37
|
delete process.env.E2E_CHANNELBOT_ACCESS_KEY
|
|
38
38
|
delete process.env.E2E_CHANNELBOT_ACCESS_SECRET
|
|
39
|
+
delete process.env.E2E_CHANNELTALKBOT_ACCESS_KEY
|
|
40
|
+
delete process.env.E2E_CHANNELTALKBOT_ACCESS_SECRET
|
|
39
41
|
})
|
|
40
42
|
|
|
41
43
|
describe('load', () => {
|
|
@@ -299,10 +301,102 @@ describe('ChannelBotCredentialManager', () => {
|
|
|
299
301
|
it('saves file with secure permissions (600)', async () => {
|
|
300
302
|
await manager.setCredentials(WORKSPACE_A)
|
|
301
303
|
|
|
302
|
-
const credPath = join(tempDir, '
|
|
304
|
+
const credPath = join(tempDir, 'channeltalkbot-credentials.json')
|
|
303
305
|
const stats = await stat(credPath)
|
|
304
306
|
|
|
305
307
|
expect(stats.mode & 0o777).toBe(0o600)
|
|
306
308
|
})
|
|
307
309
|
})
|
|
310
|
+
|
|
311
|
+
describe('legacy filename migration', () => {
|
|
312
|
+
it('renames channelbot-credentials.json to channeltalkbot-credentials.json on load', async () => {
|
|
313
|
+
const legacyPath = join(tempDir, 'channelbot-credentials.json')
|
|
314
|
+
const newPath = join(tempDir, 'channeltalkbot-credentials.json')
|
|
315
|
+
const legacyConfig = {
|
|
316
|
+
current: { workspace_id: WORKSPACE_A.workspace_id },
|
|
317
|
+
workspaces: { [WORKSPACE_A.workspace_id]: WORKSPACE_A },
|
|
318
|
+
default_bot: null,
|
|
319
|
+
}
|
|
320
|
+
await writeFile(legacyPath, JSON.stringify(legacyConfig))
|
|
321
|
+
|
|
322
|
+
const config = await manager.load()
|
|
323
|
+
|
|
324
|
+
expect(config.workspaces[WORKSPACE_A.workspace_id]).toEqual(WORKSPACE_A)
|
|
325
|
+
expect(existsSync(legacyPath)).toBe(false)
|
|
326
|
+
expect(existsSync(newPath)).toBe(true)
|
|
327
|
+
const migrated = JSON.parse(await readFile(newPath, 'utf-8'))
|
|
328
|
+
expect(migrated.workspaces[WORKSPACE_A.workspace_id]).toEqual(WORKSPACE_A)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it('does not overwrite an existing channeltalkbot-credentials.json', async () => {
|
|
332
|
+
const legacyPath = join(tempDir, 'channelbot-credentials.json')
|
|
333
|
+
const newPath = join(tempDir, 'channeltalkbot-credentials.json')
|
|
334
|
+
await writeFile(legacyPath, JSON.stringify({ workspaces: { stale: WORKSPACE_A } }))
|
|
335
|
+
const newConfig = {
|
|
336
|
+
current: { workspace_id: WORKSPACE_B.workspace_id },
|
|
337
|
+
workspaces: { [WORKSPACE_B.workspace_id]: WORKSPACE_B },
|
|
338
|
+
default_bot: null,
|
|
339
|
+
}
|
|
340
|
+
await writeFile(newPath, JSON.stringify(newConfig))
|
|
341
|
+
|
|
342
|
+
const config = await manager.load()
|
|
343
|
+
|
|
344
|
+
expect(config.workspaces[WORKSPACE_B.workspace_id]).toEqual(WORKSPACE_B)
|
|
345
|
+
expect(config.workspaces['stale']).toBeUndefined()
|
|
346
|
+
expect(existsSync(legacyPath)).toBe(true)
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('does not write back to legacy path when migration fails (no split-brain)', async () => {
|
|
350
|
+
const legacyPath = join(tempDir, 'channelbot-credentials.json')
|
|
351
|
+
const newPath = join(tempDir, 'channeltalkbot-credentials.json')
|
|
352
|
+
await writeFile(legacyPath, JSON.stringify({ workspaces: {}, default_bot: null }))
|
|
353
|
+
|
|
354
|
+
// Inject a rename that fails on first call to simulate a concurrent migration losing the race.
|
|
355
|
+
class FailingMigrationManager extends ChannelBotCredentialManager {
|
|
356
|
+
constructor(dir: string) {
|
|
357
|
+
super(dir)
|
|
358
|
+
let called = false
|
|
359
|
+
this.renameFile = async () => {
|
|
360
|
+
if (!called) {
|
|
361
|
+
called = true
|
|
362
|
+
throw new Error('ENOENT: simulated concurrent rename winner')
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const failingManager = new FailingMigrationManager(tempDir)
|
|
368
|
+
|
|
369
|
+
await failingManager.load()
|
|
370
|
+
await failingManager.setCredentials(WORKSPACE_A)
|
|
371
|
+
|
|
372
|
+
// After failure, the manager must write to the NEW path, never re-create the legacy file.
|
|
373
|
+
expect(existsSync(newPath)).toBe(true)
|
|
374
|
+
const persisted = JSON.parse(await readFile(newPath, 'utf-8'))
|
|
375
|
+
expect(persisted.workspaces[WORKSPACE_A.workspace_id]).toEqual(WORKSPACE_A)
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
describe('env var prefix compatibility', () => {
|
|
380
|
+
it('prefers E2E_CHANNELTALKBOT_* over E2E_CHANNELBOT_*', async () => {
|
|
381
|
+
process.env.E2E_CHANNELBOT_ACCESS_KEY = 'old-key'
|
|
382
|
+
process.env.E2E_CHANNELBOT_ACCESS_SECRET = 'old-secret'
|
|
383
|
+
process.env.E2E_CHANNELTALKBOT_ACCESS_KEY = 'new-key'
|
|
384
|
+
process.env.E2E_CHANNELTALKBOT_ACCESS_SECRET = 'new-secret'
|
|
385
|
+
|
|
386
|
+
const creds = await manager.getCredentials()
|
|
387
|
+
|
|
388
|
+
expect(creds?.access_key).toBe('new-key')
|
|
389
|
+
expect(creds?.access_secret).toBe('new-secret')
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
it('falls back to E2E_CHANNELBOT_* when E2E_CHANNELTALKBOT_* is unset', async () => {
|
|
393
|
+
process.env.E2E_CHANNELBOT_ACCESS_KEY = 'legacy-key'
|
|
394
|
+
process.env.E2E_CHANNELBOT_ACCESS_SECRET = 'legacy-secret'
|
|
395
|
+
|
|
396
|
+
const creds = await manager.getCredentials()
|
|
397
|
+
|
|
398
|
+
expect(creds?.access_key).toBe('legacy-key')
|
|
399
|
+
expect(creds?.access_secret).toBe('legacy-secret')
|
|
400
|
+
})
|
|
401
|
+
})
|
|
308
402
|
})
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
|
-
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
3
3
|
import { homedir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
6
6
|
import type { ChannelBotConfig, ChannelBotCredentials, ChannelBotWorkspaceEntry } from './types'
|
|
7
7
|
import { ChannelBotConfigSchema } from './types'
|
|
8
8
|
|
|
9
|
+
const LEGACY_FILENAME = 'channelbot-credentials.json'
|
|
10
|
+
const CREDENTIALS_FILENAME = 'channeltalkbot-credentials.json'
|
|
11
|
+
|
|
9
12
|
export class ChannelBotCredentialManager {
|
|
10
13
|
private configDir: string
|
|
11
14
|
private credentialsPath: string
|
|
15
|
+
private legacyPath: string
|
|
16
|
+
private migratedLegacyFile = false
|
|
17
|
+
protected renameFile: typeof rename = rename
|
|
12
18
|
|
|
13
19
|
constructor(configDir?: string) {
|
|
14
20
|
this.configDir = configDir ?? join(homedir(), '.config', 'agent-messenger')
|
|
15
|
-
this.credentialsPath = join(this.configDir,
|
|
21
|
+
this.credentialsPath = join(this.configDir, CREDENTIALS_FILENAME)
|
|
22
|
+
this.legacyPath = join(this.configDir, LEGACY_FILENAME)
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
async load(): Promise<ChannelBotConfig> {
|
|
26
|
+
await this.migrateLegacyFileIfNeeded()
|
|
27
|
+
|
|
19
28
|
if (!existsSync(this.credentialsPath)) {
|
|
20
29
|
return { current: null, workspaces: {}, default_bot: null }
|
|
21
30
|
}
|
|
@@ -34,6 +43,30 @@ export class ChannelBotCredentialManager {
|
|
|
34
43
|
return parsed.data
|
|
35
44
|
}
|
|
36
45
|
|
|
46
|
+
private async migrateLegacyFileIfNeeded(): Promise<void> {
|
|
47
|
+
if (this.migratedLegacyFile) return
|
|
48
|
+
if (existsSync(this.credentialsPath)) {
|
|
49
|
+
this.migratedLegacyFile = true
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
if (!existsSync(this.legacyPath)) {
|
|
53
|
+
this.migratedLegacyFile = true
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
await this.renameFile(this.legacyPath, this.credentialsPath)
|
|
58
|
+
process.stderr.write(
|
|
59
|
+
`[agent-channeltalkbot] Migrated credentials: ${LEGACY_FILENAME} -> ${CREDENTIALS_FILENAME}\n`,
|
|
60
|
+
)
|
|
61
|
+
} catch {
|
|
62
|
+
// Rename failed. If a concurrent process succeeded, the new file now exists — use it.
|
|
63
|
+
// Otherwise (real failure: permissions, etc.) keep the new path; load() will return
|
|
64
|
+
// empty config and the user can re-run `auth set`. Never fall back to writing the
|
|
65
|
+
// legacy path, which would resurrect the split-brain we are migrating away from.
|
|
66
|
+
}
|
|
67
|
+
this.migratedLegacyFile = true
|
|
68
|
+
}
|
|
69
|
+
|
|
37
70
|
async save(config: ChannelBotConfig): Promise<void> {
|
|
38
71
|
await mkdir(this.configDir, { recursive: true })
|
|
39
72
|
await writeFile(this.credentialsPath, JSON.stringify(config, null, 2), { mode: 0o600 })
|
|
@@ -41,8 +74,8 @@ export class ChannelBotCredentialManager {
|
|
|
41
74
|
}
|
|
42
75
|
|
|
43
76
|
async getCredentials(workspaceId?: string): Promise<ChannelBotCredentials | null> {
|
|
44
|
-
const envAccessKey = process.env.E2E_CHANNELBOT_ACCESS_KEY
|
|
45
|
-
const envAccessSecret = process.env.E2E_CHANNELBOT_ACCESS_SECRET
|
|
77
|
+
const envAccessKey = process.env.E2E_CHANNELTALKBOT_ACCESS_KEY ?? process.env.E2E_CHANNELBOT_ACCESS_KEY
|
|
78
|
+
const envAccessSecret = process.env.E2E_CHANNELTALKBOT_ACCESS_SECRET ?? process.env.E2E_CHANNELBOT_ACCESS_SECRET
|
|
46
79
|
|
|
47
80
|
if (envAccessKey && envAccessSecret && !workspaceId) {
|
|
48
81
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
|
|
3
|
+
import { collectBrowserProfileOption } from '@/shared/chromium'
|
|
3
4
|
import { handleError } from '@/shared/utils/error-handler'
|
|
4
5
|
import { formatOutput } from '@/shared/utils/output'
|
|
5
6
|
import { debug } from '@/shared/utils/stderr'
|
|
@@ -8,9 +9,13 @@ import { DiscordClient } from '../client'
|
|
|
8
9
|
import { DiscordCredentialManager } from '../credential-manager'
|
|
9
10
|
import { DiscordTokenExtractor } from '../token-extractor'
|
|
10
11
|
|
|
11
|
-
export async function extractAction(options: {
|
|
12
|
+
export async function extractAction(options: {
|
|
13
|
+
pretty?: boolean
|
|
14
|
+
debug?: boolean
|
|
15
|
+
browserProfile?: string[]
|
|
16
|
+
}): Promise<void> {
|
|
12
17
|
try {
|
|
13
|
-
const extractor = new DiscordTokenExtractor()
|
|
18
|
+
const extractor = new DiscordTokenExtractor(undefined, undefined, undefined, undefined, options.browserProfile)
|
|
14
19
|
|
|
15
20
|
if (process.platform === 'darwin') {
|
|
16
21
|
console.log('')
|
|
@@ -195,6 +200,12 @@ export const authCommand = new Command('auth')
|
|
|
195
200
|
.description('Extract token from Discord desktop app or a supported Chromium browser')
|
|
196
201
|
.option('--pretty', 'Pretty print JSON output')
|
|
197
202
|
.option('--debug', 'Show debug output for troubleshooting')
|
|
203
|
+
.option(
|
|
204
|
+
'--browser-profile <path>',
|
|
205
|
+
'Additional Chromium profile/user-data directory to scan (repeatable, comma-separated supported)',
|
|
206
|
+
collectBrowserProfileOption,
|
|
207
|
+
[],
|
|
208
|
+
)
|
|
198
209
|
.action(extractAction),
|
|
199
210
|
)
|
|
200
211
|
.addCommand(
|
|
@@ -16,12 +16,16 @@ let mockWsInstance: MockWs
|
|
|
16
16
|
class MockWs {
|
|
17
17
|
static OPEN = 1
|
|
18
18
|
static CLOSED = 3
|
|
19
|
+
static lastUrl: string | null = null
|
|
19
20
|
readyState = MockWs.OPEN
|
|
20
21
|
|
|
21
22
|
private handlers = new Map<string, WsHandler[]>()
|
|
22
23
|
sent: string[] = []
|
|
24
|
+
url: string
|
|
23
25
|
|
|
24
|
-
constructor(
|
|
26
|
+
constructor(url: string, _options?: any) {
|
|
27
|
+
this.url = url
|
|
28
|
+
MockWs.lastUrl = url
|
|
25
29
|
// oxlint-disable-next-line typescript-eslint/no-this-alias
|
|
26
30
|
mockWsInstance = this
|
|
27
31
|
}
|
|
@@ -785,4 +789,58 @@ describe('DiscordListener', () => {
|
|
|
785
789
|
expect((listener as any).reconnectAttempts).toBe(0)
|
|
786
790
|
})
|
|
787
791
|
})
|
|
792
|
+
|
|
793
|
+
describe('reconnect URL', () => {
|
|
794
|
+
it('appends ?v=10&encoding=json to resume_gateway_url on reconnect', async () => {
|
|
795
|
+
const client = createMockClient()
|
|
796
|
+
listener = new DiscordListener(client)
|
|
797
|
+
|
|
798
|
+
await listener.start()
|
|
799
|
+
mockWsInstance.simulateOpen()
|
|
800
|
+
mockWsInstance.simulateHello()
|
|
801
|
+
mockWsInstance.simulateMessage({
|
|
802
|
+
op: 0,
|
|
803
|
+
t: 'READY',
|
|
804
|
+
s: 1,
|
|
805
|
+
d: {
|
|
806
|
+
session_id: 'session_xyz',
|
|
807
|
+
resume_gateway_url: 'wss://gateway-us-east1-b.discord.gg',
|
|
808
|
+
user: { id: 'U_SELF', username: 'user' },
|
|
809
|
+
},
|
|
810
|
+
})
|
|
811
|
+
|
|
812
|
+
mockWsInstance.simulateClose()
|
|
813
|
+
await new Promise((r) => setTimeout(r, 1500))
|
|
814
|
+
|
|
815
|
+
expect(MockWs.lastUrl).toBe('wss://gateway-us-east1-b.discord.gg?v=10&encoding=json')
|
|
816
|
+
})
|
|
817
|
+
})
|
|
818
|
+
|
|
819
|
+
describe('reconnectAttempts deferred to READY/RESUMED', () => {
|
|
820
|
+
it('does not reset reconnectAttempts on socket open alone', async () => {
|
|
821
|
+
const client = createMockClient()
|
|
822
|
+
listener = new DiscordListener(client)
|
|
823
|
+
|
|
824
|
+
await listener.start()
|
|
825
|
+
;(listener as any).reconnectAttempts = 5
|
|
826
|
+
|
|
827
|
+
mockWsInstance.simulateOpen()
|
|
828
|
+
|
|
829
|
+
expect((listener as any).reconnectAttempts).toBe(5)
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
it('resets reconnectAttempts on READY dispatch', async () => {
|
|
833
|
+
const client = createMockClient()
|
|
834
|
+
listener = new DiscordListener(client)
|
|
835
|
+
|
|
836
|
+
await listener.start()
|
|
837
|
+
;(listener as any).reconnectAttempts = 5
|
|
838
|
+
|
|
839
|
+
mockWsInstance.simulateOpen()
|
|
840
|
+
mockWsInstance.simulateHello()
|
|
841
|
+
mockWsInstance.simulateReady()
|
|
842
|
+
|
|
843
|
+
expect((listener as any).reconnectAttempts).toBe(0)
|
|
844
|
+
})
|
|
845
|
+
})
|
|
788
846
|
})
|
|
@@ -7,6 +7,7 @@ import type { DiscordListenerEventMap, DiscordGatewayGenericEvent } from './type
|
|
|
7
7
|
import { DiscordGatewayOpcode, DiscordIntent } from './types'
|
|
8
8
|
|
|
9
9
|
const GATEWAY_URL = 'wss://gateway.discord.gg/?v=10&encoding=json'
|
|
10
|
+
const GATEWAY_QUERY = '?v=10&encoding=json'
|
|
10
11
|
const RECONNECT_BASE_DELAY = 1_000
|
|
11
12
|
const RECONNECT_MAX_DELAY = 30_000
|
|
12
13
|
const NON_RECOVERABLE_CLOSE_CODES = [4004, 4010, 4011, 4012, 4013, 4014]
|
|
@@ -40,6 +41,7 @@ export class DiscordListener {
|
|
|
40
41
|
private resumeGatewayUrl: string | null = null
|
|
41
42
|
private token: string | null = null
|
|
42
43
|
private cachedUser: { id: string; username: string } | null = null
|
|
44
|
+
private generation = 0
|
|
43
45
|
|
|
44
46
|
constructor(client: DiscordClient, options?: { intents?: number }) {
|
|
45
47
|
this.client = client
|
|
@@ -50,11 +52,13 @@ export class DiscordListener {
|
|
|
50
52
|
if (this.running) return
|
|
51
53
|
this.running = true
|
|
52
54
|
this.reconnectAttempts = 0
|
|
53
|
-
|
|
55
|
+
this.generation++
|
|
56
|
+
await this.connect(this.generation)
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
stop(): void {
|
|
57
60
|
this.running = false
|
|
61
|
+
this.generation++
|
|
58
62
|
this.clearTimers()
|
|
59
63
|
if (this.ws) {
|
|
60
64
|
this.ws.close()
|
|
@@ -82,39 +86,46 @@ export class DiscordListener {
|
|
|
82
86
|
return this
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
private
|
|
86
|
-
if (!this.running) return
|
|
89
|
+
private isCurrent(generation: number, ws?: WebSocket): boolean {
|
|
90
|
+
if (generation !== this.generation || !this.running) return false
|
|
91
|
+
if (ws !== undefined && this.ws !== ws) return false
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private async connect(generation: number): Promise<void> {
|
|
96
|
+
if (!this.isCurrent(generation)) return
|
|
87
97
|
|
|
88
98
|
try {
|
|
89
99
|
const { token } = await this.client.gatewayConnect()
|
|
90
|
-
if (!this.
|
|
100
|
+
if (!this.isCurrent(generation)) return
|
|
91
101
|
|
|
92
102
|
this.token = token
|
|
93
103
|
|
|
94
|
-
const url = this.resumeGatewayUrl
|
|
104
|
+
const url = this.resumeGatewayUrl ? `${this.resumeGatewayUrl}${GATEWAY_QUERY}` : GATEWAY_URL
|
|
95
105
|
const ws = new WebSocket(url)
|
|
96
106
|
this.ws = ws
|
|
97
107
|
|
|
98
108
|
ws.on('open', () => {
|
|
99
|
-
if (!this.
|
|
109
|
+
if (!this.isCurrent(generation, ws)) {
|
|
100
110
|
ws.close()
|
|
101
111
|
return
|
|
102
112
|
}
|
|
103
|
-
this.reconnectAttempts = 0
|
|
104
113
|
})
|
|
105
114
|
|
|
106
115
|
ws.on('message', (raw) => {
|
|
116
|
+
if (!this.isCurrent(generation, ws)) return
|
|
107
117
|
try {
|
|
108
118
|
const data = JSON.parse(raw.toString())
|
|
109
|
-
this.handleMessage(data)
|
|
119
|
+
this.handleMessage(data, generation, ws)
|
|
110
120
|
} catch {
|
|
111
|
-
// malformed
|
|
121
|
+
// malformed gateway frame; ignore and let heartbeat handle liveness
|
|
112
122
|
}
|
|
113
123
|
})
|
|
114
124
|
|
|
115
125
|
ws.on('close', (code) => {
|
|
126
|
+
if (!this.isCurrent(generation, ws)) return
|
|
116
127
|
this.clearTimers()
|
|
117
|
-
|
|
128
|
+
this.ws = null
|
|
118
129
|
if (NON_RECOVERABLE_CLOSE_CODES.includes(code)) {
|
|
119
130
|
this.emitter.emit('error', new Error(`Discord gateway closed with non-recoverable code ${code}`))
|
|
120
131
|
this.running = false
|
|
@@ -132,9 +143,11 @@ export class DiscordListener {
|
|
|
132
143
|
})
|
|
133
144
|
|
|
134
145
|
ws.on('error', (err) => {
|
|
146
|
+
if (!this.isCurrent(generation, ws)) return
|
|
135
147
|
this.emitter.emit('error', err instanceof Error ? err : new Error(String(err)))
|
|
136
148
|
})
|
|
137
149
|
} catch (error) {
|
|
150
|
+
if (!this.isCurrent(generation)) return
|
|
138
151
|
this.emitter.emit('error', error instanceof Error ? error : new Error(String(error)))
|
|
139
152
|
if (this.running) {
|
|
140
153
|
this.scheduleReconnect()
|
|
@@ -142,12 +155,13 @@ export class DiscordListener {
|
|
|
142
155
|
}
|
|
143
156
|
}
|
|
144
157
|
|
|
145
|
-
private handleMessage(data: { op: number; d: any; s?: number; t?: string }): void {
|
|
158
|
+
private handleMessage(data: { op: number; d: any; s?: number; t?: string }, generation: number, ws: WebSocket): void {
|
|
159
|
+
if (!this.isCurrent(generation, ws)) return
|
|
146
160
|
const { op, d, s, t } = data
|
|
147
161
|
|
|
148
162
|
switch (op) {
|
|
149
163
|
case DiscordGatewayOpcode.Hello:
|
|
150
|
-
this.startHeartbeat(d.heartbeat_interval)
|
|
164
|
+
this.startHeartbeat(d.heartbeat_interval, generation, ws)
|
|
151
165
|
if (this.sessionId) {
|
|
152
166
|
this.sendResume()
|
|
153
167
|
} else {
|
|
@@ -166,22 +180,21 @@ export class DiscordListener {
|
|
|
166
180
|
|
|
167
181
|
case DiscordGatewayOpcode.Reconnect:
|
|
168
182
|
this.reconnectAttempts = 0
|
|
169
|
-
|
|
183
|
+
ws.close()
|
|
170
184
|
break
|
|
171
185
|
|
|
172
186
|
case DiscordGatewayOpcode.InvalidSession: {
|
|
173
|
-
const currentWs = this.ws
|
|
174
187
|
if (d === true) {
|
|
175
188
|
const delay = 1000 + Math.random() * 4000
|
|
176
189
|
this.invalidSessionTimer = setTimeout(() => {
|
|
177
190
|
this.invalidSessionTimer = null
|
|
178
|
-
if (
|
|
191
|
+
if (this.isCurrent(generation, ws)) ws.close()
|
|
179
192
|
}, delay)
|
|
180
193
|
} else {
|
|
181
194
|
this.sequence = null
|
|
182
195
|
this.sessionId = null
|
|
183
196
|
this.resumeGatewayUrl = null
|
|
184
|
-
|
|
197
|
+
ws.close()
|
|
185
198
|
}
|
|
186
199
|
break
|
|
187
200
|
}
|
|
@@ -197,11 +210,13 @@ export class DiscordListener {
|
|
|
197
210
|
this.sessionId = d.session_id
|
|
198
211
|
this.resumeGatewayUrl = d.resume_gateway_url
|
|
199
212
|
this.cachedUser = d.user
|
|
213
|
+
this.reconnectAttempts = 0
|
|
200
214
|
this.emitter.emit('connected', { user: d.user, sessionId: d.session_id })
|
|
201
215
|
return
|
|
202
216
|
}
|
|
203
217
|
|
|
204
218
|
if (t === 'RESUMED') {
|
|
219
|
+
this.reconnectAttempts = 0
|
|
205
220
|
this.emitter.emit('connected', { user: this.cachedUser!, sessionId: this.sessionId! })
|
|
206
221
|
return
|
|
207
222
|
}
|
|
@@ -246,18 +261,23 @@ export class DiscordListener {
|
|
|
246
261
|
this.ws?.send(JSON.stringify({ op: DiscordGatewayOpcode.Heartbeat, d: this.sequence }))
|
|
247
262
|
}
|
|
248
263
|
|
|
249
|
-
private startHeartbeat(interval: number): void {
|
|
264
|
+
private startHeartbeat(interval: number, generation: number, ws: WebSocket): void {
|
|
250
265
|
this.clearHeartbeatTimers()
|
|
251
266
|
this.heartbeatAckReceived = true
|
|
252
267
|
|
|
253
268
|
this.heartbeatJitterTimer = setTimeout(() => {
|
|
254
269
|
this.heartbeatJitterTimer = null
|
|
270
|
+
if (!this.isCurrent(generation, ws)) return
|
|
255
271
|
this.heartbeatAckReceived = false
|
|
256
272
|
this.sendHeartbeat()
|
|
257
273
|
|
|
258
274
|
this.heartbeatTimer = setInterval(() => {
|
|
275
|
+
if (!this.isCurrent(generation, ws)) {
|
|
276
|
+
this.clearHeartbeatTimers()
|
|
277
|
+
return
|
|
278
|
+
}
|
|
259
279
|
if (!this.heartbeatAckReceived) {
|
|
260
|
-
|
|
280
|
+
ws.close()
|
|
261
281
|
return
|
|
262
282
|
}
|
|
263
283
|
this.heartbeatAckReceived = false
|
|
@@ -269,7 +289,11 @@ export class DiscordListener {
|
|
|
269
289
|
private scheduleReconnect(): void {
|
|
270
290
|
const delay = Math.min(RECONNECT_BASE_DELAY * 2 ** this.reconnectAttempts, RECONNECT_MAX_DELAY)
|
|
271
291
|
this.reconnectAttempts++
|
|
272
|
-
|
|
292
|
+
const generation = this.generation
|
|
293
|
+
this.reconnectTimer = setTimeout(() => {
|
|
294
|
+
this.reconnectTimer = null
|
|
295
|
+
this.connect(generation)
|
|
296
|
+
}, delay)
|
|
273
297
|
}
|
|
274
298
|
|
|
275
299
|
private clearHeartbeatTimers(): void {
|