agent-messenger 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.env.template +35 -17
- package/README.md +7 -7
- package/bun.lock +6 -6
- package/dist/package.json +2 -2
- package/dist/src/platforms/channeltalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/commands/auth.js +35 -28
- package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/ensure-auth.js +6 -6
- package/dist/src/platforms/channeltalk/ensure-auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts +23 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.js +299 -29
- package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
- package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/auth.js +57 -49
- package/dist/src/platforms/discord/commands/auth.js.map +1 -1
- package/dist/src/platforms/discord/ensure-auth.js +3 -3
- package/dist/src/platforms/discord/ensure-auth.js.map +1 -1
- package/dist/src/platforms/discord/token-extractor.d.ts +6 -1
- package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/discord/token-extractor.js +167 -14
- package/dist/src/platforms/discord/token-extractor.js.map +1 -1
- package/dist/src/platforms/instagram/client.d.ts +2 -0
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +2 -2
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +107 -14
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/ensure-auth.js +57 -11
- package/dist/src/platforms/instagram/ensure-auth.js.map +1 -1
- package/dist/src/platforms/instagram/index.d.ts +1 -0
- package/dist/src/platforms/instagram/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/index.js +1 -0
- package/dist/src/platforms/instagram/index.js.map +1 -1
- package/dist/src/platforms/instagram/token-extractor.d.ts +44 -0
- package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/instagram/token-extractor.js +407 -0
- package/dist/src/platforms/instagram/token-extractor.js.map +1 -0
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +2 -1
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.js +14 -13
- package/dist/src/platforms/kakaotalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js +2 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
- package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/auth.js +6 -5
- package/dist/src/platforms/line/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/auth.js +11 -10
- package/dist/src/platforms/slack/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/token-extractor.d.ts +9 -0
- package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/slack/token-extractor.js +300 -23
- package/dist/src/platforms/slack/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +9 -8
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +2 -1
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/token-extractor.d.ts +5 -0
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +161 -29
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/telegram/client.d.ts.map +1 -1
- package/dist/src/platforms/telegram/client.js +25 -7
- package/dist/src/platforms/telegram/client.js.map +1 -1
- package/dist/src/platforms/telegram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/telegram/commands/auth.js +6 -5
- package/dist/src/platforms/telegram/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/client.d.ts +10 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -1
- package/dist/src/platforms/webex/client.js +124 -0
- package/dist/src/platforms/webex/client.js.map +1 -1
- package/dist/src/platforms/webex/commands/auth.d.ts +4 -0
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/auth.js +46 -4
- package/dist/src/platforms/webex/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/credential-manager.js +1 -1
- package/dist/src/platforms/webex/credential-manager.js.map +1 -1
- package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/ensure-auth.js +21 -5
- package/dist/src/platforms/webex/ensure-auth.js.map +1 -1
- package/dist/src/platforms/webex/index.d.ts +2 -0
- package/dist/src/platforms/webex/index.d.ts.map +1 -1
- package/dist/src/platforms/webex/index.js +1 -0
- package/dist/src/platforms/webex/index.js.map +1 -1
- package/dist/src/platforms/webex/token-extractor.d.ts +28 -0
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/webex/token-extractor.js +344 -0
- package/dist/src/platforms/webex/token-extractor.js.map +1 -0
- package/dist/src/platforms/webex/types.d.ts +4 -1
- package/dist/src/platforms/webex/types.d.ts.map +1 -1
- package/dist/src/platforms/webex/types.js +2 -1
- package/dist/src/platforms/webex/types.js.map +1 -1
- package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.js +6 -2
- package/dist/src/platforms/whatsapp/client.js.map +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts.map +1 -1
- package/dist/src/shared/utils/error-handler.d.ts +1 -1
- package/dist/src/shared/utils/error-handler.d.ts.map +1 -1
- package/dist/src/shared/utils/error-handler.js +3 -2
- package/dist/src/shared/utils/error-handler.js.map +1 -1
- package/dist/src/shared/utils/stderr.d.ts +5 -0
- package/dist/src/shared/utils/stderr.d.ts.map +1 -0
- package/dist/src/shared/utils/stderr.js +18 -0
- package/dist/src/shared/utils/stderr.js.map +1 -0
- package/docs/content/docs/cli/channeltalk.mdx +7 -7
- package/docs/content/docs/cli/discord.mdx +3 -3
- package/docs/content/docs/cli/instagram.mdx +28 -6
- package/docs/content/docs/cli/slack.mdx +2 -2
- package/docs/content/docs/cli/teams.mdx +6 -4
- package/docs/content/docs/cli/webex.mdx +30 -11
- package/e2e/README.md +132 -8
- package/e2e/channeltalk.e2e.test.ts +2 -7
- package/e2e/channeltalkbot.e2e.test.ts +2 -6
- package/e2e/config.ts +172 -10
- package/e2e/helpers.ts +7 -0
- package/e2e/instagram.e2e.test.ts +97 -0
- package/e2e/kakaotalk.e2e.test.ts +74 -0
- package/e2e/line.e2e.test.ts +92 -0
- package/e2e/teams.e2e.test.ts +46 -1
- package/e2e/telegram.e2e.test.ts +84 -0
- package/e2e/webex.e2e.test.ts +190 -0
- package/e2e/whatsapp.e2e.test.ts +90 -0
- package/e2e/whatsappbot.e2e.test.ts +78 -0
- package/package.json +2 -2
- package/skills/agent-channeltalk/SKILL.md +9 -9
- package/skills/agent-channeltalk/references/authentication.md +21 -18
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +5 -5
- package/skills/agent-discord/references/authentication.md +8 -8
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +51 -9
- package/skills/agent-instagram/references/authentication.md +35 -3
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +5 -5
- package/skills/agent-slack/references/authentication.md +8 -8
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +6 -6
- package/skills/agent-teams/references/authentication.md +8 -8
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +35 -15
- package/skills/agent-webex/references/authentication.md +62 -9
- package/skills/agent-webex/references/common-patterns.md +6 -3
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/channeltalk/commands/auth.test.ts +5 -5
- package/src/platforms/channeltalk/commands/auth.ts +38 -32
- package/src/platforms/channeltalk/ensure-auth.test.ts +6 -6
- package/src/platforms/channeltalk/ensure-auth.ts +6 -6
- package/src/platforms/channeltalk/token-extractor.test.ts +182 -15
- package/src/platforms/channeltalk/token-extractor.ts +344 -30
- package/src/platforms/discord/commands/auth.test.ts +3 -3
- package/src/platforms/discord/commands/auth.ts +58 -54
- package/src/platforms/discord/ensure-auth.test.ts +3 -3
- package/src/platforms/discord/ensure-auth.ts +3 -3
- package/src/platforms/discord/token-extractor.test.ts +199 -27
- package/src/platforms/discord/token-extractor.ts +190 -17
- package/src/platforms/instagram/client.ts +2 -2
- package/src/platforms/instagram/commands/auth.ts +133 -14
- package/src/platforms/instagram/ensure-auth.ts +63 -12
- package/src/platforms/instagram/index.ts +1 -0
- package/src/platforms/instagram/token-extractor.test.ts +424 -0
- package/src/platforms/instagram/token-extractor.ts +478 -0
- package/src/platforms/kakaotalk/client.ts +3 -1
- package/src/platforms/kakaotalk/commands/auth.ts +14 -13
- package/src/platforms/kakaotalk/protocol/connection.ts +3 -1
- package/src/platforms/line/commands/auth.ts +7 -6
- package/src/platforms/slack/cli.test.ts +6 -5
- package/src/platforms/slack/commands/auth.test.ts +11 -7
- package/src/platforms/slack/commands/auth.ts +11 -10
- package/src/platforms/slack/token-extractor.test.ts +98 -1
- package/src/platforms/slack/token-extractor.ts +338 -26
- package/src/platforms/teams/commands/auth.ts +9 -8
- package/src/platforms/teams/ensure-auth.ts +3 -1
- package/src/platforms/teams/token-extractor.test.ts +136 -17
- package/src/platforms/teams/token-extractor.ts +182 -31
- package/src/platforms/telegram/client.test.ts +134 -0
- package/src/platforms/telegram/client.ts +27 -6
- package/src/platforms/telegram/commands/auth.ts +6 -5
- package/src/platforms/webex/client.test.ts +314 -0
- package/src/platforms/webex/client.ts +158 -0
- package/src/platforms/webex/commands/auth.ts +67 -4
- package/src/platforms/webex/commands/member.test.ts +10 -1
- package/src/platforms/webex/commands/message.test.ts +9 -5
- package/src/platforms/webex/commands/snapshot.test.ts +13 -4
- package/src/platforms/webex/commands/space.test.ts +12 -2
- package/src/platforms/webex/credential-manager.ts +1 -1
- package/src/platforms/webex/ensure-auth.test.ts +4 -0
- package/src/platforms/webex/ensure-auth.ts +23 -4
- package/src/platforms/webex/index.ts +2 -0
- package/src/platforms/webex/token-extractor.test.ts +327 -0
- package/src/platforms/webex/token-extractor.ts +393 -0
- package/src/platforms/webex/types.ts +4 -2
- package/src/platforms/whatsapp/client.ts +11 -7
- package/src/shared/utils/derived-key-cache.ts +1 -1
- package/src/shared/utils/error-handler.ts +4 -2
- package/src/shared/utils/stderr.ts +22 -0
|
@@ -16,6 +16,140 @@ const mockPaths: TelegramAccountPaths = {
|
|
|
16
16
|
files_dir: '/tmp/test-files',
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function createMockClient(sendHandler: (request: any, events: any[]) => void) {
|
|
20
|
+
const events: any[] = []
|
|
21
|
+
const createClientId = mock(() => 1)
|
|
22
|
+
const send = mock((_clientId: number, request: any) => sendHandler(request, events))
|
|
23
|
+
const receive = mock(() => events.shift() ?? null)
|
|
24
|
+
|
|
25
|
+
const client = new (TelegramTdlibClient as unknown as new (
|
|
26
|
+
account: TelegramAccount,
|
|
27
|
+
paths: TelegramAccountPaths,
|
|
28
|
+
tdjson: any,
|
|
29
|
+
) => TelegramTdlibClient)(mockAccount, mockPaths, {
|
|
30
|
+
createClientId,
|
|
31
|
+
send,
|
|
32
|
+
receive,
|
|
33
|
+
libraryPath: '/mock/lib',
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return { client, events, send }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function pushAuthReady(events: any[], extra: string) {
|
|
40
|
+
events.push({
|
|
41
|
+
'@type': 'updateAuthorizationState',
|
|
42
|
+
authorization_state: { '@type': 'authorizationStateReady' },
|
|
43
|
+
'@extra': extra,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('listChats', () => {
|
|
48
|
+
test('loads chats across multiple loadChats calls until 404', async () => {
|
|
49
|
+
let loadChatsCallCount = 0
|
|
50
|
+
const allChatIds = [1, 2, 3, 4, 5]
|
|
51
|
+
|
|
52
|
+
const { client } = createMockClient((request, events) => {
|
|
53
|
+
if (request['@type'] === 'getAuthorizationState') {
|
|
54
|
+
pushAuthReady(events, request['@extra'])
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (request['@type'] === 'loadChats') {
|
|
59
|
+
loadChatsCallCount += 1
|
|
60
|
+
if (loadChatsCallCount <= 2) {
|
|
61
|
+
// given — first two calls succeed (simulate partial loading)
|
|
62
|
+
events.push({ '@type': 'ok', '@extra': request['@extra'] })
|
|
63
|
+
} else {
|
|
64
|
+
// given — third call returns 404 (all chats loaded)
|
|
65
|
+
events.push({ '@type': 'error', code: 404, message: 'Chat list has been loaded completely', '@extra': request['@extra'] })
|
|
66
|
+
}
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (request['@type'] === 'getChats') {
|
|
71
|
+
// when — after first two loadChats calls, return partial; after 404, return all
|
|
72
|
+
const returnCount = loadChatsCallCount >= 3 ? allChatIds.length : Math.min(loadChatsCallCount * 2, allChatIds.length)
|
|
73
|
+
events.push({
|
|
74
|
+
'@type': 'chats',
|
|
75
|
+
total_count: returnCount,
|
|
76
|
+
chat_ids: allChatIds.slice(0, returnCount),
|
|
77
|
+
'@extra': request['@extra'],
|
|
78
|
+
})
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (request['@type'] === 'getChat') {
|
|
83
|
+
const chatId = request.chat_id
|
|
84
|
+
const typeNames = ['chatTypePrivate', 'chatTypePrivate', 'chatTypeBasicGroup', 'chatTypeSupergroup', 'chatTypeSupergroup']
|
|
85
|
+
const idx = allChatIds.indexOf(chatId)
|
|
86
|
+
events.push({
|
|
87
|
+
'@type': 'chat',
|
|
88
|
+
id: chatId,
|
|
89
|
+
title: `Chat ${chatId}`,
|
|
90
|
+
type: { '@type': typeNames[idx] ?? 'chatTypePrivate' },
|
|
91
|
+
unread_count: 0,
|
|
92
|
+
'@extra': request['@extra'],
|
|
93
|
+
})
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const chats = await client.listChats(5)
|
|
99
|
+
|
|
100
|
+
// then — all 5 chats returned including groups
|
|
101
|
+
expect(chats).toHaveLength(5)
|
|
102
|
+
expect(chats.map((c) => c.type)).toEqual(['private', 'private', 'basicgroup', 'supergroup', 'supergroup'])
|
|
103
|
+
expect(loadChatsCallCount).toBe(3)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('stops loading when enough chats are cached before 404', async () => {
|
|
107
|
+
let loadChatsCallCount = 0
|
|
108
|
+
|
|
109
|
+
const { client } = createMockClient((request, events) => {
|
|
110
|
+
if (request['@type'] === 'getAuthorizationState') {
|
|
111
|
+
pushAuthReady(events, request['@extra'])
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (request['@type'] === 'loadChats') {
|
|
116
|
+
loadChatsCallCount += 1
|
|
117
|
+
events.push({ '@type': 'ok', '@extra': request['@extra'] })
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (request['@type'] === 'getChats') {
|
|
122
|
+
// given — always return 3 chats (enough for limit=3)
|
|
123
|
+
events.push({
|
|
124
|
+
'@type': 'chats',
|
|
125
|
+
total_count: 3,
|
|
126
|
+
chat_ids: [10, 20, 30],
|
|
127
|
+
'@extra': request['@extra'],
|
|
128
|
+
})
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (request['@type'] === 'getChat') {
|
|
133
|
+
events.push({
|
|
134
|
+
'@type': 'chat',
|
|
135
|
+
id: request.chat_id,
|
|
136
|
+
title: `Chat ${request.chat_id}`,
|
|
137
|
+
type: { '@type': 'chatTypeSupergroup' },
|
|
138
|
+
unread_count: 0,
|
|
139
|
+
'@extra': request['@extra'],
|
|
140
|
+
})
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const chats = await client.listChats(3)
|
|
146
|
+
|
|
147
|
+
// then — stops after first loop iteration since we have enough
|
|
148
|
+
expect(chats).toHaveLength(3)
|
|
149
|
+
expect(loadChatsCallCount).toBe(1)
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
19
153
|
describe('sendMessage confirmation', () => {
|
|
20
154
|
test('returns confirmed message id when updateMessageSendSucceeded arrives', async () => {
|
|
21
155
|
const tempId = 100
|
|
@@ -162,16 +162,37 @@ export class TelegramTdlibClient {
|
|
|
162
162
|
async listChats(limit: number = 20): Promise<TelegramChatSummary[]> {
|
|
163
163
|
await this.ensureReady()
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
// loadChats may load fewer chats than requested per call. Loop until TDLib
|
|
166
|
+
// signals 404 ("chat list fully loaded") or we have enough cached entries.
|
|
167
|
+
for (;;) {
|
|
168
|
+
try {
|
|
169
|
+
await this.call({
|
|
170
|
+
'@type': 'loadChats',
|
|
171
|
+
chat_list: {
|
|
172
|
+
'@type': 'chatListMain',
|
|
173
|
+
},
|
|
174
|
+
limit,
|
|
175
|
+
})
|
|
176
|
+
} catch (error) {
|
|
177
|
+
// TDLib signals 404 when the entire chat list has been loaded.
|
|
178
|
+
if (error instanceof TelegramError && error.code === 404) {
|
|
179
|
+
break
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
break
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const partial = (await this.call({
|
|
186
|
+
'@type': 'getChats',
|
|
168
187
|
chat_list: {
|
|
169
188
|
'@type': 'chatListMain',
|
|
170
189
|
},
|
|
171
190
|
limit,
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
})) as TdChats
|
|
192
|
+
|
|
193
|
+
if ((partial.chat_ids ?? []).length >= limit) {
|
|
194
|
+
break
|
|
195
|
+
}
|
|
175
196
|
}
|
|
176
197
|
|
|
177
198
|
const response = (await this.call({
|
|
@@ -4,6 +4,7 @@ import { Command } from 'commander'
|
|
|
4
4
|
|
|
5
5
|
import { handleError } from '../../../shared/utils/error-handler'
|
|
6
6
|
import { formatOutput } from '../../../shared/utils/output'
|
|
7
|
+
import { info, error as stderrError } from '@/shared/utils/stderr'
|
|
7
8
|
import { getTelegramAppCredentials } from '../app-config'
|
|
8
9
|
import { TelegramTdlibClient } from '../client'
|
|
9
10
|
import { TelegramCredentialManager } from '../credential-manager'
|
|
@@ -126,8 +127,8 @@ async function fillMissingBootstrappingInputs(
|
|
|
126
127
|
if (!resolved.apiId && !existing?.api_id) {
|
|
127
128
|
if (shouldUseInteractivePrompts()) {
|
|
128
129
|
try {
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
info('No API credentials found. Provisioning via my.telegram.org...')
|
|
131
|
+
info('A verification code will be sent to your Telegram account.\n')
|
|
131
132
|
|
|
132
133
|
const phone = resolved.phone || (await promptText('Phone number (e.g. +14155551234)'))
|
|
133
134
|
if (!phone) {
|
|
@@ -148,10 +149,10 @@ async function fillMissingBootstrappingInputs(
|
|
|
148
149
|
|
|
149
150
|
resolved.apiId = String(app.api_id)
|
|
150
151
|
resolved.apiHash = app.api_hash
|
|
151
|
-
|
|
152
|
+
info(`\n✓ API credentials obtained (api_id: ${app.api_id})`)
|
|
152
153
|
} catch (error) {
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
stderrError(`\nAuto-provisioning failed: ${error instanceof Error ? error.message : error}`)
|
|
155
|
+
info('Enter your API credentials manually (from https://my.telegram.org/apps):\n')
|
|
155
156
|
resolved.apiId = await promptText('Telegram API ID')
|
|
156
157
|
resolved.apiHash = await promptHidden('Telegram API hash')
|
|
157
158
|
}
|
|
@@ -388,6 +388,320 @@ describe('WebexClient', () => {
|
|
|
388
388
|
})
|
|
389
389
|
})
|
|
390
390
|
|
|
391
|
+
describe('internal conversation API', () => {
|
|
392
|
+
const TEST_DEVICE_URL = 'https://wdm-r.wbx2.com/wdm/api/v1/devices/test-device-id'
|
|
393
|
+
const CONV_BASE = 'https://conv-r.wbx2.com/conversation/api/v1'
|
|
394
|
+
const TEST_ROOM_ID = Buffer.from('ciscospark://urn:TEAM:us-west-2_r/ROOM/abc123-def456').toString('base64')
|
|
395
|
+
const TEST_CONV_UUID = 'abc123-def456'
|
|
396
|
+
|
|
397
|
+
const mockActivity = (text: string, overrides?: Partial<Record<string, unknown>>) => ({
|
|
398
|
+
id: 'activity-123',
|
|
399
|
+
verb: 'post',
|
|
400
|
+
actor: { displayName: 'Test User', emailAddress: 'test@example.com', entryUUID: 'user-uuid' },
|
|
401
|
+
object: { objectType: 'comment', content: text, displayName: text },
|
|
402
|
+
target: { id: TEST_CONV_UUID },
|
|
403
|
+
published: '2026-01-01T00:00:00.000Z',
|
|
404
|
+
...overrides,
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
const mockConversation = (activities: ReturnType<typeof mockActivity>[]) => ({
|
|
408
|
+
id: TEST_CONV_UUID,
|
|
409
|
+
activities: { items: activities },
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
const createExtractedClient = async () => {
|
|
413
|
+
const client = await new WebexClient().login({ token: 'extracted-token' })
|
|
414
|
+
;(client as any).deviceUrl = TEST_DEVICE_URL
|
|
415
|
+
;(client as any).tokenType = 'extracted'
|
|
416
|
+
return client
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
describe('sendMessage', () => {
|
|
420
|
+
test('posts activity to /activities with POST method', async () => {
|
|
421
|
+
mockResponse(mockActivity('Hello world'))
|
|
422
|
+
|
|
423
|
+
const client = await createExtractedClient()
|
|
424
|
+
await client.sendMessage(TEST_ROOM_ID, 'Hello world')
|
|
425
|
+
|
|
426
|
+
expect(fetchCalls[0].url).toBe(`${CONV_BASE}/activities`)
|
|
427
|
+
expect(fetchCalls[0].options?.method).toBe('POST')
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
test('body has verb, object type, displayName, and content', async () => {
|
|
431
|
+
mockResponse(mockActivity('Hello world'))
|
|
432
|
+
|
|
433
|
+
const client = await createExtractedClient()
|
|
434
|
+
await client.sendMessage(TEST_ROOM_ID, 'Hello world')
|
|
435
|
+
|
|
436
|
+
const body = JSON.parse(fetchCalls[0].options?.body as string)
|
|
437
|
+
expect(body.verb).toBe('post')
|
|
438
|
+
expect(body.object.objectType).toBe('comment')
|
|
439
|
+
expect(body.object.displayName).toBe('Hello world')
|
|
440
|
+
expect(body.object.content).toBe('Hello world')
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
test('body has target with decoded conv UUID and conversation type', async () => {
|
|
444
|
+
mockResponse(mockActivity('Hello world'))
|
|
445
|
+
|
|
446
|
+
const client = await createExtractedClient()
|
|
447
|
+
await client.sendMessage(TEST_ROOM_ID, 'Hello world')
|
|
448
|
+
|
|
449
|
+
const body = JSON.parse(fetchCalls[0].options?.body as string)
|
|
450
|
+
expect(body.target.id).toBe(TEST_CONV_UUID)
|
|
451
|
+
expect(body.target.objectType).toBe('conversation')
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
test('body has clientTempId starting with tmp-', async () => {
|
|
455
|
+
mockResponse(mockActivity('Hello world'))
|
|
456
|
+
|
|
457
|
+
const client = await createExtractedClient()
|
|
458
|
+
await client.sendMessage(TEST_ROOM_ID, 'Hello world')
|
|
459
|
+
|
|
460
|
+
const body = JSON.parse(fetchCalls[0].options?.body as string)
|
|
461
|
+
expect(body.clientTempId).toStartWith('tmp-')
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
test('includes cisco-device-url header', async () => {
|
|
465
|
+
mockResponse(mockActivity('Hello world'))
|
|
466
|
+
|
|
467
|
+
const client = await createExtractedClient()
|
|
468
|
+
await client.sendMessage(TEST_ROOM_ID, 'Hello world')
|
|
469
|
+
|
|
470
|
+
expect(fetchCalls[0].options?.headers).toMatchObject({
|
|
471
|
+
'cisco-device-url': TEST_DEVICE_URL,
|
|
472
|
+
})
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
test('returns WebexMessage mapped from activity response', async () => {
|
|
476
|
+
mockResponse(mockActivity('Hello world'))
|
|
477
|
+
|
|
478
|
+
const client = await createExtractedClient()
|
|
479
|
+
const message = await client.sendMessage(TEST_ROOM_ID, 'Hello world')
|
|
480
|
+
|
|
481
|
+
expect(message.id).toBe('activity-123')
|
|
482
|
+
expect(message.text).toBe('Hello world')
|
|
483
|
+
expect(message.personEmail).toBe('test@example.com')
|
|
484
|
+
expect(message.created).toBe('2026-01-01T00:00:00.000Z')
|
|
485
|
+
})
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
describe('listMessages', () => {
|
|
489
|
+
test('calls GET on conversations endpoint with activitiesLimit and participantsLimit', async () => {
|
|
490
|
+
mockResponse(mockConversation([mockActivity('Hello')]))
|
|
491
|
+
|
|
492
|
+
const client = await createExtractedClient()
|
|
493
|
+
await client.listMessages(TEST_ROOM_ID)
|
|
494
|
+
|
|
495
|
+
expect(fetchCalls[0].url).toBe(
|
|
496
|
+
`${CONV_BASE}/conversations/${TEST_CONV_UUID}?activitiesLimit=50&participantsLimit=0`,
|
|
497
|
+
)
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
test('filters activities to only those with verb post', async () => {
|
|
501
|
+
mockResponse(
|
|
502
|
+
mockConversation([
|
|
503
|
+
mockActivity('Hello'),
|
|
504
|
+
{ ...mockActivity('Deleted'), verb: 'delete' },
|
|
505
|
+
mockActivity('World'),
|
|
506
|
+
]),
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
const client = await createExtractedClient()
|
|
510
|
+
const messages = await client.listMessages(TEST_ROOM_ID)
|
|
511
|
+
|
|
512
|
+
expect(messages).toHaveLength(2)
|
|
513
|
+
expect(messages[0].text).toBe('Hello')
|
|
514
|
+
expect(messages[1].text).toBe('World')
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
test('maps each activity to WebexMessage format', async () => {
|
|
518
|
+
mockResponse(mockConversation([mockActivity('Hello')]))
|
|
519
|
+
|
|
520
|
+
const client = await createExtractedClient()
|
|
521
|
+
const messages = await client.listMessages(TEST_ROOM_ID)
|
|
522
|
+
|
|
523
|
+
expect(messages[0].id).toBe('activity-123')
|
|
524
|
+
expect(messages[0].text).toBe('Hello')
|
|
525
|
+
expect(messages[0].personEmail).toBe('test@example.com')
|
|
526
|
+
expect(messages[0].created).toBe('2026-01-01T00:00:00.000Z')
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
test('passes custom max to activitiesLimit', async () => {
|
|
530
|
+
mockResponse(mockConversation([]))
|
|
531
|
+
|
|
532
|
+
const client = await createExtractedClient()
|
|
533
|
+
await client.listMessages(TEST_ROOM_ID, { max: 25 })
|
|
534
|
+
|
|
535
|
+
expect(fetchCalls[0].url).toContain('activitiesLimit=25')
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
test('includes cisco-device-url header', async () => {
|
|
539
|
+
mockResponse(mockConversation([]))
|
|
540
|
+
|
|
541
|
+
const client = await createExtractedClient()
|
|
542
|
+
await client.listMessages(TEST_ROOM_ID)
|
|
543
|
+
|
|
544
|
+
expect(fetchCalls[0].options?.headers).toMatchObject({
|
|
545
|
+
'cisco-device-url': TEST_DEVICE_URL,
|
|
546
|
+
})
|
|
547
|
+
})
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
describe('getMessage', () => {
|
|
551
|
+
test('calls GET on activities endpoint', async () => {
|
|
552
|
+
mockResponse(mockActivity('Hello'))
|
|
553
|
+
|
|
554
|
+
const client = await createExtractedClient()
|
|
555
|
+
await client.getMessage('activity-123')
|
|
556
|
+
|
|
557
|
+
expect(fetchCalls[0].url).toBe(`${CONV_BASE}/activities/activity-123`)
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
test('maps activity to WebexMessage format', async () => {
|
|
561
|
+
mockResponse(mockActivity('Hello'))
|
|
562
|
+
|
|
563
|
+
const client = await createExtractedClient()
|
|
564
|
+
const message = await client.getMessage('activity-123')
|
|
565
|
+
|
|
566
|
+
expect(message.id).toBe('activity-123')
|
|
567
|
+
expect(message.text).toBe('Hello')
|
|
568
|
+
expect(message.personEmail).toBe('test@example.com')
|
|
569
|
+
})
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
describe('deleteMessage', () => {
|
|
573
|
+
test('first GETs the activity then POSTs a delete activity', async () => {
|
|
574
|
+
mockResponse(mockActivity('Hello'))
|
|
575
|
+
mockResponse({})
|
|
576
|
+
|
|
577
|
+
const client = await createExtractedClient()
|
|
578
|
+
await client.deleteMessage('activity-123')
|
|
579
|
+
|
|
580
|
+
expect(fetchCalls[0].url).toBe(`${CONV_BASE}/activities/activity-123`)
|
|
581
|
+
expect(fetchCalls[1].url).toBe(`${CONV_BASE}/activities`)
|
|
582
|
+
expect(fetchCalls[1].options?.method).toBe('POST')
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
test('delete activity body has correct verb, object, and target', async () => {
|
|
586
|
+
mockResponse(mockActivity('Hello'))
|
|
587
|
+
mockResponse({})
|
|
588
|
+
|
|
589
|
+
const client = await createExtractedClient()
|
|
590
|
+
await client.deleteMessage('activity-123')
|
|
591
|
+
|
|
592
|
+
const body = JSON.parse(fetchCalls[1].options?.body as string)
|
|
593
|
+
expect(body.verb).toBe('delete')
|
|
594
|
+
expect(body.object.id).toBe('activity-123')
|
|
595
|
+
expect(body.object.objectType).toBe('activity')
|
|
596
|
+
expect(body.target.id).toBe(TEST_CONV_UUID)
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
test('throws WebexError when activity has no target', async () => {
|
|
600
|
+
mockResponse({ ...mockActivity('Hello'), target: undefined })
|
|
601
|
+
|
|
602
|
+
const client = await createExtractedClient()
|
|
603
|
+
await expect(client.deleteMessage('activity-123')).rejects.toThrow(WebexError)
|
|
604
|
+
})
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
describe('editMessage', () => {
|
|
608
|
+
test('posts activity with verb post and parent edit reference', async () => {
|
|
609
|
+
mockResponse(mockActivity('Edited text'))
|
|
610
|
+
|
|
611
|
+
const client = await createExtractedClient()
|
|
612
|
+
await client.editMessage('activity-123', TEST_ROOM_ID, 'Edited text')
|
|
613
|
+
|
|
614
|
+
const body = JSON.parse(fetchCalls[0].options?.body as string)
|
|
615
|
+
expect(body.verb).toBe('post')
|
|
616
|
+
expect(body.parent).toEqual({ id: 'activity-123', type: 'edit' })
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
test('body has object with comment type and new text', async () => {
|
|
620
|
+
mockResponse(mockActivity('Edited text'))
|
|
621
|
+
|
|
622
|
+
const client = await createExtractedClient()
|
|
623
|
+
await client.editMessage('activity-123', TEST_ROOM_ID, 'Edited text')
|
|
624
|
+
|
|
625
|
+
const body = JSON.parse(fetchCalls[0].options?.body as string)
|
|
626
|
+
expect(body.object.objectType).toBe('comment')
|
|
627
|
+
expect(body.object.displayName).toBe('Edited text')
|
|
628
|
+
expect(body.object.content).toBe('Edited text')
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
test('target has decoded conv UUID', async () => {
|
|
632
|
+
mockResponse(mockActivity('Edited text'))
|
|
633
|
+
|
|
634
|
+
const client = await createExtractedClient()
|
|
635
|
+
await client.editMessage('activity-123', TEST_ROOM_ID, 'Edited text')
|
|
636
|
+
|
|
637
|
+
const body = JSON.parse(fetchCalls[0].options?.body as string)
|
|
638
|
+
expect(body.target.id).toBe(TEST_CONV_UUID)
|
|
639
|
+
})
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
describe('sendDirectMessage', () => {
|
|
643
|
+
test('calls public rooms and memberships API to find room, then sends via internal API', async () => {
|
|
644
|
+
mockResponse({ items: [{ id: TEST_ROOM_ID, title: 'DM', type: 'direct' }] })
|
|
645
|
+
mockResponse({
|
|
646
|
+
items: [{ id: 'm1', roomId: TEST_ROOM_ID, personEmail: 'target@example.com', isModerator: false }],
|
|
647
|
+
})
|
|
648
|
+
mockResponse(mockActivity('Hello'))
|
|
649
|
+
|
|
650
|
+
const client = await createExtractedClient()
|
|
651
|
+
const message = await client.sendDirectMessage('target@example.com', 'Hello')
|
|
652
|
+
|
|
653
|
+
expect(fetchCalls[0].url).toContain('/rooms?type=direct&max=100')
|
|
654
|
+
expect(fetchCalls[1].url).toContain('/memberships?roomId=')
|
|
655
|
+
expect(fetchCalls[2].url).toBe(`${CONV_BASE}/activities`)
|
|
656
|
+
expect(message.id).toBe('activity-123')
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
test('throws WebexError when no existing direct conversation found', async () => {
|
|
660
|
+
mockResponse({ items: [{ id: 'room-x', title: 'DM', type: 'direct' }] })
|
|
661
|
+
mockResponse({
|
|
662
|
+
items: [{ id: 'm1', roomId: 'room-x', personEmail: 'other@example.com', isModerator: false }],
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
const client = await createExtractedClient()
|
|
666
|
+
await expect(client.sendDirectMessage('target@example.com', 'Hello')).rejects.toThrow(WebexError)
|
|
667
|
+
})
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
describe('error handling', () => {
|
|
671
|
+
test('throws WebexError when internal API returns non-OK response', async () => {
|
|
672
|
+
fetchResponses.push(
|
|
673
|
+
new Response(JSON.stringify({ message: 'Activity not found' }), {
|
|
674
|
+
status: 404,
|
|
675
|
+
headers: { 'Content-Type': 'application/json' },
|
|
676
|
+
}),
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
const client = await createExtractedClient()
|
|
680
|
+
await expect(client.getMessage('bad-activity')).rejects.toThrow(WebexError)
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
test('error message extracted from internal API response body', async () => {
|
|
684
|
+
fetchResponses.push(
|
|
685
|
+
new Response(JSON.stringify({ message: 'Activity not found' }), {
|
|
686
|
+
status: 404,
|
|
687
|
+
headers: { 'Content-Type': 'application/json' },
|
|
688
|
+
}),
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
const client = await createExtractedClient()
|
|
692
|
+
let error: WebexError | null = null
|
|
693
|
+
try {
|
|
694
|
+
await client.getMessage('bad-activity')
|
|
695
|
+
} catch (err) {
|
|
696
|
+
error = err as WebexError
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
expect(error).toBeInstanceOf(WebexError)
|
|
700
|
+
expect(error?.message).toBe('Activity not found')
|
|
701
|
+
})
|
|
702
|
+
})
|
|
703
|
+
})
|
|
704
|
+
|
|
391
705
|
describe('error handling', () => {
|
|
392
706
|
test('throws WebexError with parsed message from response body', async () => {
|
|
393
707
|
mockResponse({ message: 'The requested resource could not be found.', trackingId: 'abc' }, 404)
|