agent-messenger 2.3.0 → 2.5.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/README.md +16 -16
- package/.claude-plugin/marketplace.json +29 -29
- package/.claude-plugin/plugin.json +5 -5
- package/.github/workflows/release.yml +0 -12
- package/CONTRIBUTING.md +1 -1
- package/README.md +11 -8
- package/bun.lock +70 -110
- package/bunfig.toml +3 -0
- package/dist/package.json +11 -3
- package/dist/src/platforms/channeltalk/cli.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/cli.js +2 -1
- package/dist/src/platforms/channeltalk/cli.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/index.d.ts +1 -0
- package/dist/src/platforms/channeltalk/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/commands/index.js +1 -0
- package/dist/src/platforms/channeltalk/commands/index.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/whoami.d.ts +22 -0
- package/dist/src/platforms/channeltalk/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/channeltalk/commands/whoami.js +40 -0
- package/dist/src/platforms/channeltalk/commands/whoami.js.map +1 -0
- package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/cli.js +2 -1
- package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/index.d.ts +1 -0
- package/dist/src/platforms/channeltalkbot/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/index.js +1 -0
- package/dist/src/platforms/channeltalkbot/commands/index.js.map +1 -1
- package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts +13 -0
- package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/channeltalkbot/commands/whoami.js +31 -0
- package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -0
- package/dist/src/platforms/discord/cli.d.ts.map +1 -1
- package/dist/src/platforms/discord/cli.js +2 -1
- package/dist/src/platforms/discord/cli.js.map +1 -1
- package/dist/src/platforms/discord/commands/index.d.ts +1 -0
- package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/index.js +1 -0
- package/dist/src/platforms/discord/commands/index.js.map +1 -1
- package/dist/src/platforms/discord/commands/whoami.d.ts +6 -0
- package/dist/src/platforms/discord/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/whoami.js +33 -0
- package/dist/src/platforms/discord/commands/whoami.js.map +1 -0
- package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/cli.js +2 -1
- package/dist/src/platforms/discordbot/cli.js.map +1 -1
- package/dist/src/platforms/discordbot/client.js +2 -2
- package/dist/src/platforms/discordbot/client.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/index.d.ts +1 -0
- package/dist/src/platforms/discordbot/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/index.js +1 -0
- package/dist/src/platforms/discordbot/commands/index.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/whoami.d.ts +14 -0
- package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/discordbot/commands/whoami.js +32 -0
- package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -0
- package/dist/src/platforms/instagram/cli.d.ts.map +1 -1
- package/dist/src/platforms/instagram/cli.js +2 -1
- package/dist/src/platforms/instagram/cli.js.map +1 -1
- package/dist/src/platforms/instagram/client.d.ts +6 -0
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +12 -0
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/index.d.ts +1 -0
- package/dist/src/platforms/instagram/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/index.js +1 -0
- package/dist/src/platforms/instagram/commands/index.js.map +1 -1
- package/dist/src/platforms/instagram/commands/whoami.d.ts +7 -0
- package/dist/src/platforms/instagram/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/instagram/commands/whoami.js +19 -0
- package/dist/src/platforms/instagram/commands/whoami.js.map +1 -0
- package/dist/src/platforms/kakaotalk/cli.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/cli.js +2 -1
- package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts +2 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +225 -23
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -0
- package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/index.js +1 -0
- package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/whoami.d.ts +3 -0
- package/dist/src/platforms/kakaotalk/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/commands/whoami.js +19 -0
- package/dist/src/platforms/kakaotalk/commands/whoami.js.map +1 -0
- 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/protocol/session.d.ts +4 -2
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.js +27 -7
- package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/types.d.ts +17 -0
- package/dist/src/platforms/kakaotalk/protocol/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/types.js.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +28 -0
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +14 -0
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/dist/src/platforms/line/cli.js +2 -2
- package/dist/src/platforms/line/cli.js.map +1 -1
- package/dist/src/platforms/line/client.d.ts.map +1 -1
- package/dist/src/platforms/line/client.js +9 -36
- package/dist/src/platforms/line/client.js.map +1 -1
- package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/auth.js +9 -47
- package/dist/src/platforms/line/commands/auth.js.map +1 -1
- package/dist/src/platforms/line/commands/index.d.ts +1 -1
- package/dist/src/platforms/line/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/index.js +1 -1
- package/dist/src/platforms/line/commands/index.js.map +1 -1
- package/dist/src/platforms/line/commands/whoami.d.ts +3 -0
- package/dist/src/platforms/line/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/line/commands/{profile.js → whoami.js} +5 -5
- package/dist/src/platforms/line/commands/whoami.js.map +1 -0
- package/dist/src/platforms/slack/cli.d.ts.map +1 -1
- package/dist/src/platforms/slack/cli.js +2 -1
- package/dist/src/platforms/slack/cli.js.map +1 -1
- package/dist/src/platforms/slack/commands/index.d.ts +1 -0
- package/dist/src/platforms/slack/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/index.js +1 -0
- package/dist/src/platforms/slack/commands/index.js.map +1 -1
- package/dist/src/platforms/slack/commands/whoami.d.ts +6 -0
- package/dist/src/platforms/slack/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/whoami.js +39 -0
- package/dist/src/platforms/slack/commands/whoami.js.map +1 -0
- package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/cli.js +2 -1
- package/dist/src/platforms/slackbot/cli.js.map +1 -1
- 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/whoami.d.ts +14 -0
- package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/slackbot/commands/whoami.js +32 -0
- package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -0
- package/dist/src/platforms/teams/cli.d.ts.map +1 -1
- package/dist/src/platforms/teams/cli.js +2 -1
- package/dist/src/platforms/teams/cli.js.map +1 -1
- package/dist/src/platforms/teams/commands/index.d.ts +1 -0
- package/dist/src/platforms/teams/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/index.js +1 -0
- package/dist/src/platforms/teams/commands/index.js.map +1 -1
- package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/reaction.js +2 -0
- package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
- package/dist/src/platforms/teams/commands/whoami.d.ts +6 -0
- package/dist/src/platforms/teams/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/teams/commands/whoami.js +30 -0
- package/dist/src/platforms/teams/commands/whoami.js.map +1 -0
- package/dist/src/platforms/telegram/cli.d.ts.map +1 -1
- package/dist/src/platforms/telegram/cli.js +2 -1
- package/dist/src/platforms/telegram/cli.js.map +1 -1
- package/dist/src/platforms/telegram/commands/index.d.ts +1 -0
- package/dist/src/platforms/telegram/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/telegram/commands/index.js +1 -0
- package/dist/src/platforms/telegram/commands/index.js.map +1 -1
- package/dist/src/platforms/telegram/commands/whoami.d.ts +7 -0
- package/dist/src/platforms/telegram/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/telegram/commands/whoami.js +27 -0
- package/dist/src/platforms/telegram/commands/whoami.js.map +1 -0
- package/dist/src/platforms/webex/cli.d.ts.map +1 -1
- package/dist/src/platforms/webex/cli.js +2 -1
- package/dist/src/platforms/webex/cli.js.map +1 -1
- package/dist/src/platforms/webex/commands/index.d.ts +1 -0
- package/dist/src/platforms/webex/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/index.js +1 -0
- package/dist/src/platforms/webex/commands/index.js.map +1 -1
- package/dist/src/platforms/webex/commands/message.js +1 -1
- package/dist/src/platforms/webex/commands/message.js.map +1 -1
- package/dist/src/platforms/webex/commands/whoami.d.ts +6 -0
- package/dist/src/platforms/webex/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/whoami.js +30 -0
- package/dist/src/platforms/webex/commands/whoami.js.map +1 -0
- package/dist/src/platforms/wechatbot/cli.d.ts +5 -0
- package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/cli.js +19 -0
- package/dist/src/platforms/wechatbot/cli.js.map +1 -0
- package/dist/src/platforms/wechatbot/client.d.ts +36 -0
- package/dist/src/platforms/wechatbot/client.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/client.js +208 -0
- package/dist/src/platforms/wechatbot/client.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/auth.d.ts +28 -0
- package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/auth.js +164 -0
- package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/index.d.ts +6 -0
- package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/index.js +6 -0
- package/dist/src/platforms/wechatbot/commands/index.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/message.d.ts +18 -0
- package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/message.js +80 -0
- package/dist/src/platforms/wechatbot/commands/message.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/shared.d.ts +9 -0
- package/dist/src/platforms/wechatbot/commands/shared.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/shared.js +13 -0
- package/dist/src/platforms/wechatbot/commands/shared.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/template.d.ts +19 -0
- package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/template.js +76 -0
- package/dist/src/platforms/wechatbot/commands/template.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/user.d.ts +20 -0
- package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/user.js +53 -0
- package/dist/src/platforms/wechatbot/commands/user.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/whoami.d.ts +12 -0
- package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/whoami.js +33 -0
- package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -0
- package/dist/src/platforms/wechatbot/credential-manager.d.ts +17 -0
- package/dist/src/platforms/wechatbot/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/credential-manager.js +121 -0
- package/dist/src/platforms/wechatbot/credential-manager.js.map +1 -0
- package/dist/src/platforms/wechatbot/index.d.ts +5 -0
- package/dist/src/platforms/wechatbot/index.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/index.js +4 -0
- package/dist/src/platforms/wechatbot/index.js.map +1 -0
- package/dist/src/platforms/wechatbot/types.d.ts +94 -0
- package/dist/src/platforms/wechatbot/types.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/types.js +54 -0
- package/dist/src/platforms/wechatbot/types.js.map +1 -0
- package/dist/src/platforms/whatsapp/cli.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/cli.js +2 -1
- package/dist/src/platforms/whatsapp/cli.js.map +1 -1
- package/dist/src/platforms/whatsapp/client.d.ts +9 -0
- package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.js +143 -21
- package/dist/src/platforms/whatsapp/client.js.map +1 -1
- package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/commands/auth.js +133 -60
- package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
- package/dist/src/platforms/whatsapp/commands/index.d.ts +1 -0
- package/dist/src/platforms/whatsapp/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/commands/index.js +1 -0
- package/dist/src/platforms/whatsapp/commands/index.js.map +1 -1
- package/dist/src/platforms/whatsapp/commands/shared.js +2 -2
- package/dist/src/platforms/whatsapp/commands/shared.js.map +1 -1
- package/dist/src/platforms/whatsapp/commands/whoami.d.ts +7 -0
- package/dist/src/platforms/whatsapp/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/whatsapp/commands/whoami.js +19 -0
- package/dist/src/platforms/whatsapp/commands/whoami.js.map +1 -0
- package/dist/src/platforms/whatsapp/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/credential-manager.js +14 -8
- package/dist/src/platforms/whatsapp/credential-manager.js.map +1 -1
- package/dist/src/platforms/whatsapp/ensure-auth.js +2 -2
- package/dist/src/platforms/whatsapp/ensure-auth.js.map +1 -1
- package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/cli.js +2 -1
- package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/index.d.ts +1 -0
- package/dist/src/platforms/whatsappbot/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/index.js +1 -0
- package/dist/src/platforms/whatsappbot/commands/index.js.map +1 -1
- package/dist/src/platforms/whatsappbot/commands/whoami.d.ts +17 -0
- package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/whatsappbot/commands/whoami.js +39 -0
- package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -0
- package/dist/src/shared/utils/qr.d.ts +15 -0
- package/dist/src/shared/utils/qr.d.ts.map +1 -0
- package/dist/src/shared/utils/qr.js +74 -0
- package/dist/src/shared/utils/qr.js.map +1 -0
- package/dist/src/tui/adapters/whatsapp-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/whatsapp-adapter.js +20 -15
- package/dist/src/tui/adapters/whatsapp-adapter.js.map +1 -1
- package/docs/content/docs/agent-skills.mdx +4 -4
- package/docs/content/docs/cli/channeltalk.mdx +12 -1
- package/docs/content/docs/cli/channeltalkbot.mdx +10 -1
- package/docs/content/docs/cli/discord.mdx +11 -1
- package/docs/content/docs/cli/discordbot.mdx +10 -1
- package/docs/content/docs/cli/instagram.mdx +12 -1
- package/docs/content/docs/cli/kakaotalk.mdx +25 -1
- package/docs/content/docs/cli/line.mdx +5 -5
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/slack.mdx +11 -1
- package/docs/content/docs/cli/slackbot.mdx +10 -1
- package/docs/content/docs/cli/teams.mdx +11 -1
- package/docs/content/docs/cli/telegram.mdx +11 -0
- package/docs/content/docs/cli/webex.mdx +11 -1
- package/docs/content/docs/cli/wechatbot.mdx +188 -0
- package/docs/content/docs/cli/whatsapp.mdx +37 -8
- package/docs/content/docs/cli/whatsappbot.mdx +10 -1
- package/docs/content/docs/sdk/meta.json +1 -1
- package/docs/content/docs/sdk/wechatbot.mdx +282 -0
- package/docs/content/docs/tui.mdx +1 -1
- package/docs/src/app/page.tsx +5 -5
- package/package.json +11 -3
- package/skills/agent-channeltalk/SKILL.md +12 -1
- package/skills/agent-channeltalkbot/SKILL.md +10 -1
- package/skills/agent-discord/SKILL.md +11 -1
- package/skills/agent-discordbot/SKILL.md +10 -1
- package/skills/agent-instagram/SKILL.md +12 -1
- package/skills/agent-kakaotalk/SKILL.md +30 -1
- package/skills/agent-kakaotalk/references/common-patterns.md +1 -1
- package/skills/agent-line/SKILL.md +11 -15
- package/skills/agent-line/references/authentication.md +13 -4
- package/skills/agent-slack/SKILL.md +11 -1
- package/skills/agent-slackbot/SKILL.md +10 -1
- package/skills/agent-teams/SKILL.md +11 -1
- package/skills/agent-telegram/SKILL.md +6 -1
- package/skills/agent-webex/SKILL.md +11 -1
- package/skills/agent-wechatbot/SKILL.md +394 -0
- package/skills/agent-whatsapp/SKILL.md +63 -15
- package/skills/agent-whatsapp/references/authentication.md +36 -6
- package/skills/agent-whatsappbot/SKILL.md +10 -1
- package/src/platforms/channeltalk/cli.ts +2 -0
- package/src/platforms/channeltalk/commands/index.ts +1 -0
- package/src/platforms/channeltalk/commands/whoami.test.ts +64 -0
- package/src/platforms/channeltalk/commands/whoami.ts +62 -0
- package/src/platforms/channeltalkbot/cli.ts +2 -0
- package/src/platforms/channeltalkbot/commands/index.ts +1 -0
- package/src/platforms/channeltalkbot/commands/whoami.test.ts +104 -0
- package/src/platforms/channeltalkbot/commands/whoami.ts +42 -0
- package/src/platforms/discord/cli.ts +2 -0
- package/src/platforms/discord/commands/index.ts +1 -0
- package/src/platforms/discord/commands/whoami.test.ts +91 -0
- package/src/platforms/discord/commands/whoami.ts +36 -0
- package/src/platforms/discord/credential-manager.test.ts +18 -1
- package/src/platforms/discordbot/cli.ts +2 -0
- package/src/platforms/discordbot/client.ts +2 -2
- package/src/platforms/discordbot/commands/index.ts +1 -0
- package/src/platforms/discordbot/commands/whoami.test.ts +96 -0
- package/src/platforms/discordbot/commands/whoami.ts +44 -0
- package/src/platforms/instagram/cli.ts +2 -1
- package/src/platforms/instagram/client.ts +13 -0
- package/src/platforms/instagram/commands/auth.test.ts +216 -0
- package/src/platforms/instagram/commands/chat.test.ts +123 -0
- package/src/platforms/instagram/commands/index.ts +1 -0
- package/src/platforms/instagram/commands/message.test.ts +174 -0
- package/src/platforms/instagram/commands/whoami.test.ts +60 -0
- package/src/platforms/instagram/commands/whoami.ts +21 -0
- package/src/platforms/kakaotalk/cli.ts +2 -1
- package/src/platforms/kakaotalk/client.test.ts +182 -14
- package/src/platforms/kakaotalk/client.ts +261 -27
- package/src/platforms/kakaotalk/commands/auth.test.ts +299 -0
- package/src/platforms/kakaotalk/commands/chat.test.ts +97 -0
- package/src/platforms/kakaotalk/commands/index.ts +1 -0
- package/src/platforms/kakaotalk/commands/message.test.ts +113 -0
- package/src/platforms/kakaotalk/commands/whoami.test.ts +116 -0
- package/src/platforms/kakaotalk/commands/whoami.ts +21 -0
- package/src/platforms/kakaotalk/index.test.ts +5 -0
- package/src/platforms/kakaotalk/index.ts +2 -0
- package/src/platforms/kakaotalk/protocol/session.ts +29 -7
- package/src/platforms/kakaotalk/protocol/types.ts +9 -0
- package/src/platforms/kakaotalk/types.ts +30 -0
- package/src/platforms/line/cli.ts +2 -2
- package/src/platforms/line/client.ts +14 -39
- package/src/platforms/line/commands/auth.test.ts +141 -0
- package/src/platforms/line/commands/auth.ts +37 -61
- package/src/platforms/line/commands/chat.test.ts +110 -0
- package/src/platforms/line/commands/friend.test.ts +98 -0
- package/src/platforms/line/commands/index.ts +1 -1
- package/src/platforms/line/commands/message.test.ts +119 -0
- package/src/platforms/line/commands/whoami.test.ts +85 -0
- package/src/platforms/line/commands/{profile.ts → whoami.ts} +4 -4
- package/src/platforms/slack/cli.ts +2 -0
- package/src/platforms/slack/commands/index.ts +1 -0
- package/src/platforms/slack/commands/whoami.test.ts +126 -0
- package/src/platforms/slack/commands/whoami.ts +40 -0
- package/src/platforms/slackbot/cli.ts +2 -1
- package/src/platforms/slackbot/commands/channel.test.ts +139 -0
- package/src/platforms/slackbot/commands/index.ts +1 -0
- package/src/platforms/slackbot/commands/message.test.ts +226 -0
- package/src/platforms/slackbot/commands/reaction.test.ts +90 -0
- package/src/platforms/slackbot/commands/user.test.ts +143 -0
- package/src/platforms/slackbot/commands/whoami.test.ts +102 -0
- package/src/platforms/slackbot/commands/whoami.ts +44 -0
- package/src/platforms/teams/cli.ts +2 -0
- package/src/platforms/teams/commands/index.ts +1 -0
- package/src/platforms/teams/commands/reaction.test.ts +45 -61
- package/src/platforms/teams/commands/reaction.ts +2 -0
- package/src/platforms/teams/commands/whoami.test.ts +83 -0
- package/src/platforms/teams/commands/whoami.ts +33 -0
- package/src/platforms/telegram/cli.ts +2 -1
- package/src/platforms/telegram/commands/chat.test.ts +125 -0
- package/src/platforms/telegram/commands/index.ts +1 -0
- package/src/platforms/telegram/commands/message.test.ts +92 -0
- package/src/platforms/telegram/commands/whoami.test.ts +75 -0
- package/src/platforms/telegram/commands/whoami.ts +29 -0
- package/src/platforms/webex/cli.ts +2 -1
- package/src/platforms/webex/commands/auth.test.ts +58 -46
- package/src/platforms/webex/commands/index.ts +1 -0
- package/src/platforms/webex/commands/member.test.ts +60 -57
- package/src/platforms/webex/commands/message.test.ts +74 -121
- package/src/platforms/webex/commands/message.ts +1 -1
- package/src/platforms/webex/commands/snapshot.test.ts +54 -45
- package/src/platforms/webex/commands/space.test.ts +46 -49
- package/src/platforms/webex/commands/whoami.test.ts +113 -0
- package/src/platforms/webex/commands/whoami.ts +31 -0
- package/src/platforms/webex/credential-manager.test.ts +0 -1
- package/src/platforms/wechatbot/cli.ts +25 -0
- package/src/platforms/wechatbot/client.test.ts +497 -0
- package/src/platforms/wechatbot/client.ts +268 -0
- package/src/platforms/wechatbot/commands/auth.test.ts +211 -0
- package/src/platforms/wechatbot/commands/auth.ts +203 -0
- package/src/platforms/wechatbot/commands/index.ts +5 -0
- package/src/platforms/wechatbot/commands/message.test.ts +155 -0
- package/src/platforms/wechatbot/commands/message.ts +104 -0
- package/src/platforms/wechatbot/commands/shared.ts +22 -0
- package/src/platforms/wechatbot/commands/template.test.ts +199 -0
- package/src/platforms/wechatbot/commands/template.ts +102 -0
- package/src/platforms/wechatbot/commands/user.test.ts +165 -0
- package/src/platforms/wechatbot/commands/user.ts +75 -0
- package/src/platforms/wechatbot/commands/whoami.test.ts +109 -0
- package/src/platforms/wechatbot/commands/whoami.ts +43 -0
- package/src/platforms/wechatbot/credential-manager.test.ts +255 -0
- package/src/platforms/wechatbot/credential-manager.ts +148 -0
- package/src/platforms/wechatbot/index.test.ts +49 -0
- package/src/platforms/wechatbot/index.ts +19 -0
- package/src/platforms/wechatbot/types.test.ts +223 -0
- package/src/platforms/wechatbot/types.ts +107 -0
- package/src/platforms/whatsapp/cli.ts +2 -1
- package/src/platforms/whatsapp/client.ts +180 -37
- package/src/platforms/whatsapp/commands/auth.test.ts +311 -0
- package/src/platforms/whatsapp/commands/auth.ts +194 -84
- package/src/platforms/whatsapp/commands/chat.test.ts +198 -0
- package/src/platforms/whatsapp/commands/index.ts +1 -0
- package/src/platforms/whatsapp/commands/message.test.ts +231 -0
- package/src/platforms/whatsapp/commands/shared.ts +2 -2
- package/src/platforms/whatsapp/commands/whoami.test.ts +59 -0
- package/src/platforms/whatsapp/commands/whoami.ts +21 -0
- package/src/platforms/whatsapp/credential-manager.test.ts +20 -0
- package/src/platforms/whatsapp/credential-manager.ts +17 -8
- package/src/platforms/whatsapp/ensure-auth.ts +2 -2
- package/src/platforms/whatsappbot/cli.ts +2 -1
- package/src/platforms/whatsappbot/commands/auth.test.ts +217 -0
- package/src/platforms/whatsappbot/commands/index.ts +1 -0
- package/src/platforms/whatsappbot/commands/message.test.ts +198 -0
- package/src/platforms/whatsappbot/commands/template.test.ts +112 -0
- package/src/platforms/whatsappbot/commands/whoami.test.ts +100 -0
- package/src/platforms/whatsappbot/commands/whoami.ts +57 -0
- package/src/shared/utils/qr.ts +92 -0
- package/src/tui/adapters/whatsapp-adapter.ts +19 -16
- package/dist/src/platforms/line/commands/profile.d.ts +0 -3
- package/dist/src/platforms/line/commands/profile.d.ts.map +0 -1
- package/dist/src/platforms/line/commands/profile.js.map +0 -1
|
@@ -5,6 +5,8 @@ import { KakaoTalkClient, KakaoTalkError } from './client'
|
|
|
5
5
|
// Mock LocoSession at module level
|
|
6
6
|
const mockLogin = mock(() => Promise.resolve({}))
|
|
7
7
|
const mockGetChatList = mock(() => Promise.resolve({}))
|
|
8
|
+
const mockGetChatLogs = mock(() => Promise.resolve({}))
|
|
9
|
+
const mockGetChatInfo = mock(() => Promise.resolve({}))
|
|
8
10
|
const mockSyncMessages = mock(() => Promise.resolve({}))
|
|
9
11
|
const mockSendMessage = mock(() => Promise.resolve({}))
|
|
10
12
|
const mockClose = mock(() => {})
|
|
@@ -14,6 +16,8 @@ mock.module('./protocol/session', () => ({
|
|
|
14
16
|
LocoSession: class MockLocoSession {
|
|
15
17
|
login = mockLogin
|
|
16
18
|
getChatList = mockGetChatList
|
|
19
|
+
getChatLogs = mockGetChatLogs
|
|
20
|
+
getChatInfo = mockGetChatInfo
|
|
17
21
|
syncMessages = mockSyncMessages
|
|
18
22
|
sendMessage = mockSendMessage
|
|
19
23
|
close = mockClose
|
|
@@ -28,6 +32,8 @@ function makeLong(n: number): { low: number; high: number } {
|
|
|
28
32
|
function resetAllMocks() {
|
|
29
33
|
mockLogin.mockReset()
|
|
30
34
|
mockGetChatList.mockReset()
|
|
35
|
+
mockGetChatLogs.mockReset()
|
|
36
|
+
mockGetChatInfo.mockReset()
|
|
31
37
|
mockSyncMessages.mockReset()
|
|
32
38
|
mockSendMessage.mockReset()
|
|
33
39
|
mockClose.mockReset()
|
|
@@ -67,6 +73,8 @@ describe('KakaoTalkClient', () => {
|
|
|
67
73
|
beforeEach(() => {
|
|
68
74
|
resetAllMocks()
|
|
69
75
|
mockLogin.mockResolvedValue(DEFAULT_LOGIN_RESULT)
|
|
76
|
+
mockGetChatLogs.mockResolvedValue({ body: { status: 0, chatLogs: [], eof: true } })
|
|
77
|
+
mockGetChatInfo.mockResolvedValue({ body: { l: makeLong(99999) } })
|
|
70
78
|
})
|
|
71
79
|
|
|
72
80
|
afterEach(() => {
|
|
@@ -146,6 +154,68 @@ describe('KakaoTalkClient', () => {
|
|
|
146
154
|
client.close()
|
|
147
155
|
})
|
|
148
156
|
|
|
157
|
+
test('falls back to LCHATLIST when login snapshot is empty (new device)', async () => {
|
|
158
|
+
// given — LOGINLIST returns empty chatDatas with eof:true (new device scenario)
|
|
159
|
+
const emptyLoginResult = {
|
|
160
|
+
chatDatas: [],
|
|
161
|
+
lastTokenId: makeLong(0),
|
|
162
|
+
lastChatId: makeLong(0),
|
|
163
|
+
eof: true,
|
|
164
|
+
}
|
|
165
|
+
mockLogin.mockResolvedValue(emptyLoginResult)
|
|
166
|
+
|
|
167
|
+
mockGetChatList.mockResolvedValueOnce({
|
|
168
|
+
body: {
|
|
169
|
+
chatDatas: [
|
|
170
|
+
{
|
|
171
|
+
c: 100,
|
|
172
|
+
t: 1,
|
|
173
|
+
k: ['Alice', 'Bob'],
|
|
174
|
+
a: 2,
|
|
175
|
+
n: 3,
|
|
176
|
+
o: 1700000000,
|
|
177
|
+
l: { authorId: 1, message: 'hi', sendAt: 1700000000 },
|
|
178
|
+
ll: makeLong(999),
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
c: 200,
|
|
182
|
+
t: 2,
|
|
183
|
+
k: ['Charlie'],
|
|
184
|
+
a: 1,
|
|
185
|
+
n: 0,
|
|
186
|
+
o: 1699999000,
|
|
187
|
+
l: null,
|
|
188
|
+
ll: makeLong(500),
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
lastTokenId: makeLong(1),
|
|
192
|
+
lastChatId: makeLong(200),
|
|
193
|
+
eof: true,
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// when — default chat list (no --all flag)
|
|
198
|
+
const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
|
|
199
|
+
const chats = await client.getChats()
|
|
200
|
+
|
|
201
|
+
// then — fetched via LCHATLIST despite eof:true and no --all
|
|
202
|
+
expect(chats).toHaveLength(2)
|
|
203
|
+
expect(mockGetChatList).toHaveBeenCalledTimes(1)
|
|
204
|
+
expect(chats[0].display_name).toBe('Alice, Bob')
|
|
205
|
+
expect(chats[1].display_name).toBe('Charlie')
|
|
206
|
+
|
|
207
|
+
client.close()
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('does not call LCHATLIST when login snapshot has chats', async () => {
|
|
211
|
+
const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
|
|
212
|
+
await client.getChats()
|
|
213
|
+
|
|
214
|
+
expect(mockGetChatList).not.toHaveBeenCalled()
|
|
215
|
+
|
|
216
|
+
client.close()
|
|
217
|
+
})
|
|
218
|
+
|
|
149
219
|
test('paginates when all=true and not eof', async () => {
|
|
150
220
|
const loginResult = {
|
|
151
221
|
...DEFAULT_LOGIN_RESULT,
|
|
@@ -254,13 +324,14 @@ describe('KakaoTalkClient', () => {
|
|
|
254
324
|
|
|
255
325
|
describe('getMessages', () => {
|
|
256
326
|
test('returns formatted messages', async () => {
|
|
257
|
-
|
|
327
|
+
mockGetChatLogs.mockResolvedValueOnce({
|
|
258
328
|
body: {
|
|
329
|
+
status: 0,
|
|
259
330
|
chatLogs: [
|
|
260
|
-
{ logId: makeLong(10), type: 1, authorId: 42, message: 'hello', sendAt: 1700000001 },
|
|
261
|
-
{ logId: makeLong(11), type: 1, authorId: 43, message: 'world', sendAt: 1700000002 },
|
|
331
|
+
{ logId: makeLong(10), chatId: 100, type: 1, authorId: 42, message: 'hello', sendAt: 1700000001 },
|
|
332
|
+
{ logId: makeLong(11), chatId: 100, type: 1, authorId: 43, message: 'world', sendAt: 1700000002 },
|
|
262
333
|
],
|
|
263
|
-
|
|
334
|
+
eof: true,
|
|
264
335
|
},
|
|
265
336
|
})
|
|
266
337
|
|
|
@@ -289,14 +360,15 @@ describe('KakaoTalkClient', () => {
|
|
|
289
360
|
test('respects count option', async () => {
|
|
290
361
|
const logs = Array.from({ length: 50 }, (_, i) => ({
|
|
291
362
|
logId: makeLong(i + 1),
|
|
363
|
+
chatId: 100,
|
|
292
364
|
type: 1,
|
|
293
365
|
authorId: 1,
|
|
294
366
|
message: `msg-${i}`,
|
|
295
367
|
sendAt: 1700000000 + i,
|
|
296
368
|
}))
|
|
297
369
|
|
|
298
|
-
|
|
299
|
-
body: { chatLogs: logs,
|
|
370
|
+
mockGetChatLogs.mockResolvedValueOnce({
|
|
371
|
+
body: { status: 0, chatLogs: logs, eof: true },
|
|
300
372
|
})
|
|
301
373
|
|
|
302
374
|
const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
|
|
@@ -311,13 +383,14 @@ describe('KakaoTalkClient', () => {
|
|
|
311
383
|
})
|
|
312
384
|
|
|
313
385
|
test('sorts messages by sent_at ascending', async () => {
|
|
314
|
-
|
|
386
|
+
mockGetChatLogs.mockResolvedValueOnce({
|
|
315
387
|
body: {
|
|
388
|
+
status: 0,
|
|
316
389
|
chatLogs: [
|
|
317
|
-
{ logId: makeLong(2), type: 1, authorId: 1, message: 'second', sendAt: 200 },
|
|
318
|
-
{ logId: makeLong(1), type: 1, authorId: 1, message: 'first', sendAt: 100 },
|
|
390
|
+
{ logId: makeLong(2), chatId: 100, type: 1, authorId: 1, message: 'second', sendAt: 200 },
|
|
391
|
+
{ logId: makeLong(1), chatId: 100, type: 1, authorId: 1, message: 'first', sendAt: 100 },
|
|
319
392
|
],
|
|
320
|
-
|
|
393
|
+
eof: true,
|
|
321
394
|
},
|
|
322
395
|
})
|
|
323
396
|
|
|
@@ -384,6 +457,101 @@ describe('KakaoTalkClient', () => {
|
|
|
384
457
|
})
|
|
385
458
|
})
|
|
386
459
|
|
|
460
|
+
describe('getProfile', () => {
|
|
461
|
+
const mockFetch = mock(() => Promise.resolve(new Response()))
|
|
462
|
+
|
|
463
|
+
beforeEach(() => {
|
|
464
|
+
mockFetch.mockReset()
|
|
465
|
+
globalThis.fetch = mockFetch as unknown as typeof fetch
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
afterEach(() => {
|
|
469
|
+
mockFetch.mockReset()
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
function makeJsonResponse(data: unknown, status = 200): Response {
|
|
473
|
+
return new Response(JSON.stringify(data), {
|
|
474
|
+
status,
|
|
475
|
+
headers: { 'Content-Type': 'application/json' },
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
test('returns profile data on success', async () => {
|
|
480
|
+
mockFetch
|
|
481
|
+
.mockResolvedValueOnce(makeJsonResponse({
|
|
482
|
+
profile: {
|
|
483
|
+
nickName: 'Test User',
|
|
484
|
+
profileImageUrl: 'https://example.com/profile.jpg',
|
|
485
|
+
originalProfileImageUrl: 'https://example.com/original.jpg',
|
|
486
|
+
statusMessage: 'Hello world',
|
|
487
|
+
},
|
|
488
|
+
}))
|
|
489
|
+
.mockResolvedValueOnce(makeJsonResponse({ accountDisplayId: 'testuser123' }))
|
|
490
|
+
|
|
491
|
+
const client = await new KakaoTalkClient().login({ oauthToken: 'mytoken', userId: 'user42', deviceUuid: 'device1' })
|
|
492
|
+
const profile = await client.getProfile()
|
|
493
|
+
|
|
494
|
+
expect(profile.user_id).toBe('user42')
|
|
495
|
+
expect(profile.nickname).toBe('Test User')
|
|
496
|
+
expect(profile.profile_image_url).toBe('https://example.com/profile.jpg')
|
|
497
|
+
expect(profile.original_profile_image_url).toBe('https://example.com/original.jpg')
|
|
498
|
+
expect(profile.status_message).toBe('Hello world')
|
|
499
|
+
expect(profile.account_display_id).toBe('testuser123')
|
|
500
|
+
|
|
501
|
+
client.close()
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
test('throws not_authenticated when not logged in', async () => {
|
|
505
|
+
const client = new KakaoTalkClient()
|
|
506
|
+
try {
|
|
507
|
+
await client.getProfile()
|
|
508
|
+
expect.unreachable('should have thrown')
|
|
509
|
+
} catch (e) {
|
|
510
|
+
expect(e).toBeInstanceOf(KakaoTalkError)
|
|
511
|
+
expect((e as KakaoTalkError).code).toBe('not_authenticated')
|
|
512
|
+
}
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
test('throws profile_request_failed when profile HTTP request fails', async () => {
|
|
516
|
+
mockFetch
|
|
517
|
+
.mockResolvedValueOnce(makeJsonResponse({}, 401))
|
|
518
|
+
.mockResolvedValueOnce(makeJsonResponse({ accountDisplayId: null }))
|
|
519
|
+
|
|
520
|
+
const client = await new KakaoTalkClient().login({ oauthToken: 'mytoken', userId: 'user42', deviceUuid: 'device1' })
|
|
521
|
+
try {
|
|
522
|
+
await client.getProfile()
|
|
523
|
+
expect.unreachable('should have thrown')
|
|
524
|
+
} catch (e) {
|
|
525
|
+
expect(e).toBeInstanceOf(KakaoTalkError)
|
|
526
|
+
expect((e as KakaoTalkError).code).toBe('profile_request_failed')
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
client.close()
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
test('returns null account_display_id when more_settings request fails', async () => {
|
|
533
|
+
mockFetch
|
|
534
|
+
.mockResolvedValueOnce(makeJsonResponse({
|
|
535
|
+
profile: {
|
|
536
|
+
nickName: 'Test User',
|
|
537
|
+
profileImageUrl: null,
|
|
538
|
+
originalProfileImageUrl: null,
|
|
539
|
+
statusMessage: null,
|
|
540
|
+
},
|
|
541
|
+
}))
|
|
542
|
+
.mockResolvedValueOnce(makeJsonResponse({}, 500))
|
|
543
|
+
|
|
544
|
+
const client = await new KakaoTalkClient().login({ oauthToken: 'mytoken', userId: 'user42', deviceUuid: 'device1' })
|
|
545
|
+
const profile = await client.getProfile()
|
|
546
|
+
|
|
547
|
+
expect(profile.user_id).toBe('user42')
|
|
548
|
+
expect(profile.nickname).toBe('Test User')
|
|
549
|
+
expect(profile.account_display_id).toBeNull()
|
|
550
|
+
|
|
551
|
+
client.close()
|
|
552
|
+
})
|
|
553
|
+
})
|
|
554
|
+
|
|
387
555
|
describe('session lifecycle', () => {
|
|
388
556
|
test('lazy init: does not call login until first method call', async () => {
|
|
389
557
|
const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
|
|
@@ -399,8 +567,8 @@ describe('KakaoTalkClient', () => {
|
|
|
399
567
|
})
|
|
400
568
|
|
|
401
569
|
test('reuses session across multiple calls', async () => {
|
|
402
|
-
|
|
403
|
-
body: { chatLogs: [],
|
|
570
|
+
mockGetChatLogs.mockResolvedValue({
|
|
571
|
+
body: { status: 0, chatLogs: [], eof: true },
|
|
404
572
|
})
|
|
405
573
|
|
|
406
574
|
const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
|
|
@@ -417,8 +585,8 @@ describe('KakaoTalkClient', () => {
|
|
|
417
585
|
mockLogin.mockImplementation(
|
|
418
586
|
() => new Promise((resolve) => setTimeout(() => resolve(DEFAULT_LOGIN_RESULT), 50)),
|
|
419
587
|
)
|
|
420
|
-
|
|
421
|
-
body: { chatLogs: [],
|
|
588
|
+
mockGetChatLogs.mockResolvedValue({
|
|
589
|
+
body: { status: 0, chatLogs: [], eof: true },
|
|
422
590
|
})
|
|
423
591
|
|
|
424
592
|
const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { Long } from 'bson'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { homedir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
2
6
|
|
|
3
7
|
import { warn } from '@/shared/utils/stderr'
|
|
4
8
|
|
|
9
|
+
import { APP_VERSION, LANG, OS } from './protocol/config'
|
|
5
10
|
import { LocoSession } from './protocol/session'
|
|
6
|
-
import type { ChatListResponse, LoginListResponse } from './protocol/types'
|
|
7
|
-
import type { KakaoChat, KakaoMessage, KakaoSendResult } from './types'
|
|
11
|
+
import type { ChatListResponse, LoginListResponse, SyncState } from './protocol/types'
|
|
12
|
+
import type { KakaoChat, KakaoMessage, KakaoProfile, KakaoSendResult } from './types'
|
|
8
13
|
|
|
9
14
|
export class KakaoTalkError extends Error {
|
|
10
15
|
code: string
|
|
@@ -73,6 +78,14 @@ function matchesSearch(chat: ChatData, term: string): boolean {
|
|
|
73
78
|
return names.some((n) => n.toLowerCase().includes(lower))
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
function findMaxLogId(logs: Array<Record<string, unknown>>, field: string): Long | null {
|
|
82
|
+
return logs.reduce<Long | null>((max, log) => {
|
|
83
|
+
const current = bsonToLong(log[field])
|
|
84
|
+
if (!current) return max
|
|
85
|
+
return !max || current.greaterThan(max) ? current : max
|
|
86
|
+
}, null)
|
|
87
|
+
}
|
|
88
|
+
|
|
76
89
|
function collectChats(chatDatas: ChatData[], into: ChatData[], seen: Set<string>): void {
|
|
77
90
|
for (const chat of chatDatas) {
|
|
78
91
|
const id = String(chat.c)
|
|
@@ -91,6 +104,129 @@ function wrapError(error: unknown, code: string): KakaoTalkError {
|
|
|
91
104
|
|
|
92
105
|
const MAX_PAGES = 50
|
|
93
106
|
|
|
107
|
+
const CONFIG_DIR = join(homedir(), '.config', 'agent-messenger')
|
|
108
|
+
|
|
109
|
+
function syncStatePath(deviceUuid: string): string {
|
|
110
|
+
return join(CONFIG_DIR, `kakaotalk-sync-state-${deviceUuid}.json`)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function loadSyncState(deviceUuid: string): Promise<SyncState | undefined> {
|
|
114
|
+
const path = syncStatePath(deviceUuid)
|
|
115
|
+
if (!existsSync(path)) return undefined
|
|
116
|
+
const content = await readFile(path, 'utf-8')
|
|
117
|
+
const parsed = JSON.parse(content) as Partial<SyncState>
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
parsed.version !== 2 ||
|
|
121
|
+
typeof parsed.revision !== 'number' ||
|
|
122
|
+
!Array.isArray(parsed.chatIds) ||
|
|
123
|
+
!Array.isArray(parsed.maxIds) ||
|
|
124
|
+
parsed.chatIds.length !== parsed.maxIds.length ||
|
|
125
|
+
!parsed.lastTokenId ||
|
|
126
|
+
typeof parsed.lbk !== 'number'
|
|
127
|
+
) {
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return parsed as SyncState
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function saveSyncState(deviceUuid: string, state: SyncState): Promise<void> {
|
|
135
|
+
await mkdir(CONFIG_DIR, { recursive: true })
|
|
136
|
+
const path = syncStatePath(deviceUuid)
|
|
137
|
+
await writeFile(path, JSON.stringify(state, null, 2))
|
|
138
|
+
await chmod(path, 0o600)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function toLongLike(v: unknown): { low: number; high: number } {
|
|
142
|
+
if (v && typeof v === 'object' && 'low' in v && 'high' in v) {
|
|
143
|
+
const { low, high } = v as { low: number; high: number }
|
|
144
|
+
return { low, high }
|
|
145
|
+
}
|
|
146
|
+
if (typeof v === 'number') {
|
|
147
|
+
const big = BigInt(v)
|
|
148
|
+
return { low: Number(big & 0xffffffffn), high: Number((big >> 32n) & 0xffffffffn) }
|
|
149
|
+
}
|
|
150
|
+
return { low: 0, high: 0 }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function buildSyncState(loginResult: LoginListResponse, previousRevision: number): SyncState {
|
|
154
|
+
const chatDatas = (loginResult.chatDatas ?? []) as Array<Record<string, unknown>>
|
|
155
|
+
return {
|
|
156
|
+
version: 2,
|
|
157
|
+
revision: typeof loginResult.revision === 'number' ? loginResult.revision : previousRevision,
|
|
158
|
+
chatIds: chatDatas.map((chat) => toLongLike(chat.c)),
|
|
159
|
+
maxIds: chatDatas.map((chat) => toLongLike(chat.ll)),
|
|
160
|
+
lastTokenId: toLongLike(loginResult.lastTokenId),
|
|
161
|
+
lbk: typeof loginResult.lbk === 'number' ? loginResult.lbk : 0,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function deleteFromSyncState(state: SyncState, chatId: string): void {
|
|
166
|
+
const index = state.chatIds.findIndex((entry) => longToString(entry) === chatId)
|
|
167
|
+
if (index === -1) return
|
|
168
|
+
|
|
169
|
+
state.chatIds.splice(index, 1)
|
|
170
|
+
state.maxIds.splice(index, 1)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function upsertSyncState(state: SyncState, chatId: unknown, maxId: unknown): void {
|
|
174
|
+
const chatIdString = longToString(chatId)
|
|
175
|
+
const nextChatId = toLongLike(chatId)
|
|
176
|
+
const nextMaxId = toLongLike(maxId)
|
|
177
|
+
const index = state.chatIds.findIndex((entry) => longToString(entry) === chatIdString)
|
|
178
|
+
|
|
179
|
+
if (index === -1) {
|
|
180
|
+
state.chatIds.push(nextChatId)
|
|
181
|
+
state.maxIds.push(nextMaxId)
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
state.chatIds[index] = nextChatId
|
|
186
|
+
state.maxIds[index] = nextMaxId
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function mergeSyncState(previous: SyncState | undefined, loginResult: LoginListResponse): SyncState {
|
|
190
|
+
const next = previous
|
|
191
|
+
? {
|
|
192
|
+
version: 2 as const,
|
|
193
|
+
revision: previous.revision,
|
|
194
|
+
chatIds: [...previous.chatIds],
|
|
195
|
+
maxIds: [...previous.maxIds],
|
|
196
|
+
lastTokenId: previous.lastTokenId,
|
|
197
|
+
lbk: previous.lbk,
|
|
198
|
+
}
|
|
199
|
+
: buildSyncState(loginResult, 0)
|
|
200
|
+
|
|
201
|
+
next.revision = typeof loginResult.revision === 'number' ? loginResult.revision : next.revision
|
|
202
|
+
next.lastTokenId = toLongLike(loginResult.lastTokenId)
|
|
203
|
+
next.lbk = typeof loginResult.lbk === 'number' ? loginResult.lbk : next.lbk
|
|
204
|
+
|
|
205
|
+
const delChatIds = Array.isArray(loginResult.delChatIds) ? loginResult.delChatIds : []
|
|
206
|
+
for (const chatId of delChatIds) {
|
|
207
|
+
deleteFromSyncState(next, longToString(chatId))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const chatDatas = Array.isArray(loginResult.chatDatas) ? loginResult.chatDatas : []
|
|
211
|
+
for (const chat of chatDatas) {
|
|
212
|
+
upsertSyncState(next, chat.c, chat.ll)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return next
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function formatMessages(logs: Array<Record<string, unknown>>, count: number): KakaoMessage[] {
|
|
219
|
+
logs.sort((a, b) => (a.sendAt as number) - (b.sendAt as number))
|
|
220
|
+
|
|
221
|
+
return logs.slice(-count).map((log) => ({
|
|
222
|
+
log_id: longToString(log.logId),
|
|
223
|
+
type: log.type as number,
|
|
224
|
+
author_id: log.authorId as number,
|
|
225
|
+
message: log.message as string,
|
|
226
|
+
sent_at: log.sendAt as number,
|
|
227
|
+
}))
|
|
228
|
+
}
|
|
229
|
+
|
|
94
230
|
export class KakaoTalkClient {
|
|
95
231
|
private oauthToken: string | null = null
|
|
96
232
|
private userId: string | null = null
|
|
@@ -178,7 +314,11 @@ export class KakaoTalkClient {
|
|
|
178
314
|
private async connect(): Promise<SessionState> {
|
|
179
315
|
const session = new LocoSession()
|
|
180
316
|
try {
|
|
181
|
-
const
|
|
317
|
+
const syncState = await loadSyncState(this.deviceUuid!)
|
|
318
|
+
const loginResult = await session.login(this.oauthToken!, this.userId!, this.deviceUuid!, syncState)
|
|
319
|
+
|
|
320
|
+
const newSyncState = mergeSyncState(syncState, loginResult)
|
|
321
|
+
await saveSyncState(this.deviceUuid!, newSyncState)
|
|
182
322
|
|
|
183
323
|
session.onClose(() => {
|
|
184
324
|
if (this.state?.session === session) {
|
|
@@ -206,11 +346,22 @@ export class KakaoTalkClient {
|
|
|
206
346
|
|
|
207
347
|
collectChats((loginResult.chatDatas ?? []) as ChatData[], allChats, seenChatIds)
|
|
208
348
|
|
|
209
|
-
|
|
349
|
+
// Paginate via LCHATLIST when explicitly requested (--all / --search) OR when
|
|
350
|
+
// the login snapshot is empty. New device registrations often return an empty
|
|
351
|
+
// chatDatas with eof=true because the server has no prior sync state for the
|
|
352
|
+
// device — LCHATLIST fetches the canonical chat list regardless of device history.
|
|
353
|
+
const snapshotEmpty = allChats.length === 0
|
|
354
|
+
if (options?.all || options?.search || snapshotEmpty) {
|
|
210
355
|
let cursor: ChatListResponse = loginResult
|
|
211
356
|
let pages = 0
|
|
212
357
|
|
|
213
|
-
while (
|
|
358
|
+
while (pages < MAX_PAGES) {
|
|
359
|
+
// Trust eof only when the snapshot had data. When the snapshot was empty
|
|
360
|
+
// (new device), ignore eof for the first iteration so we always attempt
|
|
361
|
+
// at least one LCHATLIST call.
|
|
362
|
+
if (cursor.eof && !snapshotEmpty) break
|
|
363
|
+
if (cursor.eof && snapshotEmpty && pages > 0) break
|
|
364
|
+
|
|
214
365
|
const lastTokenId = bsonToLong(cursor.lastTokenId)
|
|
215
366
|
const lastChatId = bsonToLong(cursor.lastChatId)
|
|
216
367
|
|
|
@@ -241,21 +392,62 @@ export class KakaoTalkClient {
|
|
|
241
392
|
}
|
|
242
393
|
|
|
243
394
|
async getMessages(chatId: string, options?: { count?: number; from?: string }): Promise<KakaoMessage[]> {
|
|
244
|
-
return this.executeWithReconnect(async ({ session
|
|
395
|
+
return this.executeWithReconnect(async ({ session }) => {
|
|
245
396
|
try {
|
|
246
|
-
const rawChats = (loginResult.chatDatas ?? []) as ChatData[]
|
|
247
|
-
const chat = rawChats.find((c) => String(c.c) === chatId)
|
|
248
|
-
const lastLogId = chat?.ll as { high: number; low: number } | undefined
|
|
249
|
-
const maxLogId = lastLogId ? new Long(lastLogId.low, lastLogId.high) : undefined
|
|
250
|
-
|
|
251
397
|
const count = options?.count ?? 20
|
|
252
398
|
const cursor = options?.from ? parseLong(options.from) : undefined
|
|
253
399
|
|
|
254
400
|
const cid = parseLong(chatId)
|
|
255
|
-
|
|
401
|
+
|
|
256
402
|
const allMessages: Array<Record<string, unknown>> = []
|
|
257
403
|
const seenLogIds = new Set<string>()
|
|
258
|
-
let cur =
|
|
404
|
+
let cur = cursor ?? Long.fromNumber(0)
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
408
|
+
const response = await session.getChatLogs([cid], [cur])
|
|
409
|
+
const responseStatus = response.body.status
|
|
410
|
+
if (typeof responseStatus === 'number' && responseStatus !== 0) {
|
|
411
|
+
throw new Error(`MCHATLOGS failed: ${responseStatus}`)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const batch = ((response.body.chatLogs ?? []) as Array<Record<string, unknown>>).filter(
|
|
415
|
+
(log) => longToString(log.chatId) === chatId,
|
|
416
|
+
)
|
|
417
|
+
if (batch.length === 0) {
|
|
418
|
+
return formatMessages(allMessages, count)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
for (const log of batch) {
|
|
422
|
+
const lid = longToString(log.logId)
|
|
423
|
+
if (!seenLogIds.has(lid)) {
|
|
424
|
+
seenLogIds.add(lid)
|
|
425
|
+
allMessages.push(log)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const maxLog = findMaxLogId(batch, 'logId')
|
|
430
|
+
if (!maxLog || maxLog.equals(cur) || response.body.eof) {
|
|
431
|
+
return formatMessages(allMessages, count)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
cur = maxLog
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
allMessages.length = 0
|
|
438
|
+
seenLogIds.clear()
|
|
439
|
+
cur = cursor ?? Long.fromNumber(0)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (allMessages.length > 0) {
|
|
443
|
+
warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`)
|
|
444
|
+
return formatMessages(allMessages, count)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Fetch fresh lastLogId via CHATONROOM (not the stale login-time snapshot)
|
|
448
|
+
const chatInfo = await session.getChatInfo(cid)
|
|
449
|
+
const chatBody = chatInfo.body as Record<string, unknown>
|
|
450
|
+
const maxLogId = bsonToLong(chatBody.l)
|
|
259
451
|
|
|
260
452
|
let reachedEnd = false
|
|
261
453
|
for (let page = 0; page < MAX_PAGES; page++) {
|
|
@@ -271,11 +463,7 @@ export class KakaoTalkClient {
|
|
|
271
463
|
}
|
|
272
464
|
}
|
|
273
465
|
|
|
274
|
-
const maxLog = batch
|
|
275
|
-
const lid = l.logId as { high: number; low: number }
|
|
276
|
-
const long = new Long(lid.low, lid.high)
|
|
277
|
-
return !max || long.greaterThan(max) ? long : max
|
|
278
|
-
}, null)
|
|
466
|
+
const maxLog = findMaxLogId(batch, 'logId')
|
|
279
467
|
|
|
280
468
|
if (!maxLog || maxLog.equals(cur) || response.body.isOK) { reachedEnd = true; break }
|
|
281
469
|
cur = maxLog
|
|
@@ -284,15 +472,7 @@ export class KakaoTalkClient {
|
|
|
284
472
|
warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`)
|
|
285
473
|
}
|
|
286
474
|
|
|
287
|
-
allMessages
|
|
288
|
-
|
|
289
|
-
return allMessages.slice(-count).map((log) => ({
|
|
290
|
-
log_id: longToString(log.logId),
|
|
291
|
-
type: log.type as number,
|
|
292
|
-
author_id: log.authorId as number,
|
|
293
|
-
message: log.message as string,
|
|
294
|
-
sent_at: log.sendAt as number,
|
|
295
|
-
}))
|
|
475
|
+
return formatMessages(allMessages, count)
|
|
296
476
|
} catch (error) {
|
|
297
477
|
throw wrapError(error, 'get_messages_failed')
|
|
298
478
|
}
|
|
@@ -317,6 +497,60 @@ export class KakaoTalkClient {
|
|
|
317
497
|
})
|
|
318
498
|
}
|
|
319
499
|
|
|
500
|
+
async getProfile(): Promise<KakaoProfile> {
|
|
501
|
+
this.ensureAuth()
|
|
502
|
+
try {
|
|
503
|
+
const headers = {
|
|
504
|
+
Authorization: `${this.oauthToken}-${this.deviceUuid}`,
|
|
505
|
+
A: `${OS}/${APP_VERSION}/${LANG}`,
|
|
506
|
+
'User-Agent': `KT/${APP_VERSION} Md/macOS ${LANG}`,
|
|
507
|
+
Accept: '*/*',
|
|
508
|
+
'Accept-Language': LANG,
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const [profileRes, settingsRes] = await Promise.all([
|
|
512
|
+
fetch('https://katalk.kakao.com/mac/profile3/me.json', { headers }),
|
|
513
|
+
fetch('https://katalk.kakao.com/mac/account/more_settings.json?since=0&lang=ko', { headers }),
|
|
514
|
+
])
|
|
515
|
+
|
|
516
|
+
if (!profileRes.ok) {
|
|
517
|
+
throw new KakaoTalkError(`Profile request failed: ${profileRes.status}`, 'profile_request_failed')
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const profileData = await profileRes.json() as Record<string, unknown>
|
|
521
|
+
const profile = profileData.profile as Record<string, unknown> | undefined
|
|
522
|
+
|
|
523
|
+
let accountDisplayId: string | null = null
|
|
524
|
+
let accountEmail: string | null = null
|
|
525
|
+
let pstnNumber: string | null = null
|
|
526
|
+
let emailVerified: boolean | null = null
|
|
527
|
+
if (settingsRes.ok) {
|
|
528
|
+
const settingsData = await settingsRes.json() as Record<string, unknown>
|
|
529
|
+
accountDisplayId = (settingsData.accountDisplayId as string) || null
|
|
530
|
+
accountEmail = (settingsData.accountEmail as string) || null
|
|
531
|
+
pstnNumber = (settingsData.pstnNumber as string) || null
|
|
532
|
+
emailVerified = typeof settingsData.emailVerified === 'boolean' ? settingsData.emailVerified : null
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
user_id: this.userId!,
|
|
537
|
+
nickname: (profile?.nickName as string) || '',
|
|
538
|
+
profile_image_url: (profile?.profileImageUrl as string) || null,
|
|
539
|
+
original_profile_image_url: (profile?.originalProfileImageUrl as string) || null,
|
|
540
|
+
background_image_url: (profile?.backgroundImageUrl as string) || null,
|
|
541
|
+
original_background_image_url: (profile?.originalBackgroundImageUrl as string) || null,
|
|
542
|
+
fullname: (profile?.fullname as string) || null,
|
|
543
|
+
status_message: (profile?.statusMessage as string) || null,
|
|
544
|
+
account_display_id: accountDisplayId,
|
|
545
|
+
account_email: accountEmail,
|
|
546
|
+
pstn_number: pstnNumber,
|
|
547
|
+
email_verified: emailVerified,
|
|
548
|
+
}
|
|
549
|
+
} catch (error) {
|
|
550
|
+
throw wrapError(error, 'get_profile_failed')
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
320
554
|
close(): void {
|
|
321
555
|
this.closed = true
|
|
322
556
|
if (this.state) {
|