@hraness/ghostget 0.17.2

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 (500) hide show
  1. package/CHANGELOG.md +458 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1359 -0
  4. package/bunfig.toml +2 -0
  5. package/dist/apple-photos-client.js +704 -0
  6. package/dist/beeper-client.js +4602 -0
  7. package/dist/client.js +1605 -0
  8. package/dist/index-26yq8q16.js +31 -0
  9. package/dist/index-9t13b4k7.js +508 -0
  10. package/dist/index-axwm9b3v.js +5 -0
  11. package/dist/index-gwk7rbyj.js +30 -0
  12. package/dist/index.js +701 -0
  13. package/dist/messaging.js +1130 -0
  14. package/dist/omni-client.js +1406 -0
  15. package/dist/whatsapp-client.js +355 -0
  16. package/docs/imessage-direct-provider.md +106 -0
  17. package/docs/rental-listings.md +94 -0
  18. package/package.json +600 -0
  19. package/skills/ghostget/SKILL.md +251 -0
  20. package/skills/ghostget/agents/openai.yaml +4 -0
  21. package/skills/ghostget/references/adapter-contract.md +181 -0
  22. package/skills/ghostget/references/apple-photos.md +115 -0
  23. package/skills/ghostget/references/article-drafts.md +235 -0
  24. package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
  25. package/skills/ghostget/references/cross-posting-video.md +139 -0
  26. package/skills/ghostget/references/cross-posting.md +82 -0
  27. package/skills/ghostget/references/derivation.md +390 -0
  28. package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
  29. package/skills/ghostget/references/install.md +66 -0
  30. package/skills/ghostget/references/linkedin-adapter.md +442 -0
  31. package/skills/ghostget/references/messaging.md +168 -0
  32. package/skills/ghostget/references/meta-comet-contract.md +153 -0
  33. package/skills/ghostget/references/platform-patterns.md +122 -0
  34. package/skills/ghostget/references/provider-plugins.md +496 -0
  35. package/skills/ghostget/references/reddit-flair.md +78 -0
  36. package/skills/ghostget/references/safety-and-state.md +214 -0
  37. package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
  38. package/skills/ghostget/references/social-platform-routing.md +66 -0
  39. package/skills/ghostget/references/social-profile-stats.md +195 -0
  40. package/skills/ghostget/references/social-video-platform-routing.md +89 -0
  41. package/skills/ghostget/references/whatsapp-adapter.md +101 -0
  42. package/skills/ghostget/references/x-adapter.md +332 -0
  43. package/skills/ghostget/references/x-ai-disclosure.md +71 -0
  44. package/src/apple-photos-cli.ts +639 -0
  45. package/src/apple-photos-client-types.ts +168 -0
  46. package/src/apple-photos-client.ts +201 -0
  47. package/src/apple-photos-contact-evidence.ts +838 -0
  48. package/src/apple-photos-local-source.ts +1131 -0
  49. package/src/args.ts +2053 -0
  50. package/src/article-draft-document.ts +417 -0
  51. package/src/article-draft-embeds.ts +123 -0
  52. package/src/article-draft-images.ts +214 -0
  53. package/src/assets/adapter-template/wrench-adapter.json +40 -0
  54. package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
  55. package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
  56. package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
  57. package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
  58. package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
  59. package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
  60. package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
  61. package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
  62. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
  63. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
  64. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
  65. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
  66. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
  67. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
  68. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
  69. package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
  70. package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
  71. package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
  72. package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
  73. package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
  74. package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
  75. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
  76. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
  77. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
  78. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
  79. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
  80. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
  81. package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
  82. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
  83. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
  84. package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
  85. package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
  86. package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
  87. package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
  88. package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
  89. package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
  90. package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
  91. package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
  92. package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
  93. package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
  94. package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
  95. package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
  96. package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
  97. package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
  98. package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
  99. package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
  100. package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
  101. package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
  102. package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
  103. package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
  104. package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
  105. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
  106. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
  107. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
  108. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
  109. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
  110. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
  111. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
  112. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
  113. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
  114. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
  115. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
  116. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
  117. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
  118. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
  119. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
  120. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
  121. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
  122. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
  123. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
  124. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
  125. package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
  126. package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
  127. package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
  128. package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
  129. package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
  130. package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
  131. package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
  132. package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
  133. package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
  134. package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
  135. package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
  136. package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
  137. package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
  138. package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
  139. package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
  140. package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
  141. package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
  142. package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
  143. package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
  144. package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
  145. package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
  146. package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
  147. package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
  148. package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
  149. package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
  150. package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
  151. package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
  152. package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
  153. package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
  154. package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
  155. package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
  156. package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
  157. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
  158. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
  159. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
  160. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
  161. package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
  162. package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
  163. package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
  164. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
  165. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
  166. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
  167. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
  168. package/src/assets/adapters/x/wrench-adapter.json +749 -0
  169. package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
  170. package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
  171. package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
  172. package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
  173. package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
  174. package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
  175. package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
  176. package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
  177. package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
  178. package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
  179. package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
  180. package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
  181. package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
  182. package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
  183. package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
  184. package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
  185. package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
  186. package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
  187. package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
  188. package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
  189. package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
  190. package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
  191. package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
  192. package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
  193. package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
  194. package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
  195. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  196. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  197. package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
  198. package/src/assets.d.ts +4 -0
  199. package/src/auth.ts +1157 -0
  200. package/src/beeper-client-types.ts +169 -0
  201. package/src/beeper-client.ts +250 -0
  202. package/src/beeper-contact-interactions-cli.ts +180 -0
  203. package/src/beeper-contact-interactions.ts +1612 -0
  204. package/src/beeper-message-bundle-v1.ts +257 -0
  205. package/src/beeper-message-like-me-cli.ts +84 -0
  206. package/src/beeper-message-like-me-export.ts +2274 -0
  207. package/src/beeper-message-like-me-recovery.ts +931 -0
  208. package/src/beeper-message-like-me-source.ts +4034 -0
  209. package/src/browser-admission.ts +928 -0
  210. package/src/browser-snapshots.ts +260 -0
  211. package/src/browser.ts +3873 -0
  212. package/src/canonical-json.ts +60 -0
  213. package/src/catalog-cli.ts +638 -0
  214. package/src/cli.ts +208 -0
  215. package/src/client-types.ts +301 -0
  216. package/src/client.ts +2726 -0
  217. package/src/confirmed-write-failure.ts +44 -0
  218. package/src/confirmed-write-model.ts +72 -0
  219. package/src/confirmed-write-platform.ts +1055 -0
  220. package/src/confirmed-write-program.ts +224 -0
  221. package/src/confirmed-write-runtime.ts +16 -0
  222. package/src/cursor-token.ts +228 -0
  223. package/src/derivation-dnr-readiness.ts +367 -0
  224. package/src/derivation-file-chooser.ts +384 -0
  225. package/src/derivation-network-boundary.ts +835 -0
  226. package/src/derivation-network-guard.ts +782 -0
  227. package/src/derivation-network-proxy-helper.ts +440 -0
  228. package/src/derivation-network-proxy.ts +385 -0
  229. package/src/derive-command-helper.ts +693 -0
  230. package/src/derive-fixtures.ts +343 -0
  231. package/src/derive-review.ts +980 -0
  232. package/src/derive.ts +4754 -0
  233. package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
  234. package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
  235. package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
  236. package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
  237. package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
  238. package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
  239. package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
  240. package/src/ghostget.ts +3672 -0
  241. package/src/gmail-capture.ts +1244 -0
  242. package/src/har-internal.ts +1242 -0
  243. package/src/har.ts +743 -0
  244. package/src/index.ts +218 -0
  245. package/src/invocation-read-platform.ts +55 -0
  246. package/src/invocation-read-program.ts +93 -0
  247. package/src/invocation-read-runtime.ts +11 -0
  248. package/src/linked-device-lifecycle-admission.ts +450 -0
  249. package/src/linked-device-lifecycle-journal.ts +1560 -0
  250. package/src/linked-device-lifecycle-runtime.ts +1602 -0
  251. package/src/local-cli-admission.ts +135 -0
  252. package/src/local-cli-contracts.ts +271 -0
  253. package/src/local-cli-execution.ts +98 -0
  254. package/src/local-cli-surface-contract.ts +1510 -0
  255. package/src/local-cli-tool-identity.ts +336 -0
  256. package/src/media/archive.ts +2325 -0
  257. package/src/media/args.ts +303 -0
  258. package/src/media/cli.ts +287 -0
  259. package/src/media/doctor.ts +475 -0
  260. package/src/media/ffmpeg.ts +1330 -0
  261. package/src/media/http-capture.ts +711 -0
  262. package/src/media/http-probe.ts +593 -0
  263. package/src/media/http.ts +387 -0
  264. package/src/media/index.ts +10 -0
  265. package/src/media/local-transcription.ts +613 -0
  266. package/src/media/lock.ts +321 -0
  267. package/src/media/manifest.ts +1712 -0
  268. package/src/media/metadata.ts +906 -0
  269. package/src/media/process.ts +723 -0
  270. package/src/media/revision.ts +166 -0
  271. package/src/media/runtime-closure.ts +1063 -0
  272. package/src/media/source-router.ts +187 -0
  273. package/src/media/transcriber-config.ts +1122 -0
  274. package/src/media/transcript-persistence-model.ts +15 -0
  275. package/src/media/transcript-persistence-platform.ts +89 -0
  276. package/src/media/transcript-persistence-program.ts +39 -0
  277. package/src/media/transcript-persistence-runtime.ts +24 -0
  278. package/src/media/transcript.ts +616 -0
  279. package/src/media/utf8-order.ts +30 -0
  280. package/src/media/whisper-cpp.ts +464 -0
  281. package/src/media/whisper-language.ts +58 -0
  282. package/src/media/yt-dlp.ts +439 -0
  283. package/src/message-like-me-agentic-messaging.ts +882 -0
  284. package/src/messaging-action-store.ts +793 -0
  285. package/src/messaging-runtime.ts +2136 -0
  286. package/src/messaging-store.ts +539 -0
  287. package/src/messaging-types.ts +1663 -0
  288. package/src/messaging.ts +334 -0
  289. package/src/model.ts +2671 -0
  290. package/src/oauth-google.ts +888 -0
  291. package/src/omni-client-types.ts +306 -0
  292. package/src/omni-client.ts +2225 -0
  293. package/src/omni-limits.ts +34 -0
  294. package/src/omni-model.ts +1614 -0
  295. package/src/omni-request.ts +379 -0
  296. package/src/omni-runtime.ts +1632 -0
  297. package/src/operation-deadline.ts +189 -0
  298. package/src/path-helper.ts +1656 -0
  299. package/src/pinned-https.ts +194 -0
  300. package/src/plan-assets.ts +832 -0
  301. package/src/platform-catalog.ts +1832 -0
  302. package/src/plugins/beeper-linked-device/plugin.ts +310 -0
  303. package/src/plugins/bluesky-web/plugin.ts +178 -0
  304. package/src/plugins/clasificados-web/plugin.ts +66 -0
  305. package/src/plugins/github-web/plugin.ts +80 -0
  306. package/src/plugins/gmail-official/plugin.ts +110 -0
  307. package/src/plugins/hacker-news-web/plugin.ts +52 -0
  308. package/src/plugins/imessage-direct/plugin.ts +195 -0
  309. package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
  310. package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
  311. package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
  312. package/src/plugins/linkedin-official/plugin.ts +84 -0
  313. package/src/plugins/linkedin-web/plugin.ts +412 -0
  314. package/src/plugins/meta-web/plugin.ts +317 -0
  315. package/src/plugins/reddit-web/flair.ts +492 -0
  316. package/src/plugins/reddit-web/plugin.ts +477 -0
  317. package/src/plugins/substack-web/plugin.ts +124 -0
  318. package/src/plugins/tiktok-web/plugin.ts +67 -0
  319. package/src/plugins/twitch-web/plugin.ts +54 -0
  320. package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
  321. package/src/plugins/x-official/plugin.ts +64 -0
  322. package/src/plugins/x-web/plugin.ts +488 -0
  323. package/src/plugins/youtube-web/plugin.ts +101 -0
  324. package/src/portable-run-recovery.ts +466 -0
  325. package/src/process-identity.ts +501 -0
  326. package/src/profile-clone-helper.ts +104 -0
  327. package/src/provider-catalog-views.ts +51 -0
  328. package/src/provider-context.ts +69 -0
  329. package/src/provider-contract-definitions-x.ts +45 -0
  330. package/src/provider-contract-definitions.ts +948 -0
  331. package/src/provider-contract-input-gmail.ts +176 -0
  332. package/src/provider-contract-input-linkedin.ts +77 -0
  333. package/src/provider-contract-input-x.ts +304 -0
  334. package/src/provider-contract-planning.ts +23 -0
  335. package/src/provider-contract-semantic-identity.ts +54 -0
  336. package/src/provider-contracts.ts +251 -0
  337. package/src/provider-http.ts +723 -0
  338. package/src/provider-plugin-auth.ts +36 -0
  339. package/src/provider-plugin-builtins.ts +229 -0
  340. package/src/provider-plugin-cleanup-barrier.ts +229 -0
  341. package/src/provider-plugin-cleanup-execution.ts +79 -0
  342. package/src/provider-plugin-cleanup-resource.ts +470 -0
  343. package/src/provider-plugin-contract-identity.ts +707 -0
  344. package/src/provider-plugin-host.ts +1972 -0
  345. package/src/provider-plugin-identifiers.ts +60 -0
  346. package/src/provider-plugin-invocation-lease.ts +889 -0
  347. package/src/provider-plugin-lifecycle-kernel.ts +566 -0
  348. package/src/provider-plugin-lifecycle.ts +1455 -0
  349. package/src/provider-plugin-module-analysis.ts +336 -0
  350. package/src/provider-plugin-package.ts +2530 -0
  351. package/src/provider-plugin-portable-authority.ts +106 -0
  352. package/src/provider-plugin-portable-catalog.ts +94 -0
  353. package/src/provider-plugin-portable-identity.ts +315 -0
  354. package/src/provider-plugin-portable-runtime.ts +2057 -0
  355. package/src/provider-plugin-protocol.ts +1625 -0
  356. package/src/provider-plugin-registry.ts +3342 -0
  357. package/src/provider-plugin-store.ts +2155 -0
  358. package/src/provider-plugin-test-fixture.ts +5 -0
  359. package/src/provider-plugin.ts +4907 -0
  360. package/src/provider-plugins.generated.ts +43 -0
  361. package/src/provider-plugins.ts +8 -0
  362. package/src/provider-subject.ts +41 -0
  363. package/src/provider.ts +315 -0
  364. package/src/providers/beeper-local-runtime.ts +5426 -0
  365. package/src/providers/beeper-local.ts +2739 -0
  366. package/src/providers/beeper-messaging.ts +462 -0
  367. package/src/providers/beeper-omni.ts +1159 -0
  368. package/src/providers/bluesky-web-runtime.ts +2895 -0
  369. package/src/providers/bluesky-web.ts +1614 -0
  370. package/src/providers/clasificados-web-runtime.ts +255 -0
  371. package/src/providers/clasificados-web.ts +503 -0
  372. package/src/providers/contact-projection.ts +301 -0
  373. package/src/providers/github-read-model.ts +94 -0
  374. package/src/providers/github-read-platform.ts +165 -0
  375. package/src/providers/github-read-program.ts +97 -0
  376. package/src/providers/github-web-runtime.ts +320 -0
  377. package/src/providers/github-web.ts +403 -0
  378. package/src/providers/gmail-api.ts +2834 -0
  379. package/src/providers/gmail-omni.ts +830 -0
  380. package/src/providers/gmail.ts +1130 -0
  381. package/src/providers/hacker-news-web-runtime.ts +220 -0
  382. package/src/providers/hacker-news-web.ts +809 -0
  383. package/src/providers/imessage-direct-install.ts +354 -0
  384. package/src/providers/imessage-direct-messaging.ts +532 -0
  385. package/src/providers/imessage-direct-omni.ts +400 -0
  386. package/src/providers/imessage-direct-runtime.ts +1498 -0
  387. package/src/providers/imessage-direct.ts +351 -0
  388. package/src/providers/instagram-video-foundations.ts +913 -0
  389. package/src/providers/instagram-web-profile-browser.ts +600 -0
  390. package/src/providers/iso-bmff.ts +338 -0
  391. package/src/providers/linkedin-company-platform.ts +42 -0
  392. package/src/providers/linkedin-company-program.ts +88 -0
  393. package/src/providers/linkedin-contact-failure.ts +84 -0
  394. package/src/providers/linkedin-contact-platform.ts +45 -0
  395. package/src/providers/linkedin-contact-program.ts +91 -0
  396. package/src/providers/linkedin-profile-activity-failure.ts +76 -0
  397. package/src/providers/linkedin-profile-activity-platform.ts +41 -0
  398. package/src/providers/linkedin-profile-activity-program.ts +67 -0
  399. package/src/providers/linkedin-read-failure.ts +52 -0
  400. package/src/providers/linkedin-self-platform.ts +53 -0
  401. package/src/providers/linkedin-self-program.ts +122 -0
  402. package/src/providers/linkedin-web-article-browser.ts +794 -0
  403. package/src/providers/linkedin-web-bootstrap.ts +146 -0
  404. package/src/providers/linkedin-web-contact.ts +830 -0
  405. package/src/providers/linkedin-web-feed-browser.ts +832 -0
  406. package/src/providers/linkedin-web-feed.ts +864 -0
  407. package/src/providers/linkedin-web-post-browser.ts +653 -0
  408. package/src/providers/linkedin-web-profile-browser.ts +683 -0
  409. package/src/providers/linkedin-web-runtime.ts +2388 -0
  410. package/src/providers/linkedin-web.ts +3305 -0
  411. package/src/providers/linkedin.ts +1885 -0
  412. package/src/providers/meta-bootstrap.ts +1043 -0
  413. package/src/providers/meta-facebook-group.ts +690 -0
  414. package/src/providers/meta-marketplace-relay.ts +747 -0
  415. package/src/providers/meta-omni.ts +373 -0
  416. package/src/providers/meta-relay-bundle.ts +1049 -0
  417. package/src/providers/meta-web-descriptors.ts +1740 -0
  418. package/src/providers/meta-web-runtime.ts +3388 -0
  419. package/src/providers/meta-web.ts +2931 -0
  420. package/src/providers/read-failure.ts +139 -0
  421. package/src/providers/reddit-omni.ts +462 -0
  422. package/src/providers/reddit-web-runtime.ts +1929 -0
  423. package/src/providers/reddit-web.ts +1611 -0
  424. package/src/providers/rental-listings.ts +460 -0
  425. package/src/providers/substack-omni.ts +406 -0
  426. package/src/providers/substack-video-mp4.ts +33 -0
  427. package/src/providers/substack-web-runtime.ts +2812 -0
  428. package/src/providers/substack-web.ts +1502 -0
  429. package/src/providers/tiktok-video-mp4.ts +33 -0
  430. package/src/providers/tiktok-web-runtime.ts +770 -0
  431. package/src/providers/tiktok-web.ts +2786 -0
  432. package/src/providers/twitch-web-runtime.ts +226 -0
  433. package/src/providers/twitch-web.ts +274 -0
  434. package/src/providers/whatsapp-account-identity.ts +55 -0
  435. package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
  436. package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
  437. package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
  438. package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
  439. package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
  440. package/src/providers/whatsapp-messaging.ts +132 -0
  441. package/src/providers/whatsapp-omni.ts +586 -0
  442. package/src/providers/whatsapp-web-runtime.ts +3108 -0
  443. package/src/providers/whatsapp-web.ts +1037 -0
  444. package/src/providers/x-image-provenance.ts +523 -0
  445. package/src/providers/x-made-with-ai.ts +106 -0
  446. package/src/providers/x-transaction-id.ts +413 -0
  447. package/src/providers/x-web-runtime.ts +3485 -0
  448. package/src/providers/x-web.ts +2332 -0
  449. package/src/providers/x.ts +1889 -0
  450. package/src/providers/youtube-web-runtime.ts +1400 -0
  451. package/src/providers/youtube-web.ts +1221 -0
  452. package/src/read-admission-runtime.ts +91 -0
  453. package/src/read-client.ts +521 -0
  454. package/src/read-effect-platform.ts +7 -0
  455. package/src/read-effect-runtime.ts +14 -0
  456. package/src/read-effect.ts +52 -0
  457. package/src/read-projection-admission.ts +866 -0
  458. package/src/read-projections.ts +3151 -0
  459. package/src/recovery.ts +1492 -0
  460. package/src/reviewed-template.ts +146 -0
  461. package/src/run-journal.ts +1272 -0
  462. package/src/runtime.ts +5116 -0
  463. package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
  464. package/src/scripts/install-whatsapp-protocol.sh +232 -0
  465. package/src/scripts/recover-install-stages.ts +1491 -0
  466. package/src/scripts/resolve-state-home.ts +30 -0
  467. package/src/scripts/scaffold-web-provider.ts +973 -0
  468. package/src/scripts/sync-bundled-adapters.ts +695 -0
  469. package/src/session-secrets.ts +1183 -0
  470. package/src/state-helper.bunfig.toml +2 -0
  471. package/src/state-helper.ts +2914 -0
  472. package/src/storage.ts +3842 -0
  473. package/src/transport-policy.ts +2 -0
  474. package/src/usage.ts +182 -0
  475. package/src/version.ts +2 -0
  476. package/src/web-session-authentication-policy.ts +136 -0
  477. package/src/web-session-cleanup-admission.ts +2344 -0
  478. package/src/web-session-cleanup-join.ts +99 -0
  479. package/src/web-session-client.ts +996 -0
  480. package/src/web-session-contract-definitions.ts +820 -0
  481. package/src/web-session-contract-planning.ts +42 -0
  482. package/src/web-session-contracts.ts +228 -0
  483. package/src/web-session-cookies.ts +500 -0
  484. package/src/web-session-execution.ts +385 -0
  485. package/src/web-session-http.ts +373 -0
  486. package/src/web-session-read-errors.ts +19 -0
  487. package/src/web-session-read-runtime.ts +124 -0
  488. package/src/web-session-recovery.ts +818 -0
  489. package/src/web-session-sites.ts +58 -0
  490. package/src/web-session-template.ts +994 -0
  491. package/src/web-session.ts +93 -0
  492. package/src/whatsapp-client-binding.ts +21 -0
  493. package/src/whatsapp-client-types.ts +75 -0
  494. package/src/whatsapp-client.ts +395 -0
  495. package/src/whatsapp-export-coordinate.ts +17 -0
  496. package/src/whatsapp-message-bundle-v2.ts +195 -0
  497. package/src/whatsapp-message-like-me-cli.ts +195 -0
  498. package/src/whatsapp-message-like-me-export.ts +1072 -0
  499. package/src/whatsapp-message-like-me-source.ts +1012 -0
  500. package/tsconfig.json +67 -0
