agent-messenger 2.2.0 → 2.4.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/CONTRIBUTING.md +1 -1
- package/README.md +9 -6
- package/bun.lock +89 -105
- package/bunfig.toml +3 -0
- package/dist/package.json +13 -3
- package/dist/src/platforms/discordbot/client.js +2 -2
- package/dist/src/platforms/discordbot/client.js.map +1 -1
- 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 +52 -2
- 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/profile.d.ts +3 -0
- package/dist/src/platforms/kakaotalk/commands/profile.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/commands/profile.js +19 -0
- package/dist/src/platforms/kakaotalk/commands/profile.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.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.js +2 -1
- package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +16 -0
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +8 -0
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/auth.js +32 -20
- package/dist/src/platforms/line/commands/auth.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/webex/client.d.ts +2 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -1
- package/dist/src/platforms/webex/client.js +66 -23
- package/dist/src/platforms/webex/client.js.map +1 -1
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/auth.js +4 -0
- package/dist/src/platforms/webex/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/encryption.d.ts +10 -0
- package/dist/src/platforms/webex/encryption.d.ts.map +1 -0
- package/dist/src/platforms/webex/encryption.js +49 -0
- package/dist/src/platforms/webex/encryption.js.map +1 -0
- package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/ensure-auth.js +4 -0
- package/dist/src/platforms/webex/ensure-auth.js.map +1 -1
- package/dist/src/platforms/webex/token-extractor.d.ts +6 -5
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/webex/token-extractor.js +92 -43
- package/dist/src/platforms/webex/token-extractor.js.map +1 -1
- package/dist/src/platforms/webex/types.d.ts +4 -0
- package/dist/src/platforms/webex/types.d.ts.map +1 -1
- package/dist/src/platforms/webex/types.js +2 -0
- package/dist/src/platforms/webex/types.js.map +1 -1
- 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 +18 -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 +5 -0
- package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/index.js +5 -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/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/client.d.ts +1 -0
- package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.js +27 -13
- 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 +21 -18
- package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
- 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/docs/content/docs/agent-skills.mdx +4 -4
- package/docs/content/docs/cli/channeltalk.mdx +1 -1
- package/docs/content/docs/cli/channeltalkbot.mdx +1 -1
- package/docs/content/docs/cli/discord.mdx +1 -1
- package/docs/content/docs/cli/discordbot.mdx +1 -1
- package/docs/content/docs/cli/instagram.mdx +1 -1
- package/docs/content/docs/cli/kakaotalk.mdx +1 -1
- package/docs/content/docs/cli/line.mdx +1 -1
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/slack.mdx +1 -1
- package/docs/content/docs/cli/slackbot.mdx +1 -1
- package/docs/content/docs/cli/teams.mdx +1 -1
- package/docs/content/docs/cli/webex.mdx +5 -3
- package/docs/content/docs/cli/wechatbot.mdx +179 -0
- package/docs/content/docs/cli/whatsapp.mdx +1 -1
- package/docs/content/docs/cli/whatsappbot.mdx +1 -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 +13 -3
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +24 -1
- package/skills/agent-line/SKILL.md +7 -11
- package/skills/agent-line/references/authentication.md +13 -4
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webex/references/authentication.md +4 -3
- package/skills/agent-webex/references/common-patterns.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +385 -0
- package/skills/agent-whatsapp/SKILL.md +12 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/discord/credential-manager.test.ts +18 -1
- package/src/platforms/discordbot/client.ts +2 -2
- package/src/platforms/instagram/commands/auth.test.ts +216 -0
- package/src/platforms/instagram/commands/chat.test.ts +127 -0
- package/src/platforms/instagram/commands/message.test.ts +178 -0
- package/src/platforms/kakaotalk/cli.ts +2 -1
- package/src/platforms/kakaotalk/client.test.ts +157 -0
- package/src/platforms/kakaotalk/client.ts +57 -3
- 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/profile.test.ts +84 -0
- package/src/platforms/kakaotalk/commands/profile.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 +2 -0
- package/src/platforms/kakaotalk/types.ts +18 -0
- package/src/platforms/line/commands/auth.test.ts +141 -0
- package/src/platforms/line/commands/auth.ts +28 -19
- 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/message.test.ts +119 -0
- package/src/platforms/line/commands/profile.test.ts +85 -0
- package/src/platforms/slackbot/commands/channel.test.ts +139 -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/teams/commands/reaction.test.ts +45 -61
- package/src/platforms/teams/commands/reaction.ts +2 -0
- package/src/platforms/telegram/commands/chat.test.ts +125 -0
- package/src/platforms/telegram/commands/message.test.ts +92 -0
- package/src/platforms/webex/client.ts +98 -26
- package/src/platforms/webex/commands/auth.ts +4 -0
- package/src/platforms/webex/commands/member.test.ts +65 -58
- package/src/platforms/webex/commands/message.test.ts +78 -121
- package/src/platforms/webex/commands/snapshot.test.ts +59 -46
- package/src/platforms/webex/commands/space.test.ts +49 -48
- package/src/platforms/webex/encryption.ts +53 -0
- package/src/platforms/webex/ensure-auth.ts +4 -0
- package/src/platforms/webex/token-extractor.ts +107 -40
- package/src/platforms/webex/types.ts +4 -0
- package/src/platforms/webex/typings/node-jose.d.ts +27 -0
- package/src/platforms/wechatbot/cli.ts +24 -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 +4 -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/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/client.ts +24 -13
- package/src/platforms/whatsapp/commands/auth.test.ts +311 -0
- package/src/platforms/whatsapp/commands/auth.ts +21 -17
- package/src/platforms/whatsapp/commands/chat.test.ts +198 -0
- package/src/platforms/whatsapp/commands/message.test.ts +231 -0
- package/src/platforms/whatsapp/credential-manager.test.ts +20 -0
- package/src/platforms/whatsapp/credential-manager.ts +17 -8
- package/src/platforms/whatsappbot/commands/auth.test.ts +217 -0
- package/src/platforms/whatsappbot/commands/message.test.ts +198 -0
- package/src/platforms/whatsappbot/commands/template.test.ts +112 -0
|
@@ -1,89 +1,73 @@
|
|
|
1
|
-
import { afterEach, beforeEach, expect,
|
|
1
|
+
import { afterEach, beforeEach, expect, spyOn, test } from 'bun:test'
|
|
2
2
|
|
|
3
3
|
import { TeamsClient } from '../client'
|
|
4
4
|
import { TeamsCredentialManager } from '../credential-manager'
|
|
5
5
|
import { addAction, removeAction } from './reaction'
|
|
6
6
|
|
|
7
|
-
let
|
|
8
|
-
let
|
|
9
|
-
let
|
|
7
|
+
let addReactionSpy: ReturnType<typeof spyOn>
|
|
8
|
+
let removeReactionSpy: ReturnType<typeof spyOn>
|
|
9
|
+
let getTokenWithExpirySpy: ReturnType<typeof spyOn>
|
|
10
|
+
let consoleLogSpy: ReturnType<typeof spyOn>
|
|
11
|
+
let processExitSpy: ReturnType<typeof spyOn>
|
|
10
12
|
|
|
11
13
|
beforeEach(() => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
teams: {},
|
|
22
|
-
},
|
|
23
|
-
},
|
|
14
|
+
addReactionSpy = spyOn(TeamsClient.prototype, 'addReaction').mockResolvedValue(undefined)
|
|
15
|
+
removeReactionSpy = spyOn(TeamsClient.prototype, 'removeReaction').mockResolvedValue(undefined)
|
|
16
|
+
getTokenWithExpirySpy = spyOn(TeamsCredentialManager.prototype, 'getTokenWithExpiry').mockImplementation(() =>
|
|
17
|
+
Promise.resolve({ token: 'test-token', tokenExpiresAt: undefined }),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
consoleLogSpy = spyOn(console, 'log').mockImplementation(() => {})
|
|
21
|
+
processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => {
|
|
22
|
+
throw new Error(`process.exit(${_code})`)
|
|
24
23
|
})
|
|
25
24
|
})
|
|
26
25
|
|
|
27
26
|
afterEach(() => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
addReactionSpy.mockRestore()
|
|
28
|
+
removeReactionSpy.mockRestore()
|
|
29
|
+
getTokenWithExpirySpy.mockRestore()
|
|
30
|
+
consoleLogSpy.mockRestore()
|
|
31
|
+
processExitSpy.mockRestore()
|
|
31
32
|
})
|
|
32
33
|
|
|
33
34
|
test('add: sends correct POST request with emoji', async () => {
|
|
34
|
-
const consoleSpy = mock((_msg: string) => {})
|
|
35
|
-
const originalLog = console.log
|
|
36
|
-
console.log = consoleSpy
|
|
37
|
-
|
|
38
35
|
try {
|
|
39
36
|
await addAction('team123', 'ch123', 'msg123', 'like', { pretty: false })
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
37
|
+
} catch {}
|
|
38
|
+
|
|
39
|
+
expect(consoleLogSpy).toHaveBeenCalled()
|
|
40
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
41
|
+
expect(output.success).toBe(true)
|
|
42
|
+
expect(output.team_id).toBe('team123')
|
|
43
|
+
expect(output.channel_id).toBe('ch123')
|
|
44
|
+
expect(output.message_id).toBe('msg123')
|
|
45
|
+
expect(output.emoji).toBe('like')
|
|
50
46
|
})
|
|
51
47
|
|
|
52
48
|
test('remove: sends correct DELETE request with emoji', async () => {
|
|
53
|
-
const consoleSpy = mock((_msg: string) => {})
|
|
54
|
-
const originalLog = console.log
|
|
55
|
-
console.log = consoleSpy
|
|
56
|
-
|
|
57
49
|
try {
|
|
58
50
|
await removeAction('team123', 'ch123', 'msg123', 'like', { pretty: false })
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
51
|
+
} catch {}
|
|
52
|
+
|
|
53
|
+
expect(consoleLogSpy).toHaveBeenCalled()
|
|
54
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
55
|
+
expect(output.success).toBe(true)
|
|
56
|
+
expect(output.team_id).toBe('team123')
|
|
57
|
+
expect(output.channel_id).toBe('ch123')
|
|
58
|
+
expect(output.message_id).toBe('msg123')
|
|
59
|
+
expect(output.emoji).toBe('like')
|
|
69
60
|
})
|
|
70
61
|
|
|
71
62
|
test('add: handles missing token gracefully', async () => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const consoleSpy = mock((_msg: string) => {})
|
|
75
|
-
const originalLog = console.log
|
|
76
|
-
const originalExit = process.exit
|
|
77
|
-
process.exit = mock(() => {}) as any
|
|
78
|
-
console.log = consoleSpy
|
|
63
|
+
getTokenWithExpirySpy.mockImplementation(() => Promise.resolve(null))
|
|
79
64
|
|
|
80
65
|
try {
|
|
81
66
|
await addAction('team123', 'ch123', 'msg123', 'like', { pretty: false })
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
67
|
+
} catch {}
|
|
68
|
+
|
|
69
|
+
expect(consoleLogSpy).toHaveBeenCalled()
|
|
70
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
71
|
+
expect(output.error).toBeDefined()
|
|
72
|
+
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
89
73
|
})
|
|
@@ -20,6 +20,7 @@ export async function addAction(
|
|
|
20
20
|
if (!cred) {
|
|
21
21
|
console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
|
|
22
22
|
process.exit(1)
|
|
23
|
+
return
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
|
|
@@ -56,6 +57,7 @@ export async function removeAction(
|
|
|
56
57
|
if (!cred) {
|
|
57
58
|
console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
|
|
58
59
|
process.exit(1)
|
|
60
|
+
return
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
const mockListChats = mock(() =>
|
|
4
|
+
Promise.resolve([
|
|
5
|
+
{ id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 },
|
|
6
|
+
{ id: 'chat-2', title: 'Random', type: 'group', unread_count: 5 },
|
|
7
|
+
]),
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
const mockSearchChats = mock(() =>
|
|
11
|
+
Promise.resolve([{ id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 }]),
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
const mockGetChat = mock(() =>
|
|
15
|
+
Promise.resolve({ id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 }),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const mockClient = {
|
|
19
|
+
listChats: mockListChats,
|
|
20
|
+
searchChats: mockSearchChats,
|
|
21
|
+
getChat: mockGetChat,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
mock.module('./shared', () => ({
|
|
25
|
+
withTelegramClient: async (_opts: unknown, fn: (client: typeof mockClient) => Promise<unknown>) =>
|
|
26
|
+
fn(mockClient),
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
import { chatCommand } from './chat'
|
|
30
|
+
|
|
31
|
+
describe('chat commands', () => {
|
|
32
|
+
let consoleSpy: ReturnType<typeof spyOn>
|
|
33
|
+
let processExitSpy: ReturnType<typeof spyOn>
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
mockListChats.mockReset()
|
|
37
|
+
mockListChats.mockImplementation(() =>
|
|
38
|
+
Promise.resolve([
|
|
39
|
+
{ id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 },
|
|
40
|
+
{ id: 'chat-2', title: 'Random', type: 'group', unread_count: 5 },
|
|
41
|
+
]),
|
|
42
|
+
)
|
|
43
|
+
mockSearchChats.mockReset()
|
|
44
|
+
mockSearchChats.mockImplementation(() =>
|
|
45
|
+
Promise.resolve([{ id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 }]),
|
|
46
|
+
)
|
|
47
|
+
mockGetChat.mockReset()
|
|
48
|
+
mockGetChat.mockImplementation(() =>
|
|
49
|
+
Promise.resolve({ id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 }),
|
|
50
|
+
)
|
|
51
|
+
consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
|
|
52
|
+
processExitSpy = spyOn(process, 'exit').mockImplementation((() => {}) as (code?: number) => never)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
consoleSpy.mockRestore()
|
|
57
|
+
processExitSpy.mockRestore()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('list subcommand', () => {
|
|
61
|
+
test('calls listChats with default limit', async () => {
|
|
62
|
+
await chatCommand.parseAsync(['list'], { from: 'user' })
|
|
63
|
+
|
|
64
|
+
expect(mockListChats).toHaveBeenCalledWith(20)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('calls listChats with custom limit', async () => {
|
|
68
|
+
await chatCommand.parseAsync(['list', '--limit', '5'], { from: 'user' })
|
|
69
|
+
|
|
70
|
+
expect(mockListChats).toHaveBeenCalledWith(5)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('outputs JSON array to console', async () => {
|
|
74
|
+
await chatCommand.parseAsync(['list'], { from: 'user' })
|
|
75
|
+
|
|
76
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
77
|
+
const output = consoleSpy.mock.calls[0][0] as string
|
|
78
|
+
const parsed = JSON.parse(output)
|
|
79
|
+
expect(parsed).toBeArray()
|
|
80
|
+
expect(parsed).toHaveLength(2)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('search subcommand', () => {
|
|
85
|
+
test('calls searchChats with query and default limit', async () => {
|
|
86
|
+
await chatCommand.parseAsync(['search', 'General'], { from: 'user' })
|
|
87
|
+
|
|
88
|
+
expect(mockSearchChats).toHaveBeenCalledWith('General', 20)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('calls searchChats with query and custom limit', async () => {
|
|
92
|
+
await chatCommand.parseAsync(['search', 'General', '--limit', '3'], { from: 'user' })
|
|
93
|
+
|
|
94
|
+
expect(mockSearchChats).toHaveBeenCalledWith('General', 3)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('outputs JSON array to console', async () => {
|
|
98
|
+
await chatCommand.parseAsync(['search', 'General'], { from: 'user' })
|
|
99
|
+
|
|
100
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
101
|
+
const output = consoleSpy.mock.calls[0][0] as string
|
|
102
|
+
const parsed = JSON.parse(output)
|
|
103
|
+
expect(parsed).toBeArray()
|
|
104
|
+
expect(parsed).toHaveLength(1)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('get subcommand', () => {
|
|
109
|
+
test('calls getChat with chat reference', async () => {
|
|
110
|
+
await chatCommand.parseAsync(['get', 'chat-1'], { from: 'user' })
|
|
111
|
+
|
|
112
|
+
expect(mockGetChat).toHaveBeenCalledWith('chat-1')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('outputs JSON object to console', async () => {
|
|
116
|
+
await chatCommand.parseAsync(['get', 'chat-1'], { from: 'user' })
|
|
117
|
+
|
|
118
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
119
|
+
const output = consoleSpy.mock.calls[0][0] as string
|
|
120
|
+
const parsed = JSON.parse(output)
|
|
121
|
+
expect(parsed).toBeObject()
|
|
122
|
+
expect(parsed.id).toBe('chat-1')
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
const mockListMessages = mock(() =>
|
|
4
|
+
Promise.resolve([
|
|
5
|
+
{ id: 1, text: 'Hello', sender_id: 'user-1', date: 1000 },
|
|
6
|
+
{ id: 2, text: 'World', sender_id: 'user-2', date: 2000 },
|
|
7
|
+
]),
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
const mockSendMessage = mock(() =>
|
|
11
|
+
Promise.resolve({ id: 3, text: 'Sent message', sender_id: 'user-1', date: 3000 }),
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
const mockClient = {
|
|
15
|
+
listMessages: mockListMessages,
|
|
16
|
+
sendMessage: mockSendMessage,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
mock.module('./shared', () => ({
|
|
20
|
+
withTelegramClient: async (_opts: unknown, fn: (client: typeof mockClient) => Promise<unknown>) =>
|
|
21
|
+
fn(mockClient),
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
import { messageCommand } from './message'
|
|
25
|
+
|
|
26
|
+
describe('message commands', () => {
|
|
27
|
+
let consoleSpy: ReturnType<typeof spyOn>
|
|
28
|
+
let processExitSpy: ReturnType<typeof spyOn>
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
mockListMessages.mockReset()
|
|
32
|
+
mockListMessages.mockImplementation(() =>
|
|
33
|
+
Promise.resolve([
|
|
34
|
+
{ id: 1, text: 'Hello', sender_id: 'user-1', date: 1000 },
|
|
35
|
+
{ id: 2, text: 'World', sender_id: 'user-2', date: 2000 },
|
|
36
|
+
]),
|
|
37
|
+
)
|
|
38
|
+
mockSendMessage.mockReset()
|
|
39
|
+
mockSendMessage.mockImplementation(() =>
|
|
40
|
+
Promise.resolve({ id: 3, text: 'Sent message', sender_id: 'user-1', date: 3000 }),
|
|
41
|
+
)
|
|
42
|
+
consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
|
|
43
|
+
processExitSpy = spyOn(process, 'exit').mockImplementation((() => {}) as (code?: number) => never)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
consoleSpy.mockRestore()
|
|
48
|
+
processExitSpy.mockRestore()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('list subcommand', () => {
|
|
52
|
+
test('calls listMessages with chat reference and default limit', async () => {
|
|
53
|
+
await messageCommand.parseAsync(['list', 'chat-123'], { from: 'user' })
|
|
54
|
+
|
|
55
|
+
expect(mockListMessages).toHaveBeenCalledWith('chat-123', 20)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('calls listMessages with custom limit', async () => {
|
|
59
|
+
await messageCommand.parseAsync(['list', 'chat-123', '--limit', '10'], { from: 'user' })
|
|
60
|
+
|
|
61
|
+
expect(mockListMessages).toHaveBeenCalledWith('chat-123', 10)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('outputs JSON to console', async () => {
|
|
65
|
+
await messageCommand.parseAsync(['list', 'chat-123'], { from: 'user' })
|
|
66
|
+
|
|
67
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
68
|
+
const output = consoleSpy.mock.calls[0][0] as string
|
|
69
|
+
const parsed = JSON.parse(output)
|
|
70
|
+
expect(parsed).toBeArray()
|
|
71
|
+
expect(parsed).toHaveLength(2)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('send subcommand', () => {
|
|
76
|
+
test('calls sendMessage with chat reference and text', async () => {
|
|
77
|
+
await messageCommand.parseAsync(['send', 'chat-123', 'Hello there'], { from: 'user' })
|
|
78
|
+
|
|
79
|
+
expect(mockSendMessage).toHaveBeenCalledWith('chat-123', 'Hello there')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('outputs JSON to console', async () => {
|
|
83
|
+
await messageCommand.parseAsync(['send', 'chat-123', 'Hello there'], { from: 'user' })
|
|
84
|
+
|
|
85
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
86
|
+
const output = consoleSpy.mock.calls[0][0] as string
|
|
87
|
+
const parsed = JSON.parse(output)
|
|
88
|
+
expect(parsed).toBeObject()
|
|
89
|
+
expect(parsed.id).toBe(3)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { WebexMembership, WebexMessage, WebexPerson, WebexSpace } from './types'
|
|
2
2
|
import { WebexError } from './types'
|
|
3
3
|
import { WebexCredentialManager } from './credential-manager'
|
|
4
|
+
import { WebexEncryptionService } from './encryption'
|
|
4
5
|
|
|
5
6
|
const BASE_URL = 'https://webexapis.com/v1'
|
|
6
7
|
const MAX_RETRIES = 3
|
|
@@ -17,6 +18,7 @@ export class WebexClient {
|
|
|
17
18
|
private tokenType: string | null = null
|
|
18
19
|
private buckets: Map<string, RateLimitBucket> = new Map()
|
|
19
20
|
private globalRateLimitUntil: number = 0
|
|
21
|
+
private encryption: WebexEncryptionService | null = null
|
|
20
22
|
|
|
21
23
|
async login(credentials?: { token: string }): Promise<this> {
|
|
22
24
|
if (credentials) {
|
|
@@ -40,7 +42,16 @@ export class WebexClient {
|
|
|
40
42
|
}
|
|
41
43
|
this.deviceUrl = config?.deviceUrl ?? null
|
|
42
44
|
this.tokenType = config?.tokenType ?? null
|
|
43
|
-
|
|
45
|
+
await this.login({ token })
|
|
46
|
+
|
|
47
|
+
if (this.tokenType === 'extracted' && config?.encryptionKeys) {
|
|
48
|
+
const keysMap = new Map(Object.entries(config.encryptionKeys))
|
|
49
|
+
if (keysMap.size > 0) {
|
|
50
|
+
this.encryption = new WebexEncryptionService(keysMap)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
private ensureAuth(): string {
|
|
@@ -210,7 +221,7 @@ export class WebexClient {
|
|
|
210
221
|
private async internalRequest<T>(path: string, init?: RequestInit): Promise<T> {
|
|
211
222
|
const response = await fetch(`${this.convBaseUrl}${path}`, {
|
|
212
223
|
...init,
|
|
213
|
-
headers: { ...this.internalHeaders, ...init?.headers as Record<string, string> },
|
|
224
|
+
headers: { ...this.internalHeaders, ...(init?.headers as Record<string, string>) },
|
|
214
225
|
})
|
|
215
226
|
|
|
216
227
|
if (!response.ok) {
|
|
@@ -225,35 +236,78 @@ export class WebexClient {
|
|
|
225
236
|
return response.json() as Promise<T>
|
|
226
237
|
}
|
|
227
238
|
|
|
228
|
-
private activityToMessage(a: InternalActivity, roomId: string): WebexMessage {
|
|
239
|
+
private async activityToMessage(a: InternalActivity, roomId: string): Promise<WebexMessage> {
|
|
240
|
+
let text = a.object?.content ?? a.object?.displayName
|
|
241
|
+
|
|
242
|
+
if (this.encryption && text?.startsWith('eyJ')) {
|
|
243
|
+
const keyUrl = a.encryptionKeyUrl ?? a.object?.encryptionKeyUrl
|
|
244
|
+
if (keyUrl) {
|
|
245
|
+
const decrypted = await this.encryption.decryptText(keyUrl, text)
|
|
246
|
+
if (decrypted !== null) {
|
|
247
|
+
text = decrypted
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
229
252
|
return {
|
|
230
253
|
id: a.id,
|
|
231
254
|
roomId,
|
|
232
255
|
roomType: 'group' as const,
|
|
233
|
-
text
|
|
256
|
+
text,
|
|
234
257
|
personId: a.actor?.entryUUID ?? a.actor?.id ?? '',
|
|
235
258
|
personEmail: a.actor?.emailAddress ?? '',
|
|
236
259
|
created: a.published,
|
|
237
260
|
}
|
|
238
261
|
}
|
|
239
262
|
|
|
263
|
+
private async buildEncryptedObject(
|
|
264
|
+
convUuid: string,
|
|
265
|
+
text: string,
|
|
266
|
+
options?: { markdown?: boolean },
|
|
267
|
+
): Promise<{ object: Record<string, string>; encryptionKeyUrl?: string }> {
|
|
268
|
+
const buildObject = (content: string): Record<string, string> =>
|
|
269
|
+
options?.markdown
|
|
270
|
+
? { objectType: 'comment', displayName: content, content, markdown: content }
|
|
271
|
+
: { objectType: 'comment', displayName: content, content }
|
|
272
|
+
|
|
273
|
+
if (this.encryption) {
|
|
274
|
+
const conv = await this.internalRequest<InternalConversation>(
|
|
275
|
+
`/conversations/${convUuid}?activitiesLimit=0&participantsLimit=0`,
|
|
276
|
+
)
|
|
277
|
+
const keyUri = conv.defaultActivityEncryptionKeyUrl
|
|
278
|
+
if (keyUri) {
|
|
279
|
+
const encrypted = await this.encryption.encryptText(keyUri, text)
|
|
280
|
+
if (encrypted) {
|
|
281
|
+
return { object: buildObject(encrypted), encryptionKeyUrl: keyUri }
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { object: buildObject(text) }
|
|
287
|
+
}
|
|
288
|
+
|
|
240
289
|
private async sendMessageInternal(
|
|
241
290
|
roomId: string,
|
|
242
291
|
text: string,
|
|
243
292
|
options?: { markdown?: boolean },
|
|
244
293
|
): Promise<WebexMessage> {
|
|
245
294
|
const convUuid = this.decodeConvUuid(roomId)
|
|
246
|
-
const object = options
|
|
247
|
-
|
|
248
|
-
|
|
295
|
+
const { object, encryptionKeyUrl } = await this.buildEncryptedObject(convUuid, text, options)
|
|
296
|
+
|
|
297
|
+
const activity: Record<string, unknown> = {
|
|
298
|
+
verb: 'post',
|
|
299
|
+
object,
|
|
300
|
+
target: { id: convUuid, objectType: 'conversation' },
|
|
301
|
+
clientTempId: `tmp-${Date.now()}`,
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (encryptionKeyUrl) {
|
|
305
|
+
activity['encryptionKeyUrl'] = encryptionKeyUrl
|
|
306
|
+
}
|
|
307
|
+
|
|
249
308
|
const result = await this.internalRequest<InternalActivity>('/activities', {
|
|
250
309
|
method: 'POST',
|
|
251
|
-
body: JSON.stringify(
|
|
252
|
-
verb: 'post',
|
|
253
|
-
object,
|
|
254
|
-
target: { id: convUuid, objectType: 'conversation' },
|
|
255
|
-
clientTempId: `tmp-${Date.now()}`,
|
|
256
|
-
}),
|
|
310
|
+
body: JSON.stringify(activity),
|
|
257
311
|
})
|
|
258
312
|
return this.activityToMessage(result, roomId)
|
|
259
313
|
}
|
|
@@ -297,9 +351,8 @@ export class WebexClient {
|
|
|
297
351
|
const conv = await this.internalRequest<InternalConversation>(
|
|
298
352
|
`/conversations/${convUuid}?activitiesLimit=${max}&participantsLimit=0`,
|
|
299
353
|
)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
.map((a) => this.activityToMessage(a, roomId))
|
|
354
|
+
const activities = (conv.activities?.items ?? []).filter((a) => a.verb === 'post')
|
|
355
|
+
return Promise.all(activities.map((a) => this.activityToMessage(a, roomId)))
|
|
303
356
|
}
|
|
304
357
|
const params = new URLSearchParams()
|
|
305
358
|
params.set('roomId', roomId)
|
|
@@ -312,7 +365,9 @@ export class WebexClient {
|
|
|
312
365
|
if (this.useInternalAPI) {
|
|
313
366
|
const activity = await this.internalRequest<InternalActivity>(`/activities/${messageId}`)
|
|
314
367
|
const convId = activity.target?.id ?? ''
|
|
315
|
-
const roomId = convId
|
|
368
|
+
const roomId = convId
|
|
369
|
+
? Buffer.from(`ciscospark://urn:TEAM:unknown/ROOM/${convId}`).toString('base64')
|
|
370
|
+
: ''
|
|
316
371
|
return this.activityToMessage(activity, roomId)
|
|
317
372
|
}
|
|
318
373
|
return this.request<WebexMessage>('GET', `/messages/${messageId}`)
|
|
@@ -344,15 +399,23 @@ export class WebexClient {
|
|
|
344
399
|
): Promise<WebexMessage> {
|
|
345
400
|
if (this.useInternalAPI) {
|
|
346
401
|
const convUuid = this.decodeConvUuid(roomId)
|
|
402
|
+
const { object, encryptionKeyUrl } = await this.buildEncryptedObject(convUuid, text, options)
|
|
403
|
+
|
|
404
|
+
const activity: Record<string, unknown> = {
|
|
405
|
+
verb: 'post',
|
|
406
|
+
object,
|
|
407
|
+
target: { id: convUuid, objectType: 'conversation' },
|
|
408
|
+
parent: { id: messageId, type: 'edit' },
|
|
409
|
+
clientTempId: `tmp-${Date.now()}`,
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (encryptionKeyUrl) {
|
|
413
|
+
activity['encryptionKeyUrl'] = encryptionKeyUrl
|
|
414
|
+
}
|
|
415
|
+
|
|
347
416
|
const result = await this.internalRequest<InternalActivity>('/activities', {
|
|
348
417
|
method: 'POST',
|
|
349
|
-
body: JSON.stringify(
|
|
350
|
-
verb: 'post',
|
|
351
|
-
object: { objectType: 'comment', displayName: text, content: text },
|
|
352
|
-
target: { id: convUuid, objectType: 'conversation' },
|
|
353
|
-
parent: { id: messageId, type: 'edit' },
|
|
354
|
-
clientTempId: `tmp-${Date.now()}`,
|
|
355
|
-
}),
|
|
418
|
+
body: JSON.stringify(activity),
|
|
356
419
|
})
|
|
357
420
|
return this.activityToMessage(result, roomId)
|
|
358
421
|
}
|
|
@@ -394,12 +457,21 @@ interface InternalActivity {
|
|
|
394
457
|
id: string
|
|
395
458
|
verb: string
|
|
396
459
|
actor?: { displayName?: string; emailAddress?: string; entryUUID?: string; id?: string }
|
|
397
|
-
object?: {
|
|
398
|
-
|
|
460
|
+
object?: {
|
|
461
|
+
content?: string
|
|
462
|
+
displayName?: string
|
|
463
|
+
objectType?: string
|
|
464
|
+
encryptionKeyUrl?: string
|
|
465
|
+
}
|
|
466
|
+
target?: { id: string; encryptionKeyUrl?: string }
|
|
399
467
|
published: string
|
|
468
|
+
encryptionKeyUrl?: string
|
|
400
469
|
}
|
|
401
470
|
|
|
402
471
|
interface InternalConversation {
|
|
403
472
|
id: string
|
|
404
473
|
activities?: { items: InternalActivity[] }
|
|
474
|
+
defaultActivityEncryptionKeyUrl?: string
|
|
475
|
+
kmsResourceObjectUrl?: string
|
|
476
|
+
encryptionKeyUrl?: string
|
|
405
477
|
}
|
|
@@ -174,6 +174,10 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
174
174
|
expiresAt: extracted.expiresAt ?? 0,
|
|
175
175
|
tokenType: 'extracted',
|
|
176
176
|
deviceUrl: extracted.deviceUrl,
|
|
177
|
+
userId: extracted.userId,
|
|
178
|
+
encryptionKeys: extracted.encryptionKeys
|
|
179
|
+
? Object.fromEntries(extracted.encryptionKeys)
|
|
180
|
+
: undefined,
|
|
177
181
|
})
|
|
178
182
|
|
|
179
183
|
console.log(
|