agent-messenger 1.0.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/commands/release.md +92 -0
- package/.claude-plugin/README.md +144 -0
- package/.claude-plugin/marketplace.json +37 -0
- package/.claude-plugin/plugin.json +17 -0
- package/.github/workflows/ci.yml +30 -0
- package/CLAUDE.md +106 -0
- package/CONTRIBUTING.md +131 -0
- package/README.md +140 -0
- package/biome.json +34 -0
- package/bun.lock +252 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +21 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +140 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/channel.d.ts +3 -0
- package/dist/commands/channel.d.ts.map +1 -0
- package/dist/commands/channel.js +118 -0
- package/dist/commands/channel.js.map +1 -0
- package/dist/commands/file.d.ts +3 -0
- package/dist/commands/file.d.ts.map +1 -0
- package/dist/commands/file.js +113 -0
- package/dist/commands/file.js.map +1 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +9 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/message.d.ts +3 -0
- package/dist/commands/message.d.ts.map +1 -0
- package/dist/commands/message.js +214 -0
- package/dist/commands/message.js.map +1 -0
- package/dist/commands/reaction.d.ts +3 -0
- package/dist/commands/reaction.d.ts.map +1 -0
- package/dist/commands/reaction.js +100 -0
- package/dist/commands/reaction.js.map +1 -0
- package/dist/commands/snapshot.d.ts +3 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +88 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/user.d.ts +3 -0
- package/dist/commands/user.d.ts.map +1 -0
- package/dist/commands/user.js +96 -0
- package/dist/commands/user.js.map +1 -0
- package/dist/commands/workspace.d.ts +3 -0
- package/dist/commands/workspace.d.ts.map +1 -0
- package/dist/commands/workspace.js +89 -0
- package/dist/commands/workspace.js.map +1 -0
- package/dist/lib/credential-manager.d.ts +13 -0
- package/dist/lib/credential-manager.d.ts.map +1 -0
- package/dist/lib/credential-manager.js +58 -0
- package/dist/lib/credential-manager.js.map +1 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/ref-manager.d.ts +26 -0
- package/dist/lib/ref-manager.d.ts.map +1 -0
- package/dist/lib/ref-manager.js +92 -0
- package/dist/lib/ref-manager.js.map +1 -0
- package/dist/lib/slack-client.d.ts +37 -0
- package/dist/lib/slack-client.d.ts.map +1 -0
- package/dist/lib/slack-client.js +379 -0
- package/dist/lib/slack-client.js.map +1 -0
- package/dist/lib/token-extractor.d.ts +28 -0
- package/dist/lib/token-extractor.d.ts.map +1 -0
- package/dist/lib/token-extractor.js +401 -0
- package/dist/lib/token-extractor.js.map +1 -0
- package/dist/package.json +37 -0
- package/dist/src/cli.d.ts +5 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +22 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/platforms/discord/cli.d.ts +5 -0
- package/dist/src/platforms/discord/cli.d.ts.map +1 -0
- package/dist/src/platforms/discord/cli.js +22 -0
- package/dist/src/platforms/discord/cli.js.map +1 -0
- package/dist/src/platforms/discord/client.d.ts +34 -0
- package/dist/src/platforms/discord/client.d.ts.map +1 -0
- package/dist/src/platforms/discord/client.js +187 -0
- package/dist/src/platforms/discord/client.js.map +1 -0
- package/dist/src/platforms/discord/client.test.d.ts +2 -0
- package/dist/src/platforms/discord/client.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/client.test.js +367 -0
- package/dist/src/platforms/discord/client.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/auth.d.ts +13 -0
- package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/auth.js +155 -0
- package/dist/src/platforms/discord/commands/auth.js.map +1 -0
- package/dist/src/platforms/discord/commands/auth.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/auth.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/auth.test.js +65 -0
- package/dist/src/platforms/discord/commands/auth.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/channel.d.ts +13 -0
- package/dist/src/platforms/discord/commands/channel.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/channel.js +99 -0
- package/dist/src/platforms/discord/commands/channel.js.map +1 -0
- package/dist/src/platforms/discord/commands/channel.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/channel.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/channel.test.js +136 -0
- package/dist/src/platforms/discord/commands/channel.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/file.d.ts +13 -0
- package/dist/src/platforms/discord/commands/file.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/file.js +99 -0
- package/dist/src/platforms/discord/commands/file.js.map +1 -0
- package/dist/src/platforms/discord/commands/file.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/file.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/file.test.js +83 -0
- package/dist/src/platforms/discord/commands/file.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/guild.d.ts +15 -0
- package/dist/src/platforms/discord/commands/guild.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/guild.js +102 -0
- package/dist/src/platforms/discord/commands/guild.js.map +1 -0
- package/dist/src/platforms/discord/commands/guild.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/guild.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/guild.test.js +100 -0
- package/dist/src/platforms/discord/commands/guild.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/index.d.ts +9 -0
- package/dist/src/platforms/discord/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/index.js +9 -0
- package/dist/src/platforms/discord/commands/index.js.map +1 -0
- package/dist/src/platforms/discord/commands/message.d.ts +17 -0
- package/dist/src/platforms/discord/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/message.js +131 -0
- package/dist/src/platforms/discord/commands/message.js.map +1 -0
- package/dist/src/platforms/discord/commands/message.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/message.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/message.test.js +91 -0
- package/dist/src/platforms/discord/commands/message.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/reaction.d.ts +12 -0
- package/dist/src/platforms/discord/commands/reaction.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/reaction.js +99 -0
- package/dist/src/platforms/discord/commands/reaction.js.map +1 -0
- package/dist/src/platforms/discord/commands/reaction.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/reaction.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/reaction.test.js +115 -0
- package/dist/src/platforms/discord/commands/reaction.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/snapshot.d.ts +9 -0
- package/dist/src/platforms/discord/commands/snapshot.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/snapshot.js +80 -0
- package/dist/src/platforms/discord/commands/snapshot.js.map +1 -0
- package/dist/src/platforms/discord/commands/snapshot.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/snapshot.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/snapshot.test.js +25 -0
- package/dist/src/platforms/discord/commands/snapshot.test.js.map +1 -0
- package/dist/src/platforms/discord/commands/user.d.ts +3 -0
- package/dist/src/platforms/discord/commands/user.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/user.js +94 -0
- package/dist/src/platforms/discord/commands/user.js.map +1 -0
- package/dist/src/platforms/discord/commands/user.test.d.ts +2 -0
- package/dist/src/platforms/discord/commands/user.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/user.test.js +103 -0
- package/dist/src/platforms/discord/commands/user.test.js.map +1 -0
- package/dist/src/platforms/discord/credential-manager.d.ts +33 -0
- package/dist/src/platforms/discord/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/discord/credential-manager.js +73 -0
- package/dist/src/platforms/discord/credential-manager.js.map +1 -0
- package/dist/src/platforms/discord/credential-manager.test.d.ts +2 -0
- package/dist/src/platforms/discord/credential-manager.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/credential-manager.test.js +136 -0
- package/dist/src/platforms/discord/credential-manager.test.js.map +1 -0
- package/dist/src/platforms/discord/token-extractor.d.ts +55 -0
- package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/discord/token-extractor.js +462 -0
- package/dist/src/platforms/discord/token-extractor.js.map +1 -0
- package/dist/src/platforms/discord/token-extractor.test.d.ts +2 -0
- package/dist/src/platforms/discord/token-extractor.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/token-extractor.test.js +789 -0
- package/dist/src/platforms/discord/token-extractor.test.js.map +1 -0
- package/dist/src/platforms/discord/types.d.ts +251 -0
- package/dist/src/platforms/discord/types.d.ts.map +1 -0
- package/dist/src/platforms/discord/types.js +74 -0
- package/dist/src/platforms/discord/types.js.map +1 -0
- package/dist/src/platforms/discord/types.test.d.ts +2 -0
- package/dist/src/platforms/discord/types.test.d.ts.map +1 -0
- package/dist/src/platforms/discord/types.test.js +211 -0
- package/dist/src/platforms/discord/types.test.js.map +1 -0
- package/dist/src/platforms/slack/cli.d.ts +5 -0
- package/dist/src/platforms/slack/cli.d.ts.map +1 -0
- package/dist/src/platforms/slack/cli.js +22 -0
- package/dist/src/platforms/slack/cli.js.map +1 -0
- package/dist/src/platforms/slack/client.d.ts +47 -0
- package/dist/src/platforms/slack/client.d.ts.map +1 -0
- package/dist/src/platforms/slack/client.js +412 -0
- package/dist/src/platforms/slack/client.js.map +1 -0
- package/dist/src/platforms/slack/commands/auth.d.ts +3 -0
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/auth.js +156 -0
- package/dist/src/platforms/slack/commands/auth.js.map +1 -0
- package/dist/src/platforms/slack/commands/channel.d.ts +3 -0
- package/dist/src/platforms/slack/commands/channel.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/channel.js +118 -0
- package/dist/src/platforms/slack/commands/channel.js.map +1 -0
- package/dist/src/platforms/slack/commands/file.d.ts +3 -0
- package/dist/src/platforms/slack/commands/file.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/file.js +113 -0
- package/dist/src/platforms/slack/commands/file.js.map +1 -0
- package/dist/src/platforms/slack/commands/index.d.ts +9 -0
- package/dist/src/platforms/slack/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/index.js +9 -0
- package/dist/src/platforms/slack/commands/index.js.map +1 -0
- package/dist/src/platforms/slack/commands/message.d.ts +3 -0
- package/dist/src/platforms/slack/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/message.js +263 -0
- package/dist/src/platforms/slack/commands/message.js.map +1 -0
- package/dist/src/platforms/slack/commands/reaction.d.ts +3 -0
- package/dist/src/platforms/slack/commands/reaction.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/reaction.js +100 -0
- package/dist/src/platforms/slack/commands/reaction.js.map +1 -0
- package/dist/src/platforms/slack/commands/snapshot.d.ts +3 -0
- package/dist/src/platforms/slack/commands/snapshot.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/snapshot.js +87 -0
- package/dist/src/platforms/slack/commands/snapshot.js.map +1 -0
- package/dist/src/platforms/slack/commands/user.d.ts +3 -0
- package/dist/src/platforms/slack/commands/user.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/user.js +96 -0
- package/dist/src/platforms/slack/commands/user.js.map +1 -0
- package/dist/src/platforms/slack/commands/workspace.d.ts +3 -0
- package/dist/src/platforms/slack/commands/workspace.d.ts.map +1 -0
- package/dist/src/platforms/slack/commands/workspace.js +89 -0
- package/dist/src/platforms/slack/commands/workspace.js.map +1 -0
- package/dist/src/platforms/slack/credential-manager.d.ts +13 -0
- package/dist/src/platforms/slack/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/slack/credential-manager.js +58 -0
- package/dist/src/platforms/slack/credential-manager.js.map +1 -0
- package/dist/src/platforms/slack/index.d.ts +3 -0
- package/dist/src/platforms/slack/index.d.ts.map +1 -0
- package/dist/src/platforms/slack/index.js +3 -0
- package/dist/src/platforms/slack/index.js.map +1 -0
- package/dist/src/platforms/slack/token-extractor.d.ts +28 -0
- package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/slack/token-extractor.js +401 -0
- package/dist/src/platforms/slack/token-extractor.js.map +1 -0
- package/dist/src/platforms/slack/types.d.ts +369 -0
- package/dist/src/platforms/slack/types.d.ts.map +1 -0
- package/dist/src/platforms/slack/types.js +92 -0
- package/dist/src/platforms/slack/types.js.map +1 -0
- package/dist/src/shared/utils/concurrency.d.ts +2 -0
- package/dist/src/shared/utils/concurrency.d.ts.map +1 -0
- package/dist/src/shared/utils/concurrency.js +14 -0
- package/dist/src/shared/utils/concurrency.js.map +1 -0
- package/dist/src/shared/utils/concurrency.test.d.ts +2 -0
- package/dist/src/shared/utils/concurrency.test.d.ts.map +1 -0
- package/dist/src/shared/utils/concurrency.test.js +39 -0
- package/dist/src/shared/utils/concurrency.test.js.map +1 -0
- package/dist/src/shared/utils/error-handler.d.ts +2 -0
- package/dist/src/shared/utils/error-handler.d.ts.map +1 -0
- package/dist/src/shared/utils/error-handler.js +5 -0
- package/dist/src/shared/utils/error-handler.js.map +1 -0
- package/dist/src/shared/utils/output.d.ts +2 -0
- package/dist/src/shared/utils/output.d.ts.map +1 -0
- package/dist/src/shared/utils/output.js +4 -0
- package/dist/src/shared/utils/output.js.map +1 -0
- package/dist/tests/cli.test.d.ts +2 -0
- package/dist/tests/cli.test.d.ts.map +1 -0
- package/dist/tests/cli.test.js +83 -0
- package/dist/tests/cli.test.js.map +1 -0
- package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/CURRENT +1 -0
- package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOCK +0 -0
- package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOG +3 -0
- package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOG.old +1 -0
- package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/MANIFEST-000004 +0 -0
- package/dist/tests/commands/auth.test.d.ts +2 -0
- package/dist/tests/commands/auth.test.d.ts.map +1 -0
- package/dist/tests/commands/auth.test.js +304 -0
- package/dist/tests/commands/auth.test.js.map +1 -0
- package/dist/tests/commands/channel.test.d.ts +2 -0
- package/dist/tests/commands/channel.test.d.ts.map +1 -0
- package/dist/tests/commands/channel.test.js +166 -0
- package/dist/tests/commands/channel.test.js.map +1 -0
- package/dist/tests/commands/file.test.d.ts +2 -0
- package/dist/tests/commands/file.test.d.ts.map +1 -0
- package/dist/tests/commands/file.test.js +175 -0
- package/dist/tests/commands/file.test.js.map +1 -0
- package/dist/tests/commands/message.test.d.ts +2 -0
- package/dist/tests/commands/message.test.d.ts.map +1 -0
- package/dist/tests/commands/message.test.js +293 -0
- package/dist/tests/commands/message.test.js.map +1 -0
- package/dist/tests/commands/reaction.test.d.ts +2 -0
- package/dist/tests/commands/reaction.test.d.ts.map +1 -0
- package/dist/tests/commands/reaction.test.js +84 -0
- package/dist/tests/commands/reaction.test.js.map +1 -0
- package/dist/tests/commands/snapshot.test.d.ts +2 -0
- package/dist/tests/commands/snapshot.test.d.ts.map +1 -0
- package/dist/tests/commands/snapshot.test.js +280 -0
- package/dist/tests/commands/snapshot.test.js.map +1 -0
- package/dist/tests/commands/user.test.d.ts +2 -0
- package/dist/tests/commands/user.test.d.ts.map +1 -0
- package/dist/tests/commands/user.test.js +117 -0
- package/dist/tests/commands/user.test.js.map +1 -0
- package/dist/tests/commands/workspace.test.d.ts +2 -0
- package/dist/tests/commands/workspace.test.d.ts.map +1 -0
- package/dist/tests/commands/workspace.test.js +453 -0
- package/dist/tests/commands/workspace.test.js.map +1 -0
- package/dist/tests/credential-manager.test.d.ts +2 -0
- package/dist/tests/credential-manager.test.d.ts.map +1 -0
- package/dist/tests/credential-manager.test.js +199 -0
- package/dist/tests/credential-manager.test.js.map +1 -0
- package/dist/tests/slack-client.test.d.ts +2 -0
- package/dist/tests/slack-client.test.d.ts.map +1 -0
- package/dist/tests/slack-client.test.js +741 -0
- package/dist/tests/slack-client.test.js.map +1 -0
- package/dist/tests/types.test.d.ts +2 -0
- package/dist/tests/types.test.d.ts.map +1 -0
- package/dist/tests/types.test.js +215 -0
- package/dist/tests/types.test.js.map +1 -0
- package/dist/types/index.d.ts +369 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +92 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/error-handler.d.ts +2 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +5 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/output.d.ts +2 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +4 -0
- package/dist/utils/output.js.map +1 -0
- package/docs/discord.md +182 -0
- package/docs/slack.md +160 -0
- package/package.json +37 -0
- package/skills/agent-discord/SKILL.md +273 -0
- package/skills/agent-discord/references/authentication.md +294 -0
- package/skills/agent-discord/references/common-patterns.md +455 -0
- package/skills/agent-discord/templates/guild-summary.sh +167 -0
- package/skills/agent-discord/templates/monitor-channel.sh +180 -0
- package/skills/agent-discord/templates/post-message.sh +173 -0
- package/skills/agent-slack/SKILL.md +268 -0
- package/skills/agent-slack/references/authentication.md +332 -0
- package/skills/agent-slack/references/common-patterns.md +527 -0
- package/skills/agent-slack/templates/monitor-channel.sh +186 -0
- package/skills/agent-slack/templates/post-message.sh +130 -0
- package/skills/agent-slack/templates/workspace-summary.sh +149 -0
- package/src/cli.ts +29 -0
- package/src/platforms/discord/cli.ts +36 -0
- package/src/platforms/discord/client.test.ts +456 -0
- package/src/platforms/discord/client.ts +281 -0
- package/src/platforms/discord/commands/auth.test.ts +72 -0
- package/src/platforms/discord/commands/auth.ts +206 -0
- package/src/platforms/discord/commands/channel.test.ts +153 -0
- package/src/platforms/discord/commands/channel.ts +127 -0
- package/src/platforms/discord/commands/file.test.ts +98 -0
- package/src/platforms/discord/commands/file.ts +134 -0
- package/src/platforms/discord/commands/guild.test.ts +117 -0
- package/src/platforms/discord/commands/guild.ts +129 -0
- package/src/platforms/discord/commands/index.ts +8 -0
- package/src/platforms/discord/commands/message.test.ts +107 -0
- package/src/platforms/discord/commands/message.ts +182 -0
- package/src/platforms/discord/commands/reaction.test.ts +123 -0
- package/src/platforms/discord/commands/reaction.ts +156 -0
- package/src/platforms/discord/commands/snapshot.test.ts +29 -0
- package/src/platforms/discord/commands/snapshot.ts +104 -0
- package/src/platforms/discord/commands/user.test.ts +115 -0
- package/src/platforms/discord/commands/user.ts +124 -0
- package/src/platforms/discord/credential-manager.test.ts +173 -0
- package/src/platforms/discord/credential-manager.ts +95 -0
- package/src/platforms/discord/token-extractor.test.ts +918 -0
- package/src/platforms/discord/token-extractor.ts +549 -0
- package/src/platforms/discord/types.test.ts +245 -0
- package/src/platforms/discord/types.ts +158 -0
- package/src/platforms/slack/cli.ts +36 -0
- package/src/platforms/slack/client.ts +466 -0
- package/src/platforms/slack/commands/auth.ts +211 -0
- package/src/platforms/slack/commands/channel.ts +158 -0
- package/src/platforms/slack/commands/file.ts +153 -0
- package/src/platforms/slack/commands/index.ts +8 -0
- package/src/platforms/slack/commands/message.ts +369 -0
- package/src/platforms/slack/commands/reaction.ts +166 -0
- package/src/platforms/slack/commands/snapshot.ts +114 -0
- package/src/platforms/slack/commands/user.ts +110 -0
- package/src/platforms/slack/commands/workspace.ts +111 -0
- package/src/platforms/slack/credential-manager.ts +74 -0
- package/src/platforms/slack/index.ts +2 -0
- package/src/platforms/slack/token-extractor.ts +496 -0
- package/src/platforms/slack/types.ts +193 -0
- package/src/shared/utils/concurrency.test.ts +53 -0
- package/src/shared/utils/concurrency.ts +20 -0
- package/src/shared/utils/error-handler.ts +4 -0
- package/src/shared/utils/output.ts +3 -0
- package/tests/cli.test.ts +94 -0
- package/tests/commands/auth.test.ts +383 -0
- package/tests/commands/channel.test.ts +185 -0
- package/tests/commands/file.test.ts +204 -0
- package/tests/commands/message.test.ts +344 -0
- package/tests/commands/reaction.test.ts +101 -0
- package/tests/commands/snapshot.test.ts +308 -0
- package/tests/commands/user.test.ts +138 -0
- package/tests/commands/workspace.test.ts +528 -0
- package/tests/credential-manager.test.ts +241 -0
- package/tests/slack-client.test.ts +916 -0
- package/tests/types.test.ts +241 -0
- package/tsconfig.json +36 -0
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
+
import { execSync, spawn } from 'node:child_process';
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { CDP_PORT, DiscordTokenExtractor, TOKEN_EXTRACTION_JS } from './token-extractor';
|
|
7
|
+
// Mock modules
|
|
8
|
+
mock.module('node:fs', () => ({
|
|
9
|
+
existsSync: mock(() => false),
|
|
10
|
+
readdirSync: mock(() => []),
|
|
11
|
+
readFileSync: mock(() => Buffer.from('')),
|
|
12
|
+
}));
|
|
13
|
+
const mockSpawn = mock(() => ({
|
|
14
|
+
unref: mock(() => { }),
|
|
15
|
+
}));
|
|
16
|
+
mock.module('node:child_process', () => ({
|
|
17
|
+
execSync: mock(() => ''),
|
|
18
|
+
spawn: mockSpawn,
|
|
19
|
+
}));
|
|
20
|
+
describe('DiscordTokenExtractor', () => {
|
|
21
|
+
let extractor;
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
extractor = new DiscordTokenExtractor();
|
|
24
|
+
});
|
|
25
|
+
describe('getDiscordDirs', () => {
|
|
26
|
+
test('returns darwin paths on macOS', () => {
|
|
27
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin');
|
|
28
|
+
const dirs = darwinExtractor.getDiscordDirs();
|
|
29
|
+
expect(dirs).toContain(join(homedir(), 'Library', 'Application Support', 'Discord'));
|
|
30
|
+
expect(dirs).toContain(join(homedir(), 'Library', 'Application Support', 'discordcanary'));
|
|
31
|
+
expect(dirs).toContain(join(homedir(), 'Library', 'Application Support', 'discordptb'));
|
|
32
|
+
});
|
|
33
|
+
test('returns linux paths on Linux', () => {
|
|
34
|
+
const linuxExtractor = new DiscordTokenExtractor('linux');
|
|
35
|
+
const dirs = linuxExtractor.getDiscordDirs();
|
|
36
|
+
expect(dirs).toContain(join(homedir(), '.config', 'discord'));
|
|
37
|
+
expect(dirs).toContain(join(homedir(), '.config', 'discordcanary'));
|
|
38
|
+
expect(dirs).toContain(join(homedir(), '.config', 'discordptb'));
|
|
39
|
+
});
|
|
40
|
+
test('returns win32 paths on Windows', () => {
|
|
41
|
+
const winExtractor = new DiscordTokenExtractor('win32');
|
|
42
|
+
const dirs = winExtractor.getDiscordDirs();
|
|
43
|
+
const appdata = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');
|
|
44
|
+
expect(dirs).toContain(join(appdata, 'Discord'));
|
|
45
|
+
expect(dirs).toContain(join(appdata, 'discordcanary'));
|
|
46
|
+
expect(dirs).toContain(join(appdata, 'discordptb'));
|
|
47
|
+
});
|
|
48
|
+
test('returns multiple paths for all 3 variants', () => {
|
|
49
|
+
const dirs = extractor.getDiscordDirs();
|
|
50
|
+
expect(dirs.length).toBe(3);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe('token patterns', () => {
|
|
54
|
+
test('validates standard token format (base64.base64.base64)', () => {
|
|
55
|
+
// Token: base64(user_id).base64(timestamp).base64(hmac)
|
|
56
|
+
const validToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.abcdefghijklmnopqrstuvwxyz1234567890';
|
|
57
|
+
expect(extractor.isValidToken(validToken)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
test('validates MFA token format', () => {
|
|
60
|
+
const mfaToken = `mfa.${'a'.repeat(84)}`;
|
|
61
|
+
expect(extractor.isValidToken(mfaToken)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
test('rejects invalid tokens', () => {
|
|
64
|
+
expect(extractor.isValidToken('')).toBe(false);
|
|
65
|
+
expect(extractor.isValidToken('invalid')).toBe(false);
|
|
66
|
+
expect(extractor.isValidToken('xoxc-123')).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
test('detects encrypted tokens by prefix', () => {
|
|
69
|
+
const encryptedToken = 'dQw4w9WgXcQ:' + 'encrypted_data';
|
|
70
|
+
expect(extractor.isEncryptedToken(encryptedToken)).toBe(true);
|
|
71
|
+
expect(extractor.isEncryptedToken('MTIzNDU2.xxx.yyy')).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('extract', () => {
|
|
75
|
+
test('returns null when no Discord directories exist on linux', async () => {
|
|
76
|
+
const mockExistsSync = existsSync;
|
|
77
|
+
mockExistsSync.mockImplementation(() => false);
|
|
78
|
+
const linuxExtractor = new DiscordTokenExtractor('linux');
|
|
79
|
+
const result = await linuxExtractor.extract();
|
|
80
|
+
expect(result).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
test('extracts token from LevelDB files on linux', async () => {
|
|
83
|
+
const mockToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.abcdefghijklmnopqrstuvwxyz1234567890';
|
|
84
|
+
const ldbContent = Buffer.from(`some_data"${mockToken}"more_data`);
|
|
85
|
+
const mockExistsSync = existsSync;
|
|
86
|
+
mockExistsSync.mockImplementation((path) => {
|
|
87
|
+
if (path.includes('discord') || path.includes('leveldb'))
|
|
88
|
+
return true;
|
|
89
|
+
if (path.includes('Local Storage'))
|
|
90
|
+
return true;
|
|
91
|
+
return false;
|
|
92
|
+
});
|
|
93
|
+
const mockReaddirSync = readdirSync;
|
|
94
|
+
mockReaddirSync.mockImplementation((path) => {
|
|
95
|
+
if (path.includes('leveldb'))
|
|
96
|
+
return ['000001.ldb'];
|
|
97
|
+
if (path.includes('Local Storage'))
|
|
98
|
+
return ['leveldb'];
|
|
99
|
+
return [];
|
|
100
|
+
});
|
|
101
|
+
const mockReadFileSync = readFileSync;
|
|
102
|
+
mockReadFileSync.mockImplementation(() => ldbContent);
|
|
103
|
+
const linuxExtractor = new DiscordTokenExtractor('linux');
|
|
104
|
+
const result = await linuxExtractor.extract();
|
|
105
|
+
expect(result).not.toBeNull();
|
|
106
|
+
expect(result?.token).toBe(mockToken);
|
|
107
|
+
});
|
|
108
|
+
test('tries LevelDB first on macOS, CDP as fallback', async () => {
|
|
109
|
+
const levelDbToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.leveldb_token_123456789012345';
|
|
110
|
+
const ldbContent = Buffer.from(`some_data"${levelDbToken}"more_data`);
|
|
111
|
+
const mockExistsSync = existsSync;
|
|
112
|
+
mockExistsSync.mockImplementation((path) => {
|
|
113
|
+
if (path.includes('Discord') || path.includes('leveldb'))
|
|
114
|
+
return true;
|
|
115
|
+
if (path.includes('Local Storage'))
|
|
116
|
+
return true;
|
|
117
|
+
return false;
|
|
118
|
+
});
|
|
119
|
+
const mockReaddirSync = readdirSync;
|
|
120
|
+
mockReaddirSync.mockImplementation((path) => {
|
|
121
|
+
if (path.includes('leveldb'))
|
|
122
|
+
return ['000001.ldb'];
|
|
123
|
+
if (path.includes('Local Storage'))
|
|
124
|
+
return ['leveldb'];
|
|
125
|
+
return [];
|
|
126
|
+
});
|
|
127
|
+
const mockReadFileSync = readFileSync;
|
|
128
|
+
mockReadFileSync.mockImplementation(() => ldbContent);
|
|
129
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin', 0);
|
|
130
|
+
const result = await darwinExtractor.extract();
|
|
131
|
+
expect(result).not.toBeNull();
|
|
132
|
+
expect(result?.token).toBe(levelDbToken);
|
|
133
|
+
});
|
|
134
|
+
test('falls back to leveldb on macOS when CDP fails', async () => {
|
|
135
|
+
const mockToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.leveldb_fallback_token_12345';
|
|
136
|
+
const ldbContent = Buffer.from(`some_data"${mockToken}"more_data`);
|
|
137
|
+
const originalFetch = globalThis.fetch;
|
|
138
|
+
globalThis.fetch = mock(async () => ({
|
|
139
|
+
ok: true,
|
|
140
|
+
json: async () => [],
|
|
141
|
+
}));
|
|
142
|
+
const mockExistsSync = existsSync;
|
|
143
|
+
mockExistsSync.mockImplementation((path) => {
|
|
144
|
+
if (path.includes('/Applications/'))
|
|
145
|
+
return false;
|
|
146
|
+
if (path.includes('Discord') || path.includes('leveldb'))
|
|
147
|
+
return true;
|
|
148
|
+
if (path.includes('Local Storage'))
|
|
149
|
+
return true;
|
|
150
|
+
return false;
|
|
151
|
+
});
|
|
152
|
+
const mockReaddirSync = readdirSync;
|
|
153
|
+
mockReaddirSync.mockImplementation((path) => {
|
|
154
|
+
if (path.includes('leveldb'))
|
|
155
|
+
return ['000001.ldb'];
|
|
156
|
+
if (path.includes('Local Storage'))
|
|
157
|
+
return ['leveldb'];
|
|
158
|
+
return [];
|
|
159
|
+
});
|
|
160
|
+
const mockReadFileSync = readFileSync;
|
|
161
|
+
mockReadFileSync.mockImplementation(() => ldbContent);
|
|
162
|
+
try {
|
|
163
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin', 0);
|
|
164
|
+
const result = await darwinExtractor.extract();
|
|
165
|
+
expect(result).not.toBeNull();
|
|
166
|
+
expect(result?.token).toBe(mockToken);
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
globalThis.fetch = originalFetch;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
test('returns first valid token found across variants on linux', async () => {
|
|
173
|
+
const mockToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.first_token_found_1234567890';
|
|
174
|
+
const ldbContent = Buffer.from(`"${mockToken}"`);
|
|
175
|
+
const mockExistsSync = existsSync;
|
|
176
|
+
mockExistsSync.mockImplementation(() => true);
|
|
177
|
+
const mockReaddirSync = readdirSync;
|
|
178
|
+
mockReaddirSync.mockImplementation((path) => {
|
|
179
|
+
if (path.includes('leveldb'))
|
|
180
|
+
return ['test.ldb'];
|
|
181
|
+
if (path.includes('Local Storage'))
|
|
182
|
+
return ['leveldb'];
|
|
183
|
+
return [];
|
|
184
|
+
});
|
|
185
|
+
const mockReadFileSync = readFileSync;
|
|
186
|
+
mockReadFileSync.mockImplementation(() => ldbContent);
|
|
187
|
+
const linuxExtractor = new DiscordTokenExtractor('linux');
|
|
188
|
+
const result = await linuxExtractor.extract();
|
|
189
|
+
expect(result).not.toBeNull();
|
|
190
|
+
expect(typeof result?.token).toBe('string');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
describe('encrypted token handling', () => {
|
|
194
|
+
test('decrypts Windows DPAPI encrypted token', async () => {
|
|
195
|
+
const mockExecSync = execSync;
|
|
196
|
+
const decryptedKey = Buffer.from('0'.repeat(32), 'hex').toString('base64');
|
|
197
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
198
|
+
if (cmd.includes('powershell') && cmd.includes('ProtectedData')) {
|
|
199
|
+
return `${decryptedKey}\n`;
|
|
200
|
+
}
|
|
201
|
+
return '';
|
|
202
|
+
});
|
|
203
|
+
const winExtractor = new DiscordTokenExtractor('win32');
|
|
204
|
+
// Mock Local State file reading
|
|
205
|
+
const mockReadFileSync = readFileSync;
|
|
206
|
+
mockReadFileSync.mockImplementation((path) => {
|
|
207
|
+
if (path.includes('Local State')) {
|
|
208
|
+
return JSON.stringify({
|
|
209
|
+
os_crypt: {
|
|
210
|
+
encrypted_key: Buffer.from(`DPAPI${'x'.repeat(32)}`).toString('base64'),
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return Buffer.from('');
|
|
215
|
+
});
|
|
216
|
+
// Test that DPAPI decryption is called
|
|
217
|
+
const encryptedToken = `dQw4w9WgXcQ:${Buffer.from('test').toString('base64')}`;
|
|
218
|
+
expect(winExtractor.isEncryptedToken(encryptedToken)).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
test('decrypts macOS Keychain encrypted token', async () => {
|
|
221
|
+
const mockExecSync = execSync;
|
|
222
|
+
const keychainPassword = 'test_password';
|
|
223
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
224
|
+
if (cmd.includes('security find-generic-password')) {
|
|
225
|
+
if (cmd.includes('Discord Safe Storage')) {
|
|
226
|
+
return keychainPassword;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
throw new Error('Not found');
|
|
230
|
+
});
|
|
231
|
+
const macExtractor = new DiscordTokenExtractor('darwin');
|
|
232
|
+
// Verify keychain command patterns
|
|
233
|
+
expect(macExtractor.getKeychainVariants()).toEqual([
|
|
234
|
+
{ service: 'discord Safe Storage', account: 'discord Key' },
|
|
235
|
+
{ service: 'discordcanary Safe Storage', account: 'discordcanary Key' },
|
|
236
|
+
{ service: 'discordptb Safe Storage', account: 'discordptb Key' },
|
|
237
|
+
{ service: 'Discord Safe Storage', account: 'Discord' },
|
|
238
|
+
{ service: 'Discord Canary Safe Storage', account: 'Discord Canary' },
|
|
239
|
+
{ service: 'Discord PTB Safe Storage', account: 'Discord PTB' },
|
|
240
|
+
]);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
describe('variant detection', () => {
|
|
244
|
+
test('identifies Discord Stable', () => {
|
|
245
|
+
expect(extractor.getVariantFromPath('/path/to/Discord')).toBe('stable');
|
|
246
|
+
expect(extractor.getVariantFromPath('/path/to/discord')).toBe('stable');
|
|
247
|
+
});
|
|
248
|
+
test('identifies Discord Canary', () => {
|
|
249
|
+
expect(extractor.getVariantFromPath('/path/to/discordcanary')).toBe('canary');
|
|
250
|
+
expect(extractor.getVariantFromPath('/path/to/Discord Canary')).toBe('canary');
|
|
251
|
+
});
|
|
252
|
+
test('identifies Discord PTB', () => {
|
|
253
|
+
expect(extractor.getVariantFromPath('/path/to/discordptb')).toBe('ptb');
|
|
254
|
+
expect(extractor.getVariantFromPath('/path/to/Discord PTB')).toBe('ptb');
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
describe('process management', () => {
|
|
258
|
+
describe('isDiscordRunning', () => {
|
|
259
|
+
test('returns true when Discord process is found on macOS', async () => {
|
|
260
|
+
const mockExecSync = execSync;
|
|
261
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
262
|
+
if (cmd.includes('pgrep') && cmd.includes('Discord')) {
|
|
263
|
+
return '12345\n';
|
|
264
|
+
}
|
|
265
|
+
return '';
|
|
266
|
+
});
|
|
267
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0);
|
|
268
|
+
const result = await darwinExtractor.isDiscordRunning('stable');
|
|
269
|
+
expect(result).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
test('returns false when no Discord process is found', async () => {
|
|
272
|
+
const mockExecSync = execSync;
|
|
273
|
+
mockExecSync.mockImplementation(() => '');
|
|
274
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0);
|
|
275
|
+
const result = await darwinExtractor.isDiscordRunning('stable');
|
|
276
|
+
expect(result).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
test('checks all variants when no specific variant provided', async () => {
|
|
279
|
+
const mockExecSync = execSync;
|
|
280
|
+
const checkedProcesses = [];
|
|
281
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
282
|
+
if (cmd.includes('pgrep')) {
|
|
283
|
+
const match = cmd.match(/pgrep -f "([^"]+)"/);
|
|
284
|
+
if (match)
|
|
285
|
+
checkedProcesses.push(match[1]);
|
|
286
|
+
}
|
|
287
|
+
return '';
|
|
288
|
+
});
|
|
289
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0);
|
|
290
|
+
await darwinExtractor.isDiscordRunning();
|
|
291
|
+
expect(checkedProcesses).toContain('Discord');
|
|
292
|
+
expect(checkedProcesses).toContain('Discord Canary');
|
|
293
|
+
expect(checkedProcesses).toContain('Discord PTB');
|
|
294
|
+
});
|
|
295
|
+
test('returns true when Windows process is found', async () => {
|
|
296
|
+
const mockExecSync = execSync;
|
|
297
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
298
|
+
if (cmd.includes('tasklist') && cmd.includes('Discord.exe')) {
|
|
299
|
+
return 'Discord.exe 12345 Console 1 123,456 K\n';
|
|
300
|
+
}
|
|
301
|
+
return '';
|
|
302
|
+
});
|
|
303
|
+
const winExtractor = new DiscordTokenExtractor('win32', 0, 0);
|
|
304
|
+
const result = await winExtractor.isDiscordRunning('stable');
|
|
305
|
+
expect(result).toBe(true);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
describe('killDiscord', () => {
|
|
309
|
+
test('kills Discord process on macOS using pkill', async () => {
|
|
310
|
+
const mockExecSync = execSync;
|
|
311
|
+
const killedProcesses = [];
|
|
312
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
313
|
+
if (cmd.includes('pkill')) {
|
|
314
|
+
const match = cmd.match(/pkill -f "([^"]+)"/);
|
|
315
|
+
if (match)
|
|
316
|
+
killedProcesses.push(match[1]);
|
|
317
|
+
}
|
|
318
|
+
return '';
|
|
319
|
+
});
|
|
320
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0);
|
|
321
|
+
await darwinExtractor.killDiscord('stable');
|
|
322
|
+
expect(killedProcesses).toContain('Discord');
|
|
323
|
+
});
|
|
324
|
+
test('kills Discord process on Windows using taskkill', async () => {
|
|
325
|
+
const mockExecSync = execSync;
|
|
326
|
+
const killedProcesses = [];
|
|
327
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
328
|
+
if (cmd.includes('taskkill')) {
|
|
329
|
+
const match = cmd.match(/taskkill \/F \/IM "([^"]+)"/);
|
|
330
|
+
if (match)
|
|
331
|
+
killedProcesses.push(match[1]);
|
|
332
|
+
}
|
|
333
|
+
return '';
|
|
334
|
+
});
|
|
335
|
+
const winExtractor = new DiscordTokenExtractor('win32', 0, 0);
|
|
336
|
+
await winExtractor.killDiscord('stable');
|
|
337
|
+
expect(killedProcesses).toContain('Discord.exe');
|
|
338
|
+
});
|
|
339
|
+
test('kills all variants when no specific variant provided', async () => {
|
|
340
|
+
const mockExecSync = execSync;
|
|
341
|
+
const killedProcesses = [];
|
|
342
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
343
|
+
if (cmd.includes('pkill')) {
|
|
344
|
+
const match = cmd.match(/pkill -f "([^"]+)"/);
|
|
345
|
+
if (match)
|
|
346
|
+
killedProcesses.push(match[1]);
|
|
347
|
+
}
|
|
348
|
+
return '';
|
|
349
|
+
});
|
|
350
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0);
|
|
351
|
+
await darwinExtractor.killDiscord();
|
|
352
|
+
expect(killedProcesses).toContain('Discord');
|
|
353
|
+
expect(killedProcesses).toContain('Discord Canary');
|
|
354
|
+
expect(killedProcesses).toContain('Discord PTB');
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
describe('launchDiscordWithDebug', () => {
|
|
358
|
+
test('throws error when Discord app not found', async () => {
|
|
359
|
+
const mockExistsSync = existsSync;
|
|
360
|
+
mockExistsSync.mockImplementation(() => false);
|
|
361
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0);
|
|
362
|
+
await expect(darwinExtractor.launchDiscordWithDebug('stable')).rejects.toThrow('Discord stable not found');
|
|
363
|
+
});
|
|
364
|
+
test('launches Discord with remote debugging port on macOS', async () => {
|
|
365
|
+
const mockExistsSync = existsSync;
|
|
366
|
+
mockExistsSync.mockImplementation((path) => {
|
|
367
|
+
return path.includes('/Applications/Discord.app');
|
|
368
|
+
});
|
|
369
|
+
const mockExecSync = execSync;
|
|
370
|
+
mockExecSync.mockImplementation(() => '');
|
|
371
|
+
let spawnedPath = '';
|
|
372
|
+
let spawnedArgs = [];
|
|
373
|
+
const mockSpawnFn = spawn;
|
|
374
|
+
mockSpawnFn.mockImplementation((path, args) => {
|
|
375
|
+
spawnedPath = path;
|
|
376
|
+
spawnedArgs = args;
|
|
377
|
+
return { unref: () => { } };
|
|
378
|
+
});
|
|
379
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0);
|
|
380
|
+
await darwinExtractor.launchDiscordWithDebug('stable', 9222);
|
|
381
|
+
expect(spawnedPath).toBe('/Applications/Discord.app/Contents/MacOS/Discord');
|
|
382
|
+
expect(spawnedArgs).toContain('--remote-debugging-port=9222');
|
|
383
|
+
});
|
|
384
|
+
test('uses default CDP port when not specified', async () => {
|
|
385
|
+
const mockExistsSync = existsSync;
|
|
386
|
+
mockExistsSync.mockImplementation(() => true);
|
|
387
|
+
const mockExecSync = execSync;
|
|
388
|
+
mockExecSync.mockImplementation(() => '');
|
|
389
|
+
let spawnedArgs = [];
|
|
390
|
+
const mockSpawnFn = spawn;
|
|
391
|
+
mockSpawnFn.mockImplementation((_path, args) => {
|
|
392
|
+
spawnedArgs = args;
|
|
393
|
+
return { unref: () => { } };
|
|
394
|
+
});
|
|
395
|
+
const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0);
|
|
396
|
+
await darwinExtractor.launchDiscordWithDebug('stable');
|
|
397
|
+
expect(spawnedArgs).toContain(`--remote-debugging-port=${CDP_PORT}`);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
describe('CDP client methods', () => {
|
|
402
|
+
describe('discoverCDPTargets', () => {
|
|
403
|
+
test('returns empty array when CDP endpoint is not reachable', async () => {
|
|
404
|
+
const originalFetch = globalThis.fetch;
|
|
405
|
+
globalThis.fetch = mock(async () => {
|
|
406
|
+
throw new Error('Connection refused');
|
|
407
|
+
});
|
|
408
|
+
try {
|
|
409
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
410
|
+
const targets = await extractor.discoverCDPTargets(19999);
|
|
411
|
+
expect(targets).toEqual([]);
|
|
412
|
+
}
|
|
413
|
+
finally {
|
|
414
|
+
globalThis.fetch = originalFetch;
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
test('returns targets from CDP endpoint', async () => {
|
|
418
|
+
const mockTargets = [
|
|
419
|
+
{
|
|
420
|
+
id: '1',
|
|
421
|
+
type: 'page',
|
|
422
|
+
title: 'Discord',
|
|
423
|
+
url: 'https://discord.com/app',
|
|
424
|
+
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
|
|
425
|
+
},
|
|
426
|
+
];
|
|
427
|
+
const originalFetch = globalThis.fetch;
|
|
428
|
+
globalThis.fetch = mock(async () => ({
|
|
429
|
+
ok: true,
|
|
430
|
+
json: async () => mockTargets,
|
|
431
|
+
}));
|
|
432
|
+
try {
|
|
433
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
434
|
+
const targets = await extractor.discoverCDPTargets(9222);
|
|
435
|
+
expect(targets).toEqual(mockTargets);
|
|
436
|
+
}
|
|
437
|
+
finally {
|
|
438
|
+
globalThis.fetch = originalFetch;
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
test('returns empty array on HTTP error', async () => {
|
|
442
|
+
const originalFetch = globalThis.fetch;
|
|
443
|
+
globalThis.fetch = mock(async () => ({
|
|
444
|
+
ok: false,
|
|
445
|
+
status: 500,
|
|
446
|
+
}));
|
|
447
|
+
try {
|
|
448
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
449
|
+
const targets = await extractor.discoverCDPTargets(9222);
|
|
450
|
+
expect(targets).toEqual([]);
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
globalThis.fetch = originalFetch;
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
describe('findDiscordPageTarget', () => {
|
|
458
|
+
test('finds target by discord.com URL', () => {
|
|
459
|
+
const targets = [
|
|
460
|
+
{
|
|
461
|
+
id: '1',
|
|
462
|
+
type: 'page',
|
|
463
|
+
title: 'Discord',
|
|
464
|
+
url: 'https://discord.com/app',
|
|
465
|
+
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
id: '2',
|
|
469
|
+
type: 'background_page',
|
|
470
|
+
title: 'background',
|
|
471
|
+
url: 'about:blank',
|
|
472
|
+
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/2',
|
|
473
|
+
},
|
|
474
|
+
];
|
|
475
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
476
|
+
const target = extractor.findDiscordPageTarget(targets);
|
|
477
|
+
expect(target).not.toBeNull();
|
|
478
|
+
expect(target?.id).toBe('1');
|
|
479
|
+
});
|
|
480
|
+
test('finds target by Discord title', () => {
|
|
481
|
+
const targets = [
|
|
482
|
+
{
|
|
483
|
+
id: '1',
|
|
484
|
+
type: 'page',
|
|
485
|
+
title: 'Discord - Chat',
|
|
486
|
+
url: 'https://app.discord.com/channels',
|
|
487
|
+
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
|
|
488
|
+
},
|
|
489
|
+
];
|
|
490
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
491
|
+
const target = extractor.findDiscordPageTarget(targets);
|
|
492
|
+
expect(target).not.toBeNull();
|
|
493
|
+
expect(target?.id).toBe('1');
|
|
494
|
+
});
|
|
495
|
+
test('returns null when no Discord page found', () => {
|
|
496
|
+
const targets = [
|
|
497
|
+
{
|
|
498
|
+
id: '1',
|
|
499
|
+
type: 'background_page',
|
|
500
|
+
title: 'background',
|
|
501
|
+
url: 'about:blank',
|
|
502
|
+
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
|
|
503
|
+
},
|
|
504
|
+
];
|
|
505
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
506
|
+
const target = extractor.findDiscordPageTarget(targets);
|
|
507
|
+
expect(target).toBeNull();
|
|
508
|
+
});
|
|
509
|
+
test('returns null for empty targets', () => {
|
|
510
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
511
|
+
const target = extractor.findDiscordPageTarget([]);
|
|
512
|
+
expect(target).toBeNull();
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
describe('executeJSViaCDP', () => {
|
|
516
|
+
test('executes JavaScript and returns result', async () => {
|
|
517
|
+
const mockToken = 'test_token_12345';
|
|
518
|
+
const mockWebSocket = class {
|
|
519
|
+
onopen = null;
|
|
520
|
+
onmessage = null;
|
|
521
|
+
onerror = null;
|
|
522
|
+
constructor() {
|
|
523
|
+
setTimeout(() => {
|
|
524
|
+
this.onopen?.();
|
|
525
|
+
}, 10);
|
|
526
|
+
}
|
|
527
|
+
send(data) {
|
|
528
|
+
const message = JSON.parse(data);
|
|
529
|
+
setTimeout(() => {
|
|
530
|
+
this.onmessage?.({
|
|
531
|
+
data: JSON.stringify({
|
|
532
|
+
id: message.id,
|
|
533
|
+
result: { result: { value: mockToken } },
|
|
534
|
+
}),
|
|
535
|
+
});
|
|
536
|
+
}, 10);
|
|
537
|
+
}
|
|
538
|
+
close() { }
|
|
539
|
+
};
|
|
540
|
+
const originalWebSocket = globalThis.WebSocket;
|
|
541
|
+
globalThis.WebSocket = mockWebSocket;
|
|
542
|
+
try {
|
|
543
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
544
|
+
const result = await extractor.executeJSViaCDP('ws://localhost:9222/devtools/page/1', TOKEN_EXTRACTION_JS);
|
|
545
|
+
expect(result).toBe(mockToken);
|
|
546
|
+
}
|
|
547
|
+
finally {
|
|
548
|
+
globalThis.WebSocket = originalWebSocket;
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
test('rejects on CDP error response', async () => {
|
|
552
|
+
const mockWebSocket = class {
|
|
553
|
+
onopen = null;
|
|
554
|
+
onmessage = null;
|
|
555
|
+
onerror = null;
|
|
556
|
+
constructor() {
|
|
557
|
+
setTimeout(() => {
|
|
558
|
+
this.onopen?.();
|
|
559
|
+
}, 10);
|
|
560
|
+
}
|
|
561
|
+
send(data) {
|
|
562
|
+
const message = JSON.parse(data);
|
|
563
|
+
setTimeout(() => {
|
|
564
|
+
this.onmessage?.({
|
|
565
|
+
data: JSON.stringify({
|
|
566
|
+
id: message.id,
|
|
567
|
+
error: { code: -32000, message: 'Evaluation failed' },
|
|
568
|
+
}),
|
|
569
|
+
});
|
|
570
|
+
}, 10);
|
|
571
|
+
}
|
|
572
|
+
close() { }
|
|
573
|
+
};
|
|
574
|
+
const originalWebSocket = globalThis.WebSocket;
|
|
575
|
+
globalThis.WebSocket = mockWebSocket;
|
|
576
|
+
try {
|
|
577
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
578
|
+
await expect(extractor.executeJSViaCDP('ws://localhost:9222/devtools/page/1', TOKEN_EXTRACTION_JS)).rejects.toThrow('Evaluation failed');
|
|
579
|
+
}
|
|
580
|
+
finally {
|
|
581
|
+
globalThis.WebSocket = originalWebSocket;
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
test('rejects on WebSocket error', async () => {
|
|
585
|
+
const mockWebSocket = class {
|
|
586
|
+
onopen = null;
|
|
587
|
+
onmessage = null;
|
|
588
|
+
onerror = null;
|
|
589
|
+
constructor() {
|
|
590
|
+
setTimeout(() => {
|
|
591
|
+
this.onerror?.(new Error('Connection failed'));
|
|
592
|
+
}, 10);
|
|
593
|
+
}
|
|
594
|
+
send() { }
|
|
595
|
+
close() { }
|
|
596
|
+
};
|
|
597
|
+
const originalWebSocket = globalThis.WebSocket;
|
|
598
|
+
globalThis.WebSocket = mockWebSocket;
|
|
599
|
+
try {
|
|
600
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
601
|
+
await expect(extractor.executeJSViaCDP('ws://localhost:9222/devtools/page/1', TOKEN_EXTRACTION_JS)).rejects.toThrow();
|
|
602
|
+
}
|
|
603
|
+
finally {
|
|
604
|
+
globalThis.WebSocket = originalWebSocket;
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
describe('extractViaCDP', () => {
|
|
609
|
+
test('returns null when no CDP targets available', async () => {
|
|
610
|
+
const originalFetch = globalThis.fetch;
|
|
611
|
+
globalThis.fetch = mock(async () => ({
|
|
612
|
+
ok: true,
|
|
613
|
+
json: async () => [],
|
|
614
|
+
}));
|
|
615
|
+
try {
|
|
616
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
617
|
+
const result = await extractor.extractViaCDP(9222);
|
|
618
|
+
expect(result).toBeNull();
|
|
619
|
+
}
|
|
620
|
+
finally {
|
|
621
|
+
globalThis.fetch = originalFetch;
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
test('returns null when no Discord page target found', async () => {
|
|
625
|
+
const originalFetch = globalThis.fetch;
|
|
626
|
+
globalThis.fetch = mock(async () => ({
|
|
627
|
+
ok: true,
|
|
628
|
+
json: async () => [
|
|
629
|
+
{
|
|
630
|
+
id: '1',
|
|
631
|
+
type: 'background_page',
|
|
632
|
+
title: 'background',
|
|
633
|
+
url: 'about:blank',
|
|
634
|
+
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
|
|
635
|
+
},
|
|
636
|
+
],
|
|
637
|
+
}));
|
|
638
|
+
try {
|
|
639
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
640
|
+
const result = await extractor.extractViaCDP(9222);
|
|
641
|
+
expect(result).toBeNull();
|
|
642
|
+
}
|
|
643
|
+
finally {
|
|
644
|
+
globalThis.fetch = originalFetch;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
test('extracts token via CDP when Discord is running with debug port', async () => {
|
|
648
|
+
const mockToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.abcdefghijklmnopqrstuvwxyz1234567890';
|
|
649
|
+
const originalFetch = globalThis.fetch;
|
|
650
|
+
globalThis.fetch = mock(async () => ({
|
|
651
|
+
ok: true,
|
|
652
|
+
json: async () => [
|
|
653
|
+
{
|
|
654
|
+
id: '1',
|
|
655
|
+
type: 'page',
|
|
656
|
+
title: 'Discord',
|
|
657
|
+
url: 'https://discord.com/app',
|
|
658
|
+
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
|
|
659
|
+
},
|
|
660
|
+
],
|
|
661
|
+
}));
|
|
662
|
+
const mockWebSocket = class {
|
|
663
|
+
onopen = null;
|
|
664
|
+
onmessage = null;
|
|
665
|
+
onerror = null;
|
|
666
|
+
constructor() {
|
|
667
|
+
setTimeout(() => this.onopen?.(), 10);
|
|
668
|
+
}
|
|
669
|
+
send(data) {
|
|
670
|
+
const message = JSON.parse(data);
|
|
671
|
+
setTimeout(() => {
|
|
672
|
+
this.onmessage?.({
|
|
673
|
+
data: JSON.stringify({
|
|
674
|
+
id: message.id,
|
|
675
|
+
result: { result: { value: mockToken } },
|
|
676
|
+
}),
|
|
677
|
+
});
|
|
678
|
+
}, 10);
|
|
679
|
+
}
|
|
680
|
+
close() { }
|
|
681
|
+
};
|
|
682
|
+
const originalWebSocket = globalThis.WebSocket;
|
|
683
|
+
globalThis.WebSocket = mockWebSocket;
|
|
684
|
+
try {
|
|
685
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
686
|
+
const result = await extractor.extractViaCDP(9222);
|
|
687
|
+
expect(result).toBe(mockToken);
|
|
688
|
+
}
|
|
689
|
+
finally {
|
|
690
|
+
globalThis.fetch = originalFetch;
|
|
691
|
+
globalThis.WebSocket = originalWebSocket;
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
test('returns null when token extraction JS fails', async () => {
|
|
695
|
+
const originalFetch = globalThis.fetch;
|
|
696
|
+
globalThis.fetch = mock(async () => ({
|
|
697
|
+
ok: true,
|
|
698
|
+
json: async () => [
|
|
699
|
+
{
|
|
700
|
+
id: '1',
|
|
701
|
+
type: 'page',
|
|
702
|
+
title: 'Discord',
|
|
703
|
+
url: 'https://discord.com/app',
|
|
704
|
+
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
|
|
705
|
+
},
|
|
706
|
+
],
|
|
707
|
+
}));
|
|
708
|
+
const mockWebSocket = class {
|
|
709
|
+
onopen = null;
|
|
710
|
+
onmessage = null;
|
|
711
|
+
onerror = null;
|
|
712
|
+
constructor() {
|
|
713
|
+
setTimeout(() => this.onopen?.(), 10);
|
|
714
|
+
}
|
|
715
|
+
send(data) {
|
|
716
|
+
const message = JSON.parse(data);
|
|
717
|
+
setTimeout(() => {
|
|
718
|
+
this.onmessage?.({
|
|
719
|
+
data: JSON.stringify({
|
|
720
|
+
id: message.id,
|
|
721
|
+
error: { code: -32000, message: 'Cannot find module' },
|
|
722
|
+
}),
|
|
723
|
+
});
|
|
724
|
+
}, 10);
|
|
725
|
+
}
|
|
726
|
+
close() { }
|
|
727
|
+
};
|
|
728
|
+
const originalWebSocket = globalThis.WebSocket;
|
|
729
|
+
globalThis.WebSocket = mockWebSocket;
|
|
730
|
+
try {
|
|
731
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
732
|
+
const result = await extractor.extractViaCDP(9222);
|
|
733
|
+
expect(result).toBeNull();
|
|
734
|
+
}
|
|
735
|
+
finally {
|
|
736
|
+
globalThis.fetch = originalFetch;
|
|
737
|
+
globalThis.WebSocket = originalWebSocket;
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
test('returns null when returned value is not a valid token', async () => {
|
|
741
|
+
const originalFetch = globalThis.fetch;
|
|
742
|
+
globalThis.fetch = mock(async () => ({
|
|
743
|
+
ok: true,
|
|
744
|
+
json: async () => [
|
|
745
|
+
{
|
|
746
|
+
id: '1',
|
|
747
|
+
type: 'page',
|
|
748
|
+
title: 'Discord',
|
|
749
|
+
url: 'https://discord.com/app',
|
|
750
|
+
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
|
|
751
|
+
},
|
|
752
|
+
],
|
|
753
|
+
}));
|
|
754
|
+
const mockWebSocket = class {
|
|
755
|
+
onopen = null;
|
|
756
|
+
onmessage = null;
|
|
757
|
+
onerror = null;
|
|
758
|
+
constructor() {
|
|
759
|
+
setTimeout(() => this.onopen?.(), 10);
|
|
760
|
+
}
|
|
761
|
+
send(data) {
|
|
762
|
+
const message = JSON.parse(data);
|
|
763
|
+
setTimeout(() => {
|
|
764
|
+
this.onmessage?.({
|
|
765
|
+
data: JSON.stringify({
|
|
766
|
+
id: message.id,
|
|
767
|
+
result: { result: { value: 'not_a_valid_token' } },
|
|
768
|
+
}),
|
|
769
|
+
});
|
|
770
|
+
}, 10);
|
|
771
|
+
}
|
|
772
|
+
close() { }
|
|
773
|
+
};
|
|
774
|
+
const originalWebSocket = globalThis.WebSocket;
|
|
775
|
+
globalThis.WebSocket = mockWebSocket;
|
|
776
|
+
try {
|
|
777
|
+
const extractor = new DiscordTokenExtractor('darwin');
|
|
778
|
+
const result = await extractor.extractViaCDP(9222);
|
|
779
|
+
expect(result).toBeNull();
|
|
780
|
+
}
|
|
781
|
+
finally {
|
|
782
|
+
globalThis.fetch = originalFetch;
|
|
783
|
+
globalThis.WebSocket = originalWebSocket;
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
//# sourceMappingURL=token-extractor.test.js.map
|