@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,1832 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reviewed policy catalog for social-site adapters.
|
|
3
|
+
*
|
|
4
|
+
* `adapter-eligible` means that ghostget may represent the operation after a
|
|
5
|
+
* site-specific adapter has been derived and reviewed. It does not mean that
|
|
6
|
+
* an adapter, selector, or browser recipe currently exists.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const semanticOperationNames = [
|
|
10
|
+
"content.read",
|
|
11
|
+
"content.clip",
|
|
12
|
+
"profiles.read",
|
|
13
|
+
"organizations.read",
|
|
14
|
+
"contacts.list",
|
|
15
|
+
"contacts.search",
|
|
16
|
+
"contacts.read",
|
|
17
|
+
"feeds.read",
|
|
18
|
+
"messaging.list",
|
|
19
|
+
"messaging.search",
|
|
20
|
+
"messaging.read",
|
|
21
|
+
"messaging.send",
|
|
22
|
+
"comments.read",
|
|
23
|
+
"comments.create",
|
|
24
|
+
"replies.create",
|
|
25
|
+
"posts.read",
|
|
26
|
+
"posts.publish",
|
|
27
|
+
"threads.publish",
|
|
28
|
+
"reactions.set",
|
|
29
|
+
"likes.set",
|
|
30
|
+
"media.read",
|
|
31
|
+
"media.publish",
|
|
32
|
+
"articles.read",
|
|
33
|
+
"articles.draft.save",
|
|
34
|
+
"articles.publish",
|
|
35
|
+
"listings.read",
|
|
36
|
+
"listings.publish",
|
|
37
|
+
"relationships.follow.set",
|
|
38
|
+
"relationships.recommendations.read",
|
|
39
|
+
"relationships.connect",
|
|
40
|
+
"posts.repost",
|
|
41
|
+
"posts.quote",
|
|
42
|
+
"content.share",
|
|
43
|
+
"content.save",
|
|
44
|
+
"content.edit",
|
|
45
|
+
"content.delete",
|
|
46
|
+
"content.schedule",
|
|
47
|
+
"content.audience.set",
|
|
48
|
+
"communities.membership.set",
|
|
49
|
+
"communities.membership.manage",
|
|
50
|
+
"administration.manage",
|
|
51
|
+
"commerce.purchase",
|
|
52
|
+
"account.delete",
|
|
53
|
+
"moderation.bulk",
|
|
54
|
+
] as const;
|
|
55
|
+
|
|
56
|
+
export type SemanticOperationName = (typeof semanticOperationNames)[number];
|
|
57
|
+
|
|
58
|
+
export const socialPlatformIds = [
|
|
59
|
+
"linkedin",
|
|
60
|
+
"x",
|
|
61
|
+
"reddit",
|
|
62
|
+
"github",
|
|
63
|
+
"hacker-news",
|
|
64
|
+
"whatsapp",
|
|
65
|
+
"substack",
|
|
66
|
+
"instagram",
|
|
67
|
+
"threads",
|
|
68
|
+
"facebook",
|
|
69
|
+
"tiktok",
|
|
70
|
+
"twitch",
|
|
71
|
+
"youtube",
|
|
72
|
+
"bluesky",
|
|
73
|
+
] as const;
|
|
74
|
+
|
|
75
|
+
export type SocialPlatformId = (typeof socialPlatformIds)[number];
|
|
76
|
+
|
|
77
|
+
export const platformSurfaceIds = [
|
|
78
|
+
"linkedin",
|
|
79
|
+
"x",
|
|
80
|
+
"reddit",
|
|
81
|
+
"github",
|
|
82
|
+
"hacker-news",
|
|
83
|
+
"whatsapp",
|
|
84
|
+
"substack",
|
|
85
|
+
"instagram",
|
|
86
|
+
"threads",
|
|
87
|
+
"facebook",
|
|
88
|
+
"facebook-page",
|
|
89
|
+
"facebook-group",
|
|
90
|
+
"facebook-marketplace",
|
|
91
|
+
"tiktok",
|
|
92
|
+
"twitch",
|
|
93
|
+
"youtube",
|
|
94
|
+
"bluesky",
|
|
95
|
+
] as const;
|
|
96
|
+
|
|
97
|
+
export type PlatformSurfaceId = (typeof platformSurfaceIds)[number];
|
|
98
|
+
export type PlatformFacet = "default" | "page" | "group" | "marketplace";
|
|
99
|
+
|
|
100
|
+
type EligibleRisk = "R1" | "R2" | "R3";
|
|
101
|
+
|
|
102
|
+
export type CapabilityPolicy =
|
|
103
|
+
| {
|
|
104
|
+
readonly state: "adapter-eligible";
|
|
105
|
+
readonly risk: EligibleRisk;
|
|
106
|
+
readonly meaning: string;
|
|
107
|
+
}
|
|
108
|
+
| {
|
|
109
|
+
readonly state: "unsupported";
|
|
110
|
+
readonly reason: string;
|
|
111
|
+
}
|
|
112
|
+
| {
|
|
113
|
+
readonly state: "not-applicable";
|
|
114
|
+
readonly reason: string;
|
|
115
|
+
}
|
|
116
|
+
| {
|
|
117
|
+
readonly state: "R4";
|
|
118
|
+
readonly risk: "R4";
|
|
119
|
+
readonly reason: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type OperationPolicyMatrix = Readonly<Record<SemanticOperationName, CapabilityPolicy>>;
|
|
123
|
+
|
|
124
|
+
type OperationGroups = {
|
|
125
|
+
readonly R1: readonly SemanticOperationName[];
|
|
126
|
+
readonly R2: readonly SemanticOperationName[];
|
|
127
|
+
readonly R3: readonly SemanticOperationName[];
|
|
128
|
+
readonly unsupported: readonly SemanticOperationName[];
|
|
129
|
+
readonly notApplicable: readonly SemanticOperationName[];
|
|
130
|
+
readonly R4: readonly SemanticOperationName[];
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const operationMeanings = {
|
|
134
|
+
"content.read": "Read one bounded content target",
|
|
135
|
+
"content.clip": "Capture one bounded content target locally",
|
|
136
|
+
"profiles.read": "Read one explicitly selected member or account profile",
|
|
137
|
+
"organizations.read": "Read one explicitly selected organization or page",
|
|
138
|
+
"contacts.list": "List one bounded provider-defined contact collection with explicit metadata and statistics completeness",
|
|
139
|
+
"contacts.search": "Search one bounded provider-defined contact candidate window",
|
|
140
|
+
"contacts.read": "Read one exact account-bound contact identity",
|
|
141
|
+
"feeds.read": "Read one explicitly selected bounded feed or timeline",
|
|
142
|
+
"messaging.list": "List one bounded provider-visible inbox or message-event collection",
|
|
143
|
+
"messaging.search": "Search one bounded provider-visible conversation candidate window",
|
|
144
|
+
"messaging.read": "Read one explicitly targeted conversation",
|
|
145
|
+
"messaging.send": "Send one message to an explicit conversation",
|
|
146
|
+
"comments.read": "Read bounded comments and replies on one target",
|
|
147
|
+
"comments.create": "Publish one comment on an explicit target",
|
|
148
|
+
"replies.create": "Publish one reply to an explicit target",
|
|
149
|
+
"posts.read": "Read one post or a bounded post collection",
|
|
150
|
+
"posts.publish": "Publish one post",
|
|
151
|
+
"threads.publish": "Publish one ordered bounded root-and-replies thread",
|
|
152
|
+
"reactions.set": "Set or clear one reversible reaction",
|
|
153
|
+
"likes.set": "Set or clear one reversible like",
|
|
154
|
+
"media.read": "Read metadata for explicitly targeted media",
|
|
155
|
+
"media.publish": "Publish one bounded media item",
|
|
156
|
+
"articles.read": "Read one native long-form article",
|
|
157
|
+
"articles.draft.save": "Save one private native long-form article draft",
|
|
158
|
+
"articles.publish": "Publish one native long-form article",
|
|
159
|
+
"listings.read": "Read one explicitly targeted marketplace listing",
|
|
160
|
+
"listings.publish": "Publish one marketplace listing",
|
|
161
|
+
"relationships.follow.set": "Follow or unfollow one explicit account or free publication",
|
|
162
|
+
"relationships.recommendations.read": "Read one bounded page of provider-recommended connections",
|
|
163
|
+
"relationships.connect": "Send one connection request to an explicit account",
|
|
164
|
+
"posts.repost": "Repost or undo one explicit post without added commentary",
|
|
165
|
+
"posts.quote": "Publish one quoted repost with explicit commentary",
|
|
166
|
+
"content.share": "Share one explicit content item to one provider-visible target",
|
|
167
|
+
"content.save": "Save, bookmark, star, or unsave one explicit content item privately",
|
|
168
|
+
"content.edit": "Edit one explicitly targeted authored item",
|
|
169
|
+
"content.delete": "Delete one explicitly targeted authored item",
|
|
170
|
+
"content.schedule": "Schedule one explicit content draft for publication",
|
|
171
|
+
"content.audience.set": "Change the audience or visibility of one existing content item",
|
|
172
|
+
"communities.membership.set": "Join or leave one explicit community as the current user",
|
|
173
|
+
"communities.membership.manage": "Approve, remove, invite, or change another community member",
|
|
174
|
+
"administration.manage": "Change roles, permissions, business settings, or administrative configuration",
|
|
175
|
+
"commerce.purchase": "Commit a purchase or financial obligation",
|
|
176
|
+
"account.delete": "Delete an account or platform identity",
|
|
177
|
+
"moderation.bulk": "Apply moderation to multiple targets",
|
|
178
|
+
} as const satisfies Readonly<Record<SemanticOperationName, string>>;
|
|
179
|
+
|
|
180
|
+
function buildOperationMatrix(groups: OperationGroups): OperationPolicyMatrix {
|
|
181
|
+
const policies: Partial<Record<SemanticOperationName, CapabilityPolicy>> = {};
|
|
182
|
+
|
|
183
|
+
const add = (name: SemanticOperationName, policy: CapabilityPolicy): void => {
|
|
184
|
+
if (policies[name] !== undefined) {
|
|
185
|
+
throw new Error(`Duplicate platform-catalog operation classification: ${name}`);
|
|
186
|
+
}
|
|
187
|
+
policies[name] = policy;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
for (const risk of ["R1", "R2", "R3"] as const) {
|
|
191
|
+
for (const name of groups[risk]) {
|
|
192
|
+
add(name, {
|
|
193
|
+
state: "adapter-eligible",
|
|
194
|
+
risk,
|
|
195
|
+
meaning: `${operationMeanings[name]}; a reviewed adapter is still required`,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const name of groups.unsupported) {
|
|
200
|
+
add(name, {
|
|
201
|
+
state: "unsupported",
|
|
202
|
+
reason: `${operationMeanings[name]} has no reviewed policy on this surface`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
for (const name of groups.notApplicable) {
|
|
206
|
+
add(name, {
|
|
207
|
+
state: "not-applicable",
|
|
208
|
+
reason: `${operationMeanings[name]} has no distinct native concept on this surface`,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
for (const name of groups.R4) {
|
|
212
|
+
add(name, {
|
|
213
|
+
state: "R4",
|
|
214
|
+
risk: "R4",
|
|
215
|
+
reason: `${operationMeanings[name]} is outside wrench's executable safety boundary`,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Collection reads are the list-shaped counterparts of the existing
|
|
220
|
+
// target-shaped operations. Keep their reviewed risk aligned by default so
|
|
221
|
+
// every surface receives an explicit policy without duplicating the same
|
|
222
|
+
// classification across the catalog. A concrete adapter still has to expose
|
|
223
|
+
// a truthful provider-specific collection and its completeness limits.
|
|
224
|
+
if (policies["feeds.read"] === undefined) {
|
|
225
|
+
const posts = policies["posts.read"];
|
|
226
|
+
add("feeds.read", posts?.state === "adapter-eligible"
|
|
227
|
+
? {
|
|
228
|
+
state: "adapter-eligible",
|
|
229
|
+
risk: "R1",
|
|
230
|
+
meaning: `${operationMeanings["feeds.read"]}; a reviewed adapter is still required`,
|
|
231
|
+
}
|
|
232
|
+
: posts?.state === "not-applicable"
|
|
233
|
+
? {
|
|
234
|
+
state: "not-applicable",
|
|
235
|
+
reason: `${operationMeanings["feeds.read"]} has no distinct native concept on this surface`,
|
|
236
|
+
}
|
|
237
|
+
: {
|
|
238
|
+
state: "unsupported",
|
|
239
|
+
reason: `${operationMeanings["feeds.read"]} has no reviewed policy on this surface`,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (policies["messaging.list"] === undefined) {
|
|
243
|
+
const messages = policies["messaging.read"];
|
|
244
|
+
add("messaging.list", messages?.state === "adapter-eligible"
|
|
245
|
+
? {
|
|
246
|
+
state: "adapter-eligible",
|
|
247
|
+
risk: "R1",
|
|
248
|
+
meaning: `${operationMeanings["messaging.list"]}; a reviewed adapter is still required`,
|
|
249
|
+
}
|
|
250
|
+
: messages?.state === "not-applicable"
|
|
251
|
+
? {
|
|
252
|
+
state: "not-applicable",
|
|
253
|
+
reason: `${operationMeanings["messaging.list"]} has no distinct native concept on this surface`,
|
|
254
|
+
}
|
|
255
|
+
: {
|
|
256
|
+
state: "unsupported",
|
|
257
|
+
reason: `${operationMeanings["messaging.list"]} has no reviewed policy on this surface`,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Identity and discovery reads are intentionally opt-in. Unlike feed and
|
|
262
|
+
// inbox collection aliases, these operations do not inherit support from a
|
|
263
|
+
// nearby target read because doing so would overclaim a provider-specific
|
|
264
|
+
// people, organization, or recommendation surface.
|
|
265
|
+
for (const name of [
|
|
266
|
+
"profiles.read",
|
|
267
|
+
"organizations.read",
|
|
268
|
+
"contacts.list",
|
|
269
|
+
"contacts.search",
|
|
270
|
+
"contacts.read",
|
|
271
|
+
"messaging.search",
|
|
272
|
+
"relationships.recommendations.read",
|
|
273
|
+
] as const) {
|
|
274
|
+
if (policies[name] === undefined) {
|
|
275
|
+
add(name, {
|
|
276
|
+
state: "unsupported",
|
|
277
|
+
reason: `${operationMeanings[name]} has no reviewed policy on this surface`,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const missing = semanticOperationNames.filter((name) => policies[name] === undefined);
|
|
283
|
+
if (missing.length > 0) {
|
|
284
|
+
throw new Error(`Missing platform-catalog operation classifications: ${missing.join(", ")}`);
|
|
285
|
+
}
|
|
286
|
+
const ordered = Object.fromEntries(
|
|
287
|
+
semanticOperationNames.map((name) => [name, policies[name]]),
|
|
288
|
+
);
|
|
289
|
+
return Object.freeze(ordered) as OperationPolicyMatrix;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export const textMeasurementNames = [
|
|
293
|
+
"unicode-code-points",
|
|
294
|
+
"utf16-code-units",
|
|
295
|
+
"x-conservative-weighted",
|
|
296
|
+
] as const;
|
|
297
|
+
|
|
298
|
+
export type TextMeasurementName = (typeof textMeasurementNames)[number];
|
|
299
|
+
|
|
300
|
+
export type CodePointWeightRange = {
|
|
301
|
+
readonly start: number;
|
|
302
|
+
readonly end: number;
|
|
303
|
+
readonly weight: number;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export type TextWeightPolicy = {
|
|
307
|
+
readonly defaultWeight: number;
|
|
308
|
+
readonly ranges: readonly CodePointWeightRange[];
|
|
309
|
+
/** A recognized URL weighs at least this much; raw weight is never discarded. */
|
|
310
|
+
readonly minimumUrlWeight?: number;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export const textWeightPolicies = {
|
|
314
|
+
"unicode-code-points": {
|
|
315
|
+
defaultWeight: 1,
|
|
316
|
+
ranges: [],
|
|
317
|
+
},
|
|
318
|
+
"utf16-code-units": {
|
|
319
|
+
defaultWeight: 1,
|
|
320
|
+
ranges: [{ start: 0x10_000, end: 0x10_ffff, weight: 2 }],
|
|
321
|
+
},
|
|
322
|
+
"x-conservative-weighted": {
|
|
323
|
+
defaultWeight: 2,
|
|
324
|
+
ranges: [
|
|
325
|
+
{ start: 0x0000, end: 0x10ff, weight: 1 },
|
|
326
|
+
{ start: 0x2000, end: 0x200d, weight: 1 },
|
|
327
|
+
{ start: 0x2010, end: 0x201f, weight: 1 },
|
|
328
|
+
{ start: 0x2032, end: 0x2037, weight: 1 },
|
|
329
|
+
],
|
|
330
|
+
minimumUrlWeight: 23,
|
|
331
|
+
},
|
|
332
|
+
} as const satisfies Readonly<Record<TextMeasurementName, TextWeightPolicy>>;
|
|
333
|
+
|
|
334
|
+
export const compositionTextFormatNames = [
|
|
335
|
+
"plain-text",
|
|
336
|
+
"decimal-amount",
|
|
337
|
+
"currency-code",
|
|
338
|
+
"provider-option",
|
|
339
|
+
"location-label",
|
|
340
|
+
] as const;
|
|
341
|
+
|
|
342
|
+
export type CompositionTextFormatName = (typeof compositionTextFormatNames)[number];
|
|
343
|
+
|
|
344
|
+
export type CompositionTextField = {
|
|
345
|
+
readonly name:
|
|
346
|
+
| "title"
|
|
347
|
+
| "body"
|
|
348
|
+
| "caption"
|
|
349
|
+
| "price"
|
|
350
|
+
| "currency"
|
|
351
|
+
| "category"
|
|
352
|
+
| "condition"
|
|
353
|
+
| "location"
|
|
354
|
+
| "delivery";
|
|
355
|
+
readonly required: boolean;
|
|
356
|
+
/** A deliberately conservative workflow bound, not a provider maximum. */
|
|
357
|
+
readonly safeMaxUnits: number;
|
|
358
|
+
readonly measurement: TextMeasurementName;
|
|
359
|
+
/** Provider-neutral value semantics; provider-option requires a stable reviewed adapter enum. */
|
|
360
|
+
readonly format: CompositionTextFormatName;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export const attachmentKinds = [
|
|
364
|
+
"image",
|
|
365
|
+
"video",
|
|
366
|
+
"audio",
|
|
367
|
+
"gif",
|
|
368
|
+
"document",
|
|
369
|
+
"link",
|
|
370
|
+
] as const;
|
|
371
|
+
|
|
372
|
+
export type AttachmentKind = (typeof attachmentKinds)[number];
|
|
373
|
+
|
|
374
|
+
export type AttachmentPolicy =
|
|
375
|
+
| {
|
|
376
|
+
readonly state: "none";
|
|
377
|
+
readonly reason: string;
|
|
378
|
+
}
|
|
379
|
+
| {
|
|
380
|
+
readonly state: "allowed";
|
|
381
|
+
readonly minItems: number;
|
|
382
|
+
readonly maxItems: number;
|
|
383
|
+
readonly kinds: readonly AttachmentKind[];
|
|
384
|
+
readonly note: string;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
export const compositionNames = [
|
|
388
|
+
"message",
|
|
389
|
+
"comment",
|
|
390
|
+
"reply",
|
|
391
|
+
"post",
|
|
392
|
+
"media",
|
|
393
|
+
"article",
|
|
394
|
+
"listing",
|
|
395
|
+
] as const;
|
|
396
|
+
|
|
397
|
+
export type CompositionName = (typeof compositionNames)[number];
|
|
398
|
+
|
|
399
|
+
export type CompositionPolicy = {
|
|
400
|
+
readonly text: readonly CompositionTextField[];
|
|
401
|
+
readonly attachments: AttachmentPolicy;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
type FeatureExclusion =
|
|
405
|
+
| { readonly state: "unsupported"; readonly reason: string }
|
|
406
|
+
| { readonly state: "not-applicable"; readonly reason: string }
|
|
407
|
+
| { readonly state: "R4"; readonly risk: "R4"; readonly reason: string };
|
|
408
|
+
|
|
409
|
+
export type LongFormAccessPolicy =
|
|
410
|
+
| {
|
|
411
|
+
readonly state: "adapter-eligible";
|
|
412
|
+
readonly operation: SemanticOperationName;
|
|
413
|
+
readonly form: "native-article" | "expanded-post";
|
|
414
|
+
readonly note: string;
|
|
415
|
+
}
|
|
416
|
+
| FeatureExclusion;
|
|
417
|
+
|
|
418
|
+
export type ThreadReadPolicy =
|
|
419
|
+
| {
|
|
420
|
+
readonly state: "adapter-eligible";
|
|
421
|
+
readonly operation: "content.read";
|
|
422
|
+
readonly note: string;
|
|
423
|
+
}
|
|
424
|
+
| FeatureExclusion;
|
|
425
|
+
|
|
426
|
+
export type ThreadPublishPolicy =
|
|
427
|
+
| {
|
|
428
|
+
readonly state: "adapter-eligible";
|
|
429
|
+
readonly operation: "threads.publish";
|
|
430
|
+
readonly rootOperation: "posts.publish";
|
|
431
|
+
readonly continuationOperation: "replies.create";
|
|
432
|
+
readonly safeMaxItems: number;
|
|
433
|
+
readonly note: string;
|
|
434
|
+
}
|
|
435
|
+
| FeatureExclusion;
|
|
436
|
+
|
|
437
|
+
export type ExactOriginPolicy = {
|
|
438
|
+
readonly exactOrigins: readonly `https://${string}`[];
|
|
439
|
+
readonly additionalExactOrigins:
|
|
440
|
+
| { readonly state: "forbidden" }
|
|
441
|
+
| {
|
|
442
|
+
readonly state: "adapter-declared";
|
|
443
|
+
readonly kind: "publication-origin";
|
|
444
|
+
readonly maxOrigins: 1;
|
|
445
|
+
readonly note: string;
|
|
446
|
+
};
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
export type PlatformSurfaceCatalogEntry = {
|
|
450
|
+
readonly id: PlatformSurfaceId;
|
|
451
|
+
readonly platform: SocialPlatformId;
|
|
452
|
+
readonly facet: PlatformFacet;
|
|
453
|
+
readonly displayName: string;
|
|
454
|
+
readonly originPolicy: ExactOriginPolicy;
|
|
455
|
+
readonly operations: OperationPolicyMatrix;
|
|
456
|
+
readonly compositions: Readonly<Partial<Record<CompositionName, CompositionPolicy>>>;
|
|
457
|
+
readonly longForm: {
|
|
458
|
+
readonly read: LongFormAccessPolicy;
|
|
459
|
+
readonly publish: LongFormAccessPolicy;
|
|
460
|
+
};
|
|
461
|
+
readonly threads: {
|
|
462
|
+
readonly read: ThreadReadPolicy;
|
|
463
|
+
readonly publish: ThreadPublishPolicy;
|
|
464
|
+
};
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const exactOrigins = (...origins: readonly `https://${string}`[]): ExactOriginPolicy => ({
|
|
468
|
+
exactOrigins: origins,
|
|
469
|
+
additionalExactOrigins: { state: "forbidden" },
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const publicationOrigins = (...origins: readonly `https://${string}`[]): ExactOriginPolicy => ({
|
|
473
|
+
exactOrigins: origins,
|
|
474
|
+
additionalExactOrigins: {
|
|
475
|
+
state: "adapter-declared",
|
|
476
|
+
kind: "publication-origin",
|
|
477
|
+
maxOrigins: 1,
|
|
478
|
+
note: "A custom publication host must be declared as one exact HTTPS origin in the reviewed adapter",
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const field = (
|
|
483
|
+
name: CompositionTextField["name"],
|
|
484
|
+
safeMaxUnits: number,
|
|
485
|
+
measurement: TextMeasurementName,
|
|
486
|
+
required = true,
|
|
487
|
+
format: CompositionTextFormatName = "plain-text",
|
|
488
|
+
): CompositionTextField => ({ name, required, safeMaxUnits, measurement, format });
|
|
489
|
+
|
|
490
|
+
const noAttachments = (reason: string): AttachmentPolicy => ({ state: "none", reason });
|
|
491
|
+
|
|
492
|
+
const attachments = (
|
|
493
|
+
maxItems: number,
|
|
494
|
+
kinds: readonly AttachmentKind[],
|
|
495
|
+
note: string,
|
|
496
|
+
minItems = 0,
|
|
497
|
+
): AttachmentPolicy => ({ state: "allowed", minItems, maxItems, kinds, note });
|
|
498
|
+
|
|
499
|
+
const excludedFeature = (
|
|
500
|
+
state: FeatureExclusion["state"],
|
|
501
|
+
reason: string,
|
|
502
|
+
): FeatureExclusion => state === "R4"
|
|
503
|
+
? { state, risk: "R4", reason }
|
|
504
|
+
: { state, reason };
|
|
505
|
+
|
|
506
|
+
const longForm = (
|
|
507
|
+
operation: SemanticOperationName,
|
|
508
|
+
form: "native-article" | "expanded-post",
|
|
509
|
+
note: string,
|
|
510
|
+
): LongFormAccessPolicy => ({ state: "adapter-eligible", operation, form, note });
|
|
511
|
+
|
|
512
|
+
const threadRead = (note: string): ThreadReadPolicy => ({
|
|
513
|
+
state: "adapter-eligible",
|
|
514
|
+
operation: "content.read",
|
|
515
|
+
note,
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const threadPublish = (safeMaxItems: number, note: string): ThreadPublishPolicy => ({
|
|
519
|
+
state: "adapter-eligible",
|
|
520
|
+
operation: "threads.publish",
|
|
521
|
+
rootOperation: "posts.publish",
|
|
522
|
+
continuationOperation: "replies.create",
|
|
523
|
+
safeMaxItems,
|
|
524
|
+
note,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const NA_LONG_FORM = excludedFeature("not-applicable", "This surface has no distinct native long-form article model");
|
|
528
|
+
const UNSUPPORTED_LONG_FORM = excludedFeature("unsupported", "No native long-form workflow is catalogued for this surface");
|
|
529
|
+
const NA_THREADS = excludedFeature("not-applicable", "This surface has no ordered multi-post thread model");
|
|
530
|
+
const UNSUPPORTED_THREADS = excludedFeature("unsupported", "No ordered multi-post thread workflow is catalogued for this surface");
|
|
531
|
+
|
|
532
|
+
export const socialPlatformCatalog = {
|
|
533
|
+
linkedin: {
|
|
534
|
+
id: "linkedin",
|
|
535
|
+
platform: "linkedin",
|
|
536
|
+
facet: "default",
|
|
537
|
+
displayName: "LinkedIn",
|
|
538
|
+
originPolicy: exactOrigins("https://www.linkedin.com"),
|
|
539
|
+
operations: buildOperationMatrix({
|
|
540
|
+
R1: [
|
|
541
|
+
"content.read",
|
|
542
|
+
"content.clip",
|
|
543
|
+
"profiles.read",
|
|
544
|
+
"organizations.read",
|
|
545
|
+
"contacts.list",
|
|
546
|
+
"contacts.read",
|
|
547
|
+
"feeds.read",
|
|
548
|
+
"messaging.list",
|
|
549
|
+
"messaging.read",
|
|
550
|
+
"comments.read",
|
|
551
|
+
"posts.read",
|
|
552
|
+
"media.read",
|
|
553
|
+
"articles.read",
|
|
554
|
+
"relationships.recommendations.read",
|
|
555
|
+
],
|
|
556
|
+
R2: ["reactions.set", "relationships.follow.set", "content.save", "articles.draft.save", "communities.membership.set"],
|
|
557
|
+
R3: [
|
|
558
|
+
"comments.create",
|
|
559
|
+
"replies.create",
|
|
560
|
+
"messaging.send",
|
|
561
|
+
"posts.publish",
|
|
562
|
+
"media.publish",
|
|
563
|
+
"articles.publish",
|
|
564
|
+
"relationships.connect",
|
|
565
|
+
"posts.repost",
|
|
566
|
+
"posts.quote",
|
|
567
|
+
"content.share",
|
|
568
|
+
"content.edit",
|
|
569
|
+
"content.schedule",
|
|
570
|
+
],
|
|
571
|
+
unsupported: ["threads.publish"],
|
|
572
|
+
notApplicable: ["likes.set", "listings.read", "listings.publish"],
|
|
573
|
+
R4: [
|
|
574
|
+
"content.delete",
|
|
575
|
+
"content.audience.set",
|
|
576
|
+
"communities.membership.manage",
|
|
577
|
+
"administration.manage",
|
|
578
|
+
"commerce.purchase",
|
|
579
|
+
"account.delete",
|
|
580
|
+
"moderation.bulk",
|
|
581
|
+
],
|
|
582
|
+
}),
|
|
583
|
+
compositions: {
|
|
584
|
+
message: {
|
|
585
|
+
text: [field("body", 8_000, "utf16-code-units", false)],
|
|
586
|
+
attachments: attachments(1, ["image", "video", "document", "link"], "One reviewed message attachment; message-request restrictions remain provider-enforced"),
|
|
587
|
+
},
|
|
588
|
+
comment: {
|
|
589
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
590
|
+
attachments: noAttachments("Comment attachments are not catalogued"),
|
|
591
|
+
},
|
|
592
|
+
reply: {
|
|
593
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
594
|
+
attachments: noAttachments("Reply attachments are not catalogued"),
|
|
595
|
+
},
|
|
596
|
+
post: {
|
|
597
|
+
text: [field("body", 3_000, "utf16-code-units")],
|
|
598
|
+
attachments: attachments(20, ["image", "video", "document", "link"], "Up to 20 images, or one reviewed video, document, or link card; adapters must enforce the media-kind union"),
|
|
599
|
+
},
|
|
600
|
+
media: {
|
|
601
|
+
text: [field("body", 3_000, "utf16-code-units"), field("title", 200, "utf16-code-units", false)],
|
|
602
|
+
attachments: attachments(1, ["video"], "Exactly one reviewed video upload", 1),
|
|
603
|
+
},
|
|
604
|
+
article: {
|
|
605
|
+
text: [field("title", 150, "utf16-code-units"), field("body", 125_000, "utf16-code-units")],
|
|
606
|
+
attachments: attachments(1, ["image", "link"], "One reviewed cover image or source link"),
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
longForm: {
|
|
610
|
+
read: longForm("articles.read", "native-article", "Read one LinkedIn article"),
|
|
611
|
+
publish: longForm("articles.publish", "native-article", "Publish one LinkedIn article through a reviewed authenticated web-session contract"),
|
|
612
|
+
},
|
|
613
|
+
threads: { read: UNSUPPORTED_THREADS, publish: UNSUPPORTED_THREADS },
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
x: {
|
|
617
|
+
id: "x",
|
|
618
|
+
platform: "x",
|
|
619
|
+
facet: "default",
|
|
620
|
+
displayName: "X",
|
|
621
|
+
originPolicy: exactOrigins("https://x.com"),
|
|
622
|
+
operations: buildOperationMatrix({
|
|
623
|
+
R1: ["content.read", "content.clip", "profiles.read", "messaging.read", "comments.read", "posts.read", "media.read", "articles.read"],
|
|
624
|
+
R2: ["likes.set", "relationships.follow.set", "content.save", "articles.draft.save", "communities.membership.set"],
|
|
625
|
+
R3: [
|
|
626
|
+
"messaging.send",
|
|
627
|
+
"replies.create",
|
|
628
|
+
"posts.publish",
|
|
629
|
+
"threads.publish",
|
|
630
|
+
"articles.publish",
|
|
631
|
+
"posts.repost",
|
|
632
|
+
"posts.quote",
|
|
633
|
+
"content.share",
|
|
634
|
+
"content.edit",
|
|
635
|
+
"content.delete",
|
|
636
|
+
"content.schedule",
|
|
637
|
+
],
|
|
638
|
+
unsupported: ["media.publish"],
|
|
639
|
+
notApplicable: ["comments.create", "reactions.set", "listings.read", "listings.publish", "relationships.connect"],
|
|
640
|
+
R4: [
|
|
641
|
+
"content.audience.set",
|
|
642
|
+
"communities.membership.manage",
|
|
643
|
+
"administration.manage",
|
|
644
|
+
"commerce.purchase",
|
|
645
|
+
"account.delete",
|
|
646
|
+
"moderation.bulk",
|
|
647
|
+
],
|
|
648
|
+
}),
|
|
649
|
+
compositions: {
|
|
650
|
+
message: {
|
|
651
|
+
text: [field("body", 1_000, "x-conservative-weighted", false)],
|
|
652
|
+
attachments: attachments(1, ["image", "video", "gif", "link"], "One reviewed message attachment"),
|
|
653
|
+
},
|
|
654
|
+
reply: {
|
|
655
|
+
text: [field("body", 280, "x-conservative-weighted", false)],
|
|
656
|
+
attachments: attachments(4, ["image", "video", "gif", "link"], "Up to four images, or one GIF/video; adapters must enforce the media-kind union"),
|
|
657
|
+
},
|
|
658
|
+
post: {
|
|
659
|
+
// Reviewed CreateTweet tweet_text bound. Do not keep a leftover 280 here;
|
|
660
|
+
// thread-split still uses min(post, reply) so reply stays the short-item bound.
|
|
661
|
+
text: [field("body", 25_000, "x-conservative-weighted", false)],
|
|
662
|
+
attachments: attachments(4, ["image", "video", "gif", "link"], "Up to four images, or one GIF/video; adapters must enforce the media-kind union"),
|
|
663
|
+
},
|
|
664
|
+
article: {
|
|
665
|
+
text: [field("title", 100, "x-conservative-weighted"), field("body", 20_000, "x-conservative-weighted")],
|
|
666
|
+
attachments: attachments(1, ["image", "link"], "One reviewed native-article attachment"),
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
longForm: {
|
|
670
|
+
read: longForm("articles.read", "native-article", "Read one native X article when entitled"),
|
|
671
|
+
publish: longForm("articles.publish", "native-article", "Publish one native X article when entitled"),
|
|
672
|
+
},
|
|
673
|
+
threads: {
|
|
674
|
+
read: threadRead("Read one bounded X post thread"),
|
|
675
|
+
publish: threadPublish(25, "Publish one root post optionally followed by bounded self-replies"),
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
|
|
679
|
+
reddit: {
|
|
680
|
+
id: "reddit",
|
|
681
|
+
platform: "reddit",
|
|
682
|
+
facet: "default",
|
|
683
|
+
displayName: "Reddit",
|
|
684
|
+
originPolicy: exactOrigins("https://www.reddit.com"),
|
|
685
|
+
operations: buildOperationMatrix({
|
|
686
|
+
R1: ["content.read", "content.clip", "profiles.read", "messaging.read", "comments.read", "posts.read", "media.read"],
|
|
687
|
+
R2: ["reactions.set", "relationships.follow.set", "content.save", "communities.membership.set"],
|
|
688
|
+
R3: [
|
|
689
|
+
"messaging.send",
|
|
690
|
+
"comments.create",
|
|
691
|
+
"replies.create",
|
|
692
|
+
"posts.publish",
|
|
693
|
+
"media.publish",
|
|
694
|
+
"posts.repost",
|
|
695
|
+
"content.share",
|
|
696
|
+
"content.edit",
|
|
697
|
+
"content.delete",
|
|
698
|
+
],
|
|
699
|
+
unsupported: ["content.schedule"],
|
|
700
|
+
notApplicable: [
|
|
701
|
+
"likes.set",
|
|
702
|
+
"articles.read",
|
|
703
|
+
"articles.draft.save",
|
|
704
|
+
"articles.publish",
|
|
705
|
+
"listings.read",
|
|
706
|
+
"listings.publish",
|
|
707
|
+
"relationships.connect",
|
|
708
|
+
"posts.quote",
|
|
709
|
+
"threads.publish",
|
|
710
|
+
],
|
|
711
|
+
R4: [
|
|
712
|
+
"content.audience.set",
|
|
713
|
+
"communities.membership.manage",
|
|
714
|
+
"administration.manage",
|
|
715
|
+
"commerce.purchase",
|
|
716
|
+
"account.delete",
|
|
717
|
+
"moderation.bulk",
|
|
718
|
+
],
|
|
719
|
+
}),
|
|
720
|
+
compositions: {
|
|
721
|
+
message: {
|
|
722
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
723
|
+
attachments: noAttachments("Message attachments are not catalogued"),
|
|
724
|
+
},
|
|
725
|
+
comment: {
|
|
726
|
+
text: [field("body", 2_000, "utf16-code-units")],
|
|
727
|
+
attachments: noAttachments("Comment attachments are not catalogued"),
|
|
728
|
+
},
|
|
729
|
+
reply: {
|
|
730
|
+
text: [field("body", 2_000, "utf16-code-units")],
|
|
731
|
+
attachments: noAttachments("Reply attachments are not catalogued"),
|
|
732
|
+
},
|
|
733
|
+
post: {
|
|
734
|
+
text: [field("title", 280, "utf16-code-units"), field("body", 10_000, "utf16-code-units", false)],
|
|
735
|
+
attachments: attachments(1, ["image", "video", "link"], "One reviewed post attachment"),
|
|
736
|
+
},
|
|
737
|
+
media: {
|
|
738
|
+
text: [field("title", 280, "utf16-code-units"), field("body", 10_000, "utf16-code-units", false)],
|
|
739
|
+
attachments: attachments(1, ["video"], "Exactly one reviewed video upload", 1),
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
longForm: {
|
|
743
|
+
read: longForm("posts.read", "expanded-post", "Read one native long self-post"),
|
|
744
|
+
publish: longForm("posts.publish", "expanded-post", "Publish one native long self-post"),
|
|
745
|
+
},
|
|
746
|
+
threads: { read: NA_THREADS, publish: NA_THREADS },
|
|
747
|
+
},
|
|
748
|
+
|
|
749
|
+
github: {
|
|
750
|
+
id: "github",
|
|
751
|
+
platform: "github",
|
|
752
|
+
facet: "default",
|
|
753
|
+
displayName: "GitHub",
|
|
754
|
+
originPolicy: exactOrigins("https://github.com"),
|
|
755
|
+
operations: buildOperationMatrix({
|
|
756
|
+
R1: ["content.read", "content.clip", "profiles.read", "organizations.read"],
|
|
757
|
+
R2: [],
|
|
758
|
+
R3: [],
|
|
759
|
+
unsupported: [
|
|
760
|
+
"messaging.read",
|
|
761
|
+
"messaging.send",
|
|
762
|
+
"comments.read",
|
|
763
|
+
"comments.create",
|
|
764
|
+
"replies.create",
|
|
765
|
+
"posts.read",
|
|
766
|
+
"posts.publish",
|
|
767
|
+
"threads.publish",
|
|
768
|
+
"likes.set",
|
|
769
|
+
"reactions.set",
|
|
770
|
+
"media.read",
|
|
771
|
+
"media.publish",
|
|
772
|
+
"articles.read",
|
|
773
|
+
"articles.draft.save",
|
|
774
|
+
"articles.publish",
|
|
775
|
+
"listings.read",
|
|
776
|
+
"listings.publish",
|
|
777
|
+
"relationships.recommendations.read",
|
|
778
|
+
"relationships.follow.set",
|
|
779
|
+
"relationships.connect",
|
|
780
|
+
"posts.repost",
|
|
781
|
+
"posts.quote",
|
|
782
|
+
"content.share",
|
|
783
|
+
"content.save",
|
|
784
|
+
"content.edit",
|
|
785
|
+
"content.schedule",
|
|
786
|
+
"content.audience.set",
|
|
787
|
+
"communities.membership.set",
|
|
788
|
+
],
|
|
789
|
+
notApplicable: [],
|
|
790
|
+
R4: [
|
|
791
|
+
"content.delete",
|
|
792
|
+
"communities.membership.manage",
|
|
793
|
+
"administration.manage",
|
|
794
|
+
"commerce.purchase",
|
|
795
|
+
"account.delete",
|
|
796
|
+
"moderation.bulk",
|
|
797
|
+
],
|
|
798
|
+
}),
|
|
799
|
+
compositions: {},
|
|
800
|
+
longForm: {
|
|
801
|
+
read: UNSUPPORTED_LONG_FORM,
|
|
802
|
+
publish: UNSUPPORTED_LONG_FORM,
|
|
803
|
+
},
|
|
804
|
+
threads: {
|
|
805
|
+
read: UNSUPPORTED_THREADS,
|
|
806
|
+
publish: UNSUPPORTED_THREADS,
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
|
|
810
|
+
"hacker-news": {
|
|
811
|
+
id: "hacker-news",
|
|
812
|
+
platform: "hacker-news",
|
|
813
|
+
facet: "default",
|
|
814
|
+
displayName: "Hacker News",
|
|
815
|
+
originPolicy: exactOrigins("https://news.ycombinator.com"),
|
|
816
|
+
operations: buildOperationMatrix({
|
|
817
|
+
R1: ["content.read", "content.clip", "comments.read", "posts.read"],
|
|
818
|
+
R2: ["reactions.set", "content.save"],
|
|
819
|
+
R3: ["comments.create", "replies.create", "posts.publish", "content.edit"],
|
|
820
|
+
unsupported: [],
|
|
821
|
+
notApplicable: [
|
|
822
|
+
"messaging.read",
|
|
823
|
+
"messaging.send",
|
|
824
|
+
"likes.set",
|
|
825
|
+
"media.read",
|
|
826
|
+
"media.publish",
|
|
827
|
+
"articles.read",
|
|
828
|
+
"articles.draft.save",
|
|
829
|
+
"articles.publish",
|
|
830
|
+
"listings.read",
|
|
831
|
+
"listings.publish",
|
|
832
|
+
"relationships.follow.set",
|
|
833
|
+
"relationships.connect",
|
|
834
|
+
"posts.repost",
|
|
835
|
+
"posts.quote",
|
|
836
|
+
"threads.publish",
|
|
837
|
+
"content.share",
|
|
838
|
+
"content.schedule",
|
|
839
|
+
"content.audience.set",
|
|
840
|
+
"communities.membership.set",
|
|
841
|
+
"communities.membership.manage",
|
|
842
|
+
"commerce.purchase",
|
|
843
|
+
],
|
|
844
|
+
R4: ["content.delete", "administration.manage", "account.delete", "moderation.bulk"],
|
|
845
|
+
}),
|
|
846
|
+
compositions: {
|
|
847
|
+
comment: {
|
|
848
|
+
text: [field("body", 2_000, "utf16-code-units")],
|
|
849
|
+
attachments: noAttachments("Comments are text-only in the catalog"),
|
|
850
|
+
},
|
|
851
|
+
reply: {
|
|
852
|
+
text: [field("body", 2_000, "utf16-code-units")],
|
|
853
|
+
attachments: noAttachments("Replies are text-only in the catalog"),
|
|
854
|
+
},
|
|
855
|
+
post: {
|
|
856
|
+
text: [field("title", 80, "utf16-code-units"), field("body", 2_000, "utf16-code-units", false)],
|
|
857
|
+
attachments: attachments(1, ["link"], "One explicitly supplied destination link"),
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
longForm: {
|
|
861
|
+
read: longForm("posts.read", "expanded-post", "Read one Ask or Show text post"),
|
|
862
|
+
publish: longForm("posts.publish", "expanded-post", "Publish one bounded text post"),
|
|
863
|
+
},
|
|
864
|
+
threads: { read: NA_THREADS, publish: NA_THREADS },
|
|
865
|
+
},
|
|
866
|
+
|
|
867
|
+
whatsapp: {
|
|
868
|
+
id: "whatsapp",
|
|
869
|
+
platform: "whatsapp",
|
|
870
|
+
facet: "default",
|
|
871
|
+
displayName: "WhatsApp Web",
|
|
872
|
+
originPolicy: exactOrigins("https://web.whatsapp.com"),
|
|
873
|
+
operations: buildOperationMatrix({
|
|
874
|
+
R1: ["content.read", "contacts.list", "messaging.read", "media.read"],
|
|
875
|
+
R2: ["reactions.set", "content.save"],
|
|
876
|
+
R3: ["messaging.send", "content.share", "content.edit"],
|
|
877
|
+
unsupported: ["content.clip", "media.publish", "communities.membership.set"],
|
|
878
|
+
notApplicable: [
|
|
879
|
+
"comments.read",
|
|
880
|
+
"comments.create",
|
|
881
|
+
"replies.create",
|
|
882
|
+
"posts.read",
|
|
883
|
+
"posts.publish",
|
|
884
|
+
"threads.publish",
|
|
885
|
+
"likes.set",
|
|
886
|
+
"articles.read",
|
|
887
|
+
"articles.draft.save",
|
|
888
|
+
"articles.publish",
|
|
889
|
+
"listings.read",
|
|
890
|
+
"listings.publish",
|
|
891
|
+
"relationships.follow.set",
|
|
892
|
+
"relationships.connect",
|
|
893
|
+
"posts.repost",
|
|
894
|
+
"posts.quote",
|
|
895
|
+
"content.schedule",
|
|
896
|
+
"content.audience.set",
|
|
897
|
+
],
|
|
898
|
+
R4: [
|
|
899
|
+
"content.delete",
|
|
900
|
+
"communities.membership.manage",
|
|
901
|
+
"administration.manage",
|
|
902
|
+
"commerce.purchase",
|
|
903
|
+
"account.delete",
|
|
904
|
+
"moderation.bulk",
|
|
905
|
+
],
|
|
906
|
+
}),
|
|
907
|
+
compositions: {
|
|
908
|
+
message: {
|
|
909
|
+
text: [field("body", 2_000, "utf16-code-units")],
|
|
910
|
+
attachments: attachments(1, ["image", "video", "audio", "document", "link"], "One explicitly selected message attachment"),
|
|
911
|
+
},
|
|
912
|
+
},
|
|
913
|
+
longForm: { read: NA_LONG_FORM, publish: NA_LONG_FORM },
|
|
914
|
+
threads: { read: NA_THREADS, publish: NA_THREADS },
|
|
915
|
+
},
|
|
916
|
+
|
|
917
|
+
substack: {
|
|
918
|
+
id: "substack",
|
|
919
|
+
platform: "substack",
|
|
920
|
+
facet: "default",
|
|
921
|
+
displayName: "Substack",
|
|
922
|
+
originPolicy: publicationOrigins("https://substack.com", "https://www.substack.com"),
|
|
923
|
+
operations: buildOperationMatrix({
|
|
924
|
+
R1: ["content.read", "content.clip", "profiles.read", "organizations.read", "messaging.read", "comments.read", "posts.read", "media.read", "articles.read"],
|
|
925
|
+
R2: ["likes.set", "relationships.follow.set", "content.save"],
|
|
926
|
+
R3: [
|
|
927
|
+
"messaging.send",
|
|
928
|
+
"comments.create",
|
|
929
|
+
"replies.create",
|
|
930
|
+
"posts.publish",
|
|
931
|
+
"media.publish",
|
|
932
|
+
"articles.publish",
|
|
933
|
+
"posts.repost",
|
|
934
|
+
"posts.quote",
|
|
935
|
+
"content.share",
|
|
936
|
+
"content.edit",
|
|
937
|
+
"content.delete",
|
|
938
|
+
"content.schedule",
|
|
939
|
+
],
|
|
940
|
+
unsupported: ["articles.draft.save", "threads.publish", "communities.membership.set"],
|
|
941
|
+
notApplicable: ["reactions.set", "listings.read", "listings.publish", "relationships.connect"],
|
|
942
|
+
R4: [
|
|
943
|
+
"content.audience.set",
|
|
944
|
+
"communities.membership.manage",
|
|
945
|
+
"administration.manage",
|
|
946
|
+
"commerce.purchase",
|
|
947
|
+
"account.delete",
|
|
948
|
+
"moderation.bulk",
|
|
949
|
+
],
|
|
950
|
+
}),
|
|
951
|
+
compositions: {
|
|
952
|
+
message: {
|
|
953
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
954
|
+
attachments: noAttachments("Chat attachments are not catalogued"),
|
|
955
|
+
},
|
|
956
|
+
comment: {
|
|
957
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
958
|
+
attachments: noAttachments("Comment attachments are not catalogued"),
|
|
959
|
+
},
|
|
960
|
+
reply: {
|
|
961
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
962
|
+
attachments: noAttachments("Reply attachments are not catalogued"),
|
|
963
|
+
},
|
|
964
|
+
post: {
|
|
965
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
966
|
+
attachments: attachments(1, ["image", "video", "link"], "One reviewed Note attachment"),
|
|
967
|
+
},
|
|
968
|
+
media: {
|
|
969
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
970
|
+
attachments: attachments(1, ["video"], "Exactly one reviewed Note video upload", 1),
|
|
971
|
+
},
|
|
972
|
+
article: {
|
|
973
|
+
text: [field("title", 160, "utf16-code-units"), field("body", 50_000, "utf16-code-units")],
|
|
974
|
+
attachments: attachments(1, ["image", "video", "audio", "link"], "One reviewed lead attachment; rich body media needs separate review"),
|
|
975
|
+
},
|
|
976
|
+
},
|
|
977
|
+
longForm: {
|
|
978
|
+
read: longForm("articles.read", "native-article", "Read one entitled publication article"),
|
|
979
|
+
publish: longForm("articles.publish", "native-article", "Publish one publication article"),
|
|
980
|
+
},
|
|
981
|
+
threads: { read: UNSUPPORTED_THREADS, publish: UNSUPPORTED_THREADS },
|
|
982
|
+
},
|
|
983
|
+
|
|
984
|
+
instagram: {
|
|
985
|
+
id: "instagram",
|
|
986
|
+
platform: "instagram",
|
|
987
|
+
facet: "default",
|
|
988
|
+
displayName: "Instagram",
|
|
989
|
+
originPolicy: exactOrigins("https://www.instagram.com"),
|
|
990
|
+
operations: buildOperationMatrix({
|
|
991
|
+
R1: ["content.read", "content.clip", "profiles.read", "contacts.list", "messaging.read", "comments.read", "posts.read", "media.read"],
|
|
992
|
+
R2: ["reactions.set", "likes.set", "relationships.follow.set", "content.save"],
|
|
993
|
+
R3: [
|
|
994
|
+
"messaging.send",
|
|
995
|
+
"comments.create",
|
|
996
|
+
"replies.create",
|
|
997
|
+
"media.publish",
|
|
998
|
+
"content.delete",
|
|
999
|
+
"posts.repost",
|
|
1000
|
+
"content.share",
|
|
1001
|
+
"content.edit",
|
|
1002
|
+
],
|
|
1003
|
+
unsupported: ["content.schedule"],
|
|
1004
|
+
notApplicable: [
|
|
1005
|
+
"posts.publish",
|
|
1006
|
+
"threads.publish",
|
|
1007
|
+
"articles.read",
|
|
1008
|
+
"articles.draft.save",
|
|
1009
|
+
"articles.publish",
|
|
1010
|
+
"listings.read",
|
|
1011
|
+
"listings.publish",
|
|
1012
|
+
"relationships.connect",
|
|
1013
|
+
"posts.quote",
|
|
1014
|
+
"communities.membership.set",
|
|
1015
|
+
"communities.membership.manage",
|
|
1016
|
+
],
|
|
1017
|
+
R4: [
|
|
1018
|
+
"content.audience.set",
|
|
1019
|
+
"administration.manage",
|
|
1020
|
+
"commerce.purchase",
|
|
1021
|
+
"account.delete",
|
|
1022
|
+
"moderation.bulk",
|
|
1023
|
+
],
|
|
1024
|
+
}),
|
|
1025
|
+
compositions: {
|
|
1026
|
+
message: {
|
|
1027
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
1028
|
+
attachments: attachments(1, ["image", "video", "link"], "One explicitly selected message attachment"),
|
|
1029
|
+
},
|
|
1030
|
+
comment: {
|
|
1031
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1032
|
+
attachments: noAttachments("Comment attachments are not catalogued"),
|
|
1033
|
+
},
|
|
1034
|
+
reply: {
|
|
1035
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1036
|
+
attachments: noAttachments("Reply attachments are not catalogued"),
|
|
1037
|
+
},
|
|
1038
|
+
media: {
|
|
1039
|
+
text: [field("caption", 1_000, "utf16-code-units")],
|
|
1040
|
+
attachments: attachments(1, ["video"], "Exactly one plan-bound MP4 video", 1),
|
|
1041
|
+
},
|
|
1042
|
+
},
|
|
1043
|
+
longForm: { read: NA_LONG_FORM, publish: NA_LONG_FORM },
|
|
1044
|
+
threads: { read: NA_THREADS, publish: NA_THREADS },
|
|
1045
|
+
},
|
|
1046
|
+
|
|
1047
|
+
threads: {
|
|
1048
|
+
id: "threads",
|
|
1049
|
+
platform: "threads",
|
|
1050
|
+
facet: "default",
|
|
1051
|
+
displayName: "Threads",
|
|
1052
|
+
originPolicy: exactOrigins("https://www.threads.com"),
|
|
1053
|
+
operations: buildOperationMatrix({
|
|
1054
|
+
R1: ["content.read", "content.clip", "profiles.read", "messaging.read", "comments.read", "posts.read", "media.read"],
|
|
1055
|
+
R2: ["likes.set", "relationships.follow.set", "content.save"],
|
|
1056
|
+
R3: [
|
|
1057
|
+
"messaging.send",
|
|
1058
|
+
"replies.create",
|
|
1059
|
+
"posts.publish",
|
|
1060
|
+
"media.publish",
|
|
1061
|
+
"threads.publish",
|
|
1062
|
+
"posts.repost",
|
|
1063
|
+
"posts.quote",
|
|
1064
|
+
"content.share",
|
|
1065
|
+
"content.edit",
|
|
1066
|
+
],
|
|
1067
|
+
unsupported: ["articles.read", "articles.draft.save", "articles.publish", "content.schedule"],
|
|
1068
|
+
notApplicable: [
|
|
1069
|
+
"comments.create",
|
|
1070
|
+
"reactions.set",
|
|
1071
|
+
"listings.read",
|
|
1072
|
+
"listings.publish",
|
|
1073
|
+
"relationships.connect",
|
|
1074
|
+
"communities.membership.set",
|
|
1075
|
+
"communities.membership.manage",
|
|
1076
|
+
"commerce.purchase",
|
|
1077
|
+
],
|
|
1078
|
+
R4: ["content.delete", "content.audience.set", "administration.manage", "account.delete", "moderation.bulk"],
|
|
1079
|
+
}),
|
|
1080
|
+
compositions: {
|
|
1081
|
+
message: {
|
|
1082
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
1083
|
+
attachments: attachments(1, ["image", "video", "link"], "One explicitly selected message attachment"),
|
|
1084
|
+
},
|
|
1085
|
+
reply: {
|
|
1086
|
+
text: [field("body", 450, "unicode-code-points")],
|
|
1087
|
+
attachments: attachments(1, ["image", "video", "gif", "link"], "One reviewed reply attachment"),
|
|
1088
|
+
},
|
|
1089
|
+
post: {
|
|
1090
|
+
text: [field("body", 450, "unicode-code-points")],
|
|
1091
|
+
attachments: attachments(1, ["image", "video", "gif", "link"], "One reviewed post attachment"),
|
|
1092
|
+
},
|
|
1093
|
+
media: {
|
|
1094
|
+
text: [field("body", 450, "unicode-code-points")],
|
|
1095
|
+
attachments: attachments(1, ["video"], "Exactly one reviewed video upload", 1),
|
|
1096
|
+
},
|
|
1097
|
+
},
|
|
1098
|
+
longForm: { read: UNSUPPORTED_LONG_FORM, publish: UNSUPPORTED_LONG_FORM },
|
|
1099
|
+
threads: {
|
|
1100
|
+
read: threadRead("Read one bounded Threads post thread"),
|
|
1101
|
+
publish: threadPublish(25, "Publish one root post optionally followed by bounded self-replies"),
|
|
1102
|
+
},
|
|
1103
|
+
},
|
|
1104
|
+
|
|
1105
|
+
facebook: {
|
|
1106
|
+
id: "facebook",
|
|
1107
|
+
platform: "facebook",
|
|
1108
|
+
facet: "default",
|
|
1109
|
+
displayName: "Facebook",
|
|
1110
|
+
originPolicy: exactOrigins("https://www.facebook.com"),
|
|
1111
|
+
operations: buildOperationMatrix({
|
|
1112
|
+
R1: ["content.read", "content.clip", "contacts.list", "messaging.read", "comments.read", "posts.read", "media.read"],
|
|
1113
|
+
R2: ["reactions.set", "likes.set", "relationships.follow.set", "content.save"],
|
|
1114
|
+
R3: [
|
|
1115
|
+
"messaging.send",
|
|
1116
|
+
"comments.create",
|
|
1117
|
+
"replies.create",
|
|
1118
|
+
"posts.publish",
|
|
1119
|
+
"media.publish",
|
|
1120
|
+
"posts.repost",
|
|
1121
|
+
"posts.quote",
|
|
1122
|
+
"content.share",
|
|
1123
|
+
"content.edit",
|
|
1124
|
+
],
|
|
1125
|
+
unsupported: ["content.schedule"],
|
|
1126
|
+
notApplicable: [
|
|
1127
|
+
"threads.publish",
|
|
1128
|
+
"articles.read",
|
|
1129
|
+
"articles.draft.save",
|
|
1130
|
+
"articles.publish",
|
|
1131
|
+
"listings.read",
|
|
1132
|
+
"listings.publish",
|
|
1133
|
+
"relationships.connect",
|
|
1134
|
+
"communities.membership.set",
|
|
1135
|
+
"communities.membership.manage",
|
|
1136
|
+
],
|
|
1137
|
+
R4: [
|
|
1138
|
+
"content.delete",
|
|
1139
|
+
"content.audience.set",
|
|
1140
|
+
"administration.manage",
|
|
1141
|
+
"commerce.purchase",
|
|
1142
|
+
"account.delete",
|
|
1143
|
+
"moderation.bulk",
|
|
1144
|
+
],
|
|
1145
|
+
}),
|
|
1146
|
+
compositions: {
|
|
1147
|
+
message: {
|
|
1148
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
1149
|
+
attachments: attachments(1, ["image", "video", "document", "link"], "One explicitly selected message attachment"),
|
|
1150
|
+
},
|
|
1151
|
+
comment: {
|
|
1152
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1153
|
+
attachments: noAttachments("Comment attachments are not catalogued"),
|
|
1154
|
+
},
|
|
1155
|
+
reply: {
|
|
1156
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1157
|
+
attachments: noAttachments("Reply attachments are not catalogued"),
|
|
1158
|
+
},
|
|
1159
|
+
post: {
|
|
1160
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
1161
|
+
attachments: attachments(1, ["image", "video", "link"], "One reviewed personal-profile post attachment"),
|
|
1162
|
+
},
|
|
1163
|
+
media: {
|
|
1164
|
+
text: [field("caption", 1_000, "utf16-code-units", false)],
|
|
1165
|
+
attachments: attachments(1, ["image", "video"], "Exactly one reviewed personal-profile media item", 1),
|
|
1166
|
+
},
|
|
1167
|
+
},
|
|
1168
|
+
longForm: {
|
|
1169
|
+
read: longForm("posts.read", "expanded-post", "Read one expanded personal-profile post"),
|
|
1170
|
+
publish: longForm("posts.publish", "expanded-post", "Publish one expanded personal-profile post"),
|
|
1171
|
+
},
|
|
1172
|
+
threads: { read: NA_THREADS, publish: NA_THREADS },
|
|
1173
|
+
},
|
|
1174
|
+
|
|
1175
|
+
"facebook-page": {
|
|
1176
|
+
id: "facebook-page",
|
|
1177
|
+
platform: "facebook",
|
|
1178
|
+
facet: "page",
|
|
1179
|
+
displayName: "Facebook Page",
|
|
1180
|
+
originPolicy: exactOrigins("https://www.facebook.com"),
|
|
1181
|
+
operations: buildOperationMatrix({
|
|
1182
|
+
R1: ["content.read", "content.clip", "messaging.read", "comments.read", "posts.read", "media.read"],
|
|
1183
|
+
R2: ["reactions.set", "likes.set", "relationships.follow.set", "content.save"],
|
|
1184
|
+
R3: [
|
|
1185
|
+
"messaging.send",
|
|
1186
|
+
"comments.create",
|
|
1187
|
+
"replies.create",
|
|
1188
|
+
"posts.publish",
|
|
1189
|
+
"media.publish",
|
|
1190
|
+
"posts.repost",
|
|
1191
|
+
"posts.quote",
|
|
1192
|
+
"content.share",
|
|
1193
|
+
"content.edit",
|
|
1194
|
+
"content.schedule",
|
|
1195
|
+
],
|
|
1196
|
+
unsupported: [],
|
|
1197
|
+
notApplicable: [
|
|
1198
|
+
"articles.read",
|
|
1199
|
+
"articles.draft.save",
|
|
1200
|
+
"articles.publish",
|
|
1201
|
+
"threads.publish",
|
|
1202
|
+
"listings.read",
|
|
1203
|
+
"listings.publish",
|
|
1204
|
+
"relationships.connect",
|
|
1205
|
+
"communities.membership.set",
|
|
1206
|
+
"communities.membership.manage",
|
|
1207
|
+
],
|
|
1208
|
+
R4: [
|
|
1209
|
+
"content.delete",
|
|
1210
|
+
"content.audience.set",
|
|
1211
|
+
"administration.manage",
|
|
1212
|
+
"commerce.purchase",
|
|
1213
|
+
"account.delete",
|
|
1214
|
+
"moderation.bulk",
|
|
1215
|
+
],
|
|
1216
|
+
}),
|
|
1217
|
+
compositions: {
|
|
1218
|
+
message: {
|
|
1219
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
1220
|
+
attachments: attachments(1, ["image", "video", "document", "link"], "One explicitly selected message attachment"),
|
|
1221
|
+
},
|
|
1222
|
+
comment: {
|
|
1223
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1224
|
+
attachments: noAttachments("Comment attachments are not catalogued"),
|
|
1225
|
+
},
|
|
1226
|
+
reply: {
|
|
1227
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1228
|
+
attachments: noAttachments("Reply attachments are not catalogued"),
|
|
1229
|
+
},
|
|
1230
|
+
post: {
|
|
1231
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
1232
|
+
attachments: attachments(1, ["image", "video", "link"], "One reviewed Page post attachment"),
|
|
1233
|
+
},
|
|
1234
|
+
media: {
|
|
1235
|
+
text: [field("caption", 1_000, "utf16-code-units", false)],
|
|
1236
|
+
attachments: attachments(1, ["image", "video"], "Exactly one reviewed Page media item", 1),
|
|
1237
|
+
},
|
|
1238
|
+
},
|
|
1239
|
+
longForm: {
|
|
1240
|
+
read: longForm("posts.read", "expanded-post", "Read one expanded Page post"),
|
|
1241
|
+
publish: longForm("posts.publish", "expanded-post", "Publish one expanded Page post"),
|
|
1242
|
+
},
|
|
1243
|
+
threads: { read: NA_THREADS, publish: NA_THREADS },
|
|
1244
|
+
},
|
|
1245
|
+
|
|
1246
|
+
"facebook-group": {
|
|
1247
|
+
id: "facebook-group",
|
|
1248
|
+
platform: "facebook",
|
|
1249
|
+
facet: "group",
|
|
1250
|
+
displayName: "Facebook Group",
|
|
1251
|
+
originPolicy: exactOrigins("https://www.facebook.com"),
|
|
1252
|
+
operations: buildOperationMatrix({
|
|
1253
|
+
R1: [
|
|
1254
|
+
"content.read",
|
|
1255
|
+
"content.clip",
|
|
1256
|
+
"feeds.read",
|
|
1257
|
+
"comments.read",
|
|
1258
|
+
"posts.read",
|
|
1259
|
+
"media.read",
|
|
1260
|
+
],
|
|
1261
|
+
R2: ["reactions.set", "likes.set", "content.save", "communities.membership.set"],
|
|
1262
|
+
R3: [
|
|
1263
|
+
"comments.create",
|
|
1264
|
+
"replies.create",
|
|
1265
|
+
"posts.publish",
|
|
1266
|
+
"media.publish",
|
|
1267
|
+
"posts.repost",
|
|
1268
|
+
"posts.quote",
|
|
1269
|
+
"content.share",
|
|
1270
|
+
"content.edit",
|
|
1271
|
+
],
|
|
1272
|
+
unsupported: ["content.schedule"],
|
|
1273
|
+
notApplicable: [
|
|
1274
|
+
"messaging.read",
|
|
1275
|
+
"messaging.send",
|
|
1276
|
+
"threads.publish",
|
|
1277
|
+
"articles.read",
|
|
1278
|
+
"articles.draft.save",
|
|
1279
|
+
"articles.publish",
|
|
1280
|
+
"listings.read",
|
|
1281
|
+
"listings.publish",
|
|
1282
|
+
"relationships.follow.set",
|
|
1283
|
+
"relationships.connect",
|
|
1284
|
+
"commerce.purchase",
|
|
1285
|
+
],
|
|
1286
|
+
R4: [
|
|
1287
|
+
"content.delete",
|
|
1288
|
+
"content.audience.set",
|
|
1289
|
+
"communities.membership.manage",
|
|
1290
|
+
"administration.manage",
|
|
1291
|
+
"account.delete",
|
|
1292
|
+
"moderation.bulk",
|
|
1293
|
+
],
|
|
1294
|
+
}),
|
|
1295
|
+
compositions: {
|
|
1296
|
+
comment: {
|
|
1297
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1298
|
+
attachments: noAttachments("Comment attachments are not catalogued"),
|
|
1299
|
+
},
|
|
1300
|
+
reply: {
|
|
1301
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1302
|
+
attachments: noAttachments("Reply attachments are not catalogued"),
|
|
1303
|
+
},
|
|
1304
|
+
post: {
|
|
1305
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
1306
|
+
attachments: attachments(1, ["image", "video", "link"], "One reviewed Group post attachment"),
|
|
1307
|
+
},
|
|
1308
|
+
media: {
|
|
1309
|
+
text: [field("caption", 1_000, "utf16-code-units", false)],
|
|
1310
|
+
attachments: attachments(1, ["image", "video"], "Exactly one reviewed Group media item", 1),
|
|
1311
|
+
},
|
|
1312
|
+
},
|
|
1313
|
+
longForm: {
|
|
1314
|
+
read: longForm("posts.read", "expanded-post", "Read one expanded Group post"),
|
|
1315
|
+
publish: longForm("posts.publish", "expanded-post", "Publish one expanded Group post"),
|
|
1316
|
+
},
|
|
1317
|
+
threads: { read: NA_THREADS, publish: NA_THREADS },
|
|
1318
|
+
},
|
|
1319
|
+
|
|
1320
|
+
"facebook-marketplace": {
|
|
1321
|
+
id: "facebook-marketplace",
|
|
1322
|
+
platform: "facebook",
|
|
1323
|
+
facet: "marketplace",
|
|
1324
|
+
displayName: "Facebook Marketplace",
|
|
1325
|
+
originPolicy: exactOrigins("https://www.facebook.com"),
|
|
1326
|
+
operations: buildOperationMatrix({
|
|
1327
|
+
R1: [
|
|
1328
|
+
"content.read",
|
|
1329
|
+
"content.clip",
|
|
1330
|
+
"feeds.read",
|
|
1331
|
+
"messaging.read",
|
|
1332
|
+
"media.read",
|
|
1333
|
+
"listings.read",
|
|
1334
|
+
],
|
|
1335
|
+
R2: ["content.save"],
|
|
1336
|
+
R3: ["messaging.send", "listings.publish", "content.share", "content.edit"],
|
|
1337
|
+
unsupported: ["media.publish"],
|
|
1338
|
+
notApplicable: [
|
|
1339
|
+
"comments.read",
|
|
1340
|
+
"comments.create",
|
|
1341
|
+
"replies.create",
|
|
1342
|
+
"posts.read",
|
|
1343
|
+
"posts.publish",
|
|
1344
|
+
"threads.publish",
|
|
1345
|
+
"reactions.set",
|
|
1346
|
+
"likes.set",
|
|
1347
|
+
"articles.read",
|
|
1348
|
+
"articles.draft.save",
|
|
1349
|
+
"articles.publish",
|
|
1350
|
+
"relationships.follow.set",
|
|
1351
|
+
"relationships.connect",
|
|
1352
|
+
"posts.repost",
|
|
1353
|
+
"posts.quote",
|
|
1354
|
+
"content.schedule",
|
|
1355
|
+
"content.audience.set",
|
|
1356
|
+
"communities.membership.set",
|
|
1357
|
+
"communities.membership.manage",
|
|
1358
|
+
"administration.manage",
|
|
1359
|
+
],
|
|
1360
|
+
R4: ["content.delete", "commerce.purchase", "account.delete", "moderation.bulk"],
|
|
1361
|
+
}),
|
|
1362
|
+
compositions: {
|
|
1363
|
+
message: {
|
|
1364
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
1365
|
+
attachments: noAttachments("Marketplace message attachments are not catalogued"),
|
|
1366
|
+
},
|
|
1367
|
+
listing: {
|
|
1368
|
+
text: [
|
|
1369
|
+
field("title", 80, "utf16-code-units"),
|
|
1370
|
+
field("body", 1_000, "utf16-code-units"),
|
|
1371
|
+
field("price", 24, "unicode-code-points", true, "decimal-amount"),
|
|
1372
|
+
field("currency", 3, "unicode-code-points", true, "currency-code"),
|
|
1373
|
+
field("category", 120, "unicode-code-points", true, "provider-option"),
|
|
1374
|
+
field("condition", 80, "unicode-code-points", true, "provider-option"),
|
|
1375
|
+
field("location", 160, "unicode-code-points", true, "location-label"),
|
|
1376
|
+
field("delivery", 80, "unicode-code-points", true, "provider-option"),
|
|
1377
|
+
],
|
|
1378
|
+
attachments: attachments(4, ["image"], "One to four explicitly selected listing images", 1),
|
|
1379
|
+
},
|
|
1380
|
+
},
|
|
1381
|
+
longForm: { read: NA_LONG_FORM, publish: NA_LONG_FORM },
|
|
1382
|
+
threads: { read: NA_THREADS, publish: NA_THREADS },
|
|
1383
|
+
},
|
|
1384
|
+
|
|
1385
|
+
tiktok: {
|
|
1386
|
+
id: "tiktok",
|
|
1387
|
+
platform: "tiktok",
|
|
1388
|
+
facet: "default",
|
|
1389
|
+
displayName: "TikTok",
|
|
1390
|
+
originPolicy: exactOrigins("https://www.tiktok.com"),
|
|
1391
|
+
operations: buildOperationMatrix({
|
|
1392
|
+
R1: ["content.read", "content.clip", "profiles.read", "messaging.read", "comments.read", "posts.read", "media.read"],
|
|
1393
|
+
R2: ["likes.set", "relationships.follow.set", "content.save"],
|
|
1394
|
+
R3: [
|
|
1395
|
+
"messaging.send",
|
|
1396
|
+
"comments.create",
|
|
1397
|
+
"replies.create",
|
|
1398
|
+
"posts.publish",
|
|
1399
|
+
"media.publish",
|
|
1400
|
+
"posts.repost",
|
|
1401
|
+
"content.share",
|
|
1402
|
+
"content.delete",
|
|
1403
|
+
"content.schedule",
|
|
1404
|
+
],
|
|
1405
|
+
unsupported: ["content.edit"],
|
|
1406
|
+
notApplicable: [
|
|
1407
|
+
"reactions.set",
|
|
1408
|
+
"articles.read",
|
|
1409
|
+
"articles.draft.save",
|
|
1410
|
+
"articles.publish",
|
|
1411
|
+
"threads.publish",
|
|
1412
|
+
"listings.read",
|
|
1413
|
+
"listings.publish",
|
|
1414
|
+
"relationships.connect",
|
|
1415
|
+
"posts.quote",
|
|
1416
|
+
"communities.membership.set",
|
|
1417
|
+
"communities.membership.manage",
|
|
1418
|
+
],
|
|
1419
|
+
R4: [
|
|
1420
|
+
"content.audience.set",
|
|
1421
|
+
"administration.manage",
|
|
1422
|
+
"commerce.purchase",
|
|
1423
|
+
"account.delete",
|
|
1424
|
+
"moderation.bulk",
|
|
1425
|
+
],
|
|
1426
|
+
}),
|
|
1427
|
+
compositions: {
|
|
1428
|
+
message: {
|
|
1429
|
+
text: [field("body", 1_000, "utf16-code-units")],
|
|
1430
|
+
attachments: attachments(1, ["video", "link"], "One explicitly selected message attachment"),
|
|
1431
|
+
},
|
|
1432
|
+
comment: {
|
|
1433
|
+
text: [field("body", 100, "utf16-code-units")],
|
|
1434
|
+
attachments: noAttachments("Comment attachments are not catalogued"),
|
|
1435
|
+
},
|
|
1436
|
+
reply: {
|
|
1437
|
+
text: [field("body", 100, "utf16-code-units")],
|
|
1438
|
+
attachments: noAttachments("Reply attachments are not catalogued"),
|
|
1439
|
+
},
|
|
1440
|
+
post: {
|
|
1441
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1442
|
+
attachments: noAttachments("Text-post media is handled as media.publish"),
|
|
1443
|
+
},
|
|
1444
|
+
media: {
|
|
1445
|
+
text: [field("caption", 500, "utf16-code-units", false)],
|
|
1446
|
+
attachments: attachments(1, ["image", "video"], "Exactly one reviewed media item", 1),
|
|
1447
|
+
},
|
|
1448
|
+
},
|
|
1449
|
+
longForm: { read: NA_LONG_FORM, publish: NA_LONG_FORM },
|
|
1450
|
+
threads: { read: NA_THREADS, publish: NA_THREADS },
|
|
1451
|
+
},
|
|
1452
|
+
|
|
1453
|
+
twitch: {
|
|
1454
|
+
id: "twitch",
|
|
1455
|
+
platform: "twitch",
|
|
1456
|
+
facet: "default",
|
|
1457
|
+
displayName: "Twitch",
|
|
1458
|
+
originPolicy: exactOrigins(
|
|
1459
|
+
"https://www.twitch.tv",
|
|
1460
|
+
"https://gql.twitch.tv",
|
|
1461
|
+
),
|
|
1462
|
+
operations: buildOperationMatrix({
|
|
1463
|
+
R1: ["profiles.read"],
|
|
1464
|
+
R2: [],
|
|
1465
|
+
R3: [],
|
|
1466
|
+
unsupported: [
|
|
1467
|
+
"content.read",
|
|
1468
|
+
"content.clip",
|
|
1469
|
+
"organizations.read",
|
|
1470
|
+
"contacts.list",
|
|
1471
|
+
"contacts.search",
|
|
1472
|
+
"feeds.read",
|
|
1473
|
+
"messaging.list",
|
|
1474
|
+
"messaging.search",
|
|
1475
|
+
"messaging.read",
|
|
1476
|
+
"messaging.send",
|
|
1477
|
+
"comments.read",
|
|
1478
|
+
"comments.create",
|
|
1479
|
+
"replies.create",
|
|
1480
|
+
"posts.read",
|
|
1481
|
+
"posts.publish",
|
|
1482
|
+
"threads.publish",
|
|
1483
|
+
"likes.set",
|
|
1484
|
+
"reactions.set",
|
|
1485
|
+
"media.read",
|
|
1486
|
+
"media.publish",
|
|
1487
|
+
"articles.read",
|
|
1488
|
+
"articles.draft.save",
|
|
1489
|
+
"articles.publish",
|
|
1490
|
+
"listings.read",
|
|
1491
|
+
"listings.publish",
|
|
1492
|
+
"relationships.recommendations.read",
|
|
1493
|
+
"relationships.follow.set",
|
|
1494
|
+
"relationships.connect",
|
|
1495
|
+
"posts.repost",
|
|
1496
|
+
"posts.quote",
|
|
1497
|
+
"content.share",
|
|
1498
|
+
"content.save",
|
|
1499
|
+
"content.edit",
|
|
1500
|
+
"content.schedule",
|
|
1501
|
+
"communities.membership.set",
|
|
1502
|
+
],
|
|
1503
|
+
notApplicable: [],
|
|
1504
|
+
R4: [
|
|
1505
|
+
"content.delete",
|
|
1506
|
+
"content.audience.set",
|
|
1507
|
+
"communities.membership.manage",
|
|
1508
|
+
"administration.manage",
|
|
1509
|
+
"commerce.purchase",
|
|
1510
|
+
"account.delete",
|
|
1511
|
+
"moderation.bulk",
|
|
1512
|
+
],
|
|
1513
|
+
}),
|
|
1514
|
+
compositions: {},
|
|
1515
|
+
longForm: {
|
|
1516
|
+
read: UNSUPPORTED_LONG_FORM,
|
|
1517
|
+
publish: UNSUPPORTED_LONG_FORM,
|
|
1518
|
+
},
|
|
1519
|
+
threads: {
|
|
1520
|
+
read: UNSUPPORTED_THREADS,
|
|
1521
|
+
publish: UNSUPPORTED_THREADS,
|
|
1522
|
+
},
|
|
1523
|
+
},
|
|
1524
|
+
|
|
1525
|
+
youtube: {
|
|
1526
|
+
id: "youtube",
|
|
1527
|
+
platform: "youtube",
|
|
1528
|
+
facet: "default",
|
|
1529
|
+
displayName: "YouTube",
|
|
1530
|
+
originPolicy: exactOrigins("https://www.youtube.com", "https://studio.youtube.com"),
|
|
1531
|
+
operations: buildOperationMatrix({
|
|
1532
|
+
R1: ["content.read", "content.clip", "profiles.read", "comments.read", "posts.read", "media.read"],
|
|
1533
|
+
R2: ["likes.set", "relationships.follow.set", "content.save"],
|
|
1534
|
+
R3: ["comments.create", "replies.create", "posts.publish", "media.publish", "content.edit", "content.delete", "content.schedule"],
|
|
1535
|
+
unsupported: [],
|
|
1536
|
+
notApplicable: [
|
|
1537
|
+
"messaging.read",
|
|
1538
|
+
"messaging.send",
|
|
1539
|
+
"reactions.set",
|
|
1540
|
+
"threads.publish",
|
|
1541
|
+
"articles.read",
|
|
1542
|
+
"articles.draft.save",
|
|
1543
|
+
"articles.publish",
|
|
1544
|
+
"listings.read",
|
|
1545
|
+
"listings.publish",
|
|
1546
|
+
"relationships.connect",
|
|
1547
|
+
"posts.repost",
|
|
1548
|
+
"posts.quote",
|
|
1549
|
+
"content.share",
|
|
1550
|
+
"communities.membership.set",
|
|
1551
|
+
"communities.membership.manage",
|
|
1552
|
+
],
|
|
1553
|
+
R4: [
|
|
1554
|
+
"content.audience.set",
|
|
1555
|
+
"administration.manage",
|
|
1556
|
+
"commerce.purchase",
|
|
1557
|
+
"account.delete",
|
|
1558
|
+
"moderation.bulk",
|
|
1559
|
+
],
|
|
1560
|
+
}),
|
|
1561
|
+
compositions: {
|
|
1562
|
+
comment: {
|
|
1563
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1564
|
+
attachments: noAttachments("Comment attachments are not catalogued"),
|
|
1565
|
+
},
|
|
1566
|
+
reply: {
|
|
1567
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1568
|
+
attachments: noAttachments("Reply attachments are not catalogued"),
|
|
1569
|
+
},
|
|
1570
|
+
post: {
|
|
1571
|
+
text: [field("body", 500, "utf16-code-units")],
|
|
1572
|
+
attachments: attachments(1, ["image"], "One reviewed Community post image"),
|
|
1573
|
+
},
|
|
1574
|
+
media: {
|
|
1575
|
+
text: [field("title", 90, "utf16-code-units"), field("caption", 1_000, "utf16-code-units", false)],
|
|
1576
|
+
attachments: attachments(1, ["video"], "Exactly one reviewed video upload", 1),
|
|
1577
|
+
},
|
|
1578
|
+
},
|
|
1579
|
+
longForm: { read: NA_LONG_FORM, publish: NA_LONG_FORM },
|
|
1580
|
+
threads: { read: NA_THREADS, publish: NA_THREADS },
|
|
1581
|
+
},
|
|
1582
|
+
|
|
1583
|
+
bluesky: {
|
|
1584
|
+
id: "bluesky",
|
|
1585
|
+
platform: "bluesky",
|
|
1586
|
+
facet: "default",
|
|
1587
|
+
displayName: "Bluesky",
|
|
1588
|
+
originPolicy: exactOrigins("https://bsky.app"),
|
|
1589
|
+
operations: buildOperationMatrix({
|
|
1590
|
+
R1: ["content.read", "content.clip", "profiles.read", "messaging.read", "comments.read", "posts.read", "media.read"],
|
|
1591
|
+
R2: ["likes.set", "relationships.follow.set", "content.save"],
|
|
1592
|
+
R3: [
|
|
1593
|
+
"messaging.send",
|
|
1594
|
+
"replies.create",
|
|
1595
|
+
"posts.publish",
|
|
1596
|
+
"media.publish",
|
|
1597
|
+
"threads.publish",
|
|
1598
|
+
"posts.repost",
|
|
1599
|
+
"posts.quote",
|
|
1600
|
+
"content.share",
|
|
1601
|
+
"content.delete",
|
|
1602
|
+
],
|
|
1603
|
+
unsupported: ["articles.read", "articles.draft.save", "articles.publish", "content.edit", "content.schedule"],
|
|
1604
|
+
notApplicable: [
|
|
1605
|
+
"comments.create",
|
|
1606
|
+
"reactions.set",
|
|
1607
|
+
"listings.read",
|
|
1608
|
+
"listings.publish",
|
|
1609
|
+
"relationships.connect",
|
|
1610
|
+
"content.audience.set",
|
|
1611
|
+
"communities.membership.set",
|
|
1612
|
+
"communities.membership.manage",
|
|
1613
|
+
"commerce.purchase",
|
|
1614
|
+
],
|
|
1615
|
+
R4: ["administration.manage", "account.delete", "moderation.bulk"],
|
|
1616
|
+
}),
|
|
1617
|
+
compositions: {
|
|
1618
|
+
message: {
|
|
1619
|
+
text: [field("body", 1_000, "unicode-code-points")],
|
|
1620
|
+
attachments: noAttachments("Message attachments are not catalogued"),
|
|
1621
|
+
},
|
|
1622
|
+
reply: {
|
|
1623
|
+
text: [field("body", 280, "unicode-code-points")],
|
|
1624
|
+
attachments: attachments(1, ["image", "video", "gif", "link"], "One reviewed reply attachment"),
|
|
1625
|
+
},
|
|
1626
|
+
post: {
|
|
1627
|
+
text: [field("body", 280, "unicode-code-points")],
|
|
1628
|
+
attachments: attachments(1, ["image", "video", "gif", "link"], "One reviewed post attachment"),
|
|
1629
|
+
},
|
|
1630
|
+
media: {
|
|
1631
|
+
text: [field("body", 280, "unicode-code-points")],
|
|
1632
|
+
attachments: attachments(1, ["video"], "Exactly one reviewed video upload", 1),
|
|
1633
|
+
},
|
|
1634
|
+
},
|
|
1635
|
+
longForm: { read: UNSUPPORTED_LONG_FORM, publish: UNSUPPORTED_LONG_FORM },
|
|
1636
|
+
threads: {
|
|
1637
|
+
read: threadRead("Read one bounded Bluesky post thread"),
|
|
1638
|
+
publish: threadPublish(25, "Publish one root post optionally followed by bounded self-replies"),
|
|
1639
|
+
},
|
|
1640
|
+
},
|
|
1641
|
+
} as const satisfies Readonly<Record<PlatformSurfaceId, PlatformSurfaceCatalogEntry>>;
|
|
1642
|
+
|
|
1643
|
+
function hasWellFormedUnicode(value: string): boolean {
|
|
1644
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1645
|
+
const codeUnit = value.charCodeAt(index);
|
|
1646
|
+
if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
|
|
1647
|
+
const following = value.charCodeAt(index + 1);
|
|
1648
|
+
if (!Number.isInteger(following) || following < 0xdc00 || following > 0xdfff) return false;
|
|
1649
|
+
index += 1;
|
|
1650
|
+
} else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
|
|
1651
|
+
return false;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return true;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
function weightPolicyIssue(policy: TextWeightPolicy): string | null {
|
|
1658
|
+
if (!Number.isSafeInteger(policy.defaultWeight) || policy.defaultWeight < 1) {
|
|
1659
|
+
return "Text weights must be positive safe integers";
|
|
1660
|
+
}
|
|
1661
|
+
if (
|
|
1662
|
+
policy.minimumUrlWeight !== undefined
|
|
1663
|
+
&& (!Number.isSafeInteger(policy.minimumUrlWeight) || policy.minimumUrlWeight < 1)
|
|
1664
|
+
) {
|
|
1665
|
+
return "The minimum URL weight must be a positive safe integer";
|
|
1666
|
+
}
|
|
1667
|
+
let previousEnd = -1;
|
|
1668
|
+
for (const range of policy.ranges) {
|
|
1669
|
+
if (
|
|
1670
|
+
!Number.isSafeInteger(range.start)
|
|
1671
|
+
|| !Number.isSafeInteger(range.end)
|
|
1672
|
+
|| !Number.isSafeInteger(range.weight)
|
|
1673
|
+
|| range.start < 0
|
|
1674
|
+
|| range.end > 0x10_ffff
|
|
1675
|
+
|| range.start > range.end
|
|
1676
|
+
|| range.start <= previousEnd
|
|
1677
|
+
|| range.weight < 1
|
|
1678
|
+
) {
|
|
1679
|
+
return "Text weight ranges must be ordered, disjoint Unicode ranges with positive safe-integer weights";
|
|
1680
|
+
}
|
|
1681
|
+
previousEnd = range.end;
|
|
1682
|
+
}
|
|
1683
|
+
return null;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
function assertWeightPolicy(policy: TextWeightPolicy): void {
|
|
1687
|
+
const issue = weightPolicyIssue(policy);
|
|
1688
|
+
if (issue !== null) throw new RangeError(issue);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function weightCodePoint(codePoint: number, policy: TextWeightPolicy): number {
|
|
1692
|
+
for (const range of policy.ranges) {
|
|
1693
|
+
if (codePoint < range.start) break;
|
|
1694
|
+
if (codePoint <= range.end) return range.weight;
|
|
1695
|
+
}
|
|
1696
|
+
return policy.defaultWeight;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function rawWeightedLength(text: string, policy: TextWeightPolicy): number {
|
|
1700
|
+
let length = 0;
|
|
1701
|
+
for (const symbol of text) {
|
|
1702
|
+
const codePoint = symbol.codePointAt(0);
|
|
1703
|
+
if (codePoint === undefined) continue;
|
|
1704
|
+
length += weightCodePoint(codePoint, policy);
|
|
1705
|
+
if (!Number.isSafeInteger(length)) throw new RangeError("Weighted text length exceeds the safe integer range");
|
|
1706
|
+
}
|
|
1707
|
+
return length;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
type WeightedUnit = { readonly text: string; readonly weight: number };
|
|
1711
|
+
|
|
1712
|
+
const graphemeSegmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
1713
|
+
const TRAILING_URL_PUNCTUATION = /[!'),.:;?\]}]+$/u;
|
|
1714
|
+
|
|
1715
|
+
function* graphemeUnits(text: string, policy: TextWeightPolicy): Generator<WeightedUnit> {
|
|
1716
|
+
for (const part of graphemeSegmenter.segment(text)) {
|
|
1717
|
+
yield { text: part.segment, weight: rawWeightedLength(part.segment, policy) };
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
function* weightedUnits(text: string, policy: TextWeightPolicy): Generator<WeightedUnit> {
|
|
1722
|
+
let cursor = 0;
|
|
1723
|
+
for (const match of text.matchAll(/https?:\/\/[^\s<>"']+/giu)) {
|
|
1724
|
+
const index = match.index;
|
|
1725
|
+
const matched = match[0];
|
|
1726
|
+
if (index > cursor) yield* graphemeUnits(text.slice(cursor, index), policy);
|
|
1727
|
+
|
|
1728
|
+
const trailing = TRAILING_URL_PUNCTUATION.exec(matched)?.[0] ?? "";
|
|
1729
|
+
const core = trailing.length === 0 ? matched : matched.slice(0, -trailing.length);
|
|
1730
|
+
if (core.length === 0) {
|
|
1731
|
+
yield* graphemeUnits(matched, policy);
|
|
1732
|
+
} else {
|
|
1733
|
+
yield {
|
|
1734
|
+
text: core,
|
|
1735
|
+
weight: policy.minimumUrlWeight === undefined
|
|
1736
|
+
? rawWeightedLength(core, policy)
|
|
1737
|
+
: Math.max(rawWeightedLength(core, policy), policy.minimumUrlWeight),
|
|
1738
|
+
};
|
|
1739
|
+
if (trailing.length > 0) yield* graphemeUnits(trailing, policy);
|
|
1740
|
+
}
|
|
1741
|
+
cursor = index + matched.length;
|
|
1742
|
+
}
|
|
1743
|
+
if (cursor < text.length) yield* graphemeUnits(text.slice(cursor), policy);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
/** Measure well-formed Unicode without splitting UTF-16 surrogate pairs. */
|
|
1747
|
+
export function weightedTextLength(text: string, policy: TextWeightPolicy): number {
|
|
1748
|
+
if (!hasWellFormedUnicode(text)) throw new TypeError("Text must contain well-formed Unicode");
|
|
1749
|
+
assertWeightPolicy(policy);
|
|
1750
|
+
let length = 0;
|
|
1751
|
+
for (const unit of weightedUnits(text, policy)) {
|
|
1752
|
+
length += unit.weight;
|
|
1753
|
+
if (!Number.isSafeInteger(length)) throw new RangeError("Weighted text length exceeds the safe integer range");
|
|
1754
|
+
}
|
|
1755
|
+
return length;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
export type WeightedThreadChunk = {
|
|
1759
|
+
readonly text: string;
|
|
1760
|
+
readonly weightedLength: number;
|
|
1761
|
+
};
|
|
1762
|
+
|
|
1763
|
+
export type ThreadSplitResult =
|
|
1764
|
+
| { readonly ok: true; readonly chunks: readonly WeightedThreadChunk[] }
|
|
1765
|
+
| { readonly ok: false; readonly reason: "invalid-unicode" }
|
|
1766
|
+
| { readonly ok: false; readonly reason: "invalid-bounds" }
|
|
1767
|
+
| { readonly ok: false; readonly reason: "invalid-weight-policy"; readonly issue: string }
|
|
1768
|
+
| {
|
|
1769
|
+
readonly ok: false;
|
|
1770
|
+
readonly reason: "unit-too-large";
|
|
1771
|
+
readonly unit: string;
|
|
1772
|
+
readonly unitWeight: number;
|
|
1773
|
+
}
|
|
1774
|
+
| {
|
|
1775
|
+
readonly ok: false;
|
|
1776
|
+
readonly reason: "too-many-items";
|
|
1777
|
+
readonly maxItems: number;
|
|
1778
|
+
};
|
|
1779
|
+
|
|
1780
|
+
export type ThreadSplitOptions = {
|
|
1781
|
+
readonly maxWeightedLength: number;
|
|
1782
|
+
readonly maxItems: number;
|
|
1783
|
+
readonly weightPolicy: TextWeightPolicy;
|
|
1784
|
+
};
|
|
1785
|
+
|
|
1786
|
+
/**
|
|
1787
|
+
* Greedily split a thread at Unicode grapheme or recognized-URL boundaries.
|
|
1788
|
+
* The chunks concatenate to the exact input; numbering and remote publication
|
|
1789
|
+
* remain the caller's responsibility.
|
|
1790
|
+
*/
|
|
1791
|
+
export function splitWeightedThread(text: string, options: ThreadSplitOptions): ThreadSplitResult {
|
|
1792
|
+
if (!hasWellFormedUnicode(text)) return { ok: false, reason: "invalid-unicode" };
|
|
1793
|
+
if (
|
|
1794
|
+
!Number.isSafeInteger(options.maxWeightedLength)
|
|
1795
|
+
|| options.maxWeightedLength < 1
|
|
1796
|
+
|| !Number.isSafeInteger(options.maxItems)
|
|
1797
|
+
|| options.maxItems < 1
|
|
1798
|
+
) {
|
|
1799
|
+
return { ok: false, reason: "invalid-bounds" };
|
|
1800
|
+
}
|
|
1801
|
+
const weightIssue = weightPolicyIssue(options.weightPolicy);
|
|
1802
|
+
if (weightIssue !== null) {
|
|
1803
|
+
return { ok: false, reason: "invalid-weight-policy", issue: weightIssue };
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
const chunks: WeightedThreadChunk[] = [];
|
|
1807
|
+
let currentText = "";
|
|
1808
|
+
let currentWeight = 0;
|
|
1809
|
+
|
|
1810
|
+
const push = (): boolean => {
|
|
1811
|
+
if (currentText.length === 0) return true;
|
|
1812
|
+
if (chunks.length >= options.maxItems) return false;
|
|
1813
|
+
chunks.push({ text: currentText, weightedLength: currentWeight });
|
|
1814
|
+
currentText = "";
|
|
1815
|
+
currentWeight = 0;
|
|
1816
|
+
return true;
|
|
1817
|
+
};
|
|
1818
|
+
|
|
1819
|
+
for (const unit of weightedUnits(text, options.weightPolicy)) {
|
|
1820
|
+
if (unit.weight > options.maxWeightedLength) {
|
|
1821
|
+
return { ok: false, reason: "unit-too-large", unit: unit.text, unitWeight: unit.weight };
|
|
1822
|
+
}
|
|
1823
|
+
if (currentWeight > 0 && currentWeight + unit.weight > options.maxWeightedLength && !push()) {
|
|
1824
|
+
return { ok: false, reason: "too-many-items", maxItems: options.maxItems };
|
|
1825
|
+
}
|
|
1826
|
+
currentText += unit.text;
|
|
1827
|
+
currentWeight += unit.weight;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
if (!push()) return { ok: false, reason: "too-many-items", maxItems: options.maxItems };
|
|
1831
|
+
return { ok: true, chunks };
|
|
1832
|
+
}
|