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
|
@@ -164,6 +164,38 @@ describe('DiscordClient', () => {
|
|
|
164
164
|
expect(fetchCalls[0].options?.method).toBe('POST')
|
|
165
165
|
expect(fetchCalls[0].options?.body).toBe(JSON.stringify({ content: 'Hello world' }))
|
|
166
166
|
})
|
|
167
|
+
|
|
168
|
+
it('includes message_reference when reply_to is provided', async () => {
|
|
169
|
+
mockResponse({
|
|
170
|
+
id: 'msg1',
|
|
171
|
+
channel_id: 'ch1',
|
|
172
|
+
author: { id: '123', username: 'bot' },
|
|
173
|
+
content: 'Reply text',
|
|
174
|
+
timestamp: '2024-01-01T00:00:00.000Z',
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const client = await new DiscordClient().login({ token: 'test-token' })
|
|
178
|
+
await client.sendMessage('ch1', 'Reply text', { reply_to: 'parent123' })
|
|
179
|
+
|
|
180
|
+
expect(fetchCalls[0].options?.body).toBe(
|
|
181
|
+
JSON.stringify({ content: 'Reply text', message_reference: { message_id: 'parent123' } }),
|
|
182
|
+
)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('omits message_reference when reply_to is not provided', async () => {
|
|
186
|
+
mockResponse({
|
|
187
|
+
id: 'msg1',
|
|
188
|
+
channel_id: 'ch1',
|
|
189
|
+
author: { id: '123', username: 'bot' },
|
|
190
|
+
content: 'Hello world',
|
|
191
|
+
timestamp: '2024-01-01T00:00:00.000Z',
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const client = await new DiscordClient().login({ token: 'test-token' })
|
|
195
|
+
await client.sendMessage('ch1', 'Hello world')
|
|
196
|
+
|
|
197
|
+
expect(fetchCalls[0].options?.body).toBe(JSON.stringify({ content: 'Hello world' }))
|
|
198
|
+
})
|
|
167
199
|
})
|
|
168
200
|
|
|
169
201
|
describe('getMessages', () => {
|
|
@@ -308,6 +340,60 @@ describe('DiscordClient', () => {
|
|
|
308
340
|
expect(fetchCalls[0].url).toBe('https://discord.com/api/v10/channels/ch1/messages')
|
|
309
341
|
expect(fetchCalls[0].options?.method).toBe('POST')
|
|
310
342
|
})
|
|
343
|
+
|
|
344
|
+
it('includes payload_json with message_reference when reply_to is provided', async () => {
|
|
345
|
+
const tempFile = '/tmp/test-upload-reply.txt'
|
|
346
|
+
await Bun.write(tempFile, 'test content')
|
|
347
|
+
|
|
348
|
+
mockResponse({
|
|
349
|
+
id: 'msg1',
|
|
350
|
+
channel_id: 'ch1',
|
|
351
|
+
author: { id: '123', username: 'bot' },
|
|
352
|
+
content: '',
|
|
353
|
+
timestamp: '2024-01-01T00:00:00.000Z',
|
|
354
|
+
attachments: [
|
|
355
|
+
{
|
|
356
|
+
id: 'att1',
|
|
357
|
+
filename: 'test-upload-reply.txt',
|
|
358
|
+
size: 12,
|
|
359
|
+
url: 'https://cdn.discord.com/attachments/ch1/att1/test-upload-reply.txt',
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
const client = await new DiscordClient().login({ token: 'test-token' })
|
|
365
|
+
await client.uploadFile('ch1', tempFile, { reply_to: 'parent123' })
|
|
366
|
+
|
|
367
|
+
const formData = fetchCalls[0].options?.body as FormData
|
|
368
|
+
expect(formData.get('payload_json')).toBe(JSON.stringify({ message_reference: { message_id: 'parent123' } }))
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
it('omits payload_json when reply_to is not provided', async () => {
|
|
372
|
+
const tempFile = '/tmp/test-upload-no-reply.txt'
|
|
373
|
+
await Bun.write(tempFile, 'test content')
|
|
374
|
+
|
|
375
|
+
mockResponse({
|
|
376
|
+
id: 'msg1',
|
|
377
|
+
channel_id: 'ch1',
|
|
378
|
+
author: { id: '123', username: 'bot' },
|
|
379
|
+
content: '',
|
|
380
|
+
timestamp: '2024-01-01T00:00:00.000Z',
|
|
381
|
+
attachments: [
|
|
382
|
+
{
|
|
383
|
+
id: 'att1',
|
|
384
|
+
filename: 'test-upload-no-reply.txt',
|
|
385
|
+
size: 12,
|
|
386
|
+
url: 'https://cdn.discord.com/attachments/ch1/att1/test-upload-no-reply.txt',
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
const client = await new DiscordClient().login({ token: 'test-token' })
|
|
392
|
+
await client.uploadFile('ch1', tempFile)
|
|
393
|
+
|
|
394
|
+
const formData = fetchCalls[0].options?.body as FormData
|
|
395
|
+
expect(formData.get('payload_json')).toBeNull()
|
|
396
|
+
})
|
|
311
397
|
})
|
|
312
398
|
|
|
313
399
|
describe('listFiles', () => {
|
|
@@ -226,8 +226,12 @@ export class DiscordClient {
|
|
|
226
226
|
return this.request<DiscordChannel>('GET', `/channels/${channelId}`)
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
async sendMessage(channelId: string, content: string): Promise<DiscordMessage> {
|
|
230
|
-
|
|
229
|
+
async sendMessage(channelId: string, content: string, options?: { reply_to?: string }): Promise<DiscordMessage> {
|
|
230
|
+
const body: Record<string, unknown> = { content }
|
|
231
|
+
if (options?.reply_to) {
|
|
232
|
+
body.message_reference = { message_id: options.reply_to }
|
|
233
|
+
}
|
|
234
|
+
return this.request<DiscordMessage>('POST', `/channels/${channelId}/messages`, body)
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
async getMessages(channelId: string, limit: number = 50): Promise<DiscordMessage[]> {
|
|
@@ -270,11 +274,14 @@ export class DiscordClient {
|
|
|
270
274
|
return this.request<DiscordUser>('GET', `/users/${userId}`)
|
|
271
275
|
}
|
|
272
276
|
|
|
273
|
-
async uploadFile(channelId: string, filePath: string): Promise<DiscordFile> {
|
|
277
|
+
async uploadFile(channelId: string, filePath: string, options?: { reply_to?: string }): Promise<DiscordFile> {
|
|
274
278
|
const fileBuffer = await readFile(filePath)
|
|
275
279
|
const filename = filePath.split('/').pop() || 'file'
|
|
276
280
|
|
|
277
281
|
const formData = new FormData()
|
|
282
|
+
if (options?.reply_to) {
|
|
283
|
+
formData.append('payload_json', JSON.stringify({ message_reference: { message_id: options.reply_to } }))
|
|
284
|
+
}
|
|
278
285
|
formData.append('files[0]', new Blob([fileBuffer]), filename)
|
|
279
286
|
|
|
280
287
|
interface MessageWithAttachments extends DiscordMessage {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import type { Command as CommandType } from 'commander'
|
|
4
|
+
import { Command } from 'commander'
|
|
5
|
+
|
|
6
|
+
import pkg from '../../../package.json' with { type: 'json' }
|
|
7
|
+
import { authCommand, chatCommand, doctorCommand, messageCommand, setupCommand, whoamiCommand } from './commands/index'
|
|
8
|
+
import { ensureIMessageAuth } from './ensure-auth'
|
|
9
|
+
|
|
10
|
+
const UNGUARDED = new Set(['auth', 'setup', 'doctor'])
|
|
11
|
+
|
|
12
|
+
function isUnguarded(command: CommandType): boolean {
|
|
13
|
+
let cmd: CommandType | null = command
|
|
14
|
+
while (cmd) {
|
|
15
|
+
if (UNGUARDED.has(cmd.name())) return true
|
|
16
|
+
cmd = cmd.parent
|
|
17
|
+
}
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const program = new Command()
|
|
22
|
+
|
|
23
|
+
program.name('agent-imessage').description('CLI tool for iMessage via imsg (local, on-Mac)').version(pkg.version)
|
|
24
|
+
|
|
25
|
+
program.hook('preAction', async (_thisCommand, actionCommand) => {
|
|
26
|
+
if (isUnguarded(actionCommand)) return
|
|
27
|
+
await ensureIMessageAuth()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
program.addCommand(authCommand)
|
|
31
|
+
program.addCommand(setupCommand)
|
|
32
|
+
program.addCommand(doctorCommand)
|
|
33
|
+
program.addCommand(chatCommand)
|
|
34
|
+
program.addCommand(messageCommand)
|
|
35
|
+
program.addCommand(whoamiCommand)
|
|
36
|
+
|
|
37
|
+
program.parse(process.argv)
|
|
38
|
+
|
|
39
|
+
export default program
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { ImsgClient } from './client'
|
|
5
|
+
import { IMessageError } from './types'
|
|
6
|
+
|
|
7
|
+
const STUB = join(import.meta.dir, 'test-stub-imsg.mjs')
|
|
8
|
+
|
|
9
|
+
function clientWith(mode = 'ok'): ImsgClient {
|
|
10
|
+
process.env.IMSG_STUB_MODE = mode
|
|
11
|
+
return new ImsgClient()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('ImsgClient (against stub imsg binary)', () => {
|
|
15
|
+
it('getVersion returns the imsg version', async () => {
|
|
16
|
+
const c = await clientWith().login({ binaryPath: STUB })
|
|
17
|
+
expect(await c.getVersion()).toBe('0.11.1')
|
|
18
|
+
await c.close()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('connect succeeds and listChats maps group/individual + fields', async () => {
|
|
22
|
+
const c = await clientWith().login({ binaryPath: STUB })
|
|
23
|
+
await c.connect()
|
|
24
|
+
const chats = await c.listChats(10)
|
|
25
|
+
expect(chats[0]).toMatchObject({ id: 42, name: 'Jane', is_group: false, service: 'iMessage' })
|
|
26
|
+
expect(chats[1]).toMatchObject({ id: 43, name: 'Crew', is_group: true })
|
|
27
|
+
expect(chats[1]?.participants).toHaveLength(2)
|
|
28
|
+
await c.close()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('connect throws full_disk_access when the DB is not readable', async () => {
|
|
32
|
+
const c = await clientWith('fda_denied').login({ binaryPath: STUB })
|
|
33
|
+
try {
|
|
34
|
+
await c.connect()
|
|
35
|
+
throw new Error('should have thrown')
|
|
36
|
+
} catch (error) {
|
|
37
|
+
expect((error as IMessageError).code).toBe('full_disk_access')
|
|
38
|
+
expect((error as IMessageError).suggestion).toContain('Full Disk Access')
|
|
39
|
+
} finally {
|
|
40
|
+
await c.close()
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('getMessages maps fields oldest-first with is_outgoing', async () => {
|
|
45
|
+
const c = await clientWith().login({ binaryPath: STUB })
|
|
46
|
+
await c.connect()
|
|
47
|
+
const msgs = await c.getMessages(42, 10)
|
|
48
|
+
expect(msgs.map((m) => m.guid)).toEqual(['m1', 'm2'])
|
|
49
|
+
expect(msgs[0]?.is_outgoing).toBe(false)
|
|
50
|
+
expect(msgs[1]?.is_outgoing).toBe(true)
|
|
51
|
+
await c.close()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('sendMessage returns guid/id; send failure maps to send_failed', async () => {
|
|
55
|
+
const ok = await clientWith('ok').login({ binaryPath: STUB })
|
|
56
|
+
await ok.connect()
|
|
57
|
+
expect(await ok.sendMessage({ chatId: 42 }, 'hi')).toMatchObject({ guid: 'sent-guid', id: 99, is_outgoing: true })
|
|
58
|
+
await ok.close()
|
|
59
|
+
|
|
60
|
+
const fail = await clientWith('send_fail').login({ binaryPath: STUB })
|
|
61
|
+
await fail.connect()
|
|
62
|
+
try {
|
|
63
|
+
await fail.sendMessage({ chatId: 42 }, 'hi')
|
|
64
|
+
throw new Error('should have thrown')
|
|
65
|
+
} catch (error) {
|
|
66
|
+
expect((error as IMessageError).code).toBe('send_failed')
|
|
67
|
+
} finally {
|
|
68
|
+
await fail.close()
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('watch delivers live notifications', async () => {
|
|
73
|
+
const c = await clientWith().login({ binaryPath: STUB })
|
|
74
|
+
await c.connect()
|
|
75
|
+
const received: string[] = []
|
|
76
|
+
const stop = await c.watch((m) => received.push(m.guid), { chatId: 42 })
|
|
77
|
+
await new Promise((r) => setTimeout(r, 120))
|
|
78
|
+
await stop()
|
|
79
|
+
await c.close()
|
|
80
|
+
expect(received).toContain('m3')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('sendReaction: standard via CLI; custom + message-target rejected', async () => {
|
|
84
|
+
const c = await clientWith().login({ binaryPath: STUB })
|
|
85
|
+
await c.connect()
|
|
86
|
+
await expect(c.sendReaction(42, 'love')).resolves.toBeUndefined()
|
|
87
|
+
await expect(c.sendReaction(42, 'sparkles')).rejects.toMatchObject({ code: 'private_api_required' })
|
|
88
|
+
await expect(c.sendReaction(42, 'love', 'some-guid')).rejects.toMatchObject({ code: 'private_api_required' })
|
|
89
|
+
await c.close()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('maps an Automation-denied "imsg react" failure to automation_denied (not send_failed)', async () => {
|
|
93
|
+
const c = await clientWith('react_automation_denied').login({ binaryPath: STUB })
|
|
94
|
+
await c.connect()
|
|
95
|
+
try {
|
|
96
|
+
await c.sendReaction(42, 'love')
|
|
97
|
+
throw new Error('should have thrown')
|
|
98
|
+
} catch (error) {
|
|
99
|
+
expect((error as IMessageError).code).toBe('automation_denied')
|
|
100
|
+
} finally {
|
|
101
|
+
await c.close()
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('maps AppleScript "not authorized" send error to automation_denied (not send_failed)', async () => {
|
|
106
|
+
const c = await clientWith('automation_denied').login({ binaryPath: STUB })
|
|
107
|
+
await c.connect()
|
|
108
|
+
try {
|
|
109
|
+
await c.sendMessage({ chatId: 42 }, 'hi')
|
|
110
|
+
throw new Error('should have thrown')
|
|
111
|
+
} catch (error) {
|
|
112
|
+
expect((error as IMessageError).code).toBe('automation_denied')
|
|
113
|
+
} finally {
|
|
114
|
+
await c.close()
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('close() rejects in-flight requests instead of hanging', async () => {
|
|
119
|
+
const c = await clientWith().login({ binaryPath: STUB })
|
|
120
|
+
await c.connect()
|
|
121
|
+
const pending = c.listChats(1)
|
|
122
|
+
await c.close()
|
|
123
|
+
await expect(pending).rejects.toMatchObject({ code: 'rpc_error' })
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('imsg_not_found when the binary does not exist', async () => {
|
|
127
|
+
const c = await new ImsgClient().login({ binaryPath: '/nonexistent/imsg-xyz' })
|
|
128
|
+
try {
|
|
129
|
+
await c.connect()
|
|
130
|
+
throw new Error('should have thrown')
|
|
131
|
+
} catch (error) {
|
|
132
|
+
expect((error as IMessageError).code).toBe('imsg_not_found')
|
|
133
|
+
} finally {
|
|
134
|
+
await c.close()
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import { classifyImsgFailure } from './errors'
|
|
4
|
+
import { ImsgRpc } from './rpc'
|
|
5
|
+
import { type IMessageChatSummary, type IMessageMessageSummary, type IMessageStatus, IMessageError } from './types'
|
|
6
|
+
|
|
7
|
+
interface ImsgChat {
|
|
8
|
+
id: number
|
|
9
|
+
name?: string | null
|
|
10
|
+
identifier?: string | null
|
|
11
|
+
guid?: string | null
|
|
12
|
+
service?: string | null
|
|
13
|
+
is_group?: boolean
|
|
14
|
+
participants?: string[] | null
|
|
15
|
+
last_message?: ImsgMessage | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ImsgMessage {
|
|
19
|
+
id: number
|
|
20
|
+
chat_id: number
|
|
21
|
+
guid: string
|
|
22
|
+
sender?: string | null
|
|
23
|
+
sender_name?: string | null
|
|
24
|
+
is_from_me?: boolean
|
|
25
|
+
text?: string | null
|
|
26
|
+
created_at?: string | null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const STANDARD_REACTIONS = new Set(['love', 'like', 'dislike', 'laugh', 'emphasis', 'question'])
|
|
30
|
+
|
|
31
|
+
export interface SendTarget {
|
|
32
|
+
chatId?: number
|
|
33
|
+
chatGuid?: string
|
|
34
|
+
chatIdentifier?: string
|
|
35
|
+
to?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type OneShotRunner = (
|
|
39
|
+
binaryPath: string,
|
|
40
|
+
args: string[],
|
|
41
|
+
) => Promise<{ code: number; stdout: string; stderr: string }>
|
|
42
|
+
|
|
43
|
+
const defaultRunner: OneShotRunner = (binaryPath, args) =>
|
|
44
|
+
new Promise((resolve, reject) => {
|
|
45
|
+
let proc: ReturnType<typeof spawn>
|
|
46
|
+
try {
|
|
47
|
+
proc = spawn(binaryPath, args, { stdio: ['ignore', 'pipe', 'pipe'] })
|
|
48
|
+
} catch {
|
|
49
|
+
reject(new IMessageError(`Could not run "${binaryPath}".`, 'imsg_not_found'))
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
let stdout = ''
|
|
53
|
+
let stderr = ''
|
|
54
|
+
proc.stdout?.on('data', (c: Buffer) => (stdout += c.toString()))
|
|
55
|
+
proc.stderr?.on('data', (c: Buffer) => (stderr += c.toString()))
|
|
56
|
+
proc.once('error', (err: NodeJS.ErrnoException) => {
|
|
57
|
+
reject(
|
|
58
|
+
err.code === 'ENOENT'
|
|
59
|
+
? new IMessageError(`Could not run "${binaryPath}".`, 'imsg_not_found', {
|
|
60
|
+
suggestion: 'Install imsg: "brew install steipete/tap/imsg".',
|
|
61
|
+
doctorCommand: 'agent-imessage doctor',
|
|
62
|
+
})
|
|
63
|
+
: err,
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
proc.once('close', (code) => resolve({ code: code ?? 0, stdout, stderr }))
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
function summarizeMessage(msg: ImsgMessage): IMessageMessageSummary {
|
|
70
|
+
return {
|
|
71
|
+
id: msg.id,
|
|
72
|
+
guid: msg.guid,
|
|
73
|
+
chat_id: msg.chat_id,
|
|
74
|
+
from: msg.is_from_me ? '' : (msg.sender ?? ''),
|
|
75
|
+
from_name: msg.sender_name ?? undefined,
|
|
76
|
+
is_outgoing: Boolean(msg.is_from_me),
|
|
77
|
+
timestamp: msg.created_at ?? new Date(0).toISOString(),
|
|
78
|
+
text: msg.text ?? undefined,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function summarizeChat(chat: ImsgChat): IMessageChatSummary {
|
|
83
|
+
return {
|
|
84
|
+
id: chat.id,
|
|
85
|
+
guid: chat.guid ?? null,
|
|
86
|
+
identifier: chat.identifier ?? null,
|
|
87
|
+
name: chat.name?.trim() || chat.identifier || chat.guid || String(chat.id),
|
|
88
|
+
service: chat.service ?? 'iMessage',
|
|
89
|
+
is_group: Boolean(chat.is_group),
|
|
90
|
+
participants: chat.participants ?? [],
|
|
91
|
+
last_message: chat.last_message ? summarizeMessage(chat.last_message) : undefined,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class ImsgClient {
|
|
96
|
+
private binaryPath = 'imsg'
|
|
97
|
+
private region: string | undefined
|
|
98
|
+
private rpc: ImsgRpc
|
|
99
|
+
private runner: OneShotRunner
|
|
100
|
+
|
|
101
|
+
constructor(deps?: { rpc?: ImsgRpc; runner?: OneShotRunner }) {
|
|
102
|
+
this.rpc = deps?.rpc ?? new ImsgRpc()
|
|
103
|
+
this.runner = deps?.runner ?? defaultRunner
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async login(credentials?: { binaryPath?: string; region?: string }): Promise<this> {
|
|
107
|
+
if (credentials) {
|
|
108
|
+
this.binaryPath = credentials.binaryPath ?? 'imsg'
|
|
109
|
+
this.region = credentials.region
|
|
110
|
+
return this
|
|
111
|
+
}
|
|
112
|
+
const { IMessageCredentialManager } = await import('./credential-manager')
|
|
113
|
+
const resolved = await new IMessageCredentialManager().resolveAccount()
|
|
114
|
+
this.binaryPath = resolved?.binary_path ?? 'imsg'
|
|
115
|
+
this.region = resolved?.region
|
|
116
|
+
return this
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async connect(): Promise<void> {
|
|
120
|
+
await this.rpc.start(this.binaryPath)
|
|
121
|
+
await this.rpc.request('chats.list', { limit: 1 })
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async getVersion(): Promise<string | null> {
|
|
125
|
+
const result = await this.runner(this.binaryPath, ['--version'])
|
|
126
|
+
if (result.code !== 0) return null
|
|
127
|
+
return result.stdout.trim() || null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async getStatus(): Promise<IMessageStatus> {
|
|
131
|
+
let version: string | null = null
|
|
132
|
+
let fullDiskAccess = false
|
|
133
|
+
try {
|
|
134
|
+
version = await this.getVersion()
|
|
135
|
+
} catch {
|
|
136
|
+
version = null
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
await this.rpc.request('chats.list', { limit: 1 })
|
|
140
|
+
fullDiskAccess = true
|
|
141
|
+
} catch {
|
|
142
|
+
fullDiskAccess = false
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
imsg_version: version,
|
|
146
|
+
binary_path: this.binaryPath,
|
|
147
|
+
full_disk_access: fullDiskAccess,
|
|
148
|
+
automation: 'unknown',
|
|
149
|
+
bridge_available: false,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async listChats(limit = 25): Promise<IMessageChatSummary[]> {
|
|
154
|
+
const result = await this.rpc.request<{ chats: ImsgChat[] }>('chats.list', { limit })
|
|
155
|
+
return result.chats.map(summarizeChat)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async searchChats(query: string, limit = 25): Promise<IMessageChatSummary[]> {
|
|
159
|
+
const all = await this.listChats(Math.max(limit, 100))
|
|
160
|
+
const lower = query.toLowerCase()
|
|
161
|
+
return all
|
|
162
|
+
.filter((c) => c.name.toLowerCase().includes(lower) || (c.identifier ?? '').toLowerCase().includes(lower))
|
|
163
|
+
.slice(0, limit)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async getMessages(chatId: number, limit = 25, start?: string): Promise<IMessageMessageSummary[]> {
|
|
167
|
+
const result = await this.rpc.request<{ messages: ImsgMessage[] }>('messages.history', {
|
|
168
|
+
chat_id: chatId,
|
|
169
|
+
limit,
|
|
170
|
+
...(start ? { start } : {}),
|
|
171
|
+
})
|
|
172
|
+
return result.messages.map(summarizeMessage).sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async sendMessage(target: SendTarget, text: string): Promise<IMessageMessageSummary> {
|
|
176
|
+
const params: Record<string, unknown> = { text }
|
|
177
|
+
if (target.chatId !== undefined) params.chat_id = target.chatId
|
|
178
|
+
else if (target.chatGuid) params.chat_guid = target.chatGuid
|
|
179
|
+
else if (target.chatIdentifier) params.chat_identifier = target.chatIdentifier
|
|
180
|
+
else if (target.to) params.to = target.to
|
|
181
|
+
else throw new IMessageError('A chat id, guid, or recipient is required to send.', 'chat_not_found')
|
|
182
|
+
if (this.region) params.region = this.region
|
|
183
|
+
|
|
184
|
+
const result = await this.rpc.request<{ ok?: boolean; guid?: string; id?: number; service?: string }>(
|
|
185
|
+
'send',
|
|
186
|
+
params,
|
|
187
|
+
)
|
|
188
|
+
return {
|
|
189
|
+
id: result.id ?? 0,
|
|
190
|
+
guid: result.guid ?? '',
|
|
191
|
+
chat_id: target.chatId ?? 0,
|
|
192
|
+
from: '',
|
|
193
|
+
is_outgoing: true,
|
|
194
|
+
timestamp: new Date().toISOString(),
|
|
195
|
+
text,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async sendReaction(chatId: number, reaction: string, messageGuid?: string): Promise<void> {
|
|
200
|
+
if (messageGuid) {
|
|
201
|
+
throw new IMessageError(
|
|
202
|
+
'Reacting to a specific message requires the imsg Private API bridge.',
|
|
203
|
+
'private_api_required',
|
|
204
|
+
{ suggestion: 'Enable the bridge with "imsg launch" (requires SIP disabled).' },
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
if (!STANDARD_REACTIONS.has(reaction)) {
|
|
208
|
+
throw new IMessageError(
|
|
209
|
+
`Custom reactions require the imsg Private API bridge. Standard tapbacks: ${[...STANDARD_REACTIONS].join(', ')}.`,
|
|
210
|
+
'private_api_required',
|
|
211
|
+
{ suggestion: 'Enable the bridge with "imsg launch" (requires SIP disabled).' },
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
const result = await this.runner(this.binaryPath, [
|
|
215
|
+
'react',
|
|
216
|
+
'--chat-id',
|
|
217
|
+
String(chatId),
|
|
218
|
+
'--reaction',
|
|
219
|
+
reaction,
|
|
220
|
+
'--json',
|
|
221
|
+
])
|
|
222
|
+
if (result.code !== 0) {
|
|
223
|
+
const detail = result.stderr.trim() || result.stdout.trim() || 'Reaction failed.'
|
|
224
|
+
throw classifyImsgFailure(detail, 'send_failed')
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async watch(
|
|
229
|
+
onMessage: (msg: IMessageMessageSummary) => void,
|
|
230
|
+
options: { chatId?: number; sinceRowId?: number; onError?: (msg: string) => void } = {},
|
|
231
|
+
): Promise<() => Promise<void>> {
|
|
232
|
+
const params: Record<string, unknown> = { include_reactions: false }
|
|
233
|
+
if (options.chatId !== undefined) params.chat_id = options.chatId
|
|
234
|
+
if (options.sinceRowId !== undefined) params.since_rowid = options.sinceRowId
|
|
235
|
+
|
|
236
|
+
const subscription = await this.rpc.subscribe(
|
|
237
|
+
params,
|
|
238
|
+
(raw) => onMessage(summarizeMessage(raw as ImsgMessage)),
|
|
239
|
+
options.onError,
|
|
240
|
+
)
|
|
241
|
+
return async () => {
|
|
242
|
+
await this.rpc.unsubscribe(subscription)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async getProfile(): Promise<IMessageStatus> {
|
|
247
|
+
return this.getStatus()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async close(): Promise<void> {
|
|
251
|
+
this.rpc.close()
|
|
252
|
+
}
|
|
253
|
+
}
|