feat: initial implementation of chat in terminal from stream.place
This commit is contained in:
commit
689304656f
39 files changed
+4243
No files matched your search
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
.env
|
||||
.session-store.json
|
||||
.state-store.json
|
||||
dist/
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2026 Stevan Freeborn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,70 @@
|
||||
[](https://www.npmjs.com/package/@stevanfreeborn/streamplace-term-chat)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
# Streamplace Chat Client
|
||||
|
||||
A lightweight, terminal-based live chat client for Streamplace powered by AT Protocol OAuth and Bluesky Jetstream.
|
||||
|
||||
## Features
|
||||
|
||||
- Secure browser login flow with persistent session storage.
|
||||
- Subscribes to live `place.stream.chat.message` events.
|
||||
- Efficient handle/DID resolution to minimize network overhead.
|
||||
- Auto-reconnecting WebSocket for uninterrupted stream chat.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run directly using `npx`:
|
||||
|
||||
```bash
|
||||
# Connect to your own stream chat room
|
||||
npx @stevanfreeborn/streamplace-term-chat your-handle.bsky.social
|
||||
|
||||
# Connect to another streamer's chat room
|
||||
npx @stevanfreeborn/streamplace-term-chat your-handle.bsky.social --streamer streamer-handle.bsky.social
|
||||
```
|
||||
|
||||
## Global Installation
|
||||
|
||||
Install globally via npm to use the streamplace-term-chat anywhere on your system:
|
||||
|
||||
```sh
|
||||
npm install -g @stevanfreeborn/streamplace-term-chat
|
||||
```
|
||||
|
||||
## From Source
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js (v20+ recommended)
|
||||
- npm
|
||||
|
||||
### Quick Start
|
||||
|
||||
Clone the repository and install dependencies:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/StevanFreeborn/stream.place-chat-client.git
|
||||
cd stream.place-chat-client
|
||||
npm install
|
||||
```
|
||||
|
||||
Start the chat client:
|
||||
|
||||
```bash
|
||||
# Connect to your own stream chat
|
||||
npm start -- <your-handle.bsky.social>
|
||||
|
||||
# Connect to a target streamer's chat room
|
||||
npm start -- <your-handle.bsky.social> --streamer <streamer.bsky.social>
|
||||
```
|
||||
|
||||
Chat commands:
|
||||
|
||||
- Type your message and hit `Enter` to post.
|
||||
- `/help`: Show available in-chat commands.
|
||||
- `/quit` or `/exit`: Gracefully disconnect and exit.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE.md)
|
||||
@@ -0,0 +1,2 @@
|
||||
STREAMER_DID=
|
||||
JETSTREAM_URL=
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": 1,
|
||||
"lexicons": [
|
||||
"app.bsky.actor.profile",
|
||||
"app.bsky.richtext.facet"
|
||||
],
|
||||
"resolutions": {
|
||||
"app.bsky.actor.profile": {
|
||||
"uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.profile",
|
||||
"cid": "bafyreia6umzg3a6d7mjbow4p57tviey45muohklhgsvjoamcctoiusr4pe"
|
||||
},
|
||||
"app.bsky.richtext.facet": {
|
||||
"uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.richtext.facet",
|
||||
"cid": "bafyreidg56eo7zynf6ihz4xb627vwoqf5idnevkmwp7sxc4tijg6xngbu4"
|
||||
},
|
||||
"com.atproto.label.defs": {
|
||||
"uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.label.defs",
|
||||
"cid": "bafyreidp2wpcbzl2qiob2uwoc7lhntojx3tjcr553mmahw2f5cumtm3wpm"
|
||||
},
|
||||
"com.atproto.repo.strongRef": {
|
||||
"uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.strongRef",
|
||||
"cid": "bafyreifrkdbnkvfjujntdaeigolnrjj3srrs53tfixjhmacclps72qlov4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"id": "app.bsky.actor.profile",
|
||||
"defs": {
|
||||
"main": {
|
||||
"key": "literal:self",
|
||||
"type": "record",
|
||||
"record": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"type": "blob",
|
||||
"accept": [
|
||||
"image/png",
|
||||
"image/jpeg"
|
||||
],
|
||||
"maxSize": 1000000,
|
||||
"description": "Small image to be displayed next to posts from account. AKA, 'profile picture'"
|
||||
},
|
||||
"banner": {
|
||||
"type": "blob",
|
||||
"accept": [
|
||||
"image/png",
|
||||
"image/jpeg"
|
||||
],
|
||||
"maxSize": 1000000,
|
||||
"description": "Larger horizontal image to display behind profile view."
|
||||
},
|
||||
"labels": {
|
||||
"refs": [
|
||||
"com.atproto.label.defs#selfLabels"
|
||||
],
|
||||
"type": "union",
|
||||
"description": "Self-label values, specific to the Bluesky application, on the overall account."
|
||||
},
|
||||
"website": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"pronouns": {
|
||||
"type": "string",
|
||||
"maxLength": 200,
|
||||
"description": "Free-form pronouns text.",
|
||||
"maxGraphemes": 20
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"pinnedPost": {
|
||||
"ref": "com.atproto.repo.strongRef",
|
||||
"type": "ref"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 2560,
|
||||
"description": "Free-form profile description text.",
|
||||
"maxGraphemes": 256
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"maxLength": 640,
|
||||
"maxGraphemes": 64
|
||||
},
|
||||
"joinedViaStarterPack": {
|
||||
"ref": "com.atproto.repo.strongRef",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "A declaration of a Bluesky account profile."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"id": "app.bsky.richtext.facet",
|
||||
"defs": {
|
||||
"tag": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"tag"
|
||||
],
|
||||
"properties": {
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"maxLength": 640,
|
||||
"maxGraphemes": 64
|
||||
}
|
||||
},
|
||||
"description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags')."
|
||||
},
|
||||
"link": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"uri"
|
||||
],
|
||||
"properties": {
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL."
|
||||
},
|
||||
"main": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"index",
|
||||
"features"
|
||||
],
|
||||
"properties": {
|
||||
"index": {
|
||||
"ref": "#byteSlice",
|
||||
"type": "ref"
|
||||
},
|
||||
"features": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"refs": [
|
||||
"#mention",
|
||||
"#link",
|
||||
"#tag"
|
||||
],
|
||||
"type": "union"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Annotation of a sub-string within rich text."
|
||||
},
|
||||
"mention": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"did"
|
||||
],
|
||||
"properties": {
|
||||
"did": {
|
||||
"type": "string",
|
||||
"format": "did"
|
||||
}
|
||||
},
|
||||
"description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID."
|
||||
},
|
||||
"byteSlice": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"byteStart",
|
||||
"byteEnd"
|
||||
],
|
||||
"properties": {
|
||||
"byteEnd": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"byteStart": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
{
|
||||
"id": "com.atproto.label.defs",
|
||||
"defs": {
|
||||
"label": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"src",
|
||||
"uri",
|
||||
"val",
|
||||
"cts"
|
||||
],
|
||||
"properties": {
|
||||
"cid": {
|
||||
"type": "string",
|
||||
"format": "cid",
|
||||
"description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to."
|
||||
},
|
||||
"cts": {
|
||||
"type": "string",
|
||||
"format": "datetime",
|
||||
"description": "Timestamp when this label was created."
|
||||
},
|
||||
"exp": {
|
||||
"type": "string",
|
||||
"format": "datetime",
|
||||
"description": "Timestamp at which this label expires (no longer applies)."
|
||||
},
|
||||
"neg": {
|
||||
"type": "boolean",
|
||||
"description": "If true, this is a negation label, overwriting a previous label."
|
||||
},
|
||||
"sig": {
|
||||
"type": "bytes",
|
||||
"description": "Signature of dag-cbor encoded label."
|
||||
},
|
||||
"src": {
|
||||
"type": "string",
|
||||
"format": "did",
|
||||
"description": "DID of the actor who created this label."
|
||||
},
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "AT URI of the record, repository (account), or other resource that this label applies to."
|
||||
},
|
||||
"val": {
|
||||
"type": "string",
|
||||
"maxLength": 128,
|
||||
"description": "The short string name of the value or type of this label."
|
||||
},
|
||||
"ver": {
|
||||
"type": "integer",
|
||||
"description": "The AT Protocol version of the label object."
|
||||
}
|
||||
},
|
||||
"description": "Metadata tag on an atproto resource (eg, repo or record)."
|
||||
},
|
||||
"selfLabel": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"val"
|
||||
],
|
||||
"properties": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"maxLength": 128,
|
||||
"description": "The short string name of the value or type of this label."
|
||||
}
|
||||
},
|
||||
"description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel."
|
||||
},
|
||||
"labelValue": {
|
||||
"type": "string",
|
||||
"knownValues": [
|
||||
"!hide",
|
||||
"!warn",
|
||||
"!no-unauthenticated",
|
||||
"porn",
|
||||
"sexual",
|
||||
"nudity",
|
||||
"graphic-media",
|
||||
"bot"
|
||||
]
|
||||
},
|
||||
"selfLabels": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"values"
|
||||
],
|
||||
"properties": {
|
||||
"values": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "#selfLabel",
|
||||
"type": "ref"
|
||||
},
|
||||
"maxLength": 10
|
||||
}
|
||||
},
|
||||
"description": "Metadata tags on an atproto record, published by the author within the record."
|
||||
},
|
||||
"labelValueDefinition": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identifier",
|
||||
"severity",
|
||||
"blurs",
|
||||
"locales"
|
||||
],
|
||||
"properties": {
|
||||
"blurs": {
|
||||
"type": "string",
|
||||
"description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
|
||||
"knownValues": [
|
||||
"content",
|
||||
"media",
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"locales": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "#labelValueDefinitionStrings",
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"severity": {
|
||||
"type": "string",
|
||||
"description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
|
||||
"knownValues": [
|
||||
"inform",
|
||||
"alert",
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"adultOnly": {
|
||||
"type": "boolean",
|
||||
"description": "Does the user need to have adult content enabled in order to configure this label?"
|
||||
},
|
||||
"identifier": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
|
||||
"maxGraphemes": 100
|
||||
},
|
||||
"defaultSetting": {
|
||||
"type": "string",
|
||||
"default": "warn",
|
||||
"description": "The default setting for this label.",
|
||||
"knownValues": [
|
||||
"ignore",
|
||||
"warn",
|
||||
"hide"
|
||||
]
|
||||
}
|
||||
},
|
||||
"description": "Declares a label value and its expected interpretations and behaviors."
|
||||
},
|
||||
"labelValueDefinitionStrings": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"lang",
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"properties": {
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"format": "language",
|
||||
"description": "The code of the language these strings are written in."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 640,
|
||||
"description": "A short human-readable name for the label.",
|
||||
"maxGraphemes": 64
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 100000,
|
||||
"description": "A longer description of what the label means and why it might be applied.",
|
||||
"maxGraphemes": 10000
|
||||
}
|
||||
},
|
||||
"description": "Strings which describe the label in the UI, localized into a specific language."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"id": "com.atproto.repo.strongRef",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"uri",
|
||||
"cid"
|
||||
],
|
||||
"properties": {
|
||||
"cid": {
|
||||
"type": "string",
|
||||
"format": "cid"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"format": "at-uri"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1,
|
||||
"description": "A URI with a content-hash fingerprint."
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"lexicon": 1,
|
||||
"id": "place.stream.chat.message",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "record",
|
||||
"description": "Record containing a Streamplace chat message.",
|
||||
"key": "tid",
|
||||
"record": {
|
||||
"type": "object",
|
||||
"required": ["text", "createdAt", "streamer"],
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"maxLength": 3000,
|
||||
"maxGraphemes": 300,
|
||||
"description": "The primary message content. May be an empty string, if there are embeds."
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "datetime",
|
||||
"description": "Client-declared timestamp when this message was originally created."
|
||||
},
|
||||
"facets": {
|
||||
"type": "array",
|
||||
"description": "Annotations of text (mentions, URLs, etc)",
|
||||
"items": {
|
||||
"type": "ref",
|
||||
"ref": "place.stream.richtext.facet"
|
||||
}
|
||||
},
|
||||
"streamer": {
|
||||
"type": "string",
|
||||
"format": "did",
|
||||
"description": "The DID of the streamer whose chat this is."
|
||||
},
|
||||
"reply": {
|
||||
"type": "ref",
|
||||
"ref": "#replyRef"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"replyRef": {
|
||||
"type": "object",
|
||||
"required": ["root", "parent"],
|
||||
"properties": {
|
||||
"root": {
|
||||
"type": "ref",
|
||||
"ref": "com.atproto.repo.strongRef"
|
||||
},
|
||||
"parent": {
|
||||
"type": "ref",
|
||||
"ref": "com.atproto.repo.strongRef"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"lexicon": 1,
|
||||
"id": "place.stream.richtext.facet",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "object",
|
||||
"description": "Annotation of a sub-string within rich text in a livestream chat message.",
|
||||
"required": ["index", "features"],
|
||||
"properties": {
|
||||
"index": {
|
||||
"type": "ref",
|
||||
"ref": "app.bsky.richtext.facet#byteSlice"
|
||||
},
|
||||
"features": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "union",
|
||||
"refs": [
|
||||
"app.bsky.richtext.facet#mention",
|
||||
"app.bsky.richtext.facet#link"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2340
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "@stevanfreeborn/streamplace-term-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "Terminal chat client for Streamplace built on AT Protocol and Jetstream",
|
||||
"main": "./dist/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"streamplace-term-chat": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"LICENSE.md"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "tsx --env-file=.env src/index.ts",
|
||||
"start": "node --env-file=.env dist/index.js",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build": "tsc",
|
||||
"prepublishOnly": "npm run typecheck && npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/stevanfreeborn/streamplace-term-chat.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/stevanfreeborn/streamplace-term-chat/issues"
|
||||
},
|
||||
"homepage": "https://github.com/stevanfreeborn/streamplace-term-chat#readme",
|
||||
"keywords": [
|
||||
"atproto",
|
||||
"streamplace",
|
||||
"chat",
|
||||
"cli",
|
||||
"bluesky",
|
||||
"jetstream",
|
||||
"terminal"
|
||||
],
|
||||
"author": {
|
||||
"name": "Stevan Freeborn",
|
||||
"url": "https://stevanfreeborn.com",
|
||||
"email": "me@stevanfreeborn.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^26.4.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"tsx": "^4.23.12",
|
||||
"typescript": "^7.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atcute/identity-resolver": "^2.0.1",
|
||||
"@atproto/lex": "^0.3.8",
|
||||
"@atproto/oauth-client-node": "^0.5.4",
|
||||
"open": "^11.0.1",
|
||||
"ws": "^8.21.3"
|
||||
},
|
||||
"allowScripts": {
|
||||
"esbuild@0.28.2": true,
|
||||
"core-js@3.50.0": true
|
||||
}
|
||||
}
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
#!/usr/bin/env node
|
||||
import http from "node:http";
|
||||
import open from "open";
|
||||
import os from 'os';
|
||||
import {
|
||||
buildAtprotoLoopbackClientMetadata,
|
||||
NodeOAuthClient,
|
||||
type NodeSavedSession,
|
||||
type NodeSavedState,
|
||||
} from "@atproto/oauth-client-node";
|
||||
import { Client } from "@atproto/lex";
|
||||
import * as place from "./lexicons/place.js";
|
||||
import WebSocket from "ws";
|
||||
import {
|
||||
CompositeDidDocumentResolver,
|
||||
PlcDidDocumentResolver,
|
||||
WebDidDocumentResolver,
|
||||
LocalActorResolver,
|
||||
WellKnownHandleResolver,
|
||||
DohJsonHandleResolver,
|
||||
CompositeHandleResolver,
|
||||
} from "@atcute/identity-resolver";
|
||||
import readline from "readline";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
|
||||
const c = {
|
||||
gray: (str: string) => `\x1b[90m${str}\x1b[0m`,
|
||||
bold: (str: string) => `\x1b[1m${str}\x1b[22m`,
|
||||
cyan: (str: string) => `\x1b[36m${str}\x1b[39m`,
|
||||
green: (str: string) => `\x1b[32m${str}\x1b[39m`,
|
||||
magenta: (str: string) => `\x1b[35m${str}\x1b[39m`,
|
||||
yellow: (str: string) => `\x1b[33m${str}\x1b[39m`,
|
||||
red: (str: string) => `\x1b[31m${str}\x1b[39m`,
|
||||
};
|
||||
|
||||
const DEFAULT_JETSTREAM_URL =
|
||||
"wss://jetstream.us-east.bsky.network/xrpc/network.bsky.jetstream.subscribeEvents?collections=place.stream.chat.message";
|
||||
const CONFIG_DIR = path.join(os.homedir(), ".streamplace-term-chat");
|
||||
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
||||
|
||||
const SESSION_FILE = path.join(CONFIG_DIR, ".session-store.json");
|
||||
const STATE_FILE = path.join(CONFIG_DIR, ".state-store.json");
|
||||
|
||||
async function loadJsonStore<T>(filePath: string): Promise<Map<string, T>> {
|
||||
try {
|
||||
const data = await fs.readFile(filePath, "utf-8");
|
||||
return new Map(Object.entries(JSON.parse(data)));
|
||||
} catch {
|
||||
return new Map();
|
||||
}
|
||||
}
|
||||
|
||||
async function saveJsonStore<T>(
|
||||
filePath: string,
|
||||
store: Map<string, T>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const obj = Object.fromEntries(store.entries());
|
||||
await fs.writeFile(filePath, JSON.stringify(obj, null, 2), "utf-8");
|
||||
} catch (err) {
|
||||
console.error(`Failed to save store to ${filePath}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
const stateStore = await loadJsonStore<NodeSavedState>(STATE_FILE);
|
||||
const sessionStore = await loadJsonStore<NodeSavedSession>(SESSION_FILE);
|
||||
|
||||
const handleResolver = new CompositeHandleResolver({
|
||||
methods: {
|
||||
dns: new DohJsonHandleResolver({
|
||||
dohUrl: "https://mozilla.cloudflare-dns.com/dns-query",
|
||||
}),
|
||||
http: new WellKnownHandleResolver(),
|
||||
},
|
||||
});
|
||||
|
||||
const didResolver = new CompositeDidDocumentResolver({
|
||||
methods: {
|
||||
plc: new PlcDidDocumentResolver(),
|
||||
web: new WebDidDocumentResolver(),
|
||||
},
|
||||
});
|
||||
|
||||
const actorResolver = new LocalActorResolver({
|
||||
handleResolver,
|
||||
didDocumentResolver: didResolver,
|
||||
});
|
||||
|
||||
const handleCache = new Map<string, string>();
|
||||
|
||||
async function resolveHandle(did: string): Promise<string> {
|
||||
const cached = handleCache.get(did);
|
||||
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
const actor = await actorResolver.resolve(did as any);
|
||||
const identifier = actor.handle ?? actor.did;
|
||||
handleCache.set(did, identifier);
|
||||
return identifier;
|
||||
} catch {
|
||||
return did;
|
||||
}
|
||||
}
|
||||
|
||||
const oauthClient = new NodeOAuthClient({
|
||||
clientMetadata: buildAtprotoLoopbackClientMetadata({
|
||||
scope: "atproto repo:place.stream.chat.message?action=create",
|
||||
redirect_uris: ["http://127.0.0.1:3000/callback"],
|
||||
}),
|
||||
stateStore: {
|
||||
async get(key: string) {
|
||||
return stateStore.get(key);
|
||||
},
|
||||
async set(key: string, value: NodeSavedState) {
|
||||
stateStore.set(key, value);
|
||||
await saveJsonStore(STATE_FILE, stateStore);
|
||||
},
|
||||
async del(key: string) {
|
||||
stateStore.delete(key);
|
||||
await saveJsonStore(STATE_FILE, stateStore);
|
||||
},
|
||||
},
|
||||
sessionStore: {
|
||||
async get(key: string) {
|
||||
return sessionStore.get(key);
|
||||
},
|
||||
async set(key: string, value: NodeSavedSession) {
|
||||
sessionStore.set(key, value);
|
||||
await saveJsonStore(SESSION_FILE, sessionStore);
|
||||
},
|
||||
async del(key: string) {
|
||||
sessionStore.delete(key);
|
||||
await saveJsonStore(SESSION_FILE, sessionStore);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function resolveToDid(input: string): Promise<string> {
|
||||
if (input.startsWith("did:")) {
|
||||
return input;
|
||||
}
|
||||
|
||||
const resolved = await actorResolver.resolve(input as any);
|
||||
return resolved.did;
|
||||
}
|
||||
|
||||
async function login(handleOrDid: string) {
|
||||
let userDid: string;
|
||||
|
||||
try {
|
||||
userDid = await resolveToDid(handleOrDid);
|
||||
} catch (err) {
|
||||
console.error(c.red(`Failed to resolve handle "${handleOrDid}":`), err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
const session = await oauthClient.restore(userDid);
|
||||
|
||||
if (session) {
|
||||
console.log(
|
||||
c.green(`\nRestored existing session for @${handleOrDid} (${userDid})`),
|
||||
);
|
||||
return session;
|
||||
}
|
||||
} catch { }
|
||||
|
||||
console.log(c.gray(`\nInitiating browser login for @${handleOrDid}...`));
|
||||
|
||||
const authUrl = await oauthClient.authorize(handleOrDid, {
|
||||
scope: "atproto repo:place.stream.chat.message?action=create",
|
||||
});
|
||||
|
||||
const params = await new Promise<URLSearchParams>((resolve, reject) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
if (!req.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(req.url, "http://127.0.0.1:3000");
|
||||
|
||||
if (url.pathname === "/callback") {
|
||||
res.writeHead(200, { "Content-Type": "text/html" });
|
||||
res.end(
|
||||
"<h1>Authorized! You can close this tab and return to your terminal.</h1>",
|
||||
);
|
||||
resolve(url.searchParams);
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(3000, "127.0.0.1", () => {
|
||||
console.log(
|
||||
c.gray(
|
||||
"Listening on http://127.0.0.1:3000/callback for OAuth redirect...",
|
||||
),
|
||||
);
|
||||
open(authUrl.toString());
|
||||
});
|
||||
|
||||
server.on("error", reject);
|
||||
});
|
||||
|
||||
const { session } = await oauthClient.callback(params);
|
||||
return session;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const handleArg = args.find((arg) => !arg.startsWith("--"));
|
||||
const streamerFlagIndex = args.indexOf("--streamer");
|
||||
const streamerInput =
|
||||
streamerFlagIndex !== -1
|
||||
? args[streamerFlagIndex + 1]
|
||||
: process.env.STREAMER_DID;
|
||||
|
||||
if (!handleArg || args.includes("--help") || args.includes("-h")) {
|
||||
console.log(
|
||||
c.gray(`
|
||||
Streamplace Chat Client
|
||||
|
||||
Usage:
|
||||
npm start -- <your-handle> [--streamer <streamer-handle-or-did>]
|
||||
|
||||
Options:
|
||||
--streamer Specify target streamer handle/DID to view and post chat messages.
|
||||
Defaults to your own handle if omitted.
|
||||
|
||||
Examples:
|
||||
npm start -- alice.bsky.social
|
||||
npm start -- alice.bsky.social --streamer streamer.bsky.social
|
||||
`),
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const session = await login(handleArg);
|
||||
const client = new Client(session);
|
||||
|
||||
let targetStreamerDid = session.did as string;
|
||||
|
||||
if (streamerInput) {
|
||||
if (streamerInput.startsWith("did:")) {
|
||||
targetStreamerDid = streamerInput;
|
||||
} else {
|
||||
try {
|
||||
const resolved = await actorResolver.resolve(streamerInput as any);
|
||||
targetStreamerDid = resolved.did;
|
||||
} catch (err) {
|
||||
console.error(
|
||||
c.red(
|
||||
`Could not resolve streamer handle "${streamerInput}". Using user's DID.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const connectedAsHandle = await resolveHandle(session.did);
|
||||
const targetStreamerHandle = await resolveHandle(targetStreamerDid);
|
||||
|
||||
console.clear();
|
||||
|
||||
console.log(
|
||||
c.green(c.bold("Connected as:")) +
|
||||
` @${connectedAsHandle} (${session.did})`,
|
||||
);
|
||||
console.log(
|
||||
c.yellow(c.bold("Chat room streamer:")) +
|
||||
` @${targetStreamerHandle} (${targetStreamerDid})`,
|
||||
);
|
||||
console.log(
|
||||
c.gray("Type your message and hit Enter. Commands: /quit, /help\n"),
|
||||
);
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
prompt: c.cyan(c.bold("> ")),
|
||||
});
|
||||
|
||||
let ws: WebSocket | null = null;
|
||||
let isConnecting = false;
|
||||
|
||||
function connectWs() {
|
||||
if (isConnecting) return;
|
||||
isConnecting = true;
|
||||
|
||||
ws = new WebSocket(process.env.JETSTREAM_URL || DEFAULT_JETSTREAM_URL);
|
||||
|
||||
ws.on("open", () => {
|
||||
isConnecting = false;
|
||||
rl.prompt();
|
||||
});
|
||||
|
||||
ws.on("message", async (msg) => {
|
||||
try {
|
||||
const jsonMsg = JSON.parse(msg.toString());
|
||||
|
||||
if (jsonMsg.payload?.record?.streamer !== targetStreamerDid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const createdAt = new Date(jsonMsg.payload.record.createdAt);
|
||||
const timestamp = createdAt.toLocaleTimeString("en-US", {
|
||||
hour12: false,
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
|
||||
const senderDid = jsonMsg.payload.did;
|
||||
const senderIdentifier = await resolveHandle(senderDid);
|
||||
const messageText = jsonMsg.payload.record.text;
|
||||
const formattedLine = `${c.gray(`[${timestamp}]`)} ${c.magenta("[SP]")} ${c.cyan(
|
||||
c.bold(senderIdentifier),
|
||||
)}: ${messageText}`;
|
||||
|
||||
readline.clearLine(process.stdout, 0);
|
||||
readline.cursorTo(process.stdout, 0);
|
||||
console.log(formattedLine);
|
||||
rl.prompt(true);
|
||||
} catch { }
|
||||
});
|
||||
|
||||
ws.on("error", (err) => {
|
||||
readline.clearLine(process.stdout, 0);
|
||||
readline.cursorTo(process.stdout, 0);
|
||||
console.error(`[WebSocket Error] ${err.message}`);
|
||||
rl.prompt(true);
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
isConnecting = false;
|
||||
setTimeout(connectWs, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
connectWs();
|
||||
|
||||
rl.on("line", async (line) => {
|
||||
process.stdout.moveCursor(0, -1);
|
||||
process.stdout.clearLine(0);
|
||||
process.stdout.cursorTo(0);
|
||||
|
||||
const text = line.trim();
|
||||
|
||||
if (!text) {
|
||||
rl.prompt();
|
||||
return;
|
||||
}
|
||||
|
||||
if (text === "/quit" || text === "/exit") {
|
||||
rl.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (text === "/help") {
|
||||
console.log("Commands: /quit, /exit, /help");
|
||||
rl.prompt();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await client.create(place.stream.chat.message as any, {
|
||||
streamer: targetStreamerDid,
|
||||
text: text,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
console.error(c.red(`Failed to send message: ${err.message}`));
|
||||
}
|
||||
}
|
||||
|
||||
rl.prompt();
|
||||
});
|
||||
|
||||
rl.on("close", () => {
|
||||
if (ws) {
|
||||
ws.removeAllListeners();
|
||||
ws.close();
|
||||
}
|
||||
|
||||
console.log(c.gray("\nExiting chat. Goodbye!"));
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
if (err instanceof Error) {
|
||||
console.error(c.red(`Fatal error: ${err.message}`));
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as bsky from './app/bsky.js'
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as actor from './bsky/actor.js'
|
||||
export * as richtext from './bsky/richtext.js'
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as profile from './actor/profile.js'
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
import { l } from '@atproto/lex'
|
||||
import * as LabelDefs from './..\\..\\..\\com\\atproto\\label\\defs.defs.js'
|
||||
import * as RepoStrongRef from './..\\..\\..\\com\\atproto\\repo\\strongRef.defs.js'
|
||||
|
||||
const $nsid = 'app.bsky.actor.profile'
|
||||
|
||||
type $nsid = typeof $nsid
|
||||
|
||||
export { $nsid }
|
||||
|
||||
/** A declaration of a Bluesky account profile. */
|
||||
type Main = {
|
||||
$type: 'app.bsky.actor.profile'
|
||||
|
||||
/**
|
||||
* Small image to be displayed next to posts from account. AKA, 'profile picture'
|
||||
*/
|
||||
avatar?: l.BlobRef
|
||||
|
||||
/**
|
||||
* Larger horizontal image to display behind profile view.
|
||||
*/
|
||||
banner?: l.BlobRef
|
||||
|
||||
/**
|
||||
* Self-label values, specific to the Bluesky application, on the overall account.
|
||||
*/
|
||||
labels?: l.$Typed<LabelDefs.SelfLabels> | l.Unknown$TypedObject
|
||||
website?: l.UriString
|
||||
|
||||
/**
|
||||
* Free-form pronouns text.
|
||||
*/
|
||||
pronouns?: string
|
||||
createdAt?: l.DatetimeString
|
||||
pinnedPost?: RepoStrongRef.Main
|
||||
|
||||
/**
|
||||
* Free-form profile description text.
|
||||
*/
|
||||
description?: string
|
||||
displayName?: string
|
||||
joinedViaStarterPack?: RepoStrongRef.Main
|
||||
}
|
||||
|
||||
export type { Main }
|
||||
|
||||
/** A declaration of a Bluesky account profile. */
|
||||
const main = /*#__PURE__*/ l.record<'literal:self', Main>(
|
||||
'literal:self',
|
||||
$nsid,
|
||||
/*#__PURE__*/ l.object({
|
||||
avatar: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.blob({
|
||||
accept: ['image/png', 'image/jpeg'],
|
||||
maxSize: 1000000,
|
||||
}),
|
||||
),
|
||||
banner: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.blob({
|
||||
accept: ['image/png', 'image/jpeg'],
|
||||
maxSize: 1000000,
|
||||
}),
|
||||
),
|
||||
labels: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.typedUnion(
|
||||
[
|
||||
/*#__PURE__*/ l.typedRef<LabelDefs.SelfLabels>(
|
||||
(() => LabelDefs.selfLabels) as any,
|
||||
),
|
||||
],
|
||||
false,
|
||||
),
|
||||
),
|
||||
website: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.string({ format: 'uri' }),
|
||||
),
|
||||
pronouns: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.string({ maxLength: 200, maxGraphemes: 20 }),
|
||||
),
|
||||
createdAt: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.string({ format: 'datetime' }),
|
||||
),
|
||||
pinnedPost: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.ref<RepoStrongRef.Main>(
|
||||
(() => RepoStrongRef.main) as any,
|
||||
),
|
||||
),
|
||||
description: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.string({ maxLength: 2560, maxGraphemes: 256 }),
|
||||
),
|
||||
displayName: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.string({ maxLength: 640, maxGraphemes: 64 }),
|
||||
),
|
||||
joinedViaStarterPack: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.ref<RepoStrongRef.Main>(
|
||||
(() => RepoStrongRef.main) as any,
|
||||
),
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
export { main }
|
||||
|
||||
const $type = $nsid
|
||||
|
||||
type $type = typeof $type
|
||||
|
||||
export { $type }
|
||||
|
||||
export const $isTypeOf = /*#__PURE__*/ main.isTypeOf.bind(main)
|
||||
export const $build = /*#__PURE__*/ main.build.bind(main)
|
||||
export const $assert = /*#__PURE__*/ main.assert.bind(main)
|
||||
export const $check = /*#__PURE__*/ main.check.bind(main)
|
||||
export const $cast = /*#__PURE__*/ main.cast.bind(main)
|
||||
export const $ifMatches = /*#__PURE__*/ main.ifMatches.bind(main)
|
||||
export const $matches = /*#__PURE__*/ main.matches.bind(main)
|
||||
export const $parse = /*#__PURE__*/ main.parse.bind(main)
|
||||
export const $safeParse = /*#__PURE__*/ main.safeParse.bind(main)
|
||||
export const $validate = /*#__PURE__*/ main.validate.bind(main)
|
||||
export const $safeValidate = /*#__PURE__*/ main.safeValidate.bind(main)
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * from './profile.defs.js'
|
||||
export { main as default } from './profile.defs.js'
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as facet from './richtext/facet.js'
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
import { l } from '@atproto/lex'
|
||||
|
||||
const $nsid = 'app.bsky.richtext.facet'
|
||||
|
||||
type $nsid = typeof $nsid
|
||||
|
||||
export { $nsid }
|
||||
|
||||
/** Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). */
|
||||
type Tag = { $type?: 'app.bsky.richtext.facet#tag'; tag: string }
|
||||
|
||||
export type { Tag }
|
||||
|
||||
/** Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). */
|
||||
const tag = /*#__PURE__*/ l.typedObject<Tag>(
|
||||
$nsid,
|
||||
'tag',
|
||||
/*#__PURE__*/ l.object({
|
||||
tag: /*#__PURE__*/ l.string({ maxLength: 640, maxGraphemes: 64 }),
|
||||
}),
|
||||
)
|
||||
|
||||
export { tag }
|
||||
|
||||
/** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. */
|
||||
type Link = { $type?: 'app.bsky.richtext.facet#link'; uri: l.UriString }
|
||||
|
||||
export type { Link }
|
||||
|
||||
/** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. */
|
||||
const link = /*#__PURE__*/ l.typedObject<Link>(
|
||||
$nsid,
|
||||
'link',
|
||||
/*#__PURE__*/ l.object({ uri: /*#__PURE__*/ l.string({ format: 'uri' }) }),
|
||||
)
|
||||
|
||||
export { link }
|
||||
|
||||
/** Annotation of a sub-string within rich text. */
|
||||
type Main = {
|
||||
$type?: 'app.bsky.richtext.facet'
|
||||
index: ByteSlice
|
||||
features: (
|
||||
l.$Typed<Mention> | l.$Typed<Link> | l.$Typed<Tag> | l.Unknown$TypedObject
|
||||
)[]
|
||||
}
|
||||
|
||||
export type { Main }
|
||||
|
||||
/** Annotation of a sub-string within rich text. */
|
||||
const main = /*#__PURE__*/ l.typedObject<Main>(
|
||||
$nsid,
|
||||
'main',
|
||||
/*#__PURE__*/ l.object({
|
||||
index: /*#__PURE__*/ l.ref<ByteSlice>((() => byteSlice) as any),
|
||||
features: /*#__PURE__*/ l.array(
|
||||
/*#__PURE__*/ l.typedUnion(
|
||||
[
|
||||
/*#__PURE__*/ l.typedRef<Mention>((() => mention) as any),
|
||||
/*#__PURE__*/ l.typedRef<Link>((() => link) as any),
|
||||
/*#__PURE__*/ l.typedRef<Tag>((() => tag) as any),
|
||||
],
|
||||
false,
|
||||
),
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
export { main }
|
||||
|
||||
const $type = $nsid
|
||||
|
||||
type $type = typeof $type
|
||||
|
||||
export { $type }
|
||||
|
||||
export const $isTypeOf = /*#__PURE__*/ main.isTypeOf.bind(main)
|
||||
export const $build = /*#__PURE__*/ main.build.bind(main)
|
||||
export const $assert = /*#__PURE__*/ main.assert.bind(main)
|
||||
export const $check = /*#__PURE__*/ main.check.bind(main)
|
||||
export const $cast = /*#__PURE__*/ main.cast.bind(main)
|
||||
export const $ifMatches = /*#__PURE__*/ main.ifMatches.bind(main)
|
||||
export const $matches = /*#__PURE__*/ main.matches.bind(main)
|
||||
export const $parse = /*#__PURE__*/ main.parse.bind(main)
|
||||
export const $safeParse = /*#__PURE__*/ main.safeParse.bind(main)
|
||||
export const $validate = /*#__PURE__*/ main.validate.bind(main)
|
||||
export const $safeValidate = /*#__PURE__*/ main.safeValidate.bind(main)
|
||||
|
||||
/** Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. */
|
||||
type Mention = { $type?: 'app.bsky.richtext.facet#mention'; did: l.DidString }
|
||||
|
||||
export type { Mention }
|
||||
|
||||
/** Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. */
|
||||
const mention = /*#__PURE__*/ l.typedObject<Mention>(
|
||||
$nsid,
|
||||
'mention',
|
||||
/*#__PURE__*/ l.object({ did: /*#__PURE__*/ l.string({ format: 'did' }) }),
|
||||
)
|
||||
|
||||
export { mention }
|
||||
|
||||
/** Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. */
|
||||
type ByteSlice = {
|
||||
$type?: 'app.bsky.richtext.facet#byteSlice'
|
||||
byteEnd: number
|
||||
byteStart: number
|
||||
}
|
||||
|
||||
export type { ByteSlice }
|
||||
|
||||
/** Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. */
|
||||
const byteSlice = /*#__PURE__*/ l.typedObject<ByteSlice>(
|
||||
$nsid,
|
||||
'byteSlice',
|
||||
/*#__PURE__*/ l.object({
|
||||
byteEnd: /*#__PURE__*/ l.integer({ minimum: 0 }),
|
||||
byteStart: /*#__PURE__*/ l.integer({ minimum: 0 }),
|
||||
}),
|
||||
)
|
||||
|
||||
export { byteSlice }
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * from './facet.defs.js'
|
||||
export { main as default } from './facet.defs.js'
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as atproto from './com/atproto.js'
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as label from './atproto/label.js'
|
||||
export * as repo from './atproto/repo.js'
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as defs from './label/defs.js'
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
import { l } from '@atproto/lex'
|
||||
|
||||
const $nsid = 'com.atproto.label.defs'
|
||||
|
||||
type $nsid = typeof $nsid
|
||||
|
||||
export { $nsid }
|
||||
|
||||
/** Metadata tag on an atproto resource (eg, repo or record). */
|
||||
type Label = {
|
||||
$type?: 'com.atproto.label.defs#label'
|
||||
|
||||
/**
|
||||
* Optionally, CID specifying the specific version of 'uri' resource this label applies to.
|
||||
*/
|
||||
cid?: l.CidString
|
||||
|
||||
/**
|
||||
* Timestamp when this label was created.
|
||||
*/
|
||||
cts: l.DatetimeString
|
||||
|
||||
/**
|
||||
* Timestamp at which this label expires (no longer applies).
|
||||
*/
|
||||
exp?: l.DatetimeString
|
||||
|
||||
/**
|
||||
* If true, this is a negation label, overwriting a previous label.
|
||||
*/
|
||||
neg?: boolean
|
||||
|
||||
/**
|
||||
* Signature of dag-cbor encoded label.
|
||||
*/
|
||||
sig?: Uint8Array
|
||||
|
||||
/**
|
||||
* DID of the actor who created this label.
|
||||
*/
|
||||
src: l.DidString
|
||||
|
||||
/**
|
||||
* AT URI of the record, repository (account), or other resource that this label applies to.
|
||||
*/
|
||||
uri: l.UriString
|
||||
|
||||
/**
|
||||
* The short string name of the value or type of this label.
|
||||
*/
|
||||
val: string
|
||||
|
||||
/**
|
||||
* The AT Protocol version of the label object.
|
||||
*/
|
||||
ver?: number
|
||||
}
|
||||
|
||||
export type { Label }
|
||||
|
||||
/** Metadata tag on an atproto resource (eg, repo or record). */
|
||||
const label = /*#__PURE__*/ l.typedObject<Label>(
|
||||
$nsid,
|
||||
'label',
|
||||
/*#__PURE__*/ l.object({
|
||||
cid: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string({ format: 'cid' })),
|
||||
cts: /*#__PURE__*/ l.string({ format: 'datetime' }),
|
||||
exp: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.string({ format: 'datetime' }),
|
||||
),
|
||||
neg: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
|
||||
sig: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.bytes()),
|
||||
src: /*#__PURE__*/ l.string({ format: 'did' }),
|
||||
uri: /*#__PURE__*/ l.string({ format: 'uri' }),
|
||||
val: /*#__PURE__*/ l.string({ maxLength: 128 }),
|
||||
ver: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.integer()),
|
||||
}),
|
||||
)
|
||||
|
||||
export { label }
|
||||
|
||||
/** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. */
|
||||
type SelfLabel = {
|
||||
$type?: 'com.atproto.label.defs#selfLabel'
|
||||
|
||||
/**
|
||||
* The short string name of the value or type of this label.
|
||||
*/
|
||||
val: string
|
||||
}
|
||||
|
||||
export type { SelfLabel }
|
||||
|
||||
/** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. */
|
||||
const selfLabel = /*#__PURE__*/ l.typedObject<SelfLabel>(
|
||||
$nsid,
|
||||
'selfLabel',
|
||||
/*#__PURE__*/ l.object({ val: /*#__PURE__*/ l.string({ maxLength: 128 }) }),
|
||||
)
|
||||
|
||||
export { selfLabel }
|
||||
|
||||
type LabelValue =
|
||||
| '!hide'
|
||||
| '!warn'
|
||||
| '!no-unauthenticated'
|
||||
| 'porn'
|
||||
| 'sexual'
|
||||
| 'nudity'
|
||||
| 'graphic-media'
|
||||
| 'bot'
|
||||
| l.UnknownString
|
||||
|
||||
export type { LabelValue }
|
||||
|
||||
const labelValue = /*#__PURE__*/ l.string<{
|
||||
knownValues: [
|
||||
'!hide',
|
||||
'!warn',
|
||||
'!no-unauthenticated',
|
||||
'porn',
|
||||
'sexual',
|
||||
'nudity',
|
||||
'graphic-media',
|
||||
'bot',
|
||||
]
|
||||
}>()
|
||||
|
||||
export { labelValue }
|
||||
|
||||
/** Metadata tags on an atproto record, published by the author within the record. */
|
||||
type SelfLabels = {
|
||||
$type?: 'com.atproto.label.defs#selfLabels'
|
||||
values: SelfLabel[]
|
||||
}
|
||||
|
||||
export type { SelfLabels }
|
||||
|
||||
/** Metadata tags on an atproto record, published by the author within the record. */
|
||||
const selfLabels = /*#__PURE__*/ l.typedObject<SelfLabels>(
|
||||
$nsid,
|
||||
'selfLabels',
|
||||
/*#__PURE__*/ l.object({
|
||||
values: /*#__PURE__*/ l.array(
|
||||
/*#__PURE__*/ l.ref<SelfLabel>((() => selfLabel) as any),
|
||||
{ maxLength: 10 },
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
export { selfLabels }
|
||||
|
||||
/** Declares a label value and its expected interpretations and behaviors. */
|
||||
type LabelValueDefinition = {
|
||||
$type?: 'com.atproto.label.defs#labelValueDefinition'
|
||||
|
||||
/**
|
||||
* What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.
|
||||
*/
|
||||
blurs: 'content' | 'media' | 'none' | l.UnknownString
|
||||
locales: LabelValueDefinitionStrings[]
|
||||
|
||||
/**
|
||||
* How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.
|
||||
*/
|
||||
severity: 'inform' | 'alert' | 'none' | l.UnknownString
|
||||
|
||||
/**
|
||||
* Does the user need to have adult content enabled in order to configure this label?
|
||||
*/
|
||||
adultOnly?: boolean
|
||||
|
||||
/**
|
||||
* The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).
|
||||
*/
|
||||
identifier: string
|
||||
|
||||
/**
|
||||
* The default setting for this label.
|
||||
*/
|
||||
defaultSetting?: 'ignore' | 'warn' | 'hide' | l.UnknownString
|
||||
}
|
||||
|
||||
export type { LabelValueDefinition }
|
||||
|
||||
/** Declares a label value and its expected interpretations and behaviors. */
|
||||
const labelValueDefinition = /*#__PURE__*/ l.typedObject<LabelValueDefinition>(
|
||||
$nsid,
|
||||
'labelValueDefinition',
|
||||
/*#__PURE__*/ l.object({
|
||||
blurs: /*#__PURE__*/ l.string<{
|
||||
knownValues: ['content', 'media', 'none']
|
||||
}>(),
|
||||
locales: /*#__PURE__*/ l.array(
|
||||
/*#__PURE__*/ l.ref<LabelValueDefinitionStrings>(
|
||||
(() => labelValueDefinitionStrings) as any,
|
||||
),
|
||||
),
|
||||
severity: /*#__PURE__*/ l.string<{
|
||||
knownValues: ['inform', 'alert', 'none']
|
||||
}>(),
|
||||
adultOnly: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
|
||||
identifier: /*#__PURE__*/ l.string({ maxLength: 100, maxGraphemes: 100 }),
|
||||
defaultSetting: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.withDefault(
|
||||
/*#__PURE__*/ l.string<{ knownValues: ['ignore', 'warn', 'hide'] }>(),
|
||||
'warn',
|
||||
),
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
export { labelValueDefinition }
|
||||
|
||||
/** Strings which describe the label in the UI, localized into a specific language. */
|
||||
type LabelValueDefinitionStrings = {
|
||||
$type?: 'com.atproto.label.defs#labelValueDefinitionStrings'
|
||||
|
||||
/**
|
||||
* The code of the language these strings are written in.
|
||||
*/
|
||||
lang: l.LanguageString
|
||||
|
||||
/**
|
||||
* A short human-readable name for the label.
|
||||
*/
|
||||
name: string
|
||||
|
||||
/**
|
||||
* A longer description of what the label means and why it might be applied.
|
||||
*/
|
||||
description: string
|
||||
}
|
||||
|
||||
export type { LabelValueDefinitionStrings }
|
||||
|
||||
/** Strings which describe the label in the UI, localized into a specific language. */
|
||||
const labelValueDefinitionStrings =
|
||||
/*#__PURE__*/ l.typedObject<LabelValueDefinitionStrings>(
|
||||
$nsid,
|
||||
'labelValueDefinitionStrings',
|
||||
/*#__PURE__*/ l.object({
|
||||
lang: /*#__PURE__*/ l.string({ format: 'language' }),
|
||||
name: /*#__PURE__*/ l.string({ maxLength: 640, maxGraphemes: 64 }),
|
||||
description: /*#__PURE__*/ l.string({
|
||||
maxLength: 100000,
|
||||
maxGraphemes: 10000,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
export { labelValueDefinitionStrings }
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * from './defs.defs.js'
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as strongRef from './repo/strongRef.js'
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
import { l } from '@atproto/lex'
|
||||
|
||||
const $nsid = 'com.atproto.repo.strongRef'
|
||||
|
||||
type $nsid = typeof $nsid
|
||||
|
||||
export { $nsid }
|
||||
|
||||
type Main = {
|
||||
$type?: 'com.atproto.repo.strongRef'
|
||||
cid: l.CidString
|
||||
uri: l.AtUriString
|
||||
}
|
||||
|
||||
export type { Main }
|
||||
|
||||
const main = /*#__PURE__*/ l.typedObject<Main>(
|
||||
$nsid,
|
||||
'main',
|
||||
/*#__PURE__*/ l.object({
|
||||
cid: /*#__PURE__*/ l.string({ format: 'cid' }),
|
||||
uri: /*#__PURE__*/ l.string({ format: 'at-uri' }),
|
||||
}),
|
||||
)
|
||||
|
||||
export { main }
|
||||
|
||||
const $type = $nsid
|
||||
|
||||
type $type = typeof $type
|
||||
|
||||
export { $type }
|
||||
|
||||
export const $isTypeOf = /*#__PURE__*/ main.isTypeOf.bind(main)
|
||||
export const $build = /*#__PURE__*/ main.build.bind(main)
|
||||
export const $assert = /*#__PURE__*/ main.assert.bind(main)
|
||||
export const $check = /*#__PURE__*/ main.check.bind(main)
|
||||
export const $cast = /*#__PURE__*/ main.cast.bind(main)
|
||||
export const $ifMatches = /*#__PURE__*/ main.ifMatches.bind(main)
|
||||
export const $matches = /*#__PURE__*/ main.matches.bind(main)
|
||||
export const $parse = /*#__PURE__*/ main.parse.bind(main)
|
||||
export const $safeParse = /*#__PURE__*/ main.safeParse.bind(main)
|
||||
export const $validate = /*#__PURE__*/ main.validate.bind(main)
|
||||
export const $safeValidate = /*#__PURE__*/ main.safeValidate.bind(main)
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * from './strongRef.defs.js'
|
||||
export { main as default } from './strongRef.defs.js'
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as stream from './place/stream.js'
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as chat from './stream/chat.js'
|
||||
export * as richtext from './stream/richtext.js'
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as message from './chat/message.js'
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
import { l } from '@atproto/lex'
|
||||
import * as RichtextFacet from './..\\richtext\\facet.defs.js'
|
||||
import * as RepoStrongRef from './..\\..\\..\\com\\atproto\\repo\\strongRef.defs.js'
|
||||
|
||||
const $nsid = 'place.stream.chat.message'
|
||||
|
||||
type $nsid = typeof $nsid
|
||||
|
||||
export { $nsid }
|
||||
|
||||
/** Record containing a Streamplace chat message. */
|
||||
type Main = {
|
||||
$type: 'place.stream.chat.message'
|
||||
|
||||
/**
|
||||
* The primary message content. May be an empty string, if there are embeds.
|
||||
*/
|
||||
text: string
|
||||
|
||||
/**
|
||||
* Client-declared timestamp when this message was originally created.
|
||||
*/
|
||||
createdAt: l.DatetimeString
|
||||
|
||||
/**
|
||||
* Annotations of text (mentions, URLs, etc)
|
||||
*/
|
||||
facets?: RichtextFacet.Main[]
|
||||
|
||||
/**
|
||||
* The DID of the streamer whose chat this is.
|
||||
*/
|
||||
streamer: l.DidString
|
||||
reply?: ReplyRef
|
||||
}
|
||||
|
||||
export type { Main }
|
||||
|
||||
/** Record containing a Streamplace chat message. */
|
||||
const main = /*#__PURE__*/ l.record<'tid', Main>(
|
||||
'tid',
|
||||
$nsid,
|
||||
/*#__PURE__*/ l.object({
|
||||
text: /*#__PURE__*/ l.string({ maxLength: 3000, maxGraphemes: 300 }),
|
||||
createdAt: /*#__PURE__*/ l.string({ format: 'datetime' }),
|
||||
facets: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.array(
|
||||
/*#__PURE__*/ l.ref<RichtextFacet.Main>(
|
||||
(() => RichtextFacet.main) as any,
|
||||
),
|
||||
),
|
||||
),
|
||||
streamer: /*#__PURE__*/ l.string({ format: 'did' }),
|
||||
reply: /*#__PURE__*/ l.optional(
|
||||
/*#__PURE__*/ l.ref<ReplyRef>((() => replyRef) as any),
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
export { main }
|
||||
|
||||
const $type = $nsid
|
||||
|
||||
type $type = typeof $type
|
||||
|
||||
export { $type }
|
||||
|
||||
export const $isTypeOf = /*#__PURE__*/ main.isTypeOf.bind(main)
|
||||
export const $build = /*#__PURE__*/ main.build.bind(main)
|
||||
export const $assert = /*#__PURE__*/ main.assert.bind(main)
|
||||
export const $check = /*#__PURE__*/ main.check.bind(main)
|
||||
export const $cast = /*#__PURE__*/ main.cast.bind(main)
|
||||
export const $ifMatches = /*#__PURE__*/ main.ifMatches.bind(main)
|
||||
export const $matches = /*#__PURE__*/ main.matches.bind(main)
|
||||
export const $parse = /*#__PURE__*/ main.parse.bind(main)
|
||||
export const $safeParse = /*#__PURE__*/ main.safeParse.bind(main)
|
||||
export const $validate = /*#__PURE__*/ main.validate.bind(main)
|
||||
export const $safeValidate = /*#__PURE__*/ main.safeValidate.bind(main)
|
||||
|
||||
type ReplyRef = {
|
||||
$type?: 'place.stream.chat.message#replyRef'
|
||||
root: RepoStrongRef.Main
|
||||
parent: RepoStrongRef.Main
|
||||
}
|
||||
|
||||
export type { ReplyRef }
|
||||
|
||||
const replyRef = /*#__PURE__*/ l.typedObject<ReplyRef>(
|
||||
$nsid,
|
||||
'replyRef',
|
||||
/*#__PURE__*/ l.object({
|
||||
root: /*#__PURE__*/ l.ref<RepoStrongRef.Main>(
|
||||
(() => RepoStrongRef.main) as any,
|
||||
),
|
||||
parent: /*#__PURE__*/ l.ref<RepoStrongRef.Main>(
|
||||
(() => RepoStrongRef.main) as any,
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
export { replyRef }
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * from './message.defs.js'
|
||||
export { main as default } from './message.defs.js'
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * as facet from './richtext/facet.js'
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
import { l } from '@atproto/lex'
|
||||
import * as RichtextFacet from './..\\..\\..\\app\\bsky\\richtext\\facet.defs.js'
|
||||
|
||||
const $nsid = 'place.stream.richtext.facet'
|
||||
|
||||
type $nsid = typeof $nsid
|
||||
|
||||
export { $nsid }
|
||||
|
||||
/** Annotation of a sub-string within rich text in a livestream chat message. */
|
||||
type Main = {
|
||||
$type?: 'place.stream.richtext.facet'
|
||||
index: RichtextFacet.ByteSlice
|
||||
features: (
|
||||
| l.$Typed<RichtextFacet.Mention>
|
||||
| l.$Typed<RichtextFacet.Link>
|
||||
| l.Unknown$TypedObject
|
||||
)[]
|
||||
}
|
||||
|
||||
export type { Main }
|
||||
|
||||
/** Annotation of a sub-string within rich text in a livestream chat message. */
|
||||
const main = /*#__PURE__*/ l.typedObject<Main>(
|
||||
$nsid,
|
||||
'main',
|
||||
/*#__PURE__*/ l.object({
|
||||
index: /*#__PURE__*/ l.ref<RichtextFacet.ByteSlice>(
|
||||
(() => RichtextFacet.byteSlice) as any,
|
||||
),
|
||||
features: /*#__PURE__*/ l.array(
|
||||
/*#__PURE__*/ l.typedUnion(
|
||||
[
|
||||
/*#__PURE__*/ l.typedRef<RichtextFacet.Mention>(
|
||||
(() => RichtextFacet.mention) as any,
|
||||
),
|
||||
/*#__PURE__*/ l.typedRef<RichtextFacet.Link>(
|
||||
(() => RichtextFacet.link) as any,
|
||||
),
|
||||
],
|
||||
false,
|
||||
),
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
export { main }
|
||||
|
||||
const $type = $nsid
|
||||
|
||||
type $type = typeof $type
|
||||
|
||||
export { $type }
|
||||
|
||||
export const $isTypeOf = /*#__PURE__*/ main.isTypeOf.bind(main)
|
||||
export const $build = /*#__PURE__*/ main.build.bind(main)
|
||||
export const $assert = /*#__PURE__*/ main.assert.bind(main)
|
||||
export const $check = /*#__PURE__*/ main.check.bind(main)
|
||||
export const $cast = /*#__PURE__*/ main.cast.bind(main)
|
||||
export const $ifMatches = /*#__PURE__*/ main.ifMatches.bind(main)
|
||||
export const $matches = /*#__PURE__*/ main.matches.bind(main)
|
||||
export const $parse = /*#__PURE__*/ main.parse.bind(main)
|
||||
export const $safeParse = /*#__PURE__*/ main.safeParse.bind(main)
|
||||
export const $validate = /*#__PURE__*/ main.validate.bind(main)
|
||||
export const $safeValidate = /*#__PURE__*/ main.safeValidate.bind(main)
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
||||
*/
|
||||
|
||||
export * from './facet.defs.js'
|
||||
export { main as default } from './facet.defs.js'
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"module": "nodenext",
|
||||
"target": "esnext",
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"strict": true,
|
||||
"jsx": "react-jsx",
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"moduleDetection": "force",
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user