@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.
- package/CHANGELOG.md +458 -0
- package/LICENSE +21 -0
- package/README.md +1359 -0
- package/bunfig.toml +2 -0
- package/dist/apple-photos-client.js +704 -0
- package/dist/beeper-client.js +4602 -0
- package/dist/client.js +1605 -0
- package/dist/index-26yq8q16.js +31 -0
- package/dist/index-9t13b4k7.js +508 -0
- package/dist/index-axwm9b3v.js +5 -0
- package/dist/index-gwk7rbyj.js +30 -0
- package/dist/index.js +701 -0
- package/dist/messaging.js +1130 -0
- package/dist/omni-client.js +1406 -0
- package/dist/whatsapp-client.js +355 -0
- package/docs/imessage-direct-provider.md +106 -0
- package/docs/rental-listings.md +94 -0
- package/package.json +600 -0
- package/skills/ghostget/SKILL.md +251 -0
- package/skills/ghostget/agents/openai.yaml +4 -0
- package/skills/ghostget/references/adapter-contract.md +181 -0
- package/skills/ghostget/references/apple-photos.md +115 -0
- package/skills/ghostget/references/article-drafts.md +235 -0
- package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
- package/skills/ghostget/references/cross-posting-video.md +139 -0
- package/skills/ghostget/references/cross-posting.md +82 -0
- package/skills/ghostget/references/derivation.md +390 -0
- package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
- package/skills/ghostget/references/install.md +66 -0
- package/skills/ghostget/references/linkedin-adapter.md +442 -0
- package/skills/ghostget/references/messaging.md +168 -0
- package/skills/ghostget/references/meta-comet-contract.md +153 -0
- package/skills/ghostget/references/platform-patterns.md +122 -0
- package/skills/ghostget/references/provider-plugins.md +496 -0
- package/skills/ghostget/references/reddit-flair.md +78 -0
- package/skills/ghostget/references/safety-and-state.md +214 -0
- package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
- package/skills/ghostget/references/social-platform-routing.md +66 -0
- package/skills/ghostget/references/social-profile-stats.md +195 -0
- package/skills/ghostget/references/social-video-platform-routing.md +89 -0
- package/skills/ghostget/references/whatsapp-adapter.md +101 -0
- package/skills/ghostget/references/x-adapter.md +332 -0
- package/skills/ghostget/references/x-ai-disclosure.md +71 -0
- package/src/apple-photos-cli.ts +639 -0
- package/src/apple-photos-client-types.ts +168 -0
- package/src/apple-photos-client.ts +201 -0
- package/src/apple-photos-contact-evidence.ts +838 -0
- package/src/apple-photos-local-source.ts +1131 -0
- package/src/args.ts +2053 -0
- package/src/article-draft-document.ts +417 -0
- package/src/article-draft-embeds.ts +123 -0
- package/src/article-draft-images.ts +214 -0
- package/src/assets/adapter-template/wrench-adapter.json +40 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
- package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
- package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
- package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
- package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
- package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
- package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
- package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
- package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
- package/src/assets/adapters/x/wrench-adapter.json +749 -0
- package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
- package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
- package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
- package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
- package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
- package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
- package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
- package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
- package/src/assets.d.ts +4 -0
- package/src/auth.ts +1157 -0
- package/src/beeper-client-types.ts +169 -0
- package/src/beeper-client.ts +250 -0
- package/src/beeper-contact-interactions-cli.ts +180 -0
- package/src/beeper-contact-interactions.ts +1612 -0
- package/src/beeper-message-bundle-v1.ts +257 -0
- package/src/beeper-message-like-me-cli.ts +84 -0
- package/src/beeper-message-like-me-export.ts +2274 -0
- package/src/beeper-message-like-me-recovery.ts +931 -0
- package/src/beeper-message-like-me-source.ts +4034 -0
- package/src/browser-admission.ts +928 -0
- package/src/browser-snapshots.ts +260 -0
- package/src/browser.ts +3873 -0
- package/src/canonical-json.ts +60 -0
- package/src/catalog-cli.ts +638 -0
- package/src/cli.ts +208 -0
- package/src/client-types.ts +301 -0
- package/src/client.ts +2726 -0
- package/src/confirmed-write-failure.ts +44 -0
- package/src/confirmed-write-model.ts +72 -0
- package/src/confirmed-write-platform.ts +1055 -0
- package/src/confirmed-write-program.ts +224 -0
- package/src/confirmed-write-runtime.ts +16 -0
- package/src/cursor-token.ts +228 -0
- package/src/derivation-dnr-readiness.ts +367 -0
- package/src/derivation-file-chooser.ts +384 -0
- package/src/derivation-network-boundary.ts +835 -0
- package/src/derivation-network-guard.ts +782 -0
- package/src/derivation-network-proxy-helper.ts +440 -0
- package/src/derivation-network-proxy.ts +385 -0
- package/src/derive-command-helper.ts +693 -0
- package/src/derive-fixtures.ts +343 -0
- package/src/derive-review.ts +980 -0
- package/src/derive.ts +4754 -0
- package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
- package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
- package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
- package/src/ghostget.ts +3672 -0
- package/src/gmail-capture.ts +1244 -0
- package/src/har-internal.ts +1242 -0
- package/src/har.ts +743 -0
- package/src/index.ts +218 -0
- package/src/invocation-read-platform.ts +55 -0
- package/src/invocation-read-program.ts +93 -0
- package/src/invocation-read-runtime.ts +11 -0
- package/src/linked-device-lifecycle-admission.ts +450 -0
- package/src/linked-device-lifecycle-journal.ts +1560 -0
- package/src/linked-device-lifecycle-runtime.ts +1602 -0
- package/src/local-cli-admission.ts +135 -0
- package/src/local-cli-contracts.ts +271 -0
- package/src/local-cli-execution.ts +98 -0
- package/src/local-cli-surface-contract.ts +1510 -0
- package/src/local-cli-tool-identity.ts +336 -0
- package/src/media/archive.ts +2325 -0
- package/src/media/args.ts +303 -0
- package/src/media/cli.ts +287 -0
- package/src/media/doctor.ts +475 -0
- package/src/media/ffmpeg.ts +1330 -0
- package/src/media/http-capture.ts +711 -0
- package/src/media/http-probe.ts +593 -0
- package/src/media/http.ts +387 -0
- package/src/media/index.ts +10 -0
- package/src/media/local-transcription.ts +613 -0
- package/src/media/lock.ts +321 -0
- package/src/media/manifest.ts +1712 -0
- package/src/media/metadata.ts +906 -0
- package/src/media/process.ts +723 -0
- package/src/media/revision.ts +166 -0
- package/src/media/runtime-closure.ts +1063 -0
- package/src/media/source-router.ts +187 -0
- package/src/media/transcriber-config.ts +1122 -0
- package/src/media/transcript-persistence-model.ts +15 -0
- package/src/media/transcript-persistence-platform.ts +89 -0
- package/src/media/transcript-persistence-program.ts +39 -0
- package/src/media/transcript-persistence-runtime.ts +24 -0
- package/src/media/transcript.ts +616 -0
- package/src/media/utf8-order.ts +30 -0
- package/src/media/whisper-cpp.ts +464 -0
- package/src/media/whisper-language.ts +58 -0
- package/src/media/yt-dlp.ts +439 -0
- package/src/message-like-me-agentic-messaging.ts +882 -0
- package/src/messaging-action-store.ts +793 -0
- package/src/messaging-runtime.ts +2136 -0
- package/src/messaging-store.ts +539 -0
- package/src/messaging-types.ts +1663 -0
- package/src/messaging.ts +334 -0
- package/src/model.ts +2671 -0
- package/src/oauth-google.ts +888 -0
- package/src/omni-client-types.ts +306 -0
- package/src/omni-client.ts +2225 -0
- package/src/omni-limits.ts +34 -0
- package/src/omni-model.ts +1614 -0
- package/src/omni-request.ts +379 -0
- package/src/omni-runtime.ts +1632 -0
- package/src/operation-deadline.ts +189 -0
- package/src/path-helper.ts +1656 -0
- package/src/pinned-https.ts +194 -0
- package/src/plan-assets.ts +832 -0
- package/src/platform-catalog.ts +1832 -0
- package/src/plugins/beeper-linked-device/plugin.ts +310 -0
- package/src/plugins/bluesky-web/plugin.ts +178 -0
- package/src/plugins/clasificados-web/plugin.ts +66 -0
- package/src/plugins/github-web/plugin.ts +80 -0
- package/src/plugins/gmail-official/plugin.ts +110 -0
- package/src/plugins/hacker-news-web/plugin.ts +52 -0
- package/src/plugins/imessage-direct/plugin.ts +195 -0
- package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
- package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
- package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
- package/src/plugins/linkedin-official/plugin.ts +84 -0
- package/src/plugins/linkedin-web/plugin.ts +412 -0
- package/src/plugins/meta-web/plugin.ts +317 -0
- package/src/plugins/reddit-web/flair.ts +492 -0
- package/src/plugins/reddit-web/plugin.ts +477 -0
- package/src/plugins/substack-web/plugin.ts +124 -0
- package/src/plugins/tiktok-web/plugin.ts +67 -0
- package/src/plugins/twitch-web/plugin.ts +54 -0
- package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
- package/src/plugins/x-official/plugin.ts +64 -0
- package/src/plugins/x-web/plugin.ts +488 -0
- package/src/plugins/youtube-web/plugin.ts +101 -0
- package/src/portable-run-recovery.ts +466 -0
- package/src/process-identity.ts +501 -0
- package/src/profile-clone-helper.ts +104 -0
- package/src/provider-catalog-views.ts +51 -0
- package/src/provider-context.ts +69 -0
- package/src/provider-contract-definitions-x.ts +45 -0
- package/src/provider-contract-definitions.ts +948 -0
- package/src/provider-contract-input-gmail.ts +176 -0
- package/src/provider-contract-input-linkedin.ts +77 -0
- package/src/provider-contract-input-x.ts +304 -0
- package/src/provider-contract-planning.ts +23 -0
- package/src/provider-contract-semantic-identity.ts +54 -0
- package/src/provider-contracts.ts +251 -0
- package/src/provider-http.ts +723 -0
- package/src/provider-plugin-auth.ts +36 -0
- package/src/provider-plugin-builtins.ts +229 -0
- package/src/provider-plugin-cleanup-barrier.ts +229 -0
- package/src/provider-plugin-cleanup-execution.ts +79 -0
- package/src/provider-plugin-cleanup-resource.ts +470 -0
- package/src/provider-plugin-contract-identity.ts +707 -0
- package/src/provider-plugin-host.ts +1972 -0
- package/src/provider-plugin-identifiers.ts +60 -0
- package/src/provider-plugin-invocation-lease.ts +889 -0
- package/src/provider-plugin-lifecycle-kernel.ts +566 -0
- package/src/provider-plugin-lifecycle.ts +1455 -0
- package/src/provider-plugin-module-analysis.ts +336 -0
- package/src/provider-plugin-package.ts +2530 -0
- package/src/provider-plugin-portable-authority.ts +106 -0
- package/src/provider-plugin-portable-catalog.ts +94 -0
- package/src/provider-plugin-portable-identity.ts +315 -0
- package/src/provider-plugin-portable-runtime.ts +2057 -0
- package/src/provider-plugin-protocol.ts +1625 -0
- package/src/provider-plugin-registry.ts +3342 -0
- package/src/provider-plugin-store.ts +2155 -0
- package/src/provider-plugin-test-fixture.ts +5 -0
- package/src/provider-plugin.ts +4907 -0
- package/src/provider-plugins.generated.ts +43 -0
- package/src/provider-plugins.ts +8 -0
- package/src/provider-subject.ts +41 -0
- package/src/provider.ts +315 -0
- package/src/providers/beeper-local-runtime.ts +5426 -0
- package/src/providers/beeper-local.ts +2739 -0
- package/src/providers/beeper-messaging.ts +462 -0
- package/src/providers/beeper-omni.ts +1159 -0
- package/src/providers/bluesky-web-runtime.ts +2895 -0
- package/src/providers/bluesky-web.ts +1614 -0
- package/src/providers/clasificados-web-runtime.ts +255 -0
- package/src/providers/clasificados-web.ts +503 -0
- package/src/providers/contact-projection.ts +301 -0
- package/src/providers/github-read-model.ts +94 -0
- package/src/providers/github-read-platform.ts +165 -0
- package/src/providers/github-read-program.ts +97 -0
- package/src/providers/github-web-runtime.ts +320 -0
- package/src/providers/github-web.ts +403 -0
- package/src/providers/gmail-api.ts +2834 -0
- package/src/providers/gmail-omni.ts +830 -0
- package/src/providers/gmail.ts +1130 -0
- package/src/providers/hacker-news-web-runtime.ts +220 -0
- package/src/providers/hacker-news-web.ts +809 -0
- package/src/providers/imessage-direct-install.ts +354 -0
- package/src/providers/imessage-direct-messaging.ts +532 -0
- package/src/providers/imessage-direct-omni.ts +400 -0
- package/src/providers/imessage-direct-runtime.ts +1498 -0
- package/src/providers/imessage-direct.ts +351 -0
- package/src/providers/instagram-video-foundations.ts +913 -0
- package/src/providers/instagram-web-profile-browser.ts +600 -0
- package/src/providers/iso-bmff.ts +338 -0
- package/src/providers/linkedin-company-platform.ts +42 -0
- package/src/providers/linkedin-company-program.ts +88 -0
- package/src/providers/linkedin-contact-failure.ts +84 -0
- package/src/providers/linkedin-contact-platform.ts +45 -0
- package/src/providers/linkedin-contact-program.ts +91 -0
- package/src/providers/linkedin-profile-activity-failure.ts +76 -0
- package/src/providers/linkedin-profile-activity-platform.ts +41 -0
- package/src/providers/linkedin-profile-activity-program.ts +67 -0
- package/src/providers/linkedin-read-failure.ts +52 -0
- package/src/providers/linkedin-self-platform.ts +53 -0
- package/src/providers/linkedin-self-program.ts +122 -0
- package/src/providers/linkedin-web-article-browser.ts +794 -0
- package/src/providers/linkedin-web-bootstrap.ts +146 -0
- package/src/providers/linkedin-web-contact.ts +830 -0
- package/src/providers/linkedin-web-feed-browser.ts +832 -0
- package/src/providers/linkedin-web-feed.ts +864 -0
- package/src/providers/linkedin-web-post-browser.ts +653 -0
- package/src/providers/linkedin-web-profile-browser.ts +683 -0
- package/src/providers/linkedin-web-runtime.ts +2388 -0
- package/src/providers/linkedin-web.ts +3305 -0
- package/src/providers/linkedin.ts +1885 -0
- package/src/providers/meta-bootstrap.ts +1043 -0
- package/src/providers/meta-facebook-group.ts +690 -0
- package/src/providers/meta-marketplace-relay.ts +747 -0
- package/src/providers/meta-omni.ts +373 -0
- package/src/providers/meta-relay-bundle.ts +1049 -0
- package/src/providers/meta-web-descriptors.ts +1740 -0
- package/src/providers/meta-web-runtime.ts +3388 -0
- package/src/providers/meta-web.ts +2931 -0
- package/src/providers/read-failure.ts +139 -0
- package/src/providers/reddit-omni.ts +462 -0
- package/src/providers/reddit-web-runtime.ts +1929 -0
- package/src/providers/reddit-web.ts +1611 -0
- package/src/providers/rental-listings.ts +460 -0
- package/src/providers/substack-omni.ts +406 -0
- package/src/providers/substack-video-mp4.ts +33 -0
- package/src/providers/substack-web-runtime.ts +2812 -0
- package/src/providers/substack-web.ts +1502 -0
- package/src/providers/tiktok-video-mp4.ts +33 -0
- package/src/providers/tiktok-web-runtime.ts +770 -0
- package/src/providers/tiktok-web.ts +2786 -0
- package/src/providers/twitch-web-runtime.ts +226 -0
- package/src/providers/twitch-web.ts +274 -0
- package/src/providers/whatsapp-account-identity.ts +55 -0
- package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
- package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
- package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
- package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
- package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
- package/src/providers/whatsapp-messaging.ts +132 -0
- package/src/providers/whatsapp-omni.ts +586 -0
- package/src/providers/whatsapp-web-runtime.ts +3108 -0
- package/src/providers/whatsapp-web.ts +1037 -0
- package/src/providers/x-image-provenance.ts +523 -0
- package/src/providers/x-made-with-ai.ts +106 -0
- package/src/providers/x-transaction-id.ts +413 -0
- package/src/providers/x-web-runtime.ts +3485 -0
- package/src/providers/x-web.ts +2332 -0
- package/src/providers/x.ts +1889 -0
- package/src/providers/youtube-web-runtime.ts +1400 -0
- package/src/providers/youtube-web.ts +1221 -0
- package/src/read-admission-runtime.ts +91 -0
- package/src/read-client.ts +521 -0
- package/src/read-effect-platform.ts +7 -0
- package/src/read-effect-runtime.ts +14 -0
- package/src/read-effect.ts +52 -0
- package/src/read-projection-admission.ts +866 -0
- package/src/read-projections.ts +3151 -0
- package/src/recovery.ts +1492 -0
- package/src/reviewed-template.ts +146 -0
- package/src/run-journal.ts +1272 -0
- package/src/runtime.ts +5116 -0
- package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
- package/src/scripts/install-whatsapp-protocol.sh +232 -0
- package/src/scripts/recover-install-stages.ts +1491 -0
- package/src/scripts/resolve-state-home.ts +30 -0
- package/src/scripts/scaffold-web-provider.ts +973 -0
- package/src/scripts/sync-bundled-adapters.ts +695 -0
- package/src/session-secrets.ts +1183 -0
- package/src/state-helper.bunfig.toml +2 -0
- package/src/state-helper.ts +2914 -0
- package/src/storage.ts +3842 -0
- package/src/transport-policy.ts +2 -0
- package/src/usage.ts +182 -0
- package/src/version.ts +2 -0
- package/src/web-session-authentication-policy.ts +136 -0
- package/src/web-session-cleanup-admission.ts +2344 -0
- package/src/web-session-cleanup-join.ts +99 -0
- package/src/web-session-client.ts +996 -0
- package/src/web-session-contract-definitions.ts +820 -0
- package/src/web-session-contract-planning.ts +42 -0
- package/src/web-session-contracts.ts +228 -0
- package/src/web-session-cookies.ts +500 -0
- package/src/web-session-execution.ts +385 -0
- package/src/web-session-http.ts +373 -0
- package/src/web-session-read-errors.ts +19 -0
- package/src/web-session-read-runtime.ts +124 -0
- package/src/web-session-recovery.ts +818 -0
- package/src/web-session-sites.ts +58 -0
- package/src/web-session-template.ts +994 -0
- package/src/web-session.ts +93 -0
- package/src/whatsapp-client-binding.ts +21 -0
- package/src/whatsapp-client-types.ts +75 -0
- package/src/whatsapp-client.ts +395 -0
- package/src/whatsapp-export-coordinate.ts +17 -0
- package/src/whatsapp-message-bundle-v2.ts +195 -0
- package/src/whatsapp-message-like-me-cli.ts +195 -0
- package/src/whatsapp-message-like-me-export.ts +1072 -0
- package/src/whatsapp-message-like-me-source.ts +1012 -0
- package/tsconfig.json +67 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { OperationDeadlineError } from "../operation-deadline";
|
|
2
|
+
import {
|
|
3
|
+
readFailureProjection,
|
|
4
|
+
type ReadFailureProjection,
|
|
5
|
+
type WebSessionExecution,
|
|
6
|
+
} from "../web-session-execution";
|
|
7
|
+
|
|
8
|
+
export type ProviderReadFailureStage =
|
|
9
|
+
| "bootstrap"
|
|
10
|
+
| "identity"
|
|
11
|
+
| "target"
|
|
12
|
+
| "supplemental";
|
|
13
|
+
|
|
14
|
+
export class ProviderReadResponseRejectedError extends Error {
|
|
15
|
+
readonly status: number;
|
|
16
|
+
|
|
17
|
+
constructor(status: number) {
|
|
18
|
+
super("provider read response did not match the reviewed status contract");
|
|
19
|
+
this.name = "ProviderReadResponseRejectedError";
|
|
20
|
+
this.status = status;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class ProviderReadTransportError extends Error {
|
|
25
|
+
constructor(cause: unknown) {
|
|
26
|
+
super("provider read transport failed before a reviewed response", { cause });
|
|
27
|
+
this.name = "ProviderReadTransportError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class ProviderReadThrottledError extends Error {
|
|
32
|
+
constructor() {
|
|
33
|
+
super("provider read was throttled by bounded response metadata");
|
|
34
|
+
this.name = "ProviderReadThrottledError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type ProviderReadFailureOptions = {
|
|
39
|
+
readonly stage: ProviderReadFailureStage;
|
|
40
|
+
readonly authenticated: boolean;
|
|
41
|
+
readonly accountMismatch?: (error: Error) => boolean;
|
|
42
|
+
readonly authRepairRequired?: (error: Error) => boolean;
|
|
43
|
+
readonly targetUnavailable?: (error: Error) => boolean;
|
|
44
|
+
readonly targetStatusUnavailable?: boolean;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function responseStatus(error: Error): number | null {
|
|
48
|
+
if (error instanceof ProviderReadResponseRejectedError) return error.status;
|
|
49
|
+
const match = /^(?:authenticated web (?:API )?request|authenticated web API|public first-party web asset) returned unreviewed status(?:\/content type)? ([1-5][0-9]{2})(?:\/|$)/u
|
|
50
|
+
.exec(error.message);
|
|
51
|
+
return match?.[1] === undefined ? null : Number(match[1]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function transientTransportFailure(error: Error): boolean {
|
|
55
|
+
return error.message === "authenticated web API request failed before a reviewed response was received"
|
|
56
|
+
|| error.message === "authenticated web request failed before a reviewed response was received"
|
|
57
|
+
|| error.message === "authenticated web response body stream failed before completion"
|
|
58
|
+
|| error.message === "public first-party web asset request failed"
|
|
59
|
+
|| error.message === "public web asset upload failed before a reviewed response was received"
|
|
60
|
+
|| error instanceof ProviderReadTransportError;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function providerReadFailureProjection(
|
|
64
|
+
error: unknown,
|
|
65
|
+
options: ProviderReadFailureOptions,
|
|
66
|
+
): ReadFailureProjection {
|
|
67
|
+
let deadlineCause: unknown = error;
|
|
68
|
+
for (let depth = 0; depth < 8 && deadlineCause !== undefined; depth += 1) {
|
|
69
|
+
if (deadlineCause instanceof OperationDeadlineError) {
|
|
70
|
+
return deadlineCause.failure === "timed-out"
|
|
71
|
+
? readFailureProjection("operation-timeout")
|
|
72
|
+
: readFailureProjection("contract-drift");
|
|
73
|
+
}
|
|
74
|
+
deadlineCause = deadlineCause instanceof Error
|
|
75
|
+
? deadlineCause.cause
|
|
76
|
+
: undefined;
|
|
77
|
+
}
|
|
78
|
+
let current: unknown = error;
|
|
79
|
+
for (let depth = 0; depth < 8 && current !== undefined; depth += 1) {
|
|
80
|
+
if (
|
|
81
|
+
current instanceof OperationDeadlineError
|
|
82
|
+
&& current.failure === "timed-out"
|
|
83
|
+
) return readFailureProjection("operation-timeout");
|
|
84
|
+
if (current instanceof ProviderReadThrottledError) {
|
|
85
|
+
return readFailureProjection("provider-throttled");
|
|
86
|
+
}
|
|
87
|
+
if (current instanceof Error) {
|
|
88
|
+
if (options.accountMismatch?.(current) === true) {
|
|
89
|
+
return readFailureProjection("account-mismatch");
|
|
90
|
+
}
|
|
91
|
+
if (options.authRepairRequired?.(current) === true) {
|
|
92
|
+
return readFailureProjection("auth-repair-required");
|
|
93
|
+
}
|
|
94
|
+
if (
|
|
95
|
+
options.stage === "target"
|
|
96
|
+
&& options.targetUnavailable?.(current) === true
|
|
97
|
+
) return readFailureProjection("target-unavailable");
|
|
98
|
+
const status = responseStatus(current);
|
|
99
|
+
if (status === 401 || status === 403) {
|
|
100
|
+
return options.authenticated
|
|
101
|
+
? readFailureProjection("auth-repair-required")
|
|
102
|
+
: readFailureProjection("contract-drift");
|
|
103
|
+
}
|
|
104
|
+
if (status === 429) return readFailureProjection("provider-throttled");
|
|
105
|
+
if (status === 404) {
|
|
106
|
+
return options.stage === "target" && options.targetStatusUnavailable === true
|
|
107
|
+
? readFailureProjection("target-unavailable")
|
|
108
|
+
: readFailureProjection("contract-drift");
|
|
109
|
+
}
|
|
110
|
+
if (status === 302 || status === 408 || (status !== null && status >= 500)) {
|
|
111
|
+
return readFailureProjection("provider-temporary");
|
|
112
|
+
}
|
|
113
|
+
if (transientTransportFailure(current)) {
|
|
114
|
+
return readFailureProjection("provider-temporary");
|
|
115
|
+
}
|
|
116
|
+
current = current.cause;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
return readFailureProjection("contract-drift");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function failedProviderRead(
|
|
125
|
+
provider: string,
|
|
126
|
+
error: unknown,
|
|
127
|
+
finalUrl: string | null,
|
|
128
|
+
options: ProviderReadFailureOptions,
|
|
129
|
+
): WebSessionExecution {
|
|
130
|
+
return {
|
|
131
|
+
status: "failed",
|
|
132
|
+
output: null,
|
|
133
|
+
finalUrl,
|
|
134
|
+
dispatchStarted: false,
|
|
135
|
+
dispatch: { planned: 0, started: 0, verified: 0 },
|
|
136
|
+
error: `${provider} read failed before the dispatch boundary`,
|
|
137
|
+
readFailure: providerReadFailureProjection(error, options),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { types as nodeTypes } from "node:util";
|
|
2
|
+
|
|
3
|
+
import type { OperationInput } from "../model";
|
|
4
|
+
import { OmniMaterializerDriftError } from "../omni-model";
|
|
5
|
+
import type {
|
|
6
|
+
OmniParticipantV1,
|
|
7
|
+
ProviderMaterializedEntityV1,
|
|
8
|
+
ProviderMaterializedPageV1,
|
|
9
|
+
} from "../omni-model";
|
|
10
|
+
|
|
11
|
+
type RedditInboxFolder = "inbox" | "sent" | "unread";
|
|
12
|
+
|
|
13
|
+
type ParsedRedditInboxEntity = Readonly<{
|
|
14
|
+
kind: "message" | "notification";
|
|
15
|
+
providerId: string;
|
|
16
|
+
author: string | null;
|
|
17
|
+
recipient: string | null;
|
|
18
|
+
subject: string;
|
|
19
|
+
body: string;
|
|
20
|
+
orderedAt: string | null;
|
|
21
|
+
unread: boolean;
|
|
22
|
+
parentId: string | null;
|
|
23
|
+
context: string | null;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
type ParsedRedditInboxPage = Readonly<{
|
|
27
|
+
folder: RedditInboxFolder;
|
|
28
|
+
requestedId: string | null;
|
|
29
|
+
entities: readonly ParsedRedditInboxEntity[];
|
|
30
|
+
nextAfter: string | null;
|
|
31
|
+
previousBefore: string | null;
|
|
32
|
+
limit: number;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
function drift(path: string, message: string): never {
|
|
36
|
+
throw new OmniMaterializerDriftError("reddit", path, message);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function record(value: unknown, path: string): Readonly<Record<string, unknown>> {
|
|
40
|
+
if (
|
|
41
|
+
nodeTypes.isProxy(value)
|
|
42
|
+
|| typeof value !== "object"
|
|
43
|
+
|| value === null
|
|
44
|
+
|| Array.isArray(value)
|
|
45
|
+
) {
|
|
46
|
+
return drift(path, "must be a plain object");
|
|
47
|
+
}
|
|
48
|
+
const prototype = Object.getPrototypeOf(value) as unknown;
|
|
49
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
50
|
+
return drift(path, "must be a plain object");
|
|
51
|
+
}
|
|
52
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
53
|
+
for (const name of Reflect.ownKeys(descriptors)) {
|
|
54
|
+
if (typeof name !== "string") return drift(path, "must not have symbol properties");
|
|
55
|
+
const descriptor = descriptors[name];
|
|
56
|
+
if (
|
|
57
|
+
descriptor === undefined
|
|
58
|
+
|| !("value" in descriptor)
|
|
59
|
+
|| !descriptor.enumerable
|
|
60
|
+
) {
|
|
61
|
+
return drift(`${path}.*`, "must be an enumerable data property");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return value as Readonly<Record<string, unknown>>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function exactKeys(
|
|
68
|
+
value: Readonly<Record<string, unknown>>,
|
|
69
|
+
required: readonly string[],
|
|
70
|
+
optional: readonly string[],
|
|
71
|
+
path: string,
|
|
72
|
+
): void {
|
|
73
|
+
const allowed = new Set([...required, ...optional]);
|
|
74
|
+
for (const key of Object.keys(value)) {
|
|
75
|
+
if (!allowed.has(key)) drift(path, "contains an unreviewed property");
|
|
76
|
+
}
|
|
77
|
+
for (const key of required) {
|
|
78
|
+
if (!Object.hasOwn(value, key)) drift(`${path}.${key}`, "is required");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function array(value: unknown, path: string, maximum = 100): readonly unknown[] {
|
|
83
|
+
if (
|
|
84
|
+
nodeTypes.isProxy(value)
|
|
85
|
+
|| !Array.isArray(value)
|
|
86
|
+
|| Object.getPrototypeOf(value) !== Array.prototype
|
|
87
|
+
|| value.length > maximum
|
|
88
|
+
) {
|
|
89
|
+
return drift(path, `must be an array of at most ${maximum} items`);
|
|
90
|
+
}
|
|
91
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
92
|
+
if (!Object.hasOwn(value, index)) drift(`${path}[${index}]`, "must not be sparse");
|
|
93
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, index);
|
|
94
|
+
if (
|
|
95
|
+
descriptor === undefined
|
|
96
|
+
|| !("value" in descriptor)
|
|
97
|
+
|| !descriptor.enumerable
|
|
98
|
+
) drift(`${path}[${index}]`, "must be an enumerable data property");
|
|
99
|
+
}
|
|
100
|
+
const expected = new Set<PropertyKey>([
|
|
101
|
+
"length",
|
|
102
|
+
...Array.from({ length: value.length }, (_unused, index) => String(index)),
|
|
103
|
+
]);
|
|
104
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
105
|
+
if (ownKeys.length !== expected.size || ownKeys.some((key) => !expected.has(key))) {
|
|
106
|
+
drift(path, "must be a dense array without named properties");
|
|
107
|
+
}
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function hasDisallowedControl(value: string): boolean {
|
|
112
|
+
for (const character of value) {
|
|
113
|
+
const code = character.charCodeAt(0);
|
|
114
|
+
if ((code < 32 && code !== 9 && code !== 10 && code !== 13) || code === 127) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function string(
|
|
122
|
+
value: unknown,
|
|
123
|
+
path: string,
|
|
124
|
+
maximum: number,
|
|
125
|
+
allowEmpty = false,
|
|
126
|
+
): string {
|
|
127
|
+
if (
|
|
128
|
+
typeof value !== "string"
|
|
129
|
+
|| Buffer.byteLength(value, "utf8") > maximum
|
|
130
|
+
|| (!allowEmpty && value.length === 0)
|
|
131
|
+
|| hasDisallowedControl(value)
|
|
132
|
+
) return drift(path, `must be a bounded${allowEmpty ? "" : " non-empty"} string`);
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function boolean(value: unknown, path: string): boolean {
|
|
137
|
+
if (typeof value !== "boolean") return drift(path, "must be boolean");
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function integer(
|
|
142
|
+
value: unknown,
|
|
143
|
+
path: string,
|
|
144
|
+
minimum: number,
|
|
145
|
+
maximum: number,
|
|
146
|
+
): number {
|
|
147
|
+
if (
|
|
148
|
+
typeof value !== "number"
|
|
149
|
+
|| !Number.isSafeInteger(value)
|
|
150
|
+
|| value < minimum
|
|
151
|
+
|| value > maximum
|
|
152
|
+
) return drift(path, `must be an integer from ${minimum} through ${maximum}`);
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function fullname(
|
|
157
|
+
value: unknown,
|
|
158
|
+
path: string,
|
|
159
|
+
kinds: readonly ("t1" | "t3" | "t4")[],
|
|
160
|
+
): string {
|
|
161
|
+
const result = string(value, path, 40);
|
|
162
|
+
const match = /^(t[134])_([a-z0-9]{1,32})$/u.exec(result);
|
|
163
|
+
if (match === null || !kinds.includes(match[1] as "t1" | "t3" | "t4")) {
|
|
164
|
+
return drift(path, `must be an exact ${kinds.join(" or ")} Reddit fullname`);
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function username(value: unknown, path: string): string | null {
|
|
170
|
+
if (value === null) return null;
|
|
171
|
+
const result = string(value, path, 64);
|
|
172
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/u.test(result)) {
|
|
173
|
+
return drift(path, "must be an exact Reddit username");
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function absoluteRedditUrl(value: unknown, path: string): string | null {
|
|
179
|
+
if (value === null) return null;
|
|
180
|
+
const raw = string(value, path, 2_048);
|
|
181
|
+
let url: URL;
|
|
182
|
+
try {
|
|
183
|
+
url = new URL(raw);
|
|
184
|
+
} catch {
|
|
185
|
+
return drift(path, "must be an absolute Reddit HTTPS URL");
|
|
186
|
+
}
|
|
187
|
+
if (
|
|
188
|
+
url.protocol !== "https:"
|
|
189
|
+
|| url.hostname !== "www.reddit.com"
|
|
190
|
+
|| url.username !== ""
|
|
191
|
+
|| url.password !== ""
|
|
192
|
+
) return drift(path, "must be an absolute Reddit HTTPS URL");
|
|
193
|
+
return url.href;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function timestamp(value: unknown, path: string): string | null {
|
|
197
|
+
if (value === null) return null;
|
|
198
|
+
if (
|
|
199
|
+
typeof value !== "number"
|
|
200
|
+
|| !Number.isFinite(value)
|
|
201
|
+
|| value < 0
|
|
202
|
+
|| value > 253_402_300_799
|
|
203
|
+
) return drift(path, "must be a finite Unix timestamp");
|
|
204
|
+
const date = new Date(value * 1_000);
|
|
205
|
+
if (!Number.isFinite(date.getTime())) return drift(path, "must be a valid Unix timestamp");
|
|
206
|
+
return date.toISOString();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function folder(value: unknown, path: string): RedditInboxFolder {
|
|
210
|
+
if (value !== "inbox" && value !== "sent" && value !== "unread") {
|
|
211
|
+
return drift(path, "must be inbox, sent, or unread");
|
|
212
|
+
}
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function parseListInput(input: OperationInput): {
|
|
217
|
+
readonly folder: RedditInboxFolder;
|
|
218
|
+
readonly limit: number;
|
|
219
|
+
readonly after: string | null;
|
|
220
|
+
} {
|
|
221
|
+
const source = record(input, "messaging.list input");
|
|
222
|
+
exactKeys(source, ["folder"], ["after", "limit"], "messaging.list input");
|
|
223
|
+
const after = source.after === undefined
|
|
224
|
+
? null
|
|
225
|
+
: fullname(source.after, "messaging.list input.after", ["t1", "t4"]);
|
|
226
|
+
return Object.freeze({
|
|
227
|
+
folder: folder(source.folder, "messaging.list input.folder"),
|
|
228
|
+
limit: source.limit === undefined
|
|
229
|
+
? 25
|
|
230
|
+
: integer(source.limit, "messaging.list input.limit", 1, 100),
|
|
231
|
+
after,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function participant(value: string | null): OmniParticipantV1 | null {
|
|
236
|
+
return value === null
|
|
237
|
+
? null
|
|
238
|
+
: Object.freeze({
|
|
239
|
+
providerId: null,
|
|
240
|
+
displayName: value,
|
|
241
|
+
handle: value,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function materializedEntity(
|
|
246
|
+
value: ParsedRedditInboxEntity,
|
|
247
|
+
folderValue: RedditInboxFolder,
|
|
248
|
+
): ProviderMaterializedEntityV1 {
|
|
249
|
+
if (value.kind === "notification") {
|
|
250
|
+
return Object.freeze({
|
|
251
|
+
kind: "notification",
|
|
252
|
+
providerId: value.providerId,
|
|
253
|
+
providerRevision: null,
|
|
254
|
+
orderedAt: value.orderedAt,
|
|
255
|
+
actor: participant(value.author),
|
|
256
|
+
subject: value.subject,
|
|
257
|
+
body: value.body,
|
|
258
|
+
unread: value.unread,
|
|
259
|
+
context: value.context,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
const recipient = participant(value.recipient);
|
|
263
|
+
return Object.freeze({
|
|
264
|
+
kind: "message",
|
|
265
|
+
providerId: value.providerId,
|
|
266
|
+
providerRevision: null,
|
|
267
|
+
orderedAt: value.orderedAt,
|
|
268
|
+
conversationProviderId: null,
|
|
269
|
+
sender: participant(value.author),
|
|
270
|
+
recipients: Object.freeze(recipient === null ? [] : [recipient]),
|
|
271
|
+
direction: folderValue === "sent" ? "outgoing" : "incoming",
|
|
272
|
+
subject: value.subject,
|
|
273
|
+
body: value.body,
|
|
274
|
+
unread: value.unread,
|
|
275
|
+
replyToProviderId: value.parentId,
|
|
276
|
+
state: "active",
|
|
277
|
+
attachments: Object.freeze([]),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function parseReadInput(input: OperationInput): {
|
|
282
|
+
readonly folder: RedditInboxFolder;
|
|
283
|
+
readonly requestedId: string;
|
|
284
|
+
} {
|
|
285
|
+
const source = record(input, "messaging.read input");
|
|
286
|
+
exactKeys(source, ["folder", "message_id"], [], "messaging.read input");
|
|
287
|
+
return Object.freeze({
|
|
288
|
+
folder: folder(source.folder, "messaging.read input.folder"),
|
|
289
|
+
requestedId: fullname(
|
|
290
|
+
source.message_id,
|
|
291
|
+
"messaging.read input.message_id",
|
|
292
|
+
["t4"],
|
|
293
|
+
),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function parseEntity(value: unknown, path: string): ParsedRedditInboxEntity {
|
|
298
|
+
const source = record(value, path);
|
|
299
|
+
exactKeys(source, [
|
|
300
|
+
"kind",
|
|
301
|
+
"id",
|
|
302
|
+
"author",
|
|
303
|
+
"recipient",
|
|
304
|
+
"subject",
|
|
305
|
+
"body",
|
|
306
|
+
"createdUtc",
|
|
307
|
+
"unread",
|
|
308
|
+
"parentId",
|
|
309
|
+
"context",
|
|
310
|
+
], [], path);
|
|
311
|
+
if (source.kind !== "message" && source.kind !== "notification") {
|
|
312
|
+
return drift(`${path}.kind`, "must be message or notification");
|
|
313
|
+
}
|
|
314
|
+
const kind = source.kind;
|
|
315
|
+
const providerId = fullname(
|
|
316
|
+
source.id,
|
|
317
|
+
`${path}.id`,
|
|
318
|
+
kind === "message" ? ["t4"] : ["t1"],
|
|
319
|
+
);
|
|
320
|
+
const parentId = source.parentId === null
|
|
321
|
+
? null
|
|
322
|
+
: fullname(source.parentId, `${path}.parentId`, ["t1", "t3", "t4"]);
|
|
323
|
+
return Object.freeze({
|
|
324
|
+
kind,
|
|
325
|
+
providerId,
|
|
326
|
+
author: username(source.author, `${path}.author`),
|
|
327
|
+
recipient: username(source.recipient, `${path}.recipient`),
|
|
328
|
+
subject: string(source.subject, `${path}.subject`, 1_000, true),
|
|
329
|
+
body: string(source.body, `${path}.body`, 100_000, true),
|
|
330
|
+
orderedAt: timestamp(source.createdUtc, `${path}.createdUtc`),
|
|
331
|
+
unread: boolean(source.unread, `${path}.unread`),
|
|
332
|
+
parentId,
|
|
333
|
+
context: absoluteRedditUrl(source.context, `${path}.context`),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function parseOutput(
|
|
338
|
+
output: unknown,
|
|
339
|
+
options: {
|
|
340
|
+
readonly folder: RedditInboxFolder;
|
|
341
|
+
readonly requestedId: string | null;
|
|
342
|
+
readonly limit: number;
|
|
343
|
+
},
|
|
344
|
+
): ParsedRedditInboxPage {
|
|
345
|
+
const source = record(output, "output");
|
|
346
|
+
exactKeys(source, ["messages", "after", "before", "requested"], [], "output");
|
|
347
|
+
const entities = array(source.messages, "output.messages", 101).map((value, index) =>
|
|
348
|
+
parseEntity(value, `output.messages[${index}]`));
|
|
349
|
+
if (options.requestedId === null && entities.length > options.limit) {
|
|
350
|
+
drift("output.messages", "exceeds messaging.list input.limit");
|
|
351
|
+
}
|
|
352
|
+
const ids = entities.map((entity) => entity.providerId);
|
|
353
|
+
if (new Set(ids).size !== ids.length) drift("output.messages", "contains duplicate stable IDs");
|
|
354
|
+
const nextAfter = source.after === null
|
|
355
|
+
? null
|
|
356
|
+
: fullname(source.after, "output.after", ["t1", "t4"]);
|
|
357
|
+
const previousBefore = source.before === null
|
|
358
|
+
? null
|
|
359
|
+
: fullname(source.before, "output.before", ["t1", "t4"]);
|
|
360
|
+
if (nextAfter !== null && nextAfter === previousBefore) {
|
|
361
|
+
drift("output.after", "must not repeat output.before");
|
|
362
|
+
}
|
|
363
|
+
if (options.requestedId === null) {
|
|
364
|
+
if (source.requested !== null) drift("output.requested", "must be null for messaging.list");
|
|
365
|
+
} else {
|
|
366
|
+
if (source.requested === null) drift("output.requested", "is required for messaging.read");
|
|
367
|
+
const requested = parseEntity(source.requested, "output.requested");
|
|
368
|
+
if (requested.kind !== "message" || requested.providerId !== options.requestedId) {
|
|
369
|
+
drift("output.requested.id", "must bind the requested legacy message");
|
|
370
|
+
}
|
|
371
|
+
const matches = entities.filter((entity) => entity.providerId === requested.providerId);
|
|
372
|
+
if (matches.length !== 1 || JSON.stringify(matches[0]) !== JSON.stringify(requested)) {
|
|
373
|
+
drift("output.requested", "must equal one ordered output.messages entity");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return Object.freeze({
|
|
377
|
+
folder: options.folder,
|
|
378
|
+
requestedId: options.requestedId,
|
|
379
|
+
entities: Object.freeze(entities),
|
|
380
|
+
nextAfter,
|
|
381
|
+
previousBefore,
|
|
382
|
+
limit: options.limit,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Strict provider-owned parse; the shared omni envelope is assembled below this boundary. */
|
|
387
|
+
export function materializeRedditMessagingList(
|
|
388
|
+
input: OperationInput,
|
|
389
|
+
output: unknown,
|
|
390
|
+
): ProviderMaterializedPageV1 {
|
|
391
|
+
const parsed = parseListInput(input);
|
|
392
|
+
const page = parseOutput(output, {
|
|
393
|
+
folder: parsed.folder,
|
|
394
|
+
requestedId: null,
|
|
395
|
+
limit: parsed.limit,
|
|
396
|
+
});
|
|
397
|
+
if (page.nextAfter !== null && page.nextAfter === parsed.after) {
|
|
398
|
+
drift("output.after", "must advance beyond messaging.list input.after");
|
|
399
|
+
}
|
|
400
|
+
const entities = Object.freeze(page.entities.map((entity) =>
|
|
401
|
+
materializedEntity(entity, parsed.folder)));
|
|
402
|
+
return Object.freeze({
|
|
403
|
+
schemaVersion: 1,
|
|
404
|
+
partition: `reddit:inbox:${parsed.folder}`,
|
|
405
|
+
completeness: Object.freeze({
|
|
406
|
+
kind: page.nextAfter === null && parsed.after === null
|
|
407
|
+
? "complete"
|
|
408
|
+
: "page",
|
|
409
|
+
reason: page.nextAfter === null
|
|
410
|
+
? parsed.after === null
|
|
411
|
+
? "Reddit returned the complete legacy inbox Listing in one root page."
|
|
412
|
+
: "Reddit returned a terminal continuation page; it is merged without claiming standalone partition completeness."
|
|
413
|
+
: "Reddit returned an older-page cursor for the legacy inbox Listing.",
|
|
414
|
+
}),
|
|
415
|
+
cursor: Object.freeze({
|
|
416
|
+
direction: "backward",
|
|
417
|
+
request: parsed.after,
|
|
418
|
+
nextInput: page.nextAfter === null
|
|
419
|
+
? null
|
|
420
|
+
: Object.freeze({
|
|
421
|
+
folder: parsed.folder,
|
|
422
|
+
after: page.nextAfter,
|
|
423
|
+
limit: parsed.limit,
|
|
424
|
+
}),
|
|
425
|
+
}),
|
|
426
|
+
entities,
|
|
427
|
+
tombstones: Object.freeze([]),
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Strict provider-owned parse; the shared omni envelope is assembled below this boundary. */
|
|
432
|
+
export function materializeRedditMessagingRead(
|
|
433
|
+
input: OperationInput,
|
|
434
|
+
output: unknown,
|
|
435
|
+
): ProviderMaterializedPageV1 {
|
|
436
|
+
const parsed = parseReadInput(input);
|
|
437
|
+
const page = parseOutput(output, {
|
|
438
|
+
folder: parsed.folder,
|
|
439
|
+
requestedId: parsed.requestedId,
|
|
440
|
+
limit: 101,
|
|
441
|
+
});
|
|
442
|
+
if (page.nextAfter !== null || page.previousBefore !== null) {
|
|
443
|
+
drift("output", "must not paginate an exact messaging.read result");
|
|
444
|
+
}
|
|
445
|
+
const entities = Object.freeze(page.entities.map((entity) =>
|
|
446
|
+
materializedEntity(entity, parsed.folder)));
|
|
447
|
+
return Object.freeze({
|
|
448
|
+
schemaVersion: 1,
|
|
449
|
+
partition: `reddit:message:${parsed.requestedId}`,
|
|
450
|
+
completeness: Object.freeze({
|
|
451
|
+
kind: "complete",
|
|
452
|
+
reason: "Reddit returned and identity-bound the exact requested legacy message.",
|
|
453
|
+
}),
|
|
454
|
+
cursor: Object.freeze({
|
|
455
|
+
direction: "none",
|
|
456
|
+
request: null,
|
|
457
|
+
nextInput: null,
|
|
458
|
+
}),
|
|
459
|
+
entities,
|
|
460
|
+
tombstones: Object.freeze([]),
|
|
461
|
+
});
|
|
462
|
+
}
|