@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,616 @@
|
|
|
1
|
+
const MAX_WEBVTT_CODE_UNITS = 64 * 1024 * 1024;
|
|
2
|
+
const MAX_CUE_COUNT = 1_000_000;
|
|
3
|
+
|
|
4
|
+
const TIMESTAMP_SOURCE = String.raw`(?:\d{2,}:\d{2}:\d{2}\.\d{3}|\d{2}:\d{2}\.\d{3})`;
|
|
5
|
+
const TIMING_LINE_PATTERN = new RegExp(
|
|
6
|
+
String.raw`^(${TIMESTAMP_SOURCE})[ \t]+-->[ \t]+(${TIMESTAMP_SOURCE})(?:[ \t]+.*)?$`,
|
|
7
|
+
"u",
|
|
8
|
+
);
|
|
9
|
+
const STRICT_TIMING_LINE_PATTERN = new RegExp(
|
|
10
|
+
String.raw`^(${TIMESTAMP_SOURCE})[ \t]+-->[ \t]+(${TIMESTAMP_SOURCE})$`,
|
|
11
|
+
"u",
|
|
12
|
+
);
|
|
13
|
+
const VTT_TAG_PATTERN = /^(?:\/?(?:b|i|u|ruby|rt|br)|\/?c(?:\.[^\s.<>]+)*|\/?v(?:\s+[^<>]*)?|\/?lang(?:\s+[^<>]*)?)$/iu;
|
|
14
|
+
const VTT_TIMESTAMP_TAG_PATTERN = new RegExp(String.raw`^${TIMESTAMP_SOURCE}$`, "u");
|
|
15
|
+
const ENTITY_PATTERN = /&(?:#(?:[xX][0-9a-fA-F]+|\d+)|[a-zA-Z][a-zA-Z0-9]+);/gu;
|
|
16
|
+
// Intentional WebVTT control-code sanitation; printable whitespace is handled separately.
|
|
17
|
+
// eslint-disable-next-line no-control-regex
|
|
18
|
+
const CONTROL_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu;
|
|
19
|
+
const WHITESPACE_PATTERN = /\s+/gu;
|
|
20
|
+
const TOKEN_EDGE_PATTERN = /^(?:[\p{P}\p{S}]+)|(?:[\p{P}\p{S}]+)$/gu;
|
|
21
|
+
|
|
22
|
+
export type TranscriptCue = Readonly<{
|
|
23
|
+
startMs: number;
|
|
24
|
+
endMs: number;
|
|
25
|
+
text: string;
|
|
26
|
+
}>;
|
|
27
|
+
|
|
28
|
+
export type TranscriptDocument = Readonly<{
|
|
29
|
+
version: 1;
|
|
30
|
+
cues: readonly TranscriptCue[];
|
|
31
|
+
}>;
|
|
32
|
+
|
|
33
|
+
export type TranscriptParseErrorCode =
|
|
34
|
+
| "invalid-input"
|
|
35
|
+
| "input-too-large"
|
|
36
|
+
| "invalid-webvtt-header"
|
|
37
|
+
| "no-valid-cues"
|
|
38
|
+
| "empty-transcript";
|
|
39
|
+
|
|
40
|
+
export type TranscriptParseError = Readonly<{
|
|
41
|
+
code: TranscriptParseErrorCode;
|
|
42
|
+
message: string;
|
|
43
|
+
}>;
|
|
44
|
+
|
|
45
|
+
export type TranscriptParseResult =
|
|
46
|
+
| Readonly<{
|
|
47
|
+
ok: true;
|
|
48
|
+
cues: readonly TranscriptCue[];
|
|
49
|
+
text: string;
|
|
50
|
+
json: string;
|
|
51
|
+
}>
|
|
52
|
+
| Readonly<{
|
|
53
|
+
ok: false;
|
|
54
|
+
error: TranscriptParseError;
|
|
55
|
+
}>;
|
|
56
|
+
|
|
57
|
+
export type StrictLocalWebVttResult =
|
|
58
|
+
| Readonly<{ ok: true; cues: readonly TranscriptCue[] }>
|
|
59
|
+
| Readonly<{
|
|
60
|
+
ok: false;
|
|
61
|
+
error: Readonly<{
|
|
62
|
+
code: "invalid-input" | "input-too-large" | "invalid-header" | "invalid-cue-block" | "too-many-cues";
|
|
63
|
+
message: string;
|
|
64
|
+
}>;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
export type TranscriptCueValidationErrorCode =
|
|
68
|
+
| "invalid-cues"
|
|
69
|
+
| "too-many-cues"
|
|
70
|
+
| "empty-transcript"
|
|
71
|
+
| "invalid-cue"
|
|
72
|
+
| "non-monotonic-cues";
|
|
73
|
+
|
|
74
|
+
export type TranscriptCueValidationResult =
|
|
75
|
+
| Readonly<{
|
|
76
|
+
ok: true;
|
|
77
|
+
cues: readonly TranscriptCue[];
|
|
78
|
+
vtt: string;
|
|
79
|
+
text: string;
|
|
80
|
+
json: string;
|
|
81
|
+
}>
|
|
82
|
+
| Readonly<{
|
|
83
|
+
ok: false;
|
|
84
|
+
error: Readonly<{
|
|
85
|
+
code: TranscriptCueValidationErrorCode;
|
|
86
|
+
message: string;
|
|
87
|
+
}>;
|
|
88
|
+
}>;
|
|
89
|
+
|
|
90
|
+
type ParsedTiming = Readonly<{
|
|
91
|
+
startMs: number;
|
|
92
|
+
endMs: number;
|
|
93
|
+
}>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Parses WebVTT into canonical transcript derivatives. Malformed cue blocks are
|
|
97
|
+
* ignored so that one damaged provider cue cannot discard an otherwise useful
|
|
98
|
+
* transcript, but the envelope, timestamps, and nonempty-success invariant are
|
|
99
|
+
* strict.
|
|
100
|
+
*/
|
|
101
|
+
export function parseWebVtt(input: unknown): TranscriptParseResult {
|
|
102
|
+
if (typeof input !== "string") {
|
|
103
|
+
return failure("invalid-input", "WebVTT input must be a string.");
|
|
104
|
+
}
|
|
105
|
+
if (input.length > MAX_WEBVTT_CODE_UNITS) {
|
|
106
|
+
return failure(
|
|
107
|
+
"input-too-large",
|
|
108
|
+
`WebVTT input exceeds the ${String(MAX_WEBVTT_CODE_UNITS)} code-unit limit.`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const normalizedInput = toUnicodeScalarString(input)
|
|
113
|
+
.replace(/^\uFEFF/u, "")
|
|
114
|
+
.replace(/\r\n?/gu, "\n");
|
|
115
|
+
const lines = normalizedInput.split("\n");
|
|
116
|
+
const header = lines[0];
|
|
117
|
+
if (header === undefined || !/^WEBVTT(?:[ \t].*)?$/u.test(header)) {
|
|
118
|
+
return failure("invalid-webvtt-header", "WebVTT input must begin with a WEBVTT header.");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let cursor = 1;
|
|
122
|
+
while (cursor < lines.length && !isBlank(lines[cursor])) cursor += 1;
|
|
123
|
+
|
|
124
|
+
const parsedCues: TranscriptCue[] = [];
|
|
125
|
+
while (cursor < lines.length && parsedCues.length < MAX_CUE_COUNT) {
|
|
126
|
+
while (cursor < lines.length && isBlank(lines[cursor])) cursor += 1;
|
|
127
|
+
if (cursor >= lines.length) break;
|
|
128
|
+
|
|
129
|
+
const blockStart = cursor;
|
|
130
|
+
while (cursor < lines.length && !isBlank(lines[cursor])) cursor += 1;
|
|
131
|
+
const block = lines.slice(blockStart, cursor);
|
|
132
|
+
const firstLine = block[0];
|
|
133
|
+
if (firstLine === undefined || isMetadataBlock(firstLine)) continue;
|
|
134
|
+
|
|
135
|
+
const timingIndex = timingLineIndex(block);
|
|
136
|
+
if (timingIndex === -1) continue;
|
|
137
|
+
const timingLine = block[timingIndex];
|
|
138
|
+
if (timingLine === undefined) continue;
|
|
139
|
+
const timing = parseTimingLine(timingLine);
|
|
140
|
+
if (timing === undefined) continue;
|
|
141
|
+
|
|
142
|
+
const cueText = stripWebVttMarkup(block.slice(timingIndex + 1).join("\n"));
|
|
143
|
+
if (cueText.length === 0) continue;
|
|
144
|
+
parsedCues.push({ ...timing, text: cueText });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (parsedCues.length === 0) {
|
|
148
|
+
return failure("no-valid-cues", "WebVTT input contains no valid, nonempty cues.");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const cues = deduplicateRollingCaptionCues(parsedCues);
|
|
152
|
+
if (cues.length === 0) {
|
|
153
|
+
return failure("empty-transcript", "WebVTT cues contain no transcript text after cleanup.");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const text = renderTranscriptText(cues);
|
|
157
|
+
if (text.trim().length === 0) {
|
|
158
|
+
return failure("empty-transcript", "WebVTT cues contain no transcript text after cleanup.");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
ok: true,
|
|
163
|
+
cues,
|
|
164
|
+
text,
|
|
165
|
+
json: renderTranscriptJson(cues),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Parses the exact WebVTT subset emitted by Ghostget media's whisper.cpp profile. Unlike
|
|
171
|
+
* provider caption recovery, every nonblank block must be one complete cue;
|
|
172
|
+
* malformed or excess output fails the whole local attempt.
|
|
173
|
+
*/
|
|
174
|
+
export function parseStrictLocalWebVtt(input: unknown): StrictLocalWebVttResult {
|
|
175
|
+
if (typeof input !== "string") {
|
|
176
|
+
return strictVttFailure("invalid-input", "Local WebVTT input must be a string.");
|
|
177
|
+
}
|
|
178
|
+
if (input.length > MAX_WEBVTT_CODE_UNITS) {
|
|
179
|
+
return strictVttFailure(
|
|
180
|
+
"input-too-large",
|
|
181
|
+
`Local WebVTT input exceeds the ${String(MAX_WEBVTT_CODE_UNITS)} code-unit limit.`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const scalarInput = toUnicodeScalarString(input);
|
|
186
|
+
if (scalarInput !== input) {
|
|
187
|
+
return strictVttFailure("invalid-input", "Local WebVTT must contain valid Unicode scalar text.");
|
|
188
|
+
}
|
|
189
|
+
const lines = input.replace(/^\uFEFF/u, "").replace(/\r\n?/gu, "\n").split("\n");
|
|
190
|
+
if (lines[0] !== "WEBVTT" || lines[1] === undefined || !isBlank(lines[1])) {
|
|
191
|
+
return strictVttFailure("invalid-header", "Local WebVTT must use Ghostget media's exact WEBVTT envelope.");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const cues: TranscriptCue[] = [];
|
|
195
|
+
let cursor = 2;
|
|
196
|
+
while (cursor < lines.length) {
|
|
197
|
+
while (cursor < lines.length && isBlank(lines[cursor])) cursor += 1;
|
|
198
|
+
if (cursor >= lines.length) break;
|
|
199
|
+
if (cues.length >= MAX_CUE_COUNT) {
|
|
200
|
+
return strictVttFailure(
|
|
201
|
+
"too-many-cues",
|
|
202
|
+
`Local WebVTT exceeds the ${String(MAX_CUE_COUNT)} cue limit.`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const blockStart = cursor;
|
|
207
|
+
while (cursor < lines.length && !isBlank(lines[cursor])) cursor += 1;
|
|
208
|
+
const block = lines.slice(blockStart, cursor);
|
|
209
|
+
const timingLine = block[0];
|
|
210
|
+
if (
|
|
211
|
+
timingLine === undefined
|
|
212
|
+
|| !STRICT_TIMING_LINE_PATTERN.test(timingLine)
|
|
213
|
+
|| block.length < 2
|
|
214
|
+
) {
|
|
215
|
+
return strictVttFailure(
|
|
216
|
+
"invalid-cue-block",
|
|
217
|
+
`Local WebVTT cue ${String(cues.length)} is malformed.`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
const timing = parseTimingLine(timingLine);
|
|
221
|
+
const text = stripWebVttMarkup(block.slice(1).join("\n"));
|
|
222
|
+
if (timing === undefined || text.length === 0) {
|
|
223
|
+
return strictVttFailure(
|
|
224
|
+
"invalid-cue-block",
|
|
225
|
+
`Local WebVTT cue ${String(cues.length)} is malformed.`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
cues.push({ ...timing, text });
|
|
229
|
+
}
|
|
230
|
+
return { ok: true, cues };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Removes WebVTT cue tags and decodes a conservative entity allowlist. Unknown
|
|
235
|
+
* angle-bracket text and named entities remain literal instead of being guessed.
|
|
236
|
+
*/
|
|
237
|
+
export function stripWebVttMarkup(input: string): string {
|
|
238
|
+
const scalarInput = toUnicodeScalarString(input);
|
|
239
|
+
const chunks: string[] = [];
|
|
240
|
+
let cursor = 0;
|
|
241
|
+
|
|
242
|
+
while (cursor < scalarInput.length) {
|
|
243
|
+
const tagStart = scalarInput.indexOf("<", cursor);
|
|
244
|
+
if (tagStart === -1) {
|
|
245
|
+
chunks.push(scalarInput.slice(cursor));
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
chunks.push(scalarInput.slice(cursor, tagStart));
|
|
249
|
+
const tagEnd = scalarInput.indexOf(">", tagStart + 1);
|
|
250
|
+
if (tagEnd === -1) {
|
|
251
|
+
chunks.push(scalarInput.slice(tagStart));
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const tag = scalarInput.slice(tagStart + 1, tagEnd).trim();
|
|
256
|
+
if (!VTT_TAG_PATTERN.test(tag) && !VTT_TIMESTAMP_TAG_PATTERN.test(tag)) {
|
|
257
|
+
chunks.push(scalarInput.slice(tagStart, tagEnd + 1));
|
|
258
|
+
cursor = tagEnd + 1;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (/^br$/iu.test(tag)) chunks.push(" ");
|
|
263
|
+
cursor = tagEnd + 1;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const decoded = chunks.join("").replace(ENTITY_PATTERN, (entity) => decodeEntity(entity));
|
|
267
|
+
return decoded
|
|
268
|
+
.replace(CONTROL_PATTERN, " ")
|
|
269
|
+
.replace(WHITESPACE_PATTERN, " ")
|
|
270
|
+
.trim()
|
|
271
|
+
.normalize("NFC");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Returns the largest token count shared by the left suffix and right prefix. */
|
|
275
|
+
export function longestTokenOverlap(
|
|
276
|
+
leftTokens: readonly string[],
|
|
277
|
+
rightTokens: readonly string[],
|
|
278
|
+
): number {
|
|
279
|
+
const maximum = Math.min(leftTokens.length, rightTokens.length);
|
|
280
|
+
if (maximum === 0) return 0;
|
|
281
|
+
|
|
282
|
+
const pattern = rightTokens.slice(0, maximum).map(normalizeToken);
|
|
283
|
+
const prefixLengths = new Array<number>(pattern.length).fill(0);
|
|
284
|
+
for (let index = 1; index < pattern.length; index += 1) {
|
|
285
|
+
let matched = prefixLengths[index - 1] ?? 0;
|
|
286
|
+
const token = pattern[index];
|
|
287
|
+
while (matched > 0 && pattern[matched] !== token) {
|
|
288
|
+
matched = prefixLengths[matched - 1] ?? 0;
|
|
289
|
+
}
|
|
290
|
+
if (pattern[matched] === token) matched += 1;
|
|
291
|
+
prefixLengths[index] = matched;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let matched = 0;
|
|
295
|
+
for (const rawToken of leftTokens.slice(-maximum)) {
|
|
296
|
+
const token = normalizeToken(rawToken);
|
|
297
|
+
while (matched > 0 && (matched === pattern.length || pattern[matched] !== token)) {
|
|
298
|
+
matched = prefixLengths[matched - 1] ?? 0;
|
|
299
|
+
}
|
|
300
|
+
if (matched < pattern.length && pattern[matched] === token) matched += 1;
|
|
301
|
+
}
|
|
302
|
+
return matched;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Removes the repeated prefix emitted by YouTube-style rolling captions while
|
|
307
|
+
* retaining each cue's original surface spelling and timing.
|
|
308
|
+
*/
|
|
309
|
+
export function deduplicateRollingCaptionCues(
|
|
310
|
+
cues: readonly TranscriptCue[],
|
|
311
|
+
): readonly TranscriptCue[] {
|
|
312
|
+
const output: TranscriptCue[] = [];
|
|
313
|
+
let previousCue: Readonly<{
|
|
314
|
+
endMs: number;
|
|
315
|
+
tokens: readonly string[];
|
|
316
|
+
}> | undefined;
|
|
317
|
+
|
|
318
|
+
for (const cue of cues) {
|
|
319
|
+
const cueText = canonicalCueText(cue.text);
|
|
320
|
+
if (cueText.length === 0) continue;
|
|
321
|
+
const cueTokens = tokenize(cueText);
|
|
322
|
+
// Rolling captions repeat the suffix of the immediately preceding cue
|
|
323
|
+
// while that cue is still on screen. Equal words spoken again after the
|
|
324
|
+
// prior cue ends are new speech and must remain in durable derivatives.
|
|
325
|
+
const overlap = previousCue !== undefined && cue.startMs < previousCue.endMs
|
|
326
|
+
? longestTokenOverlap(previousCue.tokens, cueTokens)
|
|
327
|
+
: 0;
|
|
328
|
+
const novelTokens = cueTokens.slice(overlap);
|
|
329
|
+
previousCue = { endMs: cue.endMs, tokens: cueTokens };
|
|
330
|
+
if (novelTokens.length === 0) continue;
|
|
331
|
+
|
|
332
|
+
output.push({
|
|
333
|
+
startMs: cue.startMs,
|
|
334
|
+
endMs: cue.endMs,
|
|
335
|
+
text: novelTokens.join(" "),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return output;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** Renders one canonical NFC cue per line with exactly one trailing newline. */
|
|
343
|
+
export function renderTranscriptText(cues: readonly TranscriptCue[]): string {
|
|
344
|
+
const lines = cues
|
|
345
|
+
.map(({ text }) => canonicalCueText(text))
|
|
346
|
+
.filter((text) => text.length > 0);
|
|
347
|
+
return lines.length === 0 ? "" : `${lines.join("\n")}\n`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Renders stable, newline-terminated JSON for durable transcript interchange. */
|
|
351
|
+
export function renderTranscriptJson(cues: readonly TranscriptCue[]): string {
|
|
352
|
+
const document: TranscriptDocument = {
|
|
353
|
+
version: 1,
|
|
354
|
+
cues: cues.map(({ startMs, endMs, text }) => ({
|
|
355
|
+
startMs,
|
|
356
|
+
endMs,
|
|
357
|
+
text: canonicalCueText(text),
|
|
358
|
+
})),
|
|
359
|
+
};
|
|
360
|
+
return `${JSON.stringify(document, null, 2)}\n`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Validates the stricter cue contract used for locally generated transcripts.
|
|
365
|
+
* Provider WebVTT remains recoverable cue-by-cue; output from a configured
|
|
366
|
+
* local engine must instead be complete, ordered, exact, and independently
|
|
367
|
+
* renderable before Ghostget media can promote an archive.
|
|
368
|
+
*/
|
|
369
|
+
export function validateTranscriptCues(input: unknown): TranscriptCueValidationResult {
|
|
370
|
+
if (!Array.isArray(input)) {
|
|
371
|
+
return cueValidationFailure("invalid-cues", "Transcript cues must be an array.");
|
|
372
|
+
}
|
|
373
|
+
if (input.length > MAX_CUE_COUNT) {
|
|
374
|
+
return cueValidationFailure(
|
|
375
|
+
"too-many-cues",
|
|
376
|
+
`Transcript output exceeds the ${String(MAX_CUE_COUNT)} cue limit.`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
if (input.length === 0) {
|
|
380
|
+
return cueValidationFailure("empty-transcript", "Transcript output contains no cues.");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const cues: TranscriptCue[] = [];
|
|
384
|
+
let previousStartMs = -1;
|
|
385
|
+
let previousEndMs = -1;
|
|
386
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
387
|
+
const value: unknown = input[index];
|
|
388
|
+
if (!isExactCue(value)) {
|
|
389
|
+
return cueValidationFailure(
|
|
390
|
+
"invalid-cue",
|
|
391
|
+
`Transcript cue ${String(index)} is not an exact timed-text cue.`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
const text = canonicalCueText(value.text);
|
|
395
|
+
if (text.length === 0) {
|
|
396
|
+
return cueValidationFailure(
|
|
397
|
+
"invalid-cue",
|
|
398
|
+
`Transcript cue ${String(index)} has no printable text.`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (value.startMs < previousStartMs || value.endMs < previousEndMs) {
|
|
402
|
+
return cueValidationFailure(
|
|
403
|
+
"non-monotonic-cues",
|
|
404
|
+
`Transcript cue ${String(index)} moves backwards in time.`,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
const cue = { startMs: value.startMs, endMs: value.endMs, text };
|
|
408
|
+
cues.push(cue);
|
|
409
|
+
previousStartMs = cue.startMs;
|
|
410
|
+
previousEndMs = cue.endMs;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const text = renderTranscriptText(cues);
|
|
414
|
+
if (text.length === 0) {
|
|
415
|
+
return cueValidationFailure("empty-transcript", "Transcript output contains no text.");
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
ok: true,
|
|
419
|
+
cues,
|
|
420
|
+
vtt: renderTranscriptVtt(cues),
|
|
421
|
+
text,
|
|
422
|
+
json: renderTranscriptJson(cues),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** Renders plain canonical cues as deterministic, newline-terminated WebVTT. */
|
|
427
|
+
export function renderTranscriptVtt(cues: readonly TranscriptCue[]): string {
|
|
428
|
+
if (cues.length === 0) return "WEBVTT\n\n";
|
|
429
|
+
const blocks = cues.map((cue, index) => [
|
|
430
|
+
String(index + 1),
|
|
431
|
+
`${renderWebVttTimestamp(cue.startMs)} --> ${renderWebVttTimestamp(cue.endMs)}`,
|
|
432
|
+
escapeWebVttText(canonicalCueText(cue.text)),
|
|
433
|
+
].join("\n"));
|
|
434
|
+
return `WEBVTT\n\n${blocks.join("\n\n")}\n`;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function cueValidationFailure(
|
|
438
|
+
code: TranscriptCueValidationErrorCode,
|
|
439
|
+
message: string,
|
|
440
|
+
): TranscriptCueValidationResult {
|
|
441
|
+
return { ok: false, error: { code, message } };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function strictVttFailure(
|
|
445
|
+
code: "invalid-input" | "input-too-large" | "invalid-header" | "invalid-cue-block" | "too-many-cues",
|
|
446
|
+
message: string,
|
|
447
|
+
): StrictLocalWebVttResult {
|
|
448
|
+
return { ok: false, error: { code, message } };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function isExactCue(value: unknown): value is TranscriptCue {
|
|
452
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
453
|
+
const record = value as Record<string, unknown>;
|
|
454
|
+
const keys = Object.keys(record).sort();
|
|
455
|
+
return (
|
|
456
|
+
keys.length === 3
|
|
457
|
+
&& keys[0] === "endMs"
|
|
458
|
+
&& keys[1] === "startMs"
|
|
459
|
+
&& keys[2] === "text"
|
|
460
|
+
&& Number.isSafeInteger(record["startMs"])
|
|
461
|
+
&& (record["startMs"] as number) >= 0
|
|
462
|
+
&& Number.isSafeInteger(record["endMs"])
|
|
463
|
+
&& (record["endMs"] as number) > (record["startMs"] as number)
|
|
464
|
+
&& typeof record["text"] === "string"
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function renderWebVttTimestamp(milliseconds: number): string {
|
|
469
|
+
const hours = Math.floor(milliseconds / 3_600_000);
|
|
470
|
+
const minutes = Math.floor((milliseconds % 3_600_000) / 60_000);
|
|
471
|
+
const seconds = Math.floor((milliseconds % 60_000) / 1_000);
|
|
472
|
+
const remainder = milliseconds % 1_000;
|
|
473
|
+
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}.${String(remainder).padStart(3, "0")}`;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function escapeWebVttText(text: string): string {
|
|
477
|
+
return text
|
|
478
|
+
.replace(/&/gu, "&")
|
|
479
|
+
.replace(/</gu, "<")
|
|
480
|
+
.replace(/>/gu, ">");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function failure(code: TranscriptParseErrorCode, message: string): TranscriptParseResult {
|
|
484
|
+
return { ok: false, error: { code, message } };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function isBlank(line: string | undefined): boolean {
|
|
488
|
+
return line === undefined || /^[ \t]*$/u.test(line);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function isMetadataBlock(line: string): boolean {
|
|
492
|
+
return /^(?:NOTE(?:[ \t].*)?|STYLE|REGION)$/u.test(line);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function timingLineIndex(block: readonly string[]): number {
|
|
496
|
+
if (block[0] !== undefined && TIMING_LINE_PATTERN.test(block[0])) return 0;
|
|
497
|
+
if (block[1] !== undefined && TIMING_LINE_PATTERN.test(block[1])) return 1;
|
|
498
|
+
return -1;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function parseTimingLine(line: string): ParsedTiming | undefined {
|
|
502
|
+
const match = TIMING_LINE_PATTERN.exec(line);
|
|
503
|
+
const startText = match?.[1];
|
|
504
|
+
const endText = match?.[2];
|
|
505
|
+
if (startText === undefined || endText === undefined) return undefined;
|
|
506
|
+
const startMs = parseTimestamp(startText);
|
|
507
|
+
const endMs = parseTimestamp(endText);
|
|
508
|
+
if (startMs === undefined || endMs === undefined || endMs <= startMs) return undefined;
|
|
509
|
+
return { startMs, endMs };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function parseTimestamp(timestamp: string): number | undefined {
|
|
513
|
+
const parts = timestamp.split(":");
|
|
514
|
+
if (parts.length !== 2 && parts.length !== 3) return undefined;
|
|
515
|
+
|
|
516
|
+
const secondsPart = parts.at(-1);
|
|
517
|
+
const minutesPart = parts.at(-2);
|
|
518
|
+
const hoursPart = parts.length === 3 ? parts[0] : "0";
|
|
519
|
+
if (secondsPart === undefined || minutesPart === undefined || hoursPart === undefined) {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const secondsMatch = /^(\d{2})\.(\d{3})$/u.exec(secondsPart);
|
|
524
|
+
if (secondsMatch === null || !/^\d{2}$/u.test(minutesPart) || !/^\d+$/u.test(hoursPart)) {
|
|
525
|
+
return undefined;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const hours = Number(hoursPart);
|
|
529
|
+
const minutes = Number(minutesPart);
|
|
530
|
+
const seconds = Number(secondsMatch[1]);
|
|
531
|
+
const milliseconds = Number(secondsMatch[2]);
|
|
532
|
+
if (
|
|
533
|
+
!Number.isSafeInteger(hours)
|
|
534
|
+
|| minutes > 59
|
|
535
|
+
|| seconds > 59
|
|
536
|
+
) {
|
|
537
|
+
return undefined;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const result = (((hours * 60) + minutes) * 60 + seconds) * 1_000 + milliseconds;
|
|
541
|
+
return Number.isSafeInteger(result) ? result : undefined;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function decodeEntity(entity: string): string {
|
|
545
|
+
const namedEntities: Readonly<Record<string, string>> = {
|
|
546
|
+
"&": "&",
|
|
547
|
+
"'": "'",
|
|
548
|
+
">": ">",
|
|
549
|
+
"‎": "\u200E",
|
|
550
|
+
"<": "<",
|
|
551
|
+
" ": "\u00A0",
|
|
552
|
+
""": '"',
|
|
553
|
+
"‏": "\u200F",
|
|
554
|
+
};
|
|
555
|
+
const named = namedEntities[entity.toLowerCase()];
|
|
556
|
+
if (named !== undefined) return named;
|
|
557
|
+
|
|
558
|
+
const numericMatch = /^&#(?:([xX])([0-9a-fA-F]+)|(\d+));$/u.exec(entity);
|
|
559
|
+
if (numericMatch === null) return entity;
|
|
560
|
+
const radix = numericMatch[1] === undefined ? 10 : 16;
|
|
561
|
+
const digits = numericMatch[2] ?? numericMatch[3];
|
|
562
|
+
if (digits === undefined) return entity;
|
|
563
|
+
const codePoint = Number.parseInt(digits, radix);
|
|
564
|
+
if (
|
|
565
|
+
!Number.isInteger(codePoint)
|
|
566
|
+
|| codePoint === 0
|
|
567
|
+
|| codePoint > 0x10_FFFF
|
|
568
|
+
|| (codePoint >= 0xD800 && codePoint <= 0xDFFF)
|
|
569
|
+
) {
|
|
570
|
+
return entity;
|
|
571
|
+
}
|
|
572
|
+
return String.fromCodePoint(codePoint);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function tokenize(text: string): string[] {
|
|
576
|
+
return text.split(WHITESPACE_PATTERN).filter((token) => token.length > 0);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function normalizeToken(token: string): string {
|
|
580
|
+
const normalized = toUnicodeScalarString(token)
|
|
581
|
+
.normalize("NFKC")
|
|
582
|
+
.toLowerCase()
|
|
583
|
+
.replace(TOKEN_EDGE_PATTERN, "");
|
|
584
|
+
return normalized.length === 0 ? token.normalize("NFKC").toLowerCase() : normalized;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function canonicalCueText(text: string): string {
|
|
588
|
+
return toUnicodeScalarString(text)
|
|
589
|
+
.replace(CONTROL_PATTERN, " ")
|
|
590
|
+
.replace(WHITESPACE_PATTERN, " ")
|
|
591
|
+
.trim()
|
|
592
|
+
.normalize("NFC");
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function toUnicodeScalarString(input: string): string {
|
|
596
|
+
if (!/[\uD800-\uDFFF]/u.test(input)) return input;
|
|
597
|
+
|
|
598
|
+
const chunks: string[] = [];
|
|
599
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
600
|
+
const codeUnit = input.charCodeAt(index);
|
|
601
|
+
if (codeUnit >= 0xD800 && codeUnit <= 0xDBFF) {
|
|
602
|
+
const next = input.charCodeAt(index + 1);
|
|
603
|
+
if (next >= 0xDC00 && next <= 0xDFFF) {
|
|
604
|
+
chunks.push(input.slice(index, index + 2));
|
|
605
|
+
index += 1;
|
|
606
|
+
} else {
|
|
607
|
+
chunks.push("\uFFFD");
|
|
608
|
+
}
|
|
609
|
+
} else if (codeUnit >= 0xDC00 && codeUnit <= 0xDFFF) {
|
|
610
|
+
chunks.push("\uFFFD");
|
|
611
|
+
} else {
|
|
612
|
+
chunks.push(input[index] ?? "");
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return chunks.join("");
|
|
616
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const encoder = new TextEncoder();
|
|
2
|
+
|
|
3
|
+
function compareCodeUnits(left: string, right: string): number {
|
|
4
|
+
const length = Math.min(left.length, right.length);
|
|
5
|
+
for (let index = 0; index < length; index += 1) {
|
|
6
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
7
|
+
if (difference !== 0) return difference;
|
|
8
|
+
}
|
|
9
|
+
return left.length - right.length;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A locale-independent total order for strings that enter hashes, manifests,
|
|
14
|
+
* checksums, or filesystem projections. UTF-16 breaks the otherwise-possible
|
|
15
|
+
* tie when TextEncoder replaces two different lone surrogates with U+FFFD.
|
|
16
|
+
*/
|
|
17
|
+
export function compareUtf8(left: string, right: string): number {
|
|
18
|
+
if (left === right) return 0;
|
|
19
|
+
const leftBytes = encoder.encode(left);
|
|
20
|
+
const rightBytes = encoder.encode(right);
|
|
21
|
+
const length = Math.min(leftBytes.byteLength, rightBytes.byteLength);
|
|
22
|
+
for (let index = 0; index < length; index += 1) {
|
|
23
|
+
const difference = (leftBytes[index] ?? 0) - (rightBytes[index] ?? 0);
|
|
24
|
+
if (difference !== 0) return difference;
|
|
25
|
+
}
|
|
26
|
+
const byteLengthDifference = leftBytes.byteLength - rightBytes.byteLength;
|
|
27
|
+
return byteLengthDifference !== 0
|
|
28
|
+
? byteLengthDifference
|
|
29
|
+
: compareCodeUnits(left, right);
|
|
30
|
+
}
|