agent-messenger 2.27.2 → 2.29.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 +11 -1
- package/.claude-plugin/marketplace.json +14 -1
- package/.claude-plugin/plugin.json +4 -2
- package/AGENTS.md +4 -0
- package/README.md +58 -10
- package/dist/package.json +10 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/platforms/discord/client.d.ts +6 -2
- package/dist/src/platforms/discord/client.d.ts.map +1 -1
- package/dist/src/platforms/discord/client.js +10 -3
- package/dist/src/platforms/discord/client.js.map +1 -1
- package/dist/src/platforms/discordbot/cli.js +1 -1
- package/dist/src/platforms/imessage/cli.d.ts +5 -0
- package/dist/src/platforms/imessage/cli.d.ts.map +1 -0
- package/dist/src/platforms/imessage/cli.js +31 -0
- package/dist/src/platforms/imessage/cli.js.map +1 -0
- package/dist/src/platforms/imessage/client.d.ts +43 -0
- package/dist/src/platforms/imessage/client.d.ts.map +1 -0
- package/dist/src/platforms/imessage/client.js +190 -0
- package/dist/src/platforms/imessage/client.js.map +1 -0
- package/dist/src/platforms/imessage/commands/auth.d.ts +31 -0
- package/dist/src/platforms/imessage/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/auth.js +100 -0
- package/dist/src/platforms/imessage/commands/auth.js.map +1 -0
- package/dist/src/platforms/imessage/commands/chat.d.ts +3 -0
- package/dist/src/platforms/imessage/commands/chat.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/chat.js +42 -0
- package/dist/src/platforms/imessage/commands/chat.js.map +1 -0
- package/dist/src/platforms/imessage/commands/doctor.d.ts +25 -0
- package/dist/src/platforms/imessage/commands/doctor.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/doctor.js +101 -0
- package/dist/src/platforms/imessage/commands/doctor.js.map +1 -0
- package/dist/src/platforms/imessage/commands/index.d.ts +7 -0
- package/dist/src/platforms/imessage/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/index.js +7 -0
- package/dist/src/platforms/imessage/commands/index.js.map +1 -0
- package/dist/src/platforms/imessage/commands/message.d.ts +5 -0
- package/dist/src/platforms/imessage/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/message.js +127 -0
- package/dist/src/platforms/imessage/commands/message.js.map +1 -0
- package/dist/src/platforms/imessage/commands/setup.d.ts +20 -0
- package/dist/src/platforms/imessage/commands/setup.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/setup.js +57 -0
- package/dist/src/platforms/imessage/commands/setup.js.map +1 -0
- package/dist/src/platforms/imessage/commands/shared.d.ts +10 -0
- package/dist/src/platforms/imessage/commands/shared.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/shared.js +53 -0
- package/dist/src/platforms/imessage/commands/shared.js.map +1 -0
- package/dist/src/platforms/imessage/commands/whoami.d.ts +3 -0
- package/dist/src/platforms/imessage/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/whoami.js +20 -0
- package/dist/src/platforms/imessage/commands/whoami.js.map +1 -0
- package/dist/src/platforms/imessage/credential-manager.d.ts +22 -0
- package/dist/src/platforms/imessage/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/imessage/credential-manager.js +111 -0
- package/dist/src/platforms/imessage/credential-manager.js.map +1 -0
- package/dist/src/platforms/imessage/ensure-auth.d.ts +2 -0
- package/dist/src/platforms/imessage/ensure-auth.d.ts.map +1 -0
- package/dist/src/platforms/imessage/ensure-auth.js +13 -0
- package/dist/src/platforms/imessage/ensure-auth.js.map +1 -0
- package/dist/src/platforms/imessage/errors.d.ts +3 -0
- package/dist/src/platforms/imessage/errors.d.ts.map +1 -0
- package/dist/src/platforms/imessage/errors.js +25 -0
- package/dist/src/platforms/imessage/errors.js.map +1 -0
- package/dist/src/platforms/imessage/index.d.ts +7 -0
- package/dist/src/platforms/imessage/index.d.ts.map +1 -0
- package/dist/src/platforms/imessage/index.js +5 -0
- package/dist/src/platforms/imessage/index.js.map +1 -0
- package/dist/src/platforms/imessage/rpc.d.ts +20 -0
- package/dist/src/platforms/imessage/rpc.d.ts.map +1 -0
- package/dist/src/platforms/imessage/rpc.js +133 -0
- package/dist/src/platforms/imessage/rpc.js.map +1 -0
- package/dist/src/platforms/imessage/types.d.ts +59 -0
- package/dist/src/platforms/imessage/types.d.ts.map +1 -0
- package/dist/src/platforms/imessage/types.js +29 -0
- package/dist/src/platforms/imessage/types.js.map +1 -0
- package/dist/src/platforms/instagram/cli.js +1 -1
- package/dist/src/platforms/instagram/client.d.ts +29 -2
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +360 -53
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts +1 -0
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +123 -11
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts +4 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.js +17 -1
- package/dist/src/platforms/instagram/credential-manager.js.map +1 -1
- package/dist/src/platforms/instagram/hybrid-listener.d.ts +48 -0
- package/dist/src/platforms/instagram/hybrid-listener.d.ts.map +1 -0
- package/dist/src/platforms/instagram/hybrid-listener.js +154 -0
- package/dist/src/platforms/instagram/hybrid-listener.js.map +1 -0
- package/dist/src/platforms/instagram/index.d.ts +2 -0
- package/dist/src/platforms/instagram/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/index.js +2 -0
- package/dist/src/platforms/instagram/index.js.map +1 -1
- package/dist/src/platforms/instagram/mqtt/connection.d.ts +4 -0
- package/dist/src/platforms/instagram/mqtt/connection.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/connection.js +66 -0
- package/dist/src/platforms/instagram/mqtt/connection.js.map +1 -0
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts +22 -0
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/thrift.js +143 -0
- package/dist/src/platforms/instagram/mqtt/thrift.js.map +1 -0
- package/dist/src/platforms/instagram/mqtt/transport.d.ts +42 -0
- package/dist/src/platforms/instagram/mqtt/transport.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/transport.js +246 -0
- package/dist/src/platforms/instagram/mqtt/transport.js.map +1 -0
- package/dist/src/platforms/instagram/realtime-listener.d.ts +33 -0
- package/dist/src/platforms/instagram/realtime-listener.d.ts.map +1 -0
- package/dist/src/platforms/instagram/realtime-listener.js +151 -0
- package/dist/src/platforms/instagram/realtime-listener.js.map +1 -0
- package/dist/src/platforms/instagram/types.d.ts +10 -8
- package/dist/src/platforms/instagram/types.d.ts.map +1 -1
- package/dist/src/platforms/instagram/types.js.map +1 -1
- package/dist/src/platforms/kakaotalk/cli.js +1 -1
- package/dist/src/platforms/line/cli.js +1 -1
- package/dist/src/platforms/teams/app-config.d.ts +36 -0
- package/dist/src/platforms/teams/app-config.d.ts.map +1 -0
- package/dist/src/platforms/teams/app-config.js +69 -0
- package/dist/src/platforms/teams/app-config.js.map +1 -0
- package/dist/src/platforms/teams/client.d.ts +19 -2
- package/dist/src/platforms/teams/client.d.ts.map +1 -1
- package/dist/src/platforms/teams/client.js +295 -7
- package/dist/src/platforms/teams/client.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts +10 -0
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +137 -3
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/commands/file.d.ts +3 -0
- package/dist/src/platforms/teams/commands/file.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/file.js +59 -2
- package/dist/src/platforms/teams/commands/file.js.map +1 -1
- package/dist/src/platforms/teams/commands/message.d.ts +11 -0
- package/dist/src/platforms/teams/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/message.js +121 -2
- package/dist/src/platforms/teams/commands/message.js.map +1 -1
- package/dist/src/platforms/teams/credential-manager.d.ts +23 -1
- package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/teams/credential-manager.js +46 -2
- package/dist/src/platforms/teams/credential-manager.js.map +1 -1
- package/dist/src/platforms/teams/device-code.d.ts +48 -0
- package/dist/src/platforms/teams/device-code.d.ts.map +1 -0
- package/dist/src/platforms/teams/device-code.js +199 -0
- package/dist/src/platforms/teams/device-code.js.map +1 -0
- package/dist/src/platforms/teams/device-login.d.ts +37 -0
- package/dist/src/platforms/teams/device-login.d.ts.map +1 -0
- package/dist/src/platforms/teams/device-login.js +129 -0
- package/dist/src/platforms/teams/device-login.js.map +1 -0
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +12 -0
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/index.d.ts +7 -3
- package/dist/src/platforms/teams/index.d.ts.map +1 -1
- package/dist/src/platforms/teams/index.js +5 -2
- package/dist/src/platforms/teams/index.js.map +1 -1
- package/dist/src/platforms/teams/listener.d.ts +35 -0
- package/dist/src/platforms/teams/listener.d.ts.map +1 -0
- package/dist/src/platforms/teams/listener.js +236 -0
- package/dist/src/platforms/teams/listener.js.map +1 -0
- package/dist/src/platforms/teams/realm-discovery.d.ts +3 -0
- package/dist/src/platforms/teams/realm-discovery.d.ts.map +1 -0
- package/dist/src/platforms/teams/realm-discovery.js +33 -0
- package/dist/src/platforms/teams/realm-discovery.js.map +1 -0
- package/dist/src/platforms/teams/token-extractor.d.ts +3 -0
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +60 -0
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/token-provider.d.ts +25 -0
- package/dist/src/platforms/teams/token-provider.d.ts.map +1 -0
- package/dist/src/platforms/teams/token-provider.js +190 -0
- package/dist/src/platforms/teams/token-provider.js.map +1 -0
- package/dist/src/platforms/teams/trouter.d.ts +31 -0
- package/dist/src/platforms/teams/trouter.d.ts.map +1 -0
- package/dist/src/platforms/teams/trouter.js +181 -0
- package/dist/src/platforms/teams/trouter.js.map +1 -0
- package/dist/src/platforms/teams/types.d.ts +94 -0
- package/dist/src/platforms/teams/types.d.ts.map +1 -1
- package/dist/src/platforms/teams/types.js +30 -0
- package/dist/src/platforms/teams/types.js.map +1 -1
- package/dist/src/platforms/webex/cli.js +1 -1
- package/dist/src/platforms/webex/types.d.ts +1 -1
- package/dist/src/platforms/webexbot/cli.js +1 -1
- package/dist/src/platforms/wechatbot/cli.js +1 -1
- package/dist/src/platforms/whatsapp/cli.js +1 -1
- package/dist/src/platforms/whatsappbot/cli.js +1 -1
- package/dist/src/tui/adapters/imessage-adapter.d.ts +21 -0
- package/dist/src/tui/adapters/imessage-adapter.d.ts.map +1 -0
- package/dist/src/tui/adapters/imessage-adapter.js +110 -0
- package/dist/src/tui/adapters/imessage-adapter.js.map +1 -0
- package/dist/src/tui/adapters/instagram-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/instagram-adapter.js +16 -0
- package/dist/src/tui/adapters/instagram-adapter.js.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.d.ts +3 -0
- package/dist/src/tui/adapters/teams-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.js +21 -0
- package/dist/src/tui/adapters/teams-adapter.js.map +1 -1
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +12 -3
- package/dist/src/tui/app.js.map +1 -1
- package/docs/content/docs/agent-skills.mdx +1 -1
- package/docs/content/docs/cli/imessage.mdx +99 -0
- package/docs/content/docs/cli/instagram.mdx +2 -1
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/teams.mdx +22 -0
- package/docs/content/docs/index.mdx +9 -6
- package/docs/content/docs/quick-start.mdx +2 -0
- package/docs/content/docs/sdk/instagram.mdx +90 -5
- package/docs/content/docs/tui.mdx +4 -3
- package/package.json +10 -2
- package/scripts/postbuild.ts +12 -15
- 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-imessage/SKILL.md +133 -0
- package/skills/agent-imessage/references/authentication.md +66 -0
- package/skills/agent-imessage/references/permissions.md +31 -0
- package/skills/agent-imessage/references/setup.md +49 -0
- package/skills/agent-instagram/SKILL.md +72 -2
- package/skills/agent-instagram/references/authentication.md +11 -0
- package/skills/agent-instagram/references/common-patterns.md +36 -0
- package/skills/agent-instagram/templates/monitor-chat.sh +5 -0
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +59 -6
- package/skills/agent-teams/references/authentication.md +31 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/cli.ts +4 -0
- package/src/platforms/discord/client.test.ts +86 -0
- package/src/platforms/discord/client.ts +10 -3
- package/src/platforms/imessage/cli.ts +39 -0
- package/src/platforms/imessage/client.test.ts +137 -0
- package/src/platforms/imessage/client.ts +253 -0
- package/src/platforms/imessage/commands/auth.ts +142 -0
- package/src/platforms/imessage/commands/chat.ts +51 -0
- package/src/platforms/imessage/commands/doctor.test.ts +80 -0
- package/src/platforms/imessage/commands/doctor.ts +139 -0
- package/src/platforms/imessage/commands/index.ts +6 -0
- package/src/platforms/imessage/commands/message.test.ts +20 -0
- package/src/platforms/imessage/commands/message.ts +151 -0
- package/src/platforms/imessage/commands/setup.test.ts +52 -0
- package/src/platforms/imessage/commands/setup.ts +76 -0
- package/src/platforms/imessage/commands/shared.test.ts +19 -0
- package/src/platforms/imessage/commands/shared.ts +69 -0
- package/src/platforms/imessage/commands/whoami.ts +22 -0
- package/src/platforms/imessage/credential-manager.test.ts +122 -0
- package/src/platforms/imessage/credential-manager.ts +123 -0
- package/src/platforms/imessage/ensure-auth.ts +18 -0
- package/src/platforms/imessage/errors.ts +27 -0
- package/src/platforms/imessage/index.test.ts +13 -0
- package/src/platforms/imessage/index.ts +16 -0
- package/src/platforms/imessage/rpc.ts +157 -0
- package/src/platforms/imessage/test-stub-imsg.mjs +147 -0
- package/src/platforms/imessage/types.test.ts +47 -0
- package/src/platforms/imessage/types.ts +95 -0
- package/src/platforms/instagram/client.test.ts +478 -33
- package/src/platforms/instagram/client.ts +472 -68
- package/src/platforms/instagram/commands/auth.test.ts +177 -1
- package/src/platforms/instagram/commands/auth.ts +182 -11
- package/src/platforms/instagram/credential-manager.test.ts +30 -0
- package/src/platforms/instagram/credential-manager.ts +23 -1
- package/src/platforms/instagram/hybrid-listener.test.ts +135 -0
- package/src/platforms/instagram/hybrid-listener.ts +196 -0
- package/src/platforms/instagram/index.ts +10 -0
- package/src/platforms/instagram/mqtt/connection.ts +72 -0
- package/src/platforms/instagram/mqtt/thrift.test.ts +91 -0
- package/src/platforms/instagram/mqtt/thrift.ts +159 -0
- package/src/platforms/instagram/mqtt/transport.ts +285 -0
- package/src/platforms/instagram/realtime-listener.test.ts +34 -0
- package/src/platforms/instagram/realtime-listener.ts +201 -0
- package/src/platforms/instagram/types.ts +11 -8
- package/src/platforms/teams/app-config.ts +92 -0
- package/src/platforms/teams/client.test.ts +335 -1
- package/src/platforms/teams/client.ts +342 -7
- package/src/platforms/teams/commands/auth.test.ts +93 -1
- package/src/platforms/teams/commands/auth.ts +182 -3
- package/src/platforms/teams/commands/file.test.ts +92 -1
- package/src/platforms/teams/commands/file.ts +76 -1
- package/src/platforms/teams/commands/message.test.ts +95 -1
- package/src/platforms/teams/commands/message.ts +145 -3
- package/src/platforms/teams/credential-manager.test.ts +20 -0
- package/src/platforms/teams/credential-manager.ts +68 -3
- package/src/platforms/teams/device-code.test.ts +311 -0
- package/src/platforms/teams/device-code.ts +277 -0
- package/src/platforms/teams/device-login.test.ts +288 -0
- package/src/platforms/teams/device-login.ts +206 -0
- package/src/platforms/teams/ensure-auth.ts +11 -0
- package/src/platforms/teams/index.test.ts +10 -0
- package/src/platforms/teams/index.ts +16 -1
- package/src/platforms/teams/listener.ts +295 -0
- package/src/platforms/teams/realm-discovery.test.ts +73 -0
- package/src/platforms/teams/realm-discovery.ts +42 -0
- package/src/platforms/teams/token-extractor.ts +69 -0
- package/src/platforms/teams/token-provider.test.ts +227 -0
- package/src/platforms/teams/token-provider.ts +284 -0
- package/src/platforms/teams/trouter.test.ts +114 -0
- package/src/platforms/teams/trouter.ts +228 -0
- package/src/platforms/teams/types.test.ts +17 -0
- package/src/platforms/teams/types.ts +86 -0
- package/src/platforms/webexbot/cli.ts +0 -0
- package/src/tui/adapters/imessage-adapter.ts +131 -0
- package/src/tui/adapters/instagram-adapter.ts +13 -0
- package/src/tui/adapters/teams-adapter.ts +23 -0
- package/src/tui/app.ts +16 -3
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { expect, it } from 'bun:test'
|
|
2
|
+
import { gzipSync } from 'node:zlib'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
buildActivityFrame,
|
|
6
|
+
buildAuthenticateFrame,
|
|
7
|
+
buildEventAck,
|
|
8
|
+
buildPingFrame,
|
|
9
|
+
buildRequestAck,
|
|
10
|
+
buildWebSocketUrl,
|
|
11
|
+
decodeMessageBody,
|
|
12
|
+
extractChatId,
|
|
13
|
+
isMessageLossFrame,
|
|
14
|
+
parseRequestFrame,
|
|
15
|
+
type TrouterInfo,
|
|
16
|
+
} from './trouter'
|
|
17
|
+
|
|
18
|
+
const info: TrouterInfo = {
|
|
19
|
+
socketio: 'https://pub-ent-test.trouter.teams.microsoft.com:443/',
|
|
20
|
+
surl: 'https://pub-ent-test.trouter.teams.microsoft.com:3443/v4/f/HASH/',
|
|
21
|
+
connectparams: { sr: 'abc', issuer: 'prod-2', sig: 'xyz' },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
it('buildWebSocketUrl targets the socket.io v1 websocket path', () => {
|
|
25
|
+
const url = buildWebSocketUrl(info, 'SESSION123', 'endpoint-uuid')
|
|
26
|
+
expect(url.startsWith(`${info.socketio}socket.io/1/websocket/SESSION123?`)).toBe(true)
|
|
27
|
+
expect(url).toContain('epid=endpoint-uuid')
|
|
28
|
+
expect(url).toContain('sr=abc')
|
|
29
|
+
expect(url).toContain('auth=true')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('buildAuthenticateFrame embeds the Bearer id_token and connectparams', () => {
|
|
33
|
+
const frame = buildAuthenticateFrame(info, 'ID_TOKEN')
|
|
34
|
+
expect(frame.startsWith('5:::')).toBe(true)
|
|
35
|
+
const payload = JSON.parse(frame.slice(4))
|
|
36
|
+
expect(payload.name).toBe('user.authenticate')
|
|
37
|
+
expect(payload.args[0].headers.Authorization).toBe('Bearer ID_TOKEN')
|
|
38
|
+
expect(payload.args[0].connectparams).toEqual(info.connectparams)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('buildActivityFrame and buildPingFrame use the sequence number', () => {
|
|
42
|
+
expect(buildActivityFrame(3)).toBe('5:3::{"name":"user.activity","args":[{"state":"active"}]}')
|
|
43
|
+
expect(buildPingFrame(7)).toBe('5:7::{"name":"ping"}')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('buildRequestAck returns a 200 ack for the request id', () => {
|
|
47
|
+
const ack = buildRequestAck(42)
|
|
48
|
+
expect(ack.startsWith('3:::')).toBe(true)
|
|
49
|
+
expect(JSON.parse(ack.slice(4))).toEqual({ id: 42, status: 200, body: '' })
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('buildEventAck acks numbered event frames and ignores others', () => {
|
|
53
|
+
expect(buildEventAck('5:12::{"name":"trouter.message_loss"}')).toBe('6:12::')
|
|
54
|
+
expect(buildEventAck('5:9+::{"name":"x"}')).toBe('6:9::')
|
|
55
|
+
expect(buildEventAck('1::')).toBeNull()
|
|
56
|
+
expect(buildEventAck('6:::1["pong"]')).toBeNull()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('parseRequestFrame parses 3::: frames and rejects others', () => {
|
|
60
|
+
const frame = '3:::{"id":1,"method":"POST","url":"/v4/f/HASH/messaging","body":"{}"}'
|
|
61
|
+
const parsed = parseRequestFrame(frame)
|
|
62
|
+
expect(parsed?.id).toBe(1)
|
|
63
|
+
expect(parsed?.url).toBe('/v4/f/HASH/messaging')
|
|
64
|
+
expect(parseRequestFrame('5:1::{}')).toBeNull()
|
|
65
|
+
expect(parseRequestFrame('3:::not-json')).toBeNull()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('parseRequestFrame rejects frames without a numeric id', () => {
|
|
69
|
+
expect(parseRequestFrame('3:::{"url":"/x/messaging"}')).toBeNull()
|
|
70
|
+
expect(parseRequestFrame('3:::{"id":"abc"}')).toBeNull()
|
|
71
|
+
expect(parseRequestFrame('3:::"just-a-string"')).toBeNull()
|
|
72
|
+
expect(parseRequestFrame('3:::null')).toBeNull()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('isMessageLossFrame detects trouter.message_loss events', () => {
|
|
76
|
+
expect(isMessageLossFrame('5:2::{"name":"trouter.message_loss","args":[{}]}')).toBe(true)
|
|
77
|
+
expect(isMessageLossFrame('5:1::{"name":"trouter.connected","args":[{}]}')).toBe(false)
|
|
78
|
+
expect(isMessageLossFrame('1::')).toBe(false)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('extractChatId pulls the conversation id from a link', () => {
|
|
82
|
+
expect(extractChatId('https://notifications.skype.net/v1/users/ME/conversations/19:uni01_abc@thread.v2')).toBe(
|
|
83
|
+
'19:uni01_abc@thread.v2',
|
|
84
|
+
)
|
|
85
|
+
expect(extractChatId('https://x/conversations/19:uni01_abc@thread.v2/messages/123')).toBe('19:uni01_abc@thread.v2')
|
|
86
|
+
expect(extractChatId(undefined)).toBeNull()
|
|
87
|
+
expect(extractChatId('no-conversation-here')).toBeNull()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('decodeMessageBody parses plain JSON bodies', () => {
|
|
91
|
+
const body = JSON.stringify({ resourceType: 'NewMessage', resource: { id: '1' } })
|
|
92
|
+
expect(decodeMessageBody({}, body)).toEqual({ resourceType: 'NewMessage', resource: { id: '1' } })
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('decodeMessageBody inflates gzip+base64 bodies', () => {
|
|
96
|
+
const inner = JSON.stringify({ resourceType: 'NewMessage' })
|
|
97
|
+
const gzipped = gzipSync(Buffer.from(inner)).toString('base64')
|
|
98
|
+
const decoded = decodeMessageBody({ 'X-Microsoft-Skype-Content-Encoding': 'gzip' }, gzipped)
|
|
99
|
+
expect(decoded).toEqual({ resourceType: 'NewMessage' })
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('decodeMessageBody unwraps nested cp (gzip+base64) payloads', () => {
|
|
103
|
+
const inner = JSON.stringify({ resourceType: 'NewMessage', resource: { content: 'hi' } })
|
|
104
|
+
const cp = gzipSync(Buffer.from(inner)).toString('base64')
|
|
105
|
+
const decoded = decodeMessageBody({}, JSON.stringify({ cp }))
|
|
106
|
+
expect(decoded).toEqual({ resourceType: 'NewMessage', resource: { content: 'hi' } })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('decodeMessageBody unwraps nested gp (base64) payloads', () => {
|
|
110
|
+
const inner = JSON.stringify({ resourceType: 'NewMessage' })
|
|
111
|
+
const gp = Buffer.from(inner).toString('base64')
|
|
112
|
+
const decoded = decodeMessageBody({}, JSON.stringify({ gp }))
|
|
113
|
+
expect(decoded).toEqual({ resourceType: 'NewMessage' })
|
|
114
|
+
})
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { gunzipSync } from 'node:zlib'
|
|
2
|
+
|
|
3
|
+
import type { TeamsAccountType } from './types'
|
|
4
|
+
|
|
5
|
+
// Trouter is Teams' internal real-time notification service. Protocol shape was
|
|
6
|
+
// reverse-engineered from the Teams web client and cross-checked against the
|
|
7
|
+
// open-source purple-teams and eisbaw/ost implementations. It speaks socket.io
|
|
8
|
+
// v1 (colon-prefixed frames) over a WebSocket, NOT engine.io.
|
|
9
|
+
|
|
10
|
+
const TROUTER_BOOTSTRAP_URL = 'https://go.trouter.teams.microsoft.com/v4/a'
|
|
11
|
+
// Personal/TFL accounts register against edge.skype.com; work accounts use
|
|
12
|
+
// teams.microsoft.com.
|
|
13
|
+
const REGISTRAR_URL_PERSONAL = 'https://edge.skype.com/registrar/prod/v2/registrations'
|
|
14
|
+
const REGISTRAR_URL_WORK = 'https://teams.microsoft.com/registrar/prod/V2/registrations'
|
|
15
|
+
const CLIENTINFO_VERSION = '27/1.0.0.2024101502'
|
|
16
|
+
const TC = JSON.stringify({ cv: '2024.04.01.1', ua: 'TeamsCDL', hr: '', v: CLIENTINFO_VERSION })
|
|
17
|
+
const USER_AGENT =
|
|
18
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Electron/35.3.0'
|
|
19
|
+
const REGISTRATION_TTL_SECONDS = 86400
|
|
20
|
+
|
|
21
|
+
export interface TrouterConnectParams {
|
|
22
|
+
[key: string]: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TrouterInfo {
|
|
26
|
+
socketio: string
|
|
27
|
+
surl: string
|
|
28
|
+
connectparams: TrouterConnectParams
|
|
29
|
+
ccid?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface TrouterRequestFrame {
|
|
33
|
+
id: number
|
|
34
|
+
url?: string
|
|
35
|
+
headers?: Record<string, string>
|
|
36
|
+
body?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface RegistrationSpec {
|
|
40
|
+
appId: string
|
|
41
|
+
templateKey: string
|
|
42
|
+
pathSuffix: string
|
|
43
|
+
productContext: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const REGISTRATION_SPECS: RegistrationSpec[] = [
|
|
47
|
+
{
|
|
48
|
+
appId: 'NextGenCalling',
|
|
49
|
+
templateKey: 'DesktopNgc_2.5:SkypeNgc',
|
|
50
|
+
pathSuffix: 'NGCallManagerWin',
|
|
51
|
+
productContext: '',
|
|
52
|
+
},
|
|
53
|
+
{ appId: 'SkypeSpacesWeb', templateKey: 'SkypeSpacesWeb_2.4', pathSuffix: 'SkypeSpacesWeb', productContext: '' },
|
|
54
|
+
{ appId: 'TeamsCDLWebWorker', templateKey: 'TeamsCDLWebWorker_2.6', pathSuffix: '', productContext: 'TFL' },
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
export function buildTrouterQuery(info: TrouterInfo, endpointId: string): string {
|
|
58
|
+
const params = new URLSearchParams()
|
|
59
|
+
params.set('v', 'v4')
|
|
60
|
+
for (const [key, value] of Object.entries(info.connectparams)) params.set(key, value)
|
|
61
|
+
params.set('tc', TC)
|
|
62
|
+
params.set('timeout', '40')
|
|
63
|
+
params.set('auth', 'true')
|
|
64
|
+
params.set('epid', endpointId)
|
|
65
|
+
if (info.ccid) params.set('ccid', info.ccid)
|
|
66
|
+
params.set('con_num', `${Date.now()}_1`)
|
|
67
|
+
return params.toString()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function fetchTrouterInfo(skypeToken: string, endpointId: string): Promise<TrouterInfo> {
|
|
71
|
+
const response = await fetch(`${TROUTER_BOOTSTRAP_URL}?epid=${encodeURIComponent(endpointId)}`, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { 'x-skypetoken': skypeToken, 'Content-Length': '0', 'User-Agent': USER_AGENT },
|
|
74
|
+
})
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new Error(`Trouter bootstrap failed: ${response.status} ${response.statusText}`)
|
|
77
|
+
}
|
|
78
|
+
return (await response.json()) as TrouterInfo
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function fetchTrouterSessionId(
|
|
82
|
+
info: TrouterInfo,
|
|
83
|
+
skypeToken: string,
|
|
84
|
+
endpointId: string,
|
|
85
|
+
): Promise<string> {
|
|
86
|
+
const url = `${info.socketio}socket.io/1/?${buildTrouterQuery(info, endpointId)}`
|
|
87
|
+
const response = await fetch(url, { headers: { 'X-Skypetoken': skypeToken, 'User-Agent': USER_AGENT } })
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw new Error(`Trouter handshake failed: ${response.status} ${response.statusText}`)
|
|
90
|
+
}
|
|
91
|
+
const body = await response.text()
|
|
92
|
+
return body.split(':')[0]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function buildWebSocketUrl(info: TrouterInfo, sessionId: string, endpointId: string): string {
|
|
96
|
+
return `${info.socketio}socket.io/1/websocket/${sessionId}?${buildTrouterQuery(info, endpointId)}`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function buildAuthenticateFrame(info: TrouterInfo, idToken: string): string {
|
|
100
|
+
const message = {
|
|
101
|
+
name: 'user.authenticate',
|
|
102
|
+
args: [
|
|
103
|
+
{
|
|
104
|
+
headers: {
|
|
105
|
+
'X-Ms-Test-User': 'False',
|
|
106
|
+
Authorization: `Bearer ${idToken}`,
|
|
107
|
+
'X-MS-Migration': 'True',
|
|
108
|
+
},
|
|
109
|
+
connectparams: info.connectparams,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
}
|
|
113
|
+
return `5:::${JSON.stringify(message)}`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function buildActivityFrame(sequence: number): string {
|
|
117
|
+
return `5:${sequence}::{"name":"user.activity","args":[{"state":"active"}]}`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function buildPingFrame(sequence: number): string {
|
|
121
|
+
return `5:${sequence}::{"name":"ping"}`
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Trouter delivers messages as socket.io "3:::" data frames, which require an
|
|
125
|
+
// HTTP-style 200 acknowledgement or the server marks the endpoint unresponsive
|
|
126
|
+
// and stops routing.
|
|
127
|
+
export function buildRequestAck(requestId: number): string {
|
|
128
|
+
return `3:::${JSON.stringify({ id: requestId, status: 200, body: '' })}`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Server "5:<id>+::" event frames must be acked with "6:<id>::" or trouter
|
|
132
|
+
// eventually drops the connection.
|
|
133
|
+
export function buildEventAck(frame: string): string | null {
|
|
134
|
+
const match = frame.match(/^5:(\d+)\+?::/)
|
|
135
|
+
return match ? `6:${match[1]}::` : null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function registerEndpoint(
|
|
139
|
+
info: TrouterInfo,
|
|
140
|
+
skypeToken: string,
|
|
141
|
+
idToken: string,
|
|
142
|
+
endpointId: string,
|
|
143
|
+
accountType: TeamsAccountType,
|
|
144
|
+
makeId: () => string,
|
|
145
|
+
): Promise<void> {
|
|
146
|
+
const registrarUrl = accountType === 'personal' ? REGISTRAR_URL_PERSONAL : REGISTRAR_URL_WORK
|
|
147
|
+
|
|
148
|
+
for (const spec of REGISTRATION_SPECS) {
|
|
149
|
+
// The messaging worker must reuse the endpoint id; call apps get fresh ids.
|
|
150
|
+
const registrationId = spec.appId === 'TeamsCDLWebWorker' ? endpointId : makeId()
|
|
151
|
+
const payload = {
|
|
152
|
+
clientDescription: {
|
|
153
|
+
appId: spec.appId,
|
|
154
|
+
aesKey: '',
|
|
155
|
+
languageId: 'en-US',
|
|
156
|
+
platform: 'edge',
|
|
157
|
+
templateKey: spec.templateKey,
|
|
158
|
+
platformUIVersion: CLIENTINFO_VERSION,
|
|
159
|
+
productContext: accountType === 'personal' ? spec.productContext : '',
|
|
160
|
+
},
|
|
161
|
+
registrationId,
|
|
162
|
+
nodeId: '',
|
|
163
|
+
transports: { TROUTER: [{ context: '', path: `${info.surl}${spec.pathSuffix}`, ttl: REGISTRATION_TTL_SECONDS }] },
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const response = await fetch(registrarUrl, {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: {
|
|
169
|
+
'Content-Type': 'application/json',
|
|
170
|
+
'X-Skypetoken': skypeToken,
|
|
171
|
+
Authorization: `Bearer ${idToken}`,
|
|
172
|
+
'User-Agent': USER_AGENT,
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify(payload),
|
|
175
|
+
})
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
throw new Error(`Trouter registration for ${spec.appId} failed: ${response.status}`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function parseRequestFrame(frame: string): TrouterRequestFrame | null {
|
|
183
|
+
if (!frame.startsWith('3:::')) return null
|
|
184
|
+
try {
|
|
185
|
+
const parsed = JSON.parse(frame.slice(4)) as unknown
|
|
186
|
+
if (typeof parsed !== 'object' || parsed === null) return null
|
|
187
|
+
const { id } = parsed as { id?: unknown }
|
|
188
|
+
if (typeof id !== 'number' || !Number.isFinite(id)) return null
|
|
189
|
+
return parsed as TrouterRequestFrame
|
|
190
|
+
} catch {
|
|
191
|
+
return null
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function isMessageLossFrame(frame: string): boolean {
|
|
196
|
+
if (!frame.startsWith('5:')) return false
|
|
197
|
+
const jsonStart = frame.indexOf('{')
|
|
198
|
+
if (jsonStart === -1) return false
|
|
199
|
+
try {
|
|
200
|
+
const payload = JSON.parse(frame.slice(jsonStart)) as { name?: string }
|
|
201
|
+
return payload.name === 'trouter.message_loss'
|
|
202
|
+
} catch {
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Trouter message bodies may be gzip+base64 encoded, and nest a further encoded
|
|
208
|
+
// payload under `cp` (gzip+base64) or `gp` (base64).
|
|
209
|
+
export function decodeMessageBody(headers: Record<string, string>, body: string): Record<string, unknown> {
|
|
210
|
+
let raw = body
|
|
211
|
+
if (headers['X-Microsoft-Skype-Content-Encoding'] === 'gzip') {
|
|
212
|
+
raw = gunzipSync(Buffer.from(body, 'base64')).toString('utf8')
|
|
213
|
+
}
|
|
214
|
+
const obj = JSON.parse(raw) as Record<string, unknown>
|
|
215
|
+
if (typeof obj.cp === 'string') {
|
|
216
|
+
return JSON.parse(gunzipSync(Buffer.from(obj.cp, 'base64')).toString('utf8')) as Record<string, unknown>
|
|
217
|
+
}
|
|
218
|
+
if (typeof obj.gp === 'string') {
|
|
219
|
+
return JSON.parse(Buffer.from(obj.gp, 'base64').toString('utf8')) as Record<string, unknown>
|
|
220
|
+
}
|
|
221
|
+
return obj
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function extractChatId(link: string | undefined): string | null {
|
|
225
|
+
if (!link) return null
|
|
226
|
+
const match = link.match(/conversations\/([^/]+)/)
|
|
227
|
+
return match ? decodeURIComponent(match[1]) : null
|
|
228
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
TeamsFileSchema,
|
|
9
9
|
TeamsMessageSchema,
|
|
10
10
|
TeamsReactionSchema,
|
|
11
|
+
TeamsSearchResultSchema,
|
|
11
12
|
TeamsTeamSchema,
|
|
12
13
|
TeamsUserSchema,
|
|
13
14
|
} from './types'
|
|
@@ -87,6 +88,22 @@ it('TeamsMessageSchema rejects missing required fields', () => {
|
|
|
87
88
|
expect(result.success).toBe(false)
|
|
88
89
|
})
|
|
89
90
|
|
|
91
|
+
it('TeamsSearchResultSchema validates search result shape', () => {
|
|
92
|
+
const result = TeamsSearchResultSchema.safeParse({
|
|
93
|
+
id: 'msg-123',
|
|
94
|
+
content: 'Deploy complete',
|
|
95
|
+
author: { id: 'user-123', displayName: 'Alice' },
|
|
96
|
+
channel_id: 'channel-123',
|
|
97
|
+
thread_id: 'thread-123',
|
|
98
|
+
team_name: 'Team One',
|
|
99
|
+
channel_name: 'General',
|
|
100
|
+
timestamp: '2024-01-01T00:00:00.000Z',
|
|
101
|
+
permalink: 'https://teams.microsoft.com/l/message/msg-123',
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
expect(result.success).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
90
107
|
// TeamsUserSchema tests
|
|
91
108
|
it('TeamsUserSchema validates correct user', () => {
|
|
92
109
|
const result = TeamsUserSchema.safeParse({
|
|
@@ -26,6 +26,24 @@ export interface TeamsMessage {
|
|
|
26
26
|
}
|
|
27
27
|
content: string
|
|
28
28
|
timestamp: string
|
|
29
|
+
root_message_id?: string
|
|
30
|
+
parent_message_id?: string
|
|
31
|
+
is_thread_reply?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TeamsSearchResult {
|
|
35
|
+
id: string
|
|
36
|
+
content: string
|
|
37
|
+
author: {
|
|
38
|
+
id: string
|
|
39
|
+
displayName: string
|
|
40
|
+
}
|
|
41
|
+
channel_id: string
|
|
42
|
+
thread_id?: string
|
|
43
|
+
team_name?: string
|
|
44
|
+
channel_name?: string
|
|
45
|
+
timestamp: string
|
|
46
|
+
permalink?: string
|
|
29
47
|
}
|
|
30
48
|
|
|
31
49
|
export interface TeamsUser {
|
|
@@ -56,6 +74,8 @@ export interface TeamsFile {
|
|
|
56
74
|
size: number
|
|
57
75
|
url: string
|
|
58
76
|
contentType?: string
|
|
77
|
+
sharepoint_url?: string
|
|
78
|
+
object_url?: string
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
export interface TeamsCredentials {
|
|
@@ -67,6 +87,8 @@ export type TeamsAccountType = 'work' | 'personal'
|
|
|
67
87
|
|
|
68
88
|
export type TeamsRegion = 'amer' | 'emea' | 'apac'
|
|
69
89
|
|
|
90
|
+
export type TeamsAuthMethod = 'device-code' | 'browser' | 'extracted' | 'manual'
|
|
91
|
+
|
|
70
92
|
export interface TeamsAccount {
|
|
71
93
|
token: string
|
|
72
94
|
token_expires_at?: string
|
|
@@ -81,6 +103,10 @@ export interface TeamsAccount {
|
|
|
81
103
|
team_name: string
|
|
82
104
|
}
|
|
83
105
|
>
|
|
106
|
+
auth_method?: TeamsAuthMethod
|
|
107
|
+
aad_refresh_token?: string
|
|
108
|
+
aad_client_id?: string
|
|
109
|
+
aad_tenant_id?: string
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
export interface TeamsConfig {
|
|
@@ -125,6 +151,24 @@ export const TeamsMessageSchema = z.object({
|
|
|
125
151
|
}),
|
|
126
152
|
content: z.string(),
|
|
127
153
|
timestamp: z.string(),
|
|
154
|
+
root_message_id: z.string().optional(),
|
|
155
|
+
parent_message_id: z.string().optional(),
|
|
156
|
+
is_thread_reply: z.boolean().optional(),
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
export const TeamsSearchResultSchema = z.object({
|
|
160
|
+
id: z.string(),
|
|
161
|
+
content: z.string(),
|
|
162
|
+
author: z.object({
|
|
163
|
+
id: z.string(),
|
|
164
|
+
displayName: z.string(),
|
|
165
|
+
}),
|
|
166
|
+
channel_id: z.string(),
|
|
167
|
+
thread_id: z.string().optional(),
|
|
168
|
+
team_name: z.string().optional(),
|
|
169
|
+
channel_name: z.string().optional(),
|
|
170
|
+
timestamp: z.string(),
|
|
171
|
+
permalink: z.string().optional(),
|
|
128
172
|
})
|
|
129
173
|
|
|
130
174
|
export const TeamsUserSchema = z.object({
|
|
@@ -155,6 +199,8 @@ export const TeamsFileSchema = z.object({
|
|
|
155
199
|
size: z.number(),
|
|
156
200
|
url: z.string(),
|
|
157
201
|
contentType: z.string().optional(),
|
|
202
|
+
sharepoint_url: z.string().optional(),
|
|
203
|
+
object_url: z.string().optional(),
|
|
158
204
|
})
|
|
159
205
|
|
|
160
206
|
export const TeamsCredentialsSchema = z.object({
|
|
@@ -166,6 +212,8 @@ export const TeamsAccountTypeSchema = z.enum(['work', 'personal'])
|
|
|
166
212
|
|
|
167
213
|
export const TeamsRegionSchema = z.enum(['amer', 'emea', 'apac'])
|
|
168
214
|
|
|
215
|
+
export const TeamsAuthMethodSchema = z.enum(['device-code', 'browser', 'extracted', 'manual'])
|
|
216
|
+
|
|
169
217
|
export const TeamsAccountSchema = z.object({
|
|
170
218
|
token: z.string(),
|
|
171
219
|
token_expires_at: z.string().optional(),
|
|
@@ -180,6 +228,10 @@ export const TeamsAccountSchema = z.object({
|
|
|
180
228
|
team_name: z.string(),
|
|
181
229
|
}),
|
|
182
230
|
),
|
|
231
|
+
auth_method: TeamsAuthMethodSchema.optional(),
|
|
232
|
+
aad_refresh_token: z.string().optional(),
|
|
233
|
+
aad_client_id: z.string().optional(),
|
|
234
|
+
aad_tenant_id: z.string().optional(),
|
|
183
235
|
})
|
|
184
236
|
|
|
185
237
|
export const TeamsConfigSchema = z.object({
|
|
@@ -200,6 +252,31 @@ export const TeamsConfigLegacySchema = z.object({
|
|
|
200
252
|
),
|
|
201
253
|
})
|
|
202
254
|
|
|
255
|
+
export interface TeamsRealtimeMessage {
|
|
256
|
+
id: string
|
|
257
|
+
chatId: string
|
|
258
|
+
content: string
|
|
259
|
+
author: {
|
|
260
|
+
id: string
|
|
261
|
+
displayName: string
|
|
262
|
+
}
|
|
263
|
+
messageType: string
|
|
264
|
+
timestamp: string
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface TeamsTrouterGenericEvent {
|
|
268
|
+
resourceType: string
|
|
269
|
+
[key: string]: unknown
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface TeamsListenerEventMap {
|
|
273
|
+
message: [message: TeamsRealtimeMessage]
|
|
274
|
+
teams_event: [event: TeamsTrouterGenericEvent]
|
|
275
|
+
connected: [info: { endpointId: string }]
|
|
276
|
+
disconnected: []
|
|
277
|
+
error: [error: Error]
|
|
278
|
+
}
|
|
279
|
+
|
|
203
280
|
export class TeamsError extends Error {
|
|
204
281
|
code: string
|
|
205
282
|
|
|
@@ -209,3 +286,12 @@ export class TeamsError extends Error {
|
|
|
209
286
|
this.code = code
|
|
210
287
|
}
|
|
211
288
|
}
|
|
289
|
+
|
|
290
|
+
export class TeamsAuthCapabilityError extends Error {
|
|
291
|
+
constructor() {
|
|
292
|
+
super(
|
|
293
|
+
'Requires `agent-teams auth login` — cookie-based auth (`auth extract`) can only provide a Skype token, not the Microsoft token needed for search or SharePoint/OneDrive file downloads.',
|
|
294
|
+
)
|
|
295
|
+
this.name = 'TeamsAuthCapabilityError'
|
|
296
|
+
}
|
|
297
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ImsgClient } from '@/platforms/imessage/client'
|
|
2
|
+
import { IMessageCredentialManager } from '@/platforms/imessage/credential-manager'
|
|
3
|
+
|
|
4
|
+
import type { AuthHint, AuthIO, PlatformAdapter, UnifiedChannel, UnifiedMessage, Workspace } from './types'
|
|
5
|
+
|
|
6
|
+
export class IMessageAdapter implements PlatformAdapter {
|
|
7
|
+
readonly name = 'iMessage'
|
|
8
|
+
|
|
9
|
+
private client: ImsgClient | null = null
|
|
10
|
+
private credManager = new IMessageCredentialManager()
|
|
11
|
+
private currentAccount: Workspace | null = null
|
|
12
|
+
private stopWatch: (() => Promise<void>) | null = null
|
|
13
|
+
|
|
14
|
+
async login(): Promise<void> {
|
|
15
|
+
const resolved = await this.credManager.resolveAccount()
|
|
16
|
+
if (!resolved) {
|
|
17
|
+
throw new Error('iMessage is not configured. Run "agent-imessage setup" or set AGENT_IMESSAGE_BIN.')
|
|
18
|
+
}
|
|
19
|
+
const client = new ImsgClient()
|
|
20
|
+
await client.login({ binaryPath: resolved.binary_path, region: resolved.region })
|
|
21
|
+
await client.connect()
|
|
22
|
+
this.client = client
|
|
23
|
+
|
|
24
|
+
const config = await this.credManager.loadConfig()
|
|
25
|
+
if (config.current && config.accounts[config.current]) {
|
|
26
|
+
const acct = config.accounts[config.current]
|
|
27
|
+
this.currentAccount = { id: acct.account_id, name: acct.label ?? acct.account_id }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getChannels(): Promise<UnifiedChannel[]> {
|
|
32
|
+
const client = this.ensureClient()
|
|
33
|
+
const chats = await client.listChats(50)
|
|
34
|
+
return chats.map((chat) => ({ id: String(chat.id), name: chat.name }))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getMessages(channelId: string, limit = 50): Promise<UnifiedMessage[]> {
|
|
38
|
+
const client = this.ensureClient()
|
|
39
|
+
const messages = await client.getMessages(Number.parseInt(channelId, 10), limit)
|
|
40
|
+
return messages.map((msg) => ({
|
|
41
|
+
id: msg.guid,
|
|
42
|
+
channelId,
|
|
43
|
+
author: msg.is_outgoing ? 'you' : (msg.from_name ?? msg.from ?? 'unknown'),
|
|
44
|
+
content: msg.text ?? '',
|
|
45
|
+
timestamp: msg.timestamp,
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async sendMessage(channelId: string, text: string): Promise<void> {
|
|
50
|
+
const client = this.ensureClient()
|
|
51
|
+
await client.sendMessage({ chatId: Number.parseInt(channelId, 10) }, text)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async startListening(onMessage: (msg: UnifiedMessage) => void): Promise<void> {
|
|
55
|
+
const client = this.ensureClient()
|
|
56
|
+
this.stopWatch = await client.watch(
|
|
57
|
+
(msg) =>
|
|
58
|
+
onMessage({
|
|
59
|
+
id: msg.guid,
|
|
60
|
+
channelId: String(msg.chat_id),
|
|
61
|
+
author: msg.is_outgoing ? 'you' : (msg.from_name ?? msg.from ?? 'unknown'),
|
|
62
|
+
content: msg.text ?? '',
|
|
63
|
+
timestamp: msg.timestamp,
|
|
64
|
+
}),
|
|
65
|
+
{ onError: () => undefined },
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
stopListening(): void {
|
|
70
|
+
void this.stopWatch?.()
|
|
71
|
+
this.stopWatch = null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getWorkspaces(): Promise<Workspace[]> {
|
|
75
|
+
const accounts = await this.credManager.listAccounts()
|
|
76
|
+
return accounts.map((acct) => ({ id: acct.account_id, name: acct.label ?? acct.account_id }))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async switchWorkspace(accountId: string): Promise<void> {
|
|
80
|
+
if (this.client) {
|
|
81
|
+
await this.client.close().catch(() => {})
|
|
82
|
+
this.client = null
|
|
83
|
+
}
|
|
84
|
+
const account = await this.credManager.getAccount(accountId)
|
|
85
|
+
if (!account) throw new Error(`Account ${accountId} not found`)
|
|
86
|
+
|
|
87
|
+
const client = new ImsgClient()
|
|
88
|
+
await client.login({ binaryPath: account.binary_path, region: account.region })
|
|
89
|
+
await client.connect()
|
|
90
|
+
this.client = client
|
|
91
|
+
this.currentAccount = { id: account.account_id, name: account.label ?? account.account_id }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getCurrentWorkspace(): Workspace | null {
|
|
95
|
+
return this.currentAccount
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getAuthHint(): AuthHint {
|
|
99
|
+
return {
|
|
100
|
+
command: 'agent-imessage setup',
|
|
101
|
+
description: 'iMessage runs on this Mac via the imsg tool. Run the guided setup below.',
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async authenticate(io: AuthIO): Promise<void> {
|
|
106
|
+
const bin = (await io.prompt('Path to imsg binary (blank for "imsg" on PATH)')).trim() || undefined
|
|
107
|
+
const client = new ImsgClient()
|
|
108
|
+
await client.login({ binaryPath: bin })
|
|
109
|
+
await client.connect()
|
|
110
|
+
|
|
111
|
+
const { createAccountId } = await import('@/platforms/imessage/types')
|
|
112
|
+
const now = new Date().toISOString()
|
|
113
|
+
const accountId = createAccountId(bin ?? 'default')
|
|
114
|
+
await this.credManager.setAccount({
|
|
115
|
+
account_id: accountId,
|
|
116
|
+
provider: 'imsg',
|
|
117
|
+
binary_path: bin,
|
|
118
|
+
created_at: now,
|
|
119
|
+
updated_at: now,
|
|
120
|
+
})
|
|
121
|
+
await this.credManager.setCurrent(accountId)
|
|
122
|
+
|
|
123
|
+
this.client = client
|
|
124
|
+
this.currentAccount = { id: accountId, name: accountId }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private ensureClient(): ImsgClient {
|
|
128
|
+
if (!this.client) throw new Error('Not logged in. Call login() first.')
|
|
129
|
+
return this.client
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -107,8 +107,21 @@ export class InstagramAdapter implements PlatformAdapter {
|
|
|
107
107
|
if (!code) throw new Error('Verification code is required')
|
|
108
108
|
const challengeResult = await client.challengeSubmitCode(result.challengePath, code)
|
|
109
109
|
userId = challengeResult.userId || userId
|
|
110
|
+
} else if (result.oneClickEmailAvailable) {
|
|
111
|
+
const { parseOneClickLoginLink } = await import('@/platforms/instagram/client')
|
|
112
|
+
io.print('Password login was rejected; this account can log in by email. Sending a login email...')
|
|
113
|
+
const { contactPoint } = await client.sendOneClickLoginEmail(username)
|
|
114
|
+
io.print(contactPoint ? `Login email sent to ${contactPoint}.` : 'Login email sent.')
|
|
115
|
+
const link = await io.prompt('Paste the "Login as ..." link from the email')
|
|
116
|
+
if (!link) throw new Error('Login link is required')
|
|
117
|
+
const parsed = parseOneClickLoginLink(link)
|
|
118
|
+
if (!parsed) throw new Error('Could not read uid and token from the pasted link')
|
|
119
|
+
const emailResult = await client.oneClickLogin(parsed.uid, parsed.token)
|
|
120
|
+
userId = emailResult.userId || userId
|
|
110
121
|
}
|
|
111
122
|
|
|
123
|
+
if (!userId) throw new Error('Login did not complete')
|
|
124
|
+
|
|
112
125
|
const now = new Date().toISOString()
|
|
113
126
|
await this.credManager.setAccount({
|
|
114
127
|
account_id: accountId,
|