@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,226 @@
1
+ import type { GhostgetAuth } from "../auth";
2
+ import { canonicalJson } from "../canonical-json";
3
+ import type { OperationInput, WebSessionRecipe } from "../model";
4
+ import {
5
+ createWebSessionClient,
6
+ webSessionAuthSubject,
7
+ webSessionCookie,
8
+ type WebSessionClient,
9
+ type WebSessionNetworkDependencies,
10
+ } from "../web-session-client";
11
+ import type {
12
+ WebSessionDispatchEvent,
13
+ WebSessionExecution,
14
+ WebSessionOperationDeadline,
15
+ } from "../web-session-execution";
16
+ import { failedProviderRead, type ProviderReadFailureStage } from "./read-failure";
17
+ import {
18
+ TWITCH_GQL_ORIGIN,
19
+ TWITCH_GQL_PATH,
20
+ TWITCH_WEB_CLIENT_ID,
21
+ TWITCH_WEB_OPERATIONS,
22
+ parseTwitchCurrentViewerResponse,
23
+ parseTwitchProfileResponse,
24
+ projectTwitchProfileStats,
25
+ twitchCurrentViewerRequest,
26
+ twitchLogin,
27
+ twitchProfileRequest,
28
+ TwitchProfileTargetUnavailableError,
29
+ TwitchViewerAuthRepairRequiredError,
30
+ type TwitchPersistedQueryRequest,
31
+ type TwitchViewer,
32
+ } from "./twitch-web";
33
+
34
+ const MAX_TWITCH_RESPONSE_BYTES = 256 * 1024;
35
+
36
+ export type TwitchWebRuntimeDependencies =
37
+ Partial<WebSessionNetworkDependencies> & {
38
+ readonly now?: () => number;
39
+ };
40
+
41
+ function exactProfileInput(input: OperationInput): string {
42
+ const keys = Object.keys(input);
43
+ if (keys.length !== 1 || keys[0] !== "profile") {
44
+ throw new Error("Twitch profiles.read accepts only input.profile");
45
+ }
46
+ return twitchLogin(input.profile, "input.profile");
47
+ }
48
+
49
+ function twitchAuthToken(client: WebSessionClient): string {
50
+ const token = webSessionCookie(client.cookies, "auth-token");
51
+ if (!/^[A-Za-z0-9._~-]{16,512}$/u.test(token)) {
52
+ throw new Error("Twitch auth-token cookie did not match the reviewed credential shape");
53
+ }
54
+ return token;
55
+ }
56
+
57
+ function twitchHeaders(
58
+ client: WebSessionClient,
59
+ referer: string,
60
+ ): Readonly<Record<string, string>> {
61
+ if (!/^[a-z0-9]{20,64}$/u.test(TWITCH_WEB_CLIENT_ID)) {
62
+ throw new Error("Twitch web client ID is not reviewed");
63
+ }
64
+ const token = twitchAuthToken(client);
65
+ return Object.freeze({
66
+ accept: "*/*",
67
+ authorization: `OAuth ${token}`,
68
+ "client-id": TWITCH_WEB_CLIENT_ID,
69
+ "content-type": "text/plain",
70
+ referer,
71
+ });
72
+ }
73
+
74
+ async function requestTwitchBatch(
75
+ client: WebSessionClient,
76
+ request: readonly TwitchPersistedQueryRequest[],
77
+ referer: string,
78
+ maximumBytes: number,
79
+ ): Promise<unknown> {
80
+ return client.requestJson({
81
+ url: new URL(TWITCH_GQL_PATH, TWITCH_GQL_ORIGIN),
82
+ method: "POST",
83
+ headers: twitchHeaders(client, referer),
84
+ body: canonicalJson(request),
85
+ expectedStatuses: [200],
86
+ expectedContentTypes: ["application/json"],
87
+ maxBytes: Math.min(maximumBytes, MAX_TWITCH_RESPONSE_BYTES),
88
+ });
89
+ }
90
+
91
+ async function currentViewer(
92
+ client: WebSessionClient,
93
+ ): Promise<TwitchViewer> {
94
+ return parseTwitchCurrentViewerResponse(await requestTwitchBatch(
95
+ client,
96
+ twitchCurrentViewerRequest(),
97
+ "https://www.twitch.tv/",
98
+ MAX_TWITCH_RESPONSE_BYTES,
99
+ ));
100
+ }
101
+
102
+ function viewerSubject(viewer: TwitchViewer): string {
103
+ return `twitch:${viewer.id}`;
104
+ }
105
+
106
+ function requireBoundViewer(auth: GhostgetAuth, viewer: TwitchViewer): void {
107
+ const expected = webSessionAuthSubject(auth);
108
+ if (expected === null || !/^twitch:[1-9][0-9]{0,31}$/u.test(expected)) {
109
+ throw new Error(
110
+ "Twitch profile statistics require an auth locator bound to the exact viewer subject",
111
+ );
112
+ }
113
+ if (viewerSubject(viewer) !== expected) {
114
+ throw new Error(
115
+ "Twitch browser session viewer no longer matches the confirmed auth subject",
116
+ );
117
+ }
118
+ }
119
+
120
+ function observedAt(
121
+ dependencies: TwitchWebRuntimeDependencies | undefined,
122
+ ): string {
123
+ const now = dependencies?.now?.() ?? Date.now();
124
+ if (
125
+ !Number.isSafeInteger(now)
126
+ || now < 0
127
+ || now > 8_640_000_000_000_000
128
+ ) {
129
+ throw new Error("Twitch profile observation time is invalid");
130
+ }
131
+ return new Date(now).toISOString();
132
+ }
133
+
134
+ export async function probeTwitchWebSubject(
135
+ auth: GhostgetAuth,
136
+ options: {
137
+ readonly timeoutMs?: number;
138
+ readonly dependencies?: TwitchWebRuntimeDependencies;
139
+ readonly signal?: AbortSignal;
140
+ } = {},
141
+ ): Promise<string> {
142
+ const client = await createWebSessionClient(TWITCH_GQL_ORIGIN, auth, {
143
+ timeoutMs: options.timeoutMs ?? 60_000,
144
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
145
+ ...(options.dependencies === undefined
146
+ ? {}
147
+ : { dependencies: options.dependencies }),
148
+ });
149
+ return viewerSubject(await currentViewer(client));
150
+ }
151
+
152
+ export async function executeTwitchWebOperation(
153
+ recipe: WebSessionRecipe,
154
+ input: OperationInput,
155
+ auth: GhostgetAuth,
156
+ options: {
157
+ readonly signal?: AbortSignal;
158
+ readonly operationDeadline?: WebSessionOperationDeadline;
159
+ readonly beforeDispatch?: (
160
+ event: WebSessionDispatchEvent,
161
+ ) => Promise<void>;
162
+ readonly afterDispatchVerified?: (
163
+ event: WebSessionDispatchEvent,
164
+ ) => Promise<void>;
165
+ readonly dependencies?: TwitchWebRuntimeDependencies;
166
+ } = {},
167
+ ): Promise<WebSessionExecution> {
168
+ if (
169
+ recipe.site !== "twitch"
170
+ || recipe.action !== "profiles.read"
171
+ || recipe.contractVersion !== 1
172
+ || TWITCH_WEB_OPERATIONS["profiles.read"].state !== "observed"
173
+ ) {
174
+ throw new Error("Twitch authenticated profiles.read contract is not installed");
175
+ }
176
+ const profile = exactProfileInput(input);
177
+ // R1 performs no dispatch and never invokes mutation lifecycle callbacks.
178
+ void options.beforeDispatch;
179
+ void options.afterDispatchVerified;
180
+ const finalUrl = `https://www.twitch.tv/${profile}`;
181
+ let stage: ProviderReadFailureStage = "bootstrap";
182
+ try {
183
+ const client = await createWebSessionClient(TWITCH_GQL_ORIGIN, auth, {
184
+ timeoutMs: recipe.timeoutMs,
185
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
186
+ ...(options.operationDeadline === undefined
187
+ ? {}
188
+ : { operationDeadline: options.operationDeadline }),
189
+ ...(options.dependencies === undefined
190
+ ? {}
191
+ : { dependencies: options.dependencies }),
192
+ });
193
+ stage = "identity";
194
+ const viewer = await currentViewer(client);
195
+ requireBoundViewer(auth, viewer);
196
+ stage = "target";
197
+ const response = await requestTwitchBatch(
198
+ client,
199
+ twitchProfileRequest(profile),
200
+ `${finalUrl}/about`,
201
+ recipe.maxOutputBytes,
202
+ );
203
+ const output = projectTwitchProfileStats(
204
+ parseTwitchProfileResponse(response, profile, viewer),
205
+ observedAt(options.dependencies),
206
+ );
207
+ return {
208
+ status: "succeeded",
209
+ output,
210
+ finalUrl: output.target.url,
211
+ dispatchStarted: false,
212
+ dispatch: { planned: 0, started: 0, verified: 0 },
213
+ };
214
+ } catch (error) {
215
+ return failedProviderRead("Twitch profile", error, finalUrl, {
216
+ stage,
217
+ authenticated: true,
218
+ accountMismatch: (candidate) => candidate.message.includes("no longer matches")
219
+ || candidate.message.includes("did not bind the current viewer ID"),
220
+ authRepairRequired: (candidate) => candidate.message.includes("auth-token cookie")
221
+ || candidate.message.includes("auth locator bound")
222
+ || candidate instanceof TwitchViewerAuthRepairRequiredError,
223
+ targetUnavailable: (candidate) => candidate instanceof TwitchProfileTargetUnavailableError,
224
+ });
225
+ }
226
+ }
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Twitch authenticated-web profile statistics contract.
3
+ *
4
+ * This module owns the two fixed registered GraphQL operations and their
5
+ * bounded projections. It has no network access and never accepts an origin,
6
+ * path, header, raw GraphQL document, revision, or response selector from a
7
+ * caller.
8
+ */
9
+
10
+ export const TWITCH_APP_ORIGIN = "https://www.twitch.tv";
11
+ export const TWITCH_GQL_ORIGIN = "https://gql.twitch.tv";
12
+ export const TWITCH_GQL_PATH = "/gql";
13
+
14
+ export const TWITCH_WEB_OPERATION_NAMES = Object.freeze([
15
+ "profiles.read",
16
+ ] as const);
17
+
18
+ export type TwitchWebOperationName =
19
+ (typeof TWITCH_WEB_OPERATION_NAMES)[number];
20
+
21
+ export const TWITCH_WEB_OPERATIONS = Object.freeze({
22
+ "profiles.read": Object.freeze({
23
+ effect: "read" as const,
24
+ risk: "R1" as const,
25
+ state: "observed" as const,
26
+ reason:
27
+ "fixed authenticated current-viewer and target-bound About-panel registered queries project one exact follower count without acknowledgement side effects",
28
+ }),
29
+ });
30
+
31
+ // These fixed public web-client contract values come from the reviewed
32
+ // first-party operations, never from caller input or runtime discovery.
33
+ export const TWITCH_WEB_CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko";
34
+ export const TWITCH_CURRENT_VIEWER_REVISION =
35
+ "6c870de60372d53089341c8af304c35c541754b72550eb2672224b017a39512e";
36
+ export const TWITCH_ABOUT_PANEL_REVISION =
37
+ "3b9cd4edd28e8e6f7ba6152a56157bc2b1c1a8f6e81d70808ad1b85250e5288f";
38
+
39
+ type JsonRecord = Record<string, unknown>;
40
+
41
+ export type TwitchViewer = Readonly<{
42
+ id: string;
43
+ }>;
44
+
45
+ export type TwitchProfile = Readonly<{
46
+ id: string;
47
+ login: string;
48
+ followers: number;
49
+ }>;
50
+
51
+ type ExactCountMetric = Readonly<{
52
+ status: "available";
53
+ value: number;
54
+ precision: "exact";
55
+ unit: "count";
56
+ }>;
57
+
58
+ export type TwitchProfileStats = Readonly<{
59
+ schemaVersion: 1;
60
+ provider: "twitch";
61
+ target: Readonly<{
62
+ kind: "profile";
63
+ id: string;
64
+ url: string;
65
+ }>;
66
+ observedAt: string;
67
+ completeness: "complete";
68
+ metrics: Readonly<{
69
+ followers: ExactCountMetric;
70
+ }>;
71
+ metadata: Readonly<{
72
+ login: string;
73
+ }>;
74
+ }>;
75
+
76
+ export type TwitchPersistedQueryRequest = Readonly<{
77
+ operationName: "TopNav_CurrentUser" | "ChannelRoot_AboutPanel";
78
+ variables: Readonly<Record<string, string | boolean>>;
79
+ extensions: Readonly<{
80
+ persistedQuery: Readonly<{
81
+ version: 1;
82
+ sha256Hash: string;
83
+ }>;
84
+ }>;
85
+ }>;
86
+
87
+ function record(value: unknown, label: string): JsonRecord {
88
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
89
+ throw new Error(`${label} must be an object`);
90
+ }
91
+ return value as JsonRecord;
92
+ }
93
+
94
+ function exactGraphQlResult(value: unknown, label: string): JsonRecord {
95
+ if (!Array.isArray(value) || value.length !== 1) {
96
+ throw new Error(`${label} must be one exact GraphQL batch result`);
97
+ }
98
+ const result = record(value[0], `${label}[0]`);
99
+ if (Object.hasOwn(result, "errors")) {
100
+ throw new Error(`${label} returned GraphQL errors`);
101
+ }
102
+ return record(result.data, `${label}[0].data`);
103
+ }
104
+
105
+ function twitchUserId(value: unknown, label: string): string {
106
+ if (
107
+ typeof value !== "string"
108
+ || !/^[1-9][0-9]{0,31}$/u.test(value)
109
+ ) {
110
+ throw new Error(`${label} must be one exact Twitch user ID`);
111
+ }
112
+ return value;
113
+ }
114
+
115
+ export function twitchLogin(
116
+ value: unknown,
117
+ label = "Twitch login",
118
+ ): string {
119
+ if (
120
+ typeof value !== "string"
121
+ || !/^[a-z0-9_]{4,25}$/u.test(value)
122
+ ) {
123
+ throw new Error(`${label} must be one exact lowercase Twitch login`);
124
+ }
125
+ return value;
126
+ }
127
+
128
+ function exactFollowerCount(value: unknown): number {
129
+ if (!Number.isSafeInteger(value) || (value as number) < 0) {
130
+ throw new Error("Twitch follower count must be an exact nonnegative safe integer");
131
+ }
132
+ return value as number;
133
+ }
134
+
135
+ function persistedQuery(
136
+ operationName: TwitchPersistedQueryRequest["operationName"],
137
+ variables: Readonly<Record<string, string | boolean>>,
138
+ revision: string,
139
+ ): TwitchPersistedQueryRequest {
140
+ if (!/^[a-f0-9]{64}$/u.test(revision)) {
141
+ throw new Error(`Twitch ${operationName} revision is not reviewed`);
142
+ }
143
+ return Object.freeze({
144
+ operationName,
145
+ variables,
146
+ extensions: Object.freeze({
147
+ persistedQuery: Object.freeze({ version: 1 as const, sha256Hash: revision }),
148
+ }),
149
+ });
150
+ }
151
+
152
+ /** Build the fixed one-entry current-viewer GraphQL batch. */
153
+ export function twitchCurrentViewerRequest(): readonly TwitchPersistedQueryRequest[] {
154
+ return Object.freeze([
155
+ persistedQuery(
156
+ "TopNav_CurrentUser",
157
+ Object.freeze({}),
158
+ TWITCH_CURRENT_VIEWER_REVISION,
159
+ ),
160
+ ]);
161
+ }
162
+
163
+ /** Build the fixed one-entry About-panel GraphQL batch for one exact login. */
164
+ export function twitchProfileRequest(
165
+ requestedLogin: unknown,
166
+ ): readonly TwitchPersistedQueryRequest[] {
167
+ const channelLogin = twitchLogin(requestedLogin, "requested Twitch login");
168
+ return Object.freeze([
169
+ persistedQuery(
170
+ "ChannelRoot_AboutPanel",
171
+ Object.freeze({ channelLogin, skipSchedule: true }),
172
+ TWITCH_ABOUT_PANEL_REVISION,
173
+ ),
174
+ ]);
175
+ }
176
+
177
+ /** Parse exactly one authenticated current-viewer result. */
178
+ export class TwitchViewerAuthRepairRequiredError extends Error {
179
+ constructor() {
180
+ super("Twitch selected session is not signed in");
181
+ this.name = "TwitchViewerAuthRepairRequiredError";
182
+ }
183
+ }
184
+
185
+ export function parseTwitchCurrentViewerResponse(value: unknown): TwitchViewer {
186
+ const data = exactGraphQlResult(value, "Twitch current-viewer response");
187
+ if (data.currentUser === null) {
188
+ throw new TwitchViewerAuthRepairRequiredError();
189
+ }
190
+ const currentUser = record(
191
+ data.currentUser,
192
+ "Twitch current-viewer response[0].data.currentUser",
193
+ );
194
+ return Object.freeze({
195
+ id: twitchUserId(
196
+ currentUser.id,
197
+ "Twitch current-viewer response[0].data.currentUser.id",
198
+ ),
199
+ });
200
+ }
201
+
202
+ export class TwitchProfileTargetUnavailableError extends Error {
203
+ constructor() {
204
+ super("Twitch profile target is unavailable");
205
+ this.name = "TwitchProfileTargetUnavailableError";
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Parse the fixed login-parameterized target response only after binding its
211
+ * immutable account ID to the already-probed viewer.
212
+ */
213
+ export function parseTwitchProfileResponse(
214
+ value: unknown,
215
+ requestedLogin: unknown,
216
+ viewer: TwitchViewer,
217
+ ): TwitchProfile {
218
+ const login = twitchLogin(requestedLogin, "requested Twitch login");
219
+ const data = exactGraphQlResult(value, "Twitch profile response");
220
+ if (data.user === null) throw new TwitchProfileTargetUnavailableError();
221
+ const user = record(data.user, "Twitch profile response[0].data.user");
222
+ const responseId = twitchUserId(user.id, "Twitch profile response[0].data.user.id");
223
+ if (responseId !== viewer.id) {
224
+ throw new Error("Twitch profile response did not bind the current viewer ID");
225
+ }
226
+ const followers = record(
227
+ user.followers,
228
+ "Twitch profile response[0].data.user.followers",
229
+ );
230
+ return Object.freeze({
231
+ id: responseId,
232
+ login,
233
+ followers: exactFollowerCount(followers.totalCount),
234
+ });
235
+ }
236
+
237
+ function exactObservedAt(value: string): string {
238
+ if (
239
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value)
240
+ || !Number.isFinite(Date.parse(value))
241
+ ) {
242
+ throw new Error("Twitch statistics observedAt must be an exact UTC observation time");
243
+ }
244
+ return value;
245
+ }
246
+
247
+ /** Project only public target metadata and the exact target-bound count. */
248
+ export function projectTwitchProfileStats(
249
+ profile: TwitchProfile,
250
+ observedAt: string,
251
+ ): TwitchProfileStats {
252
+ const login = twitchLogin(profile.login);
253
+ const followers = exactFollowerCount(profile.followers);
254
+ return Object.freeze({
255
+ schemaVersion: 1,
256
+ provider: "twitch",
257
+ target: Object.freeze({
258
+ kind: "profile",
259
+ id: twitchUserId(profile.id, "Twitch profile ID"),
260
+ url: `${TWITCH_APP_ORIGIN}/${login}`,
261
+ }),
262
+ observedAt: exactObservedAt(observedAt),
263
+ completeness: "complete",
264
+ metrics: Object.freeze({
265
+ followers: Object.freeze({
266
+ status: "available",
267
+ value: followers,
268
+ precision: "exact",
269
+ unit: "count",
270
+ }),
271
+ }),
272
+ metadata: Object.freeze({ login }),
273
+ });
274
+ }
@@ -0,0 +1,55 @@
1
+ export type CanonicalWhatsAppAccountKind = "pn" | "lid";
2
+
3
+ const CANONICAL_PN_ACCOUNT_PATTERN = /^[1-9][0-9]{4,14}$/u;
4
+ const CANONICAL_LID_ACCOUNT_PATTERN = /^[1-9][0-9]{4,19}$/u;
5
+ const CANONICAL_PARTICIPANT_JID_PATTERN =
6
+ /^([1-9][0-9]{4,14})(?::[0-9]{1,5})?@s\.whatsapp\.net$|^([1-9][0-9]{4,19})(?::[0-9]{1,5})?@lid$/u;
7
+
8
+ export function isCanonicalWhatsAppAccountId(
9
+ kind: CanonicalWhatsAppAccountKind,
10
+ value: unknown,
11
+ ): value is string {
12
+ return typeof value === "string" && (
13
+ kind === "pn"
14
+ ? CANONICAL_PN_ACCOUNT_PATTERN.test(value)
15
+ : CANONICAL_LID_ACCOUNT_PATTERN.test(value)
16
+ );
17
+ }
18
+
19
+ export function isCanonicalWhatsAppAccountSubject(value: unknown): value is string {
20
+ if (typeof value !== "string") return false;
21
+ const match = /^whatsapp:(pn|lid):([0-9]+)$/u.exec(value);
22
+ const kind = match?.[1];
23
+ const account = match?.[2];
24
+ return (kind === "pn" || kind === "lid")
25
+ && account !== undefined
26
+ && isCanonicalWhatsAppAccountId(kind, account);
27
+ }
28
+
29
+ export function canonicalWhatsAppAccountSubjectJid(subject: unknown): string {
30
+ if (!isCanonicalWhatsAppAccountSubject(subject)) {
31
+ throw new Error("WhatsApp account subject is not canonical");
32
+ }
33
+ const match = /^whatsapp:(pn|lid):([0-9]+)$/u.exec(subject);
34
+ if (match?.[1] === undefined || match[2] === undefined) {
35
+ throw new Error("WhatsApp account subject is not canonical");
36
+ }
37
+ const [, kind, account] = match;
38
+ return kind === "pn"
39
+ ? `${account}@s.whatsapp.net`
40
+ : `${account}@lid`;
41
+ }
42
+
43
+ export function canonicalWhatsAppParticipantJid(value: unknown): string {
44
+ if (typeof value !== "string") {
45
+ throw new Error("WhatsApp participant JID is not canonical");
46
+ }
47
+ const match = CANONICAL_PARTICIPANT_JID_PATTERN.exec(value);
48
+ const account = match?.[1] ?? match?.[2];
49
+ if (account === undefined) {
50
+ throw new Error("WhatsApp participant JID is not canonical");
51
+ }
52
+ return match?.[1] === undefined
53
+ ? `${account}@lid`
54
+ : `${account}@s.whatsapp.net`;
55
+ }