@@ -0,0 +1,913 @@
1
+ import { Blob } from "node:buffer";
2
+ import { createHash } from "node:crypto";
3
+ import { constants } from "node:fs";
4
+ import { open } from "node:fs/promises";
5
+ import { types as nodeTypes } from "node:util";
6
+
7
+ import type { BrowserFileResolver } from "../browser";
8
+ import { canonicalJson } from "../canonical-json";
9
+ import type { FileInputValue, OperationInput } from "../model";
10
+ import type { WebSessionOperationDeadline } from "../web-session-execution";
11
+ import {
12
+ isoBmffMp4VideoMetadata,
13
+ isoBmffVideoDimensions,
14
+ } from "./iso-bmff";
15
+
16
+ const MAX_INSTAGRAM_VIDEO_BYTES = 128 * 1024 * 1024;
17
+ const MAX_INSTAGRAM_THUMBNAIL_BYTES = 8 * 1024 * 1024;
18
+ const INSTAGRAM_MEDIA_ID_PATTERN = /^[1-9][0-9]{0,31}(?:_[1-9][0-9]{0,31})?$/u;
19
+ const INSTAGRAM_SHORTCODE_PATTERN = /^[A-Za-z0-9_-]{1,64}$/u;
20
+ const INSTAGRAM_VIEWER_ID_PATTERN = /^[1-9][0-9]{0,31}$/u;
21
+ const INSTAGRAM_ORIGIN = "https://www.instagram.com";
22
+ const INSTAGRAM_VIDEO_PUBLISH_BINDING_KEYS = Object.freeze([
23
+ "audience",
24
+ "byteLength",
25
+ "bytes",
26
+ "caption",
27
+ "durationMilliseconds",
28
+ "height",
29
+ "mediaSha256",
30
+ "mediaType",
31
+ "thumbnailByteLength",
32
+ "thumbnailBytes",
33
+ "thumbnailHeight",
34
+ "thumbnailMediaType",
35
+ "thumbnailSha256",
36
+ "thumbnailWidth",
37
+ "width",
38
+ ] as const);
39
+ const TYPED_ARRAY_PROTOTYPE = Object.getPrototypeOf(
40
+ Uint8Array.prototype,
41
+ ) as object;
42
+ const TYPED_ARRAY_BUFFER_GETTER = Object.getOwnPropertyDescriptor(
43
+ TYPED_ARRAY_PROTOTYPE,
44
+ "buffer",
45
+ )?.get;
46
+ const TYPED_ARRAY_BYTE_LENGTH_GETTER = Object.getOwnPropertyDescriptor(
47
+ TYPED_ARRAY_PROTOTYPE,
48
+ "byteLength",
49
+ )?.get;
50
+ const MP4_COMPATIBLE_BRANDS = Object.freeze(new Set([
51
+ "M4V ",
52
+ "MSNV",
53
+ "avc1",
54
+ "iso2",
55
+ "isom",
56
+ "mp41",
57
+ "mp42",
58
+ ]));
59
+
60
+ export type InstagramVideoPublishPlan = Readonly<{
61
+ audience: "default";
62
+ caption: string;
63
+ media: FileInputValue;
64
+ thumbnail: FileInputValue;
65
+ }>;
66
+
67
+ export type InstagramBoundVideoPublish = Readonly<{
68
+ audience: "default";
69
+ bytes: Uint8Array<ArrayBuffer>;
70
+ byteLength: number;
71
+ caption: string;
72
+ durationMilliseconds: number;
73
+ height: number;
74
+ mediaType: "video/mp4";
75
+ mediaSha256: string;
76
+ thumbnailByteLength: number;
77
+ thumbnailBytes: Uint8Array<ArrayBuffer>;
78
+ thumbnailHeight: number;
79
+ thumbnailMediaType: "image/jpeg";
80
+ thumbnailSha256: string;
81
+ thumbnailWidth: number;
82
+ width: number;
83
+ }>;
84
+
85
+ export type InstagramVideoPublishDispatchSnapshot = Readonly<{
86
+ audience: "default";
87
+ body: Blob;
88
+ byteLength: number;
89
+ caption: string;
90
+ durationMilliseconds: number;
91
+ height: number;
92
+ mediaType: "video/mp4";
93
+ mediaSha256: string;
94
+ thumbnailBody: Blob;
95
+ thumbnailByteLength: number;
96
+ thumbnailHeight: number;
97
+ thumbnailMediaType: "image/jpeg";
98
+ thumbnailSha256: string;
99
+ thumbnailWidth: number;
100
+ width: number;
101
+ }>;
102
+
103
+ export type InstagramAuthoredPostDeleteInput = Readonly<{
104
+ expectedCaption: string;
105
+ expectedMediaKind: "video";
106
+ mediaId: string;
107
+ }>;
108
+
109
+ export type InstagramVideoAcceptedTarget = Readonly<{
110
+ code: string;
111
+ mediaId: string;
112
+ url: string;
113
+ }>;
114
+
115
+ export type InstagramVideoReadbackExpectation = Readonly<{
116
+ expectedCaption: string;
117
+ expectedCode?: string;
118
+ mediaId: string;
119
+ viewerId: string;
120
+ }>;
121
+
122
+ export type InstagramVideoConfigureExpectation = Readonly<{
123
+ caption: string;
124
+ uploadId: string;
125
+ viewerId: string;
126
+ }>;
127
+
128
+ /** Exact remaining boundary after the authorized disposable lifecycle. */
129
+ export const INSTAGRAM_VIDEO_CAPTURE_BLOCKERS = Object.freeze({
130
+ "media.publish":
131
+ "the observed first configure response was 202 without an accepted target; neither a safe repeated configure POST nor an independent upload-ID reconciliation read is proven",
132
+ } as const);
133
+
134
+ function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
135
+ return typeof value === "object" && value !== null && !Array.isArray(value);
136
+ }
137
+
138
+ function exactInputKeys(
139
+ input: OperationInput,
140
+ required: readonly string[],
141
+ label: string,
142
+ ): void {
143
+ const permitted = new Set(required);
144
+ if (Object.keys(input).some((key) => !permitted.has(key))) {
145
+ throw new Error(`${label} contained an unsupported input field`);
146
+ }
147
+ if (required.some((key) => !Object.hasOwn(input, key))) {
148
+ throw new Error(`${label} omitted a required input field`);
149
+ }
150
+ }
151
+
152
+ function boundedCaption(value: unknown, label: string): string {
153
+ if (
154
+ typeof value !== "string"
155
+ || value.length < 1
156
+ || value.length > 1_000
157
+ || /[\0\r]/u.test(value)
158
+ ) throw new Error(`${label} must be 1 to 1000 bounded UTF-16 code units`);
159
+ return value;
160
+ }
161
+
162
+ function instagramMediaId(value: unknown, label: string): string {
163
+ if (typeof value !== "string" || !INSTAGRAM_MEDIA_ID_PATTERN.test(value)) {
164
+ throw new Error(`${label} must be one exact canonical Instagram media ID`);
165
+ }
166
+ return value;
167
+ }
168
+
169
+ function instagramShortcode(value: unknown, label: string): string {
170
+ if (typeof value !== "string" || !INSTAGRAM_SHORTCODE_PATTERN.test(value)) {
171
+ throw new Error(`${label} must be one exact Instagram shortcode`);
172
+ }
173
+ return value;
174
+ }
175
+
176
+ function instagramVideoPermalink(code: string): string {
177
+ return `${INSTAGRAM_ORIGIN}/p/${code}/`;
178
+ }
179
+
180
+ function exactNormalizedKeys(
181
+ value: Readonly<Record<string, unknown>>,
182
+ expected: readonly string[],
183
+ label: string,
184
+ ): void {
185
+ if (Object.keys(value).sort().join(",") !== [...expected].sort().join(",")) {
186
+ throw new Error(`${label} changed its bounded normalized shape`);
187
+ }
188
+ }
189
+
190
+ function boundedForeignRecord(
191
+ value: unknown,
192
+ label: string,
193
+ maximumKeys: number,
194
+ ): Readonly<Record<string, unknown>> {
195
+ if (!isRecord(value) || Object.keys(value).length > maximumKeys) {
196
+ throw new Error(`${label} must be one bounded object`);
197
+ }
198
+ return value;
199
+ }
200
+
201
+ function exactInstagramVideoPublishBinding(
202
+ value: unknown,
203
+ ): Readonly<Record<string, unknown>> {
204
+ if (
205
+ typeof value !== "object"
206
+ || value === null
207
+ ) throw new Error("Instagram video binding must be one exact object");
208
+ if (nodeTypes.isProxy(value)) {
209
+ throw new Error("Instagram video binding must not be a proxy");
210
+ }
211
+ if (Array.isArray(value)) {
212
+ throw new Error("Instagram video binding must be one exact object");
213
+ }
214
+ const prototype: unknown = Object.getPrototypeOf(value);
215
+ if (prototype !== Object.prototype && prototype !== null) {
216
+ throw new Error("Instagram video binding must use a plain prototype");
217
+ }
218
+ const descriptors = Object.getOwnPropertyDescriptors(value);
219
+ const ownKeys = Reflect.ownKeys(descriptors);
220
+ if (
221
+ ownKeys.length !== INSTAGRAM_VIDEO_PUBLISH_BINDING_KEYS.length
222
+ || ownKeys.some((key) => typeof key !== "string")
223
+ || (ownKeys as string[]).sort().join(",")
224
+ !== [...INSTAGRAM_VIDEO_PUBLISH_BINDING_KEYS].sort().join(",")
225
+ ) throw new Error("Instagram video binding contained unsupported fields");
226
+ const snapshot = Object.create(null) as Record<string, unknown>;
227
+ for (const key of INSTAGRAM_VIDEO_PUBLISH_BINDING_KEYS) {
228
+ const descriptor = descriptors[key];
229
+ if (
230
+ descriptor === undefined
231
+ || !descriptor.enumerable
232
+ || !("value" in descriptor)
233
+ ) {
234
+ throw new Error(
235
+ "Instagram video binding must contain only enumerable data properties",
236
+ );
237
+ }
238
+ snapshot[key] = descriptor.value;
239
+ }
240
+ return Object.freeze(snapshot);
241
+ }
242
+
243
+ function snapshotInstagramBytes(
244
+ value: unknown,
245
+ maximumBytes: number,
246
+ label: "MP4" | "JPEG thumbnail",
247
+ ): Uint8Array<ArrayBuffer> {
248
+ if (
249
+ typeof value !== "object"
250
+ || value === null
251
+ || nodeTypes.isProxy(value)
252
+ || !(value instanceof Uint8Array)
253
+ || Object.getPrototypeOf(value) !== Uint8Array.prototype
254
+ || TYPED_ARRAY_BUFFER_GETTER === undefined
255
+ || TYPED_ARRAY_BYTE_LENGTH_GETTER === undefined
256
+ ) throw new Error(`Instagram video binding must contain one bounded ${label}`);
257
+ let buffer: unknown;
258
+ let byteLength: unknown;
259
+ try {
260
+ buffer = TYPED_ARRAY_BUFFER_GETTER.call(value) as unknown;
261
+ byteLength = TYPED_ARRAY_BYTE_LENGTH_GETTER.call(value) as unknown;
262
+ } catch {
263
+ throw new Error(`Instagram video binding must contain one bounded ${label}`);
264
+ }
265
+ if (
266
+ typeof byteLength !== "number"
267
+ || !Number.isSafeInteger(byteLength)
268
+ || byteLength < (label === "MP4" ? 24 : 16)
269
+ || byteLength > maximumBytes
270
+ || nodeTypes.isSharedArrayBuffer(buffer)
271
+ ) throw new Error(`Instagram video binding must contain one bounded ${label}`);
272
+ const bytes = new Uint8Array(byteLength);
273
+ try {
274
+ Uint8Array.prototype.set.call(bytes, value);
275
+ } catch {
276
+ throw new Error(`Instagram video binding must contain one bounded ${label}`);
277
+ }
278
+ return bytes;
279
+ }
280
+
281
+ function instagramJpegDimensions(bytes: Uint8Array): Readonly<{
282
+ height: number;
283
+ width: number;
284
+ }> {
285
+ if (
286
+ bytes.byteLength < 16
287
+ || bytes[0] !== 0xff
288
+ || bytes[1] !== 0xd8
289
+ || bytes.at(-2) !== 0xff
290
+ || bytes.at(-1) !== 0xd9
291
+ ) throw new Error("Instagram video thumbnail must be one complete JPEG");
292
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
293
+ const frameMarkers = new Set([
294
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7,
295
+ 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
296
+ ]);
297
+ let offset = 2;
298
+ let frame: Readonly<{ height: number; width: number }> | null = null;
299
+ while (offset < bytes.byteLength - 2) {
300
+ if (bytes[offset] !== 0xff) {
301
+ throw new Error("Instagram video thumbnail JPEG marker stream changed shape");
302
+ }
303
+ while (bytes[offset] === 0xff) offset += 1;
304
+ const marker = bytes[offset];
305
+ offset += 1;
306
+ if (marker === undefined || marker === 0x00) {
307
+ throw new Error("Instagram video thumbnail JPEG marker stream changed shape");
308
+ }
309
+ if (marker === 0xd9) break;
310
+ if (marker === 0xda) {
311
+ if (frame === null) {
312
+ throw new Error("Instagram video thumbnail JPEG omitted its frame dimensions");
313
+ }
314
+ return frame;
315
+ }
316
+ if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
317
+ continue;
318
+ }
319
+ if (offset + 2 > bytes.byteLength) {
320
+ throw new Error("Instagram video thumbnail JPEG segment exceeded its bytes");
321
+ }
322
+ const length = view.getUint16(offset);
323
+ if (length < 2 || offset + length > bytes.byteLength) {
324
+ throw new Error("Instagram video thumbnail JPEG segment exceeded its bytes");
325
+ }
326
+ if (frameMarkers.has(marker)) {
327
+ if (frame !== null || length < 8 || bytes[offset + 2] !== 8) {
328
+ throw new Error("Instagram video thumbnail JPEG frame changed shape");
329
+ }
330
+ const height = view.getUint16(offset + 3);
331
+ const width = view.getUint16(offset + 5);
332
+ if (height < 1 || height > 20_000 || width < 1 || width > 20_000) {
333
+ throw new Error("Instagram video thumbnail JPEG dimensions are outside the reviewed bound");
334
+ }
335
+ frame = Object.freeze({ height, width });
336
+ }
337
+ offset += length;
338
+ }
339
+ if (frame === null) {
340
+ throw new Error("Instagram video thumbnail JPEG omitted its frame dimensions");
341
+ }
342
+ return frame;
343
+ }
344
+
345
+ function planBoundFile(value: unknown): FileInputValue {
346
+ if (
347
+ !isRecord(value)
348
+ || Object.keys(value).sort().join(",") !== "kind,reference"
349
+ || value.kind !== "file"
350
+ || typeof value.reference !== "string"
351
+ || value.reference.length < 1
352
+ || value.reference.length > 4_096
353
+ || /[\0\r\n]/u.test(value.reference)
354
+ ) throw new Error("input.media must be one exact plan-bound file");
355
+ return Object.freeze({ kind: "file", reference: value.reference });
356
+ }
357
+
358
+ function requireMp4FileType(bytes: Uint8Array): void {
359
+ if (bytes.byteLength < 24) {
360
+ throw new Error("Instagram video must be one complete MP4 file");
361
+ }
362
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
363
+ const fileTypeBytes = view.getUint32(0);
364
+ if (
365
+ fileTypeBytes < 16
366
+ || fileTypeBytes > bytes.byteLength
367
+ || String.fromCharCode(...bytes.subarray(4, 8)) !== "ftyp"
368
+ || (fileTypeBytes - 16) % 4 !== 0
369
+ ) throw new Error("Instagram video must begin with one bounded MP4 file-type box");
370
+ const majorBrand = String.fromCharCode(...bytes.subarray(8, 12));
371
+ if (majorBrand === "qt ") {
372
+ throw new Error("Instagram video file-type box is not MP4-compatible");
373
+ }
374
+ const brands: string[] = [];
375
+ for (let offset = 8; offset < fileTypeBytes; offset += offset === 8 ? 8 : 4) {
376
+ brands.push(String.fromCharCode(...bytes.subarray(offset, offset + 4)));
377
+ }
378
+ if (!brands.some((brand) => MP4_COMPATIBLE_BRANDS.has(brand))) {
379
+ throw new Error("Instagram video file-type box is not MP4-compatible");
380
+ }
381
+ }
382
+
383
+ function instagramVideoSha256(bytes: Uint8Array): string {
384
+ return createHash("sha256").update(bytes).digest("hex");
385
+ }
386
+
387
+ function instagramVideoMetadata(bytes: Uint8Array): Readonly<{
388
+ durationMilliseconds: number;
389
+ height: number;
390
+ width: number;
391
+ }> {
392
+ requireMp4FileType(bytes);
393
+ const dimensions = isoBmffVideoDimensions(bytes, "Instagram video");
394
+ const metadata = isoBmffMp4VideoMetadata(
395
+ bytes,
396
+ "Instagram video",
397
+ Object.freeze({
398
+ compatibleBrands: Object.freeze([...MP4_COMPATIBLE_BRANDS]),
399
+ rejectedMajorBrands: Object.freeze(["qt "]),
400
+ }),
401
+ );
402
+ const durationMilliseconds = Math.floor(metadata.durationSeconds * 1_000);
403
+ if (
404
+ metadata.durationSeconds < 0.001
405
+ || metadata.durationSeconds > 86_400
406
+ || !Number.isSafeInteger(durationMilliseconds)
407
+ || durationMilliseconds < 1
408
+ || durationMilliseconds > 86_400_000
409
+ || metadata.height !== dimensions.height
410
+ || metadata.width !== dimensions.width
411
+ ) throw new Error("Instagram video duration is outside the reviewed bound");
412
+ return Object.freeze({ durationMilliseconds, ...dimensions });
413
+ }
414
+
415
+ async function stableInstagramPublishFile(
416
+ path: string,
417
+ maximumBytes: number,
418
+ label: "video" | "video thumbnail",
419
+ operationDeadline?: WebSessionOperationDeadline,
420
+ ): Promise<Uint8Array<ArrayBuffer>> {
421
+ const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
422
+ const handle = operationDeadline === undefined
423
+ ? await open(path, constants.O_RDONLY | noFollow)
424
+ : await operationDeadline.run(
425
+ () => open(path, constants.O_RDONLY | noFollow),
426
+ "authenticated web operation deadline",
427
+ );
428
+ try {
429
+ const before = operationDeadline === undefined
430
+ ? await handle.stat()
431
+ : await operationDeadline.run(
432
+ () => handle.stat(),
433
+ "authenticated web operation deadline",
434
+ );
435
+ if (!before.isFile() || before.size < 16 || before.size > maximumBytes) {
436
+ throw new Error(
437
+ `Instagram ${label} must be a regular file within its reviewed in-memory bound`,
438
+ );
439
+ }
440
+ const fileBytes = operationDeadline === undefined
441
+ ? await handle.readFile()
442
+ : await operationDeadline.run(
443
+ () => handle.readFile(),
444
+ "authenticated web operation deadline",
445
+ );
446
+ const after = operationDeadline === undefined
447
+ ? await handle.stat()
448
+ : await operationDeadline.run(
449
+ () => handle.stat(),
450
+ "authenticated web operation deadline",
451
+ );
452
+ if (
453
+ before.dev !== after.dev
454
+ || before.ino !== after.ino
455
+ || before.size !== after.size
456
+ || before.mtimeMs !== after.mtimeMs
457
+ || before.ctimeMs !== after.ctimeMs
458
+ || fileBytes.byteLength !== before.size
459
+ ) throw new Error(`Instagram ${label} changed while it was materialized`);
460
+ return new Uint8Array(fileBytes);
461
+ } finally {
462
+ await handle.close();
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Validate the complete capture-neutral publication plan before any attachment
468
+ * path is resolved. Consumer-web request fields remain intentionally absent.
469
+ */
470
+ export function prepareInstagramVideoPublishInput(
471
+ input: OperationInput,
472
+ ): InstagramVideoPublishPlan {
473
+ exactInputKeys(
474
+ input,
475
+ ["audience", "caption", "media", "thumbnail"],
476
+ "Instagram video publishing",
477
+ );
478
+ if (input.audience !== "default") {
479
+ throw new Error("input.audience must be the exact default Instagram audience");
480
+ }
481
+ return Object.freeze({
482
+ audience: "default" as const,
483
+ caption: boundedCaption(input.caption, "input.caption"),
484
+ media: planBoundFile(input.media),
485
+ thumbnail: planBoundFile(input.thumbnail),
486
+ });
487
+ }
488
+
489
+ /**
490
+ * Resolve and bind one exact stable MP4. This local preflight performs no
491
+ * authenticated request and supplies no consumer-web upload implementation.
492
+ */
493
+ export async function materializeInstagramVideoPublishInput(
494
+ input: OperationInput,
495
+ fileResolver: BrowserFileResolver | undefined,
496
+ operationDeadline?: WebSessionOperationDeadline,
497
+ ): Promise<InstagramBoundVideoPublish> {
498
+ const plan = prepareInstagramVideoPublishInput(input);
499
+ if (fileResolver === undefined) {
500
+ throw new Error("Instagram video upload requires the plan-bound file resolver");
501
+ }
502
+ const resolve = () => fileResolver([plan.media, plan.thumbnail]);
503
+ const paths = operationDeadline === undefined
504
+ ? await resolve()
505
+ : await operationDeadline.run(resolve, "authenticated web operation deadline");
506
+ operationDeadline?.throwIfUnavailable("authenticated web operation deadline");
507
+ if (
508
+ paths.length !== 2
509
+ || typeof paths[0] !== "string"
510
+ || typeof paths[1] !== "string"
511
+ ) {
512
+ throw new Error(
513
+ "Instagram file resolver did not return the exact video and JPEG thumbnail paths",
514
+ );
515
+ }
516
+ const bytes = await stableInstagramPublishFile(
517
+ paths[0],
518
+ MAX_INSTAGRAM_VIDEO_BYTES,
519
+ "video",
520
+ operationDeadline,
521
+ );
522
+ const dimensions = instagramVideoMetadata(bytes);
523
+ const thumbnailBytes = await stableInstagramPublishFile(
524
+ paths[1],
525
+ MAX_INSTAGRAM_THUMBNAIL_BYTES,
526
+ "video thumbnail",
527
+ operationDeadline,
528
+ );
529
+ const thumbnailDimensions = instagramJpegDimensions(thumbnailBytes);
530
+ return Object.freeze({
531
+ audience: plan.audience,
532
+ bytes,
533
+ byteLength: bytes.byteLength,
534
+ caption: plan.caption,
535
+ durationMilliseconds: dimensions.durationMilliseconds,
536
+ height: dimensions.height,
537
+ mediaType: "video/mp4" as const,
538
+ mediaSha256: instagramVideoSha256(bytes),
539
+ thumbnailByteLength: thumbnailBytes.byteLength,
540
+ thumbnailBytes,
541
+ thumbnailHeight: thumbnailDimensions.height,
542
+ thumbnailMediaType: "image/jpeg" as const,
543
+ thumbnailSha256: instagramVideoSha256(thumbnailBytes),
544
+ thumbnailWidth: thumbnailDimensions.width,
545
+ width: dimensions.width,
546
+ });
547
+ }
548
+
549
+ /**
550
+ * Revalidate and snapshot the exact materialized MP4 immediately before a
551
+ * future dispatch. The capture-required operation does not call this helper.
552
+ */
553
+ export function revalidateInstagramVideoPublishBindingForDispatch(
554
+ value: unknown,
555
+ ): InstagramVideoPublishDispatchSnapshot {
556
+ const binding = exactInstagramVideoPublishBinding(value);
557
+ const bytes = snapshotInstagramBytes(
558
+ binding.bytes,
559
+ MAX_INSTAGRAM_VIDEO_BYTES,
560
+ "MP4",
561
+ );
562
+ const thumbnailBytes = snapshotInstagramBytes(
563
+ binding.thumbnailBytes,
564
+ MAX_INSTAGRAM_THUMBNAIL_BYTES,
565
+ "JPEG thumbnail",
566
+ );
567
+ const dimensions = instagramVideoMetadata(bytes);
568
+ const thumbnailDimensions = instagramJpegDimensions(thumbnailBytes);
569
+ const mediaSha256 = instagramVideoSha256(bytes);
570
+ const thumbnailSha256 = instagramVideoSha256(thumbnailBytes);
571
+ if (
572
+ !Number.isSafeInteger(binding.byteLength)
573
+ || binding.byteLength !== bytes.byteLength
574
+ || typeof binding.mediaSha256 !== "string"
575
+ || !/^[a-f0-9]{64}$/u.test(binding.mediaSha256)
576
+ || binding.mediaSha256 !== mediaSha256
577
+ || binding.durationMilliseconds !== dimensions.durationMilliseconds
578
+ || binding.height !== dimensions.height
579
+ || binding.width !== dimensions.width
580
+ || !Number.isSafeInteger(binding.thumbnailByteLength)
581
+ || binding.thumbnailByteLength !== thumbnailBytes.byteLength
582
+ || typeof binding.thumbnailSha256 !== "string"
583
+ || !/^[a-f0-9]{64}$/u.test(binding.thumbnailSha256)
584
+ || binding.thumbnailSha256 !== thumbnailSha256
585
+ || binding.thumbnailHeight !== thumbnailDimensions.height
586
+ || binding.thumbnailWidth !== thumbnailDimensions.width
587
+ ) throw new Error("Instagram video binding changed from its exact bytes");
588
+ if (
589
+ binding.audience !== "default"
590
+ || binding.mediaType !== "video/mp4"
591
+ || binding.thumbnailMediaType !== "image/jpeg"
592
+ ) {
593
+ throw new Error("Instagram video binding declarations are invalid");
594
+ }
595
+ const body = new Blob([bytes], { type: "video/mp4" });
596
+ const thumbnailBody = new Blob([thumbnailBytes], { type: "image/jpeg" });
597
+ if (
598
+ body.size !== bytes.byteLength
599
+ || body.type !== "video/mp4"
600
+ || thumbnailBody.size !== thumbnailBytes.byteLength
601
+ || thumbnailBody.type !== "image/jpeg"
602
+ ) {
603
+ throw new Error("Instagram video dispatch snapshot changed shape");
604
+ }
605
+ return Object.freeze({
606
+ audience: "default" as const,
607
+ body,
608
+ byteLength: bytes.byteLength,
609
+ caption: boundedCaption(binding.caption, "Instagram video binding caption"),
610
+ durationMilliseconds: dimensions.durationMilliseconds,
611
+ height: dimensions.height,
612
+ mediaSha256,
613
+ mediaType: "video/mp4" as const,
614
+ thumbnailBody,
615
+ thumbnailByteLength: thumbnailBytes.byteLength,
616
+ thumbnailHeight: thumbnailDimensions.height,
617
+ thumbnailMediaType: "image/jpeg" as const,
618
+ thumbnailSha256,
619
+ thumbnailWidth: thumbnailDimensions.width,
620
+ width: dimensions.width,
621
+ });
622
+ }
623
+
624
+ /** Validate the exact authored-video confirmation required before deletion. */
625
+ export function prepareInstagramAuthoredPostDeleteInput(
626
+ input: OperationInput,
627
+ ): InstagramAuthoredPostDeleteInput {
628
+ exactInputKeys(
629
+ input,
630
+ ["expected_caption", "expected_media_kind", "media_id"],
631
+ "Instagram authored-post deletion",
632
+ );
633
+ if (
634
+ typeof input.media_id !== "string"
635
+ || !/^[1-9][0-9]{0,31}_[1-9][0-9]{0,31}$/u.test(input.media_id)
636
+ ) throw new Error("input.media_id must be one exact full Instagram media ID");
637
+ if (input.expected_media_kind !== "video") {
638
+ throw new Error("input.expected_media_kind must be video");
639
+ }
640
+ return Object.freeze({
641
+ expectedCaption: boundedCaption(input.expected_caption, "input.expected_caption"),
642
+ expectedMediaKind: "video" as const,
643
+ mediaId: input.media_id,
644
+ });
645
+ }
646
+
647
+ /**
648
+ * Bind the already-bounded `posts.read` projection to one exact authored video.
649
+ * The shortcode is provider-derived here and is never accepted as delete input.
650
+ */
651
+ export function bindInstagramVideoMediaReadback(
652
+ value: unknown,
653
+ expectation: InstagramVideoReadbackExpectation,
654
+ ): InstagramVideoAcceptedTarget {
655
+ const expectedMediaId = instagramMediaId(
656
+ expectation.mediaId,
657
+ "Instagram video readback expected media ID",
658
+ );
659
+ const expectedCaption = boundedCaption(
660
+ expectation.expectedCaption,
661
+ "Instagram video readback expected caption",
662
+ );
663
+ if (
664
+ typeof expectation.viewerId !== "string"
665
+ || !INSTAGRAM_VIEWER_ID_PATTERN.test(expectation.viewerId)
666
+ ) throw new Error("Instagram video readback expected viewer ID is invalid");
667
+ const expectedCode = expectation.expectedCode === undefined
668
+ ? undefined
669
+ : instagramShortcode(
670
+ expectation.expectedCode,
671
+ "Instagram video readback expected shortcode",
672
+ );
673
+ if (!isRecord(value)) {
674
+ throw new Error("Instagram video readback must be one bounded media projection");
675
+ }
676
+ exactNormalizedKeys(value, [
677
+ "caption",
678
+ "code",
679
+ "comment_count",
680
+ "has_liked",
681
+ "has_viewer_saved",
682
+ "id",
683
+ "like_count",
684
+ "media_type",
685
+ "pk",
686
+ "taken_at",
687
+ "user",
688
+ ], "Instagram video readback");
689
+ const mediaId = instagramMediaId(value.id, "Instagram video readback media ID");
690
+ const code = instagramShortcode(value.code, "Instagram video readback shortcode");
691
+ if (
692
+ mediaId !== expectedMediaId
693
+ || value.caption !== expectedCaption
694
+ || value.media_type !== 2
695
+ || (expectedCode !== undefined && code !== expectedCode)
696
+ ) throw new Error("Instagram video readback did not bind the confirmed video");
697
+ if (!isRecord(value.user)) {
698
+ throw new Error("Instagram video readback did not bind the confirmed actor");
699
+ }
700
+ exactNormalizedKeys(
701
+ value.user,
702
+ ["full_name", "id", "username"],
703
+ "Instagram video readback actor",
704
+ );
705
+ if (value.user.id !== expectation.viewerId) {
706
+ throw new Error("Instagram video readback did not bind the confirmed actor");
707
+ }
708
+ return Object.freeze({
709
+ code,
710
+ mediaId,
711
+ url: instagramVideoPermalink(code),
712
+ });
713
+ }
714
+
715
+ function parseInstagramVideoPermalink(value: unknown, code: string): string {
716
+ if (typeof value !== "string" || value.length < 1 || value.length > 2_048) {
717
+ throw new Error("Instagram provider-accepted video target returned an invalid permalink");
718
+ }
719
+ let url: URL;
720
+ try {
721
+ url = new URL(value);
722
+ } catch {
723
+ throw new Error("Instagram provider-accepted video target returned an invalid permalink");
724
+ }
725
+ if (
726
+ url.origin !== INSTAGRAM_ORIGIN
727
+ || url.username !== ""
728
+ || url.password !== ""
729
+ || url.pathname !== `/p/${code}/`
730
+ || url.search !== ""
731
+ || url.hash !== ""
732
+ ) throw new Error("Instagram provider-accepted video target returned an invalid permalink");
733
+ return url.href;
734
+ }
735
+
736
+ /** Serialize only provider-derived, secret-free target identity. */
737
+ export function instagramVideoAcceptedTargetIdentifier(
738
+ target: InstagramVideoAcceptedTarget,
739
+ ): string {
740
+ const mediaId = instagramMediaId(
741
+ target.mediaId,
742
+ "Instagram provider-accepted video target media ID",
743
+ );
744
+ const code = instagramShortcode(
745
+ target.code,
746
+ "Instagram provider-accepted video target shortcode",
747
+ );
748
+ const url = parseInstagramVideoPermalink(target.url, code);
749
+ return canonicalJson({ code, mediaId, url });
750
+ }
751
+
752
+ /** Parse the exact encrypted target later supplied to read-only reconciliation. */
753
+ export function parseInstagramVideoAcceptedTargetIdentifier(
754
+ identifier: string,
755
+ ): InstagramVideoAcceptedTarget {
756
+ if (typeof identifier !== "string" || identifier.length < 1 || identifier.length > 4_096) {
757
+ throw new Error("Instagram provider-accepted video target is not canonical JSON");
758
+ }
759
+ let value: unknown;
760
+ try {
761
+ value = JSON.parse(identifier);
762
+ } catch {
763
+ throw new Error("Instagram provider-accepted video target is not canonical JSON");
764
+ }
765
+ if (!isRecord(value)) {
766
+ throw new Error("Instagram provider-accepted video target contained unsupported fields");
767
+ }
768
+ exactNormalizedKeys(
769
+ value,
770
+ ["code", "mediaId", "url"],
771
+ "Instagram provider-accepted video target",
772
+ );
773
+ const target = Object.freeze({
774
+ code: instagramShortcode(
775
+ value.code,
776
+ "Instagram provider-accepted video target shortcode",
777
+ ),
778
+ mediaId: instagramMediaId(
779
+ value.mediaId,
780
+ "Instagram provider-accepted video target media ID",
781
+ ),
782
+ url: "",
783
+ });
784
+ const parsed = Object.freeze({
785
+ code: target.code,
786
+ mediaId: target.mediaId,
787
+ url: parseInstagramVideoPermalink(value.url, target.code),
788
+ });
789
+ if (canonicalJson(parsed) !== identifier) {
790
+ throw new Error("Instagram provider-accepted video target is not canonical");
791
+ }
792
+ return parsed;
793
+ }
794
+
795
+ /** Decode one provider-returned shortcode into its exact decimal media PK. */
796
+ export function instagramShortcodeMediaPk(value: unknown): string {
797
+ const code = instagramShortcode(value, "Instagram video shortcode");
798
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
799
+ let decoded = 0n;
800
+ for (const character of code) {
801
+ const digit = alphabet.indexOf(character);
802
+ if (digit < 0) throw new Error("Instagram video shortcode is invalid");
803
+ decoded = (decoded * 64n) + BigInt(digit);
804
+ }
805
+ const pk = decoded.toString(10);
806
+ if (decoded < 1n || !/^[1-9][0-9]{0,31}$/u.test(pk)) {
807
+ throw new Error("Instagram video shortcode decoded outside the media-PK bound");
808
+ }
809
+ return pk;
810
+ }
811
+
812
+ /** Strict one-field acknowledgement observed for both Instagram ruploads. */
813
+ export function assertInstagramVideoUploadAcknowledgement(value: unknown): void {
814
+ if (
815
+ !isRecord(value)
816
+ || Object.keys(value).sort().join(",") !== "status"
817
+ || value.status !== "ok"
818
+ ) throw new Error("Instagram video upload acknowledgement changed shape");
819
+ }
820
+
821
+ /**
822
+ * Validate the observed asynchronous configure response without authorizing a
823
+ * second configure POST. The captured 202 body was exactly status+message.
824
+ */
825
+ export function assertInstagramVideoConfigureIndeterminate(value: unknown): void {
826
+ if (
827
+ !isRecord(value)
828
+ || Object.keys(value).sort().join(",") !== "message,status"
829
+ || value.status !== "fail"
830
+ || typeof value.message !== "string"
831
+ || value.message.length < 1
832
+ || value.message.length > 2_048
833
+ || /[\0\r]/u.test(value.message)
834
+ ) throw new Error("Instagram video configure 202 envelope changed shape");
835
+ }
836
+
837
+ /**
838
+ * Project the fixed public-bundle configure contract and bind every identifier
839
+ * established by the live accepted response. Unknown raw media metadata stays
840
+ * private and is not projected; the independent media-info GET remains the
841
+ * authoritative actor/type/caption readback.
842
+ */
843
+ export function parseInstagramVideoConfigureAccepted(
844
+ value: unknown,
845
+ expectation: InstagramVideoConfigureExpectation,
846
+ ): InstagramVideoAcceptedTarget {
847
+ const root = boundedForeignRecord(
848
+ value,
849
+ "Instagram video configure response",
850
+ 32,
851
+ );
852
+ const uploadId = instagramMediaId(
853
+ expectation.uploadId,
854
+ "Instagram video configure expected upload ID",
855
+ );
856
+ const viewerId = instagramMediaId(
857
+ expectation.viewerId,
858
+ "Instagram video configure expected viewer ID",
859
+ );
860
+ const caption = boundedCaption(
861
+ expectation.caption,
862
+ "Instagram video configure expected caption",
863
+ );
864
+ if (root.status !== "ok" || root.upload_id !== uploadId) {
865
+ throw new Error("Instagram video configure response did not accept the exact upload");
866
+ }
867
+ const media = boundedForeignRecord(
868
+ root.media,
869
+ "Instagram video configure response.media",
870
+ 256,
871
+ );
872
+ const mediaId = instagramMediaId(
873
+ media.id,
874
+ "Instagram video configure response media ID",
875
+ );
876
+ const pk = instagramMediaId(
877
+ media.pk,
878
+ "Instagram video configure response media PK",
879
+ );
880
+ if (pk.includes("_") || mediaId !== `${pk}_${viewerId}`) {
881
+ throw new Error("Instagram video configure response did not bind the current actor");
882
+ }
883
+ const code = instagramShortcode(
884
+ media.code,
885
+ "Instagram video configure response shortcode",
886
+ );
887
+ if (instagramShortcodeMediaPk(code) !== pk) {
888
+ throw new Error("Instagram video configure response shortcode changed the media PK");
889
+ }
890
+ const captionContainer = boundedForeignRecord(
891
+ media.caption,
892
+ "Instagram video configure response.media.caption",
893
+ 64,
894
+ );
895
+ if (captionContainer.text !== caption || media.media_type !== 2) {
896
+ throw new Error("Instagram video configure response did not bind the exact video");
897
+ }
898
+ return Object.freeze({
899
+ code,
900
+ mediaId,
901
+ url: instagramVideoPermalink(code),
902
+ });
903
+ }
904
+
905
+ /** Fixed public-bundle projection for the observed deletion response. */
906
+ export function assertInstagramVideoDeleteAcknowledgement(value: unknown): void {
907
+ if (
908
+ !isRecord(value)
909
+ || Object.keys(value).sort().join(",") !== "did_delete,status"
910
+ || value.status !== "ok"
911
+ || value.did_delete !== true
912
+ ) throw new Error("Instagram video deletion acknowledgement changed shape");
913
+ }