+144
CLAUDE.md
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+5
slices.json
+75
src/config.ts
+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
+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
+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
+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
+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
+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
+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
+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
+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
+
];
+6
src/utils/cn.ts
+6
src/utils/cn.ts
+43
src/utils/logging.ts
+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
+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
+
}