A lexicon to manage your song recordings with all rights owner information

Created slice init

Hilke Ros 525a6916

+3
.gitignore
··· 1 + .env* 2 + node_modules 3 + *.db*
+144
CLAUDE.md
··· 1 + # CLAUDE.md 2 + 3 + This file provides guidance to Claude Code (claude.ai/code) when working with 4 + code in this repository. 5 + 6 + ## Development Commands 7 + 8 + ```bash 9 + # Start development server with hot reload 10 + deno task dev 11 + 12 + # Start production server 13 + deno task start 14 + 15 + # Format code 16 + deno fmt 17 + 18 + # Check types 19 + deno check src/**/*.ts src/**/*.tsx 20 + ``` 21 + 22 + ## Architecture Overview 23 + 24 + This is a Deno-based web application built with the Slices CLI. It provides 25 + server-side rendering with Preact, OAuth authentication, and AT Protocol 26 + integration for building applications on the decentralized web. 27 + 28 + ### Technology Stack 29 + 30 + - **Runtime**: Deno with TypeScript 31 + - **Frontend**: Preact with server-side rendering 32 + - **Styling**: Tailwind CSS (via CDN) 33 + - **Interactivity**: HTMX + Hyperscript 34 + - **Routing**: Deno's standard HTTP routing 35 + - **Authentication**: OAuth with PKCE flow using `@slices/oauth` 36 + - **Sessions**: SQLite-based with `@slices/session` 37 + - **Database**: SQLite via OAuth and session libraries 38 + 39 + ### Core Architecture Patterns 40 + 41 + #### Feature-Based Organization 42 + 43 + The codebase is organized by features rather than technical layers: 44 + 45 + ``` 46 + src/ 47 + ├── features/ # Feature modules 48 + │ └── auth/ # Authentication (login/logout) 49 + ├── shared/ # Shared UI components 50 + ├── routes/ # Route definitions and middleware 51 + ├── utils/ # Utility functions 52 + └── config.ts # Core configuration 53 + ``` 54 + 55 + #### Handler Pattern 56 + 57 + Each feature follows a consistent pattern: 58 + 59 + - `handlers.tsx` - Route handlers that return Response objects 60 + - `templates/` - Preact components for rendering 61 + - `templates/fragments/` - Reusable UI components 62 + 63 + #### Authentication & Sessions 64 + 65 + - OAuth integration with AT Protocol using `@slices/oauth` 66 + - PKCE flow for secure authentication 67 + - Session management with `@slices/session` 68 + - SQLite storage for OAuth state and sessions 69 + - Automatic token refresh capabilities 70 + 71 + ### Key Components 72 + 73 + #### Route System 74 + 75 + - All routes defined in `src/routes/mod.ts` 76 + - Feature routes exported from `src/features/*/handlers.tsx` 77 + - Middleware in `src/routes/middleware.ts` handles auth state 78 + 79 + #### OAuth Integration 80 + 81 + - `src/config.ts` - OAuth client and session store setup 82 + - Environment variables required: `OAUTH_CLIENT_ID`, `OAUTH_CLIENT_SECRET`, 83 + `OAUTH_REDIRECT_URI`, `OAUTH_AIP_BASE_URL`, `API_URL`, `SLICE_URI` 84 + - PKCE flow implementation in auth handlers 85 + - SQLite storage for OAuth state and tokens 86 + 87 + #### Rendering System 88 + 89 + - `src/utils/render.tsx` - Unified HTML rendering with proper headers 90 + - Server-side rendering with Preact 91 + - HTMX for dynamic interactions without page reloads 92 + - Shared `Layout` component in `src/shared/fragments/Layout.tsx` 93 + 94 + ### Development Guidelines 95 + 96 + #### Component Conventions 97 + 98 + - Use `.tsx` extension for components with JSX 99 + - Preact components for all UI rendering 100 + - HTMX attributes for interactive behavior 101 + - Tailwind classes for styling 102 + 103 + #### Feature Development 104 + 105 + When adding new features: 106 + 107 + 1. Create feature directory under `src/features/` 108 + 2. Add `handlers.tsx` with route definitions 109 + 3. Create `templates/` directory with Preact components 110 + 4. Export routes from feature and add to `src/routes/mod.ts` 111 + 5. Follow existing authentication patterns using auth middleware 112 + 113 + #### Environment Setup 114 + 115 + The application requires a `.env` file with OAuth and API configuration. 116 + Copy `.env.example` and fill in your values. Missing environment variables 117 + will cause startup failures with descriptive error messages. 118 + 119 + ### Request/Response Flow 120 + 121 + 1. Request hits main server in `src/main.ts` 122 + 2. Routes processed through `src/routes/mod.ts` 123 + 3. Authentication middleware applies session state 124 + 4. Feature handlers process requests and return rendered HTML 125 + 5. HTMX handles partial page updates on client-side interactions 126 + 127 + ### OAuth Flow 128 + 129 + 1. User initiates login with handle/identifier 130 + 2. OAuth client generates PKCE challenge and redirects to auth server 131 + 3. User authenticates and is redirected back with authorization code 132 + 4. Client exchanges code for tokens using PKCE verifier 133 + 5. Session created with automatic token refresh 134 + 6. Protected routes access user data through authenticated client 135 + 136 + ### Adding New Features 137 + 138 + To add a new feature: 139 + 140 + 1. Create `src/features/feature-name/` 141 + 2. Add `handlers.tsx` with route handlers 142 + 3. Create `templates/` directory for UI components 143 + 4. Export routes and add to main router 144 + 5. Use existing patterns for authentication and rendering
+84
README.md
··· 1 + # indiemusich 2 + 3 + A Deno SSR web application with AT Protocol integration, built with Preact, 4 + HTMX, and OAuth authentication. 5 + 6 + ## Quick Start 7 + 8 + ```bash 9 + # Start the development server 10 + deno task dev 11 + ``` 12 + 13 + Visit your app at http://localhost:8080 14 + 15 + > **Note:** Your slice and OAuth credentials were automatically configured 16 + > during project creation. The `.env` file is already set up with your 17 + > credentials. 18 + 19 + ## Features 20 + 21 + - 🔐 **OAuth Authentication** with PKCE flow 22 + - ⚡ **Server-Side Rendering** with Preact 23 + - 🎯 **Interactive UI** with HTMX 24 + - 🎨 **Styling** with Tailwind CSS 25 + - 🗄️ **Session Management** with SQLite 26 + - 🔄 **Auto Token Refresh** 27 + - 🏗️ **Feature-Based Architecture** 28 + 29 + ## Development 30 + 31 + ```bash 32 + # Start development server with hot reload 33 + deno task dev 34 + 35 + # Start production server 36 + deno task start 37 + 38 + # Format code 39 + deno fmt 40 + 41 + # Check types 42 + deno check src/**/*.ts src/**/*.tsx 43 + ``` 44 + 45 + ## Project Structure 46 + 47 + ``` 48 + slices.json # Slices configuration file 49 + lexicons/ # AT Protocol lexicon definitions 50 + src/ 51 + ├── main.ts # Server entry point 52 + ├── config.ts # OAuth & session configuration 53 + ├── generated_client.ts # Generated TypeScript client from lexicons 54 + ├── routes/ # Route definitions 55 + ├── features/ # Feature modules 56 + │ └── auth/ # Authentication 57 + ├── shared/fragments/ # Reusable UI components 58 + └── utils/ # Utility functions 59 + ``` 60 + 61 + ## OAuth Setup 62 + 63 + Your OAuth application was automatically created during project initialization 64 + with: 65 + 66 + - **Client ID & Secret**: Already configured in `.env` 67 + - **Redirect URI**: `http://localhost:8080/oauth/callback` 68 + - **Slice**: Automatically created and linked 69 + 70 + To manage your OAuth clients or create additional ones: 71 + 72 + 1. Visit [Slices Network](https://slices.network) 73 + 2. Use the `slices login` CLI command 74 + 75 + ## Documentation 76 + 77 + - `CLAUDE.md` - Architecture guide for AI assistance 78 + - Feature directories contain handlers and templates 79 + - Components use Preact with server-side rendering 80 + - HTMX provides interactive behavior without page reloads 81 + 82 + ## License 83 + 84 + MIT
+25
deno.json
··· 1 + { 2 + "tasks": { 3 + "start": "deno run -A --env-file=.env src/main.ts", 4 + "dev": "deno run -A --env-file=.env --watch src/main.ts" 5 + }, 6 + "compilerOptions": { 7 + "jsx": "precompile", 8 + "jsxImportSource": "preact" 9 + }, 10 + "imports": { 11 + "@slices/client": "jsr:@slices/client@^0.1.0-alpha.4", 12 + "@slices/oauth": "jsr:@slices/oauth@^0.6.0", 13 + "@slices/session": "jsr:@slices/session@^0.3.0", 14 + "@std/assert": "jsr:@std/assert@^1.0.14", 15 + "@std/fmt": "jsr:@std/fmt@^1.0.8", 16 + "preact": "npm:preact@^10.27.1", 17 + "preact-render-to-string": "npm:preact-render-to-string@^6.5.13", 18 + "typed-htmx": "npm:typed-htmx@^0.3.1", 19 + "@std/http": "jsr:@std/http@^1.0.20", 20 + "clsx": "npm:clsx@^2.1.1", 21 + "tailwind-merge": "npm:tailwind-merge@^2.5.5", 22 + "lucide-preact": "npm:lucide-preact@^0.544.0" 23 + }, 24 + "nodeModulesDir": "auto" 25 + }
+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 + }
+90
lexicons/app/bsky/embed/images.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.images", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "required": [ 8 + "images" 9 + ], 10 + "properties": { 11 + "images": { 12 + "type": "array", 13 + "items": { 14 + "ref": "#image", 15 + "type": "ref" 16 + }, 17 + "maxLength": 4 18 + } 19 + } 20 + }, 21 + "view": { 22 + "type": "object", 23 + "required": [ 24 + "images" 25 + ], 26 + "properties": { 27 + "images": { 28 + "type": "array", 29 + "items": { 30 + "ref": "#viewImage", 31 + "type": "ref" 32 + }, 33 + "maxLength": 4 34 + } 35 + } 36 + }, 37 + "image": { 38 + "type": "object", 39 + "required": [ 40 + "image", 41 + "alt" 42 + ], 43 + "properties": { 44 + "alt": { 45 + "type": "string", 46 + "description": "Alt text description of the image, for accessibility." 47 + }, 48 + "image": { 49 + "type": "blob", 50 + "accept": [ 51 + "image/*" 52 + ], 53 + "maxSize": 1000000 54 + }, 55 + "aspectRatio": { 56 + "ref": "app.bsky.embed.defs#aspectRatio", 57 + "type": "ref" 58 + } 59 + } 60 + }, 61 + "viewImage": { 62 + "type": "object", 63 + "required": [ 64 + "thumb", 65 + "fullsize", 66 + "alt" 67 + ], 68 + "properties": { 69 + "alt": { 70 + "type": "string", 71 + "description": "Alt text description of the image, for accessibility." 72 + }, 73 + "thumb": { 74 + "type": "string", 75 + "format": "uri", 76 + "description": "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View." 77 + }, 78 + "fullsize": { 79 + "type": "string", 80 + "format": "uri", 81 + "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." 82 + }, 83 + "aspectRatio": { 84 + "ref": "app.bsky.embed.defs#aspectRatio", 85 + "type": "ref" 86 + } 87 + } 88 + } 89 + } 90 + }
+159
lexicons/app/bsky/embed/record.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.record", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "required": [ 8 + "record" 9 + ], 10 + "properties": { 11 + "record": { 12 + "ref": "com.atproto.repo.strongRef", 13 + "type": "ref" 14 + } 15 + } 16 + }, 17 + "view": { 18 + "type": "object", 19 + "required": [ 20 + "record" 21 + ], 22 + "properties": { 23 + "record": { 24 + "refs": [ 25 + "#viewRecord", 26 + "#viewNotFound", 27 + "#viewBlocked", 28 + "#viewDetached", 29 + "app.bsky.feed.defs#generatorView", 30 + "app.bsky.graph.defs#listView", 31 + "app.bsky.labeler.defs#labelerView", 32 + "app.bsky.graph.defs#starterPackViewBasic" 33 + ], 34 + "type": "union" 35 + } 36 + } 37 + }, 38 + "viewRecord": { 39 + "type": "object", 40 + "required": [ 41 + "uri", 42 + "cid", 43 + "author", 44 + "value", 45 + "indexedAt" 46 + ], 47 + "properties": { 48 + "cid": { 49 + "type": "string", 50 + "format": "cid" 51 + }, 52 + "uri": { 53 + "type": "string", 54 + "format": "at-uri" 55 + }, 56 + "value": { 57 + "type": "unknown", 58 + "description": "The record data itself." 59 + }, 60 + "author": { 61 + "ref": "app.bsky.actor.defs#profileViewBasic", 62 + "type": "ref" 63 + }, 64 + "embeds": { 65 + "type": "array", 66 + "items": { 67 + "refs": [ 68 + "app.bsky.embed.images#view", 69 + "app.bsky.embed.video#view", 70 + "app.bsky.embed.external#view", 71 + "app.bsky.embed.record#view", 72 + "app.bsky.embed.recordWithMedia#view" 73 + ], 74 + "type": "union" 75 + } 76 + }, 77 + "labels": { 78 + "type": "array", 79 + "items": { 80 + "ref": "com.atproto.label.defs#label", 81 + "type": "ref" 82 + } 83 + }, 84 + "indexedAt": { 85 + "type": "string", 86 + "format": "datetime" 87 + }, 88 + "likeCount": { 89 + "type": "integer" 90 + }, 91 + "quoteCount": { 92 + "type": "integer" 93 + }, 94 + "replyCount": { 95 + "type": "integer" 96 + }, 97 + "repostCount": { 98 + "type": "integer" 99 + } 100 + } 101 + }, 102 + "viewBlocked": { 103 + "type": "object", 104 + "required": [ 105 + "uri", 106 + "blocked", 107 + "author" 108 + ], 109 + "properties": { 110 + "uri": { 111 + "type": "string", 112 + "format": "at-uri" 113 + }, 114 + "author": { 115 + "ref": "app.bsky.feed.defs#blockedAuthor", 116 + "type": "ref" 117 + }, 118 + "blocked": { 119 + "type": "boolean", 120 + "const": true 121 + } 122 + } 123 + }, 124 + "viewDetached": { 125 + "type": "object", 126 + "required": [ 127 + "uri", 128 + "detached" 129 + ], 130 + "properties": { 131 + "uri": { 132 + "type": "string", 133 + "format": "at-uri" 134 + }, 135 + "detached": { 136 + "type": "boolean", 137 + "const": true 138 + } 139 + } 140 + }, 141 + "viewNotFound": { 142 + "type": "object", 143 + "required": [ 144 + "uri", 145 + "notFound" 146 + ], 147 + "properties": { 148 + "uri": { 149 + "type": "string", 150 + "format": "at-uri" 151 + }, 152 + "notFound": { 153 + "type": "boolean", 154 + "const": true 155 + } 156 + } 157 + } 158 + } 159 + }
+48
lexicons/app/bsky/embed/recordWithMedia.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.recordWithMedia", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "required": [ 8 + "record", 9 + "media" 10 + ], 11 + "properties": { 12 + "media": { 13 + "refs": [ 14 + "app.bsky.embed.images", 15 + "app.bsky.embed.video", 16 + "app.bsky.embed.external" 17 + ], 18 + "type": "union" 19 + }, 20 + "record": { 21 + "ref": "app.bsky.embed.record", 22 + "type": "ref" 23 + } 24 + } 25 + }, 26 + "view": { 27 + "type": "object", 28 + "required": [ 29 + "record", 30 + "media" 31 + ], 32 + "properties": { 33 + "media": { 34 + "refs": [ 35 + "app.bsky.embed.images#view", 36 + "app.bsky.embed.video#view", 37 + "app.bsky.embed.external#view" 38 + ], 39 + "type": "union" 40 + }, 41 + "record": { 42 + "ref": "app.bsky.embed.record#view", 43 + "type": "ref" 44 + } 45 + } 46 + } 47 + } 48 + }
+89
lexicons/app/bsky/embed/video.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.video", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "required": [ 8 + "video" 9 + ], 10 + "properties": { 11 + "alt": { 12 + "type": "string", 13 + "maxLength": 10000, 14 + "description": "Alt text description of the video, for accessibility.", 15 + "maxGraphemes": 1000 16 + }, 17 + "video": { 18 + "type": "blob", 19 + "accept": [ 20 + "video/mp4" 21 + ], 22 + "maxSize": 50000000 23 + }, 24 + "captions": { 25 + "type": "array", 26 + "items": { 27 + "ref": "#caption", 28 + "type": "ref" 29 + }, 30 + "maxLength": 20 31 + }, 32 + "aspectRatio": { 33 + "ref": "app.bsky.embed.defs#aspectRatio", 34 + "type": "ref" 35 + } 36 + } 37 + }, 38 + "view": { 39 + "type": "object", 40 + "required": [ 41 + "cid", 42 + "playlist" 43 + ], 44 + "properties": { 45 + "alt": { 46 + "type": "string", 47 + "maxLength": 10000, 48 + "maxGraphemes": 1000 49 + }, 50 + "cid": { 51 + "type": "string", 52 + "format": "cid" 53 + }, 54 + "playlist": { 55 + "type": "string", 56 + "format": "uri" 57 + }, 58 + "thumbnail": { 59 + "type": "string", 60 + "format": "uri" 61 + }, 62 + "aspectRatio": { 63 + "ref": "app.bsky.embed.defs#aspectRatio", 64 + "type": "ref" 65 + } 66 + } 67 + }, 68 + "caption": { 69 + "type": "object", 70 + "required": [ 71 + "lang", 72 + "file" 73 + ], 74 + "properties": { 75 + "file": { 76 + "type": "blob", 77 + "accept": [ 78 + "text/vtt" 79 + ], 80 + "maxSize": 20000 81 + }, 82 + "lang": { 83 + "type": "string", 84 + "format": "language" 85 + } 86 + } 87 + } 88 + } 89 + }
+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 + }
+191
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 + ], 12 + "properties": { 13 + "cid": { 14 + "type": "string", 15 + "format": "cid", 16 + "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." 17 + }, 18 + "cts": { 19 + "type": "string", 20 + "format": "datetime", 21 + "description": "Timestamp when this label was created." 22 + }, 23 + "exp": { 24 + "type": "string", 25 + "format": "datetime", 26 + "description": "Timestamp at which this label expires (no longer applies)." 27 + }, 28 + "neg": { 29 + "type": "boolean", 30 + "description": "If true, this is a negation label, overwriting a previous label." 31 + }, 32 + "sig": { 33 + "type": "bytes", 34 + "description": "Signature of dag-cbor encoded label." 35 + }, 36 + "src": { 37 + "type": "string", 38 + "format": "did", 39 + "description": "DID of the actor who created this label." 40 + }, 41 + "uri": { 42 + "type": "string", 43 + "format": "uri", 44 + "description": "AT URI of the record, repository (account), or other resource that this label applies to." 45 + }, 46 + "val": { 47 + "type": "string", 48 + "maxLength": 128, 49 + "description": "The short string name of the value or type of this label." 50 + }, 51 + "ver": { 52 + "type": "integer", 53 + "description": "The AT Protocol version of the label object." 54 + } 55 + }, 56 + "description": "Metadata tag on an atproto resource (eg, repo or record)." 57 + }, 58 + "selfLabel": { 59 + "type": "object", 60 + "required": [ 61 + "val" 62 + ], 63 + "properties": { 64 + "val": { 65 + "type": "string", 66 + "maxLength": 128, 67 + "description": "The short string name of the value or type of this label." 68 + } 69 + }, 70 + "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel." 71 + }, 72 + "labelValue": { 73 + "type": "string", 74 + "knownValues": [ 75 + "!hide", 76 + "!no-promote", 77 + "!warn", 78 + "!no-unauthenticated", 79 + "dmca-violation", 80 + "doxxing", 81 + "porn", 82 + "sexual", 83 + "nudity", 84 + "nsfl", 85 + "gore" 86 + ] 87 + }, 88 + "selfLabels": { 89 + "type": "object", 90 + "required": [ 91 + "values" 92 + ], 93 + "properties": { 94 + "values": { 95 + "type": "array", 96 + "items": { 97 + "ref": "#selfLabel", 98 + "type": "ref" 99 + }, 100 + "maxLength": 10 101 + } 102 + }, 103 + "description": "Metadata tags on an atproto record, published by the author within the record." 104 + }, 105 + "labelValueDefinition": { 106 + "type": "object", 107 + "required": [ 108 + "identifier", 109 + "severity", 110 + "blurs", 111 + "locales" 112 + ], 113 + "properties": { 114 + "blurs": { 115 + "type": "string", 116 + "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.", 117 + "knownValues": [ 118 + "content", 119 + "media", 120 + "none" 121 + ] 122 + }, 123 + "locales": { 124 + "type": "array", 125 + "items": { 126 + "ref": "#labelValueDefinitionStrings", 127 + "type": "ref" 128 + } 129 + }, 130 + "severity": { 131 + "type": "string", 132 + "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 133 + "knownValues": [ 134 + "inform", 135 + "alert", 136 + "none" 137 + ] 138 + }, 139 + "adultOnly": { 140 + "type": "boolean", 141 + "description": "Does the user need to have adult content enabled in order to configure this label?" 142 + }, 143 + "identifier": { 144 + "type": "string", 145 + "maxLength": 100, 146 + "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 147 + "maxGraphemes": 100 148 + }, 149 + "defaultSetting": { 150 + "type": "string", 151 + "default": "warn", 152 + "description": "The default setting for this label.", 153 + "knownValues": [ 154 + "ignore", 155 + "warn", 156 + "hide" 157 + ] 158 + } 159 + }, 160 + "description": "Declares a label value and its expected interpretations and behaviors." 161 + }, 162 + "labelValueDefinitionStrings": { 163 + "type": "object", 164 + "required": [ 165 + "lang", 166 + "name", 167 + "description" 168 + ], 169 + "properties": { 170 + "lang": { 171 + "type": "string", 172 + "format": "language", 173 + "description": "The code of the language these strings are written in." 174 + }, 175 + "name": { 176 + "type": "string", 177 + "maxLength": 640, 178 + "description": "A short human-readable name for the label.", 179 + "maxGraphemes": 64 180 + }, 181 + "description": { 182 + "type": "string", 183 + "maxLength": 100000, 184 + "description": "A longer description of what the label means and why it might be applied.", 185 + "maxGraphemes": 10000 186 + } 187 + }, 188 + "description": "Strings which describe the label in the UI, localized into a specific language." 189 + } 190 + } 191 + }
+23
lexicons/com/atproto/repo/strongRef.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.strongRef", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "required": [ 8 + "uri", 9 + "cid" 10 + ], 11 + "properties": { 12 + "cid": { 13 + "type": "string", 14 + "format": "cid" 15 + }, 16 + "uri": { 17 + "type": "string", 18 + "format": "at-uri" 19 + } 20 + } 21 + } 22 + } 23 + }
+5
slices.json
··· 1 + { 2 + "slice": "at://did:plc:giaakn4axmr5dhfnvha6r6wn/network.slices.slice/3m2gjibk6xc24", 3 + "lexiconPath": "./lexicons", 4 + "clientOutputPath": "./src/generated_client.ts" 5 + }
+75
src/config.ts
··· 1 + import { OAuthClient, SQLiteOAuthStorage } from "@slices/oauth"; 2 + import { SessionStore, SQLiteAdapter, withOAuthSession } from "@slices/session"; 3 + import { AtProtoClient } from "./generated_client.ts"; 4 + 5 + const OAUTH_CLIENT_ID = Deno.env.get("OAUTH_CLIENT_ID"); 6 + const OAUTH_CLIENT_SECRET = Deno.env.get("OAUTH_CLIENT_SECRET"); 7 + const OAUTH_REDIRECT_URI = Deno.env.get("OAUTH_REDIRECT_URI"); 8 + const OAUTH_AIP_BASE_URL = Deno.env.get("OAUTH_AIP_BASE_URL"); 9 + const API_URL = Deno.env.get("API_URL"); 10 + export const SLICE_URI = Deno.env.get("SLICE_URI"); 11 + 12 + if ( 13 + !OAUTH_CLIENT_ID || 14 + !OAUTH_CLIENT_SECRET || 15 + !OAUTH_REDIRECT_URI || 16 + !OAUTH_AIP_BASE_URL || 17 + !API_URL || 18 + !SLICE_URI 19 + ) { 20 + throw new Error( 21 + "Missing OAuth configuration. Please ensure .env file contains:\n" + 22 + "OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_REDIRECT_URI, OAUTH_AIP_BASE_URL, API_URL, SLICE_URI" 23 + ); 24 + } 25 + 26 + const DATABASE_URL = Deno.env.get("DATABASE_URL") || "slices.db"; 27 + 28 + // OAuth setup 29 + const oauthStorage = new SQLiteOAuthStorage(DATABASE_URL); 30 + const oauthConfig = { 31 + clientId: OAUTH_CLIENT_ID, 32 + clientSecret: OAUTH_CLIENT_SECRET, 33 + authBaseUrl: OAUTH_AIP_BASE_URL, 34 + redirectUri: OAUTH_REDIRECT_URI, 35 + scopes: ["atproto", "openid", "profile"], 36 + }; 37 + 38 + // Export config and storage for creating user-scoped clients 39 + export { oauthConfig, oauthStorage }; 40 + 41 + // Session setup (shared database) 42 + export const sessionStore = new SessionStore({ 43 + adapter: new SQLiteAdapter(DATABASE_URL), 44 + cookieName: "indiemusich-session", 45 + cookieOptions: { 46 + httpOnly: true, 47 + secure: Deno.env.get("DENO_ENV") === "production", 48 + sameSite: "lax", 49 + path: "/", 50 + }, 51 + }); 52 + 53 + // OAuth + Session integration 54 + export const oauthSessions = withOAuthSession( 55 + sessionStore, 56 + oauthConfig, 57 + oauthStorage, 58 + { 59 + autoRefresh: true, 60 + } 61 + ); 62 + 63 + // Helper function to create user-scoped OAuth client 64 + export function createOAuthClient(userId: string): OAuthClient { 65 + return new OAuthClient(oauthConfig, oauthStorage, userId); 66 + } 67 + 68 + // Helper function to create authenticated AtProto client for a user 69 + export function createSessionClient(userId: string): AtProtoClient { 70 + const userOAuthClient = createOAuthClient(userId); 71 + return new AtProtoClient(API_URL!, SLICE_URI!, userOAuthClient); 72 + } 73 + 74 + // Public client for unauthenticated requests 75 + export const publicClient = new AtProtoClient(API_URL, SLICE_URI);
+169
src/features/auth/handlers.tsx
··· 1 + import type { Route } from "@std/http/unstable-route"; 2 + import { withAuth } from "../../routes/middleware.ts"; 3 + import { OAuthClient } from "@slices/oauth"; 4 + import { 5 + createOAuthClient, 6 + createSessionClient, 7 + oauthConfig, 8 + oauthStorage, 9 + oauthSessions, 10 + sessionStore, 11 + } from "../../config.ts"; 12 + import { renderHTML } from "../../utils/render.tsx"; 13 + import { LoginPage } from "./templates/LoginPage.tsx"; 14 + 15 + async function handleLoginPage(req: Request): Promise<Response> { 16 + const context = await withAuth(req); 17 + const url = new URL(req.url); 18 + 19 + // Redirect if already logged in 20 + if (context.currentUser) { 21 + return Response.redirect(new URL("/dashboard", req.url), 302); 22 + } 23 + 24 + const error = url.searchParams.get("error"); 25 + return renderHTML(<LoginPage error={error || undefined} />); 26 + } 27 + 28 + async function handleOAuthAuthorize(req: Request): Promise<Response> { 29 + try { 30 + const formData = await req.formData(); 31 + const loginHint = formData.get("loginHint") as string; 32 + 33 + if (!loginHint) { 34 + return new Response("Missing login hint", { status: 400 }); 35 + } 36 + 37 + const tempOAuthClient = new OAuthClient( 38 + oauthConfig, 39 + oauthStorage, 40 + loginHint 41 + ); 42 + const authResult = await tempOAuthClient.authorize({ 43 + loginHint, 44 + }); 45 + 46 + return Response.redirect(authResult.authorizationUrl, 302); 47 + } catch (error) { 48 + console.error("OAuth authorize error:", error); 49 + 50 + return Response.redirect( 51 + new URL( 52 + "/login?error=" + 53 + encodeURIComponent("Please check your handle and try again."), 54 + req.url 55 + ), 56 + 302 57 + ); 58 + } 59 + } 60 + 61 + async function handleOAuthCallback(req: Request): Promise<Response> { 62 + try { 63 + const url = new URL(req.url); 64 + const code = url.searchParams.get("code"); 65 + const state = url.searchParams.get("state"); 66 + 67 + if (!code || !state) { 68 + return Response.redirect( 69 + new URL( 70 + "/login?error=" + encodeURIComponent("Invalid OAuth callback"), 71 + req.url 72 + ), 73 + 302 74 + ); 75 + } 76 + 77 + const tempOAuthClient = new OAuthClient(oauthConfig, oauthStorage, "temp"); 78 + const tokens = await tempOAuthClient.handleCallback({ code, state }); 79 + const sessionId = await oauthSessions.createOAuthSession(tokens); 80 + 81 + if (!sessionId) { 82 + return Response.redirect( 83 + new URL( 84 + "/login?error=" + encodeURIComponent("Failed to create session"), 85 + req.url 86 + ), 87 + 302 88 + ); 89 + } 90 + 91 + const sessionCookie = sessionStore.createSessionCookie(sessionId); 92 + 93 + let userInfo; 94 + try { 95 + const sessionOAuthClient = createOAuthClient(sessionId); 96 + userInfo = await sessionOAuthClient.getUserInfo(); 97 + } catch (error) { 98 + console.error("Failed to get user info:", error); 99 + } 100 + 101 + if (userInfo?.sub) { 102 + try { 103 + const userClient = createSessionClient(sessionId); 104 + await userClient.syncUserCollections(); 105 + console.log("Synced Bluesky profile for", userInfo.sub); 106 + } catch (error) { 107 + console.error("Error syncing Bluesky profile:", error); 108 + } 109 + } 110 + 111 + return new Response(null, { 112 + status: 302, 113 + headers: { 114 + Location: new URL("/dashboard", req.url).toString(), 115 + "Set-Cookie": sessionCookie, 116 + }, 117 + }); 118 + } catch (error) { 119 + console.error("OAuth callback error:", error); 120 + return Response.redirect( 121 + new URL( 122 + "/login?error=" + encodeURIComponent("Authentication failed"), 123 + req.url 124 + ), 125 + 302 126 + ); 127 + } 128 + } 129 + 130 + async function handleLogout(req: Request): Promise<Response> { 131 + const session = await sessionStore.getSessionFromRequest(req); 132 + 133 + if (session) { 134 + await oauthSessions.logout(session.sessionId); 135 + } 136 + 137 + const clearCookie = sessionStore.createLogoutCookie(); 138 + 139 + return new Response(null, { 140 + status: 302, 141 + headers: { 142 + Location: new URL("/login", req.url).toString(), 143 + "Set-Cookie": clearCookie, 144 + }, 145 + }); 146 + } 147 + 148 + export const authRoutes: Route[] = [ 149 + { 150 + method: "GET", 151 + pattern: new URLPattern({ pathname: "/login" }), 152 + handler: handleLoginPage, 153 + }, 154 + { 155 + method: "POST", 156 + pattern: new URLPattern({ pathname: "/oauth/authorize" }), 157 + handler: handleOAuthAuthorize, 158 + }, 159 + { 160 + method: "GET", 161 + pattern: new URLPattern({ pathname: "/oauth/callback" }), 162 + handler: handleOAuthCallback, 163 + }, 164 + { 165 + method: "POST", 166 + pattern: new URLPattern({ pathname: "/logout" }), 167 + handler: handleLogout, 168 + }, 169 + ];
+56
src/features/auth/templates/LoginPage.tsx
··· 1 + import { Layout } from "../../../shared/fragments/Layout.tsx"; 2 + import { Button } from "../../../shared/fragments/Button.tsx"; 3 + import { Input } from "../../../shared/fragments/Input.tsx"; 4 + 5 + interface LoginPageProps { 6 + error?: string; 7 + } 8 + 9 + export function LoginPage({ error }: LoginPageProps) { 10 + return ( 11 + <Layout title="Login"> 12 + <div className="min-h-screen flex items-center justify-center bg-gray-50"> 13 + <div className="max-w-md w-full space-y-8"> 14 + <div> 15 + <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900"> 16 + Sign in to your account 17 + </h2> 18 + <p className="mt-2 text-center text-sm text-gray-600"> 19 + Use your AT Protocol handle or DID 20 + </p> 21 + </div> 22 + 23 + {error && ( 24 + <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded"> 25 + {error === "OAuth initialization failed" && "Failed to start authentication"} 26 + {error === "Invalid OAuth callback" && "Authentication callback failed"} 27 + {error === "Authentication failed" && "Authentication failed"} 28 + {error === "Failed to create session" && "Failed to create session"} 29 + {!["OAuth initialization failed", "Invalid OAuth callback", "Authentication failed", "Failed to create session"].includes(error) && error} 30 + </div> 31 + )} 32 + 33 + <form className="mt-8 space-y-6" action="/oauth/authorize" method="post"> 34 + <div> 35 + <label htmlFor="loginHint" className="block text-sm font-medium text-gray-700"> 36 + Handle or DID 37 + </label> 38 + <Input 39 + id="loginHint" 40 + name="loginHint" 41 + type="text" 42 + required 43 + placeholder="alice.bsky.social or did:plc:..." 44 + className="mt-1" 45 + /> 46 + </div> 47 + 48 + <Button type="submit" className="w-full"> 49 + Sign in 50 + </Button> 51 + </form> 52 + </div> 53 + </div> 54 + </Layout> 55 + ); 56 + }
+49
src/features/dashboard/handlers.tsx
··· 1 + import type { Route } from "@std/http/unstable-route"; 2 + import { withAuth } from "../../routes/middleware.ts"; 3 + import { renderHTML } from "../../utils/render.tsx"; 4 + import { DashboardPage } from "./templates/DashboardPage.tsx"; 5 + import { publicClient } from "../../config.ts"; 6 + import { recordBlobToCdnUrl } from "@slices/client"; 7 + import { AppBskyActorProfile } from "../../generated_client.ts"; 8 + 9 + async function handleDashboard(req: Request): Promise<Response> { 10 + const context = await withAuth(req); 11 + 12 + if (!context.currentUser) { 13 + return Response.redirect(new URL("/login", req.url), 302); 14 + } 15 + 16 + let profile: AppBskyActorProfile | undefined; 17 + let avatarUrl: string | undefined; 18 + try { 19 + const profileResult = await publicClient.app.bsky.actor.profile.getRecord({ 20 + uri: `at://${context.currentUser.sub}/app.bsky.actor.profile/self`, 21 + }); 22 + 23 + if (profileResult) { 24 + profile = profileResult.value; 25 + 26 + if (profile.avatar) { 27 + avatarUrl = recordBlobToCdnUrl(profileResult, profile.avatar, "avatar"); 28 + } 29 + } 30 + } catch (error) { 31 + console.error("Error fetching profile:", error); 32 + } 33 + 34 + return renderHTML( 35 + <DashboardPage 36 + currentUser={context.currentUser} 37 + profile={profile} 38 + avatarUrl={avatarUrl} 39 + /> 40 + ); 41 + } 42 + 43 + export const dashboardRoutes: Route[] = [ 44 + { 45 + method: "GET", 46 + pattern: new URLPattern({ pathname: "/dashboard" }), 47 + handler: handleDashboard, 48 + }, 49 + ];
+56
src/features/dashboard/templates/DashboardPage.tsx
··· 1 + import { Layout } from "../../../shared/fragments/Layout.tsx"; 2 + import { Button } from "../../../shared/fragments/Button.tsx"; 3 + import type { AppBskyActorProfile } from "../../../generated_client.ts"; 4 + 5 + interface DashboardPageProps { 6 + currentUser: { 7 + name?: string; 8 + sub: string; 9 + }; 10 + profile?: AppBskyActorProfile; 11 + avatarUrl?: string; 12 + } 13 + 14 + export function DashboardPage({ 15 + currentUser, 16 + profile, 17 + avatarUrl, 18 + }: DashboardPageProps) { 19 + return ( 20 + <Layout title="Dashboard"> 21 + <div className="min-h-screen bg-gray-50 p-8"> 22 + <div className="max-w-2xl mx-auto"> 23 + <div className="bg-white rounded-lg shadow p-6"> 24 + <div className="flex justify-between items-center mb-6"> 25 + <h1 className="text-2xl font-bold">Dashboard</h1> 26 + <form method="post" action="/logout"> 27 + <Button type="submit" variant="secondary"> 28 + Logout 29 + </Button> 30 + </form> 31 + </div> 32 + 33 + <div className="mb-6"> 34 + {avatarUrl && ( 35 + <img 36 + src={avatarUrl} 37 + alt="Profile" 38 + className="w-20 h-20 rounded-full mb-4" 39 + /> 40 + )} 41 + <h2 className="text-xl font-semibold mb-2"> 42 + {profile?.displayName || currentUser.name || currentUser.sub} 43 + </h2> 44 + {currentUser.name && ( 45 + <p className="text-gray-600 mb-2">@{currentUser.name}</p> 46 + )} 47 + {profile?.description && ( 48 + <p className="text-gray-700 mt-2">{profile.description}</p> 49 + )} 50 + </div> 51 + </div> 52 + </div> 53 + </div> 54 + </Layout> 55 + ); 56 + }
+1402
src/generated_client.ts
··· 1 + // Generated TypeScript client for AT Protocol records 2 + // Generated at: 2025-10-05 06:58:57 UTC 3 + // Lexicons: 16 4 + 5 + /** 6 + * @example Usage 7 + * ```ts 8 + * import { AtProtoClient } from "./generated_client.ts"; 9 + * 10 + * const client = new AtProtoClient( 11 + * 'https://api.slices.network', 12 + * 'at://did:plc:giaakn4axmr5dhfnvha6r6wn/network.slices.slice/3m2gjibk6xc24' 13 + * ); 14 + * 15 + * // Get records from the app.bsky.feed.postgate collection 16 + * const records = await client.app.bsky.feed.postgate.getRecords(); 17 + * 18 + * // Get a specific record 19 + * const record = await client.app.bsky.feed.postgate.getRecord({ 20 + * uri: 'at://did:plc:example/app.bsky.feed.postgate/3abc123' 21 + * }); 22 + * 23 + * // Get records with filtering and search 24 + * const filteredRecords = await client.app.bsky.feed.postgate.getRecords({ 25 + * where: { 26 + * text: { contains: "example search term" } 27 + * } 28 + * }); 29 + * 30 + * // Use slice-level methods for cross-collection queries with type safety 31 + * const sliceRecords = await client.network.slices.slice.getSliceRecords<AppBskyFeedPostgate>({ 32 + * where: { 33 + * collection: { eq: 'app.bsky.feed.postgate' } 34 + * } 35 + * }); 36 + * 37 + * // Search across multiple collections using union types 38 + * const multiCollectionRecords = await client.network.slices.slice.getSliceRecords<AppBskyFeedPostgate | AppBskyActorProfile>({ 39 + * where: { 40 + * collection: { in: ['app.bsky.feed.postgate', 'app.bsky.actor.profile'] }, 41 + * text: { contains: 'example search term' }, 42 + * did: { in: ['did:plc:user1', 'did:plc:user2'] } 43 + * }, 44 + * limit: 20 45 + * }); 46 + * 47 + * // Serve the records as JSON 48 + * Deno.serve(async () => new Response(JSON.stringify(records.records.map(r => r.value)))); 49 + * ``` 50 + */ 51 + 52 + import { 53 + type AuthProvider, 54 + type BlobRef, 55 + type CountRecordsResponse, 56 + type GetRecordParams, 57 + type GetRecordsResponse, 58 + type IndexedRecordFields, 59 + type RecordResponse, 60 + SlicesClient, 61 + type SortField, 62 + type WhereCondition, 63 + } from "@slices/client"; 64 + import type { OAuthClient } from "@slices/oauth"; 65 + 66 + export type AppBskyGraphDefsListPurpose = 67 + | "app.bsky.graph.defs#modlist" 68 + | "app.bsky.graph.defs#curatelist" 69 + | "app.bsky.graph.defs#referencelist" 70 + | (string & Record<string, never>); 71 + 72 + export type AppBskyFeedDefsEvent = 73 + | "app.bsky.feed.defs#requestLess" 74 + | "app.bsky.feed.defs#requestMore" 75 + | "app.bsky.feed.defs#clickthroughItem" 76 + | "app.bsky.feed.defs#clickthroughAuthor" 77 + | "app.bsky.feed.defs#clickthroughReposter" 78 + | "app.bsky.feed.defs#clickthroughEmbed" 79 + | "app.bsky.feed.defs#interactionSeen" 80 + | "app.bsky.feed.defs#interactionLike" 81 + | "app.bsky.feed.defs#interactionRepost" 82 + | "app.bsky.feed.defs#interactionReply" 83 + | "app.bsky.feed.defs#interactionQuote" 84 + | "app.bsky.feed.defs#interactionShare" 85 + | (string & Record<string, never>); 86 + 87 + export type AppBskyFeedDefsContentMode = 88 + | "app.bsky.feed.defs#contentModeUnspecified" 89 + | "app.bsky.feed.defs#contentModeVideo" 90 + | (string & Record<string, never>); 91 + 92 + export type AppBskyActorDefsActorTarget = 93 + | "all" 94 + | "exclude-following" 95 + | (string & Record<string, never>); 96 + 97 + export type AppBskyActorDefsType = 98 + | "feed" 99 + | "list" 100 + | "timeline" 101 + | (string & Record<string, never>); 102 + 103 + export type AppBskyActorDefsSort = 104 + | "oldest" 105 + | "newest" 106 + | "most-likes" 107 + | "random" 108 + | "hotness" 109 + | (string & Record<string, never>); 110 + 111 + export type AppBskyActorDefsMutedWordTarget = 112 + | "content" 113 + | "tag" 114 + | (string & Record<string, never>); 115 + 116 + export type AppBskyActorDefsVisibility = 117 + | "ignore" 118 + | "show" 119 + | "warn" 120 + | "hide" 121 + | (string & Record<string, never>); 122 + 123 + export type AppBskyActorDefsAllowIncoming = 124 + | "all" 125 + | "none" 126 + | "following" 127 + | (string & Record<string, never>); 128 + 129 + export type ComAtprotoLabelDefsLabelValue = 130 + | "!hide" 131 + | "!no-promote" 132 + | "!warn" 133 + | "!no-unauthenticated" 134 + | "dmca-violation" 135 + | "doxxing" 136 + | "porn" 137 + | "sexual" 138 + | "nudity" 139 + | "nsfl" 140 + | "gore" 141 + | (string & Record<string, never>); 142 + 143 + export type ComAtprotoLabelDefsBlurs = 144 + | "content" 145 + | "media" 146 + | "none" 147 + | (string & Record<string, never>); 148 + 149 + export type ComAtprotoLabelDefsSeverity = 150 + | "inform" 151 + | "alert" 152 + | "none" 153 + | (string & Record<string, never>); 154 + 155 + export type ComAtprotoLabelDefsDefaultSetting = 156 + | "ignore" 157 + | "warn" 158 + | "hide" 159 + | (string & Record<string, never>); 160 + 161 + export interface AppBskyEmbedDefsAspectRatio { 162 + width: number; 163 + height: number; 164 + } 165 + 166 + export interface AppBskyEmbedRecordMain { 167 + record: ComAtprotoRepoStrongRef; 168 + } 169 + 170 + export interface AppBskyEmbedRecordView { 171 + record: 172 + | AppBskyEmbedRecord["ViewRecord"] 173 + | AppBskyEmbedRecord["ViewNotFound"] 174 + | AppBskyEmbedRecord["ViewBlocked"] 175 + | AppBskyEmbedRecord["ViewDetached"] 176 + | AppBskyFeedDefs["GeneratorView"] 177 + | AppBskyGraphDefs["ListView"] 178 + | AppBskyLabelerDefs["LabelerView"] 179 + | AppBskyGraphDefs["StarterPackViewBasic"] 180 + | { $type: string; [key: string]: unknown }; 181 + } 182 + 183 + export interface AppBskyEmbedRecordViewRecord { 184 + cid: string; 185 + uri: string; 186 + /** The record data itself. */ 187 + value: unknown; 188 + author: AppBskyActorDefs["ProfileViewBasic"]; 189 + embeds?: 190 + | AppBskyEmbedImages["View"] 191 + | AppBskyEmbedVideo["View"] 192 + | AppBskyEmbedExternal["View"] 193 + | AppBskyEmbedRecord["View"] 194 + | AppBskyEmbedRecordWithMedia["View"] 195 + | { $type: string; [key: string]: unknown }[]; 196 + labels?: ComAtprotoLabelDefs["Label"][]; 197 + indexedAt: string; 198 + likeCount?: number; 199 + quoteCount?: number; 200 + replyCount?: number; 201 + repostCount?: number; 202 + } 203 + 204 + export interface AppBskyEmbedRecordViewBlocked { 205 + uri: string; 206 + author: AppBskyFeedDefs["BlockedAuthor"]; 207 + blocked: boolean; 208 + } 209 + 210 + export interface AppBskyEmbedRecordViewDetached { 211 + uri: string; 212 + detached: boolean; 213 + } 214 + 215 + export interface AppBskyEmbedRecordViewNotFound { 216 + uri: string; 217 + notFound: boolean; 218 + } 219 + 220 + export interface AppBskyEmbedImagesMain { 221 + images: AppBskyEmbedImages["Image"][]; 222 + } 223 + 224 + export interface AppBskyEmbedImagesView { 225 + images: AppBskyEmbedImages["ViewImage"][]; 226 + } 227 + 228 + export interface AppBskyEmbedImagesImage { 229 + /** Alt text description of the image, for accessibility. */ 230 + alt: string; 231 + image: BlobRef; 232 + aspectRatio?: AppBskyEmbedDefs["AspectRatio"]; 233 + } 234 + 235 + export interface AppBskyEmbedImagesViewImage { 236 + /** Alt text description of the image, for accessibility. */ 237 + alt: string; 238 + /** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. */ 239 + thumb: string; 240 + /** 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. */ 241 + fullsize: string; 242 + aspectRatio?: AppBskyEmbedDefs["AspectRatio"]; 243 + } 244 + 245 + export interface AppBskyEmbedRecordWithMediaMain { 246 + media: 247 + | AppBskyEmbedImages["Main"] 248 + | AppBskyEmbedVideo["Main"] 249 + | AppBskyEmbedExternal["Main"] 250 + | { $type: string; [key: string]: unknown }; 251 + record: AppBskyEmbedRecord["Main"]; 252 + } 253 + 254 + export interface AppBskyEmbedRecordWithMediaView { 255 + media: 256 + | AppBskyEmbedImages["View"] 257 + | AppBskyEmbedVideo["View"] 258 + | AppBskyEmbedExternal["View"] 259 + | { $type: string; [key: string]: unknown }; 260 + record: AppBskyEmbedRecord["View"]; 261 + } 262 + 263 + export interface AppBskyEmbedVideoMain { 264 + /** Alt text description of the video, for accessibility. */ 265 + alt?: string; 266 + video: BlobRef; 267 + captions?: AppBskyEmbedVideo["Caption"][]; 268 + aspectRatio?: AppBskyEmbedDefs["AspectRatio"]; 269 + } 270 + 271 + export interface AppBskyEmbedVideoView { 272 + alt?: string; 273 + cid: string; 274 + playlist: string; 275 + thumbnail?: string; 276 + aspectRatio?: AppBskyEmbedDefs["AspectRatio"]; 277 + } 278 + 279 + export interface AppBskyEmbedVideoCaption { 280 + file: BlobRef; 281 + lang: string; 282 + } 283 + 284 + export interface AppBskyEmbedExternalMain { 285 + external: AppBskyEmbedExternal["External"]; 286 + } 287 + 288 + export interface AppBskyEmbedExternalView { 289 + external: AppBskyEmbedExternal["ViewExternal"]; 290 + } 291 + 292 + export interface AppBskyEmbedExternalExternal { 293 + uri: string; 294 + thumb?: BlobRef; 295 + title: string; 296 + description: string; 297 + } 298 + 299 + export interface AppBskyEmbedExternalViewExternal { 300 + uri: string; 301 + thumb?: string; 302 + title: string; 303 + description: string; 304 + } 305 + 306 + export type AppBskyGraphDefsModlist = "app.bsky.graph.defs#modlist"; 307 + 308 + export interface AppBskyGraphDefsListView { 309 + cid: string; 310 + uri: string; 311 + name: string; 312 + avatar?: string; 313 + labels?: ComAtprotoLabelDefs["Label"][]; 314 + viewer?: AppBskyGraphDefs["ListViewerState"]; 315 + creator: AppBskyActorDefs["ProfileView"]; 316 + purpose: AppBskyGraphDefs["ListPurpose"]; 317 + indexedAt: string; 318 + description?: string; 319 + listItemCount?: number; 320 + descriptionFacets?: AppBskyRichtextFacet["Main"][]; 321 + } 322 + 323 + export type AppBskyGraphDefsCuratelist = "app.bsky.graph.defs#curatelist"; 324 + 325 + export interface AppBskyGraphDefsListItemView { 326 + uri: string; 327 + subject: AppBskyActorDefs["ProfileView"]; 328 + } 329 + 330 + export interface AppBskyGraphDefsRelationship { 331 + did: string; 332 + /** if the actor follows this DID, this is the AT-URI of the follow record */ 333 + following?: string; 334 + /** if the actor is followed by this DID, contains the AT-URI of the follow record */ 335 + followedBy?: string; 336 + } 337 + 338 + export interface AppBskyGraphDefsListViewBasic { 339 + cid: string; 340 + uri: string; 341 + name: string; 342 + avatar?: string; 343 + labels?: ComAtprotoLabelDefs["Label"][]; 344 + viewer?: AppBskyGraphDefs["ListViewerState"]; 345 + purpose: AppBskyGraphDefs["ListPurpose"]; 346 + indexedAt?: string; 347 + listItemCount?: number; 348 + } 349 + 350 + export interface AppBskyGraphDefsNotFoundActor { 351 + actor: string; 352 + notFound: boolean; 353 + } 354 + 355 + export type AppBskyGraphDefsReferencelist = "app.bsky.graph.defs#referencelist"; 356 + 357 + export interface AppBskyGraphDefsListViewerState { 358 + muted?: boolean; 359 + blocked?: string; 360 + } 361 + 362 + export interface AppBskyGraphDefsStarterPackView { 363 + cid: string; 364 + uri: string; 365 + list?: AppBskyGraphDefs["ListViewBasic"]; 366 + feeds?: AppBskyFeedDefs["GeneratorView"][]; 367 + labels?: ComAtprotoLabelDefs["Label"][]; 368 + record: unknown; 369 + creator: AppBskyActorDefs["ProfileViewBasic"]; 370 + indexedAt: string; 371 + joinedWeekCount?: number; 372 + listItemsSample?: AppBskyGraphDefs["ListItemView"][]; 373 + joinedAllTimeCount?: number; 374 + } 375 + 376 + export interface AppBskyGraphDefsStarterPackViewBasic { 377 + cid: string; 378 + uri: string; 379 + labels?: ComAtprotoLabelDefs["Label"][]; 380 + record: unknown; 381 + creator: AppBskyActorDefs["ProfileViewBasic"]; 382 + indexedAt: string; 383 + listItemCount?: number; 384 + joinedWeekCount?: number; 385 + joinedAllTimeCount?: number; 386 + } 387 + 388 + export interface AppBskyFeedDefsPostView { 389 + cid: string; 390 + uri: string; 391 + embed?: 392 + | AppBskyEmbedImages["View"] 393 + | AppBskyEmbedVideo["View"] 394 + | AppBskyEmbedExternal["View"] 395 + | AppBskyEmbedRecord["View"] 396 + | AppBskyEmbedRecordWithMedia["View"] 397 + | { $type: string; [key: string]: unknown }; 398 + author: AppBskyActorDefs["ProfileViewBasic"]; 399 + labels?: ComAtprotoLabelDefs["Label"][]; 400 + record: unknown; 401 + viewer?: AppBskyFeedDefs["ViewerState"]; 402 + indexedAt: string; 403 + likeCount?: number; 404 + quoteCount?: number; 405 + replyCount?: number; 406 + threadgate?: AppBskyFeedDefs["ThreadgateView"]; 407 + repostCount?: number; 408 + } 409 + 410 + export interface AppBskyFeedDefsReplyRef { 411 + root: 412 + | AppBskyFeedDefs["PostView"] 413 + | AppBskyFeedDefs["NotFoundPost"] 414 + | AppBskyFeedDefs["BlockedPost"] 415 + | { $type: string; [key: string]: unknown }; 416 + parent: 417 + | AppBskyFeedDefs["PostView"] 418 + | AppBskyFeedDefs["NotFoundPost"] 419 + | AppBskyFeedDefs["BlockedPost"] 420 + | { $type: string; [key: string]: unknown }; 421 + /** When parent is a reply to another post, this is the author of that post. */ 422 + grandparentAuthor?: AppBskyActorDefs["ProfileViewBasic"]; 423 + } 424 + 425 + export type AppBskyFeedDefsReasonPin = Record<string, never>; 426 + 427 + export interface AppBskyFeedDefsBlockedPost { 428 + uri: string; 429 + author: AppBskyFeedDefs["BlockedAuthor"]; 430 + blocked: boolean; 431 + } 432 + 433 + export interface AppBskyFeedDefsInteraction { 434 + item?: string; 435 + event?: AppBskyFeedDefsEvent; 436 + /** Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton. */ 437 + feedContext?: string; 438 + } 439 + 440 + export type AppBskyFeedDefsRequestLess = "app.bsky.feed.defs#requestLess"; 441 + export type AppBskyFeedDefsRequestMore = "app.bsky.feed.defs#requestMore"; 442 + 443 + export interface AppBskyFeedDefsViewerState { 444 + like?: string; 445 + pinned?: boolean; 446 + repost?: string; 447 + threadMuted?: boolean; 448 + replyDisabled?: boolean; 449 + embeddingDisabled?: boolean; 450 + } 451 + 452 + export interface AppBskyFeedDefsFeedViewPost { 453 + post: AppBskyFeedDefs["PostView"]; 454 + reply?: AppBskyFeedDefs["ReplyRef"]; 455 + reason?: AppBskyFeedDefs["ReasonRepost"] | AppBskyFeedDefs["ReasonPin"] | { 456 + $type: string; 457 + [key: string]: unknown; 458 + }; 459 + /** Context provided by feed generator that may be passed back alongside interactions. */ 460 + feedContext?: string; 461 + } 462 + 463 + export interface AppBskyFeedDefsNotFoundPost { 464 + uri: string; 465 + notFound: boolean; 466 + } 467 + 468 + export interface AppBskyFeedDefsReasonRepost { 469 + by: AppBskyActorDefs["ProfileViewBasic"]; 470 + indexedAt: string; 471 + } 472 + 473 + export interface AppBskyFeedDefsBlockedAuthor { 474 + did: string; 475 + viewer?: AppBskyActorDefs["ViewerState"]; 476 + } 477 + 478 + export interface AppBskyFeedDefsGeneratorView { 479 + cid: string; 480 + did: string; 481 + uri: string; 482 + avatar?: string; 483 + labels?: ComAtprotoLabelDefs["Label"][]; 484 + viewer?: AppBskyFeedDefs["GeneratorViewerState"]; 485 + creator: AppBskyActorDefs["ProfileView"]; 486 + indexedAt: string; 487 + likeCount?: number; 488 + contentMode?: AppBskyFeedDefsContentMode; 489 + description?: string; 490 + displayName: string; 491 + descriptionFacets?: AppBskyRichtextFacet["Main"][]; 492 + acceptsInteractions?: boolean; 493 + } 494 + 495 + export interface AppBskyFeedDefsThreadContext { 496 + rootAuthorLike?: string; 497 + } 498 + 499 + export interface AppBskyFeedDefsThreadViewPost { 500 + post: AppBskyFeedDefs["PostView"]; 501 + parent?: 502 + | AppBskyFeedDefs["ThreadViewPost"] 503 + | AppBskyFeedDefs["NotFoundPost"] 504 + | AppBskyFeedDefs["BlockedPost"] 505 + | { $type: string; [key: string]: unknown }; 506 + replies?: 507 + | AppBskyFeedDefs["ThreadViewPost"] 508 + | AppBskyFeedDefs["NotFoundPost"] 509 + | AppBskyFeedDefs["BlockedPost"] 510 + | { $type: string; [key: string]: unknown }[]; 511 + threadContext?: AppBskyFeedDefs["ThreadContext"]; 512 + } 513 + 514 + export interface AppBskyFeedDefsThreadgateView { 515 + cid?: string; 516 + uri?: string; 517 + lists?: AppBskyGraphDefs["ListViewBasic"][]; 518 + record?: unknown; 519 + } 520 + 521 + export type AppBskyFeedDefsInteractionLike = 522 + "app.bsky.feed.defs#interactionLike"; 523 + export type AppBskyFeedDefsInteractionSeen = 524 + "app.bsky.feed.defs#interactionSeen"; 525 + export type AppBskyFeedDefsClickthroughItem = 526 + "app.bsky.feed.defs#clickthroughItem"; 527 + export type AppBskyFeedDefsContentModeVideo = 528 + "app.bsky.feed.defs#contentModeVideo"; 529 + export type AppBskyFeedDefsInteractionQuote = 530 + "app.bsky.feed.defs#interactionQuote"; 531 + export type AppBskyFeedDefsInteractionReply = 532 + "app.bsky.feed.defs#interactionReply"; 533 + export type AppBskyFeedDefsInteractionShare = 534 + "app.bsky.feed.defs#interactionShare"; 535 + 536 + export interface AppBskyFeedDefsSkeletonFeedPost { 537 + post: string; 538 + reason?: 539 + | AppBskyFeedDefs["SkeletonReasonRepost"] 540 + | AppBskyFeedDefs["SkeletonReasonPin"] 541 + | { $type: string; [key: string]: unknown }; 542 + /** Context that will be passed through to client and may be passed to feed generator back alongside interactions. */ 543 + feedContext?: string; 544 + } 545 + 546 + export type AppBskyFeedDefsClickthroughEmbed = 547 + "app.bsky.feed.defs#clickthroughEmbed"; 548 + export type AppBskyFeedDefsInteractionRepost = 549 + "app.bsky.feed.defs#interactionRepost"; 550 + export type AppBskyFeedDefsSkeletonReasonPin = Record<string, never>; 551 + export type AppBskyFeedDefsClickthroughAuthor = 552 + "app.bsky.feed.defs#clickthroughAuthor"; 553 + export type AppBskyFeedDefsClickthroughReposter = 554 + "app.bsky.feed.defs#clickthroughReposter"; 555 + 556 + export interface AppBskyFeedDefsGeneratorViewerState { 557 + like?: string; 558 + } 559 + 560 + export interface AppBskyFeedDefsSkeletonReasonRepost { 561 + repost: string; 562 + } 563 + 564 + export type AppBskyFeedDefsContentModeUnspecified = 565 + "app.bsky.feed.defs#contentModeUnspecified"; 566 + 567 + export interface AppBskyFeedPostgate { 568 + /** Reference (AT-URI) to the post record. */ 569 + post: string; 570 + createdAt: string; 571 + /** 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. */ 572 + embeddingRules?: AppBskyFeedPostgate["DisableRule"] | { 573 + $type: string; 574 + [key: string]: unknown; 575 + }[]; 576 + /** List of AT-URIs embedding this post that the author has detached from. */ 577 + detachedEmbeddingUris?: string[]; 578 + } 579 + 580 + export type AppBskyFeedPostgateSortFields = "post" | "createdAt"; 581 + export type AppBskyFeedPostgateDisableRule = Record<string, never>; 582 + 583 + export interface AppBskyFeedThreadgate { 584 + /** Reference (AT-URI) to the post record. */ 585 + post: string; 586 + /** 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. */ 587 + allow?: 588 + | AppBskyFeedThreadgate["MentionRule"] 589 + | AppBskyFeedThreadgate["FollowerRule"] 590 + | AppBskyFeedThreadgate["FollowingRule"] 591 + | AppBskyFeedThreadgate["ListRule"] 592 + | { $type: string; [key: string]: unknown }[]; 593 + createdAt: string; 594 + /** List of hidden reply URIs. */ 595 + hiddenReplies?: string[]; 596 + } 597 + 598 + export type AppBskyFeedThreadgateSortFields = "post" | "createdAt"; 599 + 600 + export interface AppBskyFeedThreadgateListRule { 601 + list: string; 602 + } 603 + 604 + export type AppBskyFeedThreadgateMentionRule = Record<string, never>; 605 + export type AppBskyFeedThreadgateFollowerRule = Record<string, never>; 606 + export type AppBskyFeedThreadgateFollowingRule = Record<string, never>; 607 + 608 + export interface AppBskyRichtextFacetTag { 609 + tag: string; 610 + } 611 + 612 + export interface AppBskyRichtextFacetLink { 613 + uri: string; 614 + } 615 + 616 + export interface AppBskyRichtextFacetMain { 617 + index: AppBskyRichtextFacet["ByteSlice"]; 618 + features: 619 + | AppBskyRichtextFacet["Mention"] 620 + | AppBskyRichtextFacet["Link"] 621 + | AppBskyRichtextFacet["Tag"] 622 + | { $type: string; [key: string]: unknown }[]; 623 + } 624 + 625 + export interface AppBskyRichtextFacetMention { 626 + did: string; 627 + } 628 + 629 + export interface AppBskyRichtextFacetByteSlice { 630 + byteEnd: number; 631 + byteStart: number; 632 + } 633 + 634 + export interface AppBskyActorDefsNux { 635 + id: string; 636 + /** Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters. */ 637 + data?: string; 638 + completed: boolean; 639 + /** The date and time at which the NUX will expire and should be considered completed. */ 640 + expiresAt?: string; 641 + } 642 + 643 + export interface AppBskyActorDefsMutedWord { 644 + id?: string; 645 + /** The muted word itself. */ 646 + value: string; 647 + /** The intended targets of the muted word. */ 648 + targets: AppBskyActorDefs["MutedWordTarget"][]; 649 + /** The date and time at which the muted word will expire and no longer be applied. */ 650 + expiresAt?: string; 651 + /** Groups of users to apply the muted word to. If undefined, applies to all users. */ 652 + actorTarget?: AppBskyActorDefsActorTarget; 653 + } 654 + 655 + export interface AppBskyActorDefsSavedFeed { 656 + id: string; 657 + type: AppBskyActorDefsType; 658 + value: string; 659 + pinned: boolean; 660 + } 661 + 662 + export type AppBskyActorDefsPreferences = 663 + | AppBskyActorDefs["AdultContentPref"] 664 + | AppBskyActorDefs["ContentLabelPref"] 665 + | AppBskyActorDefs["SavedFeedsPref"] 666 + | AppBskyActorDefs["SavedFeedsPrefV2"] 667 + | AppBskyActorDefs["PersonalDetailsPref"] 668 + | AppBskyActorDefs["FeedViewPref"] 669 + | AppBskyActorDefs["ThreadViewPref"] 670 + | AppBskyActorDefs["InterestsPref"] 671 + | AppBskyActorDefs["MutedWordsPref"] 672 + | AppBskyActorDefs["HiddenPostsPref"] 673 + | AppBskyActorDefs["BskyAppStatePref"] 674 + | AppBskyActorDefs["LabelersPref"] 675 + | AppBskyActorDefs["PostInteractionSettingsPref"] 676 + | { $type: string; [key: string]: unknown }[]; 677 + 678 + export interface AppBskyActorDefsProfileView { 679 + did: string; 680 + avatar?: string; 681 + handle: string; 682 + labels?: ComAtprotoLabelDefs["Label"][]; 683 + viewer?: AppBskyActorDefs["ViewerState"]; 684 + createdAt?: string; 685 + indexedAt?: string; 686 + associated?: AppBskyActorDefs["ProfileAssociated"]; 687 + description?: string; 688 + displayName?: string; 689 + } 690 + 691 + export interface AppBskyActorDefsViewerState { 692 + muted?: boolean; 693 + blocking?: string; 694 + blockedBy?: boolean; 695 + following?: string; 696 + followedBy?: string; 697 + mutedByList?: AppBskyGraphDefs["ListViewBasic"]; 698 + blockingByList?: AppBskyGraphDefs["ListViewBasic"]; 699 + knownFollowers?: AppBskyActorDefs["KnownFollowers"]; 700 + } 701 + 702 + export interface AppBskyActorDefsFeedViewPref { 703 + /** The URI of the feed, or an identifier which describes the feed. */ 704 + feed: string; 705 + /** Hide replies in the feed. */ 706 + hideReplies?: boolean; 707 + /** Hide reposts in the feed. */ 708 + hideReposts?: boolean; 709 + /** Hide quote posts in the feed. */ 710 + hideQuotePosts?: boolean; 711 + /** Hide replies in the feed if they do not have this number of likes. */ 712 + hideRepliesByLikeCount?: number; 713 + /** Hide replies in the feed if they are not by followed users. */ 714 + hideRepliesByUnfollowed?: boolean; 715 + } 716 + 717 + export interface AppBskyActorDefsLabelersPref { 718 + labelers: AppBskyActorDefs["LabelerPrefItem"][]; 719 + } 720 + 721 + export interface AppBskyActorDefsInterestsPref { 722 + /** A list of tags which describe the account owner's interests gathered during onboarding. */ 723 + tags: string[]; 724 + } 725 + 726 + export interface AppBskyActorDefsKnownFollowers { 727 + count: number; 728 + followers: AppBskyActorDefs["ProfileViewBasic"][]; 729 + } 730 + 731 + export interface AppBskyActorDefsMutedWordsPref { 732 + /** A list of words the account owner has muted. */ 733 + items: AppBskyActorDefs["MutedWord"][]; 734 + } 735 + 736 + export interface AppBskyActorDefsSavedFeedsPref { 737 + saved: string[]; 738 + pinned: string[]; 739 + timelineIndex?: number; 740 + } 741 + 742 + export interface AppBskyActorDefsThreadViewPref { 743 + /** Sorting mode for threads. */ 744 + sort?: AppBskyActorDefsSort; 745 + /** Show followed users at the top of all replies. */ 746 + prioritizeFollowedUsers?: boolean; 747 + } 748 + 749 + export interface AppBskyActorDefsHiddenPostsPref { 750 + /** A list of URIs of posts the account owner has hidden. */ 751 + items: string[]; 752 + } 753 + 754 + export interface AppBskyActorDefsLabelerPrefItem { 755 + did: string; 756 + } 757 + 758 + export interface AppBskyActorDefsAdultContentPref { 759 + enabled: boolean; 760 + } 761 + 762 + export interface AppBskyActorDefsBskyAppStatePref { 763 + /** Storage for NUXs the user has encountered. */ 764 + nuxs?: AppBskyActorDefs["Nux"][]; 765 + /** An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user. */ 766 + queuedNudges?: string[]; 767 + activeProgressGuide?: AppBskyActorDefs["BskyAppProgressGuide"]; 768 + } 769 + 770 + export interface AppBskyActorDefsContentLabelPref { 771 + label: string; 772 + /** Which labeler does this preference apply to? If undefined, applies globally. */ 773 + labelerDid?: string; 774 + visibility: AppBskyActorDefsVisibility; 775 + } 776 + 777 + export interface AppBskyActorDefsProfileViewBasic { 778 + did: string; 779 + avatar?: string; 780 + handle: string; 781 + labels?: ComAtprotoLabelDefs["Label"][]; 782 + viewer?: AppBskyActorDefs["ViewerState"]; 783 + createdAt?: string; 784 + associated?: AppBskyActorDefs["ProfileAssociated"]; 785 + displayName?: string; 786 + } 787 + 788 + export interface AppBskyActorDefsSavedFeedsPrefV2 { 789 + items: AppBskyActorDefs["SavedFeed"][]; 790 + } 791 + 792 + export interface AppBskyActorDefsProfileAssociated { 793 + chat?: AppBskyActorDefs["ProfileAssociatedChat"]; 794 + lists?: number; 795 + labeler?: boolean; 796 + feedgens?: number; 797 + starterPacks?: number; 798 + } 799 + 800 + export interface AppBskyActorDefsPersonalDetailsPref { 801 + /** The birth date of account owner. */ 802 + birthDate?: string; 803 + } 804 + 805 + export interface AppBskyActorDefsProfileViewDetailed { 806 + did: string; 807 + avatar?: string; 808 + banner?: string; 809 + handle: string; 810 + labels?: ComAtprotoLabelDefs["Label"][]; 811 + viewer?: AppBskyActorDefs["ViewerState"]; 812 + createdAt?: string; 813 + indexedAt?: string; 814 + associated?: AppBskyActorDefs["ProfileAssociated"]; 815 + pinnedPost?: ComAtprotoRepoStrongRef; 816 + postsCount?: number; 817 + description?: string; 818 + displayName?: string; 819 + followsCount?: number; 820 + followersCount?: number; 821 + joinedViaStarterPack?: AppBskyGraphDefs["StarterPackViewBasic"]; 822 + } 823 + 824 + export interface AppBskyActorDefsBskyAppProgressGuide { 825 + guide: string; 826 + } 827 + 828 + export interface AppBskyActorDefsProfileAssociatedChat { 829 + allowIncoming: AppBskyActorDefsAllowIncoming; 830 + } 831 + 832 + export interface AppBskyActorDefsPostInteractionSettingsPref { 833 + /** 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. */ 834 + threadgateAllowRules?: 835 + | AppBskyFeedThreadgate["MentionRule"] 836 + | AppBskyFeedThreadgate["FollowerRule"] 837 + | AppBskyFeedThreadgate["FollowingRule"] 838 + | AppBskyFeedThreadgate["ListRule"] 839 + | { $type: string; [key: string]: unknown }[]; 840 + /** 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. */ 841 + postgateEmbeddingRules?: AppBskyFeedPostgate["DisableRule"] | { 842 + $type: string; 843 + [key: string]: unknown; 844 + }[]; 845 + } 846 + 847 + export interface AppBskyActorProfile { 848 + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ 849 + avatar?: BlobRef; 850 + /** Larger horizontal image to display behind profile view. */ 851 + banner?: BlobRef; 852 + /** Self-label values, specific to the Bluesky application, on the overall account. */ 853 + labels?: ComAtprotoLabelDefs["SelfLabels"] | { 854 + $type: string; 855 + [key: string]: unknown; 856 + }; 857 + createdAt?: string; 858 + pinnedPost?: ComAtprotoRepoStrongRef; 859 + /** Free-form profile description text. */ 860 + description?: string; 861 + displayName?: string; 862 + joinedViaStarterPack?: ComAtprotoRepoStrongRef; 863 + } 864 + 865 + export type AppBskyActorProfileSortFields = 866 + | "createdAt" 867 + | "description" 868 + | "displayName"; 869 + 870 + export interface AppBskyLabelerDefsLabelerView { 871 + cid: string; 872 + uri: string; 873 + labels?: ComAtprotoLabelDefs["Label"][]; 874 + viewer?: AppBskyLabelerDefs["LabelerViewerState"]; 875 + creator: AppBskyActorDefs["ProfileView"]; 876 + indexedAt: string; 877 + likeCount?: number; 878 + } 879 + 880 + export interface AppBskyLabelerDefsLabelerPolicies { 881 + /** The label values which this labeler publishes. May include global or custom labels. */ 882 + labelValues: ComAtprotoLabelDefs["LabelValue"][]; 883 + /** Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler. */ 884 + labelValueDefinitions?: ComAtprotoLabelDefs["LabelValueDefinition"][]; 885 + } 886 + 887 + export interface AppBskyLabelerDefsLabelerViewerState { 888 + like?: string; 889 + } 890 + 891 + export interface AppBskyLabelerDefsLabelerViewDetailed { 892 + cid: string; 893 + uri: string; 894 + labels?: ComAtprotoLabelDefs["Label"][]; 895 + viewer?: AppBskyLabelerDefs["LabelerViewerState"]; 896 + creator: AppBskyActorDefs["ProfileView"]; 897 + policies: AppBskyLabelerDefs["LabelerPolicies"]; 898 + indexedAt: string; 899 + likeCount?: number; 900 + } 901 + 902 + export interface ComAtprotoLabelDefsLabel { 903 + /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */ 904 + cid?: string; 905 + /** Timestamp when this label was created. */ 906 + cts?: string; 907 + /** Timestamp at which this label expires (no longer applies). */ 908 + exp?: string; 909 + /** If true, this is a negation label, overwriting a previous label. */ 910 + neg?: boolean; 911 + /** Signature of dag-cbor encoded label. */ 912 + sig?: string; 913 + /** DID of the actor who created this label. */ 914 + src: string; 915 + /** AT URI of the record, repository (account), or other resource that this label applies to. */ 916 + uri: string; 917 + /** The short string name of the value or type of this label. */ 918 + val: string; 919 + /** The AT Protocol version of the label object. */ 920 + ver?: number; 921 + } 922 + 923 + export interface ComAtprotoLabelDefsSelfLabel { 924 + /** The short string name of the value or type of this label. */ 925 + val: string; 926 + } 927 + 928 + export interface ComAtprotoLabelDefsSelfLabels { 929 + values: ComAtprotoLabelDefs["SelfLabel"][]; 930 + } 931 + 932 + export interface ComAtprotoLabelDefsLabelValueDefinition { 933 + /** 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. */ 934 + blurs: ComAtprotoLabelDefsBlurs; 935 + locales: ComAtprotoLabelDefs["LabelValueDefinitionStrings"][]; 936 + /** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */ 937 + severity: ComAtprotoLabelDefsSeverity; 938 + /** Does the user need to have adult content enabled in order to configure this label? */ 939 + adultOnly?: boolean; 940 + /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */ 941 + identifier: string; 942 + /** The default setting for this label. */ 943 + defaultSetting?: ComAtprotoLabelDefsDefaultSetting; 944 + } 945 + 946 + export interface ComAtprotoLabelDefsLabelValueDefinitionStrings { 947 + /** The code of the language these strings are written in. */ 948 + lang: string; 949 + /** A short human-readable name for the label. */ 950 + name: string; 951 + /** A longer description of what the label means and why it might be applied. */ 952 + description: string; 953 + } 954 + 955 + export interface ComAtprotoRepoStrongRef { 956 + cid: string; 957 + uri: string; 958 + } 959 + 960 + export interface AppBskyEmbedDefs { 961 + readonly AspectRatio: AppBskyEmbedDefsAspectRatio; 962 + } 963 + 964 + export interface AppBskyEmbedRecord { 965 + readonly Main: AppBskyEmbedRecordMain; 966 + readonly View: AppBskyEmbedRecordView; 967 + readonly ViewRecord: AppBskyEmbedRecordViewRecord; 968 + readonly ViewBlocked: AppBskyEmbedRecordViewBlocked; 969 + readonly ViewDetached: AppBskyEmbedRecordViewDetached; 970 + readonly ViewNotFound: AppBskyEmbedRecordViewNotFound; 971 + } 972 + 973 + export interface AppBskyEmbedImages { 974 + readonly Main: AppBskyEmbedImagesMain; 975 + readonly View: AppBskyEmbedImagesView; 976 + readonly Image: AppBskyEmbedImagesImage; 977 + readonly ViewImage: AppBskyEmbedImagesViewImage; 978 + } 979 + 980 + export interface AppBskyEmbedRecordWithMedia { 981 + readonly Main: AppBskyEmbedRecordWithMediaMain; 982 + readonly View: AppBskyEmbedRecordWithMediaView; 983 + } 984 + 985 + export interface AppBskyEmbedVideo { 986 + readonly Main: AppBskyEmbedVideoMain; 987 + readonly View: AppBskyEmbedVideoView; 988 + readonly Caption: AppBskyEmbedVideoCaption; 989 + } 990 + 991 + export interface AppBskyEmbedExternal { 992 + readonly Main: AppBskyEmbedExternalMain; 993 + readonly View: AppBskyEmbedExternalView; 994 + readonly External: AppBskyEmbedExternalExternal; 995 + readonly ViewExternal: AppBskyEmbedExternalViewExternal; 996 + } 997 + 998 + export interface AppBskyGraphDefs { 999 + readonly Modlist: AppBskyGraphDefsModlist; 1000 + readonly ListView: AppBskyGraphDefsListView; 1001 + readonly Curatelist: AppBskyGraphDefsCuratelist; 1002 + readonly ListPurpose: AppBskyGraphDefsListPurpose; 1003 + readonly ListItemView: AppBskyGraphDefsListItemView; 1004 + readonly Relationship: AppBskyGraphDefsRelationship; 1005 + readonly ListViewBasic: AppBskyGraphDefsListViewBasic; 1006 + readonly NotFoundActor: AppBskyGraphDefsNotFoundActor; 1007 + readonly Referencelist: AppBskyGraphDefsReferencelist; 1008 + readonly ListViewerState: AppBskyGraphDefsListViewerState; 1009 + readonly StarterPackView: AppBskyGraphDefsStarterPackView; 1010 + readonly StarterPackViewBasic: AppBskyGraphDefsStarterPackViewBasic; 1011 + } 1012 + 1013 + export interface AppBskyFeedDefs { 1014 + readonly PostView: AppBskyFeedDefsPostView; 1015 + readonly ReplyRef: AppBskyFeedDefsReplyRef; 1016 + readonly ReasonPin: AppBskyFeedDefsReasonPin; 1017 + readonly BlockedPost: AppBskyFeedDefsBlockedPost; 1018 + readonly Interaction: AppBskyFeedDefsInteraction; 1019 + readonly RequestLess: AppBskyFeedDefsRequestLess; 1020 + readonly RequestMore: AppBskyFeedDefsRequestMore; 1021 + readonly ViewerState: AppBskyFeedDefsViewerState; 1022 + readonly FeedViewPost: AppBskyFeedDefsFeedViewPost; 1023 + readonly NotFoundPost: AppBskyFeedDefsNotFoundPost; 1024 + readonly ReasonRepost: AppBskyFeedDefsReasonRepost; 1025 + readonly BlockedAuthor: AppBskyFeedDefsBlockedAuthor; 1026 + readonly GeneratorView: AppBskyFeedDefsGeneratorView; 1027 + readonly ThreadContext: AppBskyFeedDefsThreadContext; 1028 + readonly ThreadViewPost: AppBskyFeedDefsThreadViewPost; 1029 + readonly ThreadgateView: AppBskyFeedDefsThreadgateView; 1030 + readonly InteractionLike: AppBskyFeedDefsInteractionLike; 1031 + readonly InteractionSeen: AppBskyFeedDefsInteractionSeen; 1032 + readonly ClickthroughItem: AppBskyFeedDefsClickthroughItem; 1033 + readonly ContentModeVideo: AppBskyFeedDefsContentModeVideo; 1034 + readonly InteractionQuote: AppBskyFeedDefsInteractionQuote; 1035 + readonly InteractionReply: AppBskyFeedDefsInteractionReply; 1036 + readonly InteractionShare: AppBskyFeedDefsInteractionShare; 1037 + readonly SkeletonFeedPost: AppBskyFeedDefsSkeletonFeedPost; 1038 + readonly ClickthroughEmbed: AppBskyFeedDefsClickthroughEmbed; 1039 + readonly InteractionRepost: AppBskyFeedDefsInteractionRepost; 1040 + readonly SkeletonReasonPin: AppBskyFeedDefsSkeletonReasonPin; 1041 + readonly ClickthroughAuthor: AppBskyFeedDefsClickthroughAuthor; 1042 + readonly ClickthroughReposter: AppBskyFeedDefsClickthroughReposter; 1043 + readonly GeneratorViewerState: AppBskyFeedDefsGeneratorViewerState; 1044 + readonly SkeletonReasonRepost: AppBskyFeedDefsSkeletonReasonRepost; 1045 + readonly ContentModeUnspecified: AppBskyFeedDefsContentModeUnspecified; 1046 + } 1047 + 1048 + export interface AppBskyFeedPostgate { 1049 + readonly Main: AppBskyFeedPostgate; 1050 + readonly DisableRule: AppBskyFeedPostgateDisableRule; 1051 + } 1052 + 1053 + export interface AppBskyFeedThreadgate { 1054 + readonly Main: AppBskyFeedThreadgate; 1055 + readonly ListRule: AppBskyFeedThreadgateListRule; 1056 + readonly MentionRule: AppBskyFeedThreadgateMentionRule; 1057 + readonly FollowerRule: AppBskyFeedThreadgateFollowerRule; 1058 + readonly FollowingRule: AppBskyFeedThreadgateFollowingRule; 1059 + } 1060 + 1061 + export interface AppBskyRichtextFacet { 1062 + readonly Tag: AppBskyRichtextFacetTag; 1063 + readonly Link: AppBskyRichtextFacetLink; 1064 + readonly Main: AppBskyRichtextFacetMain; 1065 + readonly Mention: AppBskyRichtextFacetMention; 1066 + readonly ByteSlice: AppBskyRichtextFacetByteSlice; 1067 + } 1068 + 1069 + export interface AppBskyActorDefs { 1070 + readonly Nux: AppBskyActorDefsNux; 1071 + readonly MutedWord: AppBskyActorDefsMutedWord; 1072 + readonly SavedFeed: AppBskyActorDefsSavedFeed; 1073 + readonly Preferences: AppBskyActorDefsPreferences; 1074 + readonly ProfileView: AppBskyActorDefsProfileView; 1075 + readonly ViewerState: AppBskyActorDefsViewerState; 1076 + readonly FeedViewPref: AppBskyActorDefsFeedViewPref; 1077 + readonly LabelersPref: AppBskyActorDefsLabelersPref; 1078 + readonly InterestsPref: AppBskyActorDefsInterestsPref; 1079 + readonly KnownFollowers: AppBskyActorDefsKnownFollowers; 1080 + readonly MutedWordsPref: AppBskyActorDefsMutedWordsPref; 1081 + readonly SavedFeedsPref: AppBskyActorDefsSavedFeedsPref; 1082 + readonly ThreadViewPref: AppBskyActorDefsThreadViewPref; 1083 + readonly HiddenPostsPref: AppBskyActorDefsHiddenPostsPref; 1084 + readonly LabelerPrefItem: AppBskyActorDefsLabelerPrefItem; 1085 + readonly MutedWordTarget: AppBskyActorDefsMutedWordTarget; 1086 + readonly AdultContentPref: AppBskyActorDefsAdultContentPref; 1087 + readonly BskyAppStatePref: AppBskyActorDefsBskyAppStatePref; 1088 + readonly ContentLabelPref: AppBskyActorDefsContentLabelPref; 1089 + readonly ProfileViewBasic: AppBskyActorDefsProfileViewBasic; 1090 + readonly SavedFeedsPrefV2: AppBskyActorDefsSavedFeedsPrefV2; 1091 + readonly ProfileAssociated: AppBskyActorDefsProfileAssociated; 1092 + readonly PersonalDetailsPref: AppBskyActorDefsPersonalDetailsPref; 1093 + readonly ProfileViewDetailed: AppBskyActorDefsProfileViewDetailed; 1094 + readonly BskyAppProgressGuide: AppBskyActorDefsBskyAppProgressGuide; 1095 + readonly ProfileAssociatedChat: AppBskyActorDefsProfileAssociatedChat; 1096 + readonly PostInteractionSettingsPref: 1097 + AppBskyActorDefsPostInteractionSettingsPref; 1098 + } 1099 + 1100 + export interface AppBskyLabelerDefs { 1101 + readonly LabelerView: AppBskyLabelerDefsLabelerView; 1102 + readonly LabelerPolicies: AppBskyLabelerDefsLabelerPolicies; 1103 + readonly LabelerViewerState: AppBskyLabelerDefsLabelerViewerState; 1104 + readonly LabelerViewDetailed: AppBskyLabelerDefsLabelerViewDetailed; 1105 + } 1106 + 1107 + export interface ComAtprotoLabelDefs { 1108 + readonly Label: ComAtprotoLabelDefsLabel; 1109 + readonly SelfLabel: ComAtprotoLabelDefsSelfLabel; 1110 + readonly LabelValue: ComAtprotoLabelDefsLabelValue; 1111 + readonly SelfLabels: ComAtprotoLabelDefsSelfLabels; 1112 + readonly LabelValueDefinition: ComAtprotoLabelDefsLabelValueDefinition; 1113 + readonly LabelValueDefinitionStrings: 1114 + ComAtprotoLabelDefsLabelValueDefinitionStrings; 1115 + } 1116 + 1117 + class PostgateFeedBskyAppClient { 1118 + private readonly client: SlicesClient; 1119 + 1120 + constructor(client: SlicesClient) { 1121 + this.client = client; 1122 + } 1123 + 1124 + async getRecords( 1125 + params?: { 1126 + limit?: number; 1127 + cursor?: string; 1128 + where?: { 1129 + [K in AppBskyFeedPostgateSortFields | IndexedRecordFields]?: 1130 + WhereCondition; 1131 + }; 1132 + orWhere?: { 1133 + [K in AppBskyFeedPostgateSortFields | IndexedRecordFields]?: 1134 + WhereCondition; 1135 + }; 1136 + sortBy?: SortField<AppBskyFeedPostgateSortFields>[]; 1137 + }, 1138 + ): Promise<GetRecordsResponse<AppBskyFeedPostgate>> { 1139 + return await this.client.getRecords("app.bsky.feed.postgate", params); 1140 + } 1141 + 1142 + async getRecord( 1143 + params: GetRecordParams, 1144 + ): Promise<RecordResponse<AppBskyFeedPostgate>> { 1145 + return await this.client.getRecord("app.bsky.feed.postgate", params); 1146 + } 1147 + 1148 + async countRecords( 1149 + params?: { 1150 + limit?: number; 1151 + cursor?: string; 1152 + where?: { 1153 + [K in AppBskyFeedPostgateSortFields | IndexedRecordFields]?: 1154 + WhereCondition; 1155 + }; 1156 + orWhere?: { 1157 + [K in AppBskyFeedPostgateSortFields | IndexedRecordFields]?: 1158 + WhereCondition; 1159 + }; 1160 + sortBy?: SortField<AppBskyFeedPostgateSortFields>[]; 1161 + }, 1162 + ): Promise<CountRecordsResponse> { 1163 + return await this.client.countRecords("app.bsky.feed.postgate", params); 1164 + } 1165 + 1166 + async createRecord( 1167 + record: AppBskyFeedPostgate, 1168 + useSelfRkey?: boolean, 1169 + ): Promise<{ uri: string; cid: string }> { 1170 + return await this.client.createRecord( 1171 + "app.bsky.feed.postgate", 1172 + record, 1173 + useSelfRkey, 1174 + ); 1175 + } 1176 + 1177 + async updateRecord( 1178 + rkey: string, 1179 + record: AppBskyFeedPostgate, 1180 + ): Promise<{ uri: string; cid: string }> { 1181 + return await this.client.updateRecord( 1182 + "app.bsky.feed.postgate", 1183 + rkey, 1184 + record, 1185 + ); 1186 + } 1187 + 1188 + async deleteRecord(rkey: string): Promise<void> { 1189 + return await this.client.deleteRecord("app.bsky.feed.postgate", rkey); 1190 + } 1191 + } 1192 + 1193 + class ThreadgateFeedBskyAppClient { 1194 + private readonly client: SlicesClient; 1195 + 1196 + constructor(client: SlicesClient) { 1197 + this.client = client; 1198 + } 1199 + 1200 + async getRecords( 1201 + params?: { 1202 + limit?: number; 1203 + cursor?: string; 1204 + where?: { 1205 + [K in AppBskyFeedThreadgateSortFields | IndexedRecordFields]?: 1206 + WhereCondition; 1207 + }; 1208 + orWhere?: { 1209 + [K in AppBskyFeedThreadgateSortFields | IndexedRecordFields]?: 1210 + WhereCondition; 1211 + }; 1212 + sortBy?: SortField<AppBskyFeedThreadgateSortFields>[]; 1213 + }, 1214 + ): Promise<GetRecordsResponse<AppBskyFeedThreadgate>> { 1215 + return await this.client.getRecords("app.bsky.feed.threadgate", params); 1216 + } 1217 + 1218 + async getRecord( 1219 + params: GetRecordParams, 1220 + ): Promise<RecordResponse<AppBskyFeedThreadgate>> { 1221 + return await this.client.getRecord("app.bsky.feed.threadgate", params); 1222 + } 1223 + 1224 + async countRecords( 1225 + params?: { 1226 + limit?: number; 1227 + cursor?: string; 1228 + where?: { 1229 + [K in AppBskyFeedThreadgateSortFields | IndexedRecordFields]?: 1230 + WhereCondition; 1231 + }; 1232 + orWhere?: { 1233 + [K in AppBskyFeedThreadgateSortFields | IndexedRecordFields]?: 1234 + WhereCondition; 1235 + }; 1236 + sortBy?: SortField<AppBskyFeedThreadgateSortFields>[]; 1237 + }, 1238 + ): Promise<CountRecordsResponse> { 1239 + return await this.client.countRecords("app.bsky.feed.threadgate", params); 1240 + } 1241 + 1242 + async createRecord( 1243 + record: AppBskyFeedThreadgate, 1244 + useSelfRkey?: boolean, 1245 + ): Promise<{ uri: string; cid: string }> { 1246 + return await this.client.createRecord( 1247 + "app.bsky.feed.threadgate", 1248 + record, 1249 + useSelfRkey, 1250 + ); 1251 + } 1252 + 1253 + async updateRecord( 1254 + rkey: string, 1255 + record: AppBskyFeedThreadgate, 1256 + ): Promise<{ uri: string; cid: string }> { 1257 + return await this.client.updateRecord( 1258 + "app.bsky.feed.threadgate", 1259 + rkey, 1260 + record, 1261 + ); 1262 + } 1263 + 1264 + async deleteRecord(rkey: string): Promise<void> { 1265 + return await this.client.deleteRecord("app.bsky.feed.threadgate", rkey); 1266 + } 1267 + } 1268 + 1269 + class FeedBskyAppClient { 1270 + readonly postgate: PostgateFeedBskyAppClient; 1271 + readonly threadgate: ThreadgateFeedBskyAppClient; 1272 + private readonly client: SlicesClient; 1273 + 1274 + constructor(client: SlicesClient) { 1275 + this.client = client; 1276 + this.postgate = new PostgateFeedBskyAppClient(client); 1277 + this.threadgate = new ThreadgateFeedBskyAppClient(client); 1278 + } 1279 + } 1280 + 1281 + class ProfileActorBskyAppClient { 1282 + private readonly client: SlicesClient; 1283 + 1284 + constructor(client: SlicesClient) { 1285 + this.client = client; 1286 + } 1287 + 1288 + async getRecords( 1289 + params?: { 1290 + limit?: number; 1291 + cursor?: string; 1292 + where?: { 1293 + [K in AppBskyActorProfileSortFields | IndexedRecordFields]?: 1294 + WhereCondition; 1295 + }; 1296 + orWhere?: { 1297 + [K in AppBskyActorProfileSortFields | IndexedRecordFields]?: 1298 + WhereCondition; 1299 + }; 1300 + sortBy?: SortField<AppBskyActorProfileSortFields>[]; 1301 + }, 1302 + ): Promise<GetRecordsResponse<AppBskyActorProfile>> { 1303 + return await this.client.getRecords("app.bsky.actor.profile", params); 1304 + } 1305 + 1306 + async getRecord( 1307 + params: GetRecordParams, 1308 + ): Promise<RecordResponse<AppBskyActorProfile>> { 1309 + return await this.client.getRecord("app.bsky.actor.profile", params); 1310 + } 1311 + 1312 + async countRecords( 1313 + params?: { 1314 + limit?: number; 1315 + cursor?: string; 1316 + where?: { 1317 + [K in AppBskyActorProfileSortFields | IndexedRecordFields]?: 1318 + WhereCondition; 1319 + }; 1320 + orWhere?: { 1321 + [K in AppBskyActorProfileSortFields | IndexedRecordFields]?: 1322 + WhereCondition; 1323 + }; 1324 + sortBy?: SortField<AppBskyActorProfileSortFields>[]; 1325 + }, 1326 + ): Promise<CountRecordsResponse> { 1327 + return await this.client.countRecords("app.bsky.actor.profile", params); 1328 + } 1329 + 1330 + async createRecord( 1331 + record: AppBskyActorProfile, 1332 + useSelfRkey?: boolean, 1333 + ): Promise<{ uri: string; cid: string }> { 1334 + return await this.client.createRecord( 1335 + "app.bsky.actor.profile", 1336 + record, 1337 + useSelfRkey, 1338 + ); 1339 + } 1340 + 1341 + async updateRecord( 1342 + rkey: string, 1343 + record: AppBskyActorProfile, 1344 + ): Promise<{ uri: string; cid: string }> { 1345 + return await this.client.updateRecord( 1346 + "app.bsky.actor.profile", 1347 + rkey, 1348 + record, 1349 + ); 1350 + } 1351 + 1352 + async deleteRecord(rkey: string): Promise<void> { 1353 + return await this.client.deleteRecord("app.bsky.actor.profile", rkey); 1354 + } 1355 + } 1356 + 1357 + class ActorBskyAppClient { 1358 + readonly profile: ProfileActorBskyAppClient; 1359 + private readonly client: SlicesClient; 1360 + 1361 + constructor(client: SlicesClient) { 1362 + this.client = client; 1363 + this.profile = new ProfileActorBskyAppClient(client); 1364 + } 1365 + } 1366 + 1367 + class BskyAppClient { 1368 + readonly feed: FeedBskyAppClient; 1369 + readonly actor: ActorBskyAppClient; 1370 + private readonly client: SlicesClient; 1371 + 1372 + constructor(client: SlicesClient) { 1373 + this.client = client; 1374 + this.feed = new FeedBskyAppClient(client); 1375 + this.actor = new ActorBskyAppClient(client); 1376 + } 1377 + } 1378 + 1379 + class AppClient { 1380 + readonly bsky: BskyAppClient; 1381 + private readonly client: SlicesClient; 1382 + 1383 + constructor(client: SlicesClient) { 1384 + this.client = client; 1385 + this.bsky = new BskyAppClient(client); 1386 + } 1387 + } 1388 + 1389 + export class AtProtoClient extends SlicesClient { 1390 + readonly app: AppClient; 1391 + readonly oauth?: OAuthClient | AuthProvider; 1392 + 1393 + constructor( 1394 + baseUrl: string, 1395 + sliceUri: string, 1396 + oauthClient?: OAuthClient | AuthProvider, 1397 + ) { 1398 + super(baseUrl, sliceUri, oauthClient); 1399 + this.app = new AppClient(this); 1400 + this.oauth = oauthClient; 1401 + } 1402 + }
+19
src/main.ts
··· 1 + import { route } from "@std/http/unstable-route"; 2 + import { allRoutes } from "./routes/mod.ts"; 3 + import { createLoggingHandler } from "./utils/logging.ts"; 4 + 5 + function defaultHandler(req: Request): Promise<Response> { 6 + return Promise.resolve(Response.redirect(new URL("/", req.url), 302)); 7 + } 8 + 9 + const handler = createLoggingHandler(route(allRoutes, defaultHandler)); 10 + 11 + Deno.serve( 12 + { 13 + port: parseInt(Deno.env.get("PORT") || "8080"), 14 + hostname: "0.0.0.0", 15 + onListen: ({ port, hostname }) => 16 + console.log(`🚀 Server running on http://${hostname}:${port}`), 17 + }, 18 + handler, 19 + );
+43
src/routes/middleware.ts
··· 1 + import { sessionStore, createOAuthClient } from "../config.ts"; 2 + 3 + export interface AuthContext { 4 + currentUser: { 5 + sub: string; 6 + name?: string; 7 + email?: string; 8 + } | null; 9 + sessionId: string | null; 10 + } 11 + 12 + export async function withAuth(req: Request): Promise<AuthContext> { 13 + const session = await sessionStore.getSessionFromRequest(req); 14 + 15 + if (!session) { 16 + return { currentUser: null, sessionId: null }; 17 + } 18 + 19 + try { 20 + const sessionOAuthClient = createOAuthClient(session.sessionId); 21 + const userInfo = await sessionOAuthClient.getUserInfo(); 22 + return { 23 + currentUser: userInfo || null, 24 + sessionId: session.sessionId, 25 + }; 26 + } catch { 27 + return { currentUser: null, sessionId: session.sessionId }; 28 + } 29 + } 30 + 31 + export function requireAuth( 32 + handler: (req: Request, context: AuthContext) => Promise<Response> 33 + ) { 34 + return async (req: Request): Promise<Response> => { 35 + const context = await withAuth(req); 36 + 37 + if (!context.currentUser) { 38 + return Response.redirect(new URL("/login", req.url), 302); 39 + } 40 + 41 + return handler(req, context); 42 + }; 43 + }
+18
src/routes/mod.ts
··· 1 + import type { Route } from "@std/http/unstable-route"; 2 + import { authRoutes } from "../features/auth/handlers.tsx"; 3 + import { dashboardRoutes } from "../features/dashboard/handlers.tsx"; 4 + 5 + export const allRoutes: Route[] = [ 6 + // Root redirect to login for now 7 + { 8 + method: "GET", 9 + pattern: new URLPattern({ pathname: "/" }), 10 + handler: (req) => Response.redirect(new URL("/login", req.url), 302), 11 + }, 12 + 13 + // Auth routes 14 + ...authRoutes, 15 + 16 + // Dashboard routes 17 + ...dashboardRoutes, 18 + ];
+50
src/shared/fragments/Button.tsx
··· 1 + import { ComponentChildren, JSX } from "preact"; 2 + import { cn } from "../../utils/cn.ts"; 3 + 4 + interface ButtonProps extends Omit<JSX.IntrinsicElements['button'], "size"> { 5 + children: ComponentChildren; 6 + variant?: "primary" | "secondary" | "danger"; 7 + size?: "sm" | "md" | "lg"; 8 + } 9 + 10 + export function Button({ 11 + children, 12 + type = "button", 13 + variant = "primary", 14 + size = "md", 15 + className, 16 + disabled, 17 + ...props 18 + }: ButtonProps) { 19 + const baseClasses = 20 + "inline-flex items-center justify-center font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"; 21 + 22 + const variantClasses = { 23 + primary: "bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-500", 24 + secondary: 25 + "bg-gray-200 hover:bg-gray-300 text-gray-900 focus:ring-gray-500", 26 + danger: "bg-red-600 hover:bg-red-700 text-white focus:ring-red-500", 27 + }; 28 + 29 + const sizeClasses = { 30 + sm: "px-3 py-1.5 text-sm", 31 + md: "px-4 py-2 text-sm", 32 + lg: "px-6 py-3 text-base", 33 + }; 34 + 35 + return ( 36 + <button 37 + type={type} 38 + disabled={disabled} 39 + className={cn( 40 + baseClasses, 41 + variantClasses[variant], 42 + sizeClasses[size], 43 + className 44 + )} 45 + {...props} 46 + > 47 + {children} 48 + </button> 49 + ); 50 + }
+23
src/shared/fragments/Input.tsx
··· 1 + import { JSX } from "preact"; 2 + import { cn } from "../../utils/cn.ts"; 3 + 4 + type InputProps = JSX.IntrinsicElements['input']; 5 + 6 + export function Input({ 7 + type = "text", 8 + className, 9 + ...props 10 + }: InputProps) { 11 + return ( 12 + <input 13 + type={type} 14 + className={cn( 15 + "block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm", 16 + "focus:outline-none focus:ring-blue-500 focus:border-blue-500", 17 + "disabled:bg-gray-50 disabled:text-gray-500", 18 + className 19 + )} 20 + {...props} 21 + /> 22 + ); 23 + }
+23
src/shared/fragments/Layout.tsx
··· 1 + import { ComponentChildren } from "preact"; 2 + 3 + interface LayoutProps { 4 + title?: string; 5 + children: ComponentChildren; 6 + } 7 + 8 + export function Layout({ title = "App", children }: LayoutProps) { 9 + return ( 10 + <html lang="en"> 11 + <head> 12 + <meta charset="UTF-8" /> 13 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 14 + <title>{title}</title> 15 + <script src="https://cdn.tailwindcss.com"></script> 16 + <script src="https://unpkg.com/htmx.org@1.9.10"></script> 17 + </head> 18 + <body> 19 + {children} 20 + </body> 21 + </html> 22 + ); 23 + }
+6
src/utils/cn.ts
··· 1 + import { type ClassValue, clsx } from "clsx"; 2 + import { twMerge } from "tailwind-merge"; 3 + 4 + export function cn(...inputs: ClassValue[]): string { 5 + return twMerge(clsx(inputs)); 6 + }
+43
src/utils/logging.ts
··· 1 + import { cyan, green, red, yellow, bold, dim } from "@std/fmt/colors"; 2 + 3 + export function createLoggingHandler( 4 + handler: (req: Request) => Response | Promise<Response> 5 + ) { 6 + return async (req: Request): Promise<Response> => { 7 + const start = Date.now(); 8 + const method = req.method; 9 + const url = new URL(req.url); 10 + 11 + try { 12 + const response = await Promise.resolve(handler(req)); 13 + const duration = Date.now() - start; 14 + 15 + const methodColor = cyan(bold(method)); 16 + const statusColor = 17 + response.status >= 200 && response.status < 300 18 + ? green(String(response.status)) 19 + : response.status >= 300 && response.status < 400 20 + ? yellow(String(response.status)) 21 + : response.status >= 400 22 + ? red(String(response.status)) 23 + : String(response.status); 24 + const durationText = dim(`(${duration}ms)`); 25 + 26 + console.log( 27 + `${methodColor} ${url.pathname} - ${statusColor} ${durationText}` 28 + ); 29 + return response; 30 + } catch (error) { 31 + const duration = Date.now() - start; 32 + const methodColor = cyan(bold(method)); 33 + const errorText = red(bold("ERROR")); 34 + const durationText = dim(`(${duration}ms)`); 35 + 36 + console.error( 37 + `${methodColor} ${url.pathname} - ${errorText} ${durationText}:`, 38 + error 39 + ); 40 + throw error; 41 + } 42 + }; 43 + }
+12
src/utils/render.tsx
··· 1 + import { renderToString } from "preact-render-to-string"; 2 + import { VNode } from "preact"; 3 + 4 + export function renderHTML(element: VNode): Response { 5 + const html = renderToString(element); 6 + 7 + return new Response(html, { 8 + headers: { 9 + "Content-Type": "text/html; charset=utf-8", 10 + }, 11 + }); 12 + }