@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,417 @@
|
|
|
1
|
+
import { canonicalJson } from "./canonical-json";
|
|
2
|
+
|
|
3
|
+
export const ARTICLE_DRAFT_DOCUMENT_SCHEMA_VERSION = 1;
|
|
4
|
+
export const ARTICLE_DRAFT_DOCUMENT_IMAGE_SCHEMA_VERSION = 2;
|
|
5
|
+
export const MAX_ARTICLE_DRAFT_DOCUMENT_BYTES = 512 * 1024;
|
|
6
|
+
export const MAX_ARTICLE_DRAFT_BLOCKS = 5_000;
|
|
7
|
+
export const MAX_ARTICLE_DRAFT_CHARACTERS = 125_000;
|
|
8
|
+
|
|
9
|
+
export type ArticleDraftTextBlockType =
|
|
10
|
+
| "paragraph"
|
|
11
|
+
| "heading1"
|
|
12
|
+
| "heading2"
|
|
13
|
+
| "blockquote"
|
|
14
|
+
| "unordered-list-item"
|
|
15
|
+
| "ordered-list-item";
|
|
16
|
+
|
|
17
|
+
export type ArticleDraftLinkRange = {
|
|
18
|
+
readonly offset: number;
|
|
19
|
+
readonly length: number;
|
|
20
|
+
readonly url: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ArticleDraftStyleRange = {
|
|
24
|
+
readonly offset: number;
|
|
25
|
+
readonly length: number;
|
|
26
|
+
readonly style: "bold" | "italic" | "strikethrough";
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const articleDraftStyleOrder = Object.freeze({
|
|
30
|
+
bold: 0,
|
|
31
|
+
italic: 1,
|
|
32
|
+
strikethrough: 2,
|
|
33
|
+
} as const satisfies Readonly<Record<ArticleDraftStyleRange["style"], number>>);
|
|
34
|
+
|
|
35
|
+
export type ArticleDraftTextBlock = {
|
|
36
|
+
readonly type: ArticleDraftTextBlockType;
|
|
37
|
+
readonly text: string;
|
|
38
|
+
readonly links: readonly ArticleDraftLinkRange[];
|
|
39
|
+
readonly styles: readonly ArticleDraftStyleRange[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ArticleDraftDocument = {
|
|
43
|
+
readonly schemaVersion: typeof ARTICLE_DRAFT_DOCUMENT_SCHEMA_VERSION;
|
|
44
|
+
readonly blocks: readonly ArticleDraftTextBlock[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ArticleDraftImageBlock = {
|
|
48
|
+
readonly type: "image";
|
|
49
|
+
readonly imageIndex: number;
|
|
50
|
+
readonly altText?: string;
|
|
51
|
+
readonly caption?: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type ArticleDraftDocumentV2 = {
|
|
55
|
+
readonly schemaVersion: typeof ARTICLE_DRAFT_DOCUMENT_IMAGE_SCHEMA_VERSION;
|
|
56
|
+
readonly blocks: readonly (ArticleDraftTextBlock | ArticleDraftImageBlock)[];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type ArticleDraftDocumentLimits = {
|
|
60
|
+
readonly maximumBlocks: number;
|
|
61
|
+
readonly maximumCharacters: number;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type ArticleDraftDocumentV2Limits = ArticleDraftDocumentLimits & {
|
|
65
|
+
readonly maximumImages: number;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const textBlockTypes = new Set<ArticleDraftTextBlockType>([
|
|
69
|
+
"paragraph",
|
|
70
|
+
"heading1",
|
|
71
|
+
"heading2",
|
|
72
|
+
"blockquote",
|
|
73
|
+
"unordered-list-item",
|
|
74
|
+
"ordered-list-item",
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
78
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function record(value: unknown, label: string): Record<string, unknown> {
|
|
82
|
+
if (!isRecord(value)) throw new Error(`${label} must be an object`);
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function exactKeys(value: Readonly<Record<string, unknown>>, keys: readonly string[], label: string): void {
|
|
87
|
+
const actual = Object.keys(value).sort().join(",");
|
|
88
|
+
const expected = [...keys].sort().join(",");
|
|
89
|
+
if (actual !== expected) throw new Error(`${label} must contain exactly ${expected || "no fields"}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function boundedHttpsUrl(value: unknown, label: string): string {
|
|
93
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 8_192 || /[\0\r\n]/u.test(value)) {
|
|
94
|
+
throw new Error(`${label} must be a bounded absolute HTTPS URL`);
|
|
95
|
+
}
|
|
96
|
+
let parsed: URL;
|
|
97
|
+
try {
|
|
98
|
+
parsed = new URL(value);
|
|
99
|
+
} catch {
|
|
100
|
+
throw new Error(`${label} must be a bounded absolute HTTPS URL`);
|
|
101
|
+
}
|
|
102
|
+
if (
|
|
103
|
+
parsed.protocol !== "https:"
|
|
104
|
+
|| parsed.username !== ""
|
|
105
|
+
|| parsed.password !== ""
|
|
106
|
+
|| parsed.href !== value
|
|
107
|
+
) throw new Error(`${label} must be one canonical absolute HTTPS URL`);
|
|
108
|
+
return parsed.href;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function validUtf16Boundary(text: string, offset: number): boolean {
|
|
112
|
+
if (offset <= 0 || offset >= text.length) return true;
|
|
113
|
+
const previous = text.charCodeAt(offset - 1);
|
|
114
|
+
const next = text.charCodeAt(offset);
|
|
115
|
+
return !(previous >= 0xd800 && previous <= 0xdbff && next >= 0xdc00 && next <= 0xdfff);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function boundedRange(
|
|
119
|
+
value: Readonly<Record<string, unknown>>,
|
|
120
|
+
text: string,
|
|
121
|
+
label: string,
|
|
122
|
+
): { readonly offset: number; readonly length: number } {
|
|
123
|
+
const offset = value.offset;
|
|
124
|
+
const length = value.length;
|
|
125
|
+
if (
|
|
126
|
+
!Number.isSafeInteger(offset)
|
|
127
|
+
|| !Number.isSafeInteger(length)
|
|
128
|
+
|| (offset as number) < 0
|
|
129
|
+
|| (length as number) < 1
|
|
130
|
+
|| (offset as number) + (length as number) > text.length
|
|
131
|
+
|| !validUtf16Boundary(text, offset as number)
|
|
132
|
+
|| !validUtf16Boundary(text, (offset as number) + (length as number))
|
|
133
|
+
) throw new Error(`${label} must stay on UTF-16 boundaries inside its text`);
|
|
134
|
+
return Object.freeze({ offset: offset as number, length: length as number });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function checkedLimits(value: ArticleDraftDocumentLimits): ArticleDraftDocumentLimits {
|
|
138
|
+
if (
|
|
139
|
+
!Number.isSafeInteger(value.maximumBlocks)
|
|
140
|
+
|| value.maximumBlocks < 1
|
|
141
|
+
|| value.maximumBlocks > MAX_ARTICLE_DRAFT_BLOCKS
|
|
142
|
+
|| !Number.isSafeInteger(value.maximumCharacters)
|
|
143
|
+
|| value.maximumCharacters < 1
|
|
144
|
+
|| value.maximumCharacters > MAX_ARTICLE_DRAFT_CHARACTERS
|
|
145
|
+
) throw new Error("Article draft document limits are invalid");
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function compareStyleRanges(left: ArticleDraftStyleRange, right: ArticleDraftStyleRange): number {
|
|
150
|
+
return left.offset - right.offset
|
|
151
|
+
|| left.length - right.length
|
|
152
|
+
|| articleDraftStyleOrder[left.style] - articleDraftStyleOrder[right.style];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Parse one canonical, provider-neutral rich-text article draft document. */
|
|
156
|
+
export function parseArticleDraftDocument(
|
|
157
|
+
value: unknown,
|
|
158
|
+
limitsValue: ArticleDraftDocumentLimits,
|
|
159
|
+
): ArticleDraftDocument {
|
|
160
|
+
const limits = checkedLimits(limitsValue);
|
|
161
|
+
if (
|
|
162
|
+
typeof value !== "string"
|
|
163
|
+
|| value.length < 1
|
|
164
|
+
|| Buffer.byteLength(value, "utf8") > MAX_ARTICLE_DRAFT_DOCUMENT_BYTES
|
|
165
|
+
|| value.includes("\0")
|
|
166
|
+
) throw new Error("input.document must be bounded canonical ArticleDraftDocument JSON");
|
|
167
|
+
|
|
168
|
+
let parsed: unknown;
|
|
169
|
+
try {
|
|
170
|
+
parsed = JSON.parse(value) as unknown;
|
|
171
|
+
} catch {
|
|
172
|
+
throw new Error("input.document must be valid ArticleDraftDocument JSON");
|
|
173
|
+
}
|
|
174
|
+
if (canonicalJson(parsed) !== value) {
|
|
175
|
+
throw new Error("input.document must use canonical JSON encoding");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const root = record(parsed, "input.document");
|
|
179
|
+
exactKeys(root, ["schemaVersion", "blocks"], "input.document");
|
|
180
|
+
if (root.schemaVersion !== ARTICLE_DRAFT_DOCUMENT_SCHEMA_VERSION || !Array.isArray(root.blocks)) {
|
|
181
|
+
throw new Error(`input.document must use ArticleDraftDocument schemaVersion ${ARTICLE_DRAFT_DOCUMENT_SCHEMA_VERSION}`);
|
|
182
|
+
}
|
|
183
|
+
if (root.blocks.length < 1 || root.blocks.length > limits.maximumBlocks) {
|
|
184
|
+
throw new Error(`input.document must contain 1-${limits.maximumBlocks} blocks`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const blocks: ArticleDraftTextBlock[] = [];
|
|
188
|
+
let totalCharacters = 0;
|
|
189
|
+
for (const [index, rawBlock] of root.blocks.entries()) {
|
|
190
|
+
const label = `input.document.blocks[${index}]`;
|
|
191
|
+
const block = record(rawBlock, label);
|
|
192
|
+
const allowed = [
|
|
193
|
+
"type",
|
|
194
|
+
"text",
|
|
195
|
+
...(block.links === undefined ? [] : ["links"]),
|
|
196
|
+
...(block.styles === undefined ? [] : ["styles"]),
|
|
197
|
+
];
|
|
198
|
+
exactKeys(block, allowed, label);
|
|
199
|
+
if (typeof block.type !== "string" || !textBlockTypes.has(block.type as ArticleDraftTextBlockType)) {
|
|
200
|
+
throw new Error(`${label}.type is outside ArticleDraftDocument schemaVersion 1`);
|
|
201
|
+
}
|
|
202
|
+
if (typeof block.text !== "string" || /[\0\r\n]/u.test(block.text)) {
|
|
203
|
+
throw new Error(`${label}.text must be one bounded line`);
|
|
204
|
+
}
|
|
205
|
+
totalCharacters += block.text.length;
|
|
206
|
+
if (totalCharacters > limits.maximumCharacters) {
|
|
207
|
+
throw new Error(`input.document text must contain at most ${limits.maximumCharacters} UTF-16 code units`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const rawLinks = block.links ?? [];
|
|
211
|
+
const rawStyles = block.styles ?? [];
|
|
212
|
+
if (
|
|
213
|
+
!Array.isArray(rawLinks)
|
|
214
|
+
|| rawLinks.length > 500
|
|
215
|
+
|| !Array.isArray(rawStyles)
|
|
216
|
+
|| rawStyles.length > 500
|
|
217
|
+
) throw new Error(`${label} ranges exceeded their bounds`);
|
|
218
|
+
|
|
219
|
+
const links = rawLinks.map((rawLink, rangeIndex) => {
|
|
220
|
+
const rangeLabel = `${label}.links[${rangeIndex}]`;
|
|
221
|
+
const link = record(rawLink, rangeLabel);
|
|
222
|
+
exactKeys(link, ["offset", "length", "url"], rangeLabel);
|
|
223
|
+
const range = boundedRange(link, block.text as string, rangeLabel);
|
|
224
|
+
return Object.freeze({ ...range, url: boundedHttpsUrl(link.url, `${rangeLabel}.url`) });
|
|
225
|
+
});
|
|
226
|
+
let linkEnd = 0;
|
|
227
|
+
for (const link of links) {
|
|
228
|
+
if (link.offset < linkEnd) throw new Error(`${label}.links must be ordered and non-overlapping`);
|
|
229
|
+
linkEnd = link.offset + link.length;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const styles = rawStyles.map((rawStyle, rangeIndex) => {
|
|
233
|
+
const rangeLabel = `${label}.styles[${rangeIndex}]`;
|
|
234
|
+
const style = record(rawStyle, rangeLabel);
|
|
235
|
+
exactKeys(style, ["offset", "length", "style"], rangeLabel);
|
|
236
|
+
const range = boundedRange(style, block.text as string, rangeLabel);
|
|
237
|
+
if (style.style !== "bold" && style.style !== "italic" && style.style !== "strikethrough") {
|
|
238
|
+
throw new Error(`${rangeLabel}.style is outside ArticleDraftDocument schemaVersion 1`);
|
|
239
|
+
}
|
|
240
|
+
return Object.freeze({ ...range, style: style.style });
|
|
241
|
+
});
|
|
242
|
+
for (let rangeIndex = 1; rangeIndex < styles.length; rangeIndex += 1) {
|
|
243
|
+
const previous = styles[rangeIndex - 1] as ArticleDraftStyleRange;
|
|
244
|
+
const current = styles[rangeIndex] as ArticleDraftStyleRange;
|
|
245
|
+
if (compareStyleRanges(previous, current) > 0) {
|
|
246
|
+
throw new Error(`${label}.styles must be ordered by offset, then length, then style`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const styleEnds: Partial<Record<ArticleDraftStyleRange["style"], number>> = {};
|
|
250
|
+
for (const style of styles) {
|
|
251
|
+
const previousEnd = styleEnds[style.style];
|
|
252
|
+
if (previousEnd !== undefined && style.offset < previousEnd) {
|
|
253
|
+
throw new Error(`${label}.styles must not contain duplicate or overlapping same-style ranges`);
|
|
254
|
+
}
|
|
255
|
+
styleEnds[style.style] = style.offset + style.length;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
blocks.push(Object.freeze({
|
|
259
|
+
type: block.type as ArticleDraftTextBlockType,
|
|
260
|
+
text: block.text,
|
|
261
|
+
links: Object.freeze(links),
|
|
262
|
+
styles: Object.freeze(styles),
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
if (totalCharacters < 1) throw new Error("input.document must contain article text");
|
|
266
|
+
return Object.freeze({
|
|
267
|
+
schemaVersion: ARTICLE_DRAFT_DOCUMENT_SCHEMA_VERSION,
|
|
268
|
+
blocks: Object.freeze(blocks),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Project strict parser failures into a provider-plugin validation hook. */
|
|
273
|
+
export function articleDraftDocumentIssues(
|
|
274
|
+
value: unknown,
|
|
275
|
+
limits: ArticleDraftDocumentLimits,
|
|
276
|
+
): readonly string[] {
|
|
277
|
+
try {
|
|
278
|
+
parseArticleDraftDocument(value, limits);
|
|
279
|
+
return Object.freeze([]);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return Object.freeze([
|
|
282
|
+
error instanceof Error ? error.message : "input.document is invalid",
|
|
283
|
+
]);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function checkedImageLimits(value: ArticleDraftDocumentV2Limits): ArticleDraftDocumentV2Limits {
|
|
288
|
+
const text = checkedLimits(value);
|
|
289
|
+
if (
|
|
290
|
+
!Number.isSafeInteger(value.maximumImages)
|
|
291
|
+
|| value.maximumImages < 1
|
|
292
|
+
|| value.maximumImages > 100
|
|
293
|
+
) throw new Error("Article draft image limits are invalid");
|
|
294
|
+
return Object.freeze({ ...text, maximumImages: value.maximumImages });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function optionalImageText(
|
|
298
|
+
value: unknown,
|
|
299
|
+
label: string,
|
|
300
|
+
maximum: number,
|
|
301
|
+
): string | undefined {
|
|
302
|
+
if (value === undefined) return undefined;
|
|
303
|
+
if (
|
|
304
|
+
typeof value !== "string"
|
|
305
|
+
|| value.length < 1
|
|
306
|
+
|| value.length > maximum
|
|
307
|
+
|| /[\0\r]/u.test(value)
|
|
308
|
+
) throw new Error(`${label} must be bounded text`);
|
|
309
|
+
return value;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Parse the canonical mixed text-and-inline-image ArticleDraftDocument v2. */
|
|
313
|
+
export function parseArticleDraftDocumentV2(
|
|
314
|
+
value: unknown,
|
|
315
|
+
limitsValue: ArticleDraftDocumentV2Limits,
|
|
316
|
+
): ArticleDraftDocumentV2 {
|
|
317
|
+
const limits = checkedImageLimits(limitsValue);
|
|
318
|
+
if (
|
|
319
|
+
typeof value !== "string"
|
|
320
|
+
|| value.length < 1
|
|
321
|
+
|| Buffer.byteLength(value, "utf8") > MAX_ARTICLE_DRAFT_DOCUMENT_BYTES
|
|
322
|
+
|| value.includes("\0")
|
|
323
|
+
) throw new Error("input.document must be bounded canonical ArticleDraftDocument JSON");
|
|
324
|
+
|
|
325
|
+
let parsed: unknown;
|
|
326
|
+
try {
|
|
327
|
+
parsed = JSON.parse(value) as unknown;
|
|
328
|
+
} catch {
|
|
329
|
+
throw new Error("input.document must be valid ArticleDraftDocument JSON");
|
|
330
|
+
}
|
|
331
|
+
if (canonicalJson(parsed) !== value) {
|
|
332
|
+
throw new Error("input.document must use canonical JSON encoding");
|
|
333
|
+
}
|
|
334
|
+
const root = record(parsed, "input.document");
|
|
335
|
+
exactKeys(root, ["schemaVersion", "blocks"], "input.document");
|
|
336
|
+
if (
|
|
337
|
+
root.schemaVersion !== ARTICLE_DRAFT_DOCUMENT_IMAGE_SCHEMA_VERSION
|
|
338
|
+
|| !Array.isArray(root.blocks)
|
|
339
|
+
) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
`input.document must use ArticleDraftDocument schemaVersion ${ARTICLE_DRAFT_DOCUMENT_IMAGE_SCHEMA_VERSION}`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (root.blocks.length < 1 || root.blocks.length > limits.maximumBlocks) {
|
|
345
|
+
throw new Error(`input.document must contain 1-${limits.maximumBlocks} blocks`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const blocks: (ArticleDraftTextBlock | ArticleDraftImageBlock)[] = [];
|
|
349
|
+
const imageIndexes = new Set<number>();
|
|
350
|
+
let totalCharacters = 0;
|
|
351
|
+
for (const [index, rawBlock] of root.blocks.entries()) {
|
|
352
|
+
const label = `input.document.blocks[${index}]`;
|
|
353
|
+
const block = record(rawBlock, label);
|
|
354
|
+
if (block.type === "image") {
|
|
355
|
+
exactKeys(block, [
|
|
356
|
+
"type",
|
|
357
|
+
"imageIndex",
|
|
358
|
+
...(block.altText === undefined ? [] : ["altText"]),
|
|
359
|
+
...(block.caption === undefined ? [] : ["caption"]),
|
|
360
|
+
], label);
|
|
361
|
+
if (
|
|
362
|
+
!Number.isSafeInteger(block.imageIndex)
|
|
363
|
+
|| (block.imageIndex as number) < 0
|
|
364
|
+
|| (block.imageIndex as number) >= limits.maximumImages
|
|
365
|
+
|| imageIndexes.has(block.imageIndex as number)
|
|
366
|
+
) throw new Error(`${label}.imageIndex must be a unique bounded zero-based image index`);
|
|
367
|
+
const altText = optionalImageText(block.altText, `${label}.altText`, 1_000);
|
|
368
|
+
const caption = optionalImageText(block.caption, `${label}.caption`, 1_000);
|
|
369
|
+
imageIndexes.add(block.imageIndex as number);
|
|
370
|
+
blocks.push(Object.freeze({
|
|
371
|
+
type: "image",
|
|
372
|
+
imageIndex: block.imageIndex as number,
|
|
373
|
+
...(altText === undefined ? {} : { altText }),
|
|
374
|
+
...(caption === undefined ? {} : { caption }),
|
|
375
|
+
}));
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const parsedText = parseArticleDraftDocument(canonicalJson({
|
|
379
|
+
schemaVersion: ARTICLE_DRAFT_DOCUMENT_SCHEMA_VERSION,
|
|
380
|
+
blocks: [rawBlock],
|
|
381
|
+
}), {
|
|
382
|
+
maximumBlocks: 1,
|
|
383
|
+
maximumCharacters: limits.maximumCharacters,
|
|
384
|
+
}).blocks[0];
|
|
385
|
+
if (parsedText === undefined) throw new Error(`${label} was omitted after validation`);
|
|
386
|
+
totalCharacters += parsedText.text.length;
|
|
387
|
+
if (totalCharacters > limits.maximumCharacters) {
|
|
388
|
+
throw new Error(
|
|
389
|
+
`input.document text must contain at most ${limits.maximumCharacters} UTF-16 code units`,
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
blocks.push(parsedText);
|
|
393
|
+
}
|
|
394
|
+
if (totalCharacters < 1) throw new Error("input.document must contain article text");
|
|
395
|
+
const orderedIndexes = [...imageIndexes].sort((left, right) => left - right);
|
|
396
|
+
if (orderedIndexes.some((value, index) => value !== index)) {
|
|
397
|
+
throw new Error("input.document imageIndex values must be contiguous from zero");
|
|
398
|
+
}
|
|
399
|
+
return Object.freeze({
|
|
400
|
+
schemaVersion: ARTICLE_DRAFT_DOCUMENT_IMAGE_SCHEMA_VERSION,
|
|
401
|
+
blocks: Object.freeze(blocks),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function articleDraftDocumentV2Issues(
|
|
406
|
+
value: unknown,
|
|
407
|
+
limits: ArticleDraftDocumentV2Limits,
|
|
408
|
+
): readonly string[] {
|
|
409
|
+
try {
|
|
410
|
+
parseArticleDraftDocumentV2(value, limits);
|
|
411
|
+
return Object.freeze([]);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
return Object.freeze([
|
|
414
|
+
error instanceof Error ? error.message : "input.document is invalid",
|
|
415
|
+
]);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { ArticleDraftTextBlock } from "./article-draft-document";
|
|
2
|
+
|
|
3
|
+
export const MAX_X_STATUS_ARTICLE_EMBED_CHARACTERS = 25_000;
|
|
4
|
+
|
|
5
|
+
export type ArticleDraftEmbedTarget = "x-web" | "linkedin-web";
|
|
6
|
+
|
|
7
|
+
export type XStatusArticleEmbed = {
|
|
8
|
+
readonly text: string;
|
|
9
|
+
readonly url: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function exactEmbed(value: unknown): XStatusArticleEmbed {
|
|
17
|
+
if (!isRecord(value)) {
|
|
18
|
+
throw new Error("X status Article embed must contain exactly text,url");
|
|
19
|
+
}
|
|
20
|
+
const keys = Reflect.ownKeys(value);
|
|
21
|
+
if (
|
|
22
|
+
keys.some((key) => typeof key !== "string")
|
|
23
|
+
|| (keys as string[]).sort().join(",") !== "text,url"
|
|
24
|
+
) {
|
|
25
|
+
throw new Error("X status Article embed must contain exactly text,url");
|
|
26
|
+
}
|
|
27
|
+
const textDescriptor = Object.getOwnPropertyDescriptor(value, "text");
|
|
28
|
+
const urlDescriptor = Object.getOwnPropertyDescriptor(value, "url");
|
|
29
|
+
if (
|
|
30
|
+
textDescriptor === undefined
|
|
31
|
+
|| urlDescriptor === undefined
|
|
32
|
+
|| !("value" in textDescriptor)
|
|
33
|
+
|| !("value" in urlDescriptor)
|
|
34
|
+
|| !textDescriptor.enumerable
|
|
35
|
+
|| !urlDescriptor.enumerable
|
|
36
|
+
) throw new Error("X status Article embed must contain plain text,url values");
|
|
37
|
+
const text = textDescriptor.value as unknown;
|
|
38
|
+
const url = urlDescriptor.value as unknown;
|
|
39
|
+
if (
|
|
40
|
+
typeof text !== "string"
|
|
41
|
+
|| text.length < 1
|
|
42
|
+
|| text.length > MAX_X_STATUS_ARTICLE_EMBED_CHARACTERS
|
|
43
|
+
|| text.includes("\0")
|
|
44
|
+
|| text.trim().length < 1
|
|
45
|
+
) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`X status Article embed text must contain 1-${MAX_X_STATUS_ARTICLE_EMBED_CHARACTERS} characters`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (
|
|
51
|
+
typeof url !== "string"
|
|
52
|
+
|| url.length < 1
|
|
53
|
+
|| url.length > 8_192
|
|
54
|
+
|| /[\0\r\n]/u.test(url)
|
|
55
|
+
) {
|
|
56
|
+
throw new Error("X status Article embed URL must be one bounded X status URL");
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze({ text, url });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function canonicalXStatusUrl(value: string): string {
|
|
62
|
+
let url: URL;
|
|
63
|
+
try {
|
|
64
|
+
url = new URL(value);
|
|
65
|
+
} catch {
|
|
66
|
+
throw new Error("X status Article embed URL must be one bounded X status URL");
|
|
67
|
+
}
|
|
68
|
+
const hosts = new Set(["x.com", "www.x.com", "twitter.com", "www.twitter.com"]);
|
|
69
|
+
const match = /^\/([A-Za-z0-9_]{1,15})\/status\/([0-9]{1,32})\/?$/u.exec(url.pathname);
|
|
70
|
+
if (
|
|
71
|
+
url.protocol !== "https:"
|
|
72
|
+
|| url.username !== ""
|
|
73
|
+
|| url.password !== ""
|
|
74
|
+
|| url.port !== ""
|
|
75
|
+
|| !hosts.has(url.hostname)
|
|
76
|
+
|| match === null
|
|
77
|
+
) throw new Error("X status Article embed URL must be one bounded X status URL");
|
|
78
|
+
return `https://x.com/${match[1]}/status/${match[2]}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function textBlock(type: "blockquote" | "paragraph", text: string): ArticleDraftTextBlock {
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
type,
|
|
84
|
+
text,
|
|
85
|
+
links: Object.freeze([]),
|
|
86
|
+
styles: Object.freeze([]),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Project one source X status as quoted text followed by its canonical native
|
|
92
|
+
* link. This is an editorial fallback, not a claim that the destination
|
|
93
|
+
* provider created a proprietary embed card.
|
|
94
|
+
*/
|
|
95
|
+
export function projectXStatusArticleEmbed(
|
|
96
|
+
value: unknown,
|
|
97
|
+
target: unknown,
|
|
98
|
+
): readonly ArticleDraftTextBlock[] {
|
|
99
|
+
const embed = exactEmbed(value);
|
|
100
|
+
if (target !== "x-web" && target !== "linkedin-web") {
|
|
101
|
+
throw new Error("X status Article embed target must be x-web or linkedin-web");
|
|
102
|
+
}
|
|
103
|
+
const canonicalUrl = canonicalXStatusUrl(embed.url);
|
|
104
|
+
const quoteBlocks = embed.text
|
|
105
|
+
.replaceAll("\r\n", "\n")
|
|
106
|
+
.replaceAll("\r", "\n")
|
|
107
|
+
.split("\n")
|
|
108
|
+
.filter((line) => line.length > 0)
|
|
109
|
+
.map((line) => textBlock("blockquote", line));
|
|
110
|
+
return Object.freeze([
|
|
111
|
+
...quoteBlocks,
|
|
112
|
+
Object.freeze({
|
|
113
|
+
type: "paragraph" as const,
|
|
114
|
+
text: canonicalUrl,
|
|
115
|
+
links: Object.freeze([Object.freeze({
|
|
116
|
+
offset: 0,
|
|
117
|
+
length: canonicalUrl.length,
|
|
118
|
+
url: canonicalUrl,
|
|
119
|
+
})]),
|
|
120
|
+
styles: Object.freeze([]),
|
|
121
|
+
}),
|
|
122
|
+
]);
|
|
123
|
+
}
|