@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,492 @@
|
|
|
1
|
+
import type { InputField, InputSchema } from "../../model";
|
|
2
|
+
import type { WebSessionContract } from "../../web-session-contract-definitions";
|
|
3
|
+
|
|
4
|
+
export const REDDIT_FLAIR_OPERATION_NAMES = [
|
|
5
|
+
"flair.user.choices",
|
|
6
|
+
"flair.post.choices",
|
|
7
|
+
"flair.user.select",
|
|
8
|
+
"flair.post.select",
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export type RedditFlairOperation = (typeof REDDIT_FLAIR_OPERATION_NAMES)[number];
|
|
12
|
+
|
|
13
|
+
type FlairChoicesTarget = Readonly<{
|
|
14
|
+
kind: "user" | "post";
|
|
15
|
+
community: string;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
type FlairSelectionTarget =
|
|
19
|
+
| Readonly<{ kind: "user"; community: string }>
|
|
20
|
+
| Readonly<{ kind: "post"; community: string; postId: string }>;
|
|
21
|
+
|
|
22
|
+
export type RedditFlairInput =
|
|
23
|
+
| Readonly<{ action: "choices"; target: FlairChoicesTarget }>
|
|
24
|
+
| Readonly<{
|
|
25
|
+
action: "select";
|
|
26
|
+
target: FlairSelectionTarget;
|
|
27
|
+
templateId: string;
|
|
28
|
+
expectedText: string;
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
export type RedditFlairChoice = Readonly<{
|
|
32
|
+
templateId: string;
|
|
33
|
+
text: string;
|
|
34
|
+
textEditable: boolean;
|
|
35
|
+
position: "left" | "right";
|
|
36
|
+
selected: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
|
|
39
|
+
export type RedditFlairChoices = Readonly<{
|
|
40
|
+
schemaVersion: 1;
|
|
41
|
+
community: string;
|
|
42
|
+
kind: "user" | "post";
|
|
43
|
+
choices: readonly RedditFlairChoice[];
|
|
44
|
+
selectedTemplateId: string | null;
|
|
45
|
+
selectedText: string | null;
|
|
46
|
+
}>;
|
|
47
|
+
|
|
48
|
+
type JsonRecord = Record<string, unknown>;
|
|
49
|
+
|
|
50
|
+
function record(value: unknown, label: string): JsonRecord {
|
|
51
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
52
|
+
throw new Error(`${label} must be an object`);
|
|
53
|
+
}
|
|
54
|
+
return value as JsonRecord;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function exactKeys(
|
|
58
|
+
value: JsonRecord,
|
|
59
|
+
required: readonly string[],
|
|
60
|
+
label: string,
|
|
61
|
+
): void {
|
|
62
|
+
const keys = Object.keys(value);
|
|
63
|
+
const allowed = new Set(required);
|
|
64
|
+
if (
|
|
65
|
+
keys.length !== required.length
|
|
66
|
+
|| required.some((key) => !Object.hasOwn(value, key))
|
|
67
|
+
|| keys.some((key) => !allowed.has(key))
|
|
68
|
+
) throw new Error(`${label} changed its reviewed fields`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function templateId(value: unknown, label: string): string {
|
|
72
|
+
if (
|
|
73
|
+
typeof value !== "string"
|
|
74
|
+
|| !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u.test(value)
|
|
75
|
+
) throw new Error(`${label} must be a canonical template ID`);
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function text(value: unknown, label: string): string {
|
|
80
|
+
if (
|
|
81
|
+
typeof value !== "string"
|
|
82
|
+
|| value.length > 256
|
|
83
|
+
|| /[\u0000-\u001f\u007f]/u.test(value)
|
|
84
|
+
) throw new Error(`${label} must be bounded text`);
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function nullableText(value: unknown, label: string): string | null {
|
|
89
|
+
return value === null ? null : text(value, label);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function position(value: unknown, label: string): "left" | "right" {
|
|
93
|
+
if (value !== "left" && value !== "right") {
|
|
94
|
+
throw new Error(`${label} must be left or right`);
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function cssClass(value: unknown, label: string, nullable = false): void {
|
|
100
|
+
if (nullable && value === null) return;
|
|
101
|
+
if (
|
|
102
|
+
typeof value !== "string"
|
|
103
|
+
|| value.length > 128
|
|
104
|
+
|| /[^A-Za-z0-9 _-]/u.test(value)
|
|
105
|
+
) throw new Error(`${label} changed shape`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function htmlAttribute(attributes: string, name: string): string | null {
|
|
109
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
110
|
+
const matches = [...attributes.matchAll(new RegExp(
|
|
111
|
+
`(?:^|\\s)${escaped}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,
|
|
112
|
+
"giu",
|
|
113
|
+
))];
|
|
114
|
+
if (matches.length > 1) {
|
|
115
|
+
throw new Error(`Reddit flair selector repeated its ${name} attribute`);
|
|
116
|
+
}
|
|
117
|
+
const match = matches[0];
|
|
118
|
+
return match?.[1] ?? match?.[2] ?? null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function decodeHtml(value: string): string {
|
|
122
|
+
return value.replace(
|
|
123
|
+
/&(?:#([0-9]{1,7})|#x([0-9a-f]{1,6})|([a-z]+));/giu,
|
|
124
|
+
(entity, decimal: string | undefined, hexadecimal: string | undefined, named: string | undefined) => {
|
|
125
|
+
if (decimal !== undefined || hexadecimal !== undefined) {
|
|
126
|
+
const codePoint = Number.parseInt(
|
|
127
|
+
decimal ?? hexadecimal!,
|
|
128
|
+
decimal === undefined ? 16 : 10,
|
|
129
|
+
);
|
|
130
|
+
if (
|
|
131
|
+
!Number.isSafeInteger(codePoint)
|
|
132
|
+
|| codePoint < 0
|
|
133
|
+
|| codePoint > 0x10ffff
|
|
134
|
+
) return "\uFFFD";
|
|
135
|
+
try {
|
|
136
|
+
return String.fromCodePoint(codePoint);
|
|
137
|
+
} catch {
|
|
138
|
+
return "\uFFFD";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
switch (named?.toLowerCase()) {
|
|
142
|
+
case "amp": return "&";
|
|
143
|
+
case "apos": return "'";
|
|
144
|
+
case "gt": return ">";
|
|
145
|
+
case "lt": return "<";
|
|
146
|
+
case "nbsp": return " ";
|
|
147
|
+
case "quot": return "\"";
|
|
148
|
+
default: return entity;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function plainHtmlText(value: string, label: string): string {
|
|
155
|
+
const result = decodeHtml(value.replace(/<[^>]*>/gu, ""))
|
|
156
|
+
.replace(/\s+/gu, " ")
|
|
157
|
+
.trim();
|
|
158
|
+
return text(result, label);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function classTokens(attributes: string): readonly string[] {
|
|
162
|
+
const value = htmlAttribute(attributes, "class");
|
|
163
|
+
return value === null
|
|
164
|
+
? Object.freeze([])
|
|
165
|
+
: Object.freeze(value.trim().split(/\s+/u).filter(Boolean));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function hasClass(html: string, tag: string, className: string): boolean {
|
|
169
|
+
const expression = new RegExp(`<${tag}\\b([^>]*)>`, "giu");
|
|
170
|
+
return [...html.matchAll(expression)].some((match) =>
|
|
171
|
+
classTokens(match[1] ?? "").includes(className));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function parseRedditFlairChoicesHtml(
|
|
175
|
+
value: string,
|
|
176
|
+
target: FlairChoicesTarget,
|
|
177
|
+
): RedditFlairChoices {
|
|
178
|
+
if (
|
|
179
|
+
value.length < 1
|
|
180
|
+
|| new TextEncoder().encode(value).byteLength > 512 * 1024
|
|
181
|
+
|| value.includes("\0")
|
|
182
|
+
) throw new Error("Reddit flair selector HTML exceeded its reviewed bound");
|
|
183
|
+
if (/<(?:body|html|iframe|script)\b/iu.test(value)) {
|
|
184
|
+
throw new Error("Reddit flair selector returned a full or active document");
|
|
185
|
+
}
|
|
186
|
+
const headings = [...value.matchAll(/<h2\b[^>]*>([\s\S]*?)<\/h2>/giu)];
|
|
187
|
+
if (
|
|
188
|
+
headings.length !== 1
|
|
189
|
+
|| plainHtmlText(headings[0]?.[1] ?? "", "Reddit flair selector heading").length < 1
|
|
190
|
+
) throw new Error("Reddit flair selector changed its heading");
|
|
191
|
+
|
|
192
|
+
const listItems = [...value.matchAll(/<li\b([^>]*)>([\s\S]*?)<\/li>/giu)];
|
|
193
|
+
const openingItems = [...value.matchAll(/<li\b[^>]*>/giu)].length;
|
|
194
|
+
const closingItems = [...value.matchAll(/<\/li\s*>/giu)].length;
|
|
195
|
+
if (openingItems !== listItems.length || closingItems !== listItems.length) {
|
|
196
|
+
throw new Error("Reddit flair selector changed its list structure");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (listItems.length === 0) {
|
|
200
|
+
const errors = [...value.matchAll(/<div\b([^>]*)>([\s\S]*?)<\/div>/giu)]
|
|
201
|
+
.filter((match) => classTokens(match[1] ?? "").includes("error"));
|
|
202
|
+
if (
|
|
203
|
+
errors.length !== 1
|
|
204
|
+
|| plainHtmlText(errors[0]?.[2] ?? "", "Reddit flair unavailable text").length < 1
|
|
205
|
+
|| hasClass(value, "div", "flairoptionpane")
|
|
206
|
+
|| /<form\b/iu.test(value)
|
|
207
|
+
) throw new Error("Reddit flair selector omitted its reviewed unavailable state");
|
|
208
|
+
return Object.freeze({
|
|
209
|
+
schemaVersion: 1,
|
|
210
|
+
community: target.community,
|
|
211
|
+
kind: target.kind,
|
|
212
|
+
choices: Object.freeze([]),
|
|
213
|
+
selectedTemplateId: null,
|
|
214
|
+
selectedText: null,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!hasClass(value, "div", "flairoptionpane")) {
|
|
219
|
+
throw new Error("Reddit flair selector omitted its option pane");
|
|
220
|
+
}
|
|
221
|
+
const forms = [...value.matchAll(/<form\b([^>]*)>([\s\S]*?)<\/form>/giu)]
|
|
222
|
+
.filter((match) => decodeHtml(htmlAttribute(match[1] ?? "", "action") ?? "")
|
|
223
|
+
=== "/post/selectflair");
|
|
224
|
+
if (forms.length !== 1) {
|
|
225
|
+
throw new Error("Reddit flair selector changed its selection form");
|
|
226
|
+
}
|
|
227
|
+
const templateInputs = [...(forms[0]?.[2] ?? "").matchAll(/<input\b([^>]*)>/giu)]
|
|
228
|
+
.filter((match) => htmlAttribute(match[1] ?? "", "name") === "flair_template_id");
|
|
229
|
+
if (
|
|
230
|
+
templateInputs.length !== 1
|
|
231
|
+
|| htmlAttribute(templateInputs[0]?.[1] ?? "", "type")?.toLowerCase() !== "hidden"
|
|
232
|
+
) throw new Error("Reddit flair selector changed its template binding");
|
|
233
|
+
|
|
234
|
+
const allowedClasses = new Set([
|
|
235
|
+
"flairsample-left",
|
|
236
|
+
"flairsample-right",
|
|
237
|
+
"selected",
|
|
238
|
+
"texteditable",
|
|
239
|
+
]);
|
|
240
|
+
const seen = new Set<string>();
|
|
241
|
+
const choices = listItems.map((match, index): RedditFlairChoice => {
|
|
242
|
+
const attributes = match[1] ?? "";
|
|
243
|
+
const classes = classTokens(attributes);
|
|
244
|
+
if (
|
|
245
|
+
new Set(classes).size !== classes.length
|
|
246
|
+
|| classes.some((candidate) => !allowedClasses.has(candidate))
|
|
247
|
+
) {
|
|
248
|
+
throw new Error(`Reddit flair choice ${index} changed its class set`);
|
|
249
|
+
}
|
|
250
|
+
const positions = classes.filter((candidate) =>
|
|
251
|
+
candidate === "flairsample-left" || candidate === "flairsample-right");
|
|
252
|
+
if (positions.length !== 1) {
|
|
253
|
+
throw new Error(`Reddit flair choice ${index} changed its position`);
|
|
254
|
+
}
|
|
255
|
+
const id = templateId(
|
|
256
|
+
htmlAttribute(attributes, "id"),
|
|
257
|
+
`Reddit flair choice ${index} template ID`,
|
|
258
|
+
);
|
|
259
|
+
if (seen.has(id)) throw new Error("Reddit flair response repeated a template ID");
|
|
260
|
+
seen.add(id);
|
|
261
|
+
const labels = [...(match[2] ?? "").matchAll(/<span\b([^>]*)>([\s\S]*?)<\/span>/giu)]
|
|
262
|
+
.filter((span) => {
|
|
263
|
+
const tokens = classTokens(span[1] ?? "");
|
|
264
|
+
return tokens.includes("flair") || tokens.includes("linkflairlabel");
|
|
265
|
+
});
|
|
266
|
+
if (labels.length !== 1) {
|
|
267
|
+
throw new Error(`Reddit flair choice ${index} changed its visible label`);
|
|
268
|
+
}
|
|
269
|
+
return Object.freeze({
|
|
270
|
+
templateId: id,
|
|
271
|
+
text: plainHtmlText(labels[0]?.[2] ?? "", `Reddit flair choice ${index} text`),
|
|
272
|
+
textEditable: classes.includes("texteditable"),
|
|
273
|
+
position: positions[0] === "flairsample-left" ? "left" : "right",
|
|
274
|
+
selected: classes.includes("selected"),
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
const selected = choices.filter((choice) => choice.selected);
|
|
278
|
+
if (selected.length > 1) {
|
|
279
|
+
throw new Error("Reddit flair selector marked multiple current choices");
|
|
280
|
+
}
|
|
281
|
+
return Object.freeze({
|
|
282
|
+
schemaVersion: 1,
|
|
283
|
+
community: target.community,
|
|
284
|
+
kind: target.kind,
|
|
285
|
+
choices: Object.freeze(choices),
|
|
286
|
+
selectedTemplateId: selected[0]?.templateId ?? null,
|
|
287
|
+
selectedText: selected[0]?.text ?? null,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function parseRedditFlairChoicesResponse(
|
|
292
|
+
value: unknown,
|
|
293
|
+
target: FlairChoicesTarget,
|
|
294
|
+
): RedditFlairChoices {
|
|
295
|
+
if (!/^[A-Za-z0-9_]{2,21}$/u.test(target.community)) {
|
|
296
|
+
throw new Error("Reddit flair community must be an exact subreddit name");
|
|
297
|
+
}
|
|
298
|
+
if (typeof value === "string") {
|
|
299
|
+
return parseRedditFlairChoicesHtml(value, target);
|
|
300
|
+
}
|
|
301
|
+
const root = record(value, "Reddit flair response");
|
|
302
|
+
exactKeys(root, ["choices", "current"], "Reddit flair response");
|
|
303
|
+
if (!Array.isArray(root.choices) || root.choices.length > 350) {
|
|
304
|
+
throw new Error("Reddit flair choices exceeded their reviewed bound");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const current = record(root.current, "Reddit flair current selection");
|
|
308
|
+
exactKeys(
|
|
309
|
+
current,
|
|
310
|
+
["flair_css_class", "flair_position", "flair_template_id", "flair_text"],
|
|
311
|
+
"Reddit flair current selection",
|
|
312
|
+
);
|
|
313
|
+
cssClass(current.flair_css_class, "Reddit flair current CSS class", true);
|
|
314
|
+
position(current.flair_position, "Reddit flair current position");
|
|
315
|
+
const selectedTemplateId = current.flair_template_id === null
|
|
316
|
+
? null
|
|
317
|
+
: templateId(current.flair_template_id, "Reddit flair current template ID");
|
|
318
|
+
const selectedText = nullableText(current.flair_text, "Reddit flair current text");
|
|
319
|
+
if ((selectedTemplateId === null) !== (selectedText === null)) {
|
|
320
|
+
throw new Error("Reddit flair current selection was incomplete");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const seen = new Set<string>();
|
|
324
|
+
const choices = root.choices.map((rawChoice, index): RedditFlairChoice => {
|
|
325
|
+
const choice = record(rawChoice, `Reddit flair choice ${index}`);
|
|
326
|
+
exactKeys(
|
|
327
|
+
choice,
|
|
328
|
+
[
|
|
329
|
+
"flair_css_class",
|
|
330
|
+
"flair_position",
|
|
331
|
+
"flair_template_id",
|
|
332
|
+
"flair_text",
|
|
333
|
+
"flair_text_editable",
|
|
334
|
+
],
|
|
335
|
+
`Reddit flair choice ${index}`,
|
|
336
|
+
);
|
|
337
|
+
cssClass(choice.flair_css_class, `Reddit flair choice ${index} CSS class`);
|
|
338
|
+
const id = templateId(
|
|
339
|
+
choice.flair_template_id,
|
|
340
|
+
`Reddit flair choice ${index} template ID`,
|
|
341
|
+
);
|
|
342
|
+
if (seen.has(id)) throw new Error("Reddit flair response repeated a template ID");
|
|
343
|
+
seen.add(id);
|
|
344
|
+
if (typeof choice.flair_text_editable !== "boolean") {
|
|
345
|
+
throw new Error(`Reddit flair choice ${index} editability must be boolean`);
|
|
346
|
+
}
|
|
347
|
+
return Object.freeze({
|
|
348
|
+
templateId: id,
|
|
349
|
+
text: text(choice.flair_text, `Reddit flair choice ${index} text`),
|
|
350
|
+
textEditable: choice.flair_text_editable,
|
|
351
|
+
position: position(
|
|
352
|
+
choice.flair_position,
|
|
353
|
+
`Reddit flair choice ${index} position`,
|
|
354
|
+
),
|
|
355
|
+
selected: selectedTemplateId === id,
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
return Object.freeze({
|
|
360
|
+
schemaVersion: 1,
|
|
361
|
+
community: target.community,
|
|
362
|
+
kind: target.kind,
|
|
363
|
+
choices: Object.freeze(choices),
|
|
364
|
+
selectedTemplateId,
|
|
365
|
+
selectedText,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function isRedditFlairOperation(value: string): value is RedditFlairOperation {
|
|
370
|
+
return REDDIT_FLAIR_OPERATION_NAMES.some((operation) => operation === value);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function parseRedditFlairInput(
|
|
374
|
+
operation: RedditFlairOperation,
|
|
375
|
+
value: unknown,
|
|
376
|
+
): RedditFlairInput {
|
|
377
|
+
if (!isRedditFlairOperation(operation)) {
|
|
378
|
+
throw new Error("Unsupported Reddit flair operation");
|
|
379
|
+
}
|
|
380
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
381
|
+
throw new Error("Reddit flair input must be an object");
|
|
382
|
+
}
|
|
383
|
+
const input = value as Record<string, unknown>;
|
|
384
|
+
const isPost = operation.startsWith("flair.post.");
|
|
385
|
+
const isSelect = operation.endsWith(".select");
|
|
386
|
+
const required = [
|
|
387
|
+
"community",
|
|
388
|
+
...(isPost && isSelect ? ["post_id"] : []),
|
|
389
|
+
...(isSelect ? ["template_id", "expected_text"] : []),
|
|
390
|
+
];
|
|
391
|
+
if (
|
|
392
|
+
Object.keys(input).length !== required.length
|
|
393
|
+
|| required.some((key) => !Object.hasOwn(input, key))
|
|
394
|
+
) throw new Error("Reddit flair input contains missing or unsupported fields");
|
|
395
|
+
const community = input.community;
|
|
396
|
+
if (typeof community !== "string" || !/^[A-Za-z0-9_]{2,21}$/u.test(community)) {
|
|
397
|
+
throw new Error("Reddit flair community must be an exact subreddit name");
|
|
398
|
+
}
|
|
399
|
+
if (!isSelect) {
|
|
400
|
+
return {
|
|
401
|
+
action: "choices",
|
|
402
|
+
target: { kind: isPost ? "post" : "user", community },
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let target: FlairSelectionTarget = { kind: "user", community };
|
|
407
|
+
if (isPost) {
|
|
408
|
+
const postId = input.post_id;
|
|
409
|
+
if (typeof postId !== "string" || !/^t3_[a-z0-9]{1,32}$/u.test(postId)) {
|
|
410
|
+
throw new Error("Reddit post flair requires an exact post fullname");
|
|
411
|
+
}
|
|
412
|
+
target = { kind: "post", community, postId };
|
|
413
|
+
}
|
|
414
|
+
const selectedTemplateId = templateId(
|
|
415
|
+
input.template_id,
|
|
416
|
+
"Reddit flair selection",
|
|
417
|
+
);
|
|
418
|
+
const expectedText = input.expected_text;
|
|
419
|
+
if (
|
|
420
|
+
typeof expectedText !== "string"
|
|
421
|
+
|| expectedText.length < 1
|
|
422
|
+
|| expectedText.length > 256
|
|
423
|
+
|| /[\u0000-\u001f\u007f]/u.test(expectedText)
|
|
424
|
+
|| expectedText.trim() !== expectedText
|
|
425
|
+
) throw new Error("Reddit flair selection requires a bounded exact label");
|
|
426
|
+
return {
|
|
427
|
+
action: "select",
|
|
428
|
+
target,
|
|
429
|
+
templateId: selectedTemplateId,
|
|
430
|
+
expectedText,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const communityField = {
|
|
435
|
+
type: "string",
|
|
436
|
+
description: "Exact subreddit name without r/",
|
|
437
|
+
minLength: 2,
|
|
438
|
+
maxLength: 21,
|
|
439
|
+
} as const satisfies InputField;
|
|
440
|
+
|
|
441
|
+
export const redditFlairContracts: readonly WebSessionContract[] = Object.freeze(
|
|
442
|
+
REDDIT_FLAIR_OPERATION_NAMES.map((operation): WebSessionContract => {
|
|
443
|
+
const isPost = operation.startsWith("flair.post.");
|
|
444
|
+
const isSelect = operation.endsWith(".select");
|
|
445
|
+
const properties: Record<string, InputField> = { community: communityField };
|
|
446
|
+
if (isPost && isSelect) {
|
|
447
|
+
properties.post_id = {
|
|
448
|
+
type: "string",
|
|
449
|
+
description: "Exact t3_ fullname of the current account's post",
|
|
450
|
+
minLength: 4,
|
|
451
|
+
maxLength: 35,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
if (isSelect) {
|
|
455
|
+
properties.template_id = {
|
|
456
|
+
type: "string",
|
|
457
|
+
description: "Exact self-selectable template ID from a fresh choices read",
|
|
458
|
+
minLength: 36,
|
|
459
|
+
maxLength: 36,
|
|
460
|
+
};
|
|
461
|
+
properties.expected_text = {
|
|
462
|
+
type: "string",
|
|
463
|
+
description: "Exact label from that same choices read; not custom flair text",
|
|
464
|
+
minLength: 1,
|
|
465
|
+
maxLength: 256,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const input: InputSchema = {
|
|
469
|
+
properties: Object.freeze(properties),
|
|
470
|
+
required: Object.freeze(Object.keys(properties)),
|
|
471
|
+
};
|
|
472
|
+
return Object.freeze({
|
|
473
|
+
site: "reddit",
|
|
474
|
+
operation,
|
|
475
|
+
contractVersion: 1,
|
|
476
|
+
risk: isSelect ? (isPost ? "R3" : "R2") : "R1",
|
|
477
|
+
input,
|
|
478
|
+
sideEffect: isSelect
|
|
479
|
+
? (isPost
|
|
480
|
+
? "Select one flair on the current account's exact post"
|
|
481
|
+
: "Select one community user flair for the current account")
|
|
482
|
+
: "none",
|
|
483
|
+
idempotency: isSelect ? "local-at-most-once" : "none",
|
|
484
|
+
dedupeWindowMs: isSelect ? 300_000 : 0,
|
|
485
|
+
state: isSelect ? "capture-required" : "observed",
|
|
486
|
+
dispatch: isSelect ? "single" : "none",
|
|
487
|
+
implementation: isSelect
|
|
488
|
+
? "Reddit flair selection requires reviewed same-account mutation and readback evidence; no request is installed"
|
|
489
|
+
: "bound current-account modhash plus exact old-Reddit flairselector JSON with strict template projection",
|
|
490
|
+
});
|
|
491
|
+
}),
|
|
492
|
+
);
|