@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,93 @@
|
|
|
1
|
+
import type { GhostgetAuth } from "./auth";
|
|
2
|
+
import { requireProviderPluginAuth } from "./provider-plugin-auth";
|
|
3
|
+
import type { ProviderPluginRegistry } from "./provider-plugin-registry";
|
|
4
|
+
import { getWebSessionContract, planWebSessionDispatches } from "./web-session-contracts";
|
|
5
|
+
import type { WebSessionSiteId } from "./web-session-sites";
|
|
6
|
+
import {
|
|
7
|
+
requireValidWebSessionOperationInput,
|
|
8
|
+
runWebSessionOperationWithDeadline,
|
|
9
|
+
type WebSessionExecution,
|
|
10
|
+
type WebSessionOperationExecutor,
|
|
11
|
+
} from "./web-session-execution";
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
requireValidWebSessionOperationInput,
|
|
15
|
+
runWebSessionOperationWithDeadline,
|
|
16
|
+
startWebSessionCleanupTrackedOperation,
|
|
17
|
+
type WebSessionCleanupBarrierRegistrar,
|
|
18
|
+
type WebSessionDispatchEvent,
|
|
19
|
+
type WebSessionExecution,
|
|
20
|
+
type WebSessionExecutionOptions,
|
|
21
|
+
type WebSessionOperationDeadline,
|
|
22
|
+
type WebSessionOperationExecutor,
|
|
23
|
+
type WebSessionProviderAcceptedMutationTargetEvent,
|
|
24
|
+
type WebSessionProviderBoundMutationTargetEvent,
|
|
25
|
+
} from "./web-session-execution";
|
|
26
|
+
|
|
27
|
+
export async function probeWebSessionSubject(
|
|
28
|
+
site: WebSessionSiteId,
|
|
29
|
+
auth: GhostgetAuth,
|
|
30
|
+
registry: ProviderPluginRegistry,
|
|
31
|
+
): Promise<string> {
|
|
32
|
+
const binding = registry.requireSessionRoute(site);
|
|
33
|
+
requireProviderPluginAuth(binding, auth);
|
|
34
|
+
const probe = binding.subject.probe;
|
|
35
|
+
if (probe === undefined) {
|
|
36
|
+
throw new Error("authenticated web site has no registered current-account probe");
|
|
37
|
+
}
|
|
38
|
+
return probe(auth);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Execute one code-owned first-party API capability. Provider implementations
|
|
43
|
+
* are registered here as they graduate from capture-required evidence.
|
|
44
|
+
*/
|
|
45
|
+
export function createWebSessionOperationExecutor(
|
|
46
|
+
registry: ProviderPluginRegistry,
|
|
47
|
+
): WebSessionOperationExecutor {
|
|
48
|
+
return async (
|
|
49
|
+
manifest,
|
|
50
|
+
recipe,
|
|
51
|
+
input,
|
|
52
|
+
auth,
|
|
53
|
+
options,
|
|
54
|
+
) => runWebSessionOperationWithDeadline(
|
|
55
|
+
recipe,
|
|
56
|
+
options,
|
|
57
|
+
async (boundedOptions) => {
|
|
58
|
+
const route = registry.requireSessionRoute(recipe.site);
|
|
59
|
+
const { binding, operation } = registry.requireOperationDefinition(
|
|
60
|
+
route.transport,
|
|
61
|
+
recipe.site,
|
|
62
|
+
recipe.action,
|
|
63
|
+
recipe.contractVersion,
|
|
64
|
+
);
|
|
65
|
+
const contract = getWebSessionContract(recipe, registry);
|
|
66
|
+
requireProviderPluginAuth(binding, auth);
|
|
67
|
+
requireValidWebSessionOperationInput(operation, input);
|
|
68
|
+
const planned = planWebSessionDispatches(recipe, input, registry).length;
|
|
69
|
+
const unavailable = (reason: string): WebSessionExecution => ({
|
|
70
|
+
status: "failed",
|
|
71
|
+
output: null,
|
|
72
|
+
finalUrl: binding.origin,
|
|
73
|
+
dispatchStarted: false,
|
|
74
|
+
dispatch: { planned, started: 0, verified: 0 },
|
|
75
|
+
error: reason,
|
|
76
|
+
});
|
|
77
|
+
if (contract.state === "capture-required") {
|
|
78
|
+
return unavailable(`${recipe.site} ${recipe.action} requires a fresh reviewed HAR contract before execution`);
|
|
79
|
+
}
|
|
80
|
+
// Provider runtimes own the distinction between a known pre-dispatch
|
|
81
|
+
// failure and an indeterminate post-dispatch outcome. Do not flatten an
|
|
82
|
+
// unexpected throw here: runPrepared will conservatively reconcile it
|
|
83
|
+
// against the durable dispatch ledger.
|
|
84
|
+
if (
|
|
85
|
+
binding.transport !== "web-session-api"
|
|
86
|
+
&& binding.transport !== "linked-device"
|
|
87
|
+
) {
|
|
88
|
+
return unavailable("authenticated web site operation resolved to the wrong plugin transport");
|
|
89
|
+
}
|
|
90
|
+
return binding.execute(manifest, recipe, input, auth, boundedOptions);
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WhatsAppMessageLikeMeClientRequest,
|
|
3
|
+
WhatsAppMessageLikeMeExportReceipt,
|
|
4
|
+
} from "./whatsapp-client-types";
|
|
5
|
+
|
|
6
|
+
function fail(message: string): never {
|
|
7
|
+
throw new Error(`Ghostget WhatsApp client: ${message}`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function requireWhatsAppMessageLikeMeReceiptRequestBinding(
|
|
11
|
+
receipt: WhatsAppMessageLikeMeExportReceipt,
|
|
12
|
+
request: WhatsAppMessageLikeMeClientRequest,
|
|
13
|
+
): WhatsAppMessageLikeMeExportReceipt {
|
|
14
|
+
if (receipt.auth.id !== request.authId) {
|
|
15
|
+
return fail("receipt auth identity does not match the requested auth locator");
|
|
16
|
+
}
|
|
17
|
+
if (receipt.output.directory !== request.output) {
|
|
18
|
+
return fail("receipt output directory does not match the requested output");
|
|
19
|
+
}
|
|
20
|
+
return receipt;
|
|
21
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Side-effect-free DTO declarations for the direct WhatsApp local export client. */
|
|
2
|
+
|
|
3
|
+
export type WhatsAppMessageLikeMeClientRequest = Readonly<{
|
|
4
|
+
authId: string;
|
|
5
|
+
output: string;
|
|
6
|
+
}>;
|
|
7
|
+
|
|
8
|
+
export type WhatsAppMessageLikeMeClientOptions = Readonly<{
|
|
9
|
+
environment?: Readonly<Record<string, string | undefined>>;
|
|
10
|
+
}>;
|
|
11
|
+
|
|
12
|
+
export type WhatsAppMessageLikeMeExportReceipt = Readonly<{
|
|
13
|
+
schemaVersion: 1;
|
|
14
|
+
format: "wrench.whatsapp-message-like-me-export-receipt";
|
|
15
|
+
runId: string;
|
|
16
|
+
operation: "whatsapp.export-message-like-me";
|
|
17
|
+
status: "succeeded";
|
|
18
|
+
transport: "linked-device-local-store";
|
|
19
|
+
startedAt: string;
|
|
20
|
+
finishedAt: string;
|
|
21
|
+
auth: Readonly<{
|
|
22
|
+
id: string;
|
|
23
|
+
provider: "whatsapp";
|
|
24
|
+
identitySha256: string;
|
|
25
|
+
}>;
|
|
26
|
+
source: Readonly<{ id: "wacli-local"; version: "1.0.0" }>;
|
|
27
|
+
provider: Readonly<{ id: "whatsapp"; version: "0.15.0" }>;
|
|
28
|
+
completeness: Readonly<{
|
|
29
|
+
kind: "bounded-local";
|
|
30
|
+
reason: "local-store-coverage-unknown";
|
|
31
|
+
observedFrom: string | null;
|
|
32
|
+
observedThrough: string | null;
|
|
33
|
+
}>;
|
|
34
|
+
warnings: readonly [
|
|
35
|
+
"remote-history-incomplete",
|
|
36
|
+
...(
|
|
37
|
+
| "reaction-state-unproven"
|
|
38
|
+
| "self-chat-excluded"
|
|
39
|
+
| "message-payload-purged"
|
|
40
|
+
| "non-conversation-chats-excluded"
|
|
41
|
+
)[],
|
|
42
|
+
];
|
|
43
|
+
counts: Readonly<{
|
|
44
|
+
account: 1;
|
|
45
|
+
participant: number;
|
|
46
|
+
conversation: number;
|
|
47
|
+
message: number;
|
|
48
|
+
reaction: 0;
|
|
49
|
+
tombstone: 0;
|
|
50
|
+
}>;
|
|
51
|
+
output: Readonly<{
|
|
52
|
+
schemaVersion: 2;
|
|
53
|
+
format: "message-like-me.local-message-bundle";
|
|
54
|
+
directory: string;
|
|
55
|
+
manifestSha256: string;
|
|
56
|
+
}>;
|
|
57
|
+
privacy: Readonly<{
|
|
58
|
+
classification: "private-local";
|
|
59
|
+
attachments: "metadata-only";
|
|
60
|
+
credentials: "excluded";
|
|
61
|
+
sourcePaths: "excluded";
|
|
62
|
+
mediaBytes: "excluded";
|
|
63
|
+
cloudSync: "none";
|
|
64
|
+
}>;
|
|
65
|
+
integrity: Readonly<{ algorithm: "sha256"; receiptSha256: string }>;
|
|
66
|
+
}>;
|
|
67
|
+
|
|
68
|
+
export declare function exportWhatsAppMessageLikeMeSync(
|
|
69
|
+
request: WhatsAppMessageLikeMeClientRequest,
|
|
70
|
+
options?: WhatsAppMessageLikeMeClientOptions,
|
|
71
|
+
): WhatsAppMessageLikeMeExportReceipt;
|
|
72
|
+
|
|
73
|
+
export declare function parseWhatsAppMessageLikeMeExportReceipt(
|
|
74
|
+
value: unknown,
|
|
75
|
+
): WhatsAppMessageLikeMeExportReceipt;
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { types as nodeTypes } from "node:util";
|
|
5
|
+
|
|
6
|
+
import { canonicalJson, sha256 } from "./canonical-json";
|
|
7
|
+
import { requireWhatsAppMessageLikeMeReceiptRequestBinding } from "./whatsapp-client-binding";
|
|
8
|
+
import type {
|
|
9
|
+
WhatsAppMessageLikeMeClientOptions,
|
|
10
|
+
WhatsAppMessageLikeMeClientRequest,
|
|
11
|
+
WhatsAppMessageLikeMeExportReceipt,
|
|
12
|
+
} from "./whatsapp-client-types";
|
|
13
|
+
import {
|
|
14
|
+
isWhatsAppExportAuthId,
|
|
15
|
+
isWhatsAppExportOutputDirectory,
|
|
16
|
+
} from "./whatsapp-export-coordinate";
|
|
17
|
+
|
|
18
|
+
const MAX_STDOUT_BYTES = 2 * 1024 * 1024;
|
|
19
|
+
const MAX_STDERR_BYTES = 8 * 1024;
|
|
20
|
+
const MAX_V2_RECORDS = 500_000;
|
|
21
|
+
const PROCESS_TIMEOUT_MS = 6 * 60 * 60 * 1_000 + 60_000;
|
|
22
|
+
const DIGEST_PATTERN = /^[a-f0-9]{64}$/u;
|
|
23
|
+
|
|
24
|
+
type JsonRecord = Readonly<Record<string, unknown>>;
|
|
25
|
+
|
|
26
|
+
function fail(message: string): never {
|
|
27
|
+
throw new Error(`Ghostget WhatsApp client: ${message}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function record(value: unknown, label: string): JsonRecord {
|
|
31
|
+
if (
|
|
32
|
+
typeof value !== "object"
|
|
33
|
+
|| value === null
|
|
34
|
+
|| Array.isArray(value)
|
|
35
|
+
|| nodeTypes.isProxy(value)
|
|
36
|
+
|| (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
|
|
37
|
+
) return fail(`${label} must be a plain data object`);
|
|
38
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
39
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
40
|
+
if (typeof key !== "string") return fail(`${label} must contain only enumerable string data fields`);
|
|
41
|
+
const descriptor = descriptors[key];
|
|
42
|
+
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor)) {
|
|
43
|
+
return fail(`${label} must contain only enumerable string data fields`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return Object.freeze(Object.fromEntries(
|
|
47
|
+
Object.entries(descriptors).map(([key, descriptor]) => [key, descriptor.value]),
|
|
48
|
+
));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function exact(value: JsonRecord, keys: readonly string[], label: string): void {
|
|
52
|
+
const observed = Object.keys(value).sort();
|
|
53
|
+
const expected = [...keys].sort();
|
|
54
|
+
if (observed.length !== expected.length || observed.some((key, index) => key !== expected[index])) {
|
|
55
|
+
fail(`${label} contains unsupported or missing fields`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function timestamp(value: unknown, label: string, nullable = false): string | null {
|
|
60
|
+
if (nullable && value === null) return null;
|
|
61
|
+
if (typeof value !== "string") return fail(`${label} must be a timestamp`);
|
|
62
|
+
const parsed = new Date(value);
|
|
63
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== value) {
|
|
64
|
+
return fail(`${label} must be a canonical UTC timestamp`);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function digest(value: unknown, label: string): string {
|
|
70
|
+
if (typeof value !== "string" || !DIGEST_PATTERN.test(value)) {
|
|
71
|
+
return fail(`${label} must be lowercase SHA-256`);
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function nonNegative(value: unknown, label: string): number {
|
|
77
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
78
|
+
return fail(`${label} must be a non-negative safe integer`);
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function denseDataArray(
|
|
84
|
+
value: unknown,
|
|
85
|
+
label: string,
|
|
86
|
+
maximum: number,
|
|
87
|
+
): readonly unknown[] {
|
|
88
|
+
if (
|
|
89
|
+
nodeTypes.isProxy(value)
|
|
90
|
+
|| !Array.isArray(value)
|
|
91
|
+
|| Object.getPrototypeOf(value) !== Array.prototype
|
|
92
|
+
) return fail(`${label} must be an ordinary non-proxy array`);
|
|
93
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
94
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
95
|
+
if (
|
|
96
|
+
lengthDescriptor === undefined
|
|
97
|
+
|| !("value" in lengthDescriptor)
|
|
98
|
+
|| typeof lengthDescriptor.value !== "number"
|
|
99
|
+
|| !Number.isSafeInteger(lengthDescriptor.value)
|
|
100
|
+
|| lengthDescriptor.value < 0
|
|
101
|
+
|| lengthDescriptor.value > maximum
|
|
102
|
+
) return fail(`${label} length exceeds its reviewed bound`);
|
|
103
|
+
const length = lengthDescriptor.value;
|
|
104
|
+
const keys = Reflect.ownKeys(descriptors);
|
|
105
|
+
if (
|
|
106
|
+
keys.length !== length + 1
|
|
107
|
+
|| keys.some((key) => typeof key !== "string")
|
|
108
|
+
) return fail(`${label} must be a dense array without named properties`);
|
|
109
|
+
const items: unknown[] = [];
|
|
110
|
+
for (let index = 0; index < length; index += 1) {
|
|
111
|
+
const descriptor = descriptors[String(index)];
|
|
112
|
+
if (
|
|
113
|
+
descriptor === undefined
|
|
114
|
+
|| !descriptor.enumerable
|
|
115
|
+
|| !("value" in descriptor)
|
|
116
|
+
) return fail(`${label} must contain only dense enumerable data elements`);
|
|
117
|
+
items.push(descriptor.value);
|
|
118
|
+
}
|
|
119
|
+
if (keys.some((key) =>
|
|
120
|
+
key !== "length"
|
|
121
|
+
&& (typeof key !== "string" || !/^(0|[1-9][0-9]*)$/u.test(key)))) {
|
|
122
|
+
return fail(`${label} must be a dense array without named properties`);
|
|
123
|
+
}
|
|
124
|
+
return Object.freeze(items);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function parseWhatsAppMessageLikeMeExportReceipt(
|
|
128
|
+
value: unknown,
|
|
129
|
+
): WhatsAppMessageLikeMeExportReceipt {
|
|
130
|
+
const root = record(value, "receipt");
|
|
131
|
+
exact(root, [
|
|
132
|
+
"schemaVersion", "format", "runId", "operation", "status", "transport",
|
|
133
|
+
"startedAt", "finishedAt", "auth", "source", "provider", "completeness",
|
|
134
|
+
"warnings", "counts", "output", "privacy", "integrity",
|
|
135
|
+
], "receipt");
|
|
136
|
+
if (
|
|
137
|
+
root.schemaVersion !== 1
|
|
138
|
+
|| root.format !== "wrench.whatsapp-message-like-me-export-receipt"
|
|
139
|
+
|| typeof root.runId !== "string"
|
|
140
|
+
|| !/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/u.test(root.runId)
|
|
141
|
+
|| root.operation !== "whatsapp.export-message-like-me"
|
|
142
|
+
|| root.status !== "succeeded"
|
|
143
|
+
|| root.transport !== "linked-device-local-store"
|
|
144
|
+
) return fail("receipt identity is unsupported");
|
|
145
|
+
const startedAt = timestamp(root.startedAt, "receipt.startedAt")! as string;
|
|
146
|
+
const finishedAt = timestamp(root.finishedAt, "receipt.finishedAt")! as string;
|
|
147
|
+
if (finishedAt < startedAt) return fail("receipt timestamps are reversed");
|
|
148
|
+
const auth = record(root.auth, "receipt.auth");
|
|
149
|
+
exact(auth, ["id", "provider", "identitySha256"], "receipt.auth");
|
|
150
|
+
if (!isWhatsAppExportAuthId(auth.id)) {
|
|
151
|
+
return fail("receipt.auth.id is not a bounded lowercase auth coordinate");
|
|
152
|
+
}
|
|
153
|
+
const parsedAuthId = auth.id;
|
|
154
|
+
if (auth.provider !== "whatsapp") return fail("receipt auth identity is unsupported");
|
|
155
|
+
const identitySha256 = digest(auth.identitySha256, "receipt.auth.identitySha256");
|
|
156
|
+
const source = record(root.source, "receipt.source");
|
|
157
|
+
exact(source, ["id", "version"], "receipt.source");
|
|
158
|
+
const provider = record(root.provider, "receipt.provider");
|
|
159
|
+
exact(provider, ["id", "version"], "receipt.provider");
|
|
160
|
+
if (
|
|
161
|
+
source.id !== "wacli-local"
|
|
162
|
+
|| source.version !== "1.0.0"
|
|
163
|
+
|| provider.id !== "whatsapp"
|
|
164
|
+
|| provider.version !== "0.15.0"
|
|
165
|
+
) return fail("receipt producer identity is unsupported");
|
|
166
|
+
const completeness = record(root.completeness, "receipt.completeness");
|
|
167
|
+
exact(completeness, ["kind", "reason", "observedFrom", "observedThrough"], "receipt.completeness");
|
|
168
|
+
if (
|
|
169
|
+
completeness.kind !== "bounded-local"
|
|
170
|
+
|| completeness.reason !== "local-store-coverage-unknown"
|
|
171
|
+
) return fail("receipt completeness boundary is unsupported");
|
|
172
|
+
const observedFrom = timestamp(completeness.observedFrom, "receipt.completeness.observedFrom", true);
|
|
173
|
+
const observedThrough = timestamp(completeness.observedThrough, "receipt.completeness.observedThrough", true);
|
|
174
|
+
if (
|
|
175
|
+
(observedFrom === null) !== (observedThrough === null)
|
|
176
|
+
|| (observedFrom !== null && observedThrough !== null && observedThrough < observedFrom)
|
|
177
|
+
) {
|
|
178
|
+
return fail("receipt completeness timestamps are reversed");
|
|
179
|
+
}
|
|
180
|
+
const warnings = denseDataArray(root.warnings, "receipt.warnings", 5);
|
|
181
|
+
const warningOrder = [
|
|
182
|
+
"remote-history-incomplete",
|
|
183
|
+
"reaction-state-unproven",
|
|
184
|
+
"self-chat-excluded",
|
|
185
|
+
"message-payload-purged",
|
|
186
|
+
"non-conversation-chats-excluded",
|
|
187
|
+
] as const;
|
|
188
|
+
if (
|
|
189
|
+
warnings[0] !== warningOrder[0]
|
|
190
|
+
|| warnings.some((item) => typeof item !== "string" || !warningOrder.includes(
|
|
191
|
+
item as (typeof warningOrder)[number],
|
|
192
|
+
))
|
|
193
|
+
|| warnings.some((item, index) => index > 0
|
|
194
|
+
&& warningOrder.indexOf(item as (typeof warningOrder)[number])
|
|
195
|
+
<= warningOrder.indexOf(warnings[index - 1] as (typeof warningOrder)[number]))
|
|
196
|
+
) return fail("receipt warnings are unsupported");
|
|
197
|
+
type ReceiptWarning = WhatsAppMessageLikeMeExportReceipt["warnings"][number];
|
|
198
|
+
type ReceiptWarningTail = Exclude<ReceiptWarning, "remote-history-incomplete">;
|
|
199
|
+
const warningTail = warnings.slice(1).map((warning) => warning as ReceiptWarningTail);
|
|
200
|
+
const canonicalWarnings: WhatsAppMessageLikeMeExportReceipt["warnings"] = Object.freeze([
|
|
201
|
+
"remote-history-incomplete" as const,
|
|
202
|
+
...warningTail,
|
|
203
|
+
]);
|
|
204
|
+
const counts = record(root.counts, "receipt.counts");
|
|
205
|
+
const countKinds = ["account", "participant", "conversation", "message", "reaction", "tombstone"] as const;
|
|
206
|
+
exact(counts, countKinds, "receipt.counts");
|
|
207
|
+
const parsedCounts = Object.fromEntries(countKinds.map((kind) => [
|
|
208
|
+
kind,
|
|
209
|
+
nonNegative(counts[kind], `receipt.counts.${kind}`),
|
|
210
|
+
])) as Record<(typeof countKinds)[number], number>;
|
|
211
|
+
const observed = observedFrom !== null;
|
|
212
|
+
const totalRecords = countKinds.reduce((total, kind) => total + parsedCounts[kind], 0);
|
|
213
|
+
if (
|
|
214
|
+
parsedCounts.account !== 1
|
|
215
|
+
|| parsedCounts.participant < 1
|
|
216
|
+
|| parsedCounts.reaction !== 0
|
|
217
|
+
|| parsedCounts.tombstone !== 0
|
|
218
|
+
|| (parsedCounts.message > 0) !== observed
|
|
219
|
+
|| (parsedCounts.conversation > 0) !== observed
|
|
220
|
+
|| parsedCounts.conversation > parsedCounts.message
|
|
221
|
+
|| parsedCounts.message > MAX_V2_RECORDS
|
|
222
|
+
|| parsedCounts.participant > parsedCounts.message + 1
|
|
223
|
+
|| !Number.isSafeInteger(totalRecords)
|
|
224
|
+
|| totalRecords > MAX_V2_RECORDS
|
|
225
|
+
|| (warnings.includes("message-payload-purged") && parsedCounts.message === 0)
|
|
226
|
+
) return fail("receipt counts contradict the fixed producer");
|
|
227
|
+
const output = record(root.output, "receipt.output");
|
|
228
|
+
exact(output, ["schemaVersion", "format", "directory", "manifestSha256"], "receipt.output");
|
|
229
|
+
if (!isWhatsAppExportOutputDirectory(output.directory)) {
|
|
230
|
+
return fail("receipt.output.directory is not a bounded normalized non-root absolute directory");
|
|
231
|
+
}
|
|
232
|
+
const parsedOutputDirectory = output.directory;
|
|
233
|
+
if (
|
|
234
|
+
output.schemaVersion !== 2
|
|
235
|
+
|| output.format !== "message-like-me.local-message-bundle"
|
|
236
|
+
) return fail("receipt output identity is unsupported");
|
|
237
|
+
const manifestSha256 = digest(output.manifestSha256, "receipt.output.manifestSha256");
|
|
238
|
+
const privacy = record(root.privacy, "receipt.privacy");
|
|
239
|
+
exact(privacy, [
|
|
240
|
+
"classification", "attachments", "credentials", "sourcePaths", "mediaBytes", "cloudSync",
|
|
241
|
+
], "receipt.privacy");
|
|
242
|
+
if (
|
|
243
|
+
privacy.classification !== "private-local"
|
|
244
|
+
|| privacy.attachments !== "metadata-only"
|
|
245
|
+
|| privacy.credentials !== "excluded"
|
|
246
|
+
|| privacy.sourcePaths !== "excluded"
|
|
247
|
+
|| privacy.mediaBytes !== "excluded"
|
|
248
|
+
|| privacy.cloudSync !== "none"
|
|
249
|
+
) return fail("receipt privacy boundary is unsupported");
|
|
250
|
+
const integrity = record(root.integrity, "receipt.integrity");
|
|
251
|
+
exact(integrity, ["algorithm", "receiptSha256"], "receipt.integrity");
|
|
252
|
+
if (integrity.algorithm !== "sha256") return fail("receipt integrity algorithm is unsupported");
|
|
253
|
+
const expectedDigest = digest(integrity.receiptSha256, "receipt.integrity.receiptSha256");
|
|
254
|
+
const projection = Object.freeze({
|
|
255
|
+
schemaVersion: 1 as const,
|
|
256
|
+
format: "wrench.whatsapp-message-like-me-export-receipt" as const,
|
|
257
|
+
runId: root.runId as string,
|
|
258
|
+
operation: "whatsapp.export-message-like-me" as const,
|
|
259
|
+
status: "succeeded" as const,
|
|
260
|
+
transport: "linked-device-local-store" as const,
|
|
261
|
+
startedAt,
|
|
262
|
+
finishedAt,
|
|
263
|
+
auth: Object.freeze({
|
|
264
|
+
id: parsedAuthId,
|
|
265
|
+
provider: "whatsapp" as const,
|
|
266
|
+
identitySha256,
|
|
267
|
+
}),
|
|
268
|
+
source: Object.freeze({ id: "wacli-local" as const, version: "1.0.0" as const }),
|
|
269
|
+
provider: Object.freeze({ id: "whatsapp" as const, version: "0.15.0" as const }),
|
|
270
|
+
completeness: Object.freeze({
|
|
271
|
+
kind: "bounded-local" as const,
|
|
272
|
+
reason: "local-store-coverage-unknown" as const,
|
|
273
|
+
observedFrom,
|
|
274
|
+
observedThrough,
|
|
275
|
+
}),
|
|
276
|
+
warnings: canonicalWarnings,
|
|
277
|
+
counts: Object.freeze({
|
|
278
|
+
account: 1 as const,
|
|
279
|
+
participant: parsedCounts.participant,
|
|
280
|
+
conversation: parsedCounts.conversation,
|
|
281
|
+
message: parsedCounts.message,
|
|
282
|
+
reaction: 0 as const,
|
|
283
|
+
tombstone: 0 as const,
|
|
284
|
+
}),
|
|
285
|
+
output: Object.freeze({
|
|
286
|
+
schemaVersion: 2 as const,
|
|
287
|
+
format: "message-like-me.local-message-bundle" as const,
|
|
288
|
+
directory: parsedOutputDirectory,
|
|
289
|
+
manifestSha256,
|
|
290
|
+
}),
|
|
291
|
+
privacy: Object.freeze({
|
|
292
|
+
classification: "private-local" as const,
|
|
293
|
+
attachments: "metadata-only" as const,
|
|
294
|
+
credentials: "excluded" as const,
|
|
295
|
+
sourcePaths: "excluded" as const,
|
|
296
|
+
mediaBytes: "excluded" as const,
|
|
297
|
+
cloudSync: "none" as const,
|
|
298
|
+
}),
|
|
299
|
+
});
|
|
300
|
+
if (sha256(canonicalJson(projection)) !== expectedDigest) {
|
|
301
|
+
return fail("receipt digest does not match its canonical projection");
|
|
302
|
+
}
|
|
303
|
+
return Object.freeze({
|
|
304
|
+
...projection,
|
|
305
|
+
integrity: Object.freeze({ algorithm: "sha256" as const, receiptSha256: expectedDigest }),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function cliSourcePath(): string {
|
|
310
|
+
const besideSource = fileURLToPath(new URL("./cli.ts", import.meta.url));
|
|
311
|
+
if (existsSync(besideSource)) return besideSource;
|
|
312
|
+
const packagedSource = fileURLToPath(new URL("../src/cli.ts", import.meta.url));
|
|
313
|
+
if (existsSync(packagedSource)) return packagedSource;
|
|
314
|
+
return fail("the installed Ghostget CLI source is unavailable");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function preparedEnvironment(
|
|
318
|
+
additions: WhatsAppMessageLikeMeClientOptions["environment"],
|
|
319
|
+
): Readonly<Record<string, string>> {
|
|
320
|
+
const environment = Object.create(null) as Record<string, string>;
|
|
321
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
322
|
+
if (typeof value === "string") environment[key] = value;
|
|
323
|
+
}
|
|
324
|
+
if (additions === undefined) return Object.freeze(environment);
|
|
325
|
+
const source = record(additions, "options.environment");
|
|
326
|
+
for (const [key, value] of Object.entries(source)) {
|
|
327
|
+
if (key.length < 1 || key.includes("=") || key.includes("\0")) {
|
|
328
|
+
return fail("environment name is malformed");
|
|
329
|
+
}
|
|
330
|
+
if (value === undefined) delete environment[key];
|
|
331
|
+
else if (typeof value !== "string" || value.includes("\0")) {
|
|
332
|
+
return fail("environment value is malformed");
|
|
333
|
+
} else environment[key] = value;
|
|
334
|
+
}
|
|
335
|
+
return Object.freeze(environment);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function exportWhatsAppMessageLikeMeSync(
|
|
339
|
+
requestValue: WhatsAppMessageLikeMeClientRequest,
|
|
340
|
+
optionsValue: WhatsAppMessageLikeMeClientOptions = {},
|
|
341
|
+
): WhatsAppMessageLikeMeExportReceipt {
|
|
342
|
+
if (typeof process.versions.bun !== "string") {
|
|
343
|
+
return fail("@hraness/ghostget/whatsapp requires Bun");
|
|
344
|
+
}
|
|
345
|
+
const request = record(requestValue, "request");
|
|
346
|
+
exact(request, ["authId", "output"], "request");
|
|
347
|
+
if (!isWhatsAppExportAuthId(request.authId)) {
|
|
348
|
+
return fail("request.authId is not a bounded lowercase auth coordinate");
|
|
349
|
+
}
|
|
350
|
+
if (!isWhatsAppExportOutputDirectory(request.output)) {
|
|
351
|
+
return fail("request.output is not a bounded normalized non-root absolute directory");
|
|
352
|
+
}
|
|
353
|
+
const requestedAuthId = request.authId;
|
|
354
|
+
const requestedOutput = request.output;
|
|
355
|
+
const requested = Object.freeze({
|
|
356
|
+
authId: requestedAuthId,
|
|
357
|
+
output: requestedOutput,
|
|
358
|
+
});
|
|
359
|
+
const options = record(optionsValue, "options");
|
|
360
|
+
exact(options, Object.hasOwn(options, "environment") ? ["environment"] : [], "options");
|
|
361
|
+
const result = spawnSync(process.execPath, [
|
|
362
|
+
cliSourcePath(),
|
|
363
|
+
"whatsapp",
|
|
364
|
+
"export-message-like-me",
|
|
365
|
+
"--auth",
|
|
366
|
+
requestedAuthId,
|
|
367
|
+
"--output",
|
|
368
|
+
requestedOutput,
|
|
369
|
+
"--json",
|
|
370
|
+
], {
|
|
371
|
+
cwd: process.cwd(),
|
|
372
|
+
env: preparedEnvironment(options.environment as WhatsAppMessageLikeMeClientOptions["environment"]),
|
|
373
|
+
encoding: "utf8",
|
|
374
|
+
maxBuffer: MAX_STDOUT_BYTES,
|
|
375
|
+
timeout: PROCESS_TIMEOUT_MS,
|
|
376
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
377
|
+
});
|
|
378
|
+
if (result.error !== undefined) return fail("installed CLI could not complete the export");
|
|
379
|
+
if (result.status !== 0 || result.signal !== null) {
|
|
380
|
+
const errorText = typeof result.stderr === "string"
|
|
381
|
+
? Buffer.from(result.stderr, "utf8").subarray(0, MAX_STDERR_BYTES).toString("utf8").trim()
|
|
382
|
+
: "";
|
|
383
|
+
return fail(errorText.length > 0 ? errorText : "installed CLI rejected the export");
|
|
384
|
+
}
|
|
385
|
+
let raw: unknown;
|
|
386
|
+
try {
|
|
387
|
+
raw = JSON.parse(result.stdout.trim()) as unknown;
|
|
388
|
+
} catch {
|
|
389
|
+
return fail("installed CLI returned malformed JSON");
|
|
390
|
+
}
|
|
391
|
+
return requireWhatsAppMessageLikeMeReceiptRequestBinding(
|
|
392
|
+
parseWhatsAppMessageLikeMeExportReceipt(raw),
|
|
393
|
+
requested,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
2
|
+
|
|
3
|
+
const WHATSAPP_EXPORT_AUTH_ID_PATTERN = /^[a-z][a-z0-9-]{0,47}$/u;
|
|
4
|
+
const MAX_WHATSAPP_EXPORT_OUTPUT_BYTES = 4_096;
|
|
5
|
+
|
|
6
|
+
export function isWhatsAppExportAuthId(value: unknown): value is string {
|
|
7
|
+
return typeof value === "string" && WHATSAPP_EXPORT_AUTH_ID_PATTERN.test(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isWhatsAppExportOutputDirectory(value: unknown): value is string {
|
|
11
|
+
return typeof value === "string"
|
|
12
|
+
&& isAbsolute(value)
|
|
13
|
+
&& resolve(value) === value
|
|
14
|
+
&& dirname(value) !== value
|
|
15
|
+
&& Buffer.byteLength(value, "utf8") <= MAX_WHATSAPP_EXPORT_OUTPUT_BYTES
|
|
16
|
+
&& !/[\0\r\n]/u.test(value);
|
|
17
|
+
}
|