@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,770 @@
1
+ import { Blob } from "node:buffer";
2
+ import { constants } from "node:fs";
3
+ import { open } from "node:fs/promises";
4
+ import { types as nodeTypes } from "node:util";
5
+
6
+ import type { GhostgetAuth } from "../auth";
7
+ import type { BrowserFileResolver } from "../browser";
8
+ import type { FileInputValue, OperationInput, WebSessionRecipe } from "../model";
9
+ import {
10
+ createWebSessionClient,
11
+ webSessionAuthSubject,
12
+ type WebSessionClient,
13
+ type WebSessionNetworkDependencies,
14
+ } from "../web-session-client";
15
+ import type {
16
+ WebSessionDispatchEvent,
17
+ WebSessionExecution,
18
+ WebSessionOperationDeadline,
19
+ } from "../web-session-execution";
20
+ import { failedProviderRead } from "./read-failure";
21
+ import {
22
+ TIKTOK_WEB_OPERATION_NAMES,
23
+ TIKTOK_WEB_OPERATIONS,
24
+ authorizeTikTokWebR1Request,
25
+ enforceTikTokWebHeaderSinkPolicy,
26
+ normalizeTikTokWebCommentsResponse,
27
+ normalizeTikTokWebFeedResponse,
28
+ parseTikTokWebProfileResponse,
29
+ parseTikTokWebViewerResponse,
30
+ tikTokVideoSha256,
31
+ type TikTokWebOperationName,
32
+ type TikTokWebProfile,
33
+ type TikTokWebViewer,
34
+ } from "./tiktok-web";
35
+ import { tiktokMp4Metadata } from "./tiktok-video-mp4";
36
+
37
+ const TIKTOK_ORIGIN = "https://www.tiktok.com";
38
+ const MAX_VIEWER_BYTES = 2 * 1024 * 1024;
39
+ const MAX_READ_BYTES = 4 * 1024 * 1024;
40
+ const MAX_TIKTOK_VIDEO_BYTES = 128 * 1024 * 1024;
41
+ const DEFAULT_FEED_LIMIT = 20;
42
+ const DEFAULT_COMMENT_LIMIT = 20;
43
+ const TYPED_ARRAY_PROTOTYPE = Object.getPrototypeOf(
44
+ Uint8Array.prototype,
45
+ ) as object;
46
+ const TYPED_ARRAY_BUFFER_GETTER = Object.getOwnPropertyDescriptor(
47
+ TYPED_ARRAY_PROTOTYPE,
48
+ "buffer",
49
+ )?.get;
50
+ const TYPED_ARRAY_BYTE_LENGTH_GETTER = Object.getOwnPropertyDescriptor(
51
+ TYPED_ARRAY_PROTOTYPE,
52
+ "byteLength",
53
+ )?.get;
54
+
55
+ export type TikTokWebRuntimeDependencies = Partial<WebSessionNetworkDependencies> & {
56
+ readonly now?: () => number;
57
+ };
58
+
59
+ export type TikTokBoundVideoPublish = Readonly<{
60
+ allowAiRemix: boolean;
61
+ allowComments: boolean;
62
+ allowContentReuse: boolean;
63
+ allowDuet: boolean;
64
+ allowStitch: boolean;
65
+ audience: "public" | "friends" | "private";
66
+ bytes: Uint8Array<ArrayBuffer>;
67
+ byteLength: number;
68
+ caption: string | null;
69
+ commercialContent: "none";
70
+ containsSyntheticMedia: boolean;
71
+ durationSeconds: number;
72
+ height: number;
73
+ mediaType: "video/mp4";
74
+ mediaSha256: string;
75
+ width: number;
76
+ }>;
77
+
78
+ export type TikTokVideoPublishDispatchSnapshot = Readonly<{
79
+ allowAiRemix: boolean;
80
+ allowComments: boolean;
81
+ allowContentReuse: boolean;
82
+ allowDuet: boolean;
83
+ allowStitch: boolean;
84
+ audience: "public" | "friends" | "private";
85
+ body: Blob;
86
+ byteLength: number;
87
+ caption: string | null;
88
+ commercialContent: "none";
89
+ containsSyntheticMedia: boolean;
90
+ durationSeconds: number;
91
+ height: number;
92
+ mediaType: "video/mp4";
93
+ mediaSha256: string;
94
+ width: number;
95
+ }>;
96
+
97
+ export type TikTokPublishedPostDeleteInput = Readonly<{
98
+ expectedCaption: string;
99
+ postId: string;
100
+ }>;
101
+
102
+ function exactInputKeys(
103
+ input: OperationInput,
104
+ required: readonly string[],
105
+ optional: readonly string[],
106
+ label: string,
107
+ ): void {
108
+ const allowed = new Set([...required, ...optional]);
109
+ if (
110
+ required.some((key) => !Object.hasOwn(input, key))
111
+ || Object.keys(input).some((key) => !allowed.has(key))
112
+ ) throw new Error(`${label} contained unsupported or missing input fields`);
113
+ }
114
+
115
+ function exactFileInput(value: unknown, label: string): FileInputValue {
116
+ if (
117
+ typeof value !== "object"
118
+ || value === null
119
+ || Array.isArray(value)
120
+ || Object.keys(value).sort().join(",") !== "kind,reference"
121
+ || (value as { readonly kind?: unknown }).kind !== "file"
122
+ || typeof (value as { readonly reference?: unknown }).reference !== "string"
123
+ || (value as { readonly reference: string }).reference.length < 1
124
+ || (value as { readonly reference: string }).reference.length > 4_096
125
+ || /[\0\r\n]/u.test((value as { readonly reference: string }).reference)
126
+ ) throw new Error(`${label} must be one exact plan-bound file`);
127
+ return value as FileInputValue;
128
+ }
129
+
130
+ function boundedInputText(
131
+ value: unknown,
132
+ label: string,
133
+ maximum: number,
134
+ allowEmpty = false,
135
+ ): string {
136
+ if (
137
+ typeof value !== "string"
138
+ || (!allowEmpty && value.length < 1)
139
+ || value.length > maximum
140
+ || /[\0\r]/u.test(value)
141
+ ) throw new Error(`${label} must be bounded text`);
142
+ return value;
143
+ }
144
+
145
+ function exactBooleanInput(input: OperationInput, name: string): boolean {
146
+ const value = input[name];
147
+ if (typeof value !== "boolean") throw new Error(`input.${name} must be boolean`);
148
+ return value;
149
+ }
150
+
151
+ function exactTikTokVideoBinding(
152
+ value: unknown,
153
+ ): Readonly<Record<string, unknown>> {
154
+ if (
155
+ typeof value !== "object"
156
+ || value === null
157
+ || Array.isArray(value)
158
+ ) throw new Error("TikTok video binding must be one exact object");
159
+ if (nodeTypes.isProxy(value)) {
160
+ throw new Error("TikTok video binding must not be a proxy");
161
+ }
162
+ const prototype: unknown = Object.getPrototypeOf(value);
163
+ if (prototype !== Object.prototype && prototype !== null) {
164
+ throw new Error("TikTok video binding must use a plain prototype");
165
+ }
166
+ const expected = [
167
+ "allowAiRemix",
168
+ "allowComments",
169
+ "allowContentReuse",
170
+ "allowDuet",
171
+ "allowStitch",
172
+ "audience",
173
+ "byteLength",
174
+ "bytes",
175
+ "caption",
176
+ "commercialContent",
177
+ "containsSyntheticMedia",
178
+ "durationSeconds",
179
+ "height",
180
+ "mediaSha256",
181
+ "mediaType",
182
+ "width",
183
+ ] as const;
184
+ const ownKeys = Reflect.ownKeys(value);
185
+ if (
186
+ ownKeys.length !== expected.length
187
+ || ownKeys.some((key) => typeof key !== "string")
188
+ || (ownKeys as string[]).sort().join(",") !== [...expected].sort().join(",")
189
+ ) throw new Error("TikTok video binding contained unsupported fields");
190
+ const snapshot = Object.create(null) as Record<string, unknown>;
191
+ for (const key of expected) {
192
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
193
+ if (
194
+ descriptor === undefined
195
+ || !descriptor.enumerable
196
+ || !("value" in descriptor)
197
+ ) throw new Error("TikTok video binding must contain only enumerable data properties");
198
+ snapshot[key] = descriptor.value;
199
+ }
200
+ return Object.freeze(snapshot);
201
+ }
202
+
203
+ function exactTikTokCreatorBoolean(
204
+ binding: Readonly<Record<string, unknown>>,
205
+ name: string,
206
+ ): boolean {
207
+ const value = binding[name];
208
+ if (typeof value !== "boolean") {
209
+ throw new Error("TikTok video binding creator declarations are invalid");
210
+ }
211
+ return value;
212
+ }
213
+
214
+ function snapshotTikTokVideoBytes(value: unknown): Uint8Array<ArrayBuffer> {
215
+ if (
216
+ typeof value !== "object"
217
+ || value === null
218
+ || nodeTypes.isProxy(value)
219
+ || !(value instanceof Uint8Array)
220
+ || Object.getPrototypeOf(value) !== Uint8Array.prototype
221
+ || TYPED_ARRAY_BUFFER_GETTER === undefined
222
+ || TYPED_ARRAY_BYTE_LENGTH_GETTER === undefined
223
+ ) throw new Error("TikTok video binding must contain one bounded MP4");
224
+ let buffer: unknown;
225
+ let byteLength: unknown;
226
+ try {
227
+ buffer = TYPED_ARRAY_BUFFER_GETTER.call(value) as unknown;
228
+ byteLength = TYPED_ARRAY_BYTE_LENGTH_GETTER.call(value) as unknown;
229
+ } catch {
230
+ throw new Error("TikTok video binding must contain one bounded MP4");
231
+ }
232
+ if (
233
+ typeof byteLength !== "number"
234
+ || !Number.isSafeInteger(byteLength)
235
+ || byteLength < 24
236
+ || byteLength > MAX_TIKTOK_VIDEO_BYTES
237
+ || nodeTypes.isSharedArrayBuffer(buffer)
238
+ ) throw new Error("TikTok video binding must contain one bounded MP4");
239
+
240
+ // Allocate the bounded destination first, then copy through the intrinsic
241
+ // typed-array operation. Caller-defined buffer, byteLength, iterator, or
242
+ // indexed property behavior cannot supply the validated dispatch bytes.
243
+ let bytes: Uint8Array<ArrayBuffer>;
244
+ try {
245
+ bytes = new Uint8Array(byteLength);
246
+ Uint8Array.prototype.set.call(bytes, value);
247
+ } catch {
248
+ throw new Error("TikTok video binding must contain one bounded MP4");
249
+ }
250
+ return bytes;
251
+ }
252
+
253
+ /**
254
+ * Materialize one exact plan-bound MP4 and all supported creator choices.
255
+ * This is local-only preflight; capture-required execution rejects before it.
256
+ */
257
+ export async function materializeTikTokVideoPublishInput(
258
+ input: OperationInput,
259
+ fileResolver: BrowserFileResolver | undefined,
260
+ operationDeadline?: WebSessionOperationDeadline,
261
+ ): Promise<TikTokBoundVideoPublish> {
262
+ const required = [
263
+ "allow_ai_remix",
264
+ "allow_comments",
265
+ "allow_content_reuse",
266
+ "allow_duet",
267
+ "allow_stitch",
268
+ "audience",
269
+ "commercial_content",
270
+ "contains_synthetic_media",
271
+ "media",
272
+ ] as const;
273
+ exactInputKeys(input, required, ["caption"], "TikTok video publishing");
274
+ const media = exactFileInput(input.media, "input.media");
275
+ const caption = input.caption === undefined
276
+ ? null
277
+ : boundedInputText(input.caption, "input.caption", 500);
278
+ const audience = input.audience;
279
+ if (audience !== "public" && audience !== "friends" && audience !== "private") {
280
+ throw new Error("input.audience must be public, friends, or private");
281
+ }
282
+ if (input.commercial_content !== "none") {
283
+ throw new Error("input.commercial_content must explicitly be none");
284
+ }
285
+ const allowAiRemix = exactBooleanInput(input, "allow_ai_remix");
286
+ const allowComments = exactBooleanInput(input, "allow_comments");
287
+ const allowContentReuse = exactBooleanInput(input, "allow_content_reuse");
288
+ const allowDuet = exactBooleanInput(input, "allow_duet");
289
+ const allowStitch = exactBooleanInput(input, "allow_stitch");
290
+ const containsSyntheticMedia = exactBooleanInput(input, "contains_synthetic_media");
291
+ if (fileResolver === undefined) {
292
+ throw new Error("TikTok video upload requires the plan-bound file resolver");
293
+ }
294
+ const resolve = () => fileResolver([media]);
295
+ const paths = operationDeadline === undefined
296
+ ? await resolve()
297
+ : await operationDeadline.run(resolve, "authenticated web operation deadline");
298
+ operationDeadline?.throwIfUnavailable("authenticated web operation deadline");
299
+ if (paths.length !== 1 || typeof paths[0] !== "string") {
300
+ throw new Error("TikTok file resolver did not return one exact video path");
301
+ }
302
+ const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
303
+ const handle = operationDeadline === undefined
304
+ ? await open(paths[0], constants.O_RDONLY | noFollow)
305
+ : await operationDeadline.run(
306
+ () => open(paths[0]!, constants.O_RDONLY | noFollow),
307
+ "authenticated web operation deadline",
308
+ );
309
+ try {
310
+ const before = operationDeadline === undefined
311
+ ? await handle.stat()
312
+ : await operationDeadline.run(
313
+ () => handle.stat(),
314
+ "authenticated web operation deadline",
315
+ );
316
+ if (!before.isFile() || before.size < 24 || before.size > MAX_TIKTOK_VIDEO_BYTES) {
317
+ throw new Error(
318
+ "TikTok video must be a regular MP4 no larger than the 128 MiB in-memory publish limit",
319
+ );
320
+ }
321
+ const fileBytes = operationDeadline === undefined
322
+ ? await handle.readFile()
323
+ : await operationDeadline.run(
324
+ () => handle.readFile(),
325
+ "authenticated web operation deadline",
326
+ );
327
+ const after = operationDeadline === undefined
328
+ ? await handle.stat()
329
+ : await operationDeadline.run(
330
+ () => handle.stat(),
331
+ "authenticated web operation deadline",
332
+ );
333
+ if (
334
+ before.dev !== after.dev
335
+ || before.ino !== after.ino
336
+ || before.size !== after.size
337
+ || before.mtimeMs !== after.mtimeMs
338
+ || before.ctimeMs !== after.ctimeMs
339
+ || fileBytes.byteLength !== before.size
340
+ ) throw new Error("TikTok video changed while it was materialized");
341
+ const bytes = new Uint8Array(fileBytes);
342
+ const metadata = tiktokMp4Metadata(bytes, "TikTok video");
343
+ const mediaSha256 = tikTokVideoSha256(bytes);
344
+ return Object.freeze({
345
+ allowAiRemix,
346
+ allowComments,
347
+ allowContentReuse,
348
+ allowDuet,
349
+ allowStitch,
350
+ audience,
351
+ bytes,
352
+ byteLength: bytes.byteLength,
353
+ caption,
354
+ commercialContent: "none",
355
+ containsSyntheticMedia,
356
+ durationSeconds: metadata.durationSeconds,
357
+ height: metadata.height,
358
+ mediaType: "video/mp4",
359
+ mediaSha256,
360
+ width: metadata.width,
361
+ });
362
+ } finally {
363
+ await handle.close();
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Rebind and snapshot a materialized MP4 immediately before a future
369
+ * dispatch. The capture-required operation does not call this helper and this
370
+ * checkpoint deliberately contains no route, signer, credential, or target.
371
+ */
372
+ export function revalidateTikTokVideoPublishBindingForDispatch(
373
+ value: unknown,
374
+ ): TikTokVideoPublishDispatchSnapshot {
375
+ const binding = exactTikTokVideoBinding(value);
376
+ const bytes = snapshotTikTokVideoBytes(binding.bytes);
377
+ const metadata = tiktokMp4Metadata(bytes, "TikTok video binding");
378
+ const mediaSha256 = tikTokVideoSha256(bytes);
379
+ if (
380
+ !Number.isSafeInteger(binding.byteLength)
381
+ || binding.byteLength !== bytes.byteLength
382
+ || typeof binding.mediaSha256 !== "string"
383
+ || !/^[a-f0-9]{64}$/u.test(binding.mediaSha256)
384
+ || binding.mediaSha256 !== mediaSha256
385
+ || binding.durationSeconds !== metadata.durationSeconds
386
+ || binding.height !== metadata.height
387
+ || binding.width !== metadata.width
388
+ ) throw new Error("TikTok video binding changed from its exact bytes");
389
+
390
+ const audience = binding.audience;
391
+ if (
392
+ (audience !== "public" && audience !== "friends" && audience !== "private")
393
+ || binding.commercialContent !== "none"
394
+ || binding.mediaType !== "video/mp4"
395
+ ) throw new Error("TikTok video binding creator declarations are invalid");
396
+ const allowAiRemix = exactTikTokCreatorBoolean(binding, "allowAiRemix");
397
+ const allowComments = exactTikTokCreatorBoolean(binding, "allowComments");
398
+ const allowContentReuse = exactTikTokCreatorBoolean(binding, "allowContentReuse");
399
+ const allowDuet = exactTikTokCreatorBoolean(binding, "allowDuet");
400
+ const allowStitch = exactTikTokCreatorBoolean(binding, "allowStitch");
401
+ const containsSyntheticMedia = exactTikTokCreatorBoolean(
402
+ binding,
403
+ "containsSyntheticMedia",
404
+ );
405
+ const caption = binding.caption === null
406
+ ? null
407
+ : boundedInputText(binding.caption, "TikTok video binding caption", 500);
408
+ const body = new Blob([bytes], { type: "video/mp4" });
409
+ if (body.size !== bytes.byteLength || body.type !== "video/mp4") {
410
+ throw new Error("TikTok video dispatch snapshot changed shape");
411
+ }
412
+ return Object.freeze({
413
+ allowAiRemix,
414
+ allowComments,
415
+ allowContentReuse,
416
+ allowDuet,
417
+ allowStitch,
418
+ audience,
419
+ body,
420
+ byteLength: bytes.byteLength,
421
+ caption,
422
+ commercialContent: "none" as const,
423
+ containsSyntheticMedia,
424
+ durationSeconds: metadata.durationSeconds,
425
+ height: metadata.height,
426
+ mediaSha256,
427
+ mediaType: "video/mp4" as const,
428
+ width: metadata.width,
429
+ });
430
+ }
431
+
432
+ export function prepareTikTokPublishedPostDeleteInput(
433
+ input: OperationInput,
434
+ ): TikTokPublishedPostDeleteInput {
435
+ exactInputKeys(
436
+ input,
437
+ ["post_id", "expected_caption"],
438
+ [],
439
+ "TikTok authored-post deletion",
440
+ );
441
+ const postId = input.post_id;
442
+ if (typeof postId !== "string" || !/^[0-9]{1,32}$/u.test(postId)) {
443
+ throw new Error("input.post_id must be an exact decimal TikTok post ID");
444
+ }
445
+ return Object.freeze({
446
+ expectedCaption: boundedInputText(
447
+ input.expected_caption,
448
+ "input.expected_caption",
449
+ 500,
450
+ ),
451
+ postId,
452
+ });
453
+ }
454
+
455
+ function isTikTokOperation(value: string): value is TikTokWebOperationName {
456
+ return (TIKTOK_WEB_OPERATION_NAMES as readonly string[]).includes(value);
457
+ }
458
+
459
+ function integerInput(
460
+ input: OperationInput,
461
+ name: string,
462
+ fallback: number,
463
+ minimum: number,
464
+ maximum: number,
465
+ ): number {
466
+ const value = input[name] ?? fallback;
467
+ if (!Number.isSafeInteger(value) || (value as number) < minimum || (value as number) > maximum) {
468
+ throw new Error(`input.${name} must be an integer between ${minimum} and ${maximum}`);
469
+ }
470
+ return value as number;
471
+ }
472
+
473
+ function stringInput(input: OperationInput, name: string, pattern: RegExp, label: string): string {
474
+ const value = input[name];
475
+ if (typeof value !== "string" || !pattern.test(value)) throw new Error(`input.${name} must be ${label}`);
476
+ return value;
477
+ }
478
+
479
+ function exactReadHeaders(referer: "https://www.tiktok.com/" | "https://www.tiktok.com/foryou"): Readonly<Record<string, string>> {
480
+ return enforceTikTokWebHeaderSinkPolicy({
481
+ source: "code",
482
+ sink: "network-request",
483
+ headers: {
484
+ accept: "application/json, text/plain, */*",
485
+ referer,
486
+ },
487
+ });
488
+ }
489
+
490
+ async function currentViewer(
491
+ client: WebSessionClient,
492
+ maximumBytes = MAX_VIEWER_BYTES,
493
+ ): Promise<TikTokWebViewer> {
494
+ const url = new URL("/api/user/detail/self/", TIKTOK_ORIGIN);
495
+ authorizeTikTokWebR1Request({
496
+ operation: "viewer.current",
497
+ url,
498
+ method: "GET",
499
+ });
500
+ const response = await client.requestJson({
501
+ url,
502
+ method: "GET",
503
+ headers: exactReadHeaders("https://www.tiktok.com/"),
504
+ maxBytes: Math.min(maximumBytes, MAX_VIEWER_BYTES),
505
+ });
506
+ return parseTikTokWebViewerResponse(response);
507
+ }
508
+
509
+ async function currentProfile(
510
+ client: WebSessionClient,
511
+ maximumBytes = MAX_VIEWER_BYTES,
512
+ ): Promise<TikTokWebProfile> {
513
+ const url = new URL("/api/user/detail/self/", TIKTOK_ORIGIN);
514
+ authorizeTikTokWebR1Request({
515
+ operation: "profiles.current",
516
+ url,
517
+ method: "GET",
518
+ });
519
+ const response = await client.requestJson({
520
+ url,
521
+ method: "GET",
522
+ headers: exactReadHeaders("https://www.tiktok.com/"),
523
+ maxBytes: Math.min(maximumBytes, MAX_VIEWER_BYTES),
524
+ });
525
+ return parseTikTokWebProfileResponse(response);
526
+ }
527
+
528
+ function viewerSubject(viewer: TikTokWebViewer): string {
529
+ return `tiktok:uid:${viewer.id}/sec:${viewer.secUid}`;
530
+ }
531
+
532
+ export async function probeTikTokWebSubject(
533
+ auth: GhostgetAuth,
534
+ options: {
535
+ readonly timeoutMs?: number;
536
+ readonly dependencies?: TikTokWebRuntimeDependencies;
537
+ readonly signal?: AbortSignal;
538
+ } = {},
539
+ ): Promise<string> {
540
+ const client = await createWebSessionClient(TIKTOK_ORIGIN, auth, {
541
+ timeoutMs: options.timeoutMs ?? 60_000,
542
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
543
+ ...(options.dependencies === undefined ? {} : { dependencies: options.dependencies }),
544
+ });
545
+ const viewer = await currentViewer(client);
546
+ return viewerSubject(viewer);
547
+ }
548
+
549
+ async function requireBoundViewer(
550
+ client: WebSessionClient,
551
+ auth: GhostgetAuth,
552
+ ): Promise<TikTokWebViewer> {
553
+ const viewer = await currentViewer(client);
554
+ assertBoundViewer(auth, viewer);
555
+ return viewer;
556
+ }
557
+
558
+ function assertBoundViewer(auth: GhostgetAuth, viewer: TikTokWebViewer): void {
559
+ const expected = webSessionAuthSubject(auth);
560
+ if (expected === null || !/^tiktok:uid:[0-9]{1,32}\/sec:[A-Za-z0-9._-]{16,256}$/u.test(expected)) {
561
+ throw new Error("TikTok personalized operations require an auth locator bound to the exact viewer subject");
562
+ }
563
+ if (viewerSubject(viewer) !== expected) {
564
+ throw new Error("TikTok browser session viewer no longer matches the confirmed auth subject");
565
+ }
566
+ }
567
+
568
+ function profileInput(input: OperationInput): string {
569
+ const profile = input.profile;
570
+ if (typeof profile !== "string" || !/^[A-Za-z0-9._]{2,24}$/u.test(profile)) {
571
+ throw new Error("input.profile must be an exact TikTok handle without @");
572
+ }
573
+ return profile;
574
+ }
575
+
576
+ function observedAt(dependencies: TikTokWebRuntimeDependencies | undefined): string {
577
+ const now = dependencies?.now?.() ?? Date.now();
578
+ if (!Number.isSafeInteger(now) || now < 0 || now > 8_640_000_000_000_000) {
579
+ throw new Error("TikTok profile observation time is invalid");
580
+ }
581
+ return new Date(now).toISOString();
582
+ }
583
+
584
+ function exactCount(value: number): Readonly<Record<string, unknown>> {
585
+ return Object.freeze({
586
+ status: "available",
587
+ value,
588
+ precision: "exact",
589
+ unit: "count",
590
+ });
591
+ }
592
+
593
+ async function readProfile(
594
+ client: WebSessionClient,
595
+ input: OperationInput,
596
+ auth: GhostgetAuth,
597
+ dependencies: TikTokWebRuntimeDependencies | undefined,
598
+ ): Promise<Readonly<Record<string, unknown>>> {
599
+ const requestedProfile = profileInput(input);
600
+ const profile = await currentProfile(client);
601
+ assertBoundViewer(auth, profile);
602
+ if (profile.handle.toLocaleLowerCase("en-US") !== requestedProfile.toLocaleLowerCase("en-US")) {
603
+ throw new Error("TikTok requested profile did not match the bound current account");
604
+ }
605
+ return Object.freeze({
606
+ schemaVersion: 1,
607
+ provider: "tiktok",
608
+ target: Object.freeze({
609
+ kind: "profile",
610
+ id: profile.handle,
611
+ url: `${TIKTOK_ORIGIN}/@${encodeURIComponent(profile.handle)}`,
612
+ }),
613
+ observedAt: observedAt(dependencies),
614
+ completeness: "complete",
615
+ metrics: Object.freeze({
616
+ followers: exactCount(profile.followers),
617
+ following: exactCount(profile.following),
618
+ likes: exactCount(profile.likes),
619
+ }),
620
+ metadata: Object.freeze({
621
+ handle: profile.handle,
622
+ displayName: profile.displayName,
623
+ ...(profile.bio === null ? {} : { bio: profile.bio }),
624
+ ...(profile.websiteUrl === null ? {} : { websiteUrl: profile.websiteUrl }),
625
+ }),
626
+ });
627
+ }
628
+
629
+ async function readForYou(
630
+ client: WebSessionClient,
631
+ input: OperationInput,
632
+ maximumBytes: number,
633
+ ): Promise<unknown> {
634
+ if (input.feed !== "for-you") {
635
+ throw new Error("input.feed must be the observed signer-free for-you feed");
636
+ }
637
+ const limit = integerInput(input, "limit", DEFAULT_FEED_LIMIT, 1, 30);
638
+ const url = new URL("/api/recommend/item_list/", TIKTOK_ORIGIN);
639
+ url.searchParams.set("aid", "1988");
640
+ url.searchParams.set("count", String(limit));
641
+ authorizeTikTokWebR1Request({
642
+ operation: "feeds.for-you",
643
+ url,
644
+ method: "GET",
645
+ });
646
+ const response = await client.requestJson({
647
+ url,
648
+ method: "GET",
649
+ headers: exactReadHeaders("https://www.tiktok.com/foryou"),
650
+ maxBytes: Math.min(maximumBytes, MAX_READ_BYTES),
651
+ });
652
+ return normalizeTikTokWebFeedResponse(response, limit);
653
+ }
654
+
655
+ async function readComments(
656
+ client: WebSessionClient,
657
+ input: OperationInput,
658
+ maximumBytes: number,
659
+ ): Promise<unknown> {
660
+ const postId = stringInput(input, "post_id", /^[0-9]{1,32}$/u, "a decimal TikTok post ID");
661
+ const cursor = integerInput(input, "cursor", 0, 0, Number.MAX_SAFE_INTEGER);
662
+ const limit = integerInput(input, "limit", DEFAULT_COMMENT_LIMIT, 1, 50);
663
+ const url = new URL("/api/comment/list/", TIKTOK_ORIGIN);
664
+ url.searchParams.set("aid", "1988");
665
+ url.searchParams.set("aweme_id", postId);
666
+ url.searchParams.set("count", String(limit));
667
+ url.searchParams.set("cursor", String(cursor));
668
+ authorizeTikTokWebR1Request({
669
+ operation: "comments.list",
670
+ url,
671
+ method: "GET",
672
+ });
673
+ const response = await client.requestJson({
674
+ url,
675
+ method: "GET",
676
+ headers: exactReadHeaders("https://www.tiktok.com/foryou"),
677
+ maxBytes: Math.min(maximumBytes, MAX_READ_BYTES),
678
+ });
679
+ return normalizeTikTokWebCommentsResponse(response, postId, limit);
680
+ }
681
+
682
+ export async function executeTikTokWebOperation(
683
+ recipe: WebSessionRecipe,
684
+ input: OperationInput,
685
+ auth: GhostgetAuth,
686
+ options: {
687
+ readonly signal?: AbortSignal;
688
+ readonly operationDeadline?: WebSessionOperationDeadline;
689
+ readonly beforeDispatch?: (event: WebSessionDispatchEvent) => Promise<void>;
690
+ readonly afterDispatchVerified?: (event: WebSessionDispatchEvent) => Promise<void>;
691
+ readonly dependencies?: TikTokWebRuntimeDependencies;
692
+ } = {},
693
+ ): Promise<WebSessionExecution> {
694
+ if (recipe.site !== "tiktok" || !isTikTokOperation(recipe.action)) {
695
+ throw new Error("TikTok authenticated web recipe is not installed");
696
+ }
697
+ const contract = TIKTOK_WEB_OPERATIONS[recipe.action];
698
+ const contractVersion = "contractVersion" in contract ? contract.contractVersion : 1;
699
+ if (recipe.contractVersion !== contractVersion) {
700
+ throw new Error("TikTok authenticated web recipe is not installed");
701
+ }
702
+ if (contract.state !== "observed") {
703
+ throw new Error(`TikTok authenticated web operation ${recipe.action} is capture-required: ${contract.reason}`);
704
+ }
705
+ if (
706
+ recipe.action !== "profiles.read"
707
+ && recipe.action !== "feeds.read"
708
+ && recipe.action !== "comments.read"
709
+ ) {
710
+ throw new Error(`TikTok authenticated web operation ${recipe.action} has no executable reviewed contract`);
711
+ }
712
+ // R1 operations never enter a dispatch ledger or invoke mutation callbacks.
713
+ void options.beforeDispatch;
714
+ void options.afterDispatchVerified;
715
+ let client: WebSessionClient;
716
+ try {
717
+ client = await createWebSessionClient(TIKTOK_ORIGIN, auth, {
718
+ timeoutMs: recipe.timeoutMs,
719
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
720
+ ...(options.operationDeadline === undefined
721
+ ? {}
722
+ : { operationDeadline: options.operationDeadline }),
723
+ ...(options.dependencies === undefined ? {} : { dependencies: options.dependencies }),
724
+ });
725
+ } catch (error) {
726
+ if (recipe.action !== "profiles.read") throw error;
727
+ return failedProviderRead(
728
+ "TikTok profile",
729
+ error,
730
+ `${TIKTOK_ORIGIN}/@${encodeURIComponent(profileInput(input))}`,
731
+ { stage: "bootstrap", authenticated: true },
732
+ );
733
+ }
734
+ if (recipe.action === "profiles.read") {
735
+ const finalUrl = `${TIKTOK_ORIGIN}/@${encodeURIComponent(profileInput(input))}`;
736
+ try {
737
+ const output = await readProfile(client, input, auth, options.dependencies);
738
+ return {
739
+ status: "succeeded",
740
+ output,
741
+ finalUrl,
742
+ dispatchStarted: false,
743
+ dispatch: { planned: 0, started: 0, verified: 0 },
744
+ };
745
+ } catch (error) {
746
+ return failedProviderRead("TikTok profile", error, finalUrl, {
747
+ stage: "identity",
748
+ authenticated: true,
749
+ accountMismatch: (candidate) => candidate.message.includes("no longer matches")
750
+ || candidate.message.includes("did not match the bound current account"),
751
+ authRepairRequired: (candidate) => candidate.message.includes("auth locator bound"),
752
+ });
753
+ }
754
+ }
755
+ const output = await (async () => {
756
+ await requireBoundViewer(client, auth);
757
+ return recipe.action === "feeds.read"
758
+ ? readForYou(client, input, recipe.maxOutputBytes)
759
+ : readComments(client, input, recipe.maxOutputBytes);
760
+ })();
761
+ return {
762
+ status: "succeeded",
763
+ output,
764
+ finalUrl: recipe.action === "feeds.read"
765
+ ? `${TIKTOK_ORIGIN}/foryou`
766
+ : TIKTOK_ORIGIN,
767
+ dispatchStarted: false,
768
+ dispatch: { planned: 0, started: 0, verified: 0 },
769
+ };
770
+ }