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.
Files changed (316) hide show
  1. package/.claude-plugin/README.md +11 -1
  2. package/.claude-plugin/marketplace.json +14 -1
  3. package/.claude-plugin/plugin.json +4 -2
  4. package/AGENTS.md +4 -0
  5. package/README.md +58 -10
  6. package/dist/package.json +10 -2
  7. package/dist/src/cli.d.ts.map +1 -1
  8. package/dist/src/cli.js +3 -0
  9. package/dist/src/cli.js.map +1 -1
  10. package/dist/src/platforms/discord/client.d.ts +6 -2
  11. package/dist/src/platforms/discord/client.d.ts.map +1 -1
  12. package/dist/src/platforms/discord/client.js +10 -3
  13. package/dist/src/platforms/discord/client.js.map +1 -1
  14. package/dist/src/platforms/discordbot/cli.js +1 -1
  15. package/dist/src/platforms/imessage/cli.d.ts +5 -0
  16. package/dist/src/platforms/imessage/cli.d.ts.map +1 -0
  17. package/dist/src/platforms/imessage/cli.js +31 -0
  18. package/dist/src/platforms/imessage/cli.js.map +1 -0
  19. package/dist/src/platforms/imessage/client.d.ts +43 -0
  20. package/dist/src/platforms/imessage/client.d.ts.map +1 -0
  21. package/dist/src/platforms/imessage/client.js +190 -0
  22. package/dist/src/platforms/imessage/client.js.map +1 -0
  23. package/dist/src/platforms/imessage/commands/auth.d.ts +31 -0
  24. package/dist/src/platforms/imessage/commands/auth.d.ts.map +1 -0
  25. package/dist/src/platforms/imessage/commands/auth.js +100 -0
  26. package/dist/src/platforms/imessage/commands/auth.js.map +1 -0
  27. package/dist/src/platforms/imessage/commands/chat.d.ts +3 -0
  28. package/dist/src/platforms/imessage/commands/chat.d.ts.map +1 -0
  29. package/dist/src/platforms/imessage/commands/chat.js +42 -0
  30. package/dist/src/platforms/imessage/commands/chat.js.map +1 -0
  31. package/dist/src/platforms/imessage/commands/doctor.d.ts +25 -0
  32. package/dist/src/platforms/imessage/commands/doctor.d.ts.map +1 -0
  33. package/dist/src/platforms/imessage/commands/doctor.js +101 -0
  34. package/dist/src/platforms/imessage/commands/doctor.js.map +1 -0
  35. package/dist/src/platforms/imessage/commands/index.d.ts +7 -0
  36. package/dist/src/platforms/imessage/commands/index.d.ts.map +1 -0
  37. package/dist/src/platforms/imessage/commands/index.js +7 -0
  38. package/dist/src/platforms/imessage/commands/index.js.map +1 -0
  39. package/dist/src/platforms/imessage/commands/message.d.ts +5 -0
  40. package/dist/src/platforms/imessage/commands/message.d.ts.map +1 -0
  41. package/dist/src/platforms/imessage/commands/message.js +127 -0
  42. package/dist/src/platforms/imessage/commands/message.js.map +1 -0
  43. package/dist/src/platforms/imessage/commands/setup.d.ts +20 -0
  44. package/dist/src/platforms/imessage/commands/setup.d.ts.map +1 -0
  45. package/dist/src/platforms/imessage/commands/setup.js +57 -0
  46. package/dist/src/platforms/imessage/commands/setup.js.map +1 -0
  47. package/dist/src/platforms/imessage/commands/shared.d.ts +10 -0
  48. package/dist/src/platforms/imessage/commands/shared.d.ts.map +1 -0
  49. package/dist/src/platforms/imessage/commands/shared.js +53 -0
  50. package/dist/src/platforms/imessage/commands/shared.js.map +1 -0
  51. package/dist/src/platforms/imessage/commands/whoami.d.ts +3 -0
  52. package/dist/src/platforms/imessage/commands/whoami.d.ts.map +1 -0
  53. package/dist/src/platforms/imessage/commands/whoami.js +20 -0
  54. package/dist/src/platforms/imessage/commands/whoami.js.map +1 -0
  55. package/dist/src/platforms/imessage/credential-manager.d.ts +22 -0
  56. package/dist/src/platforms/imessage/credential-manager.d.ts.map +1 -0
  57. package/dist/src/platforms/imessage/credential-manager.js +111 -0
  58. package/dist/src/platforms/imessage/credential-manager.js.map +1 -0
  59. package/dist/src/platforms/imessage/ensure-auth.d.ts +2 -0
  60. package/dist/src/platforms/imessage/ensure-auth.d.ts.map +1 -0
  61. package/dist/src/platforms/imessage/ensure-auth.js +13 -0
  62. package/dist/src/platforms/imessage/ensure-auth.js.map +1 -0
  63. package/dist/src/platforms/imessage/errors.d.ts +3 -0
  64. package/dist/src/platforms/imessage/errors.d.ts.map +1 -0
  65. package/dist/src/platforms/imessage/errors.js +25 -0
  66. package/dist/src/platforms/imessage/errors.js.map +1 -0
  67. package/dist/src/platforms/imessage/index.d.ts +7 -0
  68. package/dist/src/platforms/imessage/index.d.ts.map +1 -0
  69. package/dist/src/platforms/imessage/index.js +5 -0
  70. package/dist/src/platforms/imessage/index.js.map +1 -0
  71. package/dist/src/platforms/imessage/rpc.d.ts +20 -0
  72. package/dist/src/platforms/imessage/rpc.d.ts.map +1 -0
  73. package/dist/src/platforms/imessage/rpc.js +133 -0
  74. package/dist/src/platforms/imessage/rpc.js.map +1 -0
  75. package/dist/src/platforms/imessage/types.d.ts +59 -0
  76. package/dist/src/platforms/imessage/types.d.ts.map +1 -0
  77. package/dist/src/platforms/imessage/types.js +29 -0
  78. package/dist/src/platforms/imessage/types.js.map +1 -0
  79. package/dist/src/platforms/instagram/cli.js +1 -1
  80. package/dist/src/platforms/instagram/client.d.ts +29 -2
  81. package/dist/src/platforms/instagram/client.d.ts.map +1 -1
  82. package/dist/src/platforms/instagram/client.js +360 -53
  83. package/dist/src/platforms/instagram/client.js.map +1 -1
  84. package/dist/src/platforms/instagram/commands/auth.d.ts +1 -0
  85. package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
  86. package/dist/src/platforms/instagram/commands/auth.js +123 -11
  87. package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
  88. package/dist/src/platforms/instagram/credential-manager.d.ts +4 -1
  89. package/dist/src/platforms/instagram/credential-manager.d.ts.map +1 -1
  90. package/dist/src/platforms/instagram/credential-manager.js +17 -1
  91. package/dist/src/platforms/instagram/credential-manager.js.map +1 -1
  92. package/dist/src/platforms/instagram/hybrid-listener.d.ts +48 -0
  93. package/dist/src/platforms/instagram/hybrid-listener.d.ts.map +1 -0
  94. package/dist/src/platforms/instagram/hybrid-listener.js +154 -0
  95. package/dist/src/platforms/instagram/hybrid-listener.js.map +1 -0
  96. package/dist/src/platforms/instagram/index.d.ts +2 -0
  97. package/dist/src/platforms/instagram/index.d.ts.map +1 -1
  98. package/dist/src/platforms/instagram/index.js +2 -0
  99. package/dist/src/platforms/instagram/index.js.map +1 -1
  100. package/dist/src/platforms/instagram/mqtt/connection.d.ts +4 -0
  101. package/dist/src/platforms/instagram/mqtt/connection.d.ts.map +1 -0
  102. package/dist/src/platforms/instagram/mqtt/connection.js +66 -0
  103. package/dist/src/platforms/instagram/mqtt/connection.js.map +1 -0
  104. package/dist/src/platforms/instagram/mqtt/thrift.d.ts +22 -0
  105. package/dist/src/platforms/instagram/mqtt/thrift.d.ts.map +1 -0
  106. package/dist/src/platforms/instagram/mqtt/thrift.js +143 -0
  107. package/dist/src/platforms/instagram/mqtt/thrift.js.map +1 -0
  108. package/dist/src/platforms/instagram/mqtt/transport.d.ts +42 -0
  109. package/dist/src/platforms/instagram/mqtt/transport.d.ts.map +1 -0
  110. package/dist/src/platforms/instagram/mqtt/transport.js +246 -0
  111. package/dist/src/platforms/instagram/mqtt/transport.js.map +1 -0
  112. package/dist/src/platforms/instagram/realtime-listener.d.ts +33 -0
  113. package/dist/src/platforms/instagram/realtime-listener.d.ts.map +1 -0
  114. package/dist/src/platforms/instagram/realtime-listener.js +151 -0
  115. package/dist/src/platforms/instagram/realtime-listener.js.map +1 -0
  116. package/dist/src/platforms/instagram/types.d.ts +10 -8
  117. package/dist/src/platforms/instagram/types.d.ts.map +1 -1
  118. package/dist/src/platforms/instagram/types.js.map +1 -1
  119. package/dist/src/platforms/kakaotalk/cli.js +1 -1
  120. package/dist/src/platforms/line/cli.js +1 -1
  121. package/dist/src/platforms/teams/app-config.d.ts +36 -0
  122. package/dist/src/platforms/teams/app-config.d.ts.map +1 -0
  123. package/dist/src/platforms/teams/app-config.js +69 -0
  124. package/dist/src/platforms/teams/app-config.js.map +1 -0
  125. package/dist/src/platforms/teams/client.d.ts +19 -2
  126. package/dist/src/platforms/teams/client.d.ts.map +1 -1
  127. package/dist/src/platforms/teams/client.js +295 -7
  128. package/dist/src/platforms/teams/client.js.map +1 -1
  129. package/dist/src/platforms/teams/commands/auth.d.ts +10 -0
  130. package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
  131. package/dist/src/platforms/teams/commands/auth.js +137 -3
  132. package/dist/src/platforms/teams/commands/auth.js.map +1 -1
  133. package/dist/src/platforms/teams/commands/file.d.ts +3 -0
  134. package/dist/src/platforms/teams/commands/file.d.ts.map +1 -1
  135. package/dist/src/platforms/teams/commands/file.js +59 -2
  136. package/dist/src/platforms/teams/commands/file.js.map +1 -1
  137. package/dist/src/platforms/teams/commands/message.d.ts +11 -0
  138. package/dist/src/platforms/teams/commands/message.d.ts.map +1 -1
  139. package/dist/src/platforms/teams/commands/message.js +121 -2
  140. package/dist/src/platforms/teams/commands/message.js.map +1 -1
  141. package/dist/src/platforms/teams/credential-manager.d.ts +23 -1
  142. package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
  143. package/dist/src/platforms/teams/credential-manager.js +46 -2
  144. package/dist/src/platforms/teams/credential-manager.js.map +1 -1
  145. package/dist/src/platforms/teams/device-code.d.ts +48 -0
  146. package/dist/src/platforms/teams/device-code.d.ts.map +1 -0
  147. package/dist/src/platforms/teams/device-code.js +199 -0
  148. package/dist/src/platforms/teams/device-code.js.map +1 -0
  149. package/dist/src/platforms/teams/device-login.d.ts +37 -0
  150. package/dist/src/platforms/teams/device-login.d.ts.map +1 -0
  151. package/dist/src/platforms/teams/device-login.js +129 -0
  152. package/dist/src/platforms/teams/device-login.js.map +1 -0
  153. package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
  154. package/dist/src/platforms/teams/ensure-auth.js +12 -0
  155. package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
  156. package/dist/src/platforms/teams/index.d.ts +7 -3
  157. package/dist/src/platforms/teams/index.d.ts.map +1 -1
  158. package/dist/src/platforms/teams/index.js +5 -2
  159. package/dist/src/platforms/teams/index.js.map +1 -1
  160. package/dist/src/platforms/teams/listener.d.ts +35 -0
  161. package/dist/src/platforms/teams/listener.d.ts.map +1 -0
  162. package/dist/src/platforms/teams/listener.js +236 -0
  163. package/dist/src/platforms/teams/listener.js.map +1 -0
  164. package/dist/src/platforms/teams/realm-discovery.d.ts +3 -0
  165. package/dist/src/platforms/teams/realm-discovery.d.ts.map +1 -0
  166. package/dist/src/platforms/teams/realm-discovery.js +33 -0
  167. package/dist/src/platforms/teams/realm-discovery.js.map +1 -0
  168. package/dist/src/platforms/teams/token-extractor.d.ts +3 -0
  169. package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
  170. package/dist/src/platforms/teams/token-extractor.js +60 -0
  171. package/dist/src/platforms/teams/token-extractor.js.map +1 -1
  172. package/dist/src/platforms/teams/token-provider.d.ts +25 -0
  173. package/dist/src/platforms/teams/token-provider.d.ts.map +1 -0
  174. package/dist/src/platforms/teams/token-provider.js +190 -0
  175. package/dist/src/platforms/teams/token-provider.js.map +1 -0
  176. package/dist/src/platforms/teams/trouter.d.ts +31 -0
  177. package/dist/src/platforms/teams/trouter.d.ts.map +1 -0
  178. package/dist/src/platforms/teams/trouter.js +181 -0
  179. package/dist/src/platforms/teams/trouter.js.map +1 -0
  180. package/dist/src/platforms/teams/types.d.ts +94 -0
  181. package/dist/src/platforms/teams/types.d.ts.map +1 -1
  182. package/dist/src/platforms/teams/types.js +30 -0
  183. package/dist/src/platforms/teams/types.js.map +1 -1
  184. package/dist/src/platforms/webex/cli.js +1 -1
  185. package/dist/src/platforms/webex/types.d.ts +1 -1
  186. package/dist/src/platforms/webexbot/cli.js +1 -1
  187. package/dist/src/platforms/wechatbot/cli.js +1 -1
  188. package/dist/src/platforms/whatsapp/cli.js +1 -1
  189. package/dist/src/platforms/whatsappbot/cli.js +1 -1
  190. package/dist/src/tui/adapters/imessage-adapter.d.ts +21 -0
  191. package/dist/src/tui/adapters/imessage-adapter.d.ts.map +1 -0
  192. package/dist/src/tui/adapters/imessage-adapter.js +110 -0
  193. package/dist/src/tui/adapters/imessage-adapter.js.map +1 -0
  194. package/dist/src/tui/adapters/instagram-adapter.d.ts.map +1 -1
  195. package/dist/src/tui/adapters/instagram-adapter.js +16 -0
  196. package/dist/src/tui/adapters/instagram-adapter.js.map +1 -1
  197. package/dist/src/tui/adapters/teams-adapter.d.ts +3 -0
  198. package/dist/src/tui/adapters/teams-adapter.d.ts.map +1 -1
  199. package/dist/src/tui/adapters/teams-adapter.js +21 -0
  200. package/dist/src/tui/adapters/teams-adapter.js.map +1 -1
  201. package/dist/src/tui/app.d.ts.map +1 -1
  202. package/dist/src/tui/app.js +12 -3
  203. package/dist/src/tui/app.js.map +1 -1
  204. package/docs/content/docs/agent-skills.mdx +1 -1
  205. package/docs/content/docs/cli/imessage.mdx +99 -0
  206. package/docs/content/docs/cli/instagram.mdx +2 -1
  207. package/docs/content/docs/cli/meta.json +1 -0
  208. package/docs/content/docs/cli/teams.mdx +22 -0
  209. package/docs/content/docs/index.mdx +9 -6
  210. package/docs/content/docs/quick-start.mdx +2 -0
  211. package/docs/content/docs/sdk/instagram.mdx +90 -5
  212. package/docs/content/docs/tui.mdx +4 -3
  213. package/package.json +10 -2
  214. package/scripts/postbuild.ts +12 -15
  215. package/skills/agent-channeltalk/SKILL.md +1 -1
  216. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  217. package/skills/agent-discord/SKILL.md +1 -1
  218. package/skills/agent-discordbot/SKILL.md +1 -1
  219. package/skills/agent-imessage/SKILL.md +133 -0
  220. package/skills/agent-imessage/references/authentication.md +66 -0
  221. package/skills/agent-imessage/references/permissions.md +31 -0
  222. package/skills/agent-imessage/references/setup.md +49 -0
  223. package/skills/agent-instagram/SKILL.md +72 -2
  224. package/skills/agent-instagram/references/authentication.md +11 -0
  225. package/skills/agent-instagram/references/common-patterns.md +36 -0
  226. package/skills/agent-instagram/templates/monitor-chat.sh +5 -0
  227. package/skills/agent-kakaotalk/SKILL.md +1 -1
  228. package/skills/agent-line/SKILL.md +1 -1
  229. package/skills/agent-slack/SKILL.md +1 -1
  230. package/skills/agent-slackbot/SKILL.md +1 -1
  231. package/skills/agent-teams/SKILL.md +59 -6
  232. package/skills/agent-teams/references/authentication.md +31 -1
  233. package/skills/agent-telegram/SKILL.md +1 -1
  234. package/skills/agent-telegrambot/SKILL.md +1 -1
  235. package/skills/agent-webex/SKILL.md +1 -1
  236. package/skills/agent-webexbot/SKILL.md +1 -1
  237. package/skills/agent-wechatbot/SKILL.md +1 -1
  238. package/skills/agent-whatsapp/SKILL.md +1 -1
  239. package/skills/agent-whatsappbot/SKILL.md +1 -1
  240. package/src/cli.ts +4 -0
  241. package/src/platforms/discord/client.test.ts +86 -0
  242. package/src/platforms/discord/client.ts +10 -3
  243. package/src/platforms/imessage/cli.ts +39 -0
  244. package/src/platforms/imessage/client.test.ts +137 -0
  245. package/src/platforms/imessage/client.ts +253 -0
  246. package/src/platforms/imessage/commands/auth.ts +142 -0
  247. package/src/platforms/imessage/commands/chat.ts +51 -0
  248. package/src/platforms/imessage/commands/doctor.test.ts +80 -0
  249. package/src/platforms/imessage/commands/doctor.ts +139 -0
  250. package/src/platforms/imessage/commands/index.ts +6 -0
  251. package/src/platforms/imessage/commands/message.test.ts +20 -0
  252. package/src/platforms/imessage/commands/message.ts +151 -0
  253. package/src/platforms/imessage/commands/setup.test.ts +52 -0
  254. package/src/platforms/imessage/commands/setup.ts +76 -0
  255. package/src/platforms/imessage/commands/shared.test.ts +19 -0
  256. package/src/platforms/imessage/commands/shared.ts +69 -0
  257. package/src/platforms/imessage/commands/whoami.ts +22 -0
  258. package/src/platforms/imessage/credential-manager.test.ts +122 -0
  259. package/src/platforms/imessage/credential-manager.ts +123 -0
  260. package/src/platforms/imessage/ensure-auth.ts +18 -0
  261. package/src/platforms/imessage/errors.ts +27 -0
  262. package/src/platforms/imessage/index.test.ts +13 -0
  263. package/src/platforms/imessage/index.ts +16 -0
  264. package/src/platforms/imessage/rpc.ts +157 -0
  265. package/src/platforms/imessage/test-stub-imsg.mjs +147 -0
  266. package/src/platforms/imessage/types.test.ts +47 -0
  267. package/src/platforms/imessage/types.ts +95 -0
  268. package/src/platforms/instagram/client.test.ts +478 -33
  269. package/src/platforms/instagram/client.ts +472 -68
  270. package/src/platforms/instagram/commands/auth.test.ts +177 -1
  271. package/src/platforms/instagram/commands/auth.ts +182 -11
  272. package/src/platforms/instagram/credential-manager.test.ts +30 -0
  273. package/src/platforms/instagram/credential-manager.ts +23 -1
  274. package/src/platforms/instagram/hybrid-listener.test.ts +135 -0
  275. package/src/platforms/instagram/hybrid-listener.ts +196 -0
  276. package/src/platforms/instagram/index.ts +10 -0
  277. package/src/platforms/instagram/mqtt/connection.ts +72 -0
  278. package/src/platforms/instagram/mqtt/thrift.test.ts +91 -0
  279. package/src/platforms/instagram/mqtt/thrift.ts +159 -0
  280. package/src/platforms/instagram/mqtt/transport.ts +285 -0
  281. package/src/platforms/instagram/realtime-listener.test.ts +34 -0
  282. package/src/platforms/instagram/realtime-listener.ts +201 -0
  283. package/src/platforms/instagram/types.ts +11 -8
  284. package/src/platforms/teams/app-config.ts +92 -0
  285. package/src/platforms/teams/client.test.ts +335 -1
  286. package/src/platforms/teams/client.ts +342 -7
  287. package/src/platforms/teams/commands/auth.test.ts +93 -1
  288. package/src/platforms/teams/commands/auth.ts +182 -3
  289. package/src/platforms/teams/commands/file.test.ts +92 -1
  290. package/src/platforms/teams/commands/file.ts +76 -1
  291. package/src/platforms/teams/commands/message.test.ts +95 -1
  292. package/src/platforms/teams/commands/message.ts +145 -3
  293. package/src/platforms/teams/credential-manager.test.ts +20 -0
  294. package/src/platforms/teams/credential-manager.ts +68 -3
  295. package/src/platforms/teams/device-code.test.ts +311 -0
  296. package/src/platforms/teams/device-code.ts +277 -0
  297. package/src/platforms/teams/device-login.test.ts +288 -0
  298. package/src/platforms/teams/device-login.ts +206 -0
  299. package/src/platforms/teams/ensure-auth.ts +11 -0
  300. package/src/platforms/teams/index.test.ts +10 -0
  301. package/src/platforms/teams/index.ts +16 -1
  302. package/src/platforms/teams/listener.ts +295 -0
  303. package/src/platforms/teams/realm-discovery.test.ts +73 -0
  304. package/src/platforms/teams/realm-discovery.ts +42 -0
  305. package/src/platforms/teams/token-extractor.ts +69 -0
  306. package/src/platforms/teams/token-provider.test.ts +227 -0
  307. package/src/platforms/teams/token-provider.ts +284 -0
  308. package/src/platforms/teams/trouter.test.ts +114 -0
  309. package/src/platforms/teams/trouter.ts +228 -0
  310. package/src/platforms/teams/types.test.ts +17 -0
  311. package/src/platforms/teams/types.ts +86 -0
  312. package/src/platforms/webexbot/cli.ts +0 -0
  313. package/src/tui/adapters/imessage-adapter.ts +131 -0
  314. package/src/tui/adapters/instagram-adapter.ts +13 -0
  315. package/src/tui/adapters/teams-adapter.ts +23 -0
  316. package/src/tui/app.ts +16 -3
@@ -0,0 +1,196 @@
1
+ import { EventEmitter } from 'node:events'
2
+
3
+ import type { InstagramClient } from './client'
4
+ import { InstagramListener } from './listener'
5
+ import { InstagramRealtimeListener } from './realtime-listener'
6
+ import type { InstagramMessageSummary } from './types'
7
+
8
+ export interface InstagramHybridListenerEventMap {
9
+ message: [InstagramMessageSummary]
10
+ error: [Error]
11
+ connected: [{ userId: string; transport: 'realtime' | 'polling' }]
12
+ disconnected: []
13
+ }
14
+
15
+ type EventKey = keyof InstagramHybridListenerEventMap
16
+
17
+ const DEFAULT_REALTIME_RETRY_BASE_MS = 30_000
18
+ const DEFAULT_REALTIME_RETRY_MAX_MS = 5 * 60_000
19
+
20
+ export interface InstagramHybridListenerOptions {
21
+ pollInterval?: number
22
+ realtimeRetryBaseMs?: number
23
+ realtimeRetryMaxMs?: number
24
+ disableRealtime?: boolean
25
+ connackTimeoutMs?: number
26
+ }
27
+
28
+ export class InstagramHybridListener {
29
+ private client: InstagramClient
30
+ private emitter = new EventEmitter()
31
+ private realtime: InstagramRealtimeListener | null = null
32
+ private poller: InstagramListener | null = null
33
+ private options: Required<
34
+ Pick<InstagramHybridListenerOptions, 'realtimeRetryBaseMs' | 'realtimeRetryMaxMs' | 'disableRealtime'>
35
+ > &
36
+ Pick<InstagramHybridListenerOptions, 'pollInterval' | 'connackTimeoutMs'>
37
+ private running = false
38
+ private realtimeRetryTimer: ReturnType<typeof setTimeout> | null = null
39
+ private realtimeFailures = 0
40
+ private activeTransport: 'realtime' | 'polling' | null = null
41
+ private tearingDownRealtime = false
42
+
43
+ constructor(client: InstagramClient, options: InstagramHybridListenerOptions = {}) {
44
+ this.client = client
45
+ this.options = {
46
+ pollInterval: options.pollInterval,
47
+ realtimeRetryBaseMs: options.realtimeRetryBaseMs ?? DEFAULT_REALTIME_RETRY_BASE_MS,
48
+ realtimeRetryMaxMs: options.realtimeRetryMaxMs ?? DEFAULT_REALTIME_RETRY_MAX_MS,
49
+ disableRealtime: options.disableRealtime ?? false,
50
+ connackTimeoutMs: options.connackTimeoutMs,
51
+ }
52
+ }
53
+
54
+ async start(): Promise<void> {
55
+ if (this.running) return
56
+ this.running = true
57
+
58
+ if (this.options.disableRealtime) {
59
+ this.startPolling()
60
+ return
61
+ }
62
+
63
+ await this.tryRealtime()
64
+ }
65
+
66
+ stop(): void {
67
+ this.running = false
68
+ this.activeTransport = null
69
+ this.clearRealtimeRetry()
70
+ this.teardownRealtime()
71
+ this.teardownPolling()
72
+ this.emitter.emit('disconnected')
73
+ }
74
+
75
+ on<K extends EventKey>(event: K, listener: (...args: InstagramHybridListenerEventMap[K]) => void): this {
76
+ this.emitter.on(event, listener as (...args: unknown[]) => void)
77
+ return this
78
+ }
79
+
80
+ off<K extends EventKey>(event: K, listener: (...args: InstagramHybridListenerEventMap[K]) => void): this {
81
+ this.emitter.off(event, listener as (...args: unknown[]) => void)
82
+ return this
83
+ }
84
+
85
+ once<K extends EventKey>(event: K, listener: (...args: InstagramHybridListenerEventMap[K]) => void): this {
86
+ this.emitter.once(event, listener as (...args: unknown[]) => void)
87
+ return this
88
+ }
89
+
90
+ protected createRealtimeListener(): InstagramRealtimeListener {
91
+ return new InstagramRealtimeListener(this.client, { connackTimeoutMs: this.options.connackTimeoutMs })
92
+ }
93
+
94
+ private async tryRealtime(): Promise<void> {
95
+ const realtime = this.createRealtimeListener()
96
+ this.realtime = realtime
97
+
98
+ realtime.on('message', (message) => this.emitter.emit('message', message))
99
+ realtime.on('connected', ({ userId }) => {
100
+ this.realtimeFailures = 0
101
+ this.activeTransport = 'realtime'
102
+ this.teardownPolling()
103
+ this.emitter.emit('connected', { userId, transport: 'realtime' })
104
+ })
105
+ realtime.on('error', (error) => this.onRealtimeFailure(error))
106
+ realtime.on('disconnected', () => {
107
+ // Ignore the 'disconnected' that our own teardown triggers; only an
108
+ // unexpected close (still marked realtime-active) is a real failure.
109
+ if (this.tearingDownRealtime) return
110
+ if (this.running && this.activeTransport === 'realtime') {
111
+ this.onRealtimeFailure(new Error('Realtime connection closed'))
112
+ }
113
+ })
114
+
115
+ try {
116
+ await realtime.start()
117
+ // stop() may have run during the bootstrap await; if so, don't leave an MQTT
118
+ // connection alive after the hybrid listener was already torn down.
119
+ if (!this.running) {
120
+ realtime.stop()
121
+ if (this.realtime === realtime) this.realtime = null
122
+ }
123
+ } catch (error) {
124
+ this.onRealtimeFailure(error instanceof Error ? error : new Error(String(error)))
125
+ }
126
+ }
127
+
128
+ private onRealtimeFailure(error: Error): void {
129
+ if (!this.running) return
130
+
131
+ this.activeTransport = null
132
+ this.emitter.emit('error', error)
133
+ this.realtimeFailures++
134
+ // teardownRealtime() suppresses the 'disconnected' it triggers, so this path
135
+ // does not re-enter via the realtime disconnected handler.
136
+ this.teardownRealtime()
137
+ this.startPolling()
138
+ this.scheduleRealtimeRetry()
139
+ }
140
+
141
+ private scheduleRealtimeRetry(): void {
142
+ if (!this.running || this.realtimeRetryTimer) return
143
+
144
+ const delay = Math.min(
145
+ this.options.realtimeRetryBaseMs * 2 ** (this.realtimeFailures - 1),
146
+ this.options.realtimeRetryMaxMs,
147
+ )
148
+
149
+ this.realtimeRetryTimer = setTimeout(() => {
150
+ this.realtimeRetryTimer = null
151
+ if (this.running) void this.tryRealtime()
152
+ }, delay)
153
+ }
154
+
155
+ private startPolling(): void {
156
+ if (this.poller) return
157
+
158
+ const poller = new InstagramListener(this.client, { pollInterval: this.options.pollInterval })
159
+ this.poller = poller
160
+
161
+ poller.on('message', (message) => this.emitter.emit('message', message))
162
+ poller.on('error', (error) => this.emitter.emit('error', error))
163
+ poller.on('connected', ({ userId }) => {
164
+ this.activeTransport = 'polling'
165
+ this.emitter.emit('connected', { userId, transport: 'polling' })
166
+ })
167
+
168
+ void poller.start()
169
+ }
170
+
171
+ private teardownRealtime(): void {
172
+ if (this.realtime) {
173
+ this.tearingDownRealtime = true
174
+ try {
175
+ this.realtime.stop()
176
+ } finally {
177
+ this.tearingDownRealtime = false
178
+ }
179
+ this.realtime = null
180
+ }
181
+ }
182
+
183
+ private teardownPolling(): void {
184
+ if (this.poller) {
185
+ this.poller.stop()
186
+ this.poller = null
187
+ }
188
+ }
189
+
190
+ private clearRealtimeRetry(): void {
191
+ if (this.realtimeRetryTimer) {
192
+ clearTimeout(this.realtimeRetryTimer)
193
+ this.realtimeRetryTimer = null
194
+ }
195
+ }
196
+ }
@@ -1,6 +1,16 @@
1
1
  export { InstagramClient } from './client'
2
2
  export { InstagramCredentialManager } from './credential-manager'
3
+ export {
4
+ InstagramHybridListener,
5
+ type InstagramHybridListenerEventMap,
6
+ type InstagramHybridListenerOptions,
7
+ } from './hybrid-listener'
3
8
  export { InstagramListener, type InstagramListenerEventMap } from './listener'
9
+ export {
10
+ InstagramRealtimeListener,
11
+ type InstagramRealtimeListenerEventMap,
12
+ type InstagramRealtimeListenerOptions,
13
+ } from './realtime-listener'
4
14
  export { InstagramTokenExtractor, type ExtractedInstagramCookies } from './token-extractor'
5
15
  export {
6
16
  createAccountId,
@@ -0,0 +1,72 @@
1
+ import { deflateSync } from 'node:zlib'
2
+
3
+ import type { InstagramSessionState } from '../types'
4
+ import { ThriftCompactWriter } from './thrift'
5
+
6
+ const IG_APP_ID = 567067343352427n
7
+ const IG_VERSION = '312.1.0.34.111'
8
+ const CLIENT_CAPABILITIES = 183n
9
+ const PUBLISH_FORMAT = 1
10
+ const NETWORK_TYPE_WIFI = 1
11
+ const CLIENT_STACK = 3
12
+ const CLIENT_IDENTIFIER_MAX_LENGTH = 20
13
+ const SESSION_ID_MASK = 0xffffffffn
14
+
15
+ const CONNECT_SUBSCRIBE_TOPIC_IDS = [88, 135, 149, 150, 133, 146]
16
+
17
+ export function extractSessionId(session: InstagramSessionState): string {
18
+ const authorization = session.authorization ?? ''
19
+ const igTokenMatch = authorization.match(/^Bearer IGT:2:(.+)$/)
20
+ if (igTokenMatch) {
21
+ try {
22
+ const decoded = JSON.parse(Buffer.from(igTokenMatch[1]!, 'base64').toString('utf8')) as { sessionid?: string }
23
+ if (decoded.sessionid) return decoded.sessionid
24
+ } catch {
25
+ // Malformed/stale token: fall through to the cookie-based sessionid below.
26
+ }
27
+ }
28
+
29
+ const cookieMatch = session.cookies.match(/sessionid=([^;]+)/)
30
+ if (cookieMatch) return cookieMatch[1]!
31
+
32
+ throw new Error('No sessionid found in Instagram session (checked authorization token and cookies)')
33
+ }
34
+
35
+ export function buildConnectPayload(session: InstagramSessionState): Buffer {
36
+ const sessionId = extractSessionId(session)
37
+ const userId = BigInt(session.user_id ?? '0')
38
+ const deviceId = session.device.phone_id
39
+ const userAgent = `Instagram ${IG_VERSION} Android (${session.device.device_string})`
40
+
41
+ const thrift = new ThriftCompactWriter()
42
+ .binary(1, deviceId.substring(0, CLIENT_IDENTIFIER_MAX_LENGTH))
43
+ .structStart(4)
44
+ .i64(1, userId)
45
+ .binary(2, userAgent)
46
+ .i64(3, CLIENT_CAPABILITIES)
47
+ .i64(4, 0)
48
+ .i32(5, PUBLISH_FORMAT)
49
+ .bool(6, false)
50
+ .bool(7, true)
51
+ .binary(8, deviceId)
52
+ .bool(9, true)
53
+ .i32(10, NETWORK_TYPE_WIFI)
54
+ .i32(11, 0)
55
+ .i64(12, BigInt(Date.now()) & SESSION_ID_MASK)
56
+ .listOfI32(14, CONNECT_SUBSCRIBE_TOPIC_IDS)
57
+ .binary(15, 'cookie_auth')
58
+ .i64(16, IG_APP_ID)
59
+ .binary(20, '')
60
+ .byte(21, CLIENT_STACK)
61
+ .structEnd()
62
+ .binary(5, `sessionid=${sessionId}`)
63
+ .mapBinaryBinary(10, {
64
+ platform: 'android',
65
+ ig_mqtt_route: 'django',
66
+ pubsub_msg_type_blacklist: 'direct, typing_type',
67
+ auth_cache_enabled: '0',
68
+ })
69
+ .finish()
70
+
71
+ return deflateSync(thrift, { level: 9 })
72
+ }
@@ -0,0 +1,91 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+
3
+ import { ThriftCompactWriter } from '@/platforms/instagram/mqtt/thrift'
4
+
5
+ describe('ThriftCompactWriter', () => {
6
+ it('encodes a true bool as a packed field header with a trailing stop byte', () => {
7
+ // field id 1 (delta 1) << 4 | TYPE_TRUE(0x01) = 0x11, then struct stop 0x00
8
+ const out = new ThriftCompactWriter().bool(1, true).finish()
9
+ expect([...out]).toEqual([0x11, 0x00])
10
+ })
11
+
12
+ it('encodes a false bool with the false type code', () => {
13
+ // field id 1 (delta 1) << 4 | TYPE_FALSE(0x02) = 0x12
14
+ const out = new ThriftCompactWriter().bool(1, false).finish()
15
+ expect([...out]).toEqual([0x12, 0x00])
16
+ })
17
+
18
+ it('encodes i32 using zigzag varint', () => {
19
+ // field 1 i32(0x05) = 0x15, zigzag(1) = 2
20
+ const out = new ThriftCompactWriter().i32(1, 1).finish()
21
+ expect([...out]).toEqual([0x15, 0x02, 0x00])
22
+ })
23
+
24
+ it('encodes negative i32 via zigzag', () => {
25
+ // zigzag(-1) = 1
26
+ const out = new ThriftCompactWriter().i32(1, -1).finish()
27
+ expect([...out]).toEqual([0x15, 0x01, 0x00])
28
+ })
29
+
30
+ it('encodes a large positive i32 as an unsigned <=5-byte varint', () => {
31
+ // 0x40000000: zigzag(1073741824) = 2147483648 = 0x80000000 -> varint 80 80 80 80 08
32
+ const out = new ThriftCompactWriter().i32(1, 0x40000000).finish()
33
+ expect([...out]).toEqual([0x15, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00])
34
+ })
35
+
36
+ it('encodes INT32_MIN without overflowing into a 64-bit varint', () => {
37
+ // zigzag(-2147483648) = 4294967295 = 0xFFFFFFFF -> varint ff ff ff ff 0f (5 bytes, not 9)
38
+ const out = new ThriftCompactWriter().i32(1, -2147483648).finish()
39
+ expect([...out]).toEqual([0x15, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00])
40
+ })
41
+
42
+ it('encodes binary as length-prefixed utf8', () => {
43
+ // field 1 binary(0x08) = 0x18, length 2, "hi"
44
+ const out = new ThriftCompactWriter().binary(1, 'hi').finish()
45
+ expect([...out]).toEqual([0x18, 0x02, 0x68, 0x69, 0x00])
46
+ })
47
+
48
+ it('packs small field-id deltas across multiple fields', () => {
49
+ // field 1 binary = 0x18, then field 3 binary uses delta 2 = 0x28
50
+ const out = new ThriftCompactWriter().binary(1, 'a').binary(3, 'b').finish()
51
+ expect([...out]).toEqual([0x18, 0x01, 0x61, 0x28, 0x01, 0x62, 0x00])
52
+ })
53
+
54
+ it('falls back to explicit zigzag id when delta exceeds 15', () => {
55
+ // field 20 binary: delta 20 > 15, so type byte 0x08 then zigzag(20)=40=0x28
56
+ const out = new ThriftCompactWriter().binary(20, 'x').finish()
57
+ expect([...out]).toEqual([0x08, 0x28, 0x01, 0x78, 0x00])
58
+ })
59
+
60
+ it('encodes a short i32 list with size packed into the header nibble', () => {
61
+ // field 1 list = 0x19, list header size 2 << 4 | i32(0x05) = 0x25, zigzag(88)=176=0xb0 0x01, zigzag(146)=292=0xa4 0x02
62
+ const out = new ThriftCompactWriter().listOfI32(1, [88, 146]).finish()
63
+ expect([...out]).toEqual([0x19, 0x25, 0xb0, 0x01, 0xa4, 0x02, 0x00])
64
+ })
65
+
66
+ it('encodes an empty map as a single zero byte', () => {
67
+ // field 1 map = 0x1b, empty map size 0x00
68
+ const out = new ThriftCompactWriter().mapBinaryBinary(1, {}).finish()
69
+ expect([...out]).toEqual([0x1b, 0x00, 0x00])
70
+ })
71
+
72
+ it('encodes a binary->binary map with the key/value type byte', () => {
73
+ // field 1 map = 0x1b, size 1, types (binary<<4|binary)=0x88, key "k", value "v"
74
+ const out = new ThriftCompactWriter().mapBinaryBinary(1, { k: 'v' }).finish()
75
+ expect([...out]).toEqual([0x1b, 0x01, 0x88, 0x01, 0x6b, 0x01, 0x76, 0x00])
76
+ })
77
+
78
+ it('resets field-id delta tracking inside nested structs', () => {
79
+ // field 4 struct = 0x4c, inner field 1 binary = 0x18 "a", inner stop 0x00, outer stop 0x00
80
+ const out = new ThriftCompactWriter().structStart(4).binary(1, 'a').structEnd().finish()
81
+ expect([...out]).toEqual([0x4c, 0x18, 0x01, 0x61, 0x00, 0x00])
82
+ })
83
+
84
+ it('encodes i64 values larger than 32 bits', () => {
85
+ // field 16 i64: delta 16 > 15 -> type 0x06, zigzag id(16)=32=0x20, then zigzag64(567067343352427)
86
+ const out = new ThriftCompactWriter().i64(16, 567067343352427n).finish()
87
+ expect(out[0]).toBe(0x06)
88
+ expect(out[1]).toBe(0x20)
89
+ expect(out[out.length - 1]).toBe(0x00)
90
+ })
91
+ })
@@ -0,0 +1,159 @@
1
+ const TYPE_TRUE = 0x01
2
+ const TYPE_FALSE = 0x02
3
+ const TYPE_BYTE = 0x03
4
+ const TYPE_I16 = 0x04
5
+ const TYPE_I32 = 0x05
6
+ const TYPE_I64 = 0x06
7
+ const TYPE_BINARY = 0x08
8
+ const TYPE_LIST = 0x09
9
+ const TYPE_MAP = 0x0b
10
+ const TYPE_STRUCT = 0x0c
11
+
12
+ const VARINT_CONTINUE = 0x80
13
+ const VARINT_MASK = 0x7f
14
+ const U64_MASK = 0xffffffffffffffffn
15
+
16
+ export class ThriftCompactWriter {
17
+ private bytes: number[] = []
18
+ private parentFieldStack: number[] = []
19
+ private lastFieldId = 0
20
+
21
+ bool(id: number, value: boolean): this {
22
+ this.writeFieldHeader(value ? TYPE_TRUE : TYPE_FALSE, id)
23
+ return this
24
+ }
25
+
26
+ byte(id: number, value: number): this {
27
+ this.writeFieldHeader(TYPE_BYTE, id)
28
+ this.bytes.push(value & 0xff)
29
+ return this
30
+ }
31
+
32
+ i16(id: number, value: number): this {
33
+ this.writeFieldHeader(TYPE_I16, id)
34
+ this.writeVarint(zigzag32(value))
35
+ return this
36
+ }
37
+
38
+ i32(id: number, value: number): this {
39
+ this.writeFieldHeader(TYPE_I32, id)
40
+ this.writeVarint(zigzag32(value))
41
+ return this
42
+ }
43
+
44
+ i64(id: number, value: number | bigint): this {
45
+ this.writeFieldHeader(TYPE_I64, id)
46
+ this.writeVarint(zigzag64(BigInt(value)))
47
+ return this
48
+ }
49
+
50
+ binary(id: number, value: string | Buffer): this {
51
+ this.writeFieldHeader(TYPE_BINARY, id)
52
+ this.writeBinaryValue(value)
53
+ return this
54
+ }
55
+
56
+ listOfI32(id: number, values: number[]): this {
57
+ this.writeFieldHeader(TYPE_LIST, id)
58
+ this.writeListHeader(TYPE_I32, values.length)
59
+ for (const value of values) this.writeVarint(zigzag32(value))
60
+ return this
61
+ }
62
+
63
+ listOfBinary(id: number, values: string[]): this {
64
+ this.writeFieldHeader(TYPE_LIST, id)
65
+ this.writeListHeader(TYPE_BINARY, values.length)
66
+ for (const value of values) this.writeBinaryValue(value)
67
+ return this
68
+ }
69
+
70
+ mapBinaryBinary(id: number, entries: Record<string, string>): this {
71
+ this.writeFieldHeader(TYPE_MAP, id)
72
+ const keys = Object.keys(entries)
73
+
74
+ // Compact protocol encodes an empty map as a single zero byte (size only).
75
+ if (keys.length === 0) {
76
+ this.bytes.push(0)
77
+ return this
78
+ }
79
+
80
+ this.writeVarint(keys.length)
81
+ this.bytes.push((TYPE_BINARY << 4) | TYPE_BINARY)
82
+ for (const key of keys) {
83
+ this.writeBinaryValue(key)
84
+ this.writeBinaryValue(entries[key]!)
85
+ }
86
+ return this
87
+ }
88
+
89
+ structStart(id: number): this {
90
+ this.writeFieldHeader(TYPE_STRUCT, id)
91
+ this.parentFieldStack.push(this.lastFieldId)
92
+ this.lastFieldId = 0
93
+ return this
94
+ }
95
+
96
+ structEnd(): this {
97
+ this.bytes.push(0)
98
+ this.lastFieldId = this.parentFieldStack.pop() ?? 0
99
+ return this
100
+ }
101
+
102
+ finish(): Buffer {
103
+ this.bytes.push(0)
104
+ return Buffer.from(this.bytes)
105
+ }
106
+
107
+ // Compact protocol packs a small field-id delta and the type into one byte
108
+ // (delta << 4 | type); deltas outside 1..15 fall back to a zigzag varint id.
109
+ private writeFieldHeader(type: number, id: number): void {
110
+ const delta = id - this.lastFieldId
111
+ if (delta > 0 && delta <= 15) {
112
+ this.bytes.push((delta << 4) | type)
113
+ } else {
114
+ this.bytes.push(type)
115
+ this.writeVarint(zigzag32(id))
116
+ }
117
+ this.lastFieldId = id
118
+ }
119
+
120
+ // Lists with fewer than 15 elements pack size into the header nibble;
121
+ // larger lists set the nibble to 0xf and append the size as a varint.
122
+ private writeListHeader(elementType: number, size: number): void {
123
+ if (size < 15) {
124
+ this.bytes.push((size << 4) | elementType)
125
+ } else {
126
+ this.bytes.push(0xf0 | elementType)
127
+ this.writeVarint(size)
128
+ }
129
+ }
130
+
131
+ private writeBinaryValue(value: string | Buffer): void {
132
+ const buf = Buffer.isBuffer(value) ? value : Buffer.from(value, 'utf8')
133
+ this.writeVarint(buf.length)
134
+ for (const b of buf) this.bytes.push(b)
135
+ }
136
+
137
+ private writeVarint(value: number | bigint): void {
138
+ let remaining = BigInt(value) & U64_MASK
139
+ while (true) {
140
+ const chunk = Number(remaining & BigInt(VARINT_MASK))
141
+ remaining >>= 7n
142
+ if (remaining === 0n) {
143
+ this.bytes.push(chunk)
144
+ return
145
+ }
146
+ this.bytes.push(chunk | VARINT_CONTINUE)
147
+ }
148
+ }
149
+ }
150
+
151
+ function zigzag32(value: number): number {
152
+ // `>>> 0` coerces the signed int32 bitwise result to an unsigned 32-bit value,
153
+ // so large positives and INT32_MIN encode as a <=5-byte varint, not a 64-bit one.
154
+ return ((value << 1) ^ (value >> 31)) >>> 0
155
+ }
156
+
157
+ function zigzag64(value: bigint): bigint {
158
+ return BigInt.asUintN(64, value << 1n) ^ (value >> 63n)
159
+ }