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