grain.social is a photo sharing platform built on atproto.

init

+12634
+4
.dockerignore
··· 1 + **/node_modules 2 + **/*.db 3 + *.toml 4 + .env
+6
.gitignore
··· 1 + node_modules 2 + *.db* 3 + .DS_Store 4 + *.fly.toml 5 + image_storage 6 + .env
+5
.vscode/extensions.json
··· 1 + { 2 + "recommendations": [ 3 + "denoland.vscode-deno" 4 + ] 5 + }
+30
.vscode/settings.json
··· 1 + { 2 + "deno.enable": true, 3 + "deno.lint": true, 4 + "deno.codeLens.testArgs": ["--allow-all"], 5 + "editor.defaultFormatter": "denoland.vscode-deno", 6 + "[typescriptreact]": { 7 + "editor.defaultFormatter": "denoland.vscode-deno" 8 + }, 9 + "[typescript]": { 10 + "editor.defaultFormatter": "denoland.vscode-deno" 11 + }, 12 + "[javascriptreact]": { 13 + "editor.defaultFormatter": "denoland.vscode-deno" 14 + }, 15 + "[javascript]": { 16 + "editor.defaultFormatter": "denoland.vscode-deno" 17 + }, 18 + "[html]": { 19 + "editor.defaultFormatter": "denoland.vscode-deno" 20 + }, 21 + "[css]": { 22 + "editor.defaultFormatter": "denoland.vscode-deno" 23 + }, 24 + "[json]": { 25 + "editor.defaultFormatter": "denoland.vscode-deno" 26 + }, 27 + "editor.codeActionsOnSave": { 28 + "source.organizeImports": "explicit" 29 + } 30 + }
+29
Dockerfile
··· 1 + FROM denoland/deno:2.2.3 AS builder 2 + 3 + WORKDIR /app 4 + 5 + COPY . . 6 + 7 + # Give ownership to deno user and cache dependencies 8 + RUN chown -R deno:deno /app && \ 9 + deno cache ./main.tsx 10 + 11 + FROM denoland/deno:alpine-2.2.3 12 + 13 + COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs 14 + COPY litefs.yml /etc/litefs.yml 15 + 16 + # libstdc++ is needed for @gfx/canvas 17 + RUN apk add --no-cache ca-certificates fuse3 libstdc++ sqlite 18 + 19 + WORKDIR /app 20 + 21 + # Needed for @gfx/canvas 22 + RUN mkdir -p /usr/share/fonts 23 + 24 + COPY --from=builder $DENO_DIR $DENO_DIR 25 + COPY --from=builder /app . 26 + 27 + # Run LiteFS as the entrypoint. After it has connected and sync'd with the 28 + # cluster, it will run the commands listed in the "exec" field of the config. 29 + ENTRYPOINT ["litefs", "mount"]
+211
__generated__/index.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { 5 + createServer as createXrpcServer, 6 + Server as XrpcServer, 7 + type Options as XrpcOptions, 8 + type AuthVerifier, 9 + type StreamAuthVerifier, 10 + } from "npm:@atproto/xrpc-server" 11 + import { schemas } from './lexicons.ts' 12 + 13 + export const APP_BSKY_GRAPH = { 14 + DefsModlist: 'app.bsky.graph.defs#modlist', 15 + DefsCuratelist: 'app.bsky.graph.defs#curatelist', 16 + DefsReferencelist: 'app.bsky.graph.defs#referencelist', 17 + } 18 + export const APP_BSKY_FEED = { 19 + DefsRequestLess: 'app.bsky.feed.defs#requestLess', 20 + DefsRequestMore: 'app.bsky.feed.defs#requestMore', 21 + DefsInteractionLike: 'app.bsky.feed.defs#interactionLike', 22 + DefsInteractionSeen: 'app.bsky.feed.defs#interactionSeen', 23 + DefsClickthroughItem: 'app.bsky.feed.defs#clickthroughItem', 24 + DefsContentModeVideo: 'app.bsky.feed.defs#contentModeVideo', 25 + DefsInteractionQuote: 'app.bsky.feed.defs#interactionQuote', 26 + DefsInteractionReply: 'app.bsky.feed.defs#interactionReply', 27 + DefsInteractionShare: 'app.bsky.feed.defs#interactionShare', 28 + DefsClickthroughEmbed: 'app.bsky.feed.defs#clickthroughEmbed', 29 + DefsInteractionRepost: 'app.bsky.feed.defs#interactionRepost', 30 + DefsClickthroughAuthor: 'app.bsky.feed.defs#clickthroughAuthor', 31 + DefsClickthroughReposter: 'app.bsky.feed.defs#clickthroughReposter', 32 + DefsContentModeUnspecified: 'app.bsky.feed.defs#contentModeUnspecified', 33 + } 34 + 35 + export function createServer(options?: XrpcOptions): Server { 36 + return new Server(options) 37 + } 38 + 39 + export class Server { 40 + xrpc: XrpcServer 41 + app: AppNS 42 + social: SocialNS 43 + com: ComNS 44 + 45 + constructor(options?: XrpcOptions) { 46 + this.xrpc = createXrpcServer(schemas, options) 47 + this.app = new AppNS(this) 48 + this.social = new SocialNS(this) 49 + this.com = new ComNS(this) 50 + } 51 + } 52 + 53 + export class AppNS { 54 + _server: Server 55 + bsky: AppBskyNS 56 + 57 + constructor(server: Server) { 58 + this._server = server 59 + this.bsky = new AppBskyNS(server) 60 + } 61 + } 62 + 63 + export class AppBskyNS { 64 + _server: Server 65 + embed: AppBskyEmbedNS 66 + feed: AppBskyFeedNS 67 + richtext: AppBskyRichtextNS 68 + actor: AppBskyActorNS 69 + 70 + constructor(server: Server) { 71 + this._server = server 72 + this.embed = new AppBskyEmbedNS(server) 73 + this.feed = new AppBskyFeedNS(server) 74 + this.richtext = new AppBskyRichtextNS(server) 75 + this.actor = new AppBskyActorNS(server) 76 + } 77 + } 78 + 79 + export class AppBskyEmbedNS { 80 + _server: Server 81 + 82 + constructor(server: Server) { 83 + this._server = server 84 + } 85 + } 86 + 87 + export class AppBskyFeedNS { 88 + _server: Server 89 + 90 + constructor(server: Server) { 91 + this._server = server 92 + } 93 + } 94 + 95 + export class AppBskyRichtextNS { 96 + _server: Server 97 + 98 + constructor(server: Server) { 99 + this._server = server 100 + } 101 + } 102 + 103 + export class AppBskyActorNS { 104 + _server: Server 105 + 106 + constructor(server: Server) { 107 + this._server = server 108 + } 109 + } 110 + 111 + export class SocialNS { 112 + _server: Server 113 + grain: SocialGrainNS 114 + 115 + constructor(server: Server) { 116 + this._server = server 117 + this.grain = new SocialGrainNS(server) 118 + } 119 + } 120 + 121 + export class SocialGrainNS { 122 + _server: Server 123 + v0: SocialGrainV0NS 124 + 125 + constructor(server: Server) { 126 + this._server = server 127 + this.v0 = new SocialGrainV0NS(server) 128 + } 129 + } 130 + 131 + export class SocialGrainV0NS { 132 + _server: Server 133 + gallery: SocialGrainV0GalleryNS 134 + actor: SocialGrainV0ActorNS 135 + 136 + constructor(server: Server) { 137 + this._server = server 138 + this.gallery = new SocialGrainV0GalleryNS(server) 139 + this.actor = new SocialGrainV0ActorNS(server) 140 + } 141 + } 142 + 143 + export class SocialGrainV0GalleryNS { 144 + _server: Server 145 + 146 + constructor(server: Server) { 147 + this._server = server 148 + } 149 + } 150 + 151 + export class SocialGrainV0ActorNS { 152 + _server: Server 153 + 154 + constructor(server: Server) { 155 + this._server = server 156 + } 157 + } 158 + 159 + export class ComNS { 160 + _server: Server 161 + atproto: ComAtprotoNS 162 + 163 + constructor(server: Server) { 164 + this._server = server 165 + this.atproto = new ComAtprotoNS(server) 166 + } 167 + } 168 + 169 + export class ComAtprotoNS { 170 + _server: Server 171 + repo: ComAtprotoRepoNS 172 + 173 + constructor(server: Server) { 174 + this._server = server 175 + this.repo = new ComAtprotoRepoNS(server) 176 + } 177 + } 178 + 179 + export class ComAtprotoRepoNS { 180 + _server: Server 181 + 182 + constructor(server: Server) { 183 + this._server = server 184 + } 185 + } 186 + 187 + type SharedRateLimitOpts<T> = { 188 + name: string 189 + calcKey?: (ctx: T) => string | null 190 + calcPoints?: (ctx: T) => number 191 + } 192 + type RouteRateLimitOpts<T> = { 193 + durationMs: number 194 + points: number 195 + calcKey?: (ctx: T) => string | null 196 + calcPoints?: (ctx: T) => number 197 + } 198 + type HandlerOpts = { blobLimit?: number } 199 + type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T> 200 + type ConfigOf<Auth, Handler, ReqCtx> = 201 + | Handler 202 + | { 203 + auth?: Auth 204 + opts?: HandlerOpts 205 + rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[] 206 + handler: Handler 207 + } 208 + type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract< 209 + Awaited<ReturnType<AV>>, 210 + { credentials: unknown } 211 + >
+2768
__generated__/lexicons.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { 5 + type LexiconDoc, 6 + Lexicons, 7 + ValidationError, 8 + type ValidationResult, 9 + } from "npm:@atproto/lexicon" 10 + import { type $Typed, is$typed, maybe$typed } from './util.ts' 11 + 12 + export const schemaDict = { 13 + AppBskyEmbedDefs: { 14 + lexicon: 1, 15 + id: 'app.bsky.embed.defs', 16 + defs: { 17 + aspectRatio: { 18 + type: 'object', 19 + required: ['width', 'height'], 20 + properties: { 21 + width: { 22 + type: 'integer', 23 + minimum: 1, 24 + }, 25 + height: { 26 + type: 'integer', 27 + minimum: 1, 28 + }, 29 + }, 30 + description: 31 + 'width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.', 32 + }, 33 + }, 34 + }, 35 + AppBskyEmbedRecord: { 36 + lexicon: 1, 37 + id: 'app.bsky.embed.record', 38 + description: 39 + 'A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record.', 40 + defs: { 41 + main: { 42 + type: 'object', 43 + required: ['record'], 44 + properties: { 45 + record: { 46 + ref: 'lex:com.atproto.repo.strongRef', 47 + type: 'ref', 48 + }, 49 + }, 50 + }, 51 + view: { 52 + type: 'object', 53 + required: ['record'], 54 + properties: { 55 + record: { 56 + refs: [ 57 + 'lex:app.bsky.embed.record#viewRecord', 58 + 'lex:app.bsky.embed.record#viewNotFound', 59 + 'lex:app.bsky.embed.record#viewBlocked', 60 + 'lex:app.bsky.embed.record#viewDetached', 61 + 'lex:app.bsky.feed.defs#generatorView', 62 + 'lex:app.bsky.graph.defs#listView', 63 + 'lex:app.bsky.labeler.defs#labelerView', 64 + 'lex:app.bsky.graph.defs#starterPackViewBasic', 65 + ], 66 + type: 'union', 67 + }, 68 + }, 69 + }, 70 + viewRecord: { 71 + type: 'object', 72 + required: ['uri', 'cid', 'author', 'value', 'indexedAt'], 73 + properties: { 74 + cid: { 75 + type: 'string', 76 + format: 'cid', 77 + }, 78 + uri: { 79 + type: 'string', 80 + format: 'at-uri', 81 + }, 82 + value: { 83 + type: 'unknown', 84 + description: 'The record data itself.', 85 + }, 86 + author: { 87 + ref: 'lex:app.bsky.actor.defs#profileViewBasic', 88 + type: 'ref', 89 + }, 90 + embeds: { 91 + type: 'array', 92 + items: { 93 + refs: [ 94 + 'lex:app.bsky.embed.images#view', 95 + 'lex:app.bsky.embed.video#view', 96 + 'lex:app.bsky.embed.external#view', 97 + 'lex:app.bsky.embed.record#view', 98 + 'lex:app.bsky.embed.recordWithMedia#view', 99 + ], 100 + type: 'union', 101 + }, 102 + }, 103 + labels: { 104 + type: 'array', 105 + items: { 106 + ref: 'lex:com.atproto.label.defs#label', 107 + type: 'ref', 108 + }, 109 + }, 110 + indexedAt: { 111 + type: 'string', 112 + format: 'datetime', 113 + }, 114 + likeCount: { 115 + type: 'integer', 116 + }, 117 + quoteCount: { 118 + type: 'integer', 119 + }, 120 + replyCount: { 121 + type: 'integer', 122 + }, 123 + repostCount: { 124 + type: 'integer', 125 + }, 126 + }, 127 + }, 128 + viewBlocked: { 129 + type: 'object', 130 + required: ['uri', 'blocked', 'author'], 131 + properties: { 132 + uri: { 133 + type: 'string', 134 + format: 'at-uri', 135 + }, 136 + author: { 137 + ref: 'lex:app.bsky.feed.defs#blockedAuthor', 138 + type: 'ref', 139 + }, 140 + blocked: { 141 + type: 'boolean', 142 + const: true, 143 + }, 144 + }, 145 + }, 146 + viewDetached: { 147 + type: 'object', 148 + required: ['uri', 'detached'], 149 + properties: { 150 + uri: { 151 + type: 'string', 152 + format: 'at-uri', 153 + }, 154 + detached: { 155 + type: 'boolean', 156 + const: true, 157 + }, 158 + }, 159 + }, 160 + viewNotFound: { 161 + type: 'object', 162 + required: ['uri', 'notFound'], 163 + properties: { 164 + uri: { 165 + type: 'string', 166 + format: 'at-uri', 167 + }, 168 + notFound: { 169 + type: 'boolean', 170 + const: true, 171 + }, 172 + }, 173 + }, 174 + }, 175 + }, 176 + AppBskyEmbedImages: { 177 + lexicon: 1, 178 + id: 'app.bsky.embed.images', 179 + description: 'A set of images embedded in a Bluesky record (eg, a post).', 180 + defs: { 181 + main: { 182 + type: 'object', 183 + required: ['images'], 184 + properties: { 185 + images: { 186 + type: 'array', 187 + items: { 188 + ref: 'lex:app.bsky.embed.images#image', 189 + type: 'ref', 190 + }, 191 + maxLength: 4, 192 + }, 193 + }, 194 + }, 195 + view: { 196 + type: 'object', 197 + required: ['images'], 198 + properties: { 199 + images: { 200 + type: 'array', 201 + items: { 202 + ref: 'lex:app.bsky.embed.images#viewImage', 203 + type: 'ref', 204 + }, 205 + maxLength: 4, 206 + }, 207 + }, 208 + }, 209 + image: { 210 + type: 'object', 211 + required: ['image', 'alt'], 212 + properties: { 213 + alt: { 214 + type: 'string', 215 + description: 216 + 'Alt text description of the image, for accessibility.', 217 + }, 218 + image: { 219 + type: 'blob', 220 + accept: ['image/*'], 221 + maxSize: 1000000, 222 + }, 223 + aspectRatio: { 224 + ref: 'lex:app.bsky.embed.defs#aspectRatio', 225 + type: 'ref', 226 + }, 227 + }, 228 + }, 229 + viewImage: { 230 + type: 'object', 231 + required: ['thumb', 'fullsize', 'alt'], 232 + properties: { 233 + alt: { 234 + type: 'string', 235 + description: 236 + 'Alt text description of the image, for accessibility.', 237 + }, 238 + thumb: { 239 + type: 'string', 240 + format: 'uri', 241 + description: 242 + 'Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View.', 243 + }, 244 + fullsize: { 245 + type: 'string', 246 + format: 'uri', 247 + description: 248 + 'Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View.', 249 + }, 250 + aspectRatio: { 251 + ref: 'lex:app.bsky.embed.defs#aspectRatio', 252 + type: 'ref', 253 + }, 254 + }, 255 + }, 256 + }, 257 + }, 258 + AppBskyEmbedRecordWithMedia: { 259 + lexicon: 1, 260 + id: 'app.bsky.embed.recordWithMedia', 261 + description: 262 + 'A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card.', 263 + defs: { 264 + main: { 265 + type: 'object', 266 + required: ['record', 'media'], 267 + properties: { 268 + media: { 269 + refs: [ 270 + 'lex:app.bsky.embed.images', 271 + 'lex:app.bsky.embed.video', 272 + 'lex:app.bsky.embed.external', 273 + ], 274 + type: 'union', 275 + }, 276 + record: { 277 + ref: 'lex:app.bsky.embed.record', 278 + type: 'ref', 279 + }, 280 + }, 281 + }, 282 + view: { 283 + type: 'object', 284 + required: ['record', 'media'], 285 + properties: { 286 + media: { 287 + refs: [ 288 + 'lex:app.bsky.embed.images#view', 289 + 'lex:app.bsky.embed.video#view', 290 + 'lex:app.bsky.embed.external#view', 291 + ], 292 + type: 'union', 293 + }, 294 + record: { 295 + ref: 'lex:app.bsky.embed.record#view', 296 + type: 'ref', 297 + }, 298 + }, 299 + }, 300 + }, 301 + }, 302 + AppBskyEmbedVideo: { 303 + lexicon: 1, 304 + id: 'app.bsky.embed.video', 305 + description: 'A video embedded in a Bluesky record (eg, a post).', 306 + defs: { 307 + main: { 308 + type: 'object', 309 + required: ['video'], 310 + properties: { 311 + alt: { 312 + type: 'string', 313 + maxLength: 10000, 314 + description: 315 + 'Alt text description of the video, for accessibility.', 316 + maxGraphemes: 1000, 317 + }, 318 + video: { 319 + type: 'blob', 320 + accept: ['video/mp4'], 321 + maxSize: 50000000, 322 + }, 323 + captions: { 324 + type: 'array', 325 + items: { 326 + ref: 'lex:app.bsky.embed.video#caption', 327 + type: 'ref', 328 + }, 329 + maxLength: 20, 330 + }, 331 + aspectRatio: { 332 + ref: 'lex:app.bsky.embed.defs#aspectRatio', 333 + type: 'ref', 334 + }, 335 + }, 336 + }, 337 + view: { 338 + type: 'object', 339 + required: ['cid', 'playlist'], 340 + properties: { 341 + alt: { 342 + type: 'string', 343 + maxLength: 10000, 344 + maxGraphemes: 1000, 345 + }, 346 + cid: { 347 + type: 'string', 348 + format: 'cid', 349 + }, 350 + playlist: { 351 + type: 'string', 352 + format: 'uri', 353 + }, 354 + thumbnail: { 355 + type: 'string', 356 + format: 'uri', 357 + }, 358 + aspectRatio: { 359 + ref: 'lex:app.bsky.embed.defs#aspectRatio', 360 + type: 'ref', 361 + }, 362 + }, 363 + }, 364 + caption: { 365 + type: 'object', 366 + required: ['lang', 'file'], 367 + properties: { 368 + file: { 369 + type: 'blob', 370 + accept: ['text/vtt'], 371 + maxSize: 20000, 372 + }, 373 + lang: { 374 + type: 'string', 375 + format: 'language', 376 + }, 377 + }, 378 + }, 379 + }, 380 + }, 381 + AppBskyEmbedExternal: { 382 + lexicon: 1, 383 + id: 'app.bsky.embed.external', 384 + defs: { 385 + main: { 386 + type: 'object', 387 + required: ['external'], 388 + properties: { 389 + external: { 390 + ref: 'lex:app.bsky.embed.external#external', 391 + type: 'ref', 392 + }, 393 + }, 394 + description: 395 + "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post).", 396 + }, 397 + view: { 398 + type: 'object', 399 + required: ['external'], 400 + properties: { 401 + external: { 402 + ref: 'lex:app.bsky.embed.external#viewExternal', 403 + type: 'ref', 404 + }, 405 + }, 406 + }, 407 + external: { 408 + type: 'object', 409 + required: ['uri', 'title', 'description'], 410 + properties: { 411 + uri: { 412 + type: 'string', 413 + format: 'uri', 414 + }, 415 + thumb: { 416 + type: 'blob', 417 + accept: ['image/*'], 418 + maxSize: 1000000, 419 + }, 420 + title: { 421 + type: 'string', 422 + }, 423 + description: { 424 + type: 'string', 425 + }, 426 + }, 427 + }, 428 + viewExternal: { 429 + type: 'object', 430 + required: ['uri', 'title', 'description'], 431 + properties: { 432 + uri: { 433 + type: 'string', 434 + format: 'uri', 435 + }, 436 + thumb: { 437 + type: 'string', 438 + format: 'uri', 439 + }, 440 + title: { 441 + type: 'string', 442 + }, 443 + description: { 444 + type: 'string', 445 + }, 446 + }, 447 + }, 448 + }, 449 + }, 450 + AppBskyGraphDefs: { 451 + lexicon: 1, 452 + id: 'app.bsky.graph.defs', 453 + defs: { 454 + modlist: { 455 + type: 'token', 456 + description: 457 + 'A list of actors to apply an aggregate moderation action (mute/block) on.', 458 + }, 459 + listView: { 460 + type: 'object', 461 + required: ['uri', 'cid', 'creator', 'name', 'purpose', 'indexedAt'], 462 + properties: { 463 + cid: { 464 + type: 'string', 465 + format: 'cid', 466 + }, 467 + uri: { 468 + type: 'string', 469 + format: 'at-uri', 470 + }, 471 + name: { 472 + type: 'string', 473 + maxLength: 64, 474 + minLength: 1, 475 + }, 476 + avatar: { 477 + type: 'string', 478 + format: 'uri', 479 + }, 480 + labels: { 481 + type: 'array', 482 + items: { 483 + ref: 'lex:com.atproto.label.defs#label', 484 + type: 'ref', 485 + }, 486 + }, 487 + viewer: { 488 + ref: 'lex:app.bsky.graph.defs#listViewerState', 489 + type: 'ref', 490 + }, 491 + creator: { 492 + ref: 'lex:app.bsky.actor.defs#profileView', 493 + type: 'ref', 494 + }, 495 + purpose: { 496 + ref: 'lex:app.bsky.graph.defs#listPurpose', 497 + type: 'ref', 498 + }, 499 + indexedAt: { 500 + type: 'string', 501 + format: 'datetime', 502 + }, 503 + description: { 504 + type: 'string', 505 + maxLength: 3000, 506 + maxGraphemes: 300, 507 + }, 508 + listItemCount: { 509 + type: 'integer', 510 + minimum: 0, 511 + }, 512 + descriptionFacets: { 513 + type: 'array', 514 + items: { 515 + ref: 'lex:app.bsky.richtext.facet', 516 + type: 'ref', 517 + }, 518 + }, 519 + }, 520 + }, 521 + curatelist: { 522 + type: 'token', 523 + description: 524 + 'A list of actors used for curation purposes such as list feeds or interaction gating.', 525 + }, 526 + listPurpose: { 527 + type: 'string', 528 + knownValues: [ 529 + 'app.bsky.graph.defs#modlist', 530 + 'app.bsky.graph.defs#curatelist', 531 + 'app.bsky.graph.defs#referencelist', 532 + ], 533 + }, 534 + listItemView: { 535 + type: 'object', 536 + required: ['uri', 'subject'], 537 + properties: { 538 + uri: { 539 + type: 'string', 540 + format: 'at-uri', 541 + }, 542 + subject: { 543 + ref: 'lex:app.bsky.actor.defs#profileView', 544 + type: 'ref', 545 + }, 546 + }, 547 + }, 548 + relationship: { 549 + type: 'object', 550 + required: ['did'], 551 + properties: { 552 + did: { 553 + type: 'string', 554 + format: 'did', 555 + }, 556 + following: { 557 + type: 'string', 558 + format: 'at-uri', 559 + description: 560 + 'if the actor follows this DID, this is the AT-URI of the follow record', 561 + }, 562 + followedBy: { 563 + type: 'string', 564 + format: 'at-uri', 565 + description: 566 + 'if the actor is followed by this DID, contains the AT-URI of the follow record', 567 + }, 568 + }, 569 + description: 570 + 'lists the bi-directional graph relationships between one actor (not indicated in the object), and the target actors (the DID included in the object)', 571 + }, 572 + listViewBasic: { 573 + type: 'object', 574 + required: ['uri', 'cid', 'name', 'purpose'], 575 + properties: { 576 + cid: { 577 + type: 'string', 578 + format: 'cid', 579 + }, 580 + uri: { 581 + type: 'string', 582 + format: 'at-uri', 583 + }, 584 + name: { 585 + type: 'string', 586 + maxLength: 64, 587 + minLength: 1, 588 + }, 589 + avatar: { 590 + type: 'string', 591 + format: 'uri', 592 + }, 593 + labels: { 594 + type: 'array', 595 + items: { 596 + ref: 'lex:com.atproto.label.defs#label', 597 + type: 'ref', 598 + }, 599 + }, 600 + viewer: { 601 + ref: 'lex:app.bsky.graph.defs#listViewerState', 602 + type: 'ref', 603 + }, 604 + purpose: { 605 + ref: 'lex:app.bsky.graph.defs#listPurpose', 606 + type: 'ref', 607 + }, 608 + indexedAt: { 609 + type: 'string', 610 + format: 'datetime', 611 + }, 612 + listItemCount: { 613 + type: 'integer', 614 + minimum: 0, 615 + }, 616 + }, 617 + }, 618 + notFoundActor: { 619 + type: 'object', 620 + required: ['actor', 'notFound'], 621 + properties: { 622 + actor: { 623 + type: 'string', 624 + format: 'at-identifier', 625 + }, 626 + notFound: { 627 + type: 'boolean', 628 + const: true, 629 + }, 630 + }, 631 + description: 'indicates that a handle or DID could not be resolved', 632 + }, 633 + referencelist: { 634 + type: 'token', 635 + description: 636 + 'A list of actors used for only for reference purposes such as within a starter pack.', 637 + }, 638 + listViewerState: { 639 + type: 'object', 640 + properties: { 641 + muted: { 642 + type: 'boolean', 643 + }, 644 + blocked: { 645 + type: 'string', 646 + format: 'at-uri', 647 + }, 648 + }, 649 + }, 650 + starterPackView: { 651 + type: 'object', 652 + required: ['uri', 'cid', 'record', 'creator', 'indexedAt'], 653 + properties: { 654 + cid: { 655 + type: 'string', 656 + format: 'cid', 657 + }, 658 + uri: { 659 + type: 'string', 660 + format: 'at-uri', 661 + }, 662 + list: { 663 + ref: 'lex:app.bsky.graph.defs#listViewBasic', 664 + type: 'ref', 665 + }, 666 + feeds: { 667 + type: 'array', 668 + items: { 669 + ref: 'lex:app.bsky.feed.defs#generatorView', 670 + type: 'ref', 671 + }, 672 + maxLength: 3, 673 + }, 674 + labels: { 675 + type: 'array', 676 + items: { 677 + ref: 'lex:com.atproto.label.defs#label', 678 + type: 'ref', 679 + }, 680 + }, 681 + record: { 682 + type: 'unknown', 683 + }, 684 + creator: { 685 + ref: 'lex:app.bsky.actor.defs#profileViewBasic', 686 + type: 'ref', 687 + }, 688 + indexedAt: { 689 + type: 'string', 690 + format: 'datetime', 691 + }, 692 + joinedWeekCount: { 693 + type: 'integer', 694 + minimum: 0, 695 + }, 696 + listItemsSample: { 697 + type: 'array', 698 + items: { 699 + ref: 'lex:app.bsky.graph.defs#listItemView', 700 + type: 'ref', 701 + }, 702 + maxLength: 12, 703 + }, 704 + joinedAllTimeCount: { 705 + type: 'integer', 706 + minimum: 0, 707 + }, 708 + }, 709 + }, 710 + starterPackViewBasic: { 711 + type: 'object', 712 + required: ['uri', 'cid', 'record', 'creator', 'indexedAt'], 713 + properties: { 714 + cid: { 715 + type: 'string', 716 + format: 'cid', 717 + }, 718 + uri: { 719 + type: 'string', 720 + format: 'at-uri', 721 + }, 722 + labels: { 723 + type: 'array', 724 + items: { 725 + ref: 'lex:com.atproto.label.defs#label', 726 + type: 'ref', 727 + }, 728 + }, 729 + record: { 730 + type: 'unknown', 731 + }, 732 + creator: { 733 + ref: 'lex:app.bsky.actor.defs#profileViewBasic', 734 + type: 'ref', 735 + }, 736 + indexedAt: { 737 + type: 'string', 738 + format: 'datetime', 739 + }, 740 + listItemCount: { 741 + type: 'integer', 742 + minimum: 0, 743 + }, 744 + joinedWeekCount: { 745 + type: 'integer', 746 + minimum: 0, 747 + }, 748 + joinedAllTimeCount: { 749 + type: 'integer', 750 + minimum: 0, 751 + }, 752 + }, 753 + }, 754 + }, 755 + }, 756 + AppBskyFeedDefs: { 757 + lexicon: 1, 758 + id: 'app.bsky.feed.defs', 759 + defs: { 760 + postView: { 761 + type: 'object', 762 + required: ['uri', 'cid', 'author', 'record', 'indexedAt'], 763 + properties: { 764 + cid: { 765 + type: 'string', 766 + format: 'cid', 767 + }, 768 + uri: { 769 + type: 'string', 770 + format: 'at-uri', 771 + }, 772 + embed: { 773 + refs: [ 774 + 'lex:app.bsky.embed.images#view', 775 + 'lex:app.bsky.embed.video#view', 776 + 'lex:app.bsky.embed.external#view', 777 + 'lex:app.bsky.embed.record#view', 778 + 'lex:app.bsky.embed.recordWithMedia#view', 779 + ], 780 + type: 'union', 781 + }, 782 + author: { 783 + ref: 'lex:app.bsky.actor.defs#profileViewBasic', 784 + type: 'ref', 785 + }, 786 + labels: { 787 + type: 'array', 788 + items: { 789 + ref: 'lex:com.atproto.label.defs#label', 790 + type: 'ref', 791 + }, 792 + }, 793 + record: { 794 + type: 'unknown', 795 + }, 796 + viewer: { 797 + ref: 'lex:app.bsky.feed.defs#viewerState', 798 + type: 'ref', 799 + }, 800 + indexedAt: { 801 + type: 'string', 802 + format: 'datetime', 803 + }, 804 + likeCount: { 805 + type: 'integer', 806 + }, 807 + quoteCount: { 808 + type: 'integer', 809 + }, 810 + replyCount: { 811 + type: 'integer', 812 + }, 813 + threadgate: { 814 + ref: 'lex:app.bsky.feed.defs#threadgateView', 815 + type: 'ref', 816 + }, 817 + repostCount: { 818 + type: 'integer', 819 + }, 820 + }, 821 + }, 822 + replyRef: { 823 + type: 'object', 824 + required: ['root', 'parent'], 825 + properties: { 826 + root: { 827 + refs: [ 828 + 'lex:app.bsky.feed.defs#postView', 829 + 'lex:app.bsky.feed.defs#notFoundPost', 830 + 'lex:app.bsky.feed.defs#blockedPost', 831 + ], 832 + type: 'union', 833 + }, 834 + parent: { 835 + refs: [ 836 + 'lex:app.bsky.feed.defs#postView', 837 + 'lex:app.bsky.feed.defs#notFoundPost', 838 + 'lex:app.bsky.feed.defs#blockedPost', 839 + ], 840 + type: 'union', 841 + }, 842 + grandparentAuthor: { 843 + ref: 'lex:app.bsky.actor.defs#profileViewBasic', 844 + type: 'ref', 845 + description: 846 + 'When parent is a reply to another post, this is the author of that post.', 847 + }, 848 + }, 849 + }, 850 + reasonPin: { 851 + type: 'object', 852 + properties: {}, 853 + }, 854 + blockedPost: { 855 + type: 'object', 856 + required: ['uri', 'blocked', 'author'], 857 + properties: { 858 + uri: { 859 + type: 'string', 860 + format: 'at-uri', 861 + }, 862 + author: { 863 + ref: 'lex:app.bsky.feed.defs#blockedAuthor', 864 + type: 'ref', 865 + }, 866 + blocked: { 867 + type: 'boolean', 868 + const: true, 869 + }, 870 + }, 871 + }, 872 + interaction: { 873 + type: 'object', 874 + properties: { 875 + item: { 876 + type: 'string', 877 + format: 'at-uri', 878 + }, 879 + event: { 880 + type: 'string', 881 + knownValues: [ 882 + 'app.bsky.feed.defs#requestLess', 883 + 'app.bsky.feed.defs#requestMore', 884 + 'app.bsky.feed.defs#clickthroughItem', 885 + 'app.bsky.feed.defs#clickthroughAuthor', 886 + 'app.bsky.feed.defs#clickthroughReposter', 887 + 'app.bsky.feed.defs#clickthroughEmbed', 888 + 'app.bsky.feed.defs#interactionSeen', 889 + 'app.bsky.feed.defs#interactionLike', 890 + 'app.bsky.feed.defs#interactionRepost', 891 + 'app.bsky.feed.defs#interactionReply', 892 + 'app.bsky.feed.defs#interactionQuote', 893 + 'app.bsky.feed.defs#interactionShare', 894 + ], 895 + }, 896 + feedContext: { 897 + type: 'string', 898 + maxLength: 2000, 899 + description: 900 + 'Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton.', 901 + }, 902 + }, 903 + }, 904 + requestLess: { 905 + type: 'token', 906 + description: 907 + 'Request that less content like the given feed item be shown in the feed', 908 + }, 909 + requestMore: { 910 + type: 'token', 911 + description: 912 + 'Request that more content like the given feed item be shown in the feed', 913 + }, 914 + viewerState: { 915 + type: 'object', 916 + properties: { 917 + like: { 918 + type: 'string', 919 + format: 'at-uri', 920 + }, 921 + pinned: { 922 + type: 'boolean', 923 + }, 924 + repost: { 925 + type: 'string', 926 + format: 'at-uri', 927 + }, 928 + threadMuted: { 929 + type: 'boolean', 930 + }, 931 + replyDisabled: { 932 + type: 'boolean', 933 + }, 934 + embeddingDisabled: { 935 + type: 'boolean', 936 + }, 937 + }, 938 + description: 939 + "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", 940 + }, 941 + feedViewPost: { 942 + type: 'object', 943 + required: ['post'], 944 + properties: { 945 + post: { 946 + ref: 'lex:app.bsky.feed.defs#postView', 947 + type: 'ref', 948 + }, 949 + reply: { 950 + ref: 'lex:app.bsky.feed.defs#replyRef', 951 + type: 'ref', 952 + }, 953 + reason: { 954 + refs: [ 955 + 'lex:app.bsky.feed.defs#reasonRepost', 956 + 'lex:app.bsky.feed.defs#reasonPin', 957 + ], 958 + type: 'union', 959 + }, 960 + feedContext: { 961 + type: 'string', 962 + maxLength: 2000, 963 + description: 964 + 'Context provided by feed generator that may be passed back alongside interactions.', 965 + }, 966 + }, 967 + }, 968 + notFoundPost: { 969 + type: 'object', 970 + required: ['uri', 'notFound'], 971 + properties: { 972 + uri: { 973 + type: 'string', 974 + format: 'at-uri', 975 + }, 976 + notFound: { 977 + type: 'boolean', 978 + const: true, 979 + }, 980 + }, 981 + }, 982 + reasonRepost: { 983 + type: 'object', 984 + required: ['by', 'indexedAt'], 985 + properties: { 986 + by: { 987 + ref: 'lex:app.bsky.actor.defs#profileViewBasic', 988 + type: 'ref', 989 + }, 990 + indexedAt: { 991 + type: 'string', 992 + format: 'datetime', 993 + }, 994 + }, 995 + }, 996 + blockedAuthor: { 997 + type: 'object', 998 + required: ['did'], 999 + properties: { 1000 + did: { 1001 + type: 'string', 1002 + format: 'did', 1003 + }, 1004 + viewer: { 1005 + ref: 'lex:app.bsky.actor.defs#viewerState', 1006 + type: 'ref', 1007 + }, 1008 + }, 1009 + }, 1010 + generatorView: { 1011 + type: 'object', 1012 + required: ['uri', 'cid', 'did', 'creator', 'displayName', 'indexedAt'], 1013 + properties: { 1014 + cid: { 1015 + type: 'string', 1016 + format: 'cid', 1017 + }, 1018 + did: { 1019 + type: 'string', 1020 + format: 'did', 1021 + }, 1022 + uri: { 1023 + type: 'string', 1024 + format: 'at-uri', 1025 + }, 1026 + avatar: { 1027 + type: 'string', 1028 + format: 'uri', 1029 + }, 1030 + labels: { 1031 + type: 'array', 1032 + items: { 1033 + ref: 'lex:com.atproto.label.defs#label', 1034 + type: 'ref', 1035 + }, 1036 + }, 1037 + viewer: { 1038 + ref: 'lex:app.bsky.feed.defs#generatorViewerState', 1039 + type: 'ref', 1040 + }, 1041 + creator: { 1042 + ref: 'lex:app.bsky.actor.defs#profileView', 1043 + type: 'ref', 1044 + }, 1045 + indexedAt: { 1046 + type: 'string', 1047 + format: 'datetime', 1048 + }, 1049 + likeCount: { 1050 + type: 'integer', 1051 + minimum: 0, 1052 + }, 1053 + contentMode: { 1054 + type: 'string', 1055 + knownValues: [ 1056 + 'app.bsky.feed.defs#contentModeUnspecified', 1057 + 'app.bsky.feed.defs#contentModeVideo', 1058 + ], 1059 + }, 1060 + description: { 1061 + type: 'string', 1062 + maxLength: 3000, 1063 + maxGraphemes: 300, 1064 + }, 1065 + displayName: { 1066 + type: 'string', 1067 + }, 1068 + descriptionFacets: { 1069 + type: 'array', 1070 + items: { 1071 + ref: 'lex:app.bsky.richtext.facet', 1072 + type: 'ref', 1073 + }, 1074 + }, 1075 + acceptsInteractions: { 1076 + type: 'boolean', 1077 + }, 1078 + }, 1079 + }, 1080 + threadContext: { 1081 + type: 'object', 1082 + properties: { 1083 + rootAuthorLike: { 1084 + type: 'string', 1085 + format: 'at-uri', 1086 + }, 1087 + }, 1088 + description: 1089 + 'Metadata about this post within the context of the thread it is in.', 1090 + }, 1091 + threadViewPost: { 1092 + type: 'object', 1093 + required: ['post'], 1094 + properties: { 1095 + post: { 1096 + ref: 'lex:app.bsky.feed.defs#postView', 1097 + type: 'ref', 1098 + }, 1099 + parent: { 1100 + refs: [ 1101 + 'lex:app.bsky.feed.defs#threadViewPost', 1102 + 'lex:app.bsky.feed.defs#notFoundPost', 1103 + 'lex:app.bsky.feed.defs#blockedPost', 1104 + ], 1105 + type: 'union', 1106 + }, 1107 + replies: { 1108 + type: 'array', 1109 + items: { 1110 + refs: [ 1111 + 'lex:app.bsky.feed.defs#threadViewPost', 1112 + 'lex:app.bsky.feed.defs#notFoundPost', 1113 + 'lex:app.bsky.feed.defs#blockedPost', 1114 + ], 1115 + type: 'union', 1116 + }, 1117 + }, 1118 + threadContext: { 1119 + ref: 'lex:app.bsky.feed.defs#threadContext', 1120 + type: 'ref', 1121 + }, 1122 + }, 1123 + }, 1124 + threadgateView: { 1125 + type: 'object', 1126 + properties: { 1127 + cid: { 1128 + type: 'string', 1129 + format: 'cid', 1130 + }, 1131 + uri: { 1132 + type: 'string', 1133 + format: 'at-uri', 1134 + }, 1135 + lists: { 1136 + type: 'array', 1137 + items: { 1138 + ref: 'lex:app.bsky.graph.defs#listViewBasic', 1139 + type: 'ref', 1140 + }, 1141 + }, 1142 + record: { 1143 + type: 'unknown', 1144 + }, 1145 + }, 1146 + }, 1147 + interactionLike: { 1148 + type: 'token', 1149 + description: 'User liked the feed item', 1150 + }, 1151 + interactionSeen: { 1152 + type: 'token', 1153 + description: 'Feed item was seen by user', 1154 + }, 1155 + clickthroughItem: { 1156 + type: 'token', 1157 + description: 'User clicked through to the feed item', 1158 + }, 1159 + contentModeVideo: { 1160 + type: 'token', 1161 + description: 1162 + 'Declares the feed generator returns posts containing app.bsky.embed.video embeds.', 1163 + }, 1164 + interactionQuote: { 1165 + type: 'token', 1166 + description: 'User quoted the feed item', 1167 + }, 1168 + interactionReply: { 1169 + type: 'token', 1170 + description: 'User replied to the feed item', 1171 + }, 1172 + interactionShare: { 1173 + type: 'token', 1174 + description: 'User shared the feed item', 1175 + }, 1176 + skeletonFeedPost: { 1177 + type: 'object', 1178 + required: ['post'], 1179 + properties: { 1180 + post: { 1181 + type: 'string', 1182 + format: 'at-uri', 1183 + }, 1184 + reason: { 1185 + refs: [ 1186 + 'lex:app.bsky.feed.defs#skeletonReasonRepost', 1187 + 'lex:app.bsky.feed.defs#skeletonReasonPin', 1188 + ], 1189 + type: 'union', 1190 + }, 1191 + feedContext: { 1192 + type: 'string', 1193 + maxLength: 2000, 1194 + description: 1195 + 'Context that will be passed through to client and may be passed to feed generator back alongside interactions.', 1196 + }, 1197 + }, 1198 + }, 1199 + clickthroughEmbed: { 1200 + type: 'token', 1201 + description: 1202 + 'User clicked through to the embedded content of the feed item', 1203 + }, 1204 + interactionRepost: { 1205 + type: 'token', 1206 + description: 'User reposted the feed item', 1207 + }, 1208 + skeletonReasonPin: { 1209 + type: 'object', 1210 + properties: {}, 1211 + }, 1212 + clickthroughAuthor: { 1213 + type: 'token', 1214 + description: 'User clicked through to the author of the feed item', 1215 + }, 1216 + clickthroughReposter: { 1217 + type: 'token', 1218 + description: 'User clicked through to the reposter of the feed item', 1219 + }, 1220 + generatorViewerState: { 1221 + type: 'object', 1222 + properties: { 1223 + like: { 1224 + type: 'string', 1225 + format: 'at-uri', 1226 + }, 1227 + }, 1228 + }, 1229 + skeletonReasonRepost: { 1230 + type: 'object', 1231 + required: ['repost'], 1232 + properties: { 1233 + repost: { 1234 + type: 'string', 1235 + format: 'at-uri', 1236 + }, 1237 + }, 1238 + }, 1239 + contentModeUnspecified: { 1240 + type: 'token', 1241 + description: 'Declares the feed generator returns any types of posts.', 1242 + }, 1243 + }, 1244 + }, 1245 + AppBskyFeedPostgate: { 1246 + lexicon: 1, 1247 + id: 'app.bsky.feed.postgate', 1248 + defs: { 1249 + main: { 1250 + key: 'tid', 1251 + type: 'record', 1252 + record: { 1253 + type: 'object', 1254 + required: ['post', 'createdAt'], 1255 + properties: { 1256 + post: { 1257 + type: 'string', 1258 + format: 'at-uri', 1259 + description: 'Reference (AT-URI) to the post record.', 1260 + }, 1261 + createdAt: { 1262 + type: 'string', 1263 + format: 'datetime', 1264 + }, 1265 + embeddingRules: { 1266 + type: 'array', 1267 + items: { 1268 + refs: ['lex:app.bsky.feed.postgate#disableRule'], 1269 + type: 'union', 1270 + }, 1271 + maxLength: 5, 1272 + description: 1273 + 'List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed.', 1274 + }, 1275 + detachedEmbeddingUris: { 1276 + type: 'array', 1277 + items: { 1278 + type: 'string', 1279 + format: 'at-uri', 1280 + }, 1281 + maxLength: 50, 1282 + description: 1283 + 'List of AT-URIs embedding this post that the author has detached from.', 1284 + }, 1285 + }, 1286 + }, 1287 + description: 1288 + 'Record defining interaction rules for a post. The record key (rkey) of the postgate record must match the record key of the post, and that record must be in the same repository.', 1289 + }, 1290 + disableRule: { 1291 + type: 'object', 1292 + properties: {}, 1293 + description: 'Disables embedding of this post.', 1294 + }, 1295 + }, 1296 + }, 1297 + AppBskyFeedThreadgate: { 1298 + lexicon: 1, 1299 + id: 'app.bsky.feed.threadgate', 1300 + defs: { 1301 + main: { 1302 + key: 'tid', 1303 + type: 'record', 1304 + record: { 1305 + type: 'object', 1306 + required: ['post', 'createdAt'], 1307 + properties: { 1308 + post: { 1309 + type: 'string', 1310 + format: 'at-uri', 1311 + description: 'Reference (AT-URI) to the post record.', 1312 + }, 1313 + allow: { 1314 + type: 'array', 1315 + items: { 1316 + refs: [ 1317 + 'lex:app.bsky.feed.threadgate#mentionRule', 1318 + 'lex:app.bsky.feed.threadgate#followerRule', 1319 + 'lex:app.bsky.feed.threadgate#followingRule', 1320 + 'lex:app.bsky.feed.threadgate#listRule', 1321 + ], 1322 + type: 'union', 1323 + }, 1324 + maxLength: 5, 1325 + description: 1326 + 'List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply.', 1327 + }, 1328 + createdAt: { 1329 + type: 'string', 1330 + format: 'datetime', 1331 + }, 1332 + hiddenReplies: { 1333 + type: 'array', 1334 + items: { 1335 + type: 'string', 1336 + format: 'at-uri', 1337 + }, 1338 + maxLength: 50, 1339 + description: 'List of hidden reply URIs.', 1340 + }, 1341 + }, 1342 + }, 1343 + description: 1344 + "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository.", 1345 + }, 1346 + listRule: { 1347 + type: 'object', 1348 + required: ['list'], 1349 + properties: { 1350 + list: { 1351 + type: 'string', 1352 + format: 'at-uri', 1353 + }, 1354 + }, 1355 + description: 'Allow replies from actors on a list.', 1356 + }, 1357 + mentionRule: { 1358 + type: 'object', 1359 + properties: {}, 1360 + description: 'Allow replies from actors mentioned in your post.', 1361 + }, 1362 + followerRule: { 1363 + type: 'object', 1364 + properties: {}, 1365 + description: 'Allow replies from actors who follow you.', 1366 + }, 1367 + followingRule: { 1368 + type: 'object', 1369 + properties: {}, 1370 + description: 'Allow replies from actors you follow.', 1371 + }, 1372 + }, 1373 + }, 1374 + AppBskyRichtextFacet: { 1375 + lexicon: 1, 1376 + id: 'app.bsky.richtext.facet', 1377 + defs: { 1378 + tag: { 1379 + type: 'object', 1380 + required: ['tag'], 1381 + properties: { 1382 + tag: { 1383 + type: 'string', 1384 + maxLength: 640, 1385 + maxGraphemes: 64, 1386 + }, 1387 + }, 1388 + description: 1389 + "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').", 1390 + }, 1391 + link: { 1392 + type: 'object', 1393 + required: ['uri'], 1394 + properties: { 1395 + uri: { 1396 + type: 'string', 1397 + format: 'uri', 1398 + }, 1399 + }, 1400 + description: 1401 + 'Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.', 1402 + }, 1403 + main: { 1404 + type: 'object', 1405 + required: ['index', 'features'], 1406 + properties: { 1407 + index: { 1408 + ref: 'lex:app.bsky.richtext.facet#byteSlice', 1409 + type: 'ref', 1410 + }, 1411 + features: { 1412 + type: 'array', 1413 + items: { 1414 + refs: [ 1415 + 'lex:app.bsky.richtext.facet#mention', 1416 + 'lex:app.bsky.richtext.facet#link', 1417 + 'lex:app.bsky.richtext.facet#tag', 1418 + ], 1419 + type: 'union', 1420 + }, 1421 + }, 1422 + }, 1423 + description: 'Annotation of a sub-string within rich text.', 1424 + }, 1425 + mention: { 1426 + type: 'object', 1427 + required: ['did'], 1428 + properties: { 1429 + did: { 1430 + type: 'string', 1431 + format: 'did', 1432 + }, 1433 + }, 1434 + description: 1435 + "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.", 1436 + }, 1437 + byteSlice: { 1438 + type: 'object', 1439 + required: ['byteStart', 'byteEnd'], 1440 + properties: { 1441 + byteEnd: { 1442 + type: 'integer', 1443 + minimum: 0, 1444 + }, 1445 + byteStart: { 1446 + type: 'integer', 1447 + minimum: 0, 1448 + }, 1449 + }, 1450 + description: 1451 + '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.', 1452 + }, 1453 + }, 1454 + }, 1455 + AppBskyActorDefs: { 1456 + lexicon: 1, 1457 + id: 'app.bsky.actor.defs', 1458 + defs: { 1459 + nux: { 1460 + type: 'object', 1461 + required: ['id', 'completed'], 1462 + properties: { 1463 + id: { 1464 + type: 'string', 1465 + maxLength: 100, 1466 + }, 1467 + data: { 1468 + type: 'string', 1469 + maxLength: 3000, 1470 + description: 1471 + 'Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters.', 1472 + maxGraphemes: 300, 1473 + }, 1474 + completed: { 1475 + type: 'boolean', 1476 + default: false, 1477 + }, 1478 + expiresAt: { 1479 + type: 'string', 1480 + format: 'datetime', 1481 + description: 1482 + 'The date and time at which the NUX will expire and should be considered completed.', 1483 + }, 1484 + }, 1485 + description: 'A new user experiences (NUX) storage object', 1486 + }, 1487 + mutedWord: { 1488 + type: 'object', 1489 + required: ['value', 'targets'], 1490 + properties: { 1491 + id: { 1492 + type: 'string', 1493 + }, 1494 + value: { 1495 + type: 'string', 1496 + maxLength: 10000, 1497 + description: 'The muted word itself.', 1498 + maxGraphemes: 1000, 1499 + }, 1500 + targets: { 1501 + type: 'array', 1502 + items: { 1503 + ref: 'lex:app.bsky.actor.defs#mutedWordTarget', 1504 + type: 'ref', 1505 + }, 1506 + description: 'The intended targets of the muted word.', 1507 + }, 1508 + expiresAt: { 1509 + type: 'string', 1510 + format: 'datetime', 1511 + description: 1512 + 'The date and time at which the muted word will expire and no longer be applied.', 1513 + }, 1514 + actorTarget: { 1515 + type: 'string', 1516 + default: 'all', 1517 + description: 1518 + 'Groups of users to apply the muted word to. If undefined, applies to all users.', 1519 + knownValues: ['all', 'exclude-following'], 1520 + }, 1521 + }, 1522 + description: 'A word that the account owner has muted.', 1523 + }, 1524 + savedFeed: { 1525 + type: 'object', 1526 + required: ['id', 'type', 'value', 'pinned'], 1527 + properties: { 1528 + id: { 1529 + type: 'string', 1530 + }, 1531 + type: { 1532 + type: 'string', 1533 + knownValues: ['feed', 'list', 'timeline'], 1534 + }, 1535 + value: { 1536 + type: 'string', 1537 + }, 1538 + pinned: { 1539 + type: 'boolean', 1540 + }, 1541 + }, 1542 + }, 1543 + preferences: { 1544 + type: 'array', 1545 + items: { 1546 + refs: [ 1547 + 'lex:app.bsky.actor.defs#adultContentPref', 1548 + 'lex:app.bsky.actor.defs#contentLabelPref', 1549 + 'lex:app.bsky.actor.defs#savedFeedsPref', 1550 + 'lex:app.bsky.actor.defs#savedFeedsPrefV2', 1551 + 'lex:app.bsky.actor.defs#personalDetailsPref', 1552 + 'lex:app.bsky.actor.defs#feedViewPref', 1553 + 'lex:app.bsky.actor.defs#threadViewPref', 1554 + 'lex:app.bsky.actor.defs#interestsPref', 1555 + 'lex:app.bsky.actor.defs#mutedWordsPref', 1556 + 'lex:app.bsky.actor.defs#hiddenPostsPref', 1557 + 'lex:app.bsky.actor.defs#bskyAppStatePref', 1558 + 'lex:app.bsky.actor.defs#labelersPref', 1559 + 'lex:app.bsky.actor.defs#postInteractionSettingsPref', 1560 + ], 1561 + type: 'union', 1562 + }, 1563 + }, 1564 + profileView: { 1565 + type: 'object', 1566 + required: ['did', 'handle'], 1567 + properties: { 1568 + did: { 1569 + type: 'string', 1570 + format: 'did', 1571 + }, 1572 + avatar: { 1573 + type: 'string', 1574 + format: 'uri', 1575 + }, 1576 + handle: { 1577 + type: 'string', 1578 + format: 'handle', 1579 + }, 1580 + labels: { 1581 + type: 'array', 1582 + items: { 1583 + ref: 'lex:com.atproto.label.defs#label', 1584 + type: 'ref', 1585 + }, 1586 + }, 1587 + viewer: { 1588 + ref: 'lex:app.bsky.actor.defs#viewerState', 1589 + type: 'ref', 1590 + }, 1591 + createdAt: { 1592 + type: 'string', 1593 + format: 'datetime', 1594 + }, 1595 + indexedAt: { 1596 + type: 'string', 1597 + format: 'datetime', 1598 + }, 1599 + associated: { 1600 + ref: 'lex:app.bsky.actor.defs#profileAssociated', 1601 + type: 'ref', 1602 + }, 1603 + description: { 1604 + type: 'string', 1605 + maxLength: 2560, 1606 + maxGraphemes: 256, 1607 + }, 1608 + displayName: { 1609 + type: 'string', 1610 + maxLength: 640, 1611 + maxGraphemes: 64, 1612 + }, 1613 + }, 1614 + }, 1615 + viewerState: { 1616 + type: 'object', 1617 + properties: { 1618 + muted: { 1619 + type: 'boolean', 1620 + }, 1621 + blocking: { 1622 + type: 'string', 1623 + format: 'at-uri', 1624 + }, 1625 + blockedBy: { 1626 + type: 'boolean', 1627 + }, 1628 + following: { 1629 + type: 'string', 1630 + format: 'at-uri', 1631 + }, 1632 + followedBy: { 1633 + type: 'string', 1634 + format: 'at-uri', 1635 + }, 1636 + mutedByList: { 1637 + ref: 'lex:app.bsky.graph.defs#listViewBasic', 1638 + type: 'ref', 1639 + }, 1640 + blockingByList: { 1641 + ref: 'lex:app.bsky.graph.defs#listViewBasic', 1642 + type: 'ref', 1643 + }, 1644 + knownFollowers: { 1645 + ref: 'lex:app.bsky.actor.defs#knownFollowers', 1646 + type: 'ref', 1647 + }, 1648 + }, 1649 + description: 1650 + "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.", 1651 + }, 1652 + feedViewPref: { 1653 + type: 'object', 1654 + required: ['feed'], 1655 + properties: { 1656 + feed: { 1657 + type: 'string', 1658 + description: 1659 + 'The URI of the feed, or an identifier which describes the feed.', 1660 + }, 1661 + hideReplies: { 1662 + type: 'boolean', 1663 + description: 'Hide replies in the feed.', 1664 + }, 1665 + hideReposts: { 1666 + type: 'boolean', 1667 + description: 'Hide reposts in the feed.', 1668 + }, 1669 + hideQuotePosts: { 1670 + type: 'boolean', 1671 + description: 'Hide quote posts in the feed.', 1672 + }, 1673 + hideRepliesByLikeCount: { 1674 + type: 'integer', 1675 + description: 1676 + 'Hide replies in the feed if they do not have this number of likes.', 1677 + }, 1678 + hideRepliesByUnfollowed: { 1679 + type: 'boolean', 1680 + default: true, 1681 + description: 1682 + 'Hide replies in the feed if they are not by followed users.', 1683 + }, 1684 + }, 1685 + }, 1686 + labelersPref: { 1687 + type: 'object', 1688 + required: ['labelers'], 1689 + properties: { 1690 + labelers: { 1691 + type: 'array', 1692 + items: { 1693 + ref: 'lex:app.bsky.actor.defs#labelerPrefItem', 1694 + type: 'ref', 1695 + }, 1696 + }, 1697 + }, 1698 + }, 1699 + interestsPref: { 1700 + type: 'object', 1701 + required: ['tags'], 1702 + properties: { 1703 + tags: { 1704 + type: 'array', 1705 + items: { 1706 + type: 'string', 1707 + maxLength: 640, 1708 + maxGraphemes: 64, 1709 + }, 1710 + maxLength: 100, 1711 + description: 1712 + "A list of tags which describe the account owner's interests gathered during onboarding.", 1713 + }, 1714 + }, 1715 + }, 1716 + knownFollowers: { 1717 + type: 'object', 1718 + required: ['count', 'followers'], 1719 + properties: { 1720 + count: { 1721 + type: 'integer', 1722 + }, 1723 + followers: { 1724 + type: 'array', 1725 + items: { 1726 + ref: 'lex:app.bsky.actor.defs#profileViewBasic', 1727 + type: 'ref', 1728 + }, 1729 + maxLength: 5, 1730 + minLength: 0, 1731 + }, 1732 + }, 1733 + description: "The subject's followers whom you also follow", 1734 + }, 1735 + mutedWordsPref: { 1736 + type: 'object', 1737 + required: ['items'], 1738 + properties: { 1739 + items: { 1740 + type: 'array', 1741 + items: { 1742 + ref: 'lex:app.bsky.actor.defs#mutedWord', 1743 + type: 'ref', 1744 + }, 1745 + description: 'A list of words the account owner has muted.', 1746 + }, 1747 + }, 1748 + }, 1749 + savedFeedsPref: { 1750 + type: 'object', 1751 + required: ['pinned', 'saved'], 1752 + properties: { 1753 + saved: { 1754 + type: 'array', 1755 + items: { 1756 + type: 'string', 1757 + format: 'at-uri', 1758 + }, 1759 + }, 1760 + pinned: { 1761 + type: 'array', 1762 + items: { 1763 + type: 'string', 1764 + format: 'at-uri', 1765 + }, 1766 + }, 1767 + timelineIndex: { 1768 + type: 'integer', 1769 + }, 1770 + }, 1771 + }, 1772 + threadViewPref: { 1773 + type: 'object', 1774 + properties: { 1775 + sort: { 1776 + type: 'string', 1777 + description: 'Sorting mode for threads.', 1778 + knownValues: [ 1779 + 'oldest', 1780 + 'newest', 1781 + 'most-likes', 1782 + 'random', 1783 + 'hotness', 1784 + ], 1785 + }, 1786 + prioritizeFollowedUsers: { 1787 + type: 'boolean', 1788 + description: 'Show followed users at the top of all replies.', 1789 + }, 1790 + }, 1791 + }, 1792 + hiddenPostsPref: { 1793 + type: 'object', 1794 + required: ['items'], 1795 + properties: { 1796 + items: { 1797 + type: 'array', 1798 + items: { 1799 + type: 'string', 1800 + format: 'at-uri', 1801 + }, 1802 + description: 1803 + 'A list of URIs of posts the account owner has hidden.', 1804 + }, 1805 + }, 1806 + }, 1807 + labelerPrefItem: { 1808 + type: 'object', 1809 + required: ['did'], 1810 + properties: { 1811 + did: { 1812 + type: 'string', 1813 + format: 'did', 1814 + }, 1815 + }, 1816 + }, 1817 + mutedWordTarget: { 1818 + type: 'string', 1819 + maxLength: 640, 1820 + knownValues: ['content', 'tag'], 1821 + maxGraphemes: 64, 1822 + }, 1823 + adultContentPref: { 1824 + type: 'object', 1825 + required: ['enabled'], 1826 + properties: { 1827 + enabled: { 1828 + type: 'boolean', 1829 + default: false, 1830 + }, 1831 + }, 1832 + }, 1833 + bskyAppStatePref: { 1834 + type: 'object', 1835 + properties: { 1836 + nuxs: { 1837 + type: 'array', 1838 + items: { 1839 + ref: 'lex:app.bsky.actor.defs#nux', 1840 + type: 'ref', 1841 + }, 1842 + maxLength: 100, 1843 + description: 'Storage for NUXs the user has encountered.', 1844 + }, 1845 + queuedNudges: { 1846 + type: 'array', 1847 + items: { 1848 + type: 'string', 1849 + maxLength: 100, 1850 + }, 1851 + maxLength: 1000, 1852 + description: 1853 + 'An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user.', 1854 + }, 1855 + activeProgressGuide: { 1856 + ref: 'lex:app.bsky.actor.defs#bskyAppProgressGuide', 1857 + type: 'ref', 1858 + }, 1859 + }, 1860 + description: 1861 + "A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this.", 1862 + }, 1863 + contentLabelPref: { 1864 + type: 'object', 1865 + required: ['label', 'visibility'], 1866 + properties: { 1867 + label: { 1868 + type: 'string', 1869 + }, 1870 + labelerDid: { 1871 + type: 'string', 1872 + format: 'did', 1873 + description: 1874 + 'Which labeler does this preference apply to? If undefined, applies globally.', 1875 + }, 1876 + visibility: { 1877 + type: 'string', 1878 + knownValues: ['ignore', 'show', 'warn', 'hide'], 1879 + }, 1880 + }, 1881 + }, 1882 + profileViewBasic: { 1883 + type: 'object', 1884 + required: ['did', 'handle'], 1885 + properties: { 1886 + did: { 1887 + type: 'string', 1888 + format: 'did', 1889 + }, 1890 + avatar: { 1891 + type: 'string', 1892 + format: 'uri', 1893 + }, 1894 + handle: { 1895 + type: 'string', 1896 + format: 'handle', 1897 + }, 1898 + labels: { 1899 + type: 'array', 1900 + items: { 1901 + ref: 'lex:com.atproto.label.defs#label', 1902 + type: 'ref', 1903 + }, 1904 + }, 1905 + viewer: { 1906 + ref: 'lex:app.bsky.actor.defs#viewerState', 1907 + type: 'ref', 1908 + }, 1909 + createdAt: { 1910 + type: 'string', 1911 + format: 'datetime', 1912 + }, 1913 + associated: { 1914 + ref: 'lex:app.bsky.actor.defs#profileAssociated', 1915 + type: 'ref', 1916 + }, 1917 + displayName: { 1918 + type: 'string', 1919 + maxLength: 640, 1920 + maxGraphemes: 64, 1921 + }, 1922 + }, 1923 + }, 1924 + savedFeedsPrefV2: { 1925 + type: 'object', 1926 + required: ['items'], 1927 + properties: { 1928 + items: { 1929 + type: 'array', 1930 + items: { 1931 + ref: 'lex:app.bsky.actor.defs#savedFeed', 1932 + type: 'ref', 1933 + }, 1934 + }, 1935 + }, 1936 + }, 1937 + profileAssociated: { 1938 + type: 'object', 1939 + properties: { 1940 + chat: { 1941 + ref: 'lex:app.bsky.actor.defs#profileAssociatedChat', 1942 + type: 'ref', 1943 + }, 1944 + lists: { 1945 + type: 'integer', 1946 + }, 1947 + labeler: { 1948 + type: 'boolean', 1949 + }, 1950 + feedgens: { 1951 + type: 'integer', 1952 + }, 1953 + starterPacks: { 1954 + type: 'integer', 1955 + }, 1956 + }, 1957 + }, 1958 + personalDetailsPref: { 1959 + type: 'object', 1960 + properties: { 1961 + birthDate: { 1962 + type: 'string', 1963 + format: 'datetime', 1964 + description: 'The birth date of account owner.', 1965 + }, 1966 + }, 1967 + }, 1968 + profileViewDetailed: { 1969 + type: 'object', 1970 + required: ['did', 'handle'], 1971 + properties: { 1972 + did: { 1973 + type: 'string', 1974 + format: 'did', 1975 + }, 1976 + avatar: { 1977 + type: 'string', 1978 + format: 'uri', 1979 + }, 1980 + banner: { 1981 + type: 'string', 1982 + format: 'uri', 1983 + }, 1984 + handle: { 1985 + type: 'string', 1986 + format: 'handle', 1987 + }, 1988 + labels: { 1989 + type: 'array', 1990 + items: { 1991 + ref: 'lex:com.atproto.label.defs#label', 1992 + type: 'ref', 1993 + }, 1994 + }, 1995 + viewer: { 1996 + ref: 'lex:app.bsky.actor.defs#viewerState', 1997 + type: 'ref', 1998 + }, 1999 + createdAt: { 2000 + type: 'string', 2001 + format: 'datetime', 2002 + }, 2003 + indexedAt: { 2004 + type: 'string', 2005 + format: 'datetime', 2006 + }, 2007 + associated: { 2008 + ref: 'lex:app.bsky.actor.defs#profileAssociated', 2009 + type: 'ref', 2010 + }, 2011 + pinnedPost: { 2012 + ref: 'lex:com.atproto.repo.strongRef', 2013 + type: 'ref', 2014 + }, 2015 + postsCount: { 2016 + type: 'integer', 2017 + }, 2018 + description: { 2019 + type: 'string', 2020 + maxLength: 2560, 2021 + maxGraphemes: 256, 2022 + }, 2023 + displayName: { 2024 + type: 'string', 2025 + maxLength: 640, 2026 + maxGraphemes: 64, 2027 + }, 2028 + followsCount: { 2029 + type: 'integer', 2030 + }, 2031 + followersCount: { 2032 + type: 'integer', 2033 + }, 2034 + joinedViaStarterPack: { 2035 + ref: 'lex:app.bsky.graph.defs#starterPackViewBasic', 2036 + type: 'ref', 2037 + }, 2038 + }, 2039 + }, 2040 + bskyAppProgressGuide: { 2041 + type: 'object', 2042 + required: ['guide'], 2043 + properties: { 2044 + guide: { 2045 + type: 'string', 2046 + maxLength: 100, 2047 + }, 2048 + }, 2049 + description: 2050 + 'If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress.', 2051 + }, 2052 + profileAssociatedChat: { 2053 + type: 'object', 2054 + required: ['allowIncoming'], 2055 + properties: { 2056 + allowIncoming: { 2057 + type: 'string', 2058 + knownValues: ['all', 'none', 'following'], 2059 + }, 2060 + }, 2061 + }, 2062 + postInteractionSettingsPref: { 2063 + type: 'object', 2064 + required: [], 2065 + properties: { 2066 + threadgateAllowRules: { 2067 + type: 'array', 2068 + items: { 2069 + refs: [ 2070 + 'lex:app.bsky.feed.threadgate#mentionRule', 2071 + 'lex:app.bsky.feed.threadgate#followerRule', 2072 + 'lex:app.bsky.feed.threadgate#followingRule', 2073 + 'lex:app.bsky.feed.threadgate#listRule', 2074 + ], 2075 + type: 'union', 2076 + }, 2077 + maxLength: 5, 2078 + description: 2079 + 'Matches threadgate record. List of rules defining who can reply to this users posts. If value is an empty array, no one can reply. If value is undefined, anyone can reply.', 2080 + }, 2081 + postgateEmbeddingRules: { 2082 + type: 'array', 2083 + items: { 2084 + refs: ['lex:app.bsky.feed.postgate#disableRule'], 2085 + type: 'union', 2086 + }, 2087 + maxLength: 5, 2088 + description: 2089 + 'Matches postgate record. List of rules defining who can embed this users posts. If value is an empty array or is undefined, no particular rules apply and anyone can embed.', 2090 + }, 2091 + }, 2092 + description: 2093 + 'Default post interaction settings for the account. These values should be applied as default values when creating new posts. These refs should mirror the threadgate and postgate records exactly.', 2094 + }, 2095 + }, 2096 + }, 2097 + AppBskyActorProfile: { 2098 + lexicon: 1, 2099 + id: 'app.bsky.actor.profile', 2100 + defs: { 2101 + main: { 2102 + key: 'literal:self', 2103 + type: 'record', 2104 + record: { 2105 + type: 'object', 2106 + properties: { 2107 + avatar: { 2108 + type: 'blob', 2109 + accept: ['image/png', 'image/jpeg'], 2110 + maxSize: 1000000, 2111 + description: 2112 + "Small image to be displayed next to posts from account. AKA, 'profile picture'", 2113 + }, 2114 + banner: { 2115 + type: 'blob', 2116 + accept: ['image/png', 'image/jpeg'], 2117 + maxSize: 1000000, 2118 + description: 2119 + 'Larger horizontal image to display behind profile view.', 2120 + }, 2121 + labels: { 2122 + refs: ['lex:com.atproto.label.defs#selfLabels'], 2123 + type: 'union', 2124 + description: 2125 + 'Self-label values, specific to the Bluesky application, on the overall account.', 2126 + }, 2127 + createdAt: { 2128 + type: 'string', 2129 + format: 'datetime', 2130 + }, 2131 + pinnedPost: { 2132 + ref: 'lex:com.atproto.repo.strongRef', 2133 + type: 'ref', 2134 + }, 2135 + description: { 2136 + type: 'string', 2137 + maxLength: 2560, 2138 + description: 'Free-form profile description text.', 2139 + maxGraphemes: 256, 2140 + }, 2141 + displayName: { 2142 + type: 'string', 2143 + maxLength: 640, 2144 + maxGraphemes: 64, 2145 + }, 2146 + joinedViaStarterPack: { 2147 + ref: 'lex:com.atproto.repo.strongRef', 2148 + type: 'ref', 2149 + }, 2150 + }, 2151 + }, 2152 + description: 'A declaration of a Bluesky account profile.', 2153 + }, 2154 + }, 2155 + }, 2156 + AppBskyLabelerDefs: { 2157 + lexicon: 1, 2158 + id: 'app.bsky.labeler.defs', 2159 + defs: { 2160 + labelerView: { 2161 + type: 'object', 2162 + required: ['uri', 'cid', 'creator', 'indexedAt'], 2163 + properties: { 2164 + cid: { 2165 + type: 'string', 2166 + format: 'cid', 2167 + }, 2168 + uri: { 2169 + type: 'string', 2170 + format: 'at-uri', 2171 + }, 2172 + labels: { 2173 + type: 'array', 2174 + items: { 2175 + ref: 'lex:com.atproto.label.defs#label', 2176 + type: 'ref', 2177 + }, 2178 + }, 2179 + viewer: { 2180 + ref: 'lex:app.bsky.labeler.defs#labelerViewerState', 2181 + type: 'ref', 2182 + }, 2183 + creator: { 2184 + ref: 'lex:app.bsky.actor.defs#profileView', 2185 + type: 'ref', 2186 + }, 2187 + indexedAt: { 2188 + type: 'string', 2189 + format: 'datetime', 2190 + }, 2191 + likeCount: { 2192 + type: 'integer', 2193 + minimum: 0, 2194 + }, 2195 + }, 2196 + }, 2197 + labelerPolicies: { 2198 + type: 'object', 2199 + required: ['labelValues'], 2200 + properties: { 2201 + labelValues: { 2202 + type: 'array', 2203 + items: { 2204 + ref: 'lex:com.atproto.label.defs#labelValue', 2205 + type: 'ref', 2206 + }, 2207 + description: 2208 + 'The label values which this labeler publishes. May include global or custom labels.', 2209 + }, 2210 + labelValueDefinitions: { 2211 + type: 'array', 2212 + items: { 2213 + ref: 'lex:com.atproto.label.defs#labelValueDefinition', 2214 + type: 'ref', 2215 + }, 2216 + description: 2217 + 'Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler.', 2218 + }, 2219 + }, 2220 + }, 2221 + labelerViewerState: { 2222 + type: 'object', 2223 + properties: { 2224 + like: { 2225 + type: 'string', 2226 + format: 'at-uri', 2227 + }, 2228 + }, 2229 + }, 2230 + labelerViewDetailed: { 2231 + type: 'object', 2232 + required: ['uri', 'cid', 'creator', 'policies', 'indexedAt'], 2233 + properties: { 2234 + cid: { 2235 + type: 'string', 2236 + format: 'cid', 2237 + }, 2238 + uri: { 2239 + type: 'string', 2240 + format: 'at-uri', 2241 + }, 2242 + labels: { 2243 + type: 'array', 2244 + items: { 2245 + ref: 'lex:com.atproto.label.defs#label', 2246 + type: 'ref', 2247 + }, 2248 + }, 2249 + viewer: { 2250 + ref: 'lex:app.bsky.labeler.defs#labelerViewerState', 2251 + type: 'ref', 2252 + }, 2253 + creator: { 2254 + ref: 'lex:app.bsky.actor.defs#profileView', 2255 + type: 'ref', 2256 + }, 2257 + policies: { 2258 + ref: 'lex:app.bsky.labeler.defs#labelerPolicies', 2259 + type: 'ref', 2260 + }, 2261 + indexedAt: { 2262 + type: 'string', 2263 + format: 'datetime', 2264 + }, 2265 + likeCount: { 2266 + type: 'integer', 2267 + minimum: 0, 2268 + }, 2269 + }, 2270 + }, 2271 + }, 2272 + }, 2273 + SocialGrainV0GalleryDefs: { 2274 + lexicon: 1, 2275 + id: 'social.grain.v0.gallery.defs', 2276 + defs: { 2277 + aspectRatio: { 2278 + type: 'object', 2279 + description: 2280 + 'width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.', 2281 + required: ['width', 'height'], 2282 + properties: { 2283 + width: { 2284 + type: 'integer', 2285 + minimum: 1, 2286 + }, 2287 + height: { 2288 + type: 'integer', 2289 + minimum: 1, 2290 + }, 2291 + }, 2292 + }, 2293 + galleryView: { 2294 + type: 'object', 2295 + required: ['uri', 'cid', 'creator', 'record', 'indexedAt'], 2296 + properties: { 2297 + uri: { 2298 + type: 'string', 2299 + format: 'at-uri', 2300 + }, 2301 + cid: { 2302 + type: 'string', 2303 + format: 'cid', 2304 + }, 2305 + creator: { 2306 + type: 'ref', 2307 + ref: 'lex:social.grain.v0.actor.defs#profileView', 2308 + }, 2309 + record: { 2310 + type: 'unknown', 2311 + }, 2312 + images: { 2313 + type: 'array', 2314 + items: { 2315 + type: 'ref', 2316 + ref: 'lex:social.grain.v0.gallery.defs#viewImage', 2317 + }, 2318 + }, 2319 + indexedAt: { 2320 + type: 'string', 2321 + format: 'datetime', 2322 + }, 2323 + }, 2324 + }, 2325 + image: { 2326 + type: 'object', 2327 + required: ['image', 'alt'], 2328 + properties: { 2329 + image: { 2330 + type: 'blob', 2331 + accept: ['image/*'], 2332 + maxSize: 1000000, 2333 + }, 2334 + alt: { 2335 + type: 'string', 2336 + description: 2337 + 'Alt text description of the image, for accessibility.', 2338 + }, 2339 + aspectRatio: { 2340 + type: 'ref', 2341 + ref: 'lex:social.grain.v0.gallery.defs#aspectRatio', 2342 + }, 2343 + }, 2344 + }, 2345 + viewImage: { 2346 + type: 'object', 2347 + required: ['cid', 'thumb', 'fullsize', 'alt'], 2348 + properties: { 2349 + cid: { 2350 + type: 'string', 2351 + format: 'cid', 2352 + }, 2353 + thumb: { 2354 + type: 'string', 2355 + format: 'uri', 2356 + description: 2357 + 'Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View.', 2358 + }, 2359 + fullsize: { 2360 + type: 'string', 2361 + format: 'uri', 2362 + description: 2363 + 'Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View.', 2364 + }, 2365 + alt: { 2366 + type: 'string', 2367 + description: 2368 + 'Alt text description of the image, for accessibility.', 2369 + }, 2370 + aspectRatio: { 2371 + type: 'ref', 2372 + ref: 'lex:social.grain.v0.gallery.defs#aspectRatio', 2373 + }, 2374 + }, 2375 + }, 2376 + }, 2377 + }, 2378 + SocialGrainV0Gallery: { 2379 + lexicon: 1, 2380 + id: 'social.grain.v0.gallery', 2381 + defs: { 2382 + main: { 2383 + type: 'record', 2384 + key: 'tid', 2385 + record: { 2386 + type: 'object', 2387 + required: ['title', 'createdAt'], 2388 + properties: { 2389 + title: { 2390 + type: 'string', 2391 + maxLength: 100, 2392 + }, 2393 + description: { 2394 + type: 'string', 2395 + maxLength: 1000, 2396 + }, 2397 + images: { 2398 + type: 'array', 2399 + items: { 2400 + type: 'ref', 2401 + ref: 'lex:social.grain.v0.gallery.defs#image', 2402 + }, 2403 + maxLength: 10, 2404 + }, 2405 + createdAt: { 2406 + type: 'string', 2407 + format: 'datetime', 2408 + }, 2409 + }, 2410 + }, 2411 + }, 2412 + }, 2413 + }, 2414 + SocialGrainV0GalleryStar: { 2415 + lexicon: 1, 2416 + id: 'social.grain.v0.gallery.star', 2417 + defs: { 2418 + main: { 2419 + type: 'record', 2420 + key: 'tid', 2421 + record: { 2422 + type: 'object', 2423 + required: ['createdAt', 'subject'], 2424 + properties: { 2425 + createdAt: { 2426 + type: 'string', 2427 + format: 'datetime', 2428 + }, 2429 + subject: { 2430 + type: 'string', 2431 + format: 'at-uri', 2432 + }, 2433 + }, 2434 + }, 2435 + }, 2436 + }, 2437 + }, 2438 + SocialGrainV0ActorDefs: { 2439 + lexicon: 1, 2440 + id: 'social.grain.v0.actor.defs', 2441 + defs: { 2442 + profileView: { 2443 + type: 'object', 2444 + required: ['did', 'handle'], 2445 + properties: { 2446 + did: { 2447 + type: 'string', 2448 + format: 'did', 2449 + }, 2450 + handle: { 2451 + type: 'string', 2452 + format: 'handle', 2453 + }, 2454 + displayName: { 2455 + type: 'string', 2456 + maxGraphemes: 64, 2457 + maxLength: 640, 2458 + }, 2459 + description: { 2460 + type: 'string', 2461 + maxLength: 2560, 2462 + maxGraphemes: 256, 2463 + }, 2464 + avatar: { 2465 + type: 'string', 2466 + format: 'uri', 2467 + }, 2468 + createdAt: { 2469 + type: 'string', 2470 + format: 'datetime', 2471 + }, 2472 + }, 2473 + }, 2474 + }, 2475 + }, 2476 + SocialGrainV0ActorProfile: { 2477 + lexicon: 1, 2478 + id: 'social.grain.v0.actor.profile', 2479 + defs: { 2480 + main: { 2481 + type: 'record', 2482 + description: 'A declaration of a basic account profile.', 2483 + key: 'literal:self', 2484 + record: { 2485 + type: 'object', 2486 + properties: { 2487 + displayName: { 2488 + type: 'string', 2489 + maxGraphemes: 64, 2490 + maxLength: 640, 2491 + }, 2492 + description: { 2493 + type: 'string', 2494 + description: 'Free-form profile description text.', 2495 + maxGraphemes: 256, 2496 + maxLength: 2560, 2497 + }, 2498 + avatar: { 2499 + type: 'blob', 2500 + description: 2501 + "Small image to be displayed next to posts from account. AKA, 'profile picture'", 2502 + accept: ['image/png', 'image/jpeg'], 2503 + maxSize: 1000000, 2504 + }, 2505 + createdAt: { 2506 + type: 'string', 2507 + format: 'datetime', 2508 + }, 2509 + }, 2510 + }, 2511 + }, 2512 + }, 2513 + }, 2514 + ComAtprotoLabelDefs: { 2515 + lexicon: 1, 2516 + id: 'com.atproto.label.defs', 2517 + defs: { 2518 + label: { 2519 + type: 'object', 2520 + required: ['src', 'uri', 'val', 'cts'], 2521 + properties: { 2522 + cid: { 2523 + type: 'string', 2524 + format: 'cid', 2525 + description: 2526 + "Optionally, CID specifying the specific version of 'uri' resource this label applies to.", 2527 + }, 2528 + cts: { 2529 + type: 'string', 2530 + format: 'datetime', 2531 + description: 'Timestamp when this label was created.', 2532 + }, 2533 + exp: { 2534 + type: 'string', 2535 + format: 'datetime', 2536 + description: 2537 + 'Timestamp at which this label expires (no longer applies).', 2538 + }, 2539 + neg: { 2540 + type: 'boolean', 2541 + description: 2542 + 'If true, this is a negation label, overwriting a previous label.', 2543 + }, 2544 + sig: { 2545 + type: 'bytes', 2546 + description: 'Signature of dag-cbor encoded label.', 2547 + }, 2548 + src: { 2549 + type: 'string', 2550 + format: 'did', 2551 + description: 'DID of the actor who created this label.', 2552 + }, 2553 + uri: { 2554 + type: 'string', 2555 + format: 'uri', 2556 + description: 2557 + 'AT URI of the record, repository (account), or other resource that this label applies to.', 2558 + }, 2559 + val: { 2560 + type: 'string', 2561 + maxLength: 128, 2562 + description: 2563 + 'The short string name of the value or type of this label.', 2564 + }, 2565 + ver: { 2566 + type: 'integer', 2567 + description: 'The AT Protocol version of the label object.', 2568 + }, 2569 + }, 2570 + description: 2571 + 'Metadata tag on an atproto resource (eg, repo or record).', 2572 + }, 2573 + selfLabel: { 2574 + type: 'object', 2575 + required: ['val'], 2576 + properties: { 2577 + val: { 2578 + type: 'string', 2579 + maxLength: 128, 2580 + description: 2581 + 'The short string name of the value or type of this label.', 2582 + }, 2583 + }, 2584 + description: 2585 + 'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.', 2586 + }, 2587 + labelValue: { 2588 + type: 'string', 2589 + knownValues: [ 2590 + '!hide', 2591 + '!no-promote', 2592 + '!warn', 2593 + '!no-unauthenticated', 2594 + 'dmca-violation', 2595 + 'doxxing', 2596 + 'porn', 2597 + 'sexual', 2598 + 'nudity', 2599 + 'nsfl', 2600 + 'gore', 2601 + ], 2602 + }, 2603 + selfLabels: { 2604 + type: 'object', 2605 + required: ['values'], 2606 + properties: { 2607 + values: { 2608 + type: 'array', 2609 + items: { 2610 + ref: 'lex:com.atproto.label.defs#selfLabel', 2611 + type: 'ref', 2612 + }, 2613 + maxLength: 10, 2614 + }, 2615 + }, 2616 + description: 2617 + 'Metadata tags on an atproto record, published by the author within the record.', 2618 + }, 2619 + labelValueDefinition: { 2620 + type: 'object', 2621 + required: ['identifier', 'severity', 'blurs', 'locales'], 2622 + properties: { 2623 + blurs: { 2624 + type: 'string', 2625 + description: 2626 + "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.", 2627 + knownValues: ['content', 'media', 'none'], 2628 + }, 2629 + locales: { 2630 + type: 'array', 2631 + items: { 2632 + ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings', 2633 + type: 'ref', 2634 + }, 2635 + }, 2636 + severity: { 2637 + type: 'string', 2638 + description: 2639 + "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 2640 + knownValues: ['inform', 'alert', 'none'], 2641 + }, 2642 + adultOnly: { 2643 + type: 'boolean', 2644 + description: 2645 + 'Does the user need to have adult content enabled in order to configure this label?', 2646 + }, 2647 + identifier: { 2648 + type: 'string', 2649 + maxLength: 100, 2650 + description: 2651 + "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 2652 + maxGraphemes: 100, 2653 + }, 2654 + defaultSetting: { 2655 + type: 'string', 2656 + default: 'warn', 2657 + description: 'The default setting for this label.', 2658 + knownValues: ['ignore', 'warn', 'hide'], 2659 + }, 2660 + }, 2661 + description: 2662 + 'Declares a label value and its expected interpretations and behaviors.', 2663 + }, 2664 + labelValueDefinitionStrings: { 2665 + type: 'object', 2666 + required: ['lang', 'name', 'description'], 2667 + properties: { 2668 + lang: { 2669 + type: 'string', 2670 + format: 'language', 2671 + description: 2672 + 'The code of the language these strings are written in.', 2673 + }, 2674 + name: { 2675 + type: 'string', 2676 + maxLength: 640, 2677 + description: 'A short human-readable name for the label.', 2678 + maxGraphemes: 64, 2679 + }, 2680 + description: { 2681 + type: 'string', 2682 + maxLength: 100000, 2683 + description: 2684 + 'A longer description of what the label means and why it might be applied.', 2685 + maxGraphemes: 10000, 2686 + }, 2687 + }, 2688 + description: 2689 + 'Strings which describe the label in the UI, localized into a specific language.', 2690 + }, 2691 + }, 2692 + }, 2693 + ComAtprotoRepoStrongRef: { 2694 + lexicon: 1, 2695 + id: 'com.atproto.repo.strongRef', 2696 + description: 'A URI with a content-hash fingerprint.', 2697 + defs: { 2698 + main: { 2699 + type: 'object', 2700 + required: ['uri', 'cid'], 2701 + properties: { 2702 + cid: { 2703 + type: 'string', 2704 + format: 'cid', 2705 + }, 2706 + uri: { 2707 + type: 'string', 2708 + format: 'at-uri', 2709 + }, 2710 + }, 2711 + }, 2712 + }, 2713 + }, 2714 + } as const satisfies Record<string, LexiconDoc> 2715 + export const schemas = Object.values(schemaDict) satisfies LexiconDoc[] 2716 + export const lexicons: Lexicons = new Lexicons(schemas) 2717 + 2718 + export function validate<T extends { $type: string }>( 2719 + v: unknown, 2720 + id: string, 2721 + hash: string, 2722 + requiredType: true, 2723 + ): ValidationResult<T> 2724 + export function validate<T extends { $type?: string }>( 2725 + v: unknown, 2726 + id: string, 2727 + hash: string, 2728 + requiredType?: false, 2729 + ): ValidationResult<T> 2730 + export function validate( 2731 + v: unknown, 2732 + id: string, 2733 + hash: string, 2734 + requiredType?: boolean, 2735 + ): ValidationResult { 2736 + return (requiredType ? is$typed : maybe$typed)(v, id, hash) 2737 + ? lexicons.validate(`${id}#${hash}`, v) 2738 + : { 2739 + success: false, 2740 + error: new ValidationError( 2741 + `Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`, 2742 + ), 2743 + } 2744 + } 2745 + 2746 + export const ids = { 2747 + AppBskyEmbedDefs: 'app.bsky.embed.defs', 2748 + AppBskyEmbedRecord: 'app.bsky.embed.record', 2749 + AppBskyEmbedImages: 'app.bsky.embed.images', 2750 + AppBskyEmbedRecordWithMedia: 'app.bsky.embed.recordWithMedia', 2751 + AppBskyEmbedVideo: 'app.bsky.embed.video', 2752 + AppBskyEmbedExternal: 'app.bsky.embed.external', 2753 + AppBskyGraphDefs: 'app.bsky.graph.defs', 2754 + AppBskyFeedDefs: 'app.bsky.feed.defs', 2755 + AppBskyFeedPostgate: 'app.bsky.feed.postgate', 2756 + AppBskyFeedThreadgate: 'app.bsky.feed.threadgate', 2757 + AppBskyRichtextFacet: 'app.bsky.richtext.facet', 2758 + AppBskyActorDefs: 'app.bsky.actor.defs', 2759 + AppBskyActorProfile: 'app.bsky.actor.profile', 2760 + AppBskyLabelerDefs: 'app.bsky.labeler.defs', 2761 + SocialGrainV0GalleryDefs: 'social.grain.v0.gallery.defs', 2762 + SocialGrainV0Gallery: 'social.grain.v0.gallery', 2763 + SocialGrainV0GalleryStar: 'social.grain.v0.gallery.star', 2764 + SocialGrainV0ActorDefs: 'social.grain.v0.actor.defs', 2765 + SocialGrainV0ActorProfile: 'social.grain.v0.actor.profile', 2766 + ComAtprotoLabelDefs: 'com.atproto.label.defs', 2767 + ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', 2768 + } as const
+529
__generated__/types/app/bsky/actor/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.ts' 13 + import type * as AppBskyGraphDefs from '../graph/defs.ts' 14 + import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef.ts' 15 + import type * as AppBskyFeedThreadgate from '../feed/threadgate.ts' 16 + import type * as AppBskyFeedPostgate from '../feed/postgate.ts' 17 + 18 + const is$typed = _is$typed, 19 + validate = _validate 20 + const id = 'app.bsky.actor.defs' 21 + 22 + /** A new user experiences (NUX) storage object */ 23 + export interface Nux { 24 + $type?: 'app.bsky.actor.defs#nux' 25 + id: string 26 + /** Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters. */ 27 + data?: string 28 + completed: boolean 29 + /** The date and time at which the NUX will expire and should be considered completed. */ 30 + expiresAt?: string 31 + } 32 + 33 + const hashNux = 'nux' 34 + 35 + export function isNux<V>(v: V) { 36 + return is$typed(v, id, hashNux) 37 + } 38 + 39 + export function validateNux<V>(v: V) { 40 + return validate<Nux & V>(v, id, hashNux) 41 + } 42 + 43 + /** A word that the account owner has muted. */ 44 + export interface MutedWord { 45 + $type?: 'app.bsky.actor.defs#mutedWord' 46 + id?: string 47 + /** The muted word itself. */ 48 + value: string 49 + /** The intended targets of the muted word. */ 50 + targets: MutedWordTarget[] 51 + /** The date and time at which the muted word will expire and no longer be applied. */ 52 + expiresAt?: string 53 + /** Groups of users to apply the muted word to. If undefined, applies to all users. */ 54 + actorTarget: 'all' | 'exclude-following' | (string & {}) 55 + } 56 + 57 + const hashMutedWord = 'mutedWord' 58 + 59 + export function isMutedWord<V>(v: V) { 60 + return is$typed(v, id, hashMutedWord) 61 + } 62 + 63 + export function validateMutedWord<V>(v: V) { 64 + return validate<MutedWord & V>(v, id, hashMutedWord) 65 + } 66 + 67 + export interface SavedFeed { 68 + $type?: 'app.bsky.actor.defs#savedFeed' 69 + id: string 70 + type: 'feed' | 'list' | 'timeline' | (string & {}) 71 + value: string 72 + pinned: boolean 73 + } 74 + 75 + const hashSavedFeed = 'savedFeed' 76 + 77 + export function isSavedFeed<V>(v: V) { 78 + return is$typed(v, id, hashSavedFeed) 79 + } 80 + 81 + export function validateSavedFeed<V>(v: V) { 82 + return validate<SavedFeed & V>(v, id, hashSavedFeed) 83 + } 84 + 85 + export type Preferences = ( 86 + | $Typed<AdultContentPref> 87 + | $Typed<ContentLabelPref> 88 + | $Typed<SavedFeedsPref> 89 + | $Typed<SavedFeedsPrefV2> 90 + | $Typed<PersonalDetailsPref> 91 + | $Typed<FeedViewPref> 92 + | $Typed<ThreadViewPref> 93 + | $Typed<InterestsPref> 94 + | $Typed<MutedWordsPref> 95 + | $Typed<HiddenPostsPref> 96 + | $Typed<BskyAppStatePref> 97 + | $Typed<LabelersPref> 98 + | $Typed<PostInteractionSettingsPref> 99 + | { $type: string } 100 + )[] 101 + 102 + export interface ProfileView { 103 + $type?: 'app.bsky.actor.defs#profileView' 104 + did: string 105 + avatar?: string 106 + handle: string 107 + labels?: ComAtprotoLabelDefs.Label[] 108 + viewer?: ViewerState 109 + createdAt?: string 110 + indexedAt?: string 111 + associated?: ProfileAssociated 112 + description?: string 113 + displayName?: string 114 + } 115 + 116 + const hashProfileView = 'profileView' 117 + 118 + export function isProfileView<V>(v: V) { 119 + return is$typed(v, id, hashProfileView) 120 + } 121 + 122 + export function validateProfileView<V>(v: V) { 123 + return validate<ProfileView & V>(v, id, hashProfileView) 124 + } 125 + 126 + /** Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests. */ 127 + export interface ViewerState { 128 + $type?: 'app.bsky.actor.defs#viewerState' 129 + muted?: boolean 130 + blocking?: string 131 + blockedBy?: boolean 132 + following?: string 133 + followedBy?: string 134 + mutedByList?: AppBskyGraphDefs.ListViewBasic 135 + blockingByList?: AppBskyGraphDefs.ListViewBasic 136 + knownFollowers?: KnownFollowers 137 + } 138 + 139 + const hashViewerState = 'viewerState' 140 + 141 + export function isViewerState<V>(v: V) { 142 + return is$typed(v, id, hashViewerState) 143 + } 144 + 145 + export function validateViewerState<V>(v: V) { 146 + return validate<ViewerState & V>(v, id, hashViewerState) 147 + } 148 + 149 + export interface FeedViewPref { 150 + $type?: 'app.bsky.actor.defs#feedViewPref' 151 + /** The URI of the feed, or an identifier which describes the feed. */ 152 + feed: string 153 + /** Hide replies in the feed. */ 154 + hideReplies?: boolean 155 + /** Hide reposts in the feed. */ 156 + hideReposts?: boolean 157 + /** Hide quote posts in the feed. */ 158 + hideQuotePosts?: boolean 159 + /** Hide replies in the feed if they do not have this number of likes. */ 160 + hideRepliesByLikeCount?: number 161 + /** Hide replies in the feed if they are not by followed users. */ 162 + hideRepliesByUnfollowed: boolean 163 + } 164 + 165 + const hashFeedViewPref = 'feedViewPref' 166 + 167 + export function isFeedViewPref<V>(v: V) { 168 + return is$typed(v, id, hashFeedViewPref) 169 + } 170 + 171 + export function validateFeedViewPref<V>(v: V) { 172 + return validate<FeedViewPref & V>(v, id, hashFeedViewPref) 173 + } 174 + 175 + export interface LabelersPref { 176 + $type?: 'app.bsky.actor.defs#labelersPref' 177 + labelers: LabelerPrefItem[] 178 + } 179 + 180 + const hashLabelersPref = 'labelersPref' 181 + 182 + export function isLabelersPref<V>(v: V) { 183 + return is$typed(v, id, hashLabelersPref) 184 + } 185 + 186 + export function validateLabelersPref<V>(v: V) { 187 + return validate<LabelersPref & V>(v, id, hashLabelersPref) 188 + } 189 + 190 + export interface InterestsPref { 191 + $type?: 'app.bsky.actor.defs#interestsPref' 192 + /** A list of tags which describe the account owner's interests gathered during onboarding. */ 193 + tags: string[] 194 + } 195 + 196 + const hashInterestsPref = 'interestsPref' 197 + 198 + export function isInterestsPref<V>(v: V) { 199 + return is$typed(v, id, hashInterestsPref) 200 + } 201 + 202 + export function validateInterestsPref<V>(v: V) { 203 + return validate<InterestsPref & V>(v, id, hashInterestsPref) 204 + } 205 + 206 + /** The subject's followers whom you also follow */ 207 + export interface KnownFollowers { 208 + $type?: 'app.bsky.actor.defs#knownFollowers' 209 + count: number 210 + followers: ProfileViewBasic[] 211 + } 212 + 213 + const hashKnownFollowers = 'knownFollowers' 214 + 215 + export function isKnownFollowers<V>(v: V) { 216 + return is$typed(v, id, hashKnownFollowers) 217 + } 218 + 219 + export function validateKnownFollowers<V>(v: V) { 220 + return validate<KnownFollowers & V>(v, id, hashKnownFollowers) 221 + } 222 + 223 + export interface MutedWordsPref { 224 + $type?: 'app.bsky.actor.defs#mutedWordsPref' 225 + /** A list of words the account owner has muted. */ 226 + items: MutedWord[] 227 + } 228 + 229 + const hashMutedWordsPref = 'mutedWordsPref' 230 + 231 + export function isMutedWordsPref<V>(v: V) { 232 + return is$typed(v, id, hashMutedWordsPref) 233 + } 234 + 235 + export function validateMutedWordsPref<V>(v: V) { 236 + return validate<MutedWordsPref & V>(v, id, hashMutedWordsPref) 237 + } 238 + 239 + export interface SavedFeedsPref { 240 + $type?: 'app.bsky.actor.defs#savedFeedsPref' 241 + saved: string[] 242 + pinned: string[] 243 + timelineIndex?: number 244 + } 245 + 246 + const hashSavedFeedsPref = 'savedFeedsPref' 247 + 248 + export function isSavedFeedsPref<V>(v: V) { 249 + return is$typed(v, id, hashSavedFeedsPref) 250 + } 251 + 252 + export function validateSavedFeedsPref<V>(v: V) { 253 + return validate<SavedFeedsPref & V>(v, id, hashSavedFeedsPref) 254 + } 255 + 256 + export interface ThreadViewPref { 257 + $type?: 'app.bsky.actor.defs#threadViewPref' 258 + /** Sorting mode for threads. */ 259 + sort?: 260 + | 'oldest' 261 + | 'newest' 262 + | 'most-likes' 263 + | 'random' 264 + | 'hotness' 265 + | (string & {}) 266 + /** Show followed users at the top of all replies. */ 267 + prioritizeFollowedUsers?: boolean 268 + } 269 + 270 + const hashThreadViewPref = 'threadViewPref' 271 + 272 + export function isThreadViewPref<V>(v: V) { 273 + return is$typed(v, id, hashThreadViewPref) 274 + } 275 + 276 + export function validateThreadViewPref<V>(v: V) { 277 + return validate<ThreadViewPref & V>(v, id, hashThreadViewPref) 278 + } 279 + 280 + export interface HiddenPostsPref { 281 + $type?: 'app.bsky.actor.defs#hiddenPostsPref' 282 + /** A list of URIs of posts the account owner has hidden. */ 283 + items: string[] 284 + } 285 + 286 + const hashHiddenPostsPref = 'hiddenPostsPref' 287 + 288 + export function isHiddenPostsPref<V>(v: V) { 289 + return is$typed(v, id, hashHiddenPostsPref) 290 + } 291 + 292 + export function validateHiddenPostsPref<V>(v: V) { 293 + return validate<HiddenPostsPref & V>(v, id, hashHiddenPostsPref) 294 + } 295 + 296 + export interface LabelerPrefItem { 297 + $type?: 'app.bsky.actor.defs#labelerPrefItem' 298 + did: string 299 + } 300 + 301 + const hashLabelerPrefItem = 'labelerPrefItem' 302 + 303 + export function isLabelerPrefItem<V>(v: V) { 304 + return is$typed(v, id, hashLabelerPrefItem) 305 + } 306 + 307 + export function validateLabelerPrefItem<V>(v: V) { 308 + return validate<LabelerPrefItem & V>(v, id, hashLabelerPrefItem) 309 + } 310 + 311 + export type MutedWordTarget = 'content' | 'tag' | (string & {}) 312 + 313 + export interface AdultContentPref { 314 + $type?: 'app.bsky.actor.defs#adultContentPref' 315 + enabled: boolean 316 + } 317 + 318 + const hashAdultContentPref = 'adultContentPref' 319 + 320 + export function isAdultContentPref<V>(v: V) { 321 + return is$typed(v, id, hashAdultContentPref) 322 + } 323 + 324 + export function validateAdultContentPref<V>(v: V) { 325 + return validate<AdultContentPref & V>(v, id, hashAdultContentPref) 326 + } 327 + 328 + /** A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this. */ 329 + export interface BskyAppStatePref { 330 + $type?: 'app.bsky.actor.defs#bskyAppStatePref' 331 + /** Storage for NUXs the user has encountered. */ 332 + nuxs?: Nux[] 333 + /** An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user. */ 334 + queuedNudges?: string[] 335 + activeProgressGuide?: BskyAppProgressGuide 336 + } 337 + 338 + const hashBskyAppStatePref = 'bskyAppStatePref' 339 + 340 + export function isBskyAppStatePref<V>(v: V) { 341 + return is$typed(v, id, hashBskyAppStatePref) 342 + } 343 + 344 + export function validateBskyAppStatePref<V>(v: V) { 345 + return validate<BskyAppStatePref & V>(v, id, hashBskyAppStatePref) 346 + } 347 + 348 + export interface ContentLabelPref { 349 + $type?: 'app.bsky.actor.defs#contentLabelPref' 350 + label: string 351 + /** Which labeler does this preference apply to? If undefined, applies globally. */ 352 + labelerDid?: string 353 + visibility: 'ignore' | 'show' | 'warn' | 'hide' | (string & {}) 354 + } 355 + 356 + const hashContentLabelPref = 'contentLabelPref' 357 + 358 + export function isContentLabelPref<V>(v: V) { 359 + return is$typed(v, id, hashContentLabelPref) 360 + } 361 + 362 + export function validateContentLabelPref<V>(v: V) { 363 + return validate<ContentLabelPref & V>(v, id, hashContentLabelPref) 364 + } 365 + 366 + export interface ProfileViewBasic { 367 + $type?: 'app.bsky.actor.defs#profileViewBasic' 368 + did: string 369 + avatar?: string 370 + handle: string 371 + labels?: ComAtprotoLabelDefs.Label[] 372 + viewer?: ViewerState 373 + createdAt?: string 374 + associated?: ProfileAssociated 375 + displayName?: string 376 + } 377 + 378 + const hashProfileViewBasic = 'profileViewBasic' 379 + 380 + export function isProfileViewBasic<V>(v: V) { 381 + return is$typed(v, id, hashProfileViewBasic) 382 + } 383 + 384 + export function validateProfileViewBasic<V>(v: V) { 385 + return validate<ProfileViewBasic & V>(v, id, hashProfileViewBasic) 386 + } 387 + 388 + export interface SavedFeedsPrefV2 { 389 + $type?: 'app.bsky.actor.defs#savedFeedsPrefV2' 390 + items: SavedFeed[] 391 + } 392 + 393 + const hashSavedFeedsPrefV2 = 'savedFeedsPrefV2' 394 + 395 + export function isSavedFeedsPrefV2<V>(v: V) { 396 + return is$typed(v, id, hashSavedFeedsPrefV2) 397 + } 398 + 399 + export function validateSavedFeedsPrefV2<V>(v: V) { 400 + return validate<SavedFeedsPrefV2 & V>(v, id, hashSavedFeedsPrefV2) 401 + } 402 + 403 + export interface ProfileAssociated { 404 + $type?: 'app.bsky.actor.defs#profileAssociated' 405 + chat?: ProfileAssociatedChat 406 + lists?: number 407 + labeler?: boolean 408 + feedgens?: number 409 + starterPacks?: number 410 + } 411 + 412 + const hashProfileAssociated = 'profileAssociated' 413 + 414 + export function isProfileAssociated<V>(v: V) { 415 + return is$typed(v, id, hashProfileAssociated) 416 + } 417 + 418 + export function validateProfileAssociated<V>(v: V) { 419 + return validate<ProfileAssociated & V>(v, id, hashProfileAssociated) 420 + } 421 + 422 + export interface PersonalDetailsPref { 423 + $type?: 'app.bsky.actor.defs#personalDetailsPref' 424 + /** The birth date of account owner. */ 425 + birthDate?: string 426 + } 427 + 428 + const hashPersonalDetailsPref = 'personalDetailsPref' 429 + 430 + export function isPersonalDetailsPref<V>(v: V) { 431 + return is$typed(v, id, hashPersonalDetailsPref) 432 + } 433 + 434 + export function validatePersonalDetailsPref<V>(v: V) { 435 + return validate<PersonalDetailsPref & V>(v, id, hashPersonalDetailsPref) 436 + } 437 + 438 + export interface ProfileViewDetailed { 439 + $type?: 'app.bsky.actor.defs#profileViewDetailed' 440 + did: string 441 + avatar?: string 442 + banner?: string 443 + handle: string 444 + labels?: ComAtprotoLabelDefs.Label[] 445 + viewer?: ViewerState 446 + createdAt?: string 447 + indexedAt?: string 448 + associated?: ProfileAssociated 449 + pinnedPost?: ComAtprotoRepoStrongRef.Main 450 + postsCount?: number 451 + description?: string 452 + displayName?: string 453 + followsCount?: number 454 + followersCount?: number 455 + joinedViaStarterPack?: AppBskyGraphDefs.StarterPackViewBasic 456 + } 457 + 458 + const hashProfileViewDetailed = 'profileViewDetailed' 459 + 460 + export function isProfileViewDetailed<V>(v: V) { 461 + return is$typed(v, id, hashProfileViewDetailed) 462 + } 463 + 464 + export function validateProfileViewDetailed<V>(v: V) { 465 + return validate<ProfileViewDetailed & V>(v, id, hashProfileViewDetailed) 466 + } 467 + 468 + /** If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress. */ 469 + export interface BskyAppProgressGuide { 470 + $type?: 'app.bsky.actor.defs#bskyAppProgressGuide' 471 + guide: string 472 + } 473 + 474 + const hashBskyAppProgressGuide = 'bskyAppProgressGuide' 475 + 476 + export function isBskyAppProgressGuide<V>(v: V) { 477 + return is$typed(v, id, hashBskyAppProgressGuide) 478 + } 479 + 480 + export function validateBskyAppProgressGuide<V>(v: V) { 481 + return validate<BskyAppProgressGuide & V>(v, id, hashBskyAppProgressGuide) 482 + } 483 + 484 + export interface ProfileAssociatedChat { 485 + $type?: 'app.bsky.actor.defs#profileAssociatedChat' 486 + allowIncoming: 'all' | 'none' | 'following' | (string & {}) 487 + } 488 + 489 + const hashProfileAssociatedChat = 'profileAssociatedChat' 490 + 491 + export function isProfileAssociatedChat<V>(v: V) { 492 + return is$typed(v, id, hashProfileAssociatedChat) 493 + } 494 + 495 + export function validateProfileAssociatedChat<V>(v: V) { 496 + return validate<ProfileAssociatedChat & V>(v, id, hashProfileAssociatedChat) 497 + } 498 + 499 + /** Default post interaction settings for the account. These values should be applied as default values when creating new posts. These refs should mirror the threadgate and postgate records exactly. */ 500 + export interface PostInteractionSettingsPref { 501 + $type?: 'app.bsky.actor.defs#postInteractionSettingsPref' 502 + /** Matches threadgate record. List of rules defining who can reply to this users posts. If value is an empty array, no one can reply. If value is undefined, anyone can reply. */ 503 + threadgateAllowRules?: ( 504 + | $Typed<AppBskyFeedThreadgate.MentionRule> 505 + | $Typed<AppBskyFeedThreadgate.FollowerRule> 506 + | $Typed<AppBskyFeedThreadgate.FollowingRule> 507 + | $Typed<AppBskyFeedThreadgate.ListRule> 508 + | { $type: string } 509 + )[] 510 + /** Matches postgate record. List of rules defining who can embed this users posts. If value is an empty array or is undefined, no particular rules apply and anyone can embed. */ 511 + postgateEmbeddingRules?: ( 512 + | $Typed<AppBskyFeedPostgate.DisableRule> 513 + | { $type: string } 514 + )[] 515 + } 516 + 517 + const hashPostInteractionSettingsPref = 'postInteractionSettingsPref' 518 + 519 + export function isPostInteractionSettingsPref<V>(v: V) { 520 + return is$typed(v, id, hashPostInteractionSettingsPref) 521 + } 522 + 523 + export function validatePostInteractionSettingsPref<V>(v: V) { 524 + return validate<PostInteractionSettingsPref & V>( 525 + v, 526 + id, 527 + hashPostInteractionSettingsPref, 528 + ) 529 + }
+43
__generated__/types/app/bsky/actor/profile.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.ts' 13 + import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef.ts' 14 + 15 + const is$typed = _is$typed, 16 + validate = _validate 17 + const id = 'app.bsky.actor.profile' 18 + 19 + export interface Record { 20 + $type: 'app.bsky.actor.profile' 21 + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ 22 + avatar?: BlobRef 23 + /** Larger horizontal image to display behind profile view. */ 24 + banner?: BlobRef 25 + labels?: $Typed<ComAtprotoLabelDefs.SelfLabels> | { $type: string } 26 + createdAt?: string 27 + pinnedPost?: ComAtprotoRepoStrongRef.Main 28 + /** Free-form profile description text. */ 29 + description?: string 30 + displayName?: string 31 + joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main 32 + [k: string]: unknown 33 + } 34 + 35 + const hashRecord = 'main' 36 + 37 + export function isRecord<V>(v: V) { 38 + return is$typed(v, id, hashRecord) 39 + } 40 + 41 + export function validateRecord<V>(v: V) { 42 + return validate<Record & V>(v, id, hashRecord, true) 43 + }
+32
__generated__/types/app/bsky/embed/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'app.bsky.embed.defs' 16 + 17 + /** width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit. */ 18 + export interface AspectRatio { 19 + $type?: 'app.bsky.embed.defs#aspectRatio' 20 + width: number 21 + height: number 22 + } 23 + 24 + const hashAspectRatio = 'aspectRatio' 25 + 26 + export function isAspectRatio<V>(v: V) { 27 + return is$typed(v, id, hashAspectRatio) 28 + } 29 + 30 + export function validateAspectRatio<V>(v: V) { 31 + return validate<AspectRatio & V>(v, id, hashAspectRatio) 32 + }
+82
__generated__/types/app/bsky/embed/external.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'app.bsky.embed.external' 16 + 17 + /** A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post). */ 18 + export interface Main { 19 + $type?: 'app.bsky.embed.external' 20 + external: External 21 + } 22 + 23 + const hashMain = 'main' 24 + 25 + export function isMain<V>(v: V) { 26 + return is$typed(v, id, hashMain) 27 + } 28 + 29 + export function validateMain<V>(v: V) { 30 + return validate<Main & V>(v, id, hashMain) 31 + } 32 + 33 + export interface View { 34 + $type?: 'app.bsky.embed.external#view' 35 + external: ViewExternal 36 + } 37 + 38 + const hashView = 'view' 39 + 40 + export function isView<V>(v: V) { 41 + return is$typed(v, id, hashView) 42 + } 43 + 44 + export function validateView<V>(v: V) { 45 + return validate<View & V>(v, id, hashView) 46 + } 47 + 48 + export interface External { 49 + $type?: 'app.bsky.embed.external#external' 50 + uri: string 51 + thumb?: BlobRef 52 + title: string 53 + description: string 54 + } 55 + 56 + const hashExternal = 'external' 57 + 58 + export function isExternal<V>(v: V) { 59 + return is$typed(v, id, hashExternal) 60 + } 61 + 62 + export function validateExternal<V>(v: V) { 63 + return validate<External & V>(v, id, hashExternal) 64 + } 65 + 66 + export interface ViewExternal { 67 + $type?: 'app.bsky.embed.external#viewExternal' 68 + uri: string 69 + thumb?: string 70 + title: string 71 + description: string 72 + } 73 + 74 + const hashViewExternal = 'viewExternal' 75 + 76 + export function isViewExternal<V>(v: V) { 77 + return is$typed(v, id, hashViewExternal) 78 + } 79 + 80 + export function validateViewExternal<V>(v: V) { 81 + return validate<ViewExternal & V>(v, id, hashViewExternal) 82 + }
+85
__generated__/types/app/bsky/embed/images.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + import type * as AppBskyEmbedDefs from './defs.ts' 13 + 14 + const is$typed = _is$typed, 15 + validate = _validate 16 + const id = 'app.bsky.embed.images' 17 + 18 + export interface Main { 19 + $type?: 'app.bsky.embed.images' 20 + images: Image[] 21 + } 22 + 23 + const hashMain = 'main' 24 + 25 + export function isMain<V>(v: V) { 26 + return is$typed(v, id, hashMain) 27 + } 28 + 29 + export function validateMain<V>(v: V) { 30 + return validate<Main & V>(v, id, hashMain) 31 + } 32 + 33 + export interface View { 34 + $type?: 'app.bsky.embed.images#view' 35 + images: ViewImage[] 36 + } 37 + 38 + const hashView = 'view' 39 + 40 + export function isView<V>(v: V) { 41 + return is$typed(v, id, hashView) 42 + } 43 + 44 + export function validateView<V>(v: V) { 45 + return validate<View & V>(v, id, hashView) 46 + } 47 + 48 + export interface Image { 49 + $type?: 'app.bsky.embed.images#image' 50 + /** Alt text description of the image, for accessibility. */ 51 + alt: string 52 + image: BlobRef 53 + aspectRatio?: AppBskyEmbedDefs.AspectRatio 54 + } 55 + 56 + const hashImage = 'image' 57 + 58 + export function isImage<V>(v: V) { 59 + return is$typed(v, id, hashImage) 60 + } 61 + 62 + export function validateImage<V>(v: V) { 63 + return validate<Image & V>(v, id, hashImage) 64 + } 65 + 66 + export interface ViewImage { 67 + $type?: 'app.bsky.embed.images#viewImage' 68 + /** Alt text description of the image, for accessibility. */ 69 + alt: string 70 + /** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. */ 71 + thumb: string 72 + /** Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. */ 73 + fullsize: string 74 + aspectRatio?: AppBskyEmbedDefs.AspectRatio 75 + } 76 + 77 + const hashViewImage = 'viewImage' 78 + 79 + export function isViewImage<V>(v: V) { 80 + return is$typed(v, id, hashViewImage) 81 + } 82 + 83 + export function validateViewImage<V>(v: V) { 84 + return validate<ViewImage & V>(v, id, hashViewImage) 85 + }
+146
__generated__/types/app/bsky/embed/record.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef.ts' 13 + import type * as AppBskyFeedDefs from '../feed/defs.ts' 14 + import type * as AppBskyGraphDefs from '../graph/defs.ts' 15 + import type * as AppBskyLabelerDefs from '../labeler/defs.ts' 16 + import type * as AppBskyActorDefs from '../actor/defs.ts' 17 + import type * as AppBskyEmbedImages from './images.ts' 18 + import type * as AppBskyEmbedVideo from './video.ts' 19 + import type * as AppBskyEmbedExternal from './external.ts' 20 + import type * as AppBskyEmbedRecordWithMedia from './recordWithMedia.ts' 21 + import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.ts' 22 + 23 + const is$typed = _is$typed, 24 + validate = _validate 25 + const id = 'app.bsky.embed.record' 26 + 27 + export interface Main { 28 + $type?: 'app.bsky.embed.record' 29 + record: ComAtprotoRepoStrongRef.Main 30 + } 31 + 32 + const hashMain = 'main' 33 + 34 + export function isMain<V>(v: V) { 35 + return is$typed(v, id, hashMain) 36 + } 37 + 38 + export function validateMain<V>(v: V) { 39 + return validate<Main & V>(v, id, hashMain) 40 + } 41 + 42 + export interface View { 43 + $type?: 'app.bsky.embed.record#view' 44 + record: 45 + | $Typed<ViewRecord> 46 + | $Typed<ViewNotFound> 47 + | $Typed<ViewBlocked> 48 + | $Typed<ViewDetached> 49 + | $Typed<AppBskyFeedDefs.GeneratorView> 50 + | $Typed<AppBskyGraphDefs.ListView> 51 + | $Typed<AppBskyLabelerDefs.LabelerView> 52 + | $Typed<AppBskyGraphDefs.StarterPackViewBasic> 53 + | { $type: string } 54 + } 55 + 56 + const hashView = 'view' 57 + 58 + export function isView<V>(v: V) { 59 + return is$typed(v, id, hashView) 60 + } 61 + 62 + export function validateView<V>(v: V) { 63 + return validate<View & V>(v, id, hashView) 64 + } 65 + 66 + export interface ViewRecord { 67 + $type?: 'app.bsky.embed.record#viewRecord' 68 + cid: string 69 + uri: string 70 + /** The record data itself. */ 71 + value: { [_ in string]: unknown } 72 + author: AppBskyActorDefs.ProfileViewBasic 73 + embeds?: ( 74 + | $Typed<AppBskyEmbedImages.View> 75 + | $Typed<AppBskyEmbedVideo.View> 76 + | $Typed<AppBskyEmbedExternal.View> 77 + | $Typed<View> 78 + | $Typed<AppBskyEmbedRecordWithMedia.View> 79 + | { $type: string } 80 + )[] 81 + labels?: ComAtprotoLabelDefs.Label[] 82 + indexedAt: string 83 + likeCount?: number 84 + quoteCount?: number 85 + replyCount?: number 86 + repostCount?: number 87 + } 88 + 89 + const hashViewRecord = 'viewRecord' 90 + 91 + export function isViewRecord<V>(v: V) { 92 + return is$typed(v, id, hashViewRecord) 93 + } 94 + 95 + export function validateViewRecord<V>(v: V) { 96 + return validate<ViewRecord & V>(v, id, hashViewRecord) 97 + } 98 + 99 + export interface ViewBlocked { 100 + $type?: 'app.bsky.embed.record#viewBlocked' 101 + uri: string 102 + author: AppBskyFeedDefs.BlockedAuthor 103 + blocked: true 104 + } 105 + 106 + const hashViewBlocked = 'viewBlocked' 107 + 108 + export function isViewBlocked<V>(v: V) { 109 + return is$typed(v, id, hashViewBlocked) 110 + } 111 + 112 + export function validateViewBlocked<V>(v: V) { 113 + return validate<ViewBlocked & V>(v, id, hashViewBlocked) 114 + } 115 + 116 + export interface ViewDetached { 117 + $type?: 'app.bsky.embed.record#viewDetached' 118 + uri: string 119 + detached: true 120 + } 121 + 122 + const hashViewDetached = 'viewDetached' 123 + 124 + export function isViewDetached<V>(v: V) { 125 + return is$typed(v, id, hashViewDetached) 126 + } 127 + 128 + export function validateViewDetached<V>(v: V) { 129 + return validate<ViewDetached & V>(v, id, hashViewDetached) 130 + } 131 + 132 + export interface ViewNotFound { 133 + $type?: 'app.bsky.embed.record#viewNotFound' 134 + uri: string 135 + notFound: true 136 + } 137 + 138 + const hashViewNotFound = 'viewNotFound' 139 + 140 + export function isViewNotFound<V>(v: V) { 141 + return is$typed(v, id, hashViewNotFound) 142 + } 143 + 144 + export function validateViewNotFound<V>(v: V) { 145 + return validate<ViewNotFound & V>(v, id, hashViewNotFound) 146 + }
+59
__generated__/types/app/bsky/embed/recordWithMedia.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + import type * as AppBskyEmbedImages from './images.ts' 13 + import type * as AppBskyEmbedVideo from './video.ts' 14 + import type * as AppBskyEmbedExternal from './external.ts' 15 + import type * as AppBskyEmbedRecord from './record.ts' 16 + 17 + const is$typed = _is$typed, 18 + validate = _validate 19 + const id = 'app.bsky.embed.recordWithMedia' 20 + 21 + export interface Main { 22 + $type?: 'app.bsky.embed.recordWithMedia' 23 + media: 24 + | $Typed<AppBskyEmbedImages.Main> 25 + | $Typed<AppBskyEmbedVideo.Main> 26 + | $Typed<AppBskyEmbedExternal.Main> 27 + | { $type: string } 28 + record: AppBskyEmbedRecord.Main 29 + } 30 + 31 + const hashMain = 'main' 32 + 33 + export function isMain<V>(v: V) { 34 + return is$typed(v, id, hashMain) 35 + } 36 + 37 + export function validateMain<V>(v: V) { 38 + return validate<Main & V>(v, id, hashMain) 39 + } 40 + 41 + export interface View { 42 + $type?: 'app.bsky.embed.recordWithMedia#view' 43 + media: 44 + | $Typed<AppBskyEmbedImages.View> 45 + | $Typed<AppBskyEmbedVideo.View> 46 + | $Typed<AppBskyEmbedExternal.View> 47 + | { $type: string } 48 + record: AppBskyEmbedRecord.View 49 + } 50 + 51 + const hashView = 'view' 52 + 53 + export function isView<V>(v: V) { 54 + return is$typed(v, id, hashView) 55 + } 56 + 57 + export function validateView<V>(v: V) { 58 + return validate<View & V>(v, id, hashView) 59 + }
+70
__generated__/types/app/bsky/embed/video.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + import type * as AppBskyEmbedDefs from './defs.ts' 13 + 14 + const is$typed = _is$typed, 15 + validate = _validate 16 + const id = 'app.bsky.embed.video' 17 + 18 + export interface Main { 19 + $type?: 'app.bsky.embed.video' 20 + /** Alt text description of the video, for accessibility. */ 21 + alt?: string 22 + video: BlobRef 23 + captions?: Caption[] 24 + aspectRatio?: AppBskyEmbedDefs.AspectRatio 25 + } 26 + 27 + const hashMain = 'main' 28 + 29 + export function isMain<V>(v: V) { 30 + return is$typed(v, id, hashMain) 31 + } 32 + 33 + export function validateMain<V>(v: V) { 34 + return validate<Main & V>(v, id, hashMain) 35 + } 36 + 37 + export interface View { 38 + $type?: 'app.bsky.embed.video#view' 39 + alt?: string 40 + cid: string 41 + playlist: string 42 + thumbnail?: string 43 + aspectRatio?: AppBskyEmbedDefs.AspectRatio 44 + } 45 + 46 + const hashView = 'view' 47 + 48 + export function isView<V>(v: V) { 49 + return is$typed(v, id, hashView) 50 + } 51 + 52 + export function validateView<V>(v: V) { 53 + return validate<View & V>(v, id, hashView) 54 + } 55 + 56 + export interface Caption { 57 + $type?: 'app.bsky.embed.video#caption' 58 + file: BlobRef 59 + lang: string 60 + } 61 + 62 + const hashCaption = 'caption' 63 + 64 + export function isCaption<V>(v: V) { 65 + return is$typed(v, id, hashCaption) 66 + } 67 + 68 + export function validateCaption<V>(v: V) { 69 + return validate<Caption & V>(v, id, hashCaption) 70 + }
+422
__generated__/types/app/bsky/feed/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + import type * as AppBskyEmbedImages from '../embed/images.ts' 13 + import type * as AppBskyEmbedVideo from '../embed/video.ts' 14 + import type * as AppBskyEmbedExternal from '../embed/external.ts' 15 + import type * as AppBskyEmbedRecord from '../embed/record.ts' 16 + import type * as AppBskyEmbedRecordWithMedia from '../embed/recordWithMedia.ts' 17 + import type * as AppBskyActorDefs from '../actor/defs.ts' 18 + import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.ts' 19 + import type * as AppBskyRichtextFacet from '../richtext/facet.ts' 20 + import type * as AppBskyGraphDefs from '../graph/defs.ts' 21 + 22 + const is$typed = _is$typed, 23 + validate = _validate 24 + const id = 'app.bsky.feed.defs' 25 + 26 + export interface PostView { 27 + $type?: 'app.bsky.feed.defs#postView' 28 + cid: string 29 + uri: string 30 + embed?: 31 + | $Typed<AppBskyEmbedImages.View> 32 + | $Typed<AppBskyEmbedVideo.View> 33 + | $Typed<AppBskyEmbedExternal.View> 34 + | $Typed<AppBskyEmbedRecord.View> 35 + | $Typed<AppBskyEmbedRecordWithMedia.View> 36 + | { $type: string } 37 + author: AppBskyActorDefs.ProfileViewBasic 38 + labels?: ComAtprotoLabelDefs.Label[] 39 + record: { [_ in string]: unknown } 40 + viewer?: ViewerState 41 + indexedAt: string 42 + likeCount?: number 43 + quoteCount?: number 44 + replyCount?: number 45 + threadgate?: ThreadgateView 46 + repostCount?: number 47 + } 48 + 49 + const hashPostView = 'postView' 50 + 51 + export function isPostView<V>(v: V) { 52 + return is$typed(v, id, hashPostView) 53 + } 54 + 55 + export function validatePostView<V>(v: V) { 56 + return validate<PostView & V>(v, id, hashPostView) 57 + } 58 + 59 + export interface ReplyRef { 60 + $type?: 'app.bsky.feed.defs#replyRef' 61 + root: 62 + | $Typed<PostView> 63 + | $Typed<NotFoundPost> 64 + | $Typed<BlockedPost> 65 + | { $type: string } 66 + parent: 67 + | $Typed<PostView> 68 + | $Typed<NotFoundPost> 69 + | $Typed<BlockedPost> 70 + | { $type: string } 71 + grandparentAuthor?: AppBskyActorDefs.ProfileViewBasic 72 + } 73 + 74 + const hashReplyRef = 'replyRef' 75 + 76 + export function isReplyRef<V>(v: V) { 77 + return is$typed(v, id, hashReplyRef) 78 + } 79 + 80 + export function validateReplyRef<V>(v: V) { 81 + return validate<ReplyRef & V>(v, id, hashReplyRef) 82 + } 83 + 84 + export interface ReasonPin { 85 + $type?: 'app.bsky.feed.defs#reasonPin' 86 + } 87 + 88 + const hashReasonPin = 'reasonPin' 89 + 90 + export function isReasonPin<V>(v: V) { 91 + return is$typed(v, id, hashReasonPin) 92 + } 93 + 94 + export function validateReasonPin<V>(v: V) { 95 + return validate<ReasonPin & V>(v, id, hashReasonPin) 96 + } 97 + 98 + export interface BlockedPost { 99 + $type?: 'app.bsky.feed.defs#blockedPost' 100 + uri: string 101 + author: BlockedAuthor 102 + blocked: true 103 + } 104 + 105 + const hashBlockedPost = 'blockedPost' 106 + 107 + export function isBlockedPost<V>(v: V) { 108 + return is$typed(v, id, hashBlockedPost) 109 + } 110 + 111 + export function validateBlockedPost<V>(v: V) { 112 + return validate<BlockedPost & V>(v, id, hashBlockedPost) 113 + } 114 + 115 + export interface Interaction { 116 + $type?: 'app.bsky.feed.defs#interaction' 117 + item?: string 118 + event?: 119 + | 'app.bsky.feed.defs#requestLess' 120 + | 'app.bsky.feed.defs#requestMore' 121 + | 'app.bsky.feed.defs#clickthroughItem' 122 + | 'app.bsky.feed.defs#clickthroughAuthor' 123 + | 'app.bsky.feed.defs#clickthroughReposter' 124 + | 'app.bsky.feed.defs#clickthroughEmbed' 125 + | 'app.bsky.feed.defs#interactionSeen' 126 + | 'app.bsky.feed.defs#interactionLike' 127 + | 'app.bsky.feed.defs#interactionRepost' 128 + | 'app.bsky.feed.defs#interactionReply' 129 + | 'app.bsky.feed.defs#interactionQuote' 130 + | 'app.bsky.feed.defs#interactionShare' 131 + | (string & {}) 132 + /** Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton. */ 133 + feedContext?: string 134 + } 135 + 136 + const hashInteraction = 'interaction' 137 + 138 + export function isInteraction<V>(v: V) { 139 + return is$typed(v, id, hashInteraction) 140 + } 141 + 142 + export function validateInteraction<V>(v: V) { 143 + return validate<Interaction & V>(v, id, hashInteraction) 144 + } 145 + 146 + /** Request that less content like the given feed item be shown in the feed */ 147 + export const REQUESTLESS = `${id}#requestLess` 148 + /** Request that more content like the given feed item be shown in the feed */ 149 + export const REQUESTMORE = `${id}#requestMore` 150 + 151 + /** Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests. */ 152 + export interface ViewerState { 153 + $type?: 'app.bsky.feed.defs#viewerState' 154 + like?: string 155 + pinned?: boolean 156 + repost?: string 157 + threadMuted?: boolean 158 + replyDisabled?: boolean 159 + embeddingDisabled?: boolean 160 + } 161 + 162 + const hashViewerState = 'viewerState' 163 + 164 + export function isViewerState<V>(v: V) { 165 + return is$typed(v, id, hashViewerState) 166 + } 167 + 168 + export function validateViewerState<V>(v: V) { 169 + return validate<ViewerState & V>(v, id, hashViewerState) 170 + } 171 + 172 + export interface FeedViewPost { 173 + $type?: 'app.bsky.feed.defs#feedViewPost' 174 + post: PostView 175 + reply?: ReplyRef 176 + reason?: $Typed<ReasonRepost> | $Typed<ReasonPin> | { $type: string } 177 + /** Context provided by feed generator that may be passed back alongside interactions. */ 178 + feedContext?: string 179 + } 180 + 181 + const hashFeedViewPost = 'feedViewPost' 182 + 183 + export function isFeedViewPost<V>(v: V) { 184 + return is$typed(v, id, hashFeedViewPost) 185 + } 186 + 187 + export function validateFeedViewPost<V>(v: V) { 188 + return validate<FeedViewPost & V>(v, id, hashFeedViewPost) 189 + } 190 + 191 + export interface NotFoundPost { 192 + $type?: 'app.bsky.feed.defs#notFoundPost' 193 + uri: string 194 + notFound: true 195 + } 196 + 197 + const hashNotFoundPost = 'notFoundPost' 198 + 199 + export function isNotFoundPost<V>(v: V) { 200 + return is$typed(v, id, hashNotFoundPost) 201 + } 202 + 203 + export function validateNotFoundPost<V>(v: V) { 204 + return validate<NotFoundPost & V>(v, id, hashNotFoundPost) 205 + } 206 + 207 + export interface ReasonRepost { 208 + $type?: 'app.bsky.feed.defs#reasonRepost' 209 + by: AppBskyActorDefs.ProfileViewBasic 210 + indexedAt: string 211 + } 212 + 213 + const hashReasonRepost = 'reasonRepost' 214 + 215 + export function isReasonRepost<V>(v: V) { 216 + return is$typed(v, id, hashReasonRepost) 217 + } 218 + 219 + export function validateReasonRepost<V>(v: V) { 220 + return validate<ReasonRepost & V>(v, id, hashReasonRepost) 221 + } 222 + 223 + export interface BlockedAuthor { 224 + $type?: 'app.bsky.feed.defs#blockedAuthor' 225 + did: string 226 + viewer?: AppBskyActorDefs.ViewerState 227 + } 228 + 229 + const hashBlockedAuthor = 'blockedAuthor' 230 + 231 + export function isBlockedAuthor<V>(v: V) { 232 + return is$typed(v, id, hashBlockedAuthor) 233 + } 234 + 235 + export function validateBlockedAuthor<V>(v: V) { 236 + return validate<BlockedAuthor & V>(v, id, hashBlockedAuthor) 237 + } 238 + 239 + export interface GeneratorView { 240 + $type?: 'app.bsky.feed.defs#generatorView' 241 + cid: string 242 + did: string 243 + uri: string 244 + avatar?: string 245 + labels?: ComAtprotoLabelDefs.Label[] 246 + viewer?: GeneratorViewerState 247 + creator: AppBskyActorDefs.ProfileView 248 + indexedAt: string 249 + likeCount?: number 250 + contentMode?: 251 + | 'app.bsky.feed.defs#contentModeUnspecified' 252 + | 'app.bsky.feed.defs#contentModeVideo' 253 + | (string & {}) 254 + description?: string 255 + displayName: string 256 + descriptionFacets?: AppBskyRichtextFacet.Main[] 257 + acceptsInteractions?: boolean 258 + } 259 + 260 + const hashGeneratorView = 'generatorView' 261 + 262 + export function isGeneratorView<V>(v: V) { 263 + return is$typed(v, id, hashGeneratorView) 264 + } 265 + 266 + export function validateGeneratorView<V>(v: V) { 267 + return validate<GeneratorView & V>(v, id, hashGeneratorView) 268 + } 269 + 270 + /** Metadata about this post within the context of the thread it is in. */ 271 + export interface ThreadContext { 272 + $type?: 'app.bsky.feed.defs#threadContext' 273 + rootAuthorLike?: string 274 + } 275 + 276 + const hashThreadContext = 'threadContext' 277 + 278 + export function isThreadContext<V>(v: V) { 279 + return is$typed(v, id, hashThreadContext) 280 + } 281 + 282 + export function validateThreadContext<V>(v: V) { 283 + return validate<ThreadContext & V>(v, id, hashThreadContext) 284 + } 285 + 286 + export interface ThreadViewPost { 287 + $type?: 'app.bsky.feed.defs#threadViewPost' 288 + post: PostView 289 + parent?: 290 + | $Typed<ThreadViewPost> 291 + | $Typed<NotFoundPost> 292 + | $Typed<BlockedPost> 293 + | { $type: string } 294 + replies?: ( 295 + | $Typed<ThreadViewPost> 296 + | $Typed<NotFoundPost> 297 + | $Typed<BlockedPost> 298 + | { $type: string } 299 + )[] 300 + threadContext?: ThreadContext 301 + } 302 + 303 + const hashThreadViewPost = 'threadViewPost' 304 + 305 + export function isThreadViewPost<V>(v: V) { 306 + return is$typed(v, id, hashThreadViewPost) 307 + } 308 + 309 + export function validateThreadViewPost<V>(v: V) { 310 + return validate<ThreadViewPost & V>(v, id, hashThreadViewPost) 311 + } 312 + 313 + export interface ThreadgateView { 314 + $type?: 'app.bsky.feed.defs#threadgateView' 315 + cid?: string 316 + uri?: string 317 + lists?: AppBskyGraphDefs.ListViewBasic[] 318 + record?: { [_ in string]: unknown } 319 + } 320 + 321 + const hashThreadgateView = 'threadgateView' 322 + 323 + export function isThreadgateView<V>(v: V) { 324 + return is$typed(v, id, hashThreadgateView) 325 + } 326 + 327 + export function validateThreadgateView<V>(v: V) { 328 + return validate<ThreadgateView & V>(v, id, hashThreadgateView) 329 + } 330 + 331 + /** User liked the feed item */ 332 + export const INTERACTIONLIKE = `${id}#interactionLike` 333 + /** Feed item was seen by user */ 334 + export const INTERACTIONSEEN = `${id}#interactionSeen` 335 + /** User clicked through to the feed item */ 336 + export const CLICKTHROUGHITEM = `${id}#clickthroughItem` 337 + /** Declares the feed generator returns posts containing app.bsky.embed.video embeds. */ 338 + export const CONTENTMODEVIDEO = `${id}#contentModeVideo` 339 + /** User quoted the feed item */ 340 + export const INTERACTIONQUOTE = `${id}#interactionQuote` 341 + /** User replied to the feed item */ 342 + export const INTERACTIONREPLY = `${id}#interactionReply` 343 + /** User shared the feed item */ 344 + export const INTERACTIONSHARE = `${id}#interactionShare` 345 + 346 + export interface SkeletonFeedPost { 347 + $type?: 'app.bsky.feed.defs#skeletonFeedPost' 348 + post: string 349 + reason?: 350 + | $Typed<SkeletonReasonRepost> 351 + | $Typed<SkeletonReasonPin> 352 + | { $type: string } 353 + /** Context that will be passed through to client and may be passed to feed generator back alongside interactions. */ 354 + feedContext?: string 355 + } 356 + 357 + const hashSkeletonFeedPost = 'skeletonFeedPost' 358 + 359 + export function isSkeletonFeedPost<V>(v: V) { 360 + return is$typed(v, id, hashSkeletonFeedPost) 361 + } 362 + 363 + export function validateSkeletonFeedPost<V>(v: V) { 364 + return validate<SkeletonFeedPost & V>(v, id, hashSkeletonFeedPost) 365 + } 366 + 367 + /** User clicked through to the embedded content of the feed item */ 368 + export const CLICKTHROUGHEMBED = `${id}#clickthroughEmbed` 369 + /** User reposted the feed item */ 370 + export const INTERACTIONREPOST = `${id}#interactionRepost` 371 + 372 + export interface SkeletonReasonPin { 373 + $type?: 'app.bsky.feed.defs#skeletonReasonPin' 374 + } 375 + 376 + const hashSkeletonReasonPin = 'skeletonReasonPin' 377 + 378 + export function isSkeletonReasonPin<V>(v: V) { 379 + return is$typed(v, id, hashSkeletonReasonPin) 380 + } 381 + 382 + export function validateSkeletonReasonPin<V>(v: V) { 383 + return validate<SkeletonReasonPin & V>(v, id, hashSkeletonReasonPin) 384 + } 385 + 386 + /** User clicked through to the author of the feed item */ 387 + export const CLICKTHROUGHAUTHOR = `${id}#clickthroughAuthor` 388 + /** User clicked through to the reposter of the feed item */ 389 + export const CLICKTHROUGHREPOSTER = `${id}#clickthroughReposter` 390 + 391 + export interface GeneratorViewerState { 392 + $type?: 'app.bsky.feed.defs#generatorViewerState' 393 + like?: string 394 + } 395 + 396 + const hashGeneratorViewerState = 'generatorViewerState' 397 + 398 + export function isGeneratorViewerState<V>(v: V) { 399 + return is$typed(v, id, hashGeneratorViewerState) 400 + } 401 + 402 + export function validateGeneratorViewerState<V>(v: V) { 403 + return validate<GeneratorViewerState & V>(v, id, hashGeneratorViewerState) 404 + } 405 + 406 + export interface SkeletonReasonRepost { 407 + $type?: 'app.bsky.feed.defs#skeletonReasonRepost' 408 + repost: string 409 + } 410 + 411 + const hashSkeletonReasonRepost = 'skeletonReasonRepost' 412 + 413 + export function isSkeletonReasonRepost<V>(v: V) { 414 + return is$typed(v, id, hashSkeletonReasonRepost) 415 + } 416 + 417 + export function validateSkeletonReasonRepost<V>(v: V) { 418 + return validate<SkeletonReasonRepost & V>(v, id, hashSkeletonReasonRepost) 419 + } 420 + 421 + /** Declares the feed generator returns any types of posts. */ 422 + export const CONTENTMODEUNSPECIFIED = `${id}#contentModeUnspecified`
+52
__generated__/types/app/bsky/feed/postgate.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'app.bsky.feed.postgate' 16 + 17 + export interface Record { 18 + $type: 'app.bsky.feed.postgate' 19 + /** Reference (AT-URI) to the post record. */ 20 + post: string 21 + createdAt: string 22 + /** List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed. */ 23 + embeddingRules?: ($Typed<DisableRule> | { $type: string })[] 24 + /** List of AT-URIs embedding this post that the author has detached from. */ 25 + detachedEmbeddingUris?: string[] 26 + [k: string]: unknown 27 + } 28 + 29 + const hashRecord = 'main' 30 + 31 + export function isRecord<V>(v: V) { 32 + return is$typed(v, id, hashRecord) 33 + } 34 + 35 + export function validateRecord<V>(v: V) { 36 + return validate<Record & V>(v, id, hashRecord, true) 37 + } 38 + 39 + /** Disables embedding of this post. */ 40 + export interface DisableRule { 41 + $type?: 'app.bsky.feed.postgate#disableRule' 42 + } 43 + 44 + const hashDisableRule = 'disableRule' 45 + 46 + export function isDisableRule<V>(v: V) { 47 + return is$typed(v, id, hashDisableRule) 48 + } 49 + 50 + export function validateDisableRule<V>(v: V) { 51 + return validate<DisableRule & V>(v, id, hashDisableRule) 52 + }
+104
__generated__/types/app/bsky/feed/threadgate.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'app.bsky.feed.threadgate' 16 + 17 + export interface Record { 18 + $type: 'app.bsky.feed.threadgate' 19 + /** Reference (AT-URI) to the post record. */ 20 + post: string 21 + /** List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply. */ 22 + allow?: ( 23 + | $Typed<MentionRule> 24 + | $Typed<FollowerRule> 25 + | $Typed<FollowingRule> 26 + | $Typed<ListRule> 27 + | { $type: string } 28 + )[] 29 + createdAt: string 30 + /** List of hidden reply URIs. */ 31 + hiddenReplies?: string[] 32 + [k: string]: unknown 33 + } 34 + 35 + const hashRecord = 'main' 36 + 37 + export function isRecord<V>(v: V) { 38 + return is$typed(v, id, hashRecord) 39 + } 40 + 41 + export function validateRecord<V>(v: V) { 42 + return validate<Record & V>(v, id, hashRecord, true) 43 + } 44 + 45 + /** Allow replies from actors on a list. */ 46 + export interface ListRule { 47 + $type?: 'app.bsky.feed.threadgate#listRule' 48 + list: string 49 + } 50 + 51 + const hashListRule = 'listRule' 52 + 53 + export function isListRule<V>(v: V) { 54 + return is$typed(v, id, hashListRule) 55 + } 56 + 57 + export function validateListRule<V>(v: V) { 58 + return validate<ListRule & V>(v, id, hashListRule) 59 + } 60 + 61 + /** Allow replies from actors mentioned in your post. */ 62 + export interface MentionRule { 63 + $type?: 'app.bsky.feed.threadgate#mentionRule' 64 + } 65 + 66 + const hashMentionRule = 'mentionRule' 67 + 68 + export function isMentionRule<V>(v: V) { 69 + return is$typed(v, id, hashMentionRule) 70 + } 71 + 72 + export function validateMentionRule<V>(v: V) { 73 + return validate<MentionRule & V>(v, id, hashMentionRule) 74 + } 75 + 76 + /** Allow replies from actors who follow you. */ 77 + export interface FollowerRule { 78 + $type?: 'app.bsky.feed.threadgate#followerRule' 79 + } 80 + 81 + const hashFollowerRule = 'followerRule' 82 + 83 + export function isFollowerRule<V>(v: V) { 84 + return is$typed(v, id, hashFollowerRule) 85 + } 86 + 87 + export function validateFollowerRule<V>(v: V) { 88 + return validate<FollowerRule & V>(v, id, hashFollowerRule) 89 + } 90 + 91 + /** Allow replies from actors you follow. */ 92 + export interface FollowingRule { 93 + $type?: 'app.bsky.feed.threadgate#followingRule' 94 + } 95 + 96 + const hashFollowingRule = 'followingRule' 97 + 98 + export function isFollowingRule<V>(v: V) { 99 + return is$typed(v, id, hashFollowingRule) 100 + } 101 + 102 + export function validateFollowingRule<V>(v: V) { 103 + return validate<FollowingRule & V>(v, id, hashFollowingRule) 104 + }
+199
__generated__/types/app/bsky/graph/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.ts' 13 + import type * as AppBskyActorDefs from '../actor/defs.ts' 14 + import type * as AppBskyRichtextFacet from '../richtext/facet.ts' 15 + import type * as AppBskyFeedDefs from '../feed/defs.ts' 16 + 17 + const is$typed = _is$typed, 18 + validate = _validate 19 + const id = 'app.bsky.graph.defs' 20 + /** A list of actors to apply an aggregate moderation action (mute/block) on. */ 21 + export const MODLIST = `${id}#modlist` 22 + 23 + export interface ListView { 24 + $type?: 'app.bsky.graph.defs#listView' 25 + cid: string 26 + uri: string 27 + name: string 28 + avatar?: string 29 + labels?: ComAtprotoLabelDefs.Label[] 30 + viewer?: ListViewerState 31 + creator: AppBskyActorDefs.ProfileView 32 + purpose: ListPurpose 33 + indexedAt: string 34 + description?: string 35 + listItemCount?: number 36 + descriptionFacets?: AppBskyRichtextFacet.Main[] 37 + } 38 + 39 + const hashListView = 'listView' 40 + 41 + export function isListView<V>(v: V) { 42 + return is$typed(v, id, hashListView) 43 + } 44 + 45 + export function validateListView<V>(v: V) { 46 + return validate<ListView & V>(v, id, hashListView) 47 + } 48 + 49 + /** A list of actors used for curation purposes such as list feeds or interaction gating. */ 50 + export const CURATELIST = `${id}#curatelist` 51 + 52 + export type ListPurpose = 53 + | 'app.bsky.graph.defs#modlist' 54 + | 'app.bsky.graph.defs#curatelist' 55 + | 'app.bsky.graph.defs#referencelist' 56 + | (string & {}) 57 + 58 + export interface ListItemView { 59 + $type?: 'app.bsky.graph.defs#listItemView' 60 + uri: string 61 + subject: AppBskyActorDefs.ProfileView 62 + } 63 + 64 + const hashListItemView = 'listItemView' 65 + 66 + export function isListItemView<V>(v: V) { 67 + return is$typed(v, id, hashListItemView) 68 + } 69 + 70 + export function validateListItemView<V>(v: V) { 71 + return validate<ListItemView & V>(v, id, hashListItemView) 72 + } 73 + 74 + /** lists the bi-directional graph relationships between one actor (not indicated in the object), and the target actors (the DID included in the object) */ 75 + export interface Relationship { 76 + $type?: 'app.bsky.graph.defs#relationship' 77 + did: string 78 + /** if the actor follows this DID, this is the AT-URI of the follow record */ 79 + following?: string 80 + /** if the actor is followed by this DID, contains the AT-URI of the follow record */ 81 + followedBy?: string 82 + } 83 + 84 + const hashRelationship = 'relationship' 85 + 86 + export function isRelationship<V>(v: V) { 87 + return is$typed(v, id, hashRelationship) 88 + } 89 + 90 + export function validateRelationship<V>(v: V) { 91 + return validate<Relationship & V>(v, id, hashRelationship) 92 + } 93 + 94 + export interface ListViewBasic { 95 + $type?: 'app.bsky.graph.defs#listViewBasic' 96 + cid: string 97 + uri: string 98 + name: string 99 + avatar?: string 100 + labels?: ComAtprotoLabelDefs.Label[] 101 + viewer?: ListViewerState 102 + purpose: ListPurpose 103 + indexedAt?: string 104 + listItemCount?: number 105 + } 106 + 107 + const hashListViewBasic = 'listViewBasic' 108 + 109 + export function isListViewBasic<V>(v: V) { 110 + return is$typed(v, id, hashListViewBasic) 111 + } 112 + 113 + export function validateListViewBasic<V>(v: V) { 114 + return validate<ListViewBasic & V>(v, id, hashListViewBasic) 115 + } 116 + 117 + /** indicates that a handle or DID could not be resolved */ 118 + export interface NotFoundActor { 119 + $type?: 'app.bsky.graph.defs#notFoundActor' 120 + actor: string 121 + notFound: true 122 + } 123 + 124 + const hashNotFoundActor = 'notFoundActor' 125 + 126 + export function isNotFoundActor<V>(v: V) { 127 + return is$typed(v, id, hashNotFoundActor) 128 + } 129 + 130 + export function validateNotFoundActor<V>(v: V) { 131 + return validate<NotFoundActor & V>(v, id, hashNotFoundActor) 132 + } 133 + 134 + /** A list of actors used for only for reference purposes such as within a starter pack. */ 135 + export const REFERENCELIST = `${id}#referencelist` 136 + 137 + export interface ListViewerState { 138 + $type?: 'app.bsky.graph.defs#listViewerState' 139 + muted?: boolean 140 + blocked?: string 141 + } 142 + 143 + const hashListViewerState = 'listViewerState' 144 + 145 + export function isListViewerState<V>(v: V) { 146 + return is$typed(v, id, hashListViewerState) 147 + } 148 + 149 + export function validateListViewerState<V>(v: V) { 150 + return validate<ListViewerState & V>(v, id, hashListViewerState) 151 + } 152 + 153 + export interface StarterPackView { 154 + $type?: 'app.bsky.graph.defs#starterPackView' 155 + cid: string 156 + uri: string 157 + list?: ListViewBasic 158 + feeds?: AppBskyFeedDefs.GeneratorView[] 159 + labels?: ComAtprotoLabelDefs.Label[] 160 + record: { [_ in string]: unknown } 161 + creator: AppBskyActorDefs.ProfileViewBasic 162 + indexedAt: string 163 + joinedWeekCount?: number 164 + listItemsSample?: ListItemView[] 165 + joinedAllTimeCount?: number 166 + } 167 + 168 + const hashStarterPackView = 'starterPackView' 169 + 170 + export function isStarterPackView<V>(v: V) { 171 + return is$typed(v, id, hashStarterPackView) 172 + } 173 + 174 + export function validateStarterPackView<V>(v: V) { 175 + return validate<StarterPackView & V>(v, id, hashStarterPackView) 176 + } 177 + 178 + export interface StarterPackViewBasic { 179 + $type?: 'app.bsky.graph.defs#starterPackViewBasic' 180 + cid: string 181 + uri: string 182 + labels?: ComAtprotoLabelDefs.Label[] 183 + record: { [_ in string]: unknown } 184 + creator: AppBskyActorDefs.ProfileViewBasic 185 + indexedAt: string 186 + listItemCount?: number 187 + joinedWeekCount?: number 188 + joinedAllTimeCount?: number 189 + } 190 + 191 + const hashStarterPackViewBasic = 'starterPackViewBasic' 192 + 193 + export function isStarterPackViewBasic<V>(v: V) { 194 + return is$typed(v, id, hashStarterPackViewBasic) 195 + } 196 + 197 + export function validateStarterPackViewBasic<V>(v: V) { 198 + return validate<StarterPackViewBasic & V>(v, id, hashStarterPackViewBasic) 199 + }
+93
__generated__/types/app/bsky/labeler/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.ts' 13 + import type * as AppBskyActorDefs from '../actor/defs.ts' 14 + 15 + const is$typed = _is$typed, 16 + validate = _validate 17 + const id = 'app.bsky.labeler.defs' 18 + 19 + export interface LabelerView { 20 + $type?: 'app.bsky.labeler.defs#labelerView' 21 + cid: string 22 + uri: string 23 + labels?: ComAtprotoLabelDefs.Label[] 24 + viewer?: LabelerViewerState 25 + creator: AppBskyActorDefs.ProfileView 26 + indexedAt: string 27 + likeCount?: number 28 + } 29 + 30 + const hashLabelerView = 'labelerView' 31 + 32 + export function isLabelerView<V>(v: V) { 33 + return is$typed(v, id, hashLabelerView) 34 + } 35 + 36 + export function validateLabelerView<V>(v: V) { 37 + return validate<LabelerView & V>(v, id, hashLabelerView) 38 + } 39 + 40 + export interface LabelerPolicies { 41 + $type?: 'app.bsky.labeler.defs#labelerPolicies' 42 + /** The label values which this labeler publishes. May include global or custom labels. */ 43 + labelValues: ComAtprotoLabelDefs.LabelValue[] 44 + /** Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler. */ 45 + labelValueDefinitions?: ComAtprotoLabelDefs.LabelValueDefinition[] 46 + } 47 + 48 + const hashLabelerPolicies = 'labelerPolicies' 49 + 50 + export function isLabelerPolicies<V>(v: V) { 51 + return is$typed(v, id, hashLabelerPolicies) 52 + } 53 + 54 + export function validateLabelerPolicies<V>(v: V) { 55 + return validate<LabelerPolicies & V>(v, id, hashLabelerPolicies) 56 + } 57 + 58 + export interface LabelerViewerState { 59 + $type?: 'app.bsky.labeler.defs#labelerViewerState' 60 + like?: string 61 + } 62 + 63 + const hashLabelerViewerState = 'labelerViewerState' 64 + 65 + export function isLabelerViewerState<V>(v: V) { 66 + return is$typed(v, id, hashLabelerViewerState) 67 + } 68 + 69 + export function validateLabelerViewerState<V>(v: V) { 70 + return validate<LabelerViewerState & V>(v, id, hashLabelerViewerState) 71 + } 72 + 73 + export interface LabelerViewDetailed { 74 + $type?: 'app.bsky.labeler.defs#labelerViewDetailed' 75 + cid: string 76 + uri: string 77 + labels?: ComAtprotoLabelDefs.Label[] 78 + viewer?: LabelerViewerState 79 + creator: AppBskyActorDefs.ProfileView 80 + policies: LabelerPolicies 81 + indexedAt: string 82 + likeCount?: number 83 + } 84 + 85 + const hashLabelerViewDetailed = 'labelerViewDetailed' 86 + 87 + export function isLabelerViewDetailed<V>(v: V) { 88 + return is$typed(v, id, hashLabelerViewDetailed) 89 + } 90 + 91 + export function validateLabelerViewDetailed<V>(v: V) { 92 + return validate<LabelerViewDetailed & V>(v, id, hashLabelerViewDetailed) 93 + }
+97
__generated__/types/app/bsky/richtext/facet.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'app.bsky.richtext.facet' 16 + 17 + /** 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'). */ 18 + export interface Tag { 19 + $type?: 'app.bsky.richtext.facet#tag' 20 + tag: string 21 + } 22 + 23 + const hashTag = 'tag' 24 + 25 + export function isTag<V>(v: V) { 26 + return is$typed(v, id, hashTag) 27 + } 28 + 29 + export function validateTag<V>(v: V) { 30 + return validate<Tag & V>(v, id, hashTag) 31 + } 32 + 33 + /** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. */ 34 + export interface Link { 35 + $type?: 'app.bsky.richtext.facet#link' 36 + uri: string 37 + } 38 + 39 + const hashLink = 'link' 40 + 41 + export function isLink<V>(v: V) { 42 + return is$typed(v, id, hashLink) 43 + } 44 + 45 + export function validateLink<V>(v: V) { 46 + return validate<Link & V>(v, id, hashLink) 47 + } 48 + 49 + /** Annotation of a sub-string within rich text. */ 50 + export interface Main { 51 + $type?: 'app.bsky.richtext.facet' 52 + index: ByteSlice 53 + features: ($Typed<Mention> | $Typed<Link> | $Typed<Tag> | { $type: string })[] 54 + } 55 + 56 + const hashMain = 'main' 57 + 58 + export function isMain<V>(v: V) { 59 + return is$typed(v, id, hashMain) 60 + } 61 + 62 + export function validateMain<V>(v: V) { 63 + return validate<Main & V>(v, id, hashMain) 64 + } 65 + 66 + /** Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. */ 67 + export interface Mention { 68 + $type?: 'app.bsky.richtext.facet#mention' 69 + did: string 70 + } 71 + 72 + const hashMention = 'mention' 73 + 74 + export function isMention<V>(v: V) { 75 + return is$typed(v, id, hashMention) 76 + } 77 + 78 + export function validateMention<V>(v: V) { 79 + return validate<Mention & V>(v, id, hashMention) 80 + } 81 + 82 + /** 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. */ 83 + export interface ByteSlice { 84 + $type?: 'app.bsky.richtext.facet#byteSlice' 85 + byteEnd: number 86 + byteStart: number 87 + } 88 + 89 + const hashByteSlice = 'byteSlice' 90 + 91 + export function isByteSlice<V>(v: V) { 92 + return is$typed(v, id, hashByteSlice) 93 + } 94 + 95 + export function validateByteSlice<V>(v: V) { 96 + return validate<ByteSlice & V>(v, id, hashByteSlice) 97 + }
+146
__generated__/types/com/atproto/label/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'com.atproto.label.defs' 16 + 17 + /** Metadata tag on an atproto resource (eg, repo or record). */ 18 + export interface Label { 19 + $type?: 'com.atproto.label.defs#label' 20 + /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */ 21 + cid?: string 22 + /** Timestamp when this label was created. */ 23 + cts: string 24 + /** Timestamp at which this label expires (no longer applies). */ 25 + exp?: string 26 + /** If true, this is a negation label, overwriting a previous label. */ 27 + neg?: boolean 28 + /** Signature of dag-cbor encoded label. */ 29 + sig?: Uint8Array 30 + /** DID of the actor who created this label. */ 31 + src: string 32 + /** AT URI of the record, repository (account), or other resource that this label applies to. */ 33 + uri: string 34 + /** The short string name of the value or type of this label. */ 35 + val: string 36 + /** The AT Protocol version of the label object. */ 37 + ver?: number 38 + } 39 + 40 + const hashLabel = 'label' 41 + 42 + export function isLabel<V>(v: V) { 43 + return is$typed(v, id, hashLabel) 44 + } 45 + 46 + export function validateLabel<V>(v: V) { 47 + return validate<Label & V>(v, id, hashLabel) 48 + } 49 + 50 + /** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. */ 51 + export interface SelfLabel { 52 + $type?: 'com.atproto.label.defs#selfLabel' 53 + /** The short string name of the value or type of this label. */ 54 + val: string 55 + } 56 + 57 + const hashSelfLabel = 'selfLabel' 58 + 59 + export function isSelfLabel<V>(v: V) { 60 + return is$typed(v, id, hashSelfLabel) 61 + } 62 + 63 + export function validateSelfLabel<V>(v: V) { 64 + return validate<SelfLabel & V>(v, id, hashSelfLabel) 65 + } 66 + 67 + export type LabelValue = 68 + | '!hide' 69 + | '!no-promote' 70 + | '!warn' 71 + | '!no-unauthenticated' 72 + | 'dmca-violation' 73 + | 'doxxing' 74 + | 'porn' 75 + | 'sexual' 76 + | 'nudity' 77 + | 'nsfl' 78 + | 'gore' 79 + | (string & {}) 80 + 81 + /** Metadata tags on an atproto record, published by the author within the record. */ 82 + export interface SelfLabels { 83 + $type?: 'com.atproto.label.defs#selfLabels' 84 + values: SelfLabel[] 85 + } 86 + 87 + const hashSelfLabels = 'selfLabels' 88 + 89 + export function isSelfLabels<V>(v: V) { 90 + return is$typed(v, id, hashSelfLabels) 91 + } 92 + 93 + export function validateSelfLabels<V>(v: V) { 94 + return validate<SelfLabels & V>(v, id, hashSelfLabels) 95 + } 96 + 97 + /** Declares a label value and its expected interpretations and behaviors. */ 98 + export interface LabelValueDefinition { 99 + $type?: 'com.atproto.label.defs#labelValueDefinition' 100 + /** 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. */ 101 + blurs: 'content' | 'media' | 'none' | (string & {}) 102 + locales: LabelValueDefinitionStrings[] 103 + /** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */ 104 + severity: 'inform' | 'alert' | 'none' | (string & {}) 105 + /** Does the user need to have adult content enabled in order to configure this label? */ 106 + adultOnly?: boolean 107 + /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */ 108 + identifier: string 109 + /** The default setting for this label. */ 110 + defaultSetting: 'ignore' | 'warn' | 'hide' | (string & {}) 111 + } 112 + 113 + const hashLabelValueDefinition = 'labelValueDefinition' 114 + 115 + export function isLabelValueDefinition<V>(v: V) { 116 + return is$typed(v, id, hashLabelValueDefinition) 117 + } 118 + 119 + export function validateLabelValueDefinition<V>(v: V) { 120 + return validate<LabelValueDefinition & V>(v, id, hashLabelValueDefinition) 121 + } 122 + 123 + /** Strings which describe the label in the UI, localized into a specific language. */ 124 + export interface LabelValueDefinitionStrings { 125 + $type?: 'com.atproto.label.defs#labelValueDefinitionStrings' 126 + /** The code of the language these strings are written in. */ 127 + lang: string 128 + /** A short human-readable name for the label. */ 129 + name: string 130 + /** A longer description of what the label means and why it might be applied. */ 131 + description: string 132 + } 133 + 134 + const hashLabelValueDefinitionStrings = 'labelValueDefinitionStrings' 135 + 136 + export function isLabelValueDefinitionStrings<V>(v: V) { 137 + return is$typed(v, id, hashLabelValueDefinitionStrings) 138 + } 139 + 140 + export function validateLabelValueDefinitionStrings<V>(v: V) { 141 + return validate<LabelValueDefinitionStrings & V>( 142 + v, 143 + id, 144 + hashLabelValueDefinitionStrings, 145 + ) 146 + }
+31
__generated__/types/com/atproto/repo/strongRef.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'com.atproto.repo.strongRef' 16 + 17 + export interface Main { 18 + $type?: 'com.atproto.repo.strongRef' 19 + cid: string 20 + uri: string 21 + } 22 + 23 + const hashMain = 'main' 24 + 25 + export function isMain<V>(v: V) { 26 + return is$typed(v, id, hashMain) 27 + } 28 + 29 + export function validateMain<V>(v: V) { 30 + return validate<Main & V>(v, id, hashMain) 31 + }
+35
__generated__/types/social/grain/v0/actor/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'social.grain.v0.actor.defs' 16 + 17 + export interface ProfileView { 18 + $type?: 'social.grain.v0.actor.defs#profileView' 19 + did: string 20 + handle: string 21 + displayName?: string 22 + description?: string 23 + avatar?: string 24 + createdAt?: string 25 + } 26 + 27 + const hashProfileView = 'profileView' 28 + 29 + export function isProfileView<V>(v: V) { 30 + return is$typed(v, id, hashProfileView) 31 + } 32 + 33 + export function validateProfileView<V>(v: V) { 34 + return validate<ProfileView & V>(v, id, hashProfileView) 35 + }
+36
__generated__/types/social/grain/v0/actor/profile.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'social.grain.v0.actor.profile' 16 + 17 + export interface Record { 18 + $type: 'social.grain.v0.actor.profile' 19 + displayName?: string 20 + /** Free-form profile description text. */ 21 + description?: string 22 + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ 23 + avatar?: BlobRef 24 + createdAt?: string 25 + [k: string]: unknown 26 + } 27 + 28 + const hashRecord = 'main' 29 + 30 + export function isRecord<V>(v: V) { 31 + return is$typed(v, id, hashRecord) 32 + } 33 + 34 + export function validateRecord<V>(v: V) { 35 + return validate<Record & V>(v, id, hashRecord, true) 36 + }
+35
__generated__/types/social/grain/v0/gallery.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + import type * as SocialGrainV0GalleryDefs from './gallery/defs.ts' 13 + 14 + const is$typed = _is$typed, 15 + validate = _validate 16 + const id = 'social.grain.v0.gallery' 17 + 18 + export interface Record { 19 + $type: 'social.grain.v0.gallery' 20 + title: string 21 + description?: string 22 + images?: SocialGrainV0GalleryDefs.Image[] 23 + createdAt: string 24 + [k: string]: unknown 25 + } 26 + 27 + const hashRecord = 'main' 28 + 29 + export function isRecord<V>(v: V) { 30 + return is$typed(v, id, hashRecord) 31 + } 32 + 33 + export function validateRecord<V>(v: V) { 34 + return validate<Record & V>(v, id, hashRecord, true) 35 + }
+93
__generated__/types/social/grain/v0/gallery/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../../util.ts' 12 + import type * as SocialGrainV0ActorDefs from '../actor/defs.ts' 13 + 14 + const is$typed = _is$typed, 15 + validate = _validate 16 + const id = 'social.grain.v0.gallery.defs' 17 + 18 + /** width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit. */ 19 + export interface AspectRatio { 20 + $type?: 'social.grain.v0.gallery.defs#aspectRatio' 21 + width: number 22 + height: number 23 + } 24 + 25 + const hashAspectRatio = 'aspectRatio' 26 + 27 + export function isAspectRatio<V>(v: V) { 28 + return is$typed(v, id, hashAspectRatio) 29 + } 30 + 31 + export function validateAspectRatio<V>(v: V) { 32 + return validate<AspectRatio & V>(v, id, hashAspectRatio) 33 + } 34 + 35 + export interface GalleryView { 36 + $type?: 'social.grain.v0.gallery.defs#galleryView' 37 + uri: string 38 + cid: string 39 + creator: SocialGrainV0ActorDefs.ProfileView 40 + record: { [_ in string]: unknown } 41 + images?: ViewImage[] 42 + indexedAt: string 43 + } 44 + 45 + const hashGalleryView = 'galleryView' 46 + 47 + export function isGalleryView<V>(v: V) { 48 + return is$typed(v, id, hashGalleryView) 49 + } 50 + 51 + export function validateGalleryView<V>(v: V) { 52 + return validate<GalleryView & V>(v, id, hashGalleryView) 53 + } 54 + 55 + export interface Image { 56 + $type?: 'social.grain.v0.gallery.defs#image' 57 + image: BlobRef 58 + /** Alt text description of the image, for accessibility. */ 59 + alt: string 60 + aspectRatio?: AspectRatio 61 + } 62 + 63 + const hashImage = 'image' 64 + 65 + export function isImage<V>(v: V) { 66 + return is$typed(v, id, hashImage) 67 + } 68 + 69 + export function validateImage<V>(v: V) { 70 + return validate<Image & V>(v, id, hashImage) 71 + } 72 + 73 + export interface ViewImage { 74 + $type?: 'social.grain.v0.gallery.defs#viewImage' 75 + cid: string 76 + /** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. */ 77 + thumb: string 78 + /** Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. */ 79 + fullsize: string 80 + /** Alt text description of the image, for accessibility. */ 81 + alt: string 82 + aspectRatio?: AspectRatio 83 + } 84 + 85 + const hashViewImage = 'viewImage' 86 + 87 + export function isViewImage<V>(v: V) { 88 + return is$typed(v, id, hashViewImage) 89 + } 90 + 91 + export function validateViewImage<V>(v: V) { 92 + return validate<ViewImage & V>(v, id, hashViewImage) 93 + }
+32
__generated__/types/social/grain/v0/gallery/star.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'social.grain.v0.gallery.star' 16 + 17 + export interface Record { 18 + $type: 'social.grain.v0.gallery.star' 19 + createdAt: string 20 + subject: string 21 + [k: string]: unknown 22 + } 23 + 24 + const hashRecord = 'main' 25 + 26 + export function isRecord<V>(v: V) { 27 + return is$typed(v, id, hashRecord) 28 + } 29 + 30 + export function validateRecord<V>(v: V) { 31 + return validate<Record & V>(v, id, hashRecord, true) 32 + }
+82
__generated__/util.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + 5 + import { type ValidationResult } from "npm:@atproto/lexicon" 6 + 7 + export type OmitKey<T, K extends keyof T> = { 8 + [K2 in keyof T as K2 extends K ? never : K2]: T[K2] 9 + } 10 + 11 + export type $Typed<V, T extends string = string> = V & { $type: T } 12 + export type Un$Typed<V extends { $type?: string }> = OmitKey<V, '$type'> 13 + 14 + export type $Type<Id extends string, Hash extends string> = Hash extends 'main' 15 + ? Id 16 + : `${Id}#${Hash}` 17 + 18 + function isObject<V>(v: V): v is V & object { 19 + return v != null && typeof v === 'object' 20 + } 21 + 22 + function is$type<Id extends string, Hash extends string>( 23 + $type: unknown, 24 + id: Id, 25 + hash: Hash, 26 + ): $type is $Type<Id, Hash> { 27 + return hash === 'main' 28 + ? $type === id 29 + : // $type === `${id}#${hash}` 30 + typeof $type === 'string' && 31 + $type.length === id.length + 1 + hash.length && 32 + $type.charCodeAt(id.length) === 35 /* '#' */ && 33 + $type.startsWith(id) && 34 + $type.endsWith(hash) 35 + } 36 + 37 + export type $TypedObject< 38 + V, 39 + Id extends string, 40 + Hash extends string, 41 + > = V extends { 42 + $type: $Type<Id, Hash> 43 + } 44 + ? V 45 + : V extends { $type?: string } 46 + ? V extends { $type?: infer T extends $Type<Id, Hash> } 47 + ? V & { $type: T } 48 + : never 49 + : V & { $type: $Type<Id, Hash> } 50 + 51 + export function is$typed<V, Id extends string, Hash extends string>( 52 + v: V, 53 + id: Id, 54 + hash: Hash, 55 + ): v is $TypedObject<V, Id, Hash> { 56 + return isObject(v) && '$type' in v && is$type(v.$type, id, hash) 57 + } 58 + 59 + export function maybe$typed<V, Id extends string, Hash extends string>( 60 + v: V, 61 + id: Id, 62 + hash: Hash, 63 + ): v is V & object & { $type?: $Type<Id, Hash> } { 64 + return ( 65 + isObject(v) && 66 + ('$type' in v ? v.$type === undefined || is$type(v.$type, id, hash) : true) 67 + ) 68 + } 69 + 70 + export type Validator<R = unknown> = (v: unknown) => ValidationResult<R> 71 + export type ValidatorParam<V extends Validator> = 72 + V extends Validator<infer R> ? R : never 73 + 74 + /** 75 + * Utility function that allows to convert a "validate*" utility function into a 76 + * type predicate. 77 + */ 78 + export function asPredicate<V extends Validator>(validate: V) { 79 + return function <T>(v: T): v is T & ValidatorParam<V> { 80 + return validate(v).success 81 + } 82 + }
+27
deno.json
··· 1 + { 2 + "imports": { 3 + "$lexicon/": "./__generated__/", 4 + "@atproto/syntax": "npm:@atproto/syntax@^0.4.0", 5 + "@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.5", 6 + "@gfx/canvas": "jsr:@gfx/canvas@^0.5.8", 7 + "@tailwindcss/cli": "npm:@tailwindcss/cli@^4.1.4", 8 + "date-fns": "npm:date-fns@^4.1.0", 9 + "popmotion": "npm:popmotion@^11.0.5", 10 + "preact": "npm:preact@^10.26.5", 11 + "tailwindcss": "npm:tailwindcss@^4.1.4", 12 + "typed-htmx": "npm:typed-htmx@^0.3.1" 13 + }, 14 + "tasks": { 15 + "start": "deno run -A --unstable-kv --unstable-ffi main.tsx", 16 + "dev": "deno run \"dev:*\"", 17 + "dev:server": "deno run -A --unstable-kv --unstable-ffi --env-file=.env --watch ./main.tsx", 18 + "dev:tailwind": "deno run -A --node-modules-dir npm:@tailwindcss/cli -i ./input.css -o ./static/styles.css --watch", 19 + "codegen": "deno run -A ../../packages/bff-cli/mod.ts lex" 20 + }, 21 + "compilerOptions": { 22 + "jsx": "precompile", 23 + "jsxPrecompileSkipElements": ["a", "label"], 24 + "jsxImportSource": "preact" 25 + }, 26 + "nodeModulesDir": "auto" 27 + }
+1622
deno.lock
··· 1 + { 2 + "version": "4", 3 + "specifiers": { 4 + "jsr:@bigmoves/atproto-oauth-client@0.1": "0.1.0", 5 + "jsr:@bigmoves/bff@0.3.0-beta.5": "0.3.0-beta.5", 6 + "jsr:@denosaurs/plug@1": "1.0.5", 7 + "jsr:@denosaurs/plug@1.0.5": "1.0.5", 8 + "jsr:@gfx/canvas@~0.5.8": "0.5.8", 9 + "jsr:@std/assert@0.214": "0.214.0", 10 + "jsr:@std/assert@0.217": "0.217.0", 11 + "jsr:@std/assert@^1.0.12": "1.0.13", 12 + "jsr:@std/cache@0.2": "0.2.0", 13 + "jsr:@std/cli@^1.0.17": "1.0.17", 14 + "jsr:@std/encoding@0.214": "0.214.0", 15 + "jsr:@std/encoding@0.217.0": "0.217.0", 16 + "jsr:@std/encoding@^1.0.10": "1.0.10", 17 + "jsr:@std/fmt@0.214": "0.214.0", 18 + "jsr:@std/fmt@^1.0.7": "1.0.7", 19 + "jsr:@std/fs@0.214": "0.214.0", 20 + "jsr:@std/fs@0.217.0": "0.217.0", 21 + "jsr:@std/html@^1.0.3": "1.0.3", 22 + "jsr:@std/http@^1.0.13": "1.0.15", 23 + "jsr:@std/internal@^1.0.6": "1.0.6", 24 + "jsr:@std/media-types@^1.1.0": "1.1.0", 25 + "jsr:@std/net@^1.0.4": "1.0.4", 26 + "jsr:@std/path@0.214": "0.214.0", 27 + "jsr:@std/path@0.217": "0.217.0", 28 + "jsr:@std/path@0.217.0": "0.217.0", 29 + "jsr:@std/path@^1.0.8": "1.0.9", 30 + "jsr:@std/path@^1.0.9": "1.0.9", 31 + "jsr:@std/streams@^1.0.9": "1.0.9", 32 + "npm:@atproto-labs/handle-resolver-node@~0.1.14": "0.1.14", 33 + "npm:@atproto-labs/simple-store@~0.1.2": "0.1.2", 34 + "npm:@atproto/api@~0.14.19": "0.14.22", 35 + "npm:@atproto/common@~0.4.10": "0.4.10", 36 + "npm:@atproto/identity@~0.4.7": "0.4.7", 37 + "npm:@atproto/jwk@0.1.4": "0.1.4", 38 + "npm:@atproto/lex-cli@*": "0.7.2", 39 + "npm:@atproto/lexicon@*": "0.4.10", 40 + "npm:@atproto/lexicon@~0.4.10": "0.4.10", 41 + "npm:@atproto/oauth-client@~0.3.13": "0.3.13", 42 + "npm:@atproto/oauth-types@~0.2.4": "0.2.4", 43 + "npm:@atproto/syntax@0.4": "0.4.0", 44 + "npm:@atproto/xrpc-server@*": "0.7.15", 45 + "npm:@tailwindcss/cli@*": "4.1.4", 46 + "npm:@tailwindcss/cli@^4.1.4": "4.1.4", 47 + "npm:@types/node@*": "22.12.0", 48 + "npm:clsx@^2.1.1": "2.1.1", 49 + "npm:date-fns@^4.1.0": "4.1.0", 50 + "npm:jose@5.9.6": "5.9.6", 51 + "npm:multiformats@*": "13.3.2", 52 + "npm:multiformats@^13.3.2": "13.3.2", 53 + "npm:popmotion@^11.0.5": "11.0.5", 54 + "npm:preact-render-to-string@^6.5.13": "6.5.13_preact@10.26.5", 55 + "npm:preact@^10.26.5": "10.26.5", 56 + "npm:sharp@~0.34.1": "0.34.1", 57 + "npm:tailwind-merge@^3.2.0": "3.2.0", 58 + "npm:tailwindcss@^4.1.4": "4.1.4", 59 + "npm:typed-htmx@~0.3.1": "0.3.1" 60 + }, 61 + "jsr": { 62 + "@bigmoves/atproto-oauth-client@0.1.0": { 63 + "integrity": "d5858f534a800a46af28b1c03b447b179d15bbf164c24767601ae78513501711", 64 + "dependencies": [ 65 + "npm:@atproto-labs/handle-resolver-node", 66 + "npm:@atproto-labs/simple-store", 67 + "npm:@atproto/jwk", 68 + "npm:@atproto/oauth-client", 69 + "npm:@atproto/oauth-types", 70 + "npm:jose" 71 + ] 72 + }, 73 + "@bigmoves/bff@0.3.0-beta.5": { 74 + "integrity": "e9de02ccf1b90c2b97e10511c7975d04c824a750822fb2486e350f3cc5e5717d", 75 + "dependencies": [ 76 + "jsr:@bigmoves/atproto-oauth-client", 77 + "jsr:@std/assert@^1.0.12", 78 + "jsr:@std/cache", 79 + "jsr:@std/http", 80 + "jsr:@std/path@^1.0.8", 81 + "npm:@atproto/api", 82 + "npm:@atproto/common", 83 + "npm:@atproto/identity", 84 + "npm:@atproto/lexicon@~0.4.10", 85 + "npm:@atproto/oauth-client", 86 + "npm:@atproto/syntax", 87 + "npm:clsx", 88 + "npm:multiformats@^13.3.2", 89 + "npm:preact", 90 + "npm:preact-render-to-string", 91 + "npm:tailwind-merge" 92 + ] 93 + }, 94 + "@denosaurs/plug@1.0.5": { 95 + "integrity": "04cd988da558adc226202d88c3a434d5fcc08146eaf4baf0cea0c2284b16d2bf", 96 + "dependencies": [ 97 + "jsr:@std/encoding@0.214", 98 + "jsr:@std/fmt@0.214", 99 + "jsr:@std/fs@0.214", 100 + "jsr:@std/path@0.214" 101 + ] 102 + }, 103 + "@gfx/canvas@0.5.8": { 104 + "integrity": "a61c80292528e7433d428556b494a0ea496dd8e6abd4a338b8b25fc04e46ea3e", 105 + "dependencies": [ 106 + "jsr:@denosaurs/plug@1", 107 + "jsr:@denosaurs/plug@1.0.5", 108 + "jsr:@std/encoding@0.217.0", 109 + "jsr:@std/fs@0.217.0", 110 + "jsr:@std/path@0.217.0" 111 + ] 112 + }, 113 + "@std/assert@0.214.0": { 114 + "integrity": "55d398de76a9828fd3b1aa653f4dba3eee4c6985d90c514865d2be9bd082b140" 115 + }, 116 + "@std/assert@0.217.0": { 117 + "integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642" 118 + }, 119 + "@std/assert@1.0.13": { 120 + "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29", 121 + "dependencies": [ 122 + "jsr:@std/internal" 123 + ] 124 + }, 125 + "@std/cache@0.2.0": { 126 + "integrity": "63a2ccd5a9e7c03e430f7d34dfcfd0d0cfc90731a1eaf8208f4c66e418fc3035" 127 + }, 128 + "@std/cli@1.0.17": { 129 + "integrity": "e15b9abe629e17be90cc6216327f03a29eae613365f1353837fa749aad29ce7b" 130 + }, 131 + "@std/encoding@0.214.0": { 132 + "integrity": "30a8713e1db22986c7e780555ffd2fefd1d4f9374d734bb41f5970f6c3352af5" 133 + }, 134 + "@std/encoding@0.217.0": { 135 + "integrity": "b03e8ff94c98d6b6a02c02c5cf8e5d203400155516248964fc4559abc04669dc" 136 + }, 137 + "@std/encoding@1.0.10": { 138 + "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" 139 + }, 140 + "@std/fmt@0.214.0": { 141 + "integrity": "40382cff88a0783b347b4d69b94cf931ab8e549a733916718cb866c08efac4d4" 142 + }, 143 + "@std/fmt@1.0.7": { 144 + "integrity": "2a727c043d8df62cd0b819b3fb709b64dd622e42c3b1bb817ea7e6cc606360fb" 145 + }, 146 + "@std/fs@0.214.0": { 147 + "integrity": "bc880fea0be120cb1550b1ed7faf92fe071003d83f2456a1e129b39193d85bea", 148 + "dependencies": [ 149 + "jsr:@std/assert@0.214", 150 + "jsr:@std/path@0.214" 151 + ] 152 + }, 153 + "@std/fs@0.217.0": { 154 + "integrity": "0bfff5f3618d68c385b28b4ffbf3a15c98293a0f1186444458b62e0111ce77b2", 155 + "dependencies": [ 156 + "jsr:@std/assert@0.217", 157 + "jsr:@std/path@0.217" 158 + ] 159 + }, 160 + "@std/html@1.0.3": { 161 + "integrity": "7a0ac35e050431fb49d44e61c8b8aac1ebd55937e0dc9ec6409aa4bab39a7988" 162 + }, 163 + "@std/http@1.0.15": { 164 + "integrity": "435a4934b4e196e82a8233f724da525f7b7112f3566502f28815e94764c19159", 165 + "dependencies": [ 166 + "jsr:@std/cli", 167 + "jsr:@std/encoding@^1.0.10", 168 + "jsr:@std/fmt@^1.0.7", 169 + "jsr:@std/html", 170 + "jsr:@std/media-types", 171 + "jsr:@std/net", 172 + "jsr:@std/path@^1.0.9", 173 + "jsr:@std/streams" 174 + ] 175 + }, 176 + "@std/internal@1.0.6": { 177 + "integrity": "9533b128f230f73bd209408bb07a4b12f8d4255ab2a4d22a1fd6d87304aca9a4" 178 + }, 179 + "@std/media-types@1.1.0": { 180 + "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" 181 + }, 182 + "@std/net@1.0.4": { 183 + "integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852" 184 + }, 185 + "@std/path@0.214.0": { 186 + "integrity": "d5577c0b8d66f7e8e3586d864ebdf178bb326145a3611da5a51c961740300285", 187 + "dependencies": [ 188 + "jsr:@std/assert@0.214" 189 + ] 190 + }, 191 + "@std/path@0.217.0": { 192 + "integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11", 193 + "dependencies": [ 194 + "jsr:@std/assert@0.217" 195 + ] 196 + }, 197 + "@std/path@1.0.9": { 198 + "integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e" 199 + }, 200 + "@std/streams@1.0.9": { 201 + "integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035" 202 + } 203 + }, 204 + "npm": { 205 + "@atproto-labs/did-resolver@0.1.11": { 206 + "integrity": "sha512-qXNzIX2GPQnxT1gl35nv/8ErDdc4Fj/+RlJE7oyE7JGkFAPUyuY03TvKJ79SmWFsWE8wyTXEpLuphr9Da1Vhkw==", 207 + "dependencies": [ 208 + "@atproto-labs/fetch", 209 + "@atproto-labs/pipe", 210 + "@atproto-labs/simple-store", 211 + "@atproto-labs/simple-store-memory", 212 + "@atproto/did", 213 + "zod" 214 + ] 215 + }, 216 + "@atproto-labs/fetch-node@0.1.8": { 217 + "integrity": "sha512-OOTIhZNPEDDm7kaYU8iYRgzM+D5n3mP2iiBSyKuLakKTaZBL5WwYlUsJVsqX26SnUXtGEroOJEVJ6f66OcG80w==", 218 + "dependencies": [ 219 + "@atproto-labs/fetch", 220 + "@atproto-labs/pipe", 221 + "ipaddr.js@2.2.0", 222 + "psl", 223 + "undici" 224 + ] 225 + }, 226 + "@atproto-labs/fetch@0.2.2": { 227 + "integrity": "sha512-QyafkedbFeVaN20DYUpnY2hcArYxjdThPXbYMqOSoZhcvkrUqaw4xDND4wZB5TBD9cq2yqe9V6mcw9P4XQKQuQ==", 228 + "dependencies": [ 229 + "@atproto-labs/pipe" 230 + ] 231 + }, 232 + "@atproto-labs/handle-resolver-node@0.1.14": { 233 + "integrity": "sha512-+kOf+xENdxUNrrLoIcp/L4ommIa1SHnwfHIWbxumXnacfurjMOnZhfXeiNsEguaAxDNYpqDNpKsFBtcgjffXvQ==", 234 + "dependencies": [ 235 + "@atproto-labs/fetch-node", 236 + "@atproto-labs/handle-resolver", 237 + "@atproto/did" 238 + ] 239 + }, 240 + "@atproto-labs/handle-resolver@0.1.7": { 241 + "integrity": "sha512-nb4uAOgRVMp2NGVTJnor4ohqySbd1KyB5VzQLaRjMaPwH60Al057eTqiKRbeH/xD7hOBPNj1m0YjgxzvyAnWkg==", 242 + "dependencies": [ 243 + "@atproto-labs/simple-store", 244 + "@atproto-labs/simple-store-memory", 245 + "@atproto/did", 246 + "zod" 247 + ] 248 + }, 249 + "@atproto-labs/identity-resolver@0.1.15": { 250 + "integrity": "sha512-3ABob5iUDoFL85I8/pJE4wncz3148fADoxNVAdksyACxxjpH1GNhSYNyIpRpdMCJ/kjj69DM9rggumTHqnD/Xg==", 251 + "dependencies": [ 252 + "@atproto-labs/did-resolver", 253 + "@atproto-labs/handle-resolver", 254 + "@atproto/syntax" 255 + ] 256 + }, 257 + "@atproto-labs/pipe@0.1.0": { 258 + "integrity": "sha512-ghOqHFyJlQVFPESzlVHjKroP0tPzbmG5Jms0dNI9yLDEfL8xp4OFPWLX4f6T8mRq69wWs4nIDM3sSsFbFqLa1w==" 259 + }, 260 + "@atproto-labs/simple-store-memory@0.1.2": { 261 + "integrity": "sha512-q6wawjKKXuhUzr2MnkSlgr6zU6VimYkL8eNvLQvkroLnIDyMkoCKO4+EJ885ZD8lGwBo4pX9Lhrg9JJ+ncJI8g==", 262 + "dependencies": [ 263 + "@atproto-labs/simple-store", 264 + "lru-cache" 265 + ] 266 + }, 267 + "@atproto-labs/simple-store@0.1.2": { 268 + "integrity": "sha512-9vTNvyPPBs44tKVFht16wGlilW8u4wpEtKwLkWbuNEh3h9TTQ8zjVhEoGZh/v73G4Otr9JUOSIq+/5+8OZD2mQ==" 269 + }, 270 + "@atproto/api@0.14.22": { 271 + "integrity": "sha512-ziXPau+sUdFovObSnsoN7JbOmUw1C5e5L28/yXf3P8vbEnSS3HVVGD1jYcscBYY34xQqi4bVDpwMYx/4yRsTuQ==", 272 + "dependencies": [ 273 + "@atproto/common-web", 274 + "@atproto/lexicon", 275 + "@atproto/syntax", 276 + "@atproto/xrpc", 277 + "await-lock", 278 + "multiformats@9.9.0", 279 + "tlds", 280 + "zod" 281 + ] 282 + }, 283 + "@atproto/common-web@0.4.1": { 284 + "integrity": "sha512-Ghh+djHYMAUCktLKwr2IuGgtjcwSWGudp+K7+N7KBA9pDDloOXUEY8Agjc5SHSo9B1QIEFkegClU5n+apn2e0w==", 285 + "dependencies": [ 286 + "graphemer", 287 + "multiformats@9.9.0", 288 + "uint8arrays", 289 + "zod" 290 + ] 291 + }, 292 + "@atproto/common@0.4.10": { 293 + "integrity": "sha512-/Yxnax3XOhf46jYpe8/6O3ORjTNMB4YCaxx3V1f+FKy6meTm3GNrJwo8d1CBs0UiTiheRiNATOV3u0s3C7Ydaw==", 294 + "dependencies": [ 295 + "@atproto/common-web", 296 + "@ipld/dag-cbor", 297 + "cbor-x", 298 + "iso-datestring-validator", 299 + "multiformats@9.9.0", 300 + "pino" 301 + ] 302 + }, 303 + "@atproto/crypto@0.4.4": { 304 + "integrity": "sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA==", 305 + "dependencies": [ 306 + "@noble/curves", 307 + "@noble/hashes", 308 + "uint8arrays" 309 + ] 310 + }, 311 + "@atproto/did@0.1.5": { 312 + "integrity": "sha512-8+1D08QdGE5TF0bB0vV8HLVrVZJeLNITpRTUVEoABNMRaUS7CoYSVb0+JNQDeJIVmqMjOL8dOjvCUDkp3gEaGQ==", 313 + "dependencies": [ 314 + "zod" 315 + ] 316 + }, 317 + "@atproto/identity@0.4.7": { 318 + "integrity": "sha512-A61OT9yc74dEFi1elODt/tzQNSwV3ZGZCY5cRl6NYO9t/0AVdaD+fyt81yh3mRxyI8HeVOecvXl3cPX5knz9rQ==", 319 + "dependencies": [ 320 + "@atproto/common-web", 321 + "@atproto/crypto" 322 + ] 323 + }, 324 + "@atproto/jwk@0.1.4": { 325 + "integrity": "sha512-dSRuEi0FbxL5ln6hEFHp5ZW01xbQH9yJi5odZaEYpcA6beZHf/bawlU12CQy/CDsbC3FxSqrBw7Q2t7mvdSBqw==", 326 + "dependencies": [ 327 + "multiformats@9.9.0", 328 + "zod" 329 + ] 330 + }, 331 + "@atproto/lex-cli@0.7.2": { 332 + "integrity": "sha512-GdAH5Y3mEZGqASxyhRPZ8iLEOByJb+/T206AuwPAoIzLuqvSK2E4H0G61J2KdvEa1X78jroJ3/iotppD5vsmlg==", 333 + "dependencies": [ 334 + "@atproto/lexicon", 335 + "@atproto/syntax", 336 + "chalk", 337 + "commander", 338 + "prettier", 339 + "ts-morph", 340 + "yesno", 341 + "zod" 342 + ] 343 + }, 344 + "@atproto/lexicon@0.4.10": { 345 + "integrity": "sha512-uDbP20vetBgtXPuxoyRcvOGBt2gNe1dFc9yYKcb6jWmXfseHiGTnIlORJOLBXIT2Pz15Eap4fLxAu6zFAykD5A==", 346 + "dependencies": [ 347 + "@atproto/common-web", 348 + "@atproto/syntax", 349 + "iso-datestring-validator", 350 + "multiformats@9.9.0", 351 + "zod" 352 + ] 353 + }, 354 + "@atproto/oauth-client@0.3.13": { 355 + "integrity": "sha512-PqE6hWG6bhpu5OUbccoAZjoj9LQroStuPjXqkHCsnUfQGmruzuNmzMS0myLdoWCx+NSGr4sMgUPjGzAHXSLoaQ==", 356 + "dependencies": [ 357 + "@atproto-labs/did-resolver", 358 + "@atproto-labs/fetch", 359 + "@atproto-labs/handle-resolver", 360 + "@atproto-labs/identity-resolver", 361 + "@atproto-labs/simple-store", 362 + "@atproto-labs/simple-store-memory", 363 + "@atproto/did", 364 + "@atproto/jwk", 365 + "@atproto/oauth-types", 366 + "@atproto/xrpc", 367 + "multiformats@9.9.0", 368 + "zod" 369 + ] 370 + }, 371 + "@atproto/oauth-types@0.2.4": { 372 + "integrity": "sha512-V2LnlXi1CSmBQWTQgDm8l4oN7xYxlftVwM7hrvYNP+Jxo3Ozfe0QLK1Wy/CH6/ZqzrBBhYvcbf4DJYTUwPA+hw==", 373 + "dependencies": [ 374 + "@atproto/jwk", 375 + "zod" 376 + ] 377 + }, 378 + "@atproto/syntax@0.4.0": { 379 + "integrity": "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA==" 380 + }, 381 + "@atproto/xrpc-server@0.7.15": { 382 + "integrity": "sha512-9MjhQk3iaIL391j5dD2/lS908yCCwvbGken2wtZoLubSluCKTli2G53NXlfmGcPLEC5IN5iM1+BaUUzfV3Wt5g==", 383 + "dependencies": [ 384 + "@atproto/common", 385 + "@atproto/crypto", 386 + "@atproto/lexicon", 387 + "@atproto/xrpc", 388 + "cbor-x", 389 + "express", 390 + "http-errors", 391 + "mime-types", 392 + "rate-limiter-flexible", 393 + "uint8arrays", 394 + "ws", 395 + "zod" 396 + ] 397 + }, 398 + "@atproto/xrpc@0.6.12": { 399 + "integrity": "sha512-Ut3iISNLujlmY9Gu8sNU+SPDJDvqlVzWddU8qUr0Yae5oD4SguaUFjjhireMGhQ3M5E0KljQgDbTmnBo1kIZ3w==", 400 + "dependencies": [ 401 + "@atproto/lexicon", 402 + "zod" 403 + ] 404 + }, 405 + "@cbor-extract/cbor-extract-darwin-arm64@2.2.0": { 406 + "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==" 407 + }, 408 + "@cbor-extract/cbor-extract-darwin-x64@2.2.0": { 409 + "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==" 410 + }, 411 + "@cbor-extract/cbor-extract-linux-arm64@2.2.0": { 412 + "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==" 413 + }, 414 + "@cbor-extract/cbor-extract-linux-arm@2.2.0": { 415 + "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==" 416 + }, 417 + "@cbor-extract/cbor-extract-linux-x64@2.2.0": { 418 + "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==" 419 + }, 420 + "@cbor-extract/cbor-extract-win32-x64@2.2.0": { 421 + "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==" 422 + }, 423 + "@emnapi/core@1.4.1": { 424 + "integrity": "sha512-4JFstCTaToCFrPqrGzgkF8N2NHjtsaY4uRh6brZQ5L9e4wbMieX8oDT8N7qfVFTQecHFEtkj4ve49VIZ3mKVqw==", 425 + "dependencies": [ 426 + "@emnapi/wasi-threads", 427 + "tslib@2.8.1" 428 + ] 429 + }, 430 + "@emnapi/runtime@1.4.1": { 431 + "integrity": "sha512-LMshMVP0ZhACNjQNYXiU1iZJ6QCcv0lUdPDPugqGvCGXt5xtRVBPdtA0qU12pEXZzpWAhWlZYptfdAFq10DOVQ==", 432 + "dependencies": [ 433 + "tslib@2.8.1" 434 + ] 435 + }, 436 + "@emnapi/wasi-threads@1.0.1": { 437 + "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", 438 + "dependencies": [ 439 + "tslib@2.8.1" 440 + ] 441 + }, 442 + "@img/sharp-darwin-arm64@0.34.1": { 443 + "integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==", 444 + "dependencies": [ 445 + "@img/sharp-libvips-darwin-arm64" 446 + ] 447 + }, 448 + "@img/sharp-darwin-x64@0.34.1": { 449 + "integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==", 450 + "dependencies": [ 451 + "@img/sharp-libvips-darwin-x64" 452 + ] 453 + }, 454 + "@img/sharp-libvips-darwin-arm64@1.1.0": { 455 + "integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==" 456 + }, 457 + "@img/sharp-libvips-darwin-x64@1.1.0": { 458 + "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==" 459 + }, 460 + "@img/sharp-libvips-linux-arm64@1.1.0": { 461 + "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==" 462 + }, 463 + "@img/sharp-libvips-linux-arm@1.1.0": { 464 + "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==" 465 + }, 466 + "@img/sharp-libvips-linux-ppc64@1.1.0": { 467 + "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==" 468 + }, 469 + "@img/sharp-libvips-linux-s390x@1.1.0": { 470 + "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==" 471 + }, 472 + "@img/sharp-libvips-linux-x64@1.1.0": { 473 + "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==" 474 + }, 475 + "@img/sharp-libvips-linuxmusl-arm64@1.1.0": { 476 + "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==" 477 + }, 478 + "@img/sharp-libvips-linuxmusl-x64@1.1.0": { 479 + "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==" 480 + }, 481 + "@img/sharp-linux-arm64@0.34.1": { 482 + "integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==", 483 + "dependencies": [ 484 + "@img/sharp-libvips-linux-arm64" 485 + ] 486 + }, 487 + "@img/sharp-linux-arm@0.34.1": { 488 + "integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==", 489 + "dependencies": [ 490 + "@img/sharp-libvips-linux-arm" 491 + ] 492 + }, 493 + "@img/sharp-linux-s390x@0.34.1": { 494 + "integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==", 495 + "dependencies": [ 496 + "@img/sharp-libvips-linux-s390x" 497 + ] 498 + }, 499 + "@img/sharp-linux-x64@0.34.1": { 500 + "integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==", 501 + "dependencies": [ 502 + "@img/sharp-libvips-linux-x64" 503 + ] 504 + }, 505 + "@img/sharp-linuxmusl-arm64@0.34.1": { 506 + "integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==", 507 + "dependencies": [ 508 + "@img/sharp-libvips-linuxmusl-arm64" 509 + ] 510 + }, 511 + "@img/sharp-linuxmusl-x64@0.34.1": { 512 + "integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==", 513 + "dependencies": [ 514 + "@img/sharp-libvips-linuxmusl-x64" 515 + ] 516 + }, 517 + "@img/sharp-wasm32@0.34.1": { 518 + "integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==", 519 + "dependencies": [ 520 + "@emnapi/runtime" 521 + ] 522 + }, 523 + "@img/sharp-win32-ia32@0.34.1": { 524 + "integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==" 525 + }, 526 + "@img/sharp-win32-x64@0.34.1": { 527 + "integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==" 528 + }, 529 + "@ipld/dag-cbor@7.0.3": { 530 + "integrity": "sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA==", 531 + "dependencies": [ 532 + "cborg", 533 + "multiformats@9.9.0" 534 + ] 535 + }, 536 + "@napi-rs/wasm-runtime@0.2.8": { 537 + "integrity": "sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==", 538 + "dependencies": [ 539 + "@emnapi/core", 540 + "@emnapi/runtime", 541 + "@tybys/wasm-util" 542 + ] 543 + }, 544 + "@noble/curves@1.8.2": { 545 + "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", 546 + "dependencies": [ 547 + "@noble/hashes" 548 + ] 549 + }, 550 + "@noble/hashes@1.7.2": { 551 + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==" 552 + }, 553 + "@parcel/watcher-android-arm64@2.5.1": { 554 + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==" 555 + }, 556 + "@parcel/watcher-darwin-arm64@2.5.1": { 557 + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==" 558 + }, 559 + "@parcel/watcher-darwin-x64@2.5.1": { 560 + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==" 561 + }, 562 + "@parcel/watcher-freebsd-x64@2.5.1": { 563 + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==" 564 + }, 565 + "@parcel/watcher-linux-arm-glibc@2.5.1": { 566 + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==" 567 + }, 568 + "@parcel/watcher-linux-arm-musl@2.5.1": { 569 + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==" 570 + }, 571 + "@parcel/watcher-linux-arm64-glibc@2.5.1": { 572 + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==" 573 + }, 574 + "@parcel/watcher-linux-arm64-musl@2.5.1": { 575 + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==" 576 + }, 577 + "@parcel/watcher-linux-x64-glibc@2.5.1": { 578 + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==" 579 + }, 580 + "@parcel/watcher-linux-x64-musl@2.5.1": { 581 + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==" 582 + }, 583 + "@parcel/watcher-win32-arm64@2.5.1": { 584 + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==" 585 + }, 586 + "@parcel/watcher-win32-ia32@2.5.1": { 587 + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==" 588 + }, 589 + "@parcel/watcher-win32-x64@2.5.1": { 590 + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==" 591 + }, 592 + "@parcel/watcher@2.5.1": { 593 + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", 594 + "dependencies": [ 595 + "@parcel/watcher-android-arm64", 596 + "@parcel/watcher-darwin-arm64", 597 + "@parcel/watcher-darwin-x64", 598 + "@parcel/watcher-freebsd-x64", 599 + "@parcel/watcher-linux-arm-glibc", 600 + "@parcel/watcher-linux-arm-musl", 601 + "@parcel/watcher-linux-arm64-glibc", 602 + "@parcel/watcher-linux-arm64-musl", 603 + "@parcel/watcher-linux-x64-glibc", 604 + "@parcel/watcher-linux-x64-musl", 605 + "@parcel/watcher-win32-arm64", 606 + "@parcel/watcher-win32-ia32", 607 + "@parcel/watcher-win32-x64", 608 + "detect-libc@1.0.3", 609 + "is-glob", 610 + "micromatch", 611 + "node-addon-api" 612 + ] 613 + }, 614 + "@tailwindcss/cli@4.1.4": { 615 + "integrity": "sha512-gP05Qihh+cZ2FqD5fa0WJXx3KEk2YWUYv/RBKAyiOg0V4vYVDr/xlLc0sacpnVEXM45BVUR9U2hsESufYs6YTA==", 616 + "dependencies": [ 617 + "@parcel/watcher", 618 + "@tailwindcss/node", 619 + "@tailwindcss/oxide", 620 + "enhanced-resolve", 621 + "mri", 622 + "picocolors", 623 + "tailwindcss" 624 + ] 625 + }, 626 + "@tailwindcss/node@4.1.4": { 627 + "integrity": "sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==", 628 + "dependencies": [ 629 + "enhanced-resolve", 630 + "jiti", 631 + "lightningcss", 632 + "tailwindcss" 633 + ] 634 + }, 635 + "@tailwindcss/oxide-android-arm64@4.1.4": { 636 + "integrity": "sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA==" 637 + }, 638 + "@tailwindcss/oxide-darwin-arm64@4.1.4": { 639 + "integrity": "sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg==" 640 + }, 641 + "@tailwindcss/oxide-darwin-x64@4.1.4": { 642 + "integrity": "sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA==" 643 + }, 644 + "@tailwindcss/oxide-freebsd-x64@4.1.4": { 645 + "integrity": "sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA==" 646 + }, 647 + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4": { 648 + "integrity": "sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg==" 649 + }, 650 + "@tailwindcss/oxide-linux-arm64-gnu@4.1.4": { 651 + "integrity": "sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww==" 652 + }, 653 + "@tailwindcss/oxide-linux-arm64-musl@4.1.4": { 654 + "integrity": "sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==" 655 + }, 656 + "@tailwindcss/oxide-linux-x64-gnu@4.1.4": { 657 + "integrity": "sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==" 658 + }, 659 + "@tailwindcss/oxide-linux-x64-musl@4.1.4": { 660 + "integrity": "sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==" 661 + }, 662 + "@tailwindcss/oxide-wasm32-wasi@4.1.4": { 663 + "integrity": "sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==", 664 + "dependencies": [ 665 + "@emnapi/core", 666 + "@emnapi/runtime", 667 + "@emnapi/wasi-threads", 668 + "@napi-rs/wasm-runtime", 669 + "@tybys/wasm-util", 670 + "tslib@2.8.1" 671 + ] 672 + }, 673 + "@tailwindcss/oxide-win32-arm64-msvc@4.1.4": { 674 + "integrity": "sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng==" 675 + }, 676 + "@tailwindcss/oxide-win32-x64-msvc@4.1.4": { 677 + "integrity": "sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw==" 678 + }, 679 + "@tailwindcss/oxide@4.1.4": { 680 + "integrity": "sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==", 681 + "dependencies": [ 682 + "@tailwindcss/oxide-android-arm64", 683 + "@tailwindcss/oxide-darwin-arm64", 684 + "@tailwindcss/oxide-darwin-x64", 685 + "@tailwindcss/oxide-freebsd-x64", 686 + "@tailwindcss/oxide-linux-arm-gnueabihf", 687 + "@tailwindcss/oxide-linux-arm64-gnu", 688 + "@tailwindcss/oxide-linux-arm64-musl", 689 + "@tailwindcss/oxide-linux-x64-gnu", 690 + "@tailwindcss/oxide-linux-x64-musl", 691 + "@tailwindcss/oxide-wasm32-wasi", 692 + "@tailwindcss/oxide-win32-arm64-msvc", 693 + "@tailwindcss/oxide-win32-x64-msvc" 694 + ] 695 + }, 696 + "@ts-morph/common@0.25.0": { 697 + "integrity": "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==", 698 + "dependencies": [ 699 + "minimatch", 700 + "path-browserify", 701 + "tinyglobby" 702 + ] 703 + }, 704 + "@tybys/wasm-util@0.9.0": { 705 + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", 706 + "dependencies": [ 707 + "tslib@2.8.1" 708 + ] 709 + }, 710 + "@types/node@22.12.0": { 711 + "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", 712 + "dependencies": [ 713 + "undici-types" 714 + ] 715 + }, 716 + "abort-controller@3.0.0": { 717 + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 718 + "dependencies": [ 719 + "event-target-shim" 720 + ] 721 + }, 722 + "accepts@1.3.8": { 723 + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 724 + "dependencies": [ 725 + "mime-types", 726 + "negotiator" 727 + ] 728 + }, 729 + "ansi-styles@4.3.0": { 730 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 731 + "dependencies": [ 732 + "color-convert" 733 + ] 734 + }, 735 + "array-flatten@1.1.1": { 736 + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 737 + }, 738 + "atomic-sleep@1.0.0": { 739 + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" 740 + }, 741 + "await-lock@2.2.2": { 742 + "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==" 743 + }, 744 + "balanced-match@1.0.2": { 745 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 746 + }, 747 + "base64-js@1.5.1": { 748 + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 749 + }, 750 + "body-parser@1.20.3": { 751 + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 752 + "dependencies": [ 753 + "bytes", 754 + "content-type", 755 + "debug", 756 + "depd", 757 + "destroy", 758 + "http-errors", 759 + "iconv-lite", 760 + "on-finished", 761 + "qs", 762 + "raw-body", 763 + "type-is", 764 + "unpipe" 765 + ] 766 + }, 767 + "brace-expansion@2.0.1": { 768 + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 769 + "dependencies": [ 770 + "balanced-match" 771 + ] 772 + }, 773 + "braces@3.0.3": { 774 + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 775 + "dependencies": [ 776 + "fill-range" 777 + ] 778 + }, 779 + "buffer@6.0.3": { 780 + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", 781 + "dependencies": [ 782 + "base64-js", 783 + "ieee754" 784 + ] 785 + }, 786 + "bytes@3.1.2": { 787 + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 788 + }, 789 + "call-bind-apply-helpers@1.0.2": { 790 + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 791 + "dependencies": [ 792 + "es-errors", 793 + "function-bind" 794 + ] 795 + }, 796 + "call-bound@1.0.4": { 797 + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 798 + "dependencies": [ 799 + "call-bind-apply-helpers", 800 + "get-intrinsic" 801 + ] 802 + }, 803 + "cbor-extract@2.2.0": { 804 + "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", 805 + "dependencies": [ 806 + "@cbor-extract/cbor-extract-darwin-arm64", 807 + "@cbor-extract/cbor-extract-darwin-x64", 808 + "@cbor-extract/cbor-extract-linux-arm", 809 + "@cbor-extract/cbor-extract-linux-arm64", 810 + "@cbor-extract/cbor-extract-linux-x64", 811 + "@cbor-extract/cbor-extract-win32-x64", 812 + "node-gyp-build-optional-packages" 813 + ] 814 + }, 815 + "cbor-x@1.6.0": { 816 + "integrity": "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==", 817 + "dependencies": [ 818 + "cbor-extract" 819 + ] 820 + }, 821 + "cborg@1.10.2": { 822 + "integrity": "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==" 823 + }, 824 + "chalk@4.1.2": { 825 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 826 + "dependencies": [ 827 + "ansi-styles", 828 + "supports-color" 829 + ] 830 + }, 831 + "clsx@2.1.1": { 832 + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" 833 + }, 834 + "code-block-writer@13.0.3": { 835 + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==" 836 + }, 837 + "color-convert@2.0.1": { 838 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 839 + "dependencies": [ 840 + "color-name" 841 + ] 842 + }, 843 + "color-name@1.1.4": { 844 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 845 + }, 846 + "color-string@1.9.1": { 847 + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 848 + "dependencies": [ 849 + "color-name", 850 + "simple-swizzle" 851 + ] 852 + }, 853 + "color@4.2.3": { 854 + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", 855 + "dependencies": [ 856 + "color-convert", 857 + "color-string" 858 + ] 859 + }, 860 + "commander@9.5.0": { 861 + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" 862 + }, 863 + "content-disposition@0.5.4": { 864 + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 865 + "dependencies": [ 866 + "safe-buffer" 867 + ] 868 + }, 869 + "content-type@1.0.5": { 870 + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" 871 + }, 872 + "cookie-signature@1.0.6": { 873 + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 874 + }, 875 + "cookie@0.7.1": { 876 + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" 877 + }, 878 + "date-fns@4.1.0": { 879 + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==" 880 + }, 881 + "debug@2.6.9": { 882 + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 883 + "dependencies": [ 884 + "ms@2.0.0" 885 + ] 886 + }, 887 + "depd@2.0.0": { 888 + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 889 + }, 890 + "destroy@1.2.0": { 891 + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" 892 + }, 893 + "detect-libc@1.0.3": { 894 + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==" 895 + }, 896 + "detect-libc@2.0.3": { 897 + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==" 898 + }, 899 + "dunder-proto@1.0.1": { 900 + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 901 + "dependencies": [ 902 + "call-bind-apply-helpers", 903 + "es-errors", 904 + "gopd" 905 + ] 906 + }, 907 + "ee-first@1.1.1": { 908 + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 909 + }, 910 + "encodeurl@1.0.2": { 911 + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" 912 + }, 913 + "encodeurl@2.0.0": { 914 + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" 915 + }, 916 + "enhanced-resolve@5.18.1": { 917 + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", 918 + "dependencies": [ 919 + "graceful-fs", 920 + "tapable" 921 + ] 922 + }, 923 + "es-define-property@1.0.1": { 924 + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" 925 + }, 926 + "es-errors@1.3.0": { 927 + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" 928 + }, 929 + "es-object-atoms@1.1.1": { 930 + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 931 + "dependencies": [ 932 + "es-errors" 933 + ] 934 + }, 935 + "escape-html@1.0.3": { 936 + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 937 + }, 938 + "etag@1.8.1": { 939 + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" 940 + }, 941 + "event-target-shim@5.0.1": { 942 + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" 943 + }, 944 + "events@3.3.0": { 945 + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" 946 + }, 947 + "express@4.21.2": { 948 + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 949 + "dependencies": [ 950 + "accepts", 951 + "array-flatten", 952 + "body-parser", 953 + "content-disposition", 954 + "content-type", 955 + "cookie", 956 + "cookie-signature", 957 + "debug", 958 + "depd", 959 + "encodeurl@2.0.0", 960 + "escape-html", 961 + "etag", 962 + "finalhandler", 963 + "fresh", 964 + "http-errors", 965 + "merge-descriptors", 966 + "methods", 967 + "on-finished", 968 + "parseurl", 969 + "path-to-regexp", 970 + "proxy-addr", 971 + "qs", 972 + "range-parser", 973 + "safe-buffer", 974 + "send", 975 + "serve-static", 976 + "setprototypeof", 977 + "statuses", 978 + "type-is", 979 + "utils-merge", 980 + "vary" 981 + ] 982 + }, 983 + "fast-redact@3.5.0": { 984 + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==" 985 + }, 986 + "fdir@6.4.3_picomatch@4.0.2": { 987 + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", 988 + "dependencies": [ 989 + "picomatch@4.0.2" 990 + ] 991 + }, 992 + "fill-range@7.1.1": { 993 + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 994 + "dependencies": [ 995 + "to-regex-range" 996 + ] 997 + }, 998 + "finalhandler@1.3.1": { 999 + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 1000 + "dependencies": [ 1001 + "debug", 1002 + "encodeurl@2.0.0", 1003 + "escape-html", 1004 + "on-finished", 1005 + "parseurl", 1006 + "statuses", 1007 + "unpipe" 1008 + ] 1009 + }, 1010 + "forwarded@0.2.0": { 1011 + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 1012 + }, 1013 + "framesync@6.1.2": { 1014 + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", 1015 + "dependencies": [ 1016 + "tslib@2.4.0" 1017 + ] 1018 + }, 1019 + "fresh@0.5.2": { 1020 + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" 1021 + }, 1022 + "function-bind@1.1.2": { 1023 + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" 1024 + }, 1025 + "get-intrinsic@1.3.0": { 1026 + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 1027 + "dependencies": [ 1028 + "call-bind-apply-helpers", 1029 + "es-define-property", 1030 + "es-errors", 1031 + "es-object-atoms", 1032 + "function-bind", 1033 + "get-proto", 1034 + "gopd", 1035 + "has-symbols", 1036 + "hasown", 1037 + "math-intrinsics" 1038 + ] 1039 + }, 1040 + "get-proto@1.0.1": { 1041 + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 1042 + "dependencies": [ 1043 + "dunder-proto", 1044 + "es-object-atoms" 1045 + ] 1046 + }, 1047 + "gopd@1.2.0": { 1048 + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" 1049 + }, 1050 + "graceful-fs@4.2.11": { 1051 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" 1052 + }, 1053 + "graphemer@1.4.0": { 1054 + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" 1055 + }, 1056 + "has-flag@4.0.0": { 1057 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 1058 + }, 1059 + "has-symbols@1.1.0": { 1060 + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" 1061 + }, 1062 + "hasown@2.0.2": { 1063 + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1064 + "dependencies": [ 1065 + "function-bind" 1066 + ] 1067 + }, 1068 + "hey-listen@1.0.8": { 1069 + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" 1070 + }, 1071 + "http-errors@2.0.0": { 1072 + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1073 + "dependencies": [ 1074 + "depd", 1075 + "inherits", 1076 + "setprototypeof", 1077 + "statuses", 1078 + "toidentifier" 1079 + ] 1080 + }, 1081 + "iconv-lite@0.4.24": { 1082 + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1083 + "dependencies": [ 1084 + "safer-buffer" 1085 + ] 1086 + }, 1087 + "ieee754@1.2.1": { 1088 + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 1089 + }, 1090 + "inherits@2.0.4": { 1091 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1092 + }, 1093 + "ipaddr.js@1.9.1": { 1094 + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 1095 + }, 1096 + "ipaddr.js@2.2.0": { 1097 + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==" 1098 + }, 1099 + "is-arrayish@0.3.2": { 1100 + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 1101 + }, 1102 + "is-extglob@2.1.1": { 1103 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" 1104 + }, 1105 + "is-glob@4.0.3": { 1106 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1107 + "dependencies": [ 1108 + "is-extglob" 1109 + ] 1110 + }, 1111 + "is-number@7.0.0": { 1112 + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 1113 + }, 1114 + "iso-datestring-validator@2.2.2": { 1115 + "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==" 1116 + }, 1117 + "jiti@2.4.2": { 1118 + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==" 1119 + }, 1120 + "jose@5.9.6": { 1121 + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==" 1122 + }, 1123 + "lightningcss-darwin-arm64@1.29.2": { 1124 + "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==" 1125 + }, 1126 + "lightningcss-darwin-x64@1.29.2": { 1127 + "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==" 1128 + }, 1129 + "lightningcss-freebsd-x64@1.29.2": { 1130 + "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==" 1131 + }, 1132 + "lightningcss-linux-arm-gnueabihf@1.29.2": { 1133 + "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==" 1134 + }, 1135 + "lightningcss-linux-arm64-gnu@1.29.2": { 1136 + "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==" 1137 + }, 1138 + "lightningcss-linux-arm64-musl@1.29.2": { 1139 + "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==" 1140 + }, 1141 + "lightningcss-linux-x64-gnu@1.29.2": { 1142 + "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==" 1143 + }, 1144 + "lightningcss-linux-x64-musl@1.29.2": { 1145 + "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==" 1146 + }, 1147 + "lightningcss-win32-arm64-msvc@1.29.2": { 1148 + "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==" 1149 + }, 1150 + "lightningcss-win32-x64-msvc@1.29.2": { 1151 + "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==" 1152 + }, 1153 + "lightningcss@1.29.2": { 1154 + "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", 1155 + "dependencies": [ 1156 + "detect-libc@2.0.3", 1157 + "lightningcss-darwin-arm64", 1158 + "lightningcss-darwin-x64", 1159 + "lightningcss-freebsd-x64", 1160 + "lightningcss-linux-arm-gnueabihf", 1161 + "lightningcss-linux-arm64-gnu", 1162 + "lightningcss-linux-arm64-musl", 1163 + "lightningcss-linux-x64-gnu", 1164 + "lightningcss-linux-x64-musl", 1165 + "lightningcss-win32-arm64-msvc", 1166 + "lightningcss-win32-x64-msvc" 1167 + ] 1168 + }, 1169 + "lru-cache@10.4.3": { 1170 + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" 1171 + }, 1172 + "math-intrinsics@1.1.0": { 1173 + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" 1174 + }, 1175 + "media-typer@0.3.0": { 1176 + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" 1177 + }, 1178 + "merge-descriptors@1.0.3": { 1179 + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" 1180 + }, 1181 + "methods@1.1.2": { 1182 + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" 1183 + }, 1184 + "micromatch@4.0.8": { 1185 + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 1186 + "dependencies": [ 1187 + "braces", 1188 + "picomatch@2.3.1" 1189 + ] 1190 + }, 1191 + "mime-db@1.52.0": { 1192 + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 1193 + }, 1194 + "mime-types@2.1.35": { 1195 + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1196 + "dependencies": [ 1197 + "mime-db" 1198 + ] 1199 + }, 1200 + "mime@1.6.0": { 1201 + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 1202 + }, 1203 + "minimatch@9.0.5": { 1204 + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 1205 + "dependencies": [ 1206 + "brace-expansion" 1207 + ] 1208 + }, 1209 + "mri@1.2.0": { 1210 + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" 1211 + }, 1212 + "ms@2.0.0": { 1213 + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1214 + }, 1215 + "ms@2.1.3": { 1216 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1217 + }, 1218 + "multiformats@13.3.2": { 1219 + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==" 1220 + }, 1221 + "multiformats@9.9.0": { 1222 + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" 1223 + }, 1224 + "negotiator@0.6.3": { 1225 + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 1226 + }, 1227 + "node-addon-api@7.1.1": { 1228 + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==" 1229 + }, 1230 + "node-gyp-build-optional-packages@5.1.1": { 1231 + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", 1232 + "dependencies": [ 1233 + "detect-libc@2.0.3" 1234 + ] 1235 + }, 1236 + "object-inspect@1.13.4": { 1237 + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" 1238 + }, 1239 + "on-exit-leak-free@2.1.2": { 1240 + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==" 1241 + }, 1242 + "on-finished@2.4.1": { 1243 + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1244 + "dependencies": [ 1245 + "ee-first" 1246 + ] 1247 + }, 1248 + "parseurl@1.3.3": { 1249 + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 1250 + }, 1251 + "path-browserify@1.0.1": { 1252 + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" 1253 + }, 1254 + "path-to-regexp@0.1.12": { 1255 + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" 1256 + }, 1257 + "picocolors@1.1.1": { 1258 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" 1259 + }, 1260 + "picomatch@2.3.1": { 1261 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" 1262 + }, 1263 + "picomatch@4.0.2": { 1264 + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==" 1265 + }, 1266 + "pino-abstract-transport@1.2.0": { 1267 + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", 1268 + "dependencies": [ 1269 + "readable-stream", 1270 + "split2" 1271 + ] 1272 + }, 1273 + "pino-std-serializers@6.2.2": { 1274 + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" 1275 + }, 1276 + "pino@8.21.0": { 1277 + "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", 1278 + "dependencies": [ 1279 + "atomic-sleep", 1280 + "fast-redact", 1281 + "on-exit-leak-free", 1282 + "pino-abstract-transport", 1283 + "pino-std-serializers", 1284 + "process-warning", 1285 + "quick-format-unescaped", 1286 + "real-require", 1287 + "safe-stable-stringify", 1288 + "sonic-boom", 1289 + "thread-stream" 1290 + ] 1291 + }, 1292 + "popmotion@11.0.5": { 1293 + "integrity": "sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==", 1294 + "dependencies": [ 1295 + "framesync", 1296 + "hey-listen", 1297 + "style-value-types", 1298 + "tslib@2.4.0" 1299 + ] 1300 + }, 1301 + "preact-render-to-string@6.5.13_preact@10.26.5": { 1302 + "integrity": "sha512-iGPd+hKPMFKsfpR2vL4kJ6ZPcFIoWZEcBf0Dpm3zOpdVvj77aY8RlLiQji5OMrngEyaxGogeakTb54uS2FvA6w==", 1303 + "dependencies": [ 1304 + "preact" 1305 + ] 1306 + }, 1307 + "preact@10.26.5": { 1308 + "integrity": "sha512-fmpDkgfGU6JYux9teDWLhj9mKN55tyepwYbxHgQuIxbWQzgFg5vk7Mrrtfx7xRxq798ynkY4DDDxZr235Kk+4w==" 1309 + }, 1310 + "prettier@3.5.3": { 1311 + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==" 1312 + }, 1313 + "process-warning@3.0.0": { 1314 + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" 1315 + }, 1316 + "process@0.11.10": { 1317 + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" 1318 + }, 1319 + "proxy-addr@2.0.7": { 1320 + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1321 + "dependencies": [ 1322 + "forwarded", 1323 + "ipaddr.js@1.9.1" 1324 + ] 1325 + }, 1326 + "psl@1.15.0": { 1327 + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", 1328 + "dependencies": [ 1329 + "punycode" 1330 + ] 1331 + }, 1332 + "punycode@2.3.1": { 1333 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" 1334 + }, 1335 + "qs@6.13.0": { 1336 + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 1337 + "dependencies": [ 1338 + "side-channel" 1339 + ] 1340 + }, 1341 + "quick-format-unescaped@4.0.4": { 1342 + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" 1343 + }, 1344 + "range-parser@1.2.1": { 1345 + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 1346 + }, 1347 + "rate-limiter-flexible@2.4.2": { 1348 + "integrity": "sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw==" 1349 + }, 1350 + "raw-body@2.5.2": { 1351 + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 1352 + "dependencies": [ 1353 + "bytes", 1354 + "http-errors", 1355 + "iconv-lite", 1356 + "unpipe" 1357 + ] 1358 + }, 1359 + "readable-stream@4.7.0": { 1360 + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", 1361 + "dependencies": [ 1362 + "abort-controller", 1363 + "buffer", 1364 + "events", 1365 + "process", 1366 + "string_decoder" 1367 + ] 1368 + }, 1369 + "real-require@0.2.0": { 1370 + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" 1371 + }, 1372 + "safe-buffer@5.2.1": { 1373 + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1374 + }, 1375 + "safe-stable-stringify@2.5.0": { 1376 + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" 1377 + }, 1378 + "safer-buffer@2.1.2": { 1379 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1380 + }, 1381 + "semver@7.7.1": { 1382 + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" 1383 + }, 1384 + "send@0.19.0": { 1385 + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 1386 + "dependencies": [ 1387 + "debug", 1388 + "depd", 1389 + "destroy", 1390 + "encodeurl@1.0.2", 1391 + "escape-html", 1392 + "etag", 1393 + "fresh", 1394 + "http-errors", 1395 + "mime", 1396 + "ms@2.1.3", 1397 + "on-finished", 1398 + "range-parser", 1399 + "statuses" 1400 + ] 1401 + }, 1402 + "serve-static@1.16.2": { 1403 + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 1404 + "dependencies": [ 1405 + "encodeurl@2.0.0", 1406 + "escape-html", 1407 + "parseurl", 1408 + "send" 1409 + ] 1410 + }, 1411 + "setprototypeof@1.2.0": { 1412 + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1413 + }, 1414 + "sharp@0.34.1": { 1415 + "integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==", 1416 + "dependencies": [ 1417 + "@img/sharp-darwin-arm64", 1418 + "@img/sharp-darwin-x64", 1419 + "@img/sharp-libvips-darwin-arm64", 1420 + "@img/sharp-libvips-darwin-x64", 1421 + "@img/sharp-libvips-linux-arm", 1422 + "@img/sharp-libvips-linux-arm64", 1423 + "@img/sharp-libvips-linux-ppc64", 1424 + "@img/sharp-libvips-linux-s390x", 1425 + "@img/sharp-libvips-linux-x64", 1426 + "@img/sharp-libvips-linuxmusl-arm64", 1427 + "@img/sharp-libvips-linuxmusl-x64", 1428 + "@img/sharp-linux-arm", 1429 + "@img/sharp-linux-arm64", 1430 + "@img/sharp-linux-s390x", 1431 + "@img/sharp-linux-x64", 1432 + "@img/sharp-linuxmusl-arm64", 1433 + "@img/sharp-linuxmusl-x64", 1434 + "@img/sharp-wasm32", 1435 + "@img/sharp-win32-ia32", 1436 + "@img/sharp-win32-x64", 1437 + "color", 1438 + "detect-libc@2.0.3", 1439 + "semver" 1440 + ] 1441 + }, 1442 + "side-channel-list@1.0.0": { 1443 + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1444 + "dependencies": [ 1445 + "es-errors", 1446 + "object-inspect" 1447 + ] 1448 + }, 1449 + "side-channel-map@1.0.1": { 1450 + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1451 + "dependencies": [ 1452 + "call-bound", 1453 + "es-errors", 1454 + "get-intrinsic", 1455 + "object-inspect" 1456 + ] 1457 + }, 1458 + "side-channel-weakmap@1.0.2": { 1459 + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1460 + "dependencies": [ 1461 + "call-bound", 1462 + "es-errors", 1463 + "get-intrinsic", 1464 + "object-inspect", 1465 + "side-channel-map" 1466 + ] 1467 + }, 1468 + "side-channel@1.1.0": { 1469 + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1470 + "dependencies": [ 1471 + "es-errors", 1472 + "object-inspect", 1473 + "side-channel-list", 1474 + "side-channel-map", 1475 + "side-channel-weakmap" 1476 + ] 1477 + }, 1478 + "simple-swizzle@0.2.2": { 1479 + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 1480 + "dependencies": [ 1481 + "is-arrayish" 1482 + ] 1483 + }, 1484 + "sonic-boom@3.8.1": { 1485 + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", 1486 + "dependencies": [ 1487 + "atomic-sleep" 1488 + ] 1489 + }, 1490 + "split2@4.2.0": { 1491 + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" 1492 + }, 1493 + "statuses@2.0.1": { 1494 + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 1495 + }, 1496 + "string_decoder@1.3.0": { 1497 + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1498 + "dependencies": [ 1499 + "safe-buffer" 1500 + ] 1501 + }, 1502 + "style-value-types@5.1.2": { 1503 + "integrity": "sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==", 1504 + "dependencies": [ 1505 + "hey-listen", 1506 + "tslib@2.4.0" 1507 + ] 1508 + }, 1509 + "supports-color@7.2.0": { 1510 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1511 + "dependencies": [ 1512 + "has-flag" 1513 + ] 1514 + }, 1515 + "tailwind-merge@3.2.0": { 1516 + "integrity": "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==" 1517 + }, 1518 + "tailwindcss@4.1.4": { 1519 + "integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==" 1520 + }, 1521 + "tapable@2.2.1": { 1522 + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" 1523 + }, 1524 + "thread-stream@2.7.0": { 1525 + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", 1526 + "dependencies": [ 1527 + "real-require" 1528 + ] 1529 + }, 1530 + "tinyglobby@0.2.12_picomatch@4.0.2": { 1531 + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", 1532 + "dependencies": [ 1533 + "fdir", 1534 + "picomatch@4.0.2" 1535 + ] 1536 + }, 1537 + "tlds@1.256.0": { 1538 + "integrity": "sha512-ZmyVB9DAw+FFTmLElGYJgdZFsKLYd/I59Bg9NHkCGPwAbVZNRilFWDMAdX8UG+bHuv7kfursd5XGqo/9wi26lA==" 1539 + }, 1540 + "to-regex-range@5.0.1": { 1541 + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1542 + "dependencies": [ 1543 + "is-number" 1544 + ] 1545 + }, 1546 + "toidentifier@1.0.1": { 1547 + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 1548 + }, 1549 + "ts-morph@24.0.0": { 1550 + "integrity": "sha512-2OAOg/Ob5yx9Et7ZX4CvTCc0UFoZHwLEJ+dpDPSUi5TgwwlTlX47w+iFRrEwzUZwYACjq83cgjS/Da50Ga37uw==", 1551 + "dependencies": [ 1552 + "@ts-morph/common", 1553 + "code-block-writer" 1554 + ] 1555 + }, 1556 + "tslib@2.4.0": { 1557 + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" 1558 + }, 1559 + "tslib@2.8.1": { 1560 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" 1561 + }, 1562 + "type-is@1.6.18": { 1563 + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1564 + "dependencies": [ 1565 + "media-typer", 1566 + "mime-types" 1567 + ] 1568 + }, 1569 + "typed-html@3.0.1": { 1570 + "integrity": "sha512-JKCM9zTfPDuPqQqdGZBWSEiItShliKkBFg5c6yOR8zth43v763XkAzTWaOlVqc0Y6p9ee8AaAbipGfUnCsYZUA==" 1571 + }, 1572 + "typed-htmx@0.3.1": { 1573 + "integrity": "sha512-6WSPsukTIOEMsVbx5wzgVSvldLmgBUVcFIm2vJlBpRPtcbDOGC5y1IYrCWNX1yUlNsrv1Ngcw4gGM8jsPyNV7w==", 1574 + "dependencies": [ 1575 + "typed-html" 1576 + ] 1577 + }, 1578 + "uint8arrays@3.0.0": { 1579 + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", 1580 + "dependencies": [ 1581 + "multiformats@9.9.0" 1582 + ] 1583 + }, 1584 + "undici-types@6.20.0": { 1585 + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" 1586 + }, 1587 + "undici@6.21.2": { 1588 + "integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==" 1589 + }, 1590 + "unpipe@1.0.0": { 1591 + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" 1592 + }, 1593 + "utils-merge@1.0.1": { 1594 + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" 1595 + }, 1596 + "vary@1.1.2": { 1597 + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" 1598 + }, 1599 + "ws@8.18.1": { 1600 + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==" 1601 + }, 1602 + "yesno@0.4.0": { 1603 + "integrity": "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==" 1604 + }, 1605 + "zod@3.24.2": { 1606 + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==" 1607 + } 1608 + }, 1609 + "workspace": { 1610 + "dependencies": [ 1611 + "jsr:@bigmoves/bff@0.3.0-beta.5", 1612 + "jsr:@gfx/canvas@~0.5.8", 1613 + "npm:@atproto/syntax@0.4", 1614 + "npm:@tailwindcss/cli@^4.1.4", 1615 + "npm:date-fns@^4.1.0", 1616 + "npm:popmotion@^11.0.5", 1617 + "npm:preact@^10.26.5", 1618 + "npm:tailwindcss@^4.1.4", 1619 + "npm:typed-htmx@~0.3.1" 1620 + ] 1621 + } 1622 + }
+46
fly.toml
··· 1 + # fly.toml app configuration file generated for atphoto on 2025-04-18T17:11:40-07:00 2 + # 3 + # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 + # 5 + 6 + app = 'atphoto' 7 + primary_region = 'sea' 8 + 9 + [build] 10 + dockerfile = './Dockerfile' 11 + 12 + [env] 13 + BFF_DATABASE_URL = '/litefs/sqlite.db' 14 + # BFF_QUEUE_DATABASE_URL = '/litefs/my.db' 15 + BFF_LEXICON_DIR = './__generated__' 16 + BFF_PORT = '8081' 17 + BFF_PUBLIC_URL = 'https://atphoto.fly.dev' 18 + 19 + [[mounts]] 20 + source = "litefs" 21 + destination = "/var/lib/litefs" 22 + 23 + [[services]] 24 + protocol = "tcp" 25 + internal_port = 8080 26 + processes = ["app"] 27 + 28 + [[services.ports]] 29 + port = 80 30 + handlers = ["http"] 31 + force_https = true 32 + 33 + [[services.ports]] 34 + port = 443 35 + handlers = ["tls", "http"] 36 + 37 + [[services.tcp_checks]] 38 + interval = "15s" 39 + timeout = "2s" 40 + grace_period = "1s" 41 + restart_limit = 0 42 + 43 + [[vm]] 44 + memory = '1gb' 45 + cpu_kind = 'shared' 46 + cpus = 1
+8
globals.d.ts
··· 1 + 2 + import "typed-htmx"; 3 + 4 + declare module "preact" { 5 + namespace JSX { 6 + interface HTMLAttributes extends HtmxAttributes {} 7 + } 8 + }
+12
input.css
··· 1 + @import "tailwindcss"; 2 + 3 + .htmx-request.htmx-indicator { 4 + display: inline; 5 + } 6 + .htmx-indicator { 7 + display: none; 8 + } 9 + .htmx-request #submit-button { 10 + opacity: 0.5; 11 + pointer-events: none; 12 + }
+8
lexicons.json
··· 1 + { 2 + "lexicons": [ 3 + "app.feed.post", 4 + "app.bsky.feed.post", 5 + "app.bsky.actor.profile", 6 + "app.bsky.actor.defs" 7 + ] 8 + }
+695
lexicons/app/bsky/actor/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.actor.defs", 4 + "defs": { 5 + "nux": { 6 + "type": "object", 7 + "required": [ 8 + "id", 9 + "completed" 10 + ], 11 + "properties": { 12 + "id": { 13 + "type": "string", 14 + "maxLength": 100 15 + }, 16 + "data": { 17 + "type": "string", 18 + "maxLength": 3000, 19 + "description": "Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters.", 20 + "maxGraphemes": 300 21 + }, 22 + "completed": { 23 + "type": "boolean", 24 + "default": false 25 + }, 26 + "expiresAt": { 27 + "type": "string", 28 + "format": "datetime", 29 + "description": "The date and time at which the NUX will expire and should be considered completed." 30 + } 31 + }, 32 + "description": "A new user experiences (NUX) storage object" 33 + }, 34 + "mutedWord": { 35 + "type": "object", 36 + "required": [ 37 + "value", 38 + "targets" 39 + ], 40 + "properties": { 41 + "id": { 42 + "type": "string" 43 + }, 44 + "value": { 45 + "type": "string", 46 + "maxLength": 10000, 47 + "description": "The muted word itself.", 48 + "maxGraphemes": 1000 49 + }, 50 + "targets": { 51 + "type": "array", 52 + "items": { 53 + "ref": "app.bsky.actor.defs#mutedWordTarget", 54 + "type": "ref" 55 + }, 56 + "description": "The intended targets of the muted word." 57 + }, 58 + "expiresAt": { 59 + "type": "string", 60 + "format": "datetime", 61 + "description": "The date and time at which the muted word will expire and no longer be applied." 62 + }, 63 + "actorTarget": { 64 + "type": "string", 65 + "default": "all", 66 + "description": "Groups of users to apply the muted word to. If undefined, applies to all users.", 67 + "knownValues": [ 68 + "all", 69 + "exclude-following" 70 + ] 71 + } 72 + }, 73 + "description": "A word that the account owner has muted." 74 + }, 75 + "savedFeed": { 76 + "type": "object", 77 + "required": [ 78 + "id", 79 + "type", 80 + "value", 81 + "pinned" 82 + ], 83 + "properties": { 84 + "id": { 85 + "type": "string" 86 + }, 87 + "type": { 88 + "type": "string", 89 + "knownValues": [ 90 + "feed", 91 + "list", 92 + "timeline" 93 + ] 94 + }, 95 + "value": { 96 + "type": "string" 97 + }, 98 + "pinned": { 99 + "type": "boolean" 100 + } 101 + } 102 + }, 103 + "preferences": { 104 + "type": "array", 105 + "items": { 106 + "refs": [ 107 + "#adultContentPref", 108 + "#contentLabelPref", 109 + "#savedFeedsPref", 110 + "#savedFeedsPrefV2", 111 + "#personalDetailsPref", 112 + "#feedViewPref", 113 + "#threadViewPref", 114 + "#interestsPref", 115 + "#mutedWordsPref", 116 + "#hiddenPostsPref", 117 + "#bskyAppStatePref", 118 + "#labelersPref", 119 + "#postInteractionSettingsPref" 120 + ], 121 + "type": "union" 122 + } 123 + }, 124 + "profileView": { 125 + "type": "object", 126 + "required": [ 127 + "did", 128 + "handle" 129 + ], 130 + "properties": { 131 + "did": { 132 + "type": "string", 133 + "format": "did" 134 + }, 135 + "avatar": { 136 + "type": "string", 137 + "format": "uri" 138 + }, 139 + "handle": { 140 + "type": "string", 141 + "format": "handle" 142 + }, 143 + "labels": { 144 + "type": "array", 145 + "items": { 146 + "ref": "com.atproto.label.defs#label", 147 + "type": "ref" 148 + } 149 + }, 150 + "viewer": { 151 + "ref": "#viewerState", 152 + "type": "ref" 153 + }, 154 + "createdAt": { 155 + "type": "string", 156 + "format": "datetime" 157 + }, 158 + "indexedAt": { 159 + "type": "string", 160 + "format": "datetime" 161 + }, 162 + "associated": { 163 + "ref": "#profileAssociated", 164 + "type": "ref" 165 + }, 166 + "description": { 167 + "type": "string", 168 + "maxLength": 2560, 169 + "maxGraphemes": 256 170 + }, 171 + "displayName": { 172 + "type": "string", 173 + "maxLength": 640, 174 + "maxGraphemes": 64 175 + } 176 + } 177 + }, 178 + "viewerState": { 179 + "type": "object", 180 + "properties": { 181 + "muted": { 182 + "type": "boolean" 183 + }, 184 + "blocking": { 185 + "type": "string", 186 + "format": "at-uri" 187 + }, 188 + "blockedBy": { 189 + "type": "boolean" 190 + }, 191 + "following": { 192 + "type": "string", 193 + "format": "at-uri" 194 + }, 195 + "followedBy": { 196 + "type": "string", 197 + "format": "at-uri" 198 + }, 199 + "mutedByList": { 200 + "ref": "app.bsky.graph.defs#listViewBasic", 201 + "type": "ref" 202 + }, 203 + "blockingByList": { 204 + "ref": "app.bsky.graph.defs#listViewBasic", 205 + "type": "ref" 206 + }, 207 + "knownFollowers": { 208 + "ref": "#knownFollowers", 209 + "type": "ref" 210 + } 211 + }, 212 + "description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests." 213 + }, 214 + "feedViewPref": { 215 + "type": "object", 216 + "required": [ 217 + "feed" 218 + ], 219 + "properties": { 220 + "feed": { 221 + "type": "string", 222 + "description": "The URI of the feed, or an identifier which describes the feed." 223 + }, 224 + "hideReplies": { 225 + "type": "boolean", 226 + "description": "Hide replies in the feed." 227 + }, 228 + "hideReposts": { 229 + "type": "boolean", 230 + "description": "Hide reposts in the feed." 231 + }, 232 + "hideQuotePosts": { 233 + "type": "boolean", 234 + "description": "Hide quote posts in the feed." 235 + }, 236 + "hideRepliesByLikeCount": { 237 + "type": "integer", 238 + "description": "Hide replies in the feed if they do not have this number of likes." 239 + }, 240 + "hideRepliesByUnfollowed": { 241 + "type": "boolean", 242 + "default": true, 243 + "description": "Hide replies in the feed if they are not by followed users." 244 + } 245 + } 246 + }, 247 + "labelersPref": { 248 + "type": "object", 249 + "required": [ 250 + "labelers" 251 + ], 252 + "properties": { 253 + "labelers": { 254 + "type": "array", 255 + "items": { 256 + "ref": "#labelerPrefItem", 257 + "type": "ref" 258 + } 259 + } 260 + } 261 + }, 262 + "interestsPref": { 263 + "type": "object", 264 + "required": [ 265 + "tags" 266 + ], 267 + "properties": { 268 + "tags": { 269 + "type": "array", 270 + "items": { 271 + "type": "string", 272 + "maxLength": 640, 273 + "maxGraphemes": 64 274 + }, 275 + "maxLength": 100, 276 + "description": "A list of tags which describe the account owner's interests gathered during onboarding." 277 + } 278 + } 279 + }, 280 + "knownFollowers": { 281 + "type": "object", 282 + "required": [ 283 + "count", 284 + "followers" 285 + ], 286 + "properties": { 287 + "count": { 288 + "type": "integer" 289 + }, 290 + "followers": { 291 + "type": "array", 292 + "items": { 293 + "ref": "#profileViewBasic", 294 + "type": "ref" 295 + }, 296 + "maxLength": 5, 297 + "minLength": 0 298 + } 299 + }, 300 + "description": "The subject's followers whom you also follow" 301 + }, 302 + "mutedWordsPref": { 303 + "type": "object", 304 + "required": [ 305 + "items" 306 + ], 307 + "properties": { 308 + "items": { 309 + "type": "array", 310 + "items": { 311 + "ref": "app.bsky.actor.defs#mutedWord", 312 + "type": "ref" 313 + }, 314 + "description": "A list of words the account owner has muted." 315 + } 316 + } 317 + }, 318 + "savedFeedsPref": { 319 + "type": "object", 320 + "required": [ 321 + "pinned", 322 + "saved" 323 + ], 324 + "properties": { 325 + "saved": { 326 + "type": "array", 327 + "items": { 328 + "type": "string", 329 + "format": "at-uri" 330 + } 331 + }, 332 + "pinned": { 333 + "type": "array", 334 + "items": { 335 + "type": "string", 336 + "format": "at-uri" 337 + } 338 + }, 339 + "timelineIndex": { 340 + "type": "integer" 341 + } 342 + } 343 + }, 344 + "threadViewPref": { 345 + "type": "object", 346 + "properties": { 347 + "sort": { 348 + "type": "string", 349 + "description": "Sorting mode for threads.", 350 + "knownValues": [ 351 + "oldest", 352 + "newest", 353 + "most-likes", 354 + "random", 355 + "hotness" 356 + ] 357 + }, 358 + "prioritizeFollowedUsers": { 359 + "type": "boolean", 360 + "description": "Show followed users at the top of all replies." 361 + } 362 + } 363 + }, 364 + "hiddenPostsPref": { 365 + "type": "object", 366 + "required": [ 367 + "items" 368 + ], 369 + "properties": { 370 + "items": { 371 + "type": "array", 372 + "items": { 373 + "type": "string", 374 + "format": "at-uri" 375 + }, 376 + "description": "A list of URIs of posts the account owner has hidden." 377 + } 378 + } 379 + }, 380 + "labelerPrefItem": { 381 + "type": "object", 382 + "required": [ 383 + "did" 384 + ], 385 + "properties": { 386 + "did": { 387 + "type": "string", 388 + "format": "did" 389 + } 390 + } 391 + }, 392 + "mutedWordTarget": { 393 + "type": "string", 394 + "maxLength": 640, 395 + "knownValues": [ 396 + "content", 397 + "tag" 398 + ], 399 + "maxGraphemes": 64 400 + }, 401 + "adultContentPref": { 402 + "type": "object", 403 + "required": [ 404 + "enabled" 405 + ], 406 + "properties": { 407 + "enabled": { 408 + "type": "boolean", 409 + "default": false 410 + } 411 + } 412 + }, 413 + "bskyAppStatePref": { 414 + "type": "object", 415 + "properties": { 416 + "nuxs": { 417 + "type": "array", 418 + "items": { 419 + "ref": "app.bsky.actor.defs#nux", 420 + "type": "ref" 421 + }, 422 + "maxLength": 100, 423 + "description": "Storage for NUXs the user has encountered." 424 + }, 425 + "queuedNudges": { 426 + "type": "array", 427 + "items": { 428 + "type": "string", 429 + "maxLength": 100 430 + }, 431 + "maxLength": 1000, 432 + "description": "An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user." 433 + }, 434 + "activeProgressGuide": { 435 + "ref": "#bskyAppProgressGuide", 436 + "type": "ref" 437 + } 438 + }, 439 + "description": "A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this." 440 + }, 441 + "contentLabelPref": { 442 + "type": "object", 443 + "required": [ 444 + "label", 445 + "visibility" 446 + ], 447 + "properties": { 448 + "label": { 449 + "type": "string" 450 + }, 451 + "labelerDid": { 452 + "type": "string", 453 + "format": "did", 454 + "description": "Which labeler does this preference apply to? If undefined, applies globally." 455 + }, 456 + "visibility": { 457 + "type": "string", 458 + "knownValues": [ 459 + "ignore", 460 + "show", 461 + "warn", 462 + "hide" 463 + ] 464 + } 465 + } 466 + }, 467 + "profileViewBasic": { 468 + "type": "object", 469 + "required": [ 470 + "did", 471 + "handle" 472 + ], 473 + "properties": { 474 + "did": { 475 + "type": "string", 476 + "format": "did" 477 + }, 478 + "avatar": { 479 + "type": "string", 480 + "format": "uri" 481 + }, 482 + "handle": { 483 + "type": "string", 484 + "format": "handle" 485 + }, 486 + "labels": { 487 + "type": "array", 488 + "items": { 489 + "ref": "com.atproto.label.defs#label", 490 + "type": "ref" 491 + } 492 + }, 493 + "viewer": { 494 + "ref": "#viewerState", 495 + "type": "ref" 496 + }, 497 + "createdAt": { 498 + "type": "string", 499 + "format": "datetime" 500 + }, 501 + "associated": { 502 + "ref": "#profileAssociated", 503 + "type": "ref" 504 + }, 505 + "displayName": { 506 + "type": "string", 507 + "maxLength": 640, 508 + "maxGraphemes": 64 509 + } 510 + } 511 + }, 512 + "savedFeedsPrefV2": { 513 + "type": "object", 514 + "required": [ 515 + "items" 516 + ], 517 + "properties": { 518 + "items": { 519 + "type": "array", 520 + "items": { 521 + "ref": "app.bsky.actor.defs#savedFeed", 522 + "type": "ref" 523 + } 524 + } 525 + } 526 + }, 527 + "profileAssociated": { 528 + "type": "object", 529 + "properties": { 530 + "chat": { 531 + "ref": "#profileAssociatedChat", 532 + "type": "ref" 533 + }, 534 + "lists": { 535 + "type": "integer" 536 + }, 537 + "labeler": { 538 + "type": "boolean" 539 + }, 540 + "feedgens": { 541 + "type": "integer" 542 + }, 543 + "starterPacks": { 544 + "type": "integer" 545 + } 546 + } 547 + }, 548 + "personalDetailsPref": { 549 + "type": "object", 550 + "properties": { 551 + "birthDate": { 552 + "type": "string", 553 + "format": "datetime", 554 + "description": "The birth date of account owner." 555 + } 556 + } 557 + }, 558 + "profileViewDetailed": { 559 + "type": "object", 560 + "required": [ 561 + "did", 562 + "handle" 563 + ], 564 + "properties": { 565 + "did": { 566 + "type": "string", 567 + "format": "did" 568 + }, 569 + "avatar": { 570 + "type": "string", 571 + "format": "uri" 572 + }, 573 + "banner": { 574 + "type": "string", 575 + "format": "uri" 576 + }, 577 + "handle": { 578 + "type": "string", 579 + "format": "handle" 580 + }, 581 + "labels": { 582 + "type": "array", 583 + "items": { 584 + "ref": "com.atproto.label.defs#label", 585 + "type": "ref" 586 + } 587 + }, 588 + "viewer": { 589 + "ref": "#viewerState", 590 + "type": "ref" 591 + }, 592 + "createdAt": { 593 + "type": "string", 594 + "format": "datetime" 595 + }, 596 + "indexedAt": { 597 + "type": "string", 598 + "format": "datetime" 599 + }, 600 + "associated": { 601 + "ref": "#profileAssociated", 602 + "type": "ref" 603 + }, 604 + "pinnedPost": { 605 + "ref": "com.atproto.repo.strongRef", 606 + "type": "ref" 607 + }, 608 + "postsCount": { 609 + "type": "integer" 610 + }, 611 + "description": { 612 + "type": "string", 613 + "maxLength": 2560, 614 + "maxGraphemes": 256 615 + }, 616 + "displayName": { 617 + "type": "string", 618 + "maxLength": 640, 619 + "maxGraphemes": 64 620 + }, 621 + "followsCount": { 622 + "type": "integer" 623 + }, 624 + "followersCount": { 625 + "type": "integer" 626 + }, 627 + "joinedViaStarterPack": { 628 + "ref": "app.bsky.graph.defs#starterPackViewBasic", 629 + "type": "ref" 630 + } 631 + } 632 + }, 633 + "bskyAppProgressGuide": { 634 + "type": "object", 635 + "required": [ 636 + "guide" 637 + ], 638 + "properties": { 639 + "guide": { 640 + "type": "string", 641 + "maxLength": 100 642 + } 643 + }, 644 + "description": "If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress." 645 + }, 646 + "profileAssociatedChat": { 647 + "type": "object", 648 + "required": [ 649 + "allowIncoming" 650 + ], 651 + "properties": { 652 + "allowIncoming": { 653 + "type": "string", 654 + "knownValues": [ 655 + "all", 656 + "none", 657 + "following" 658 + ] 659 + } 660 + } 661 + }, 662 + "postInteractionSettingsPref": { 663 + "type": "object", 664 + "required": [], 665 + "properties": { 666 + "threadgateAllowRules": { 667 + "type": "array", 668 + "items": { 669 + "refs": [ 670 + "app.bsky.feed.threadgate#mentionRule", 671 + "app.bsky.feed.threadgate#followerRule", 672 + "app.bsky.feed.threadgate#followingRule", 673 + "app.bsky.feed.threadgate#listRule" 674 + ], 675 + "type": "union" 676 + }, 677 + "maxLength": 5, 678 + "description": "Matches threadgate record. List of rules defining who can reply to this users posts. If value is an empty array, no one can reply. If value is undefined, anyone can reply." 679 + }, 680 + "postgateEmbeddingRules": { 681 + "type": "array", 682 + "items": { 683 + "refs": [ 684 + "app.bsky.feed.postgate#disableRule" 685 + ], 686 + "type": "union" 687 + }, 688 + "maxLength": 5, 689 + "description": "Matches postgate record. List of rules defining who can embed this users posts. If value is an empty array or is undefined, no particular rules apply and anyone can embed." 690 + } 691 + }, 692 + "description": "Default post interaction settings for the account. These values should be applied as default values when creating new posts. These refs should mirror the threadgate and postgate records exactly." 693 + } 694 + } 695 + }
+64
lexicons/app/bsky/actor/profile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.actor.profile", 4 + "defs": { 5 + "main": { 6 + "key": "literal:self", 7 + "type": "record", 8 + "record": { 9 + "type": "object", 10 + "properties": { 11 + "avatar": { 12 + "type": "blob", 13 + "accept": [ 14 + "image/png", 15 + "image/jpeg" 16 + ], 17 + "maxSize": 1000000, 18 + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'" 19 + }, 20 + "banner": { 21 + "type": "blob", 22 + "accept": [ 23 + "image/png", 24 + "image/jpeg" 25 + ], 26 + "maxSize": 1000000, 27 + "description": "Larger horizontal image to display behind profile view." 28 + }, 29 + "labels": { 30 + "refs": [ 31 + "com.atproto.label.defs#selfLabels" 32 + ], 33 + "type": "union", 34 + "description": "Self-label values, specific to the Bluesky application, on the overall account." 35 + }, 36 + "createdAt": { 37 + "type": "string", 38 + "format": "datetime" 39 + }, 40 + "pinnedPost": { 41 + "ref": "com.atproto.repo.strongRef", 42 + "type": "ref" 43 + }, 44 + "description": { 45 + "type": "string", 46 + "maxLength": 2560, 47 + "description": "Free-form profile description text.", 48 + "maxGraphemes": 256 49 + }, 50 + "displayName": { 51 + "type": "string", 52 + "maxLength": 640, 53 + "maxGraphemes": 64 54 + }, 55 + "joinedViaStarterPack": { 56 + "ref": "com.atproto.repo.strongRef", 57 + "type": "ref" 58 + } 59 + } 60 + }, 61 + "description": "A declaration of a Bluesky account profile." 62 + } 63 + } 64 + }
+24
lexicons/app/bsky/embed/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.defs", 4 + "defs": { 5 + "aspectRatio": { 6 + "type": "object", 7 + "required": [ 8 + "width", 9 + "height" 10 + ], 11 + "properties": { 12 + "width": { 13 + "type": "integer", 14 + "minimum": 1 15 + }, 16 + "height": { 17 + "type": "integer", 18 + "minimum": 1 19 + } 20 + }, 21 + "description": "width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit." 22 + } 23 + } 24 + }
+82
lexicons/app/bsky/embed/external.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.external", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "required": [ 8 + "external" 9 + ], 10 + "properties": { 11 + "external": { 12 + "ref": "#external", 13 + "type": "ref" 14 + } 15 + }, 16 + "description": "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post)." 17 + }, 18 + "view": { 19 + "type": "object", 20 + "required": [ 21 + "external" 22 + ], 23 + "properties": { 24 + "external": { 25 + "ref": "#viewExternal", 26 + "type": "ref" 27 + } 28 + } 29 + }, 30 + "external": { 31 + "type": "object", 32 + "required": [ 33 + "uri", 34 + "title", 35 + "description" 36 + ], 37 + "properties": { 38 + "uri": { 39 + "type": "string", 40 + "format": "uri" 41 + }, 42 + "thumb": { 43 + "type": "blob", 44 + "accept": [ 45 + "image/*" 46 + ], 47 + "maxSize": 1000000 48 + }, 49 + "title": { 50 + "type": "string" 51 + }, 52 + "description": { 53 + "type": "string" 54 + } 55 + } 56 + }, 57 + "viewExternal": { 58 + "type": "object", 59 + "required": [ 60 + "uri", 61 + "title", 62 + "description" 63 + ], 64 + "properties": { 65 + "uri": { 66 + "type": "string", 67 + "format": "uri" 68 + }, 69 + "thumb": { 70 + "type": "string", 71 + "format": "uri" 72 + }, 73 + "title": { 74 + "type": "string" 75 + }, 76 + "description": { 77 + "type": "string" 78 + } 79 + } 80 + } 81 + } 82 + }
+91
lexicons/app/bsky/embed/images.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.images", 4 + "description": "A set of images embedded in a Bluesky record (eg, a post).", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "images" 10 + ], 11 + "properties": { 12 + "images": { 13 + "type": "array", 14 + "items": { 15 + "ref": "#image", 16 + "type": "ref" 17 + }, 18 + "maxLength": 4 19 + } 20 + } 21 + }, 22 + "view": { 23 + "type": "object", 24 + "required": [ 25 + "images" 26 + ], 27 + "properties": { 28 + "images": { 29 + "type": "array", 30 + "items": { 31 + "ref": "#viewImage", 32 + "type": "ref" 33 + }, 34 + "maxLength": 4 35 + } 36 + } 37 + }, 38 + "image": { 39 + "type": "object", 40 + "required": [ 41 + "image", 42 + "alt" 43 + ], 44 + "properties": { 45 + "alt": { 46 + "type": "string", 47 + "description": "Alt text description of the image, for accessibility." 48 + }, 49 + "image": { 50 + "type": "blob", 51 + "accept": [ 52 + "image/*" 53 + ], 54 + "maxSize": 1000000 55 + }, 56 + "aspectRatio": { 57 + "ref": "app.bsky.embed.defs#aspectRatio", 58 + "type": "ref" 59 + } 60 + } 61 + }, 62 + "viewImage": { 63 + "type": "object", 64 + "required": [ 65 + "thumb", 66 + "fullsize", 67 + "alt" 68 + ], 69 + "properties": { 70 + "alt": { 71 + "type": "string", 72 + "description": "Alt text description of the image, for accessibility." 73 + }, 74 + "thumb": { 75 + "type": "string", 76 + "format": "uri", 77 + "description": "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View." 78 + }, 79 + "fullsize": { 80 + "type": "string", 81 + "format": "uri", 82 + "description": "Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View." 83 + }, 84 + "aspectRatio": { 85 + "ref": "app.bsky.embed.defs#aspectRatio", 86 + "type": "ref" 87 + } 88 + } 89 + } 90 + } 91 + }
+160
lexicons/app/bsky/embed/record.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.record", 4 + "description": "A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "record" 10 + ], 11 + "properties": { 12 + "record": { 13 + "ref": "com.atproto.repo.strongRef", 14 + "type": "ref" 15 + } 16 + } 17 + }, 18 + "view": { 19 + "type": "object", 20 + "required": [ 21 + "record" 22 + ], 23 + "properties": { 24 + "record": { 25 + "refs": [ 26 + "#viewRecord", 27 + "#viewNotFound", 28 + "#viewBlocked", 29 + "#viewDetached", 30 + "app.bsky.feed.defs#generatorView", 31 + "app.bsky.graph.defs#listView", 32 + "app.bsky.labeler.defs#labelerView", 33 + "app.bsky.graph.defs#starterPackViewBasic" 34 + ], 35 + "type": "union" 36 + } 37 + } 38 + }, 39 + "viewRecord": { 40 + "type": "object", 41 + "required": [ 42 + "uri", 43 + "cid", 44 + "author", 45 + "value", 46 + "indexedAt" 47 + ], 48 + "properties": { 49 + "cid": { 50 + "type": "string", 51 + "format": "cid" 52 + }, 53 + "uri": { 54 + "type": "string", 55 + "format": "at-uri" 56 + }, 57 + "value": { 58 + "type": "unknown", 59 + "description": "The record data itself." 60 + }, 61 + "author": { 62 + "ref": "app.bsky.actor.defs#profileViewBasic", 63 + "type": "ref" 64 + }, 65 + "embeds": { 66 + "type": "array", 67 + "items": { 68 + "refs": [ 69 + "app.bsky.embed.images#view", 70 + "app.bsky.embed.video#view", 71 + "app.bsky.embed.external#view", 72 + "app.bsky.embed.record#view", 73 + "app.bsky.embed.recordWithMedia#view" 74 + ], 75 + "type": "union" 76 + } 77 + }, 78 + "labels": { 79 + "type": "array", 80 + "items": { 81 + "ref": "com.atproto.label.defs#label", 82 + "type": "ref" 83 + } 84 + }, 85 + "indexedAt": { 86 + "type": "string", 87 + "format": "datetime" 88 + }, 89 + "likeCount": { 90 + "type": "integer" 91 + }, 92 + "quoteCount": { 93 + "type": "integer" 94 + }, 95 + "replyCount": { 96 + "type": "integer" 97 + }, 98 + "repostCount": { 99 + "type": "integer" 100 + } 101 + } 102 + }, 103 + "viewBlocked": { 104 + "type": "object", 105 + "required": [ 106 + "uri", 107 + "blocked", 108 + "author" 109 + ], 110 + "properties": { 111 + "uri": { 112 + "type": "string", 113 + "format": "at-uri" 114 + }, 115 + "author": { 116 + "ref": "app.bsky.feed.defs#blockedAuthor", 117 + "type": "ref" 118 + }, 119 + "blocked": { 120 + "type": "boolean", 121 + "const": true 122 + } 123 + } 124 + }, 125 + "viewDetached": { 126 + "type": "object", 127 + "required": [ 128 + "uri", 129 + "detached" 130 + ], 131 + "properties": { 132 + "uri": { 133 + "type": "string", 134 + "format": "at-uri" 135 + }, 136 + "detached": { 137 + "type": "boolean", 138 + "const": true 139 + } 140 + } 141 + }, 142 + "viewNotFound": { 143 + "type": "object", 144 + "required": [ 145 + "uri", 146 + "notFound" 147 + ], 148 + "properties": { 149 + "uri": { 150 + "type": "string", 151 + "format": "at-uri" 152 + }, 153 + "notFound": { 154 + "type": "boolean", 155 + "const": true 156 + } 157 + } 158 + } 159 + } 160 + }
+49
lexicons/app/bsky/embed/recordWithMedia.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.recordWithMedia", 4 + "description": "A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "record", 10 + "media" 11 + ], 12 + "properties": { 13 + "media": { 14 + "refs": [ 15 + "app.bsky.embed.images", 16 + "app.bsky.embed.video", 17 + "app.bsky.embed.external" 18 + ], 19 + "type": "union" 20 + }, 21 + "record": { 22 + "ref": "app.bsky.embed.record", 23 + "type": "ref" 24 + } 25 + } 26 + }, 27 + "view": { 28 + "type": "object", 29 + "required": [ 30 + "record", 31 + "media" 32 + ], 33 + "properties": { 34 + "media": { 35 + "refs": [ 36 + "app.bsky.embed.images#view", 37 + "app.bsky.embed.video#view", 38 + "app.bsky.embed.external#view" 39 + ], 40 + "type": "union" 41 + }, 42 + "record": { 43 + "ref": "app.bsky.embed.record#view", 44 + "type": "ref" 45 + } 46 + } 47 + } 48 + } 49 + }
+90
lexicons/app/bsky/embed/video.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.video", 4 + "description": "A video embedded in a Bluesky record (eg, a post).", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "video" 10 + ], 11 + "properties": { 12 + "alt": { 13 + "type": "string", 14 + "maxLength": 10000, 15 + "description": "Alt text description of the video, for accessibility.", 16 + "maxGraphemes": 1000 17 + }, 18 + "video": { 19 + "type": "blob", 20 + "accept": [ 21 + "video/mp4" 22 + ], 23 + "maxSize": 50000000 24 + }, 25 + "captions": { 26 + "type": "array", 27 + "items": { 28 + "ref": "#caption", 29 + "type": "ref" 30 + }, 31 + "maxLength": 20 32 + }, 33 + "aspectRatio": { 34 + "ref": "app.bsky.embed.defs#aspectRatio", 35 + "type": "ref" 36 + } 37 + } 38 + }, 39 + "view": { 40 + "type": "object", 41 + "required": [ 42 + "cid", 43 + "playlist" 44 + ], 45 + "properties": { 46 + "alt": { 47 + "type": "string", 48 + "maxLength": 10000, 49 + "maxGraphemes": 1000 50 + }, 51 + "cid": { 52 + "type": "string", 53 + "format": "cid" 54 + }, 55 + "playlist": { 56 + "type": "string", 57 + "format": "uri" 58 + }, 59 + "thumbnail": { 60 + "type": "string", 61 + "format": "uri" 62 + }, 63 + "aspectRatio": { 64 + "ref": "app.bsky.embed.defs#aspectRatio", 65 + "type": "ref" 66 + } 67 + } 68 + }, 69 + "caption": { 70 + "type": "object", 71 + "required": [ 72 + "lang", 73 + "file" 74 + ], 75 + "properties": { 76 + "file": { 77 + "type": "blob", 78 + "accept": [ 79 + "text/vtt" 80 + ], 81 + "maxSize": 20000 82 + }, 83 + "lang": { 84 + "type": "string", 85 + "format": "language" 86 + } 87 + } 88 + } 89 + } 90 + }
+515
lexicons/app/bsky/feed/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.feed.defs", 4 + "defs": { 5 + "postView": { 6 + "type": "object", 7 + "required": [ 8 + "uri", 9 + "cid", 10 + "author", 11 + "record", 12 + "indexedAt" 13 + ], 14 + "properties": { 15 + "cid": { 16 + "type": "string", 17 + "format": "cid" 18 + }, 19 + "uri": { 20 + "type": "string", 21 + "format": "at-uri" 22 + }, 23 + "embed": { 24 + "refs": [ 25 + "app.bsky.embed.images#view", 26 + "app.bsky.embed.video#view", 27 + "app.bsky.embed.external#view", 28 + "app.bsky.embed.record#view", 29 + "app.bsky.embed.recordWithMedia#view" 30 + ], 31 + "type": "union" 32 + }, 33 + "author": { 34 + "ref": "app.bsky.actor.defs#profileViewBasic", 35 + "type": "ref" 36 + }, 37 + "labels": { 38 + "type": "array", 39 + "items": { 40 + "ref": "com.atproto.label.defs#label", 41 + "type": "ref" 42 + } 43 + }, 44 + "record": { 45 + "type": "unknown" 46 + }, 47 + "viewer": { 48 + "ref": "#viewerState", 49 + "type": "ref" 50 + }, 51 + "indexedAt": { 52 + "type": "string", 53 + "format": "datetime" 54 + }, 55 + "likeCount": { 56 + "type": "integer" 57 + }, 58 + "quoteCount": { 59 + "type": "integer" 60 + }, 61 + "replyCount": { 62 + "type": "integer" 63 + }, 64 + "threadgate": { 65 + "ref": "#threadgateView", 66 + "type": "ref" 67 + }, 68 + "repostCount": { 69 + "type": "integer" 70 + } 71 + } 72 + }, 73 + "replyRef": { 74 + "type": "object", 75 + "required": [ 76 + "root", 77 + "parent" 78 + ], 79 + "properties": { 80 + "root": { 81 + "refs": [ 82 + "#postView", 83 + "#notFoundPost", 84 + "#blockedPost" 85 + ], 86 + "type": "union" 87 + }, 88 + "parent": { 89 + "refs": [ 90 + "#postView", 91 + "#notFoundPost", 92 + "#blockedPost" 93 + ], 94 + "type": "union" 95 + }, 96 + "grandparentAuthor": { 97 + "ref": "app.bsky.actor.defs#profileViewBasic", 98 + "type": "ref", 99 + "description": "When parent is a reply to another post, this is the author of that post." 100 + } 101 + } 102 + }, 103 + "reasonPin": { 104 + "type": "object", 105 + "properties": {} 106 + }, 107 + "blockedPost": { 108 + "type": "object", 109 + "required": [ 110 + "uri", 111 + "blocked", 112 + "author" 113 + ], 114 + "properties": { 115 + "uri": { 116 + "type": "string", 117 + "format": "at-uri" 118 + }, 119 + "author": { 120 + "ref": "#blockedAuthor", 121 + "type": "ref" 122 + }, 123 + "blocked": { 124 + "type": "boolean", 125 + "const": true 126 + } 127 + } 128 + }, 129 + "interaction": { 130 + "type": "object", 131 + "properties": { 132 + "item": { 133 + "type": "string", 134 + "format": "at-uri" 135 + }, 136 + "event": { 137 + "type": "string", 138 + "knownValues": [ 139 + "app.bsky.feed.defs#requestLess", 140 + "app.bsky.feed.defs#requestMore", 141 + "app.bsky.feed.defs#clickthroughItem", 142 + "app.bsky.feed.defs#clickthroughAuthor", 143 + "app.bsky.feed.defs#clickthroughReposter", 144 + "app.bsky.feed.defs#clickthroughEmbed", 145 + "app.bsky.feed.defs#interactionSeen", 146 + "app.bsky.feed.defs#interactionLike", 147 + "app.bsky.feed.defs#interactionRepost", 148 + "app.bsky.feed.defs#interactionReply", 149 + "app.bsky.feed.defs#interactionQuote", 150 + "app.bsky.feed.defs#interactionShare" 151 + ] 152 + }, 153 + "feedContext": { 154 + "type": "string", 155 + "maxLength": 2000, 156 + "description": "Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton." 157 + } 158 + } 159 + }, 160 + "requestLess": { 161 + "type": "token", 162 + "description": "Request that less content like the given feed item be shown in the feed" 163 + }, 164 + "requestMore": { 165 + "type": "token", 166 + "description": "Request that more content like the given feed item be shown in the feed" 167 + }, 168 + "viewerState": { 169 + "type": "object", 170 + "properties": { 171 + "like": { 172 + "type": "string", 173 + "format": "at-uri" 174 + }, 175 + "pinned": { 176 + "type": "boolean" 177 + }, 178 + "repost": { 179 + "type": "string", 180 + "format": "at-uri" 181 + }, 182 + "threadMuted": { 183 + "type": "boolean" 184 + }, 185 + "replyDisabled": { 186 + "type": "boolean" 187 + }, 188 + "embeddingDisabled": { 189 + "type": "boolean" 190 + } 191 + }, 192 + "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests." 193 + }, 194 + "feedViewPost": { 195 + "type": "object", 196 + "required": [ 197 + "post" 198 + ], 199 + "properties": { 200 + "post": { 201 + "ref": "#postView", 202 + "type": "ref" 203 + }, 204 + "reply": { 205 + "ref": "#replyRef", 206 + "type": "ref" 207 + }, 208 + "reason": { 209 + "refs": [ 210 + "#reasonRepost", 211 + "#reasonPin" 212 + ], 213 + "type": "union" 214 + }, 215 + "feedContext": { 216 + "type": "string", 217 + "maxLength": 2000, 218 + "description": "Context provided by feed generator that may be passed back alongside interactions." 219 + } 220 + } 221 + }, 222 + "notFoundPost": { 223 + "type": "object", 224 + "required": [ 225 + "uri", 226 + "notFound" 227 + ], 228 + "properties": { 229 + "uri": { 230 + "type": "string", 231 + "format": "at-uri" 232 + }, 233 + "notFound": { 234 + "type": "boolean", 235 + "const": true 236 + } 237 + } 238 + }, 239 + "reasonRepost": { 240 + "type": "object", 241 + "required": [ 242 + "by", 243 + "indexedAt" 244 + ], 245 + "properties": { 246 + "by": { 247 + "ref": "app.bsky.actor.defs#profileViewBasic", 248 + "type": "ref" 249 + }, 250 + "indexedAt": { 251 + "type": "string", 252 + "format": "datetime" 253 + } 254 + } 255 + }, 256 + "blockedAuthor": { 257 + "type": "object", 258 + "required": [ 259 + "did" 260 + ], 261 + "properties": { 262 + "did": { 263 + "type": "string", 264 + "format": "did" 265 + }, 266 + "viewer": { 267 + "ref": "app.bsky.actor.defs#viewerState", 268 + "type": "ref" 269 + } 270 + } 271 + }, 272 + "generatorView": { 273 + "type": "object", 274 + "required": [ 275 + "uri", 276 + "cid", 277 + "did", 278 + "creator", 279 + "displayName", 280 + "indexedAt" 281 + ], 282 + "properties": { 283 + "cid": { 284 + "type": "string", 285 + "format": "cid" 286 + }, 287 + "did": { 288 + "type": "string", 289 + "format": "did" 290 + }, 291 + "uri": { 292 + "type": "string", 293 + "format": "at-uri" 294 + }, 295 + "avatar": { 296 + "type": "string", 297 + "format": "uri" 298 + }, 299 + "labels": { 300 + "type": "array", 301 + "items": { 302 + "ref": "com.atproto.label.defs#label", 303 + "type": "ref" 304 + } 305 + }, 306 + "viewer": { 307 + "ref": "#generatorViewerState", 308 + "type": "ref" 309 + }, 310 + "creator": { 311 + "ref": "app.bsky.actor.defs#profileView", 312 + "type": "ref" 313 + }, 314 + "indexedAt": { 315 + "type": "string", 316 + "format": "datetime" 317 + }, 318 + "likeCount": { 319 + "type": "integer", 320 + "minimum": 0 321 + }, 322 + "contentMode": { 323 + "type": "string", 324 + "knownValues": [ 325 + "app.bsky.feed.defs#contentModeUnspecified", 326 + "app.bsky.feed.defs#contentModeVideo" 327 + ] 328 + }, 329 + "description": { 330 + "type": "string", 331 + "maxLength": 3000, 332 + "maxGraphemes": 300 333 + }, 334 + "displayName": { 335 + "type": "string" 336 + }, 337 + "descriptionFacets": { 338 + "type": "array", 339 + "items": { 340 + "ref": "app.bsky.richtext.facet", 341 + "type": "ref" 342 + } 343 + }, 344 + "acceptsInteractions": { 345 + "type": "boolean" 346 + } 347 + } 348 + }, 349 + "threadContext": { 350 + "type": "object", 351 + "properties": { 352 + "rootAuthorLike": { 353 + "type": "string", 354 + "format": "at-uri" 355 + } 356 + }, 357 + "description": "Metadata about this post within the context of the thread it is in." 358 + }, 359 + "threadViewPost": { 360 + "type": "object", 361 + "required": [ 362 + "post" 363 + ], 364 + "properties": { 365 + "post": { 366 + "ref": "#postView", 367 + "type": "ref" 368 + }, 369 + "parent": { 370 + "refs": [ 371 + "#threadViewPost", 372 + "#notFoundPost", 373 + "#blockedPost" 374 + ], 375 + "type": "union" 376 + }, 377 + "replies": { 378 + "type": "array", 379 + "items": { 380 + "refs": [ 381 + "#threadViewPost", 382 + "#notFoundPost", 383 + "#blockedPost" 384 + ], 385 + "type": "union" 386 + } 387 + }, 388 + "threadContext": { 389 + "ref": "#threadContext", 390 + "type": "ref" 391 + } 392 + } 393 + }, 394 + "threadgateView": { 395 + "type": "object", 396 + "properties": { 397 + "cid": { 398 + "type": "string", 399 + "format": "cid" 400 + }, 401 + "uri": { 402 + "type": "string", 403 + "format": "at-uri" 404 + }, 405 + "lists": { 406 + "type": "array", 407 + "items": { 408 + "ref": "app.bsky.graph.defs#listViewBasic", 409 + "type": "ref" 410 + } 411 + }, 412 + "record": { 413 + "type": "unknown" 414 + } 415 + } 416 + }, 417 + "interactionLike": { 418 + "type": "token", 419 + "description": "User liked the feed item" 420 + }, 421 + "interactionSeen": { 422 + "type": "token", 423 + "description": "Feed item was seen by user" 424 + }, 425 + "clickthroughItem": { 426 + "type": "token", 427 + "description": "User clicked through to the feed item" 428 + }, 429 + "contentModeVideo": { 430 + "type": "token", 431 + "description": "Declares the feed generator returns posts containing app.bsky.embed.video embeds." 432 + }, 433 + "interactionQuote": { 434 + "type": "token", 435 + "description": "User quoted the feed item" 436 + }, 437 + "interactionReply": { 438 + "type": "token", 439 + "description": "User replied to the feed item" 440 + }, 441 + "interactionShare": { 442 + "type": "token", 443 + "description": "User shared the feed item" 444 + }, 445 + "skeletonFeedPost": { 446 + "type": "object", 447 + "required": [ 448 + "post" 449 + ], 450 + "properties": { 451 + "post": { 452 + "type": "string", 453 + "format": "at-uri" 454 + }, 455 + "reason": { 456 + "refs": [ 457 + "#skeletonReasonRepost", 458 + "#skeletonReasonPin" 459 + ], 460 + "type": "union" 461 + }, 462 + "feedContext": { 463 + "type": "string", 464 + "maxLength": 2000, 465 + "description": "Context that will be passed through to client and may be passed to feed generator back alongside interactions." 466 + } 467 + } 468 + }, 469 + "clickthroughEmbed": { 470 + "type": "token", 471 + "description": "User clicked through to the embedded content of the feed item" 472 + }, 473 + "interactionRepost": { 474 + "type": "token", 475 + "description": "User reposted the feed item" 476 + }, 477 + "skeletonReasonPin": { 478 + "type": "object", 479 + "properties": {} 480 + }, 481 + "clickthroughAuthor": { 482 + "type": "token", 483 + "description": "User clicked through to the author of the feed item" 484 + }, 485 + "clickthroughReposter": { 486 + "type": "token", 487 + "description": "User clicked through to the reposter of the feed item" 488 + }, 489 + "generatorViewerState": { 490 + "type": "object", 491 + "properties": { 492 + "like": { 493 + "type": "string", 494 + "format": "at-uri" 495 + } 496 + } 497 + }, 498 + "skeletonReasonRepost": { 499 + "type": "object", 500 + "required": [ 501 + "repost" 502 + ], 503 + "properties": { 504 + "repost": { 505 + "type": "string", 506 + "format": "at-uri" 507 + } 508 + } 509 + }, 510 + "contentModeUnspecified": { 511 + "type": "token", 512 + "description": "Declares the feed generator returns any types of posts." 513 + } 514 + } 515 + }
+54
lexicons/app/bsky/feed/postgate.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.feed.postgate", 4 + "defs": { 5 + "main": { 6 + "key": "tid", 7 + "type": "record", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "post", 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "post": { 16 + "type": "string", 17 + "format": "at-uri", 18 + "description": "Reference (AT-URI) to the post record." 19 + }, 20 + "createdAt": { 21 + "type": "string", 22 + "format": "datetime" 23 + }, 24 + "embeddingRules": { 25 + "type": "array", 26 + "items": { 27 + "refs": [ 28 + "#disableRule" 29 + ], 30 + "type": "union" 31 + }, 32 + "maxLength": 5, 33 + "description": "List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed." 34 + }, 35 + "detachedEmbeddingUris": { 36 + "type": "array", 37 + "items": { 38 + "type": "string", 39 + "format": "at-uri" 40 + }, 41 + "maxLength": 50, 42 + "description": "List of AT-URIs embedding this post that the author has detached from." 43 + } 44 + } 45 + }, 46 + "description": "Record defining interaction rules for a post. The record key (rkey) of the postgate record must match the record key of the post, and that record must be in the same repository." 47 + }, 48 + "disableRule": { 49 + "type": "object", 50 + "properties": {}, 51 + "description": "Disables embedding of this post." 52 + } 53 + } 54 + }
+80
lexicons/app/bsky/feed/threadgate.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.feed.threadgate", 4 + "defs": { 5 + "main": { 6 + "key": "tid", 7 + "type": "record", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "post", 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "post": { 16 + "type": "string", 17 + "format": "at-uri", 18 + "description": "Reference (AT-URI) to the post record." 19 + }, 20 + "allow": { 21 + "type": "array", 22 + "items": { 23 + "refs": [ 24 + "#mentionRule", 25 + "#followerRule", 26 + "#followingRule", 27 + "#listRule" 28 + ], 29 + "type": "union" 30 + }, 31 + "maxLength": 5, 32 + "description": "List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply." 33 + }, 34 + "createdAt": { 35 + "type": "string", 36 + "format": "datetime" 37 + }, 38 + "hiddenReplies": { 39 + "type": "array", 40 + "items": { 41 + "type": "string", 42 + "format": "at-uri" 43 + }, 44 + "maxLength": 50, 45 + "description": "List of hidden reply URIs." 46 + } 47 + } 48 + }, 49 + "description": "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository." 50 + }, 51 + "listRule": { 52 + "type": "object", 53 + "required": [ 54 + "list" 55 + ], 56 + "properties": { 57 + "list": { 58 + "type": "string", 59 + "format": "at-uri" 60 + } 61 + }, 62 + "description": "Allow replies from actors on a list." 63 + }, 64 + "mentionRule": { 65 + "type": "object", 66 + "properties": {}, 67 + "description": "Allow replies from actors mentioned in your post." 68 + }, 69 + "followerRule": { 70 + "type": "object", 71 + "properties": {}, 72 + "description": "Allow replies from actors who follow you." 73 + }, 74 + "followingRule": { 75 + "type": "object", 76 + "properties": {}, 77 + "description": "Allow replies from actors you follow." 78 + } 79 + } 80 + }
+332
lexicons/app/bsky/graph/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.graph.defs", 4 + "defs": { 5 + "modlist": { 6 + "type": "token", 7 + "description": "A list of actors to apply an aggregate moderation action (mute/block) on." 8 + }, 9 + "listView": { 10 + "type": "object", 11 + "required": [ 12 + "uri", 13 + "cid", 14 + "creator", 15 + "name", 16 + "purpose", 17 + "indexedAt" 18 + ], 19 + "properties": { 20 + "cid": { 21 + "type": "string", 22 + "format": "cid" 23 + }, 24 + "uri": { 25 + "type": "string", 26 + "format": "at-uri" 27 + }, 28 + "name": { 29 + "type": "string", 30 + "maxLength": 64, 31 + "minLength": 1 32 + }, 33 + "avatar": { 34 + "type": "string", 35 + "format": "uri" 36 + }, 37 + "labels": { 38 + "type": "array", 39 + "items": { 40 + "ref": "com.atproto.label.defs#label", 41 + "type": "ref" 42 + } 43 + }, 44 + "viewer": { 45 + "ref": "#listViewerState", 46 + "type": "ref" 47 + }, 48 + "creator": { 49 + "ref": "app.bsky.actor.defs#profileView", 50 + "type": "ref" 51 + }, 52 + "purpose": { 53 + "ref": "#listPurpose", 54 + "type": "ref" 55 + }, 56 + "indexedAt": { 57 + "type": "string", 58 + "format": "datetime" 59 + }, 60 + "description": { 61 + "type": "string", 62 + "maxLength": 3000, 63 + "maxGraphemes": 300 64 + }, 65 + "listItemCount": { 66 + "type": "integer", 67 + "minimum": 0 68 + }, 69 + "descriptionFacets": { 70 + "type": "array", 71 + "items": { 72 + "ref": "app.bsky.richtext.facet", 73 + "type": "ref" 74 + } 75 + } 76 + } 77 + }, 78 + "curatelist": { 79 + "type": "token", 80 + "description": "A list of actors used for curation purposes such as list feeds or interaction gating." 81 + }, 82 + "listPurpose": { 83 + "type": "string", 84 + "knownValues": [ 85 + "app.bsky.graph.defs#modlist", 86 + "app.bsky.graph.defs#curatelist", 87 + "app.bsky.graph.defs#referencelist" 88 + ] 89 + }, 90 + "listItemView": { 91 + "type": "object", 92 + "required": [ 93 + "uri", 94 + "subject" 95 + ], 96 + "properties": { 97 + "uri": { 98 + "type": "string", 99 + "format": "at-uri" 100 + }, 101 + "subject": { 102 + "ref": "app.bsky.actor.defs#profileView", 103 + "type": "ref" 104 + } 105 + } 106 + }, 107 + "relationship": { 108 + "type": "object", 109 + "required": [ 110 + "did" 111 + ], 112 + "properties": { 113 + "did": { 114 + "type": "string", 115 + "format": "did" 116 + }, 117 + "following": { 118 + "type": "string", 119 + "format": "at-uri", 120 + "description": "if the actor follows this DID, this is the AT-URI of the follow record" 121 + }, 122 + "followedBy": { 123 + "type": "string", 124 + "format": "at-uri", 125 + "description": "if the actor is followed by this DID, contains the AT-URI of the follow record" 126 + } 127 + }, 128 + "description": "lists the bi-directional graph relationships between one actor (not indicated in the object), and the target actors (the DID included in the object)" 129 + }, 130 + "listViewBasic": { 131 + "type": "object", 132 + "required": [ 133 + "uri", 134 + "cid", 135 + "name", 136 + "purpose" 137 + ], 138 + "properties": { 139 + "cid": { 140 + "type": "string", 141 + "format": "cid" 142 + }, 143 + "uri": { 144 + "type": "string", 145 + "format": "at-uri" 146 + }, 147 + "name": { 148 + "type": "string", 149 + "maxLength": 64, 150 + "minLength": 1 151 + }, 152 + "avatar": { 153 + "type": "string", 154 + "format": "uri" 155 + }, 156 + "labels": { 157 + "type": "array", 158 + "items": { 159 + "ref": "com.atproto.label.defs#label", 160 + "type": "ref" 161 + } 162 + }, 163 + "viewer": { 164 + "ref": "#listViewerState", 165 + "type": "ref" 166 + }, 167 + "purpose": { 168 + "ref": "#listPurpose", 169 + "type": "ref" 170 + }, 171 + "indexedAt": { 172 + "type": "string", 173 + "format": "datetime" 174 + }, 175 + "listItemCount": { 176 + "type": "integer", 177 + "minimum": 0 178 + } 179 + } 180 + }, 181 + "notFoundActor": { 182 + "type": "object", 183 + "required": [ 184 + "actor", 185 + "notFound" 186 + ], 187 + "properties": { 188 + "actor": { 189 + "type": "string", 190 + "format": "at-identifier" 191 + }, 192 + "notFound": { 193 + "type": "boolean", 194 + "const": true 195 + } 196 + }, 197 + "description": "indicates that a handle or DID could not be resolved" 198 + }, 199 + "referencelist": { 200 + "type": "token", 201 + "description": "A list of actors used for only for reference purposes such as within a starter pack." 202 + }, 203 + "listViewerState": { 204 + "type": "object", 205 + "properties": { 206 + "muted": { 207 + "type": "boolean" 208 + }, 209 + "blocked": { 210 + "type": "string", 211 + "format": "at-uri" 212 + } 213 + } 214 + }, 215 + "starterPackView": { 216 + "type": "object", 217 + "required": [ 218 + "uri", 219 + "cid", 220 + "record", 221 + "creator", 222 + "indexedAt" 223 + ], 224 + "properties": { 225 + "cid": { 226 + "type": "string", 227 + "format": "cid" 228 + }, 229 + "uri": { 230 + "type": "string", 231 + "format": "at-uri" 232 + }, 233 + "list": { 234 + "ref": "#listViewBasic", 235 + "type": "ref" 236 + }, 237 + "feeds": { 238 + "type": "array", 239 + "items": { 240 + "ref": "app.bsky.feed.defs#generatorView", 241 + "type": "ref" 242 + }, 243 + "maxLength": 3 244 + }, 245 + "labels": { 246 + "type": "array", 247 + "items": { 248 + "ref": "com.atproto.label.defs#label", 249 + "type": "ref" 250 + } 251 + }, 252 + "record": { 253 + "type": "unknown" 254 + }, 255 + "creator": { 256 + "ref": "app.bsky.actor.defs#profileViewBasic", 257 + "type": "ref" 258 + }, 259 + "indexedAt": { 260 + "type": "string", 261 + "format": "datetime" 262 + }, 263 + "joinedWeekCount": { 264 + "type": "integer", 265 + "minimum": 0 266 + }, 267 + "listItemsSample": { 268 + "type": "array", 269 + "items": { 270 + "ref": "#listItemView", 271 + "type": "ref" 272 + }, 273 + "maxLength": 12 274 + }, 275 + "joinedAllTimeCount": { 276 + "type": "integer", 277 + "minimum": 0 278 + } 279 + } 280 + }, 281 + "starterPackViewBasic": { 282 + "type": "object", 283 + "required": [ 284 + "uri", 285 + "cid", 286 + "record", 287 + "creator", 288 + "indexedAt" 289 + ], 290 + "properties": { 291 + "cid": { 292 + "type": "string", 293 + "format": "cid" 294 + }, 295 + "uri": { 296 + "type": "string", 297 + "format": "at-uri" 298 + }, 299 + "labels": { 300 + "type": "array", 301 + "items": { 302 + "ref": "com.atproto.label.defs#label", 303 + "type": "ref" 304 + } 305 + }, 306 + "record": { 307 + "type": "unknown" 308 + }, 309 + "creator": { 310 + "ref": "app.bsky.actor.defs#profileViewBasic", 311 + "type": "ref" 312 + }, 313 + "indexedAt": { 314 + "type": "string", 315 + "format": "datetime" 316 + }, 317 + "listItemCount": { 318 + "type": "integer", 319 + "minimum": 0 320 + }, 321 + "joinedWeekCount": { 322 + "type": "integer", 323 + "minimum": 0 324 + }, 325 + "joinedAllTimeCount": { 326 + "type": "integer", 327 + "minimum": 0 328 + } 329 + } 330 + } 331 + } 332 + }
+128
lexicons/app/bsky/labeler/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.labeler.defs", 4 + "defs": { 5 + "labelerView": { 6 + "type": "object", 7 + "required": [ 8 + "uri", 9 + "cid", 10 + "creator", 11 + "indexedAt" 12 + ], 13 + "properties": { 14 + "cid": { 15 + "type": "string", 16 + "format": "cid" 17 + }, 18 + "uri": { 19 + "type": "string", 20 + "format": "at-uri" 21 + }, 22 + "labels": { 23 + "type": "array", 24 + "items": { 25 + "ref": "com.atproto.label.defs#label", 26 + "type": "ref" 27 + } 28 + }, 29 + "viewer": { 30 + "ref": "#labelerViewerState", 31 + "type": "ref" 32 + }, 33 + "creator": { 34 + "ref": "app.bsky.actor.defs#profileView", 35 + "type": "ref" 36 + }, 37 + "indexedAt": { 38 + "type": "string", 39 + "format": "datetime" 40 + }, 41 + "likeCount": { 42 + "type": "integer", 43 + "minimum": 0 44 + } 45 + } 46 + }, 47 + "labelerPolicies": { 48 + "type": "object", 49 + "required": [ 50 + "labelValues" 51 + ], 52 + "properties": { 53 + "labelValues": { 54 + "type": "array", 55 + "items": { 56 + "ref": "com.atproto.label.defs#labelValue", 57 + "type": "ref" 58 + }, 59 + "description": "The label values which this labeler publishes. May include global or custom labels." 60 + }, 61 + "labelValueDefinitions": { 62 + "type": "array", 63 + "items": { 64 + "ref": "com.atproto.label.defs#labelValueDefinition", 65 + "type": "ref" 66 + }, 67 + "description": "Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler." 68 + } 69 + } 70 + }, 71 + "labelerViewerState": { 72 + "type": "object", 73 + "properties": { 74 + "like": { 75 + "type": "string", 76 + "format": "at-uri" 77 + } 78 + } 79 + }, 80 + "labelerViewDetailed": { 81 + "type": "object", 82 + "required": [ 83 + "uri", 84 + "cid", 85 + "creator", 86 + "policies", 87 + "indexedAt" 88 + ], 89 + "properties": { 90 + "cid": { 91 + "type": "string", 92 + "format": "cid" 93 + }, 94 + "uri": { 95 + "type": "string", 96 + "format": "at-uri" 97 + }, 98 + "labels": { 99 + "type": "array", 100 + "items": { 101 + "ref": "com.atproto.label.defs#label", 102 + "type": "ref" 103 + } 104 + }, 105 + "viewer": { 106 + "ref": "#labelerViewerState", 107 + "type": "ref" 108 + }, 109 + "creator": { 110 + "ref": "app.bsky.actor.defs#profileView", 111 + "type": "ref" 112 + }, 113 + "policies": { 114 + "ref": "app.bsky.labeler.defs#labelerPolicies", 115 + "type": "ref" 116 + }, 117 + "indexedAt": { 118 + "type": "string", 119 + "format": "datetime" 120 + }, 121 + "likeCount": { 122 + "type": "integer", 123 + "minimum": 0 124 + } 125 + } 126 + } 127 + } 128 + }
+89
lexicons/app/bsky/richtext/facet.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.richtext.facet", 4 + "defs": { 5 + "tag": { 6 + "type": "object", 7 + "required": [ 8 + "tag" 9 + ], 10 + "properties": { 11 + "tag": { 12 + "type": "string", 13 + "maxLength": 640, 14 + "maxGraphemes": 64 15 + } 16 + }, 17 + "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')." 18 + }, 19 + "link": { 20 + "type": "object", 21 + "required": [ 22 + "uri" 23 + ], 24 + "properties": { 25 + "uri": { 26 + "type": "string", 27 + "format": "uri" 28 + } 29 + }, 30 + "description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL." 31 + }, 32 + "main": { 33 + "type": "object", 34 + "required": [ 35 + "index", 36 + "features" 37 + ], 38 + "properties": { 39 + "index": { 40 + "ref": "#byteSlice", 41 + "type": "ref" 42 + }, 43 + "features": { 44 + "type": "array", 45 + "items": { 46 + "refs": [ 47 + "#mention", 48 + "#link", 49 + "#tag" 50 + ], 51 + "type": "union" 52 + } 53 + } 54 + }, 55 + "description": "Annotation of a sub-string within rich text." 56 + }, 57 + "mention": { 58 + "type": "object", 59 + "required": [ 60 + "did" 61 + ], 62 + "properties": { 63 + "did": { 64 + "type": "string", 65 + "format": "did" 66 + } 67 + }, 68 + "description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID." 69 + }, 70 + "byteSlice": { 71 + "type": "object", 72 + "required": [ 73 + "byteStart", 74 + "byteEnd" 75 + ], 76 + "properties": { 77 + "byteEnd": { 78 + "type": "integer", 79 + "minimum": 0 80 + }, 81 + "byteStart": { 82 + "type": "integer", 83 + "minimum": 0 84 + } 85 + }, 86 + "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." 87 + } 88 + } 89 + }
+192
lexicons/com/atproto/label/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.label.defs", 4 + "defs": { 5 + "label": { 6 + "type": "object", 7 + "required": [ 8 + "src", 9 + "uri", 10 + "val", 11 + "cts" 12 + ], 13 + "properties": { 14 + "cid": { 15 + "type": "string", 16 + "format": "cid", 17 + "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." 18 + }, 19 + "cts": { 20 + "type": "string", 21 + "format": "datetime", 22 + "description": "Timestamp when this label was created." 23 + }, 24 + "exp": { 25 + "type": "string", 26 + "format": "datetime", 27 + "description": "Timestamp at which this label expires (no longer applies)." 28 + }, 29 + "neg": { 30 + "type": "boolean", 31 + "description": "If true, this is a negation label, overwriting a previous label." 32 + }, 33 + "sig": { 34 + "type": "bytes", 35 + "description": "Signature of dag-cbor encoded label." 36 + }, 37 + "src": { 38 + "type": "string", 39 + "format": "did", 40 + "description": "DID of the actor who created this label." 41 + }, 42 + "uri": { 43 + "type": "string", 44 + "format": "uri", 45 + "description": "AT URI of the record, repository (account), or other resource that this label applies to." 46 + }, 47 + "val": { 48 + "type": "string", 49 + "maxLength": 128, 50 + "description": "The short string name of the value or type of this label." 51 + }, 52 + "ver": { 53 + "type": "integer", 54 + "description": "The AT Protocol version of the label object." 55 + } 56 + }, 57 + "description": "Metadata tag on an atproto resource (eg, repo or record)." 58 + }, 59 + "selfLabel": { 60 + "type": "object", 61 + "required": [ 62 + "val" 63 + ], 64 + "properties": { 65 + "val": { 66 + "type": "string", 67 + "maxLength": 128, 68 + "description": "The short string name of the value or type of this label." 69 + } 70 + }, 71 + "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel." 72 + }, 73 + "labelValue": { 74 + "type": "string", 75 + "knownValues": [ 76 + "!hide", 77 + "!no-promote", 78 + "!warn", 79 + "!no-unauthenticated", 80 + "dmca-violation", 81 + "doxxing", 82 + "porn", 83 + "sexual", 84 + "nudity", 85 + "nsfl", 86 + "gore" 87 + ] 88 + }, 89 + "selfLabels": { 90 + "type": "object", 91 + "required": [ 92 + "values" 93 + ], 94 + "properties": { 95 + "values": { 96 + "type": "array", 97 + "items": { 98 + "ref": "#selfLabel", 99 + "type": "ref" 100 + }, 101 + "maxLength": 10 102 + } 103 + }, 104 + "description": "Metadata tags on an atproto record, published by the author within the record." 105 + }, 106 + "labelValueDefinition": { 107 + "type": "object", 108 + "required": [ 109 + "identifier", 110 + "severity", 111 + "blurs", 112 + "locales" 113 + ], 114 + "properties": { 115 + "blurs": { 116 + "type": "string", 117 + "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.", 118 + "knownValues": [ 119 + "content", 120 + "media", 121 + "none" 122 + ] 123 + }, 124 + "locales": { 125 + "type": "array", 126 + "items": { 127 + "ref": "#labelValueDefinitionStrings", 128 + "type": "ref" 129 + } 130 + }, 131 + "severity": { 132 + "type": "string", 133 + "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 134 + "knownValues": [ 135 + "inform", 136 + "alert", 137 + "none" 138 + ] 139 + }, 140 + "adultOnly": { 141 + "type": "boolean", 142 + "description": "Does the user need to have adult content enabled in order to configure this label?" 143 + }, 144 + "identifier": { 145 + "type": "string", 146 + "maxLength": 100, 147 + "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 148 + "maxGraphemes": 100 149 + }, 150 + "defaultSetting": { 151 + "type": "string", 152 + "default": "warn", 153 + "description": "The default setting for this label.", 154 + "knownValues": [ 155 + "ignore", 156 + "warn", 157 + "hide" 158 + ] 159 + } 160 + }, 161 + "description": "Declares a label value and its expected interpretations and behaviors." 162 + }, 163 + "labelValueDefinitionStrings": { 164 + "type": "object", 165 + "required": [ 166 + "lang", 167 + "name", 168 + "description" 169 + ], 170 + "properties": { 171 + "lang": { 172 + "type": "string", 173 + "format": "language", 174 + "description": "The code of the language these strings are written in." 175 + }, 176 + "name": { 177 + "type": "string", 178 + "maxLength": 640, 179 + "description": "A short human-readable name for the label.", 180 + "maxGraphemes": 64 181 + }, 182 + "description": { 183 + "type": "string", 184 + "maxLength": 100000, 185 + "description": "A longer description of what the label means and why it might be applied.", 186 + "maxGraphemes": 10000 187 + } 188 + }, 189 + "description": "Strings which describe the label in the UI, localized into a specific language." 190 + } 191 + } 192 + }
+24
lexicons/com/atproto/repo/strongRef.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.strongRef", 4 + "description": "A URI with a content-hash fingerprint.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "uri", 10 + "cid" 11 + ], 12 + "properties": { 13 + "cid": { 14 + "type": "string", 15 + "format": "cid" 16 + }, 17 + "uri": { 18 + "type": "string", 19 + "format": "at-uri" 20 + } 21 + } 22 + } 23 + } 24 + }
+26
lexicons/social/grain/actor/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.actor.defs", 4 + "defs": { 5 + "profileView": { 6 + "type": "object", 7 + "required": ["did", "handle"], 8 + "properties": { 9 + "did": { "type": "string", "format": "did" }, 10 + "handle": { "type": "string", "format": "handle" }, 11 + "displayName": { 12 + "type": "string", 13 + "maxGraphemes": 64, 14 + "maxLength": 640 15 + }, 16 + "description": { 17 + "type": "string", 18 + "maxLength": 2560, 19 + "maxGraphemes": 256 20 + }, 21 + "avatar": { "type": "string", "format": "uri" }, 22 + "createdAt": { "type": "string", "format": "datetime" } 23 + } 24 + } 25 + } 26 + }
+34
lexicons/social/grain/actor/profile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.actor.profile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A declaration of a basic account profile.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "properties": { 12 + "displayName": { 13 + "type": "string", 14 + "maxGraphemes": 64, 15 + "maxLength": 640 16 + }, 17 + "description": { 18 + "type": "string", 19 + "description": "Free-form profile description text.", 20 + "maxGraphemes": 256, 21 + "maxLength": 2560 22 + }, 23 + "avatar": { 24 + "type": "blob", 25 + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'", 26 + "accept": ["image/png", "image/jpeg"], 27 + "maxSize": 1000000 28 + }, 29 + "createdAt": { "type": "string", "format": "datetime" } 30 + } 31 + } 32 + } 33 + } 34 + }
+80
lexicons/social/grain/gallery/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.defs", 4 + "defs": { 5 + "aspectRatio": { 6 + "type": "object", 7 + "description": "width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.", 8 + "required": ["width", "height"], 9 + "properties": { 10 + "width": { "type": "integer", "minimum": 1 }, 11 + "height": { "type": "integer", "minimum": 1 } 12 + } 13 + }, 14 + "galleryView": { 15 + "type": "object", 16 + "required": ["uri", "cid", "creator", "record", "indexedAt"], 17 + "properties": { 18 + "uri": { "type": "string", "format": "at-uri" }, 19 + "cid": { "type": "string", "format": "cid" }, 20 + "creator": { 21 + "type": "ref", 22 + "ref": "social.grain.actor.defs#profileView" 23 + }, 24 + "record": { "type": "unknown" }, 25 + "images": { 26 + "type": "array", 27 + "items": { "type": "ref", "ref": "#viewImage" } 28 + }, 29 + "indexedAt": { "type": "string", "format": "datetime" } 30 + } 31 + }, 32 + "image": { 33 + "type": "object", 34 + "required": ["image", "alt"], 35 + "properties": { 36 + "image": { 37 + "type": "blob", 38 + "accept": ["image/*"], 39 + "maxSize": 1000000 40 + }, 41 + "alt": { 42 + "type": "string", 43 + "description": "Alt text description of the image, for accessibility." 44 + }, 45 + "aspectRatio": { 46 + "type": "ref", 47 + "ref": "social.grain.gallery.defs#aspectRatio" 48 + } 49 + } 50 + }, 51 + "viewImage": { 52 + "type": "object", 53 + "required": ["cid", "thumb", "fullsize", "alt"], 54 + "properties": { 55 + "cid": { 56 + "type": "string", 57 + "format": "cid" 58 + }, 59 + "thumb": { 60 + "type": "string", 61 + "format": "uri", 62 + "description": "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View." 63 + }, 64 + "fullsize": { 65 + "type": "string", 66 + "format": "uri", 67 + "description": "Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View." 68 + }, 69 + "alt": { 70 + "type": "string", 71 + "description": "Alt text description of the image, for accessibility." 72 + }, 73 + "aspectRatio": { 74 + "type": "ref", 75 + "ref": "social.grain.gallery.defs#aspectRatio" 76 + } 77 + } 78 + } 79 + } 80 + }
+27
lexicons/social/grain/gallery/gallery.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": ["title", "createdAt"], 11 + "properties": { 12 + "title": { "type": "string", "maxLength": 100 }, 13 + "description": { "type": "string", "maxLength": 1000 }, 14 + "images": { 15 + "type": "array", 16 + "items": { 17 + "type": "ref", 18 + "ref": "social.grain.gallery.defs#image" 19 + }, 20 + "maxLength": 10 21 + }, 22 + "createdAt": { "type": "string", "format": "datetime" } 23 + } 24 + } 25 + } 26 + } 27 + }
+27
lexicons/social/grain/gallery/star.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.star", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "createdAt", 12 + "subject" 13 + ], 14 + "properties": { 15 + "createdAt": { 16 + "type": "string", 17 + "format": "datetime" 18 + }, 19 + "subject": { 20 + "type": "string", 21 + "format": "at-uri" 22 + } 23 + } 24 + } 25 + } 26 + } 27 + }
+49
litefs.yml
··· 1 + # The fuse section describes settings for the FUSE file system. This file system 2 + # is used as a thin layer between the SQLite client in your application and the 3 + # storage on disk. It intercepts disk writes to determine transaction boundaries 4 + # so that those transactions can be saved and shipped to replicas. 5 + fuse: 6 + dir: "/litefs" 7 + 8 + # The data section describes settings for the internal LiteFS storage. We'll 9 + # mount a volume to the data directory so it can be persisted across restarts. 10 + # However, this data should not be accessed directly by the user application. 11 + data: 12 + dir: "/var/lib/litefs" 13 + 14 + # This flag ensure that LiteFS continues to run if there is an issue on starup. 15 + # It makes it easy to ssh in and debug any issues you might be having rather 16 + # than continually restarting on initialization failure. 17 + exit-on-error: false 18 + 19 + # This section defines settings for the option HTTP proxy. 20 + # This proxy can handle primary forwarding & replica consistency 21 + # for applications that use a single SQLite database. 22 + proxy: 23 + addr: ":8080" 24 + target: "localhost:8081" 25 + db: "sqlite.db" 26 + passthrough: 27 + - "*.ico" 28 + - "*.png" 29 + 30 + # This section defines a list of commands to run after LiteFS has connected 31 + # and sync'd with the cluster. You can run multiple commands but LiteFS expects 32 + # the last command to be long-running (e.g. an application server). When the 33 + # last command exits, LiteFS is shut down. 34 + exec: 35 + - cmd: "deno run -A --unstable-kv --unstable-ffi main.tsx" 36 + 37 + # The lease section specifies how the cluster will be managed. We're using the 38 + # "consul" lease type so that our application can dynamically change the primary. 39 + # 40 + # These environment variables will be available in your Fly.io application. 41 + lease: 42 + type: "consul" 43 + advertise-url: "http://${HOSTNAME}.vm.${FLY_APP_NAME}.internal:20202" 44 + candidate: ${FLY_REGION == PRIMARY_REGION} 45 + promote: true 46 + 47 + consul: 48 + url: "${FLY_CONSUL_URL}" 49 + key: "litefs/${FLY_APP_NAME}-v1"
+1790
main.tsx
··· 1 + import { lexicons } from "$lexicon/lexicons.ts"; 2 + import { Record as BskyProfile } from "$lexicon/types/app/bsky/actor/profile.ts"; 3 + import { ProfileView } from "$lexicon/types/social/grain/v0/actor/defs.ts"; 4 + import { Record as Profile } from "$lexicon/types/social/grain/v0/actor/profile.ts"; 5 + import { Record as Gallery } from "$lexicon/types/social/grain/v0/gallery.ts"; 6 + import { 7 + GalleryView, 8 + Image as GalleryImage, 9 + ViewImage, 10 + } from "$lexicon/types/social/grain/v0/gallery/defs.ts"; 11 + import { Record as Star } from "$lexicon/types/social/grain/v0/gallery/star.ts"; 12 + import { Un$Typed } from "$lexicon/util.ts"; 13 + import { AtUri } from "@atproto/syntax"; 14 + import { 15 + bff, 16 + BffContext, 17 + BffMiddleware, 18 + CSS, 19 + JETSTREAM, 20 + oauth, 21 + OAUTH_ROUTES, 22 + onSignedInArgs, 23 + requireAuth, 24 + RootProps, 25 + route, 26 + RouteHandler, 27 + UnauthorizedError, 28 + WithBffMeta, 29 + } from "@bigmoves/bff"; 30 + import { 31 + Button, 32 + cn, 33 + Dialog, 34 + Input, 35 + Layout, 36 + Login, 37 + Meta, 38 + type MetaProps, 39 + Textarea, 40 + } from "@bigmoves/bff/components"; 41 + import { createCanvas, Image } from "@gfx/canvas"; 42 + import { formatDistanceStrict } from "date-fns"; 43 + import { wrap } from "popmotion"; 44 + import { ComponentChildren, JSX, VNode } from "preact"; 45 + 46 + bff({ 47 + appName: "Grain Social", 48 + collections: ["social.grain.gallery", "social.grain.actor.profile"], 49 + jetstreamUrl: JETSTREAM.WEST_1, 50 + lexicons, 51 + rootElement: Root, 52 + onError: (err) => { 53 + if (err instanceof UnauthorizedError) { 54 + const ctx = err.ctx; 55 + return ctx.redirect(OAUTH_ROUTES.loginPage); 56 + } 57 + return new Response("Internal Server Error", { 58 + status: 500, 59 + }); 60 + }, 61 + middlewares: [ 62 + (_req, ctx) => { 63 + if (ctx.currentUser) { 64 + const profile = getActorProfile(ctx.currentUser.did, ctx); 65 + if (profile) { 66 + ctx.state.profile = profile; 67 + return ctx.next(); 68 + } 69 + } 70 + return ctx.next(); 71 + }, 72 + oauth({ 73 + onSignedIn, 74 + LoginComponent: ({ error }) => ( 75 + <div 76 + id="login" 77 + class="flex justify-center items-center w-full h-full relative" 78 + style="background-image: url('https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:bcgltzqazw5tb6k2g3ttenbj/bafkreiewhwu3ro5dv7omedphb62db4koa7qtvyzfhiiypg3ru4tvuxkrjy@webp'); background-size: cover; background-position: center;" 79 + > 80 + <Login hx-target="#login" error={error} errorClass="text-white" /> 81 + <div class="absolute bottom-2 right-2 text-white text-sm"> 82 + Photo by{" "} 83 + <a href={profileLink("chadtmiller.com")}>@chadtmiller.com</a> 84 + </div> 85 + </div> 86 + ), 87 + }), 88 + route("/", (_req, _params, ctx) => { 89 + const items = getTimeline(ctx); 90 + return ctx.render(<Timeline items={items} />); 91 + }), 92 + route("/profile/:handle", (req, params, ctx) => { 93 + const url = new URL(req.url); 94 + const tab = url.searchParams.get("tab"); 95 + const handle = params.handle; 96 + const timelineItems = getActorTimeline(handle, ctx); 97 + const galleries = getActorGalleries(handle, ctx); 98 + const actor = ctx.indexService.getActorByHandle(handle); 99 + if (!actor) return ctx.next(); 100 + const profile = getActorProfile(actor.did, ctx); 101 + if (!profile) return ctx.next(); 102 + if (tab) { 103 + return ctx.html( 104 + <ProfilePage 105 + loggedInUserDid={ctx.currentUser?.did} 106 + timelineItems={timelineItems} 107 + profile={profile} 108 + selectedTab={tab} 109 + galleries={galleries} 110 + />, 111 + ); 112 + } 113 + return ctx.render( 114 + <ProfilePage 115 + loggedInUserDid={ctx.currentUser?.did} 116 + timelineItems={timelineItems} 117 + profile={profile} 118 + />, 119 + ); 120 + }), 121 + route("/profile/:handle/:rkey", (_req, params, ctx: BffContext<State>) => { 122 + const did = ctx.currentUser?.did; 123 + let stars: WithBffMeta<Star>[] = []; 124 + const handle = params.handle; 125 + const rkey = params.rkey; 126 + const gallery = getGallery(handle, rkey, ctx); 127 + if (did && gallery) { 128 + stars = getGalleryStars(gallery.uri, ctx); 129 + } 130 + if (!gallery) return ctx.next(); 131 + ctx.state.meta = getGalleryMeta(gallery); 132 + ctx.state.scripts = ["image_dialog.js"]; 133 + return ctx.render( 134 + <GalleryPage stars={stars} gallery={gallery} currentUserDid={did} />, 135 + ); 136 + }), 137 + route("/profile/:handle/:rkey/edit", (_req, params, ctx) => { 138 + requireAuth(ctx); 139 + const handle = params.handle; 140 + const rkey = params.rkey; 141 + const gallery = getGallery(handle, rkey, ctx); 142 + return ctx.render( 143 + <GalleryCreateEditPage userHandle={handle} gallery={gallery} />, 144 + ); 145 + }), 146 + route("/gallery/new", (_req, _params, ctx) => { 147 + requireAuth(ctx); 148 + return ctx.render( 149 + <GalleryCreateEditPage userHandle={ctx.currentUser.handle} />, 150 + ); 151 + }), 152 + route("/onboard", (_req, _params, ctx) => { 153 + requireAuth(ctx); 154 + return ctx.render( 155 + <div 156 + hx-get="/dialogs/profile" 157 + hx-trigger="load" 158 + hx-target="body" 159 + hx-swap="afterbegin" 160 + />, 161 + ); 162 + }), 163 + route("/dialogs/profile", (_req, _params, ctx: BffContext<State>) => { 164 + requireAuth(ctx); 165 + 166 + if (!ctx.state.profile) return ctx.next(); 167 + 168 + const profileRecord = ctx.indexService.getRecord<Profile>( 169 + `at://${ctx.currentUser.did}/social.grain.actor.profile/self`, 170 + ); 171 + 172 + if (!profileRecord) return ctx.next(); 173 + 174 + return ctx.html( 175 + <ProfileDialog 176 + profile={ctx.state.profile} 177 + avatarCid={profileRecord.avatar?.ref.toString()} 178 + />, 179 + ); 180 + }), 181 + route("/dialogs/avatar/:handle", (_req, params, ctx) => { 182 + const handle = params.handle; 183 + const actor = ctx.indexService.getActorByHandle(handle); 184 + if (!actor) return ctx.next(); 185 + const profile = getActorProfile(actor.did, ctx); 186 + if (!profile) return ctx.next(); 187 + return ctx.html(<AvatarDialog profile={profile} />); 188 + }), 189 + route("/dialogs/image", (req, _params, ctx) => { 190 + const url = new URL(req.url); 191 + const galleryUri = url.searchParams.get("galleryUri"); 192 + const imageCid = url.searchParams.get("imageCid"); 193 + if (!galleryUri || !imageCid) return ctx.next(); 194 + const atUri = new AtUri(galleryUri); 195 + const galleryDid = atUri.hostname; 196 + const galleryRkey = atUri.rkey; 197 + const gallery = getGallery(galleryDid, galleryRkey, ctx); 198 + if (!gallery?.images) return ctx.next(); 199 + const image = gallery?.images?.find((image) => { 200 + return image.cid === imageCid; 201 + }); 202 + const imageAtIndex = gallery.images.findIndex((image) => { 203 + return image.cid === imageCid; 204 + }); 205 + const next = wrap(0, gallery.images.length, imageAtIndex + 1); 206 + const prev = wrap(0, gallery.images.length, imageAtIndex - 1); 207 + if (!image) return ctx.next(); 208 + return ctx.html( 209 + <ImageDialog 210 + gallery={gallery} 211 + image={image} 212 + nextImage={gallery.images.at(next)} 213 + prevImage={gallery.images.at(prev)} 214 + />, 215 + ); 216 + }), 217 + route("/dialogs/image-alt", (req, _params, ctx) => { 218 + const url = new URL(req.url); 219 + const galleryUri = url.searchParams.get("galleryUri"); 220 + const imageCid = url.searchParams.get("imageCid"); 221 + if (!galleryUri || !imageCid) return ctx.next(); 222 + const atUri = new AtUri(galleryUri); 223 + const galleryDid = atUri.hostname; 224 + const galleryRkey = atUri.rkey; 225 + const gallery = getGallery(galleryDid, galleryRkey, ctx); 226 + const image = gallery?.images?.find((image) => { 227 + return image.cid === imageCid; 228 + }); 229 + if (!image || !gallery) return ctx.next(); 230 + return ctx.html( 231 + <ImageAltDialog galleryUri={gallery.uri} image={image} />, 232 + ); 233 + }), 234 + route("/actions/create-edit", ["POST"], async (req, _params, ctx) => { 235 + requireAuth(ctx); 236 + const formData = await req.formData(); 237 + const title = formData.get("title") as string; 238 + const description = formData.get("description") as string; 239 + const cids = formData.getAll("cids") as string[]; 240 + const url = new URL(req.url); 241 + const searchParams = new URLSearchParams(url.search); 242 + const uri = searchParams.get("uri"); 243 + const handle = ctx.currentUser?.handle; 244 + 245 + let images: GalleryImage[] = []; 246 + for (const cid of cids) { 247 + const blobMeta = ctx.blobMetaCache.get(cid); 248 + if (!blobMeta?.blobRef) { 249 + continue; 250 + } 251 + images.push({ 252 + image: blobMeta.blobRef, 253 + alt: "", 254 + aspectRatio: blobMeta.dimensions?.width && blobMeta.dimensions?.height 255 + ? { 256 + width: blobMeta.dimensions.width, 257 + height: blobMeta.dimensions.height, 258 + } 259 + : undefined, 260 + }); 261 + } 262 + 263 + if (uri) { 264 + const gallery = ctx.indexService.getRecord<WithBffMeta<Gallery>>(uri); 265 + if (!gallery) return ctx.next(); 266 + images = mergeUniqueImages(gallery.images, images, cids); 267 + const rkey = new AtUri(uri).rkey; 268 + try { 269 + await ctx.updateRecord<Gallery>("social.grain.gallery", rkey, { 270 + title, 271 + description, 272 + images, 273 + createdAt: gallery.createdAt, 274 + }); 275 + } catch (e) { 276 + console.error("Error updating record:", e); 277 + const errorMessage = e instanceof Error 278 + ? e.message 279 + : "Unknown error occurred"; 280 + return new Response(errorMessage, { status: 400 }); 281 + } 282 + return ctx.redirect(galleryLink(handle, rkey)); 283 + } 284 + 285 + const createdUri = await ctx.createRecord<Gallery>( 286 + "social.grain.gallery", 287 + { 288 + title, 289 + description, 290 + images, 291 + createdAt: new Date().toISOString(), 292 + }, 293 + ); 294 + return ctx.redirect(galleryLink(handle, new AtUri(createdUri).rkey)); 295 + }), 296 + route("/actions/delete", ["POST"], async (req, _params, ctx) => { 297 + requireAuth(ctx); 298 + const formData = await req.formData(); 299 + const uri = formData.get("uri") as string; 300 + await ctx.deleteRecord(uri); 301 + return ctx.redirect("/"); 302 + }), 303 + route("/actions/image-alt", ["POST"], async (req, _params, ctx) => { 304 + requireAuth(ctx); 305 + const formData = await req.formData(); 306 + const alt = formData.get("alt") as string; 307 + const cid = formData.get("cid") as string; 308 + const galleryUri = formData.get("galleryUri") as string; 309 + const gallery = ctx.indexService.getRecord<WithBffMeta<Gallery>>( 310 + galleryUri, 311 + ); 312 + if (!gallery) return ctx.next(); 313 + const images = gallery?.images?.map((image) => { 314 + if (image.image.ref.toString() === cid) { 315 + return { 316 + ...image, 317 + alt, 318 + }; 319 + } 320 + return image; 321 + }); 322 + const rkey = new AtUri(galleryUri).rkey; 323 + await ctx.updateRecord<Gallery>("social.grain.gallery", rkey, { 324 + title: gallery.title, 325 + description: gallery.description, 326 + images, 327 + createdAt: gallery.createdAt, 328 + }); 329 + return new Response(null, { status: 200 }); 330 + }), 331 + route("/actions/star", ["POST"], async (req, _params, ctx) => { 332 + requireAuth(ctx); 333 + const url = new URL(req.url); 334 + const searchParams = new URLSearchParams(url.search); 335 + const galleryUri = searchParams.get("galleryUri"); 336 + const starUri = searchParams.get("starUri") ?? undefined; 337 + if (!galleryUri) return ctx.next(); 338 + 339 + if (starUri) { 340 + await ctx.deleteRecord(starUri); 341 + const stars = getGalleryStars(galleryUri, ctx); 342 + return ctx.html( 343 + <StarButton 344 + currentUserDid={ctx.currentUser.did} 345 + stars={stars} 346 + galleryUri={galleryUri} 347 + />, 348 + ); 349 + } 350 + 351 + await ctx.createRecord<WithBffMeta<Star>>( 352 + "social.grain.gallery.star", 353 + { 354 + subject: galleryUri, 355 + createdAt: new Date().toISOString(), 356 + }, 357 + ); 358 + 359 + const stars = getGalleryStars(galleryUri, ctx); 360 + 361 + return ctx.html( 362 + <StarButton 363 + currentUserDid={ctx.currentUser.did} 364 + galleryUri={galleryUri} 365 + stars={stars} 366 + />, 367 + ); 368 + }), 369 + route("/actions/profile/update", ["POST"], async (req, _params, ctx) => { 370 + requireAuth(ctx); 371 + const formData = await req.formData(); 372 + const displayName = formData.get("displayName") as string; 373 + const description = formData.get("description") as string; 374 + const avatarCid = formData.get("avatarCid") as string; 375 + 376 + const record = ctx.indexService.getRecord<Profile>( 377 + `at://${ctx.currentUser.did}/social.grain.actor.profile/self`, 378 + ); 379 + 380 + if (!record) { 381 + return new Response("Profile record not found", { status: 404 }); 382 + } 383 + 384 + await ctx.updateRecord<Profile>("social.grain.actor.profile", "self", { 385 + displayName, 386 + description, 387 + avatar: ctx.blobMetaCache.get(avatarCid)?.blobRef ?? record.avatar, 388 + }); 389 + 390 + return ctx.redirect(`/profile/${ctx.currentUser.handle}`); 391 + }), 392 + ...imageUploadRoutes(), 393 + ...avatarUploadRoutes(), 394 + ], 395 + }); 396 + 397 + type State = { 398 + profile?: ProfileView; 399 + scripts?: string[]; 400 + meta?: MetaProps[]; 401 + }; 402 + 403 + function readFileAsDataURL(file: File): Promise<string> { 404 + return new Promise((resolve, reject) => { 405 + const reader = new FileReader(); 406 + reader.onload = (e) => resolve(e.target?.result as string); 407 + reader.onerror = (e) => reject(e); 408 + reader.readAsDataURL(file); 409 + }); 410 + } 411 + 412 + function createImageFromDataURL(dataURL: string): Promise<Image> { 413 + return new Promise((resolve) => { 414 + const img = new Image(); 415 + img.onload = () => resolve(img); 416 + img.src = dataURL; 417 + }); 418 + } 419 + 420 + async function compressImageForPreview(file: File): Promise<string> { 421 + const maxWidth = 500, 422 + maxHeight = 500, 423 + format = "jpeg"; 424 + 425 + // Create an image from the file 426 + const dataUrl = await readFileAsDataURL(file); 427 + const img = await createImageFromDataURL(dataUrl); 428 + 429 + // Create a canvas with reduced dimensions 430 + const canvas = createCanvas(img.width, img.height); 431 + let width = img.width; 432 + let height = img.height; 433 + 434 + // Calculate new dimensions while maintaining aspect ratio 435 + if (width > height) { 436 + if (width > maxWidth) { 437 + height = Math.round((height * maxWidth) / width); 438 + width = maxWidth; 439 + } 440 + } else { 441 + if (height > maxHeight) { 442 + width = Math.round((width * maxHeight) / height); 443 + height = maxHeight; 444 + } 445 + } 446 + 447 + canvas.width = width; 448 + canvas.height = height; 449 + 450 + // Draw and compress the image 451 + const ctx = canvas.getContext("2d"); 452 + if (!ctx) { 453 + throw new Error("Failed to get canvas context"); 454 + } 455 + ctx.drawImage(img, 0, 0, width, height); 456 + 457 + // Convert to compressed image data URL 458 + return canvas.toDataURL(format); 459 + } 460 + 461 + type TimelineItemType = "gallery" | "star"; 462 + 463 + interface TimelineItem { 464 + createdAt: string; 465 + itemType: TimelineItemType; 466 + itemUri: string; 467 + actor: Un$Typed<ProfileView>; 468 + gallery: GalleryView; 469 + } 470 + 471 + interface TimelineOptions { 472 + actorDid?: string; 473 + } 474 + 475 + function processGalleries( 476 + ctx: BffContext, 477 + options?: TimelineOptions, 478 + ): TimelineItem[] { 479 + const items: TimelineItem[] = []; 480 + 481 + const whereClause = options?.actorDid 482 + ? [{ field: "did", equals: options.actorDid }] 483 + : undefined; 484 + 485 + const { items: galleries } = ctx.indexService.getRecords< 486 + WithBffMeta<Gallery> 487 + >("social.grain.gallery", { 488 + orderBy: { field: "createdAt", direction: "desc" }, 489 + where: whereClause, 490 + }); 491 + 492 + for (const gallery of galleries) { 493 + const actor = ctx.indexService.getActor(gallery.did); 494 + if (!actor) continue; 495 + const profile = getActorProfile(actor.did, ctx); 496 + if (!profile) continue; 497 + 498 + const galleryView = galleryToView(gallery, profile); 499 + items.push({ 500 + itemType: "gallery", 501 + createdAt: gallery.createdAt, 502 + itemUri: galleryView.uri, 503 + actor: galleryView.creator, 504 + gallery: galleryView, 505 + }); 506 + } 507 + 508 + return items; 509 + } 510 + 511 + function processStars( 512 + ctx: BffContext, 513 + options?: TimelineOptions, 514 + ): TimelineItem[] { 515 + const items: TimelineItem[] = []; 516 + 517 + const whereClause = options?.actorDid 518 + ? [{ field: "did", equals: options.actorDid }] 519 + : undefined; 520 + 521 + const { items: stars } = ctx.indexService.getRecords<WithBffMeta<Star>>( 522 + "social.grain.gallery.star", 523 + { 524 + orderBy: { field: "createdAt", direction: "desc" }, 525 + where: whereClause, 526 + }, 527 + ); 528 + 529 + for (const star of stars) { 530 + if (!star.subject) continue; 531 + 532 + try { 533 + const atUri = new AtUri(star.subject); 534 + const galleryDid = atUri.hostname; 535 + const galleryRkey = atUri.rkey; 536 + 537 + const gallery = ctx.indexService.getRecord<WithBffMeta<Gallery>>( 538 + `at://${galleryDid}/social.grain.gallery/${galleryRkey}`, 539 + ); 540 + if (!gallery) continue; 541 + 542 + const galleryActor = ctx.indexService.getActor(galleryDid); 543 + if (!galleryActor) continue; 544 + const galleryProfile = getActorProfile(galleryActor.did, ctx); 545 + if (!galleryProfile) continue; 546 + 547 + const starActor = ctx.indexService.getActor(star.did); 548 + if (!starActor) continue; 549 + const starProfile = getActorProfile(starActor.did, ctx); 550 + if (!starProfile) continue; 551 + 552 + const galleryView = galleryToView(gallery, galleryProfile); 553 + items.push({ 554 + itemType: "star", 555 + createdAt: star.createdAt, 556 + itemUri: star.uri, 557 + actor: starProfile, 558 + gallery: galleryView, 559 + }); 560 + } catch (e) { 561 + console.error("Error processing star:", e); 562 + continue; 563 + } 564 + } 565 + 566 + return items; 567 + } 568 + 569 + function getTimelineItems( 570 + ctx: BffContext, 571 + options?: TimelineOptions, 572 + ): TimelineItem[] { 573 + const galleryItems = processGalleries(ctx, options); 574 + const starItems = processStars(ctx, options); 575 + const timelineItems = [...galleryItems, ...starItems]; 576 + 577 + return timelineItems.sort( 578 + (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), 579 + ); 580 + } 581 + 582 + function getTimeline(ctx: BffContext): TimelineItem[] { 583 + return getTimelineItems(ctx); 584 + } 585 + 586 + function getActorTimeline(handleOrDid: string, ctx: BffContext) { 587 + let did: string; 588 + if (handleOrDid.includes("did:")) { 589 + did = handleOrDid; 590 + } else { 591 + const actor = ctx.indexService.getActorByHandle(handleOrDid); 592 + if (!actor) return []; 593 + did = actor.did; 594 + } 595 + return getTimelineItems(ctx, { actorDid: did }); 596 + } 597 + 598 + function getActorGalleries(handleOrDid: string, ctx: BffContext) { 599 + let did: string; 600 + if (handleOrDid.includes("did:")) { 601 + did = handleOrDid; 602 + } else { 603 + const actor = ctx.indexService.getActorByHandle(handleOrDid); 604 + if (!actor) return []; 605 + did = actor.did; 606 + } 607 + const galleries = ctx.indexService.getRecords<WithBffMeta<Gallery>>( 608 + "social.grain.gallery", 609 + { 610 + where: [{ field: "did", equals: did }], 611 + orderBy: { field: "createdAt", direction: "desc" }, 612 + }, 613 + ); 614 + const creator = getActorProfile(did, ctx); 615 + if (!creator) return []; 616 + return galleries.items.map((gallery) => galleryToView(gallery, creator)); 617 + } 618 + 619 + function getGallery(handleOrDid: string, rkey: string, ctx: BffContext) { 620 + let did: string; 621 + if (handleOrDid.includes("did:")) { 622 + did = handleOrDid; 623 + } else { 624 + const actor = ctx.indexService.getActorByHandle(handleOrDid); 625 + if (!actor) return null; 626 + did = actor.did; 627 + } 628 + const gallery = ctx.indexService.getRecord<WithBffMeta<Gallery>>( 629 + `at://${did}/social.grain.gallery/${rkey}`, 630 + ); 631 + if (!gallery) return null; 632 + const profile = getActorProfile(did, ctx); 633 + if (!profile) return null; 634 + return galleryToView(gallery, profile); 635 + } 636 + 637 + function getGalleryStars(galleryUri: string, ctx: BffContext) { 638 + const atUri = new AtUri(galleryUri); 639 + const results = ctx.indexService.getRecords<WithBffMeta<Star>>( 640 + "social.grain.gallery.star", 641 + { 642 + where: [ 643 + { 644 + field: "subject", 645 + equals: `at://${atUri.hostname}/social.grain.gallery/${atUri.rkey}`, 646 + }, 647 + ], 648 + }, 649 + ); 650 + return results.items; 651 + } 652 + 653 + function getGalleryMeta(gallery: GalleryView): MetaProps[] { 654 + return [ 655 + { property: "og:type", content: "website" }, 656 + { property: "og:site_name", content: "Atproto Image Gallery" }, 657 + { 658 + property: "og:url", 659 + content: `${ 660 + Deno.env.get("BFF_PUBLIC_URL") ?? "http://localhost:8080" 661 + }/profile/${gallery.creator.handle}/${new AtUri(gallery.uri).rkey}`, 662 + }, 663 + { property: "og:title", content: (gallery.record as Gallery).title }, 664 + { 665 + property: "og:description", 666 + content: (gallery.record as Gallery).description, 667 + }, 668 + { property: "og:image", content: gallery?.images?.[0].thumb }, 669 + ]; 670 + } 671 + 672 + function Root(props: Readonly<RootProps<State>>) { 673 + const profile = props.ctx.state.profile; 674 + const scripts = props.ctx.state.scripts; 675 + return ( 676 + <html lang="en" class="w-full h-full"> 677 + <head> 678 + <meta charset="UTF-8" /> 679 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 680 + <Meta meta={props.ctx.state.meta} /> 681 + <script src="https://unpkg.com/htmx.org@1.9.10" /> 682 + <script src="https://unpkg.com/hyperscript.org@0.9.14" /> 683 + <style dangerouslySetInnerHTML={{ __html: CSS }} /> 684 + <link rel="stylesheet" href="/static/styles.css" /> 685 + <link rel="preconnect" href="https://fonts.googleapis.com" /> 686 + <link 687 + rel="preconnect" 688 + href="https://fonts.gstatic.com" 689 + crossOrigin="anonymous" 690 + /> 691 + <link 692 + href="https://fonts.googleapis.com/css2?family=Jersey+20&display=swap" 693 + rel="stylesheet" 694 + > 695 + </link> 696 + <link 697 + rel="stylesheet" 698 + href="https://unpkg.com/@fortawesome/fontawesome-free@6.7.2/css/all.min.css" 699 + preload 700 + /> 701 + {scripts?.includes("image_dialog.js") 702 + ? <script src="/static/image_dialog.js" /> 703 + : null} 704 + </head> 705 + <body class="h-full w-full"> 706 + <Layout id="layout"> 707 + <Layout.Nav 708 + title={ 709 + <h1 class="font-['Jersey_20'] text-4xl text-gray-900"> 710 + grain 711 + <sub class="bottom-[0.75rem] text-[1rem]">beta</sub> 712 + </h1> 713 + } 714 + profile={profile} 715 + /> 716 + <Layout.Content>{props.children}</Layout.Content> 717 + </Layout> 718 + </body> 719 + </html> 720 + ); 721 + } 722 + 723 + function Header({ 724 + children, 725 + class: classProp, 726 + ...props 727 + }: Readonly< 728 + JSX.HTMLAttributes<HTMLHeadingElement> & { children: ComponentChildren } 729 + >) { 730 + return ( 731 + <h1 class={cn("text-xl font-semibold", classProp)} {...props}> 732 + {children} 733 + </h1> 734 + ); 735 + } 736 + 737 + function AvatarButton({ 738 + profile, 739 + }: Readonly<{ profile: Un$Typed<ProfileView> }>) { 740 + return ( 741 + <button 742 + type="button" 743 + class="cursor-pointer" 744 + hx-get={`/dialogs/avatar/${profile.handle}`} 745 + hx-trigger="click" 746 + hx-target="body" 747 + hx-swap="afterbegin" 748 + > 749 + <img 750 + src={profile.avatar} 751 + alt={profile.handle} 752 + class="rounded-full object-cover size-16" 753 + /> 754 + </button> 755 + ); 756 + } 757 + 758 + function AvatarDialog({ 759 + profile, 760 + }: Readonly<{ profile: Un$Typed<ProfileView> }>) { 761 + return ( 762 + <Dialog> 763 + <div 764 + class="w-[400px] h-[400px] flex flex-col p-4 z-10" 765 + _={Dialog._closeOnClick} 766 + > 767 + <img 768 + src={profile.avatar} 769 + alt={profile.handle} 770 + class="rounded-full w-full h-full object-cover" 771 + /> 772 + </div> 773 + </Dialog> 774 + ); 775 + } 776 + 777 + function Timeline({ items }: Readonly<{ items: TimelineItem[] }>) { 778 + return ( 779 + <div class="px-4 mb-4"> 780 + <div class="my-4"> 781 + <Header>Timeline</Header> 782 + </div> 783 + <ul class="space-y-4 relative"> 784 + {items.map((item) => <TimelineItem item={item} key={item.itemUri} />)} 785 + </ul> 786 + </div> 787 + ); 788 + } 789 + 790 + function TimelineItem({ item }: Readonly<{ item: TimelineItem }>) { 791 + return ( 792 + <li class="space-y-2"> 793 + <div class="bg-gray-100 w-fit p-2"> 794 + <a 795 + href={profileLink(item.actor.handle)} 796 + class="font-semibold hover:underline" 797 + > 798 + @{item.actor.handle} 799 + </a>{" "} 800 + {item.itemType === "star" ? "starred" : "created"}{" "} 801 + <a 802 + href={galleryLink( 803 + item.gallery.creator.handle, 804 + new AtUri(item.gallery.uri).rkey, 805 + )} 806 + class="font-semibold" 807 + > 808 + {(item.gallery.record as Gallery).title} 809 + </a> 810 + <span class="ml-1"> 811 + {formatDistanceStrict(item.createdAt, new Date(), { 812 + addSuffix: true, 813 + })} 814 + </span> 815 + </div> 816 + <a 817 + href={galleryLink( 818 + item.gallery.creator.handle, 819 + new AtUri(item.gallery.uri).rkey, 820 + )} 821 + class="w-fit flex" 822 + > 823 + {item.gallery.images?.length 824 + ? ( 825 + <div class="flex w-full max-w-md mx-auto aspect-[3/2] overflow-hidden gap-2"> 826 + <div class="w-2/3 h-full"> 827 + <img 828 + src={item.gallery.images[0].thumb} 829 + alt={item.gallery.images[0].alt} 830 + class="w-full h-full object-cover" 831 + /> 832 + </div> 833 + <div class="w-1/3 flex flex-col h-full gap-2"> 834 + <div class="h-1/2"> 835 + {item.gallery.images?.[1] 836 + ? ( 837 + <img 838 + src={item.gallery.images?.[1]?.thumb} 839 + alt={item.gallery.images?.[1]?.alt} 840 + class="w-full h-full object-cover" 841 + /> 842 + ) 843 + : <div className="w-full h-full bg-gray-200" />} 844 + </div> 845 + <div class="h-1/2"> 846 + {item.gallery.images?.[2] 847 + ? ( 848 + <img 849 + src={item.gallery.images?.[2]?.thumb} 850 + alt={item.gallery.images?.[2]?.alt} 851 + class="w-full h-full object-cover" 852 + /> 853 + ) 854 + : <div className="w-full h-full bg-gray-200" />} 855 + </div> 856 + </div> 857 + </div> 858 + ) 859 + : null} 860 + </a> 861 + </li> 862 + ); 863 + } 864 + 865 + function ProfilePage({ 866 + loggedInUserDid, 867 + timelineItems, 868 + profile, 869 + selectedTab, 870 + galleries, 871 + }: Readonly<{ 872 + loggedInUserDid?: string; 873 + timelineItems: TimelineItem[]; 874 + profile: Un$Typed<ProfileView>; 875 + selectedTab?: string; 876 + galleries?: GalleryView[]; 877 + }>) { 878 + return ( 879 + <div class="px-4 mb-4" id="profile-page"> 880 + <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between my-4"> 881 + <div class="flex flex-col"> 882 + <AvatarButton profile={profile} /> 883 + <p class="text-2xl font-bold">{profile.displayName}</p> 884 + <p class="text-gray-600">@{profile.handle}</p> 885 + <p class="my-2">{profile.description}</p> 886 + </div> 887 + {loggedInUserDid === profile.did 888 + ? ( 889 + <div class="flex self-start gap-2 w-full sm:w-fit flex-col sm:flex-row"> 890 + <Button 891 + variant="primary" 892 + type="button" 893 + hx-get="/dialogs/profile" 894 + hx-target="#layout" 895 + hx-swap="afterbegin" 896 + class="w-full sm:w-fit" 897 + > 898 + Edit Profile 899 + </Button> 900 + <Button variant="primary" class="w-full sm:w-fit" asChild> 901 + <a href="/gallery/new">Create Gallery</a> 902 + </Button> 903 + </div> 904 + ) 905 + : null} 906 + </div> 907 + <div class="my-4 space-x-2 w-full flex sm:w-fit" role="tablist"> 908 + <button 909 + type="button" 910 + hx-get={profileLink(profile.handle)} 911 + hx-target="body" 912 + hx-swap="outerHTML" 913 + class={cn( 914 + "flex-1 py-2 px-4 cursor-pointer font-semibold", 915 + !selectedTab && "bg-gray-100 font-semibold", 916 + )} 917 + role="tab" 918 + aria-selected="true" 919 + aria-controls="tab-content" 920 + > 921 + Activity 922 + </button> 923 + <button 924 + type="button" 925 + hx-get={profileLink(profile.handle) + "?tab=galleries"} 926 + hx-target="#profile-page" 927 + hx-swap="outerHTML" 928 + class={cn( 929 + "flex-1 py-2 px-4 cursor-pointer font-semibold", 930 + selectedTab === "galleries" && "bg-gray-100", 931 + )} 932 + role="tab" 933 + aria-selected="false" 934 + aria-controls="tab-content" 935 + > 936 + Galleries 937 + </button> 938 + </div> 939 + <div id="tab-content" role="tabpanel"> 940 + {!selectedTab 941 + ? ( 942 + <ul class="space-y-4 relative"> 943 + {timelineItems.map((item) => ( 944 + <TimelineItem item={item} key={item.itemUri} /> 945 + ))} 946 + </ul> 947 + ) 948 + : null} 949 + {selectedTab === "galleries" 950 + ? ( 951 + <div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4"> 952 + {galleries?.length 953 + ? ( 954 + galleries.map((gallery) => ( 955 + <a 956 + href={galleryLink( 957 + gallery.creator.handle, 958 + new AtUri(gallery.uri).rkey, 959 + )} 960 + class="cursor-pointer relative aspect-square" 961 + > 962 + <img 963 + src={gallery.images?.[0]?.thumb} 964 + alt={gallery.images?.[0]?.alt} 965 + class="w-full h-full object-cover" 966 + /> 967 + <div class="absolute bottom-0 left-0 bg-black/80 text-white p-2"> 968 + {(gallery.record as Gallery).title} 969 + </div> 970 + </a> 971 + )) 972 + ) 973 + : <p>No galleries found</p>} 974 + </div> 975 + ) 976 + : null} 977 + </div> 978 + </div> 979 + ); 980 + } 981 + 982 + function ProfileDialog({ 983 + profile, 984 + avatarCid, 985 + }: Readonly<{ 986 + profile: ProfileView; 987 + avatarCid?: string; 988 + }>) { 989 + return ( 990 + <Dialog> 991 + <Dialog.Content> 992 + <Dialog.Title>Edit my profile</Dialog.Title> 993 + <div> 994 + <AvatarForm src={profile.avatar} alt={profile.handle} /> 995 + </div> 996 + <form 997 + hx-post="/actions/profile/update" 998 + hx-swap="none" 999 + _="on htmx:afterOnLoad trigger closeModal" 1000 + > 1001 + <div id="image-input"> 1002 + <input type="hidden" name="avatarCid" value={avatarCid} /> 1003 + </div> 1004 + <div class="mb-4 relative"> 1005 + <label htmlFor="displayName">Display Name</label> 1006 + <Input 1007 + type="text" 1008 + required 1009 + id="displayName" 1010 + name="displayName" 1011 + class="input" 1012 + value={profile.displayName} 1013 + /> 1014 + </div> 1015 + <div class="mb-4 relative"> 1016 + <label htmlFor="description">Description</label> 1017 + <Textarea 1018 + id="description" 1019 + name="description" 1020 + rows={4} 1021 + class="input" 1022 + > 1023 + {profile.description} 1024 + </Textarea> 1025 + </div> 1026 + <Button type="submit" variant="primary" class="w-full"> 1027 + Update 1028 + </Button> 1029 + <Button 1030 + variant="secondary" 1031 + type="button" 1032 + class="w-full" 1033 + _={Dialog._closeOnClick} 1034 + > 1035 + Cancel 1036 + </Button> 1037 + </form> 1038 + </Dialog.Content> 1039 + </Dialog> 1040 + ); 1041 + } 1042 + 1043 + function AvatarForm({ src, alt }: Readonly<{ src?: string; alt?: string }>) { 1044 + return ( 1045 + <form 1046 + id="avatar-file-form" 1047 + hx-post="/actions/avatar/upload-start" 1048 + hx-target="#image-preview" 1049 + hx-swap="innerHTML" 1050 + hx-encoding="multipart/form-data" 1051 + hx-trigger="change from:#file" 1052 + > 1053 + <label htmlFor="file"> 1054 + <span class="sr-only">Upload avatar</span> 1055 + <div class="border rounded-full border-slate-900 w-16 h-16 mx-auto mb-2 relative my-2 cursor-pointer"> 1056 + <div class="absolute bottom-0 right-0 bg-slate-800 rounded-full w-5 h-5 flex items-center justify-center z-10"> 1057 + <i class="fa-solid fa-camera text-white text-xs"></i> 1058 + </div> 1059 + <div id="image-preview" class="w-full h-full"> 1060 + {src 1061 + ? ( 1062 + <img 1063 + src={src} 1064 + alt={alt} 1065 + className="rounded-full w-full h-full object-cover" 1066 + /> 1067 + ) 1068 + : null} 1069 + </div> 1070 + </div> 1071 + <input 1072 + class="hidden" 1073 + type="file" 1074 + id="file" 1075 + name="file" 1076 + accept="image/*" 1077 + /> 1078 + </label> 1079 + </form> 1080 + ); 1081 + } 1082 + 1083 + function GalleryPage({ 1084 + gallery, 1085 + stars = [], 1086 + currentUserDid, 1087 + }: Readonly<{ 1088 + gallery: GalleryView; 1089 + stars: WithBffMeta<Star>[]; 1090 + currentUserDid?: string; 1091 + }>) { 1092 + const isCreator = currentUserDid === gallery.creator.did; 1093 + const isLoggedIn = !!currentUserDid; 1094 + return ( 1095 + <div class="px-4"> 1096 + <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between my-4 gap-2"> 1097 + <div> 1098 + <div> 1099 + <h1 class="font-bold text-2xl"> 1100 + {(gallery.record as Gallery).title} 1101 + </h1> 1102 + Gallery by{" "} 1103 + <a 1104 + href={profileLink(gallery.creator.handle)} 1105 + class="hover:underline" 1106 + > 1107 + <span class="font-semibold">{gallery.creator.displayName}</span> 1108 + {" "} 1109 + <span class="text-gray-600">@{gallery.creator.handle}</span> 1110 + </a> 1111 + </div> 1112 + {(gallery.record as Gallery).description} 1113 + </div> 1114 + {isLoggedIn && isCreator 1115 + ? ( 1116 + <Button 1117 + variant="primary" 1118 + class="self-start w-full sm:w-fit" 1119 + asChild 1120 + > 1121 + <a 1122 + href={`${ 1123 + galleryLink( 1124 + gallery.creator.handle, 1125 + new AtUri(gallery.uri).rkey, 1126 + ) 1127 + }/edit`} 1128 + > 1129 + Edit 1130 + </a> 1131 + </Button> 1132 + ) 1133 + : null} 1134 + {!isCreator 1135 + ? ( 1136 + <StarButton 1137 + currentUserDid={currentUserDid} 1138 + stars={stars} 1139 + galleryUri={gallery.uri} 1140 + /> 1141 + ) 1142 + : null} 1143 + </div> 1144 + <div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4"> 1145 + {gallery.images?.length 1146 + ? gallery?.images?.map((image) => ( 1147 + <button 1148 + key={image.fullsize} 1149 + type="button" 1150 + hx-get={imageDialogLink(gallery, image)} 1151 + hx-trigger="click" 1152 + hx-target="#layout" 1153 + hx-swap="afterbegin" 1154 + class="cursor-pointer relative sm:aspect-square" 1155 + > 1156 + {isLoggedIn && isCreator 1157 + ? <AltTextButton galleryUri={gallery.uri} cid={image.cid} /> 1158 + : null} 1159 + <img 1160 + src={image.fullsize} 1161 + alt={image.alt} 1162 + class="sm:absolute sm:inset-0 w-full h-full sm:object-contain" 1163 + /> 1164 + {!isCreator && image.alt 1165 + ? ( 1166 + <div class="absolute bg-black/80 bottom-2 right-2 sm:bottom-0 sm:right-0 text-xs text-white font-semibold py-[1px] px-[3px]"> 1167 + ALT 1168 + </div> 1169 + ) 1170 + : null} 1171 + </button> 1172 + )) 1173 + : null} 1174 + </div> 1175 + </div> 1176 + ); 1177 + } 1178 + 1179 + function StarButton({ 1180 + currentUserDid, 1181 + stars = [], 1182 + galleryUri, 1183 + }: Readonly<{ 1184 + currentUserDid?: string; 1185 + stars: WithBffMeta<Star>[]; 1186 + galleryUri: string; 1187 + }>) { 1188 + const starUri = stars.find((s) => currentUserDid === s.did)?.uri; 1189 + return ( 1190 + <Button 1191 + variant="primary" 1192 + class="self-start w-full sm:w-fit" 1193 + type="button" 1194 + hx-post={`/actions/star?galleryUri=${galleryUri}${ 1195 + starUri ? "&starUri=" + starUri : "" 1196 + }`} 1197 + hx-trigger="click" 1198 + hx-target="this" 1199 + hx-swap="outerHTML" 1200 + > 1201 + <i class={cn("fa-star", starUri ? "fa-solid" : "fa-regular")}></i>{" "} 1202 + {stars.length} 1203 + </Button> 1204 + ); 1205 + } 1206 + 1207 + function BackBtn({ href }: Readonly<{ href: string }>) { 1208 + return ( 1209 + <a href={href} class="w-fit flex items-center gap-1 mb-2"> 1210 + <i class="fas fa-arrow-left"></i> Back 1211 + </a> 1212 + ); 1213 + } 1214 + 1215 + function GalleryCreateEditPage({ 1216 + userHandle, 1217 + gallery, 1218 + }: Readonly<{ userHandle: string; gallery?: GalleryView | null }>) { 1219 + return ( 1220 + <div class="p-4"> 1221 + <BackBtn 1222 + href={gallery 1223 + ? galleryLink(gallery.creator.handle, new AtUri(gallery.uri).rkey) 1224 + : profileLink(userHandle)} 1225 + /> 1226 + <Header class="mb-2"> 1227 + {gallery ? "Edit gallery" : "Create a new gallery"} 1228 + </Header> 1229 + <form 1230 + id="gallery-form" 1231 + class="max-w-xl" 1232 + hx-post={`/actions/create-edit${gallery ? "?uri=" + gallery?.uri : ""}`} 1233 + hx-swap="none" 1234 + _="on htmx:afterOnLoad 1235 + if event.detail.xhr.status != 200 1236 + alert('Error: ' + event.detail.xhr.responseText)" 1237 + > 1238 + <div id="image-cids"> 1239 + {(gallery?.record as Gallery).images?.map((image) => ( 1240 + <input 1241 + type="hidden" 1242 + name="cids" 1243 + value={image.image.ref.toString()} 1244 + /> 1245 + ))} 1246 + </div> 1247 + <div class="mb-4 relative"> 1248 + <label htmlFor="title">Gallery name</label> 1249 + <Input 1250 + type="text" 1251 + id="title" 1252 + name="title" 1253 + class="input" 1254 + required 1255 + value={(gallery?.record as Gallery)?.title} 1256 + /> 1257 + </div> 1258 + <div class="mb-2 relative"> 1259 + <label htmlFor="description">Description</label> 1260 + <Textarea id="description" name="description" rows={4} class="input"> 1261 + {(gallery?.record as Gallery)?.description} 1262 + </Textarea> 1263 + </div> 1264 + </form> 1265 + <div class="max-w-xl"> 1266 + <input 1267 + type="button" 1268 + name="galleryUri" 1269 + value={gallery?.uri} 1270 + class="hidden" 1271 + /> 1272 + <Button variant="primary" class="mb-2" asChild> 1273 + <label class="w-fit"> 1274 + <i class="fa fa-plus"></i> Add images 1275 + <input 1276 + class="hidden" 1277 + type="file" 1278 + multiple 1279 + accept="image/*" 1280 + _="on change 1281 + set fileList to me.files 1282 + if fileList.length > 10 1283 + alert('You can only upload 10 images') 1284 + halt 1285 + end 1286 + for file in fileList 1287 + make a FormData called fd 1288 + fd.append('file', file) 1289 + fetch /actions/images/upload-start with { method:'POST', body:fd } 1290 + then put it at the end of #image-preview 1291 + then call htmx.process(#image-preview) 1292 + end 1293 + set me.value to ''" 1294 + /> 1295 + </label> 1296 + </Button> 1297 + <div id="image-preview" class="w-full h-full grid grid-cols-5 gap-2"> 1298 + {gallery?.images?.map((image) => ( 1299 + <ImagePreview key={image.cid} src={image.thumb} cid={image.cid} /> 1300 + ))} 1301 + </div> 1302 + </div> 1303 + <form id="delete-form" hx-post={`/actions/delete?uri=${gallery?.uri}`}> 1304 + <input type="hidden" name="uri" value={gallery?.uri} /> 1305 + </form> 1306 + <div class="flex flex-col gap-2 mt-2"> 1307 + <Button 1308 + variant="primary" 1309 + form="gallery-form" 1310 + type="submit" 1311 + class="w-fit" 1312 + > 1313 + {gallery ? "Update gallery" : "Create gallery"} 1314 + </Button> 1315 + 1316 + {gallery 1317 + ? ( 1318 + <Button 1319 + variant="destructive" 1320 + form="delete-form" 1321 + type="submit" 1322 + class="w-fit" 1323 + > 1324 + Delete gallery 1325 + </Button> 1326 + ) 1327 + : null} 1328 + </div> 1329 + </div> 1330 + ); 1331 + } 1332 + 1333 + function ImagePreview({ 1334 + src, 1335 + cid, 1336 + }: Readonly<{ 1337 + src: string; 1338 + cid?: string; 1339 + }>) { 1340 + return ( 1341 + <div class="relative"> 1342 + {cid 1343 + ? ( 1344 + <button 1345 + type="button" 1346 + class="bg-black/80 z-10 absolute top-2 right-2 cursor-pointer size-4 flex items-center justify-center" 1347 + _={`on click 1348 + set input to <input[value='${cid}']/> 1349 + if input exists 1350 + remove input 1351 + end 1352 + remove me.parentNode 1353 + halt 1354 + `} 1355 + > 1356 + <i class="fas fa-close text-white"></i> 1357 + </button> 1358 + ) 1359 + : null} 1360 + <img 1361 + src={src} 1362 + alt="" 1363 + data-state={cid ? "complete" : "pending"} 1364 + class="w-full h-full object-cover aspect-square data-[state=pending]:opacity-50" 1365 + /> 1366 + </div> 1367 + ); 1368 + } 1369 + 1370 + function AltTextButton({ 1371 + galleryUri, 1372 + cid, 1373 + }: Readonly<{ galleryUri: string; cid: string }>) { 1374 + return ( 1375 + <div 1376 + class="bg-black/80 py-[1px] px-[3px] absolute top-2 left-2 sm:top-0 sm:left-0 cursor-pointer flex items-center justify-center text-xs text-white font-semibold z-10" 1377 + hx-get={`/dialogs/image-alt?galleryUri=${galleryUri}&imageCid=${cid}`} 1378 + hx-trigger="click" 1379 + hx-target="#layout" 1380 + hx-swap="afterbegin" 1381 + _="on click halt" 1382 + > 1383 + <i class="fas fa-plus text-[10px] mr-1"></i> ALT 1384 + </div> 1385 + ); 1386 + } 1387 + 1388 + function ImageDialog({ 1389 + gallery, 1390 + image, 1391 + nextImage, 1392 + prevImage, 1393 + }: Readonly<{ 1394 + gallery: GalleryView; 1395 + image: ViewImage; 1396 + nextImage?: ViewImage; 1397 + prevImage?: ViewImage; 1398 + }>) { 1399 + return ( 1400 + <Dialog id="image-dialog" class="bg-black z-30"> 1401 + {nextImage 1402 + ? ( 1403 + <div 1404 + hx-get={imageDialogLink(gallery, nextImage)} 1405 + hx-trigger="keyup[key=='ArrowRight'] from:body, swipeleft from:body" 1406 + hx-target="#image-dialog" 1407 + hx-swap="innerHTML" 1408 + /> 1409 + ) 1410 + : null} 1411 + {prevImage 1412 + ? ( 1413 + <div 1414 + hx-get={imageDialogLink(gallery, prevImage)} 1415 + hx-trigger="keyup[key=='ArrowLeft'] from:body, swiperight from:body" 1416 + hx-target="#image-dialog" 1417 + hx-swap="innerHTML" 1418 + /> 1419 + ) 1420 + : null} 1421 + <div 1422 + class="flex flex-col w-5xl h-[calc(100vh-100px)] sm:h-screen z-20" 1423 + _={Dialog._closeOnClick} 1424 + > 1425 + <div class="flex flex-col p-4 z-20 flex-1 relative"> 1426 + <img 1427 + src={image.fullsize} 1428 + alt={image.alt} 1429 + class="absolute inset-0 w-full h-full object-contain" 1430 + /> 1431 + </div> 1432 + {image.alt 1433 + ? ( 1434 + <div class="px-4 sm:px-0 py-4 bg-black text-white text-left"> 1435 + {image.alt} 1436 + </div> 1437 + ) 1438 + : null} 1439 + </div> 1440 + </Dialog> 1441 + ); 1442 + } 1443 + 1444 + function ImageAltDialog({ 1445 + image, 1446 + galleryUri, 1447 + }: Readonly<{ 1448 + image: ViewImage; 1449 + galleryUri: string; 1450 + }>) { 1451 + return ( 1452 + <Dialog id="image-alt-dialog" class="z-30"> 1453 + <Dialog.Content> 1454 + <Dialog.Title>Add alt text</Dialog.Title> 1455 + <div class="aspect-square relative bg-gray-100"> 1456 + <img 1457 + src={image.fullsize} 1458 + alt={image.alt} 1459 + class="absolute inset-0 w-full h-full object-contain" 1460 + /> 1461 + </div> 1462 + <form 1463 + hx-post="/actions/image-alt" 1464 + _="on htmx:afterOnLoad[successful] trigger closeDialog" 1465 + > 1466 + <input type="hidden" name="galleryUri" value={galleryUri} /> 1467 + <input type="hidden" name="cid" value={image.cid} /> 1468 + <div class="my-2"> 1469 + <label htmlFor="alt">Descriptive alt text</label> 1470 + <Textarea 1471 + id="alt" 1472 + name="alt" 1473 + rows={4} 1474 + defaultValue={image.alt} 1475 + placeholder="Alt text" 1476 + /> 1477 + </div> 1478 + <div class="w-full flex flex-col gap-2 mt-2"> 1479 + <Button type="submit" variant="primary" class="w-full"> 1480 + Save 1481 + </Button> 1482 + <Dialog.Close class="w-full"> 1483 + Cancel 1484 + </Dialog.Close> 1485 + </div> 1486 + </form> 1487 + </Dialog.Content> 1488 + </Dialog> 1489 + ); 1490 + } 1491 + 1492 + function UploadOob({ cid }: Readonly<{ cid: string }>) { 1493 + return ( 1494 + <div hx-swap-oob="beforeend:#image-cids"> 1495 + {cid ? <input key={cid} type="hidden" name="cids" value={cid} /> : null} 1496 + </div> 1497 + ); 1498 + } 1499 + 1500 + function getActorProfile(did: string, ctx: BffContext) { 1501 + const actor = ctx.indexService.getActor(did); 1502 + if (!actor) return null; 1503 + const profileRecord = ctx.indexService.getRecord<WithBffMeta<Profile>>( 1504 + `at://${did}/social.grain.actor.profile/self`, 1505 + ); 1506 + return profileRecord ? profileToView(profileRecord, actor.handle) : null; 1507 + } 1508 + 1509 + function galleryToView( 1510 + record: WithBffMeta<Gallery>, 1511 + creator: Un$Typed<ProfileView>, 1512 + ): Un$Typed<GalleryView> { 1513 + return { 1514 + uri: record.uri, 1515 + cid: record.cid, 1516 + creator, 1517 + record, 1518 + images: record?.images?.map((image) => 1519 + imageToView(new AtUri(record.uri).hostname, image) 1520 + ), 1521 + indexedAt: record.indexedAt, 1522 + }; 1523 + } 1524 + 1525 + function imageToView(did: string, image: GalleryImage): Un$Typed<ViewImage> { 1526 + return { 1527 + cid: image.image.ref.toString(), 1528 + thumb: 1529 + `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${image.image.ref.toString()}@webp`, 1530 + fullsize: 1531 + `https://cdn.bsky.app/img/feed_fullsize/plain/${did}/${image.image.ref.toString()}@webp`, 1532 + alt: image.alt, 1533 + aspectRatio: image.aspectRatio, 1534 + }; 1535 + } 1536 + 1537 + function profileToView( 1538 + record: WithBffMeta<Profile>, 1539 + handle: string, 1540 + ): Un$Typed<ProfileView> { 1541 + return { 1542 + did: record.did, 1543 + handle, 1544 + displayName: record.displayName, 1545 + description: record.description, 1546 + avatar: record?.avatar 1547 + ? `https://cdn.bsky.app/img/feed_thumbnail/plain/${record.did}/${record.avatar.ref.toString()}` 1548 + : undefined, 1549 + }; 1550 + } 1551 + 1552 + function profileLink(handle: string) { 1553 + return `/profile/${handle}`; 1554 + } 1555 + 1556 + function galleryLink(handle: string, galleryRkey: string) { 1557 + return `/profile/${handle}/${galleryRkey}`; 1558 + } 1559 + 1560 + function imageDialogLink(gallery: GalleryView, image: ViewImage) { 1561 + return `/dialogs/image?galleryUri=${gallery.uri}&imageCid=${image.cid}`; 1562 + } 1563 + 1564 + function mergeUniqueImages( 1565 + existingImages: GalleryImage[] | undefined, 1566 + newImages: GalleryImage[], 1567 + validCids?: string[], 1568 + ): GalleryImage[] { 1569 + if (!existingImages || existingImages.length === 0) { 1570 + return validCids 1571 + ? newImages.filter((img) => validCids.includes(img.image.ref.toString())) 1572 + : newImages; 1573 + } 1574 + const uniqueImagesMap = new Map<string, GalleryImage>(); 1575 + existingImages.forEach((img) => { 1576 + const key = img.image.ref.toString(); 1577 + uniqueImagesMap.set(key, img); 1578 + }); 1579 + newImages.forEach((img) => { 1580 + const key = img.image.ref.toString(); 1581 + uniqueImagesMap.set(key, img); 1582 + }); 1583 + const mergedImages = [...uniqueImagesMap.values()]; 1584 + return validCids 1585 + ? mergedImages.filter((img) => validCids.includes(img.image.ref.toString())) 1586 + : mergedImages; 1587 + } 1588 + 1589 + async function onSignedIn({ actor, ctx }: onSignedInArgs) { 1590 + await ctx.backfillCollections( 1591 + [actor.did], 1592 + [...ctx.cfg.collections!, "app.bsky.actor.profile"], 1593 + ); 1594 + 1595 + const profileResults = ctx.indexService.getRecords<Profile>( 1596 + "social.grain.actor.profile", 1597 + { 1598 + where: [{ field: "did", equals: actor.did }], 1599 + }, 1600 + ); 1601 + 1602 + const profile = profileResults.items[0]; 1603 + 1604 + if (profile) { 1605 + console.log("Profile already exists"); 1606 + return `/profile/${actor.handle}`; 1607 + } 1608 + 1609 + const bskyProfileResults = ctx.indexService.getRecords<BskyProfile>( 1610 + "app.bsky.actor.profile", 1611 + { 1612 + where: [{ field: "did", equals: actor.did }], 1613 + }, 1614 + ); 1615 + 1616 + const bskyProfile = bskyProfileResults.items[0]; 1617 + 1618 + if (!bskyProfile) { 1619 + console.error("Failed to get profile"); 1620 + return; 1621 + } 1622 + 1623 + await ctx.createRecord<Profile>( 1624 + "social.grain.actor.profile", 1625 + { 1626 + displayName: bskyProfile.displayName ?? undefined, 1627 + description: bskyProfile.description ?? undefined, 1628 + avatar: bskyProfile.avatar ?? undefined, 1629 + createdAt: new Date().toISOString(), 1630 + }, 1631 + true, 1632 + ); 1633 + 1634 + return "/onboard"; 1635 + } 1636 + 1637 + function uploadStart( 1638 + routePrefix: string, 1639 + cb: (params: { uploadId: string; dataUrl?: string; cid?: string }) => VNode, 1640 + ): RouteHandler { 1641 + return async (req, _params, ctx) => { 1642 + requireAuth(ctx); 1643 + const formData = await req.formData(); 1644 + const file = formData.get("file") as File; 1645 + if (!file) { 1646 + return new Response("No file", { status: 400 }); 1647 + } 1648 + const dataUrl = await compressImageForPreview(file); 1649 + const uploadId = ctx.uploadBlob({ 1650 + file, 1651 + dataUrl, 1652 + }); 1653 + return ctx.html( 1654 + <div 1655 + id={`upload-id-${uploadId}`} 1656 + hx-trigger="done" 1657 + hx-get={`/actions/${routePrefix}/upload-done?uploadId=${uploadId}`} 1658 + hx-target="this" 1659 + hx-swap="outerHTML" 1660 + class="h-full w-full" 1661 + > 1662 + <div 1663 + hx-get={`/actions/${routePrefix}/upload-check-status?uploadId=${uploadId}`} 1664 + hx-trigger="every 600ms" 1665 + hx-target="this" 1666 + hx-swap="innerHTML" 1667 + class="h-full w-full" 1668 + > 1669 + {cb({ uploadId, dataUrl })} 1670 + </div> 1671 + </div>, 1672 + ); 1673 + }; 1674 + } 1675 + 1676 + function uploadCheckStatus( 1677 + cb: (params: { uploadId: string; dataUrl: string; cid?: string }) => VNode, 1678 + ): RouteHandler { 1679 + return (req, _params, ctx) => { 1680 + requireAuth(ctx); 1681 + const url = new URL(req.url); 1682 + const searchParams = new URLSearchParams(url.search); 1683 + const uploadId = searchParams.get("uploadId"); 1684 + if (!uploadId) return ctx.next(); 1685 + const meta = ctx.blobMetaCache.get(uploadId); 1686 + if (!meta?.dataUrl) return ctx.next(); 1687 + return ctx.html( 1688 + cb({ uploadId, dataUrl: meta.dataUrl }), 1689 + meta.blobRef ? { "HX-Trigger": "done" } : undefined, 1690 + ); 1691 + }; 1692 + } 1693 + 1694 + function uploadDone( 1695 + cb: (params: { dataUrl: string; cid: string }) => VNode, 1696 + ): RouteHandler { 1697 + return (req, _params, ctx) => { 1698 + requireAuth(ctx); 1699 + const url = new URL(req.url); 1700 + const searchParams = new URLSearchParams(url.search); 1701 + const uploadId = searchParams.get("uploadId"); 1702 + if (!uploadId) return ctx.next(); 1703 + const meta = ctx.blobMetaCache.get(uploadId); 1704 + if (!meta?.dataUrl || !meta?.blobRef) return ctx.next(); 1705 + return ctx.html( 1706 + cb({ dataUrl: meta.dataUrl, cid: meta.blobRef.ref.toString() }), 1707 + ); 1708 + }; 1709 + } 1710 + 1711 + function imageUploadRoutes(): BffMiddleware[] { 1712 + return [ 1713 + route( 1714 + `/actions/images/upload-start`, 1715 + ["POST"], 1716 + uploadStart( 1717 + "images", 1718 + ({ dataUrl }) => <ImagePreview src={dataUrl ?? ""} />, 1719 + ), 1720 + ), 1721 + route( 1722 + `/actions/images/upload-check-status`, 1723 + ["GET"], 1724 + uploadCheckStatus(({ uploadId, dataUrl }) => ( 1725 + <> 1726 + <input type="hidden" name="uploadId" value={uploadId} /> 1727 + <ImagePreview src={dataUrl} /> 1728 + </> 1729 + )), 1730 + ), 1731 + route( 1732 + `/actions/images/upload-done`, 1733 + ["GET"], 1734 + uploadDone(({ dataUrl, cid }) => ( 1735 + <> 1736 + <UploadOob cid={cid} /> 1737 + <ImagePreview src={dataUrl} cid={cid} /> 1738 + </> 1739 + )), 1740 + ), 1741 + ]; 1742 + } 1743 + 1744 + function avatarUploadRoutes(): BffMiddleware[] { 1745 + return [ 1746 + route( 1747 + `/actions/avatar/upload-start`, 1748 + ["POST"], 1749 + uploadStart("avatar", ({ dataUrl }) => ( 1750 + <img 1751 + src={dataUrl} 1752 + alt="" 1753 + data-state="pending" 1754 + class="rounded-full w-full h-full object-cover data-[state=pending]:opacity-50" 1755 + /> 1756 + )), 1757 + ), 1758 + route( 1759 + `/actions/avatar/upload-check-status`, 1760 + ["GET"], 1761 + uploadCheckStatus(({ uploadId, dataUrl, cid }) => ( 1762 + <> 1763 + <input type="hidden" name="uploadId" value={uploadId} /> 1764 + <img 1765 + src={dataUrl} 1766 + alt="" 1767 + data-state={cid ? "complete" : "pending"} 1768 + class="rounded-full w-full h-full object-cover data-[state=pending]:opacity-50" 1769 + /> 1770 + </> 1771 + )), 1772 + ), 1773 + route( 1774 + `/actions/avatar/upload-done`, 1775 + ["GET"], 1776 + uploadDone(({ dataUrl, cid }) => ( 1777 + <> 1778 + <div hx-swap-oob="innerHTML:#image-input"> 1779 + <input type="hidden" name="avatarCid" value={cid} /> 1780 + </div> 1781 + <img 1782 + src={dataUrl} 1783 + alt="" 1784 + class="rounded-full w-full h-full object-cover" 1785 + /> 1786 + </> 1787 + )), 1788 + ), 1789 + ]; 1790 + }
+31
static/image_dialog.js
··· 1 + let startX = 0; 2 + const threshold = 50; 3 + const onTouchStart = (e) => { 4 + startX = e.touches[0].clientX; 5 + }; 6 + const onTouchEnd = (e) => { 7 + const endX = e.changedTouches[0].clientX; 8 + const diffX = endX - startX; 9 + 10 + if (Math.abs(diffX) > threshold) { 11 + const direction = diffX > 0 ? "swiperight" : "swipeleft"; 12 + e.target.dispatchEvent(new CustomEvent(direction, { bubbles: true })); 13 + } 14 + }; 15 + const observer = new MutationObserver(() => { 16 + const modal = document.getElementById("image-dialog"); 17 + if (!modal) { 18 + console.log("Image Dialog not found, removing event listeners"); 19 + document.body.removeEventListener("touchstart", onTouchStart); 20 + document.body.removeEventListener("touchend", onTouchEnd); 21 + observer.disconnect(); 22 + } 23 + }); 24 + htmx.onLoad((evt) => { 25 + if (evt.id === "image-dialog") { 26 + document.body.addEventListener("touchstart", onTouchStart); 27 + document.body.addEventListener("touchend", onTouchEnd); 28 + } 29 + const parent = document.body; 30 + observer.observe(parent, { childList: true, subtree: true }); 31 + });
+622
static/styles.css
··· 1 + /*! tailwindcss v4.1.4 | MIT License | https://tailwindcss.com */ 2 + @layer properties; 3 + @layer theme, base, components, utilities; 4 + @layer theme { 5 + :root, :host { 6 + --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", 7 + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 8 + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", 9 + "Courier New", monospace; 10 + --color-slate-800: oklch(27.9% 0.041 260.031); 11 + --color-slate-900: oklch(20.8% 0.042 265.755); 12 + --color-gray-100: oklch(96.7% 0.003 264.542); 13 + --color-gray-200: oklch(92.8% 0.006 264.531); 14 + --color-gray-600: oklch(44.6% 0.03 256.802); 15 + --color-gray-900: oklch(21% 0.034 264.665); 16 + --color-black: #000; 17 + --color-white: #fff; 18 + --spacing: 0.25rem; 19 + --container-md: 28rem; 20 + --container-xl: 36rem; 21 + --container-5xl: 64rem; 22 + --text-xs: 0.75rem; 23 + --text-xs--line-height: calc(1 / 0.75); 24 + --text-sm: 0.875rem; 25 + --text-sm--line-height: calc(1.25 / 0.875); 26 + --text-xl: 1.25rem; 27 + --text-xl--line-height: calc(1.75 / 1.25); 28 + --text-2xl: 1.5rem; 29 + --text-2xl--line-height: calc(2 / 1.5); 30 + --text-4xl: 2.25rem; 31 + --text-4xl--line-height: calc(2.5 / 2.25); 32 + --font-weight-semibold: 600; 33 + --font-weight-bold: 700; 34 + --default-font-family: var(--font-sans); 35 + --default-mono-font-family: var(--font-mono); 36 + } 37 + } 38 + @layer base { 39 + *, ::after, ::before, ::backdrop, ::file-selector-button { 40 + box-sizing: border-box; 41 + margin: 0; 42 + padding: 0; 43 + border: 0 solid; 44 + } 45 + html, :host { 46 + line-height: 1.5; 47 + -webkit-text-size-adjust: 100%; 48 + tab-size: 4; 49 + font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); 50 + font-feature-settings: var(--default-font-feature-settings, normal); 51 + font-variation-settings: var(--default-font-variation-settings, normal); 52 + -webkit-tap-highlight-color: transparent; 53 + } 54 + hr { 55 + height: 0; 56 + color: inherit; 57 + border-top-width: 1px; 58 + } 59 + abbr:where([title]) { 60 + -webkit-text-decoration: underline dotted; 61 + text-decoration: underline dotted; 62 + } 63 + h1, h2, h3, h4, h5, h6 { 64 + font-size: inherit; 65 + font-weight: inherit; 66 + } 67 + a { 68 + color: inherit; 69 + -webkit-text-decoration: inherit; 70 + text-decoration: inherit; 71 + } 72 + b, strong { 73 + font-weight: bolder; 74 + } 75 + code, kbd, samp, pre { 76 + font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); 77 + font-feature-settings: var(--default-mono-font-feature-settings, normal); 78 + font-variation-settings: var(--default-mono-font-variation-settings, normal); 79 + font-size: 1em; 80 + } 81 + small { 82 + font-size: 80%; 83 + } 84 + sub, sup { 85 + font-size: 75%; 86 + line-height: 0; 87 + position: relative; 88 + vertical-align: baseline; 89 + } 90 + sub { 91 + bottom: -0.25em; 92 + } 93 + sup { 94 + top: -0.5em; 95 + } 96 + table { 97 + text-indent: 0; 98 + border-color: inherit; 99 + border-collapse: collapse; 100 + } 101 + :-moz-focusring { 102 + outline: auto; 103 + } 104 + progress { 105 + vertical-align: baseline; 106 + } 107 + summary { 108 + display: list-item; 109 + } 110 + ol, ul, menu { 111 + list-style: none; 112 + } 113 + img, svg, video, canvas, audio, iframe, embed, object { 114 + display: block; 115 + vertical-align: middle; 116 + } 117 + img, video { 118 + max-width: 100%; 119 + height: auto; 120 + } 121 + button, input, select, optgroup, textarea, ::file-selector-button { 122 + font: inherit; 123 + font-feature-settings: inherit; 124 + font-variation-settings: inherit; 125 + letter-spacing: inherit; 126 + color: inherit; 127 + border-radius: 0; 128 + background-color: transparent; 129 + opacity: 1; 130 + } 131 + :where(select:is([multiple], [size])) optgroup { 132 + font-weight: bolder; 133 + } 134 + :where(select:is([multiple], [size])) optgroup option { 135 + padding-inline-start: 20px; 136 + } 137 + ::file-selector-button { 138 + margin-inline-end: 4px; 139 + } 140 + ::placeholder { 141 + opacity: 1; 142 + } 143 + @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { 144 + ::placeholder { 145 + color: currentcolor; 146 + @supports (color: color-mix(in lab, red, red)) { 147 + color: color-mix(in oklab, currentcolor 50%, transparent); 148 + } 149 + } 150 + } 151 + textarea { 152 + resize: vertical; 153 + } 154 + ::-webkit-search-decoration { 155 + -webkit-appearance: none; 156 + } 157 + ::-webkit-date-and-time-value { 158 + min-height: 1lh; 159 + text-align: inherit; 160 + } 161 + ::-webkit-datetime-edit { 162 + display: inline-flex; 163 + } 164 + ::-webkit-datetime-edit-fields-wrapper { 165 + padding: 0; 166 + } 167 + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { 168 + padding-block: 0; 169 + } 170 + :-moz-ui-invalid { 171 + box-shadow: none; 172 + } 173 + button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { 174 + appearance: button; 175 + } 176 + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { 177 + height: auto; 178 + } 179 + [hidden]:where(:not([hidden="until-found"])) { 180 + display: none !important; 181 + } 182 + } 183 + @layer utilities { 184 + .sr-only { 185 + position: absolute; 186 + width: 1px; 187 + height: 1px; 188 + padding: 0; 189 + margin: -1px; 190 + overflow: hidden; 191 + clip: rect(0, 0, 0, 0); 192 + white-space: nowrap; 193 + border-width: 0; 194 + } 195 + .absolute { 196 + position: absolute; 197 + } 198 + .relative { 199 + position: relative; 200 + } 201 + .inset-0 { 202 + inset: calc(var(--spacing) * 0); 203 + } 204 + .top-2 { 205 + top: calc(var(--spacing) * 2); 206 + } 207 + .right-0 { 208 + right: calc(var(--spacing) * 0); 209 + } 210 + .right-2 { 211 + right: calc(var(--spacing) * 2); 212 + } 213 + .bottom-0 { 214 + bottom: calc(var(--spacing) * 0); 215 + } 216 + .bottom-2 { 217 + bottom: calc(var(--spacing) * 2); 218 + } 219 + .bottom-\[0\.75rem\] { 220 + bottom: 0.75rem; 221 + } 222 + .left-0 { 223 + left: calc(var(--spacing) * 0); 224 + } 225 + .left-2 { 226 + left: calc(var(--spacing) * 2); 227 + } 228 + .z-10 { 229 + z-index: 10; 230 + } 231 + .z-20 { 232 + z-index: 20; 233 + } 234 + .z-30 { 235 + z-index: 30; 236 + } 237 + .mx-auto { 238 + margin-inline: auto; 239 + } 240 + .my-2 { 241 + margin-block: calc(var(--spacing) * 2); 242 + } 243 + .my-4 { 244 + margin-block: calc(var(--spacing) * 4); 245 + } 246 + .mt-2 { 247 + margin-top: calc(var(--spacing) * 2); 248 + } 249 + .mr-1 { 250 + margin-right: calc(var(--spacing) * 1); 251 + } 252 + .mb-2 { 253 + margin-bottom: calc(var(--spacing) * 2); 254 + } 255 + .mb-4 { 256 + margin-bottom: calc(var(--spacing) * 4); 257 + } 258 + .ml-1 { 259 + margin-left: calc(var(--spacing) * 1); 260 + } 261 + .flex { 262 + display: flex; 263 + } 264 + .grid { 265 + display: grid; 266 + } 267 + .hidden { 268 + display: none; 269 + } 270 + .aspect-\[3\/2\] { 271 + aspect-ratio: 3/2; 272 + } 273 + .aspect-square { 274 + aspect-ratio: 1 / 1; 275 + } 276 + .size-4 { 277 + width: calc(var(--spacing) * 4); 278 + height: calc(var(--spacing) * 4); 279 + } 280 + .size-16 { 281 + width: calc(var(--spacing) * 16); 282 + height: calc(var(--spacing) * 16); 283 + } 284 + .h-1\/2 { 285 + height: calc(1/2 * 100%); 286 + } 287 + .h-5 { 288 + height: calc(var(--spacing) * 5); 289 + } 290 + .h-16 { 291 + height: calc(var(--spacing) * 16); 292 + } 293 + .h-\[400px\] { 294 + height: 400px; 295 + } 296 + .h-\[calc\(100vh-100px\)\] { 297 + height: calc(100vh - 100px); 298 + } 299 + .h-full { 300 + height: 100%; 301 + } 302 + .w-1\/3 { 303 + width: calc(1/3 * 100%); 304 + } 305 + .w-2\/3 { 306 + width: calc(2/3 * 100%); 307 + } 308 + .w-5 { 309 + width: calc(var(--spacing) * 5); 310 + } 311 + .w-5xl { 312 + width: var(--container-5xl); 313 + } 314 + .w-16 { 315 + width: calc(var(--spacing) * 16); 316 + } 317 + .w-\[400px\] { 318 + width: 400px; 319 + } 320 + .w-fit { 321 + width: fit-content; 322 + } 323 + .w-full { 324 + width: 100%; 325 + } 326 + .max-w-md { 327 + max-width: var(--container-md); 328 + } 329 + .max-w-xl { 330 + max-width: var(--container-xl); 331 + } 332 + .flex-1 { 333 + flex: 1; 334 + } 335 + .cursor-pointer { 336 + cursor: pointer; 337 + } 338 + .grid-cols-1 { 339 + grid-template-columns: repeat(1, minmax(0, 1fr)); 340 + } 341 + .grid-cols-5 { 342 + grid-template-columns: repeat(5, minmax(0, 1fr)); 343 + } 344 + .flex-col { 345 + flex-direction: column; 346 + } 347 + .items-center { 348 + align-items: center; 349 + } 350 + .justify-center { 351 + justify-content: center; 352 + } 353 + .gap-1 { 354 + gap: calc(var(--spacing) * 1); 355 + } 356 + .gap-2 { 357 + gap: calc(var(--spacing) * 2); 358 + } 359 + .gap-4 { 360 + gap: calc(var(--spacing) * 4); 361 + } 362 + .space-y-2 { 363 + :where(& > :not(:last-child)) { 364 + --tw-space-y-reverse: 0; 365 + margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse)); 366 + margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse))); 367 + } 368 + } 369 + .space-y-4 { 370 + :where(& > :not(:last-child)) { 371 + --tw-space-y-reverse: 0; 372 + margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse)); 373 + margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); 374 + } 375 + } 376 + .space-x-2 { 377 + :where(& > :not(:last-child)) { 378 + --tw-space-x-reverse: 0; 379 + margin-inline-start: calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse)); 380 + margin-inline-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse))); 381 + } 382 + } 383 + .self-start { 384 + align-self: flex-start; 385 + } 386 + .overflow-hidden { 387 + overflow: hidden; 388 + } 389 + .rounded-full { 390 + border-radius: calc(infinity * 1px); 391 + } 392 + .border { 393 + border-style: var(--tw-border-style); 394 + border-width: 1px; 395 + } 396 + .border-slate-900 { 397 + border-color: var(--color-slate-900); 398 + } 399 + .bg-black { 400 + background-color: var(--color-black); 401 + } 402 + .bg-black\/80 { 403 + background-color: color-mix(in srgb, #000 80%, transparent); 404 + @supports (color: color-mix(in lab, red, red)) { 405 + background-color: color-mix(in oklab, var(--color-black) 80%, transparent); 406 + } 407 + } 408 + .bg-gray-100 { 409 + background-color: var(--color-gray-100); 410 + } 411 + .bg-gray-200 { 412 + background-color: var(--color-gray-200); 413 + } 414 + .bg-slate-800 { 415 + background-color: var(--color-slate-800); 416 + } 417 + .object-contain { 418 + object-fit: contain; 419 + } 420 + .object-cover { 421 + object-fit: cover; 422 + } 423 + .p-2 { 424 + padding: calc(var(--spacing) * 2); 425 + } 426 + .p-4 { 427 + padding: calc(var(--spacing) * 4); 428 + } 429 + .px-4 { 430 + padding-inline: calc(var(--spacing) * 4); 431 + } 432 + .px-\[3px\] { 433 + padding-inline: 3px; 434 + } 435 + .py-2 { 436 + padding-block: calc(var(--spacing) * 2); 437 + } 438 + .py-4 { 439 + padding-block: calc(var(--spacing) * 4); 440 + } 441 + .py-\[1px\] { 442 + padding-block: 1px; 443 + } 444 + .text-left { 445 + text-align: left; 446 + } 447 + .font-\[\'Jersey_20\'\] { 448 + font-family: 'Jersey 20'; 449 + } 450 + .text-2xl { 451 + font-size: var(--text-2xl); 452 + line-height: var(--tw-leading, var(--text-2xl--line-height)); 453 + } 454 + .text-4xl { 455 + font-size: var(--text-4xl); 456 + line-height: var(--tw-leading, var(--text-4xl--line-height)); 457 + } 458 + .text-sm { 459 + font-size: var(--text-sm); 460 + line-height: var(--tw-leading, var(--text-sm--line-height)); 461 + } 462 + .text-xl { 463 + font-size: var(--text-xl); 464 + line-height: var(--tw-leading, var(--text-xl--line-height)); 465 + } 466 + .text-xs { 467 + font-size: var(--text-xs); 468 + line-height: var(--tw-leading, var(--text-xs--line-height)); 469 + } 470 + .text-\[1rem\] { 471 + font-size: 1rem; 472 + } 473 + .text-\[10px\] { 474 + font-size: 10px; 475 + } 476 + .font-bold { 477 + --tw-font-weight: var(--font-weight-bold); 478 + font-weight: var(--font-weight-bold); 479 + } 480 + .font-semibold { 481 + --tw-font-weight: var(--font-weight-semibold); 482 + font-weight: var(--font-weight-semibold); 483 + } 484 + .text-gray-600 { 485 + color: var(--color-gray-600); 486 + } 487 + .text-gray-900 { 488 + color: var(--color-gray-900); 489 + } 490 + .text-white { 491 + color: var(--color-white); 492 + } 493 + .lowercase { 494 + text-transform: lowercase; 495 + } 496 + .hover\:underline { 497 + &:hover { 498 + @media (hover: hover) { 499 + text-decoration-line: underline; 500 + } 501 + } 502 + } 503 + .data-\[state\=pending\]\:opacity-50 { 504 + &[data-state="pending"] { 505 + opacity: 50%; 506 + } 507 + } 508 + .sm\:absolute { 509 + @media (width >= 40rem) { 510 + position: absolute; 511 + } 512 + } 513 + .sm\:inset-0 { 514 + @media (width >= 40rem) { 515 + inset: calc(var(--spacing) * 0); 516 + } 517 + } 518 + .sm\:top-0 { 519 + @media (width >= 40rem) { 520 + top: calc(var(--spacing) * 0); 521 + } 522 + } 523 + .sm\:right-0 { 524 + @media (width >= 40rem) { 525 + right: calc(var(--spacing) * 0); 526 + } 527 + } 528 + .sm\:bottom-0 { 529 + @media (width >= 40rem) { 530 + bottom: calc(var(--spacing) * 0); 531 + } 532 + } 533 + .sm\:left-0 { 534 + @media (width >= 40rem) { 535 + left: calc(var(--spacing) * 0); 536 + } 537 + } 538 + .sm\:aspect-square { 539 + @media (width >= 40rem) { 540 + aspect-ratio: 1 / 1; 541 + } 542 + } 543 + .sm\:h-screen { 544 + @media (width >= 40rem) { 545 + height: 100vh; 546 + } 547 + } 548 + .sm\:w-fit { 549 + @media (width >= 40rem) { 550 + width: fit-content; 551 + } 552 + } 553 + .sm\:grid-cols-3 { 554 + @media (width >= 40rem) { 555 + grid-template-columns: repeat(3, minmax(0, 1fr)); 556 + } 557 + } 558 + .sm\:flex-row { 559 + @media (width >= 40rem) { 560 + flex-direction: row; 561 + } 562 + } 563 + .sm\:items-center { 564 + @media (width >= 40rem) { 565 + align-items: center; 566 + } 567 + } 568 + .sm\:justify-between { 569 + @media (width >= 40rem) { 570 + justify-content: space-between; 571 + } 572 + } 573 + .sm\:object-contain { 574 + @media (width >= 40rem) { 575 + object-fit: contain; 576 + } 577 + } 578 + .sm\:px-0 { 579 + @media (width >= 40rem) { 580 + padding-inline: calc(var(--spacing) * 0); 581 + } 582 + } 583 + } 584 + .htmx-request.htmx-indicator { 585 + display: inline; 586 + } 587 + .htmx-indicator { 588 + display: none; 589 + } 590 + .htmx-request #submit-button { 591 + opacity: 0.5; 592 + pointer-events: none; 593 + } 594 + @property --tw-space-y-reverse { 595 + syntax: "*"; 596 + inherits: false; 597 + initial-value: 0; 598 + } 599 + @property --tw-space-x-reverse { 600 + syntax: "*"; 601 + inherits: false; 602 + initial-value: 0; 603 + } 604 + @property --tw-border-style { 605 + syntax: "*"; 606 + inherits: false; 607 + initial-value: solid; 608 + } 609 + @property --tw-font-weight { 610 + syntax: "*"; 611 + inherits: false; 612 + } 613 + @layer properties { 614 + @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { 615 + *, ::before, ::after, ::backdrop { 616 + --tw-space-y-reverse: 0; 617 + --tw-space-x-reverse: 0; 618 + --tw-border-style: solid; 619 + --tw-font-weight: initial; 620 + } 621 + } 622 + }