grain.social is a photo sharing platform built on atproto. grain.social
atproto photography appview
57
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add Bluesky cross-posting for galleries and stories

- Shared Bluesky post utility with location, description, image embeds, facets, and 300 grapheme limit
- "Post to Bluesky" checkbox on gallery and story creation
- Story permalink route (/profile/[did]/story/[rkey]) with OG image endpoint
- getStory XRPC endpoint (no 24h expiry filter)
- Cross-post hydration with Bluesky butterfly link
- Checkbox atom component, refactored settings to use it
- OAuth scope restricted to create-only for bsky posts
- URLs use origin for dev compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+4190 -1530
+25
app/lib/components/atoms/Checkbox.svelte
··· 1 + <script lang="ts"> 2 + let { checked = $bindable(false), label }: { checked: boolean; label: string } = $props() 3 + </script> 4 + 5 + <label class="checkbox"> 6 + <input type="checkbox" bind:checked /> 7 + <span class="checkbox-label">{label}</span> 8 + </label> 9 + 10 + <style> 11 + .checkbox { 12 + display: flex; 13 + align-items: center; 14 + gap: 10px; 15 + cursor: pointer; 16 + font-size: 14px; 17 + color: var(--text-secondary); 18 + } 19 + .checkbox input[type="checkbox"] { 20 + width: 18px; 21 + height: 18px; 22 + accent-color: var(--grain-btn); 23 + cursor: pointer; 24 + } 25 + </style>
+28 -1
app/lib/components/molecules/StoryCreate.svelte
··· 8 8 import LocationInput from '$lib/components/atoms/LocationInput.svelte' 9 9 import type { LocationData } from '$lib/components/atoms/LocationInput.svelte' 10 10 import Button from '$lib/components/atoms/Button.svelte' 11 + import Checkbox from '$lib/components/atoms/Checkbox.svelte' 12 + import { createBskyPost } from '$lib/utils/bsky-post' 13 + import { viewer } from '$lib/stores' 11 14 12 15 let { onclose }: { onclose: () => void } = $props() 13 16 ··· 15 18 let location = $state<LocationData | null>(null) 16 19 let processing = $state(false) 17 20 let publishing = $state(false) 21 + let postToBluesky = $state(false) 18 22 let error = $state<string | null>(null) 19 23 let fileInput: HTMLInputElement = $state()! 20 24 ··· 76 80 77 81 const uploadResult = await callXrpc('dev.hatk.uploadBlob', blob as any) 78 82 79 - await callXrpc('dev.hatk.createRecord', { 83 + const result = await callXrpc('dev.hatk.createRecord', { 80 84 collection: 'social.grain.story', 81 85 record: { 82 86 media: (uploadResult as any).blob, ··· 94 98 }, 95 99 }) 96 100 101 + // Post to Bluesky if opted in 102 + if (postToBluesky && $viewer) { 103 + const storyUri = (result as any).uri as string 104 + const storyRkey = storyUri.split('/').pop() 105 + const storyUrl = `${window.location.origin}/profile/${$viewer.did}/story/${storyRkey}` 106 + await createBskyPost({ 107 + url: storyUrl, 108 + location: location ? { name: location.name, address: location.address } : null, 109 + images: [{ 110 + dataUrl: photo.dataUrl, 111 + alt: '', 112 + width: photo.width, 113 + height: photo.height, 114 + }], 115 + }) 116 + } 117 + 97 118 queryClient.invalidateQueries({ queryKey: ['storyAuthors'] }) 98 119 onclose() 99 120 } catch (err: any) { ··· 152 173 </div> 153 174 <div class="location-field"> 154 175 <LocationInput bind:value={location} placeholder="Add location..." /> 176 + </div> 177 + <div class="bsky-field"> 178 + <Checkbox bind:checked={postToBluesky} label="Post to Bluesky" /> 155 179 </div> 156 180 {/if} 157 181 </div> ··· 248 272 } 249 273 .location-field { 250 274 padding: 12px 16px; 275 + } 276 + .bsky-field { 277 + padding: 0 16px 12px; 251 278 } 252 279 </style>
+10
app/lib/queries.ts
··· 68 68 staleTime: 60_000, 69 69 }); 70 70 71 + export const storyQuery = (storyUri: string, f?: Fetch) => 72 + queryOptions({ 73 + queryKey: ["getStory", storyUri], 74 + queryFn: () => 75 + callXrpc("social.grain.unspecced.getStory", { story: storyUri }, f).then( 76 + (r) => r?.story ?? null, 77 + ), 78 + staleTime: 60_000, 79 + }); 80 + 71 81 export const storiesQuery = (did: string, f?: Fetch) => 72 82 queryOptions({ 73 83 queryKey: ["stories", did],
+103
app/lib/utils/bsky-post.ts
··· 1 + import { callXrpc } from '$hatk/client' 2 + import { parseTextToFacets } from '$lib/utils/rich-text' 3 + 4 + interface BskyPostOptions { 5 + url: string 6 + location?: { 7 + name: string 8 + address?: { 9 + locality?: string 10 + region?: string 11 + country?: string 12 + } 13 + } | null 14 + description?: string 15 + images: Array<{ 16 + dataUrl: string 17 + alt?: string 18 + width: number 19 + height: number 20 + }> 21 + } 22 + 23 + export async function createBskyPost(options: BskyPostOptions): Promise<void> { 24 + const { url, location, description, images } = options 25 + 26 + const graphemeLength = (s: string) => [...new Intl.Segmenter().segment(s)].length 27 + 28 + const lines: string[] = [] 29 + if (location) { 30 + lines.push(`📍 ${location.name}`) 31 + if (location.address) { 32 + const parts: string[] = [] 33 + if (location.address.locality) parts.push(location.address.locality) 34 + if (location.address.region) parts.push(location.address.region) 35 + if (location.address.country) parts.push(location.address.country) 36 + if (parts.length > 0) lines.push(parts.join(', ')) 37 + } 38 + } 39 + 40 + const suffix = `\n\n${url}\n\n#grainsocial` 41 + const prefixText = lines.length > 0 ? lines.join('\n') + '\n' : '' 42 + const overhead = graphemeLength(prefixText + suffix) 43 + const maxDesc = 300 - overhead 44 + 45 + if (description?.trim()) { 46 + let desc = description.trim() 47 + if (graphemeLength(desc) > maxDesc) { 48 + const segments = [...new Intl.Segmenter().segment(desc)] 49 + desc = segments.slice(0, Math.max(0, maxDesc - 1)).map((s) => s.segment).join('') + '…' 50 + } 51 + if (desc) { 52 + lines.push('') 53 + lines.push(desc) 54 + } 55 + } 56 + lines.push('') 57 + lines.push(url) 58 + lines.push('') 59 + lines.push('#grainsocial') 60 + 61 + const postText = lines.join('\n') 62 + 63 + const resolveHandle = async (handle: string): Promise<string | null> => { 64 + try { 65 + const res = await fetch( 66 + `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}` 67 + ) 68 + if (!res.ok) return null 69 + const data = await res.json() 70 + return data.did ?? null 71 + } catch { 72 + return null 73 + } 74 + } 75 + const postFacets = (await parseTextToFacets(postText, resolveHandle)).facets 76 + 77 + const imageRefs: Array<{ image: any; alt: string; aspectRatio?: { width: number; height: number } }> = [] 78 + for (const img of images.slice(0, 4)) { 79 + const base64 = img.dataUrl.split(',')[1] 80 + const binary = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)) 81 + const blob = new Blob([binary], { type: 'image/jpeg' }) 82 + const uploadResult = await callXrpc('dev.hatk.uploadBlob', blob as any) 83 + imageRefs.push({ 84 + image: (uploadResult as any).blob, 85 + alt: img.alt || '', 86 + aspectRatio: { width: img.width, height: img.height }, 87 + }) 88 + } 89 + 90 + await callXrpc('dev.hatk.createRecord', { 91 + collection: 'app.bsky.feed.post', 92 + record: { 93 + $type: 'app.bsky.feed.post', 94 + text: postText, 95 + facets: postFacets.length > 0 ? postFacets : undefined, 96 + embed: imageRefs.length > 0 97 + ? { $type: 'app.bsky.embed.images', images: imageRefs } 98 + : undefined, 99 + tags: ['grainsocial'], 100 + createdAt: new Date().toISOString(), 101 + }, 102 + }) 103 + }
+17 -1
app/routes/create/+page.svelte
··· 4 4 import { useQueryClient } from '@tanstack/svelte-query' 5 5 import { callXrpc } from '$hatk/client' 6 6 import { processPhotos, type ProcessedPhoto } from '$lib/utils/image-resize' 7 - import { parseTextToFacets } from '$lib/utils/rich-text' 8 7 import { reverseGeocode, formatLocationName, extractAddress } from '$lib/utils/nominatim' 8 + import { createBskyPost } from '$lib/utils/bsky-post' 9 9 import { latLonToH3 } from '$lib/utils/h3' 10 10 import { X, LoaderCircle } from 'lucide-svelte' 11 11 import DetailHeader from '$lib/components/molecules/DetailHeader.svelte' ··· 13 13 import Button from '$lib/components/atoms/Button.svelte' 14 14 import Field from '$lib/components/atoms/Field.svelte' 15 15 import { includeExif } from '$lib/preferences' 16 + import { viewer } from '$lib/stores' 16 17 import Input from '$lib/components/atoms/Input.svelte' 17 18 import Textarea from '$lib/components/atoms/Textarea.svelte' 18 19 import RichTextarea from '$lib/components/atoms/RichTextarea.svelte' 19 20 import LocationInput from '$lib/components/atoms/LocationInput.svelte' 21 + import Checkbox from '$lib/components/atoms/Checkbox.svelte' 20 22 import type { LocationData } from '$lib/components/atoms/LocationInput.svelte' 21 23 22 24 onMount(() => window.scrollTo(0, 0)) ··· 30 32 let location = $state<LocationData | null>(null) 31 33 let processing = $state(false) 32 34 let publishing = $state(false) 35 + let postToBluesky = $state(false) 33 36 let error = $state<string | null>(null) 34 37 35 38 let fileInput: HTMLInputElement = $state()! ··· 274 277 }) 275 278 } 276 279 280 + // 5. Create Bluesky post if opted in 281 + if (postToBluesky && $viewer) { 282 + const galleryRkey = galleryUri.split('/').pop() 283 + const galleryUrl = `${window.location.origin}/profile/${$viewer.did}/gallery/${galleryRkey}` 284 + await createBskyPost({ 285 + url: galleryUrl, 286 + location: location ? { name: location.name, address: location.address } : null, 287 + description: description.trim() || undefined, 288 + images: photos, 289 + }) 290 + } 291 + 277 292 queryClient.invalidateQueries({ queryKey: ['getFeed'] }) 278 293 goto('/') 279 294 } catch (err: any) { ··· 392 407 /> 393 408 </Field> 394 409 <LocationInput bind:value={location} /> 410 + <Checkbox bind:checked={postToBluesky} label="Post to Bluesky" /> 395 411 </div> 396 412 {/if} 397 413
+16
app/routes/profile/[did]/gallery/[rkey]/+page.svelte
··· 19 19 const galleryUri = $derived(data.galleryUri) 20 20 const galleryQ = createQuery(() => galleryQuery(galleryUri)) 21 21 const gallery = $derived((galleryQ.data as GalleryView) ?? null) 22 + const bskyUrl = $derived((gallery as any)?.crossPost?.url ?? null) 22 23 23 24 const isOwner = $derived($viewer?.did === gallery?.creator?.did) 24 25 ··· 63 64 /> 64 65 <DetailHeader label={gallery?.title ?? 'Gallery'}> 65 66 {#snippet actions()} 67 + {#if bskyUrl} 68 + <a class="bsky-link" href={bskyUrl} target="_blank" rel="noopener noreferrer" title="View on Bluesky"> 69 + <svg width="18" height="18" viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.889-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.135-1.612 123.121 33.664Z"/></svg> 70 + </a> 71 + {/if} 66 72 {#if isOwner} 67 73 <button class="delete-btn" type="button" onclick={deleteGallery} disabled={deleting} aria-label="Delete gallery"> 68 74 <Trash2 size={18} /> ··· 98 104 color: var(--text-muted); 99 105 padding: 48px 16px; 100 106 font-size: 14px; 107 + } 108 + .bsky-link { 109 + color: var(--text-muted); 110 + display: flex; 111 + align-items: center; 112 + padding: 4px; 113 + transition: color 0.15s; 114 + } 115 + .bsky-link:hover { 116 + color: #0085ff; 101 117 } 102 118 .delete-btn { 103 119 background: none;
+129
app/routes/profile/[did]/story/[rkey]/+page.svelte
··· 1 + <script lang="ts"> 2 + import { createQuery } from '@tanstack/svelte-query' 3 + import { storyQuery } from '$lib/queries' 4 + import DetailHeader from '$lib/components/molecules/DetailHeader.svelte' 5 + import OGMeta from '$lib/components/atoms/OGMeta.svelte' 6 + import { MapPin } from 'lucide-svelte' 7 + import type { StoryView } from '$hatk/client' 8 + 9 + let { data } = $props() 10 + 11 + const storyUri = $derived(data.storyUri) 12 + const storyQ = createQuery(() => storyQuery(storyUri)) 13 + const story = $derived((storyQ.data as StoryView) ?? null) 14 + const bskyUrl = $derived((story as any)?.crossPost?.url ?? null) 15 + </script> 16 + 17 + <OGMeta 18 + title={story ? `Story by @${story.creator.handle} — Grain` : 'Story — Grain'} 19 + description="Photo story on Grain" 20 + image="/og/profile/{data.did}/story/{data.rkey}" 21 + /> 22 + <DetailHeader label="Story"> 23 + {#snippet actions()} 24 + {#if bskyUrl} 25 + <a class="bsky-link" href={bskyUrl} target="_blank" rel="noopener noreferrer" title="View on Bluesky"> 26 + <svg width="18" height="18" viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.889-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.135-1.612 123.121 33.664Z"/></svg> 27 + </a> 28 + {/if} 29 + {/snippet} 30 + </DetailHeader> 31 + <div class="detail-page"> 32 + {#if storyQ.isLoading} 33 + <p class="status">Loading...</p> 34 + {:else if !story} 35 + <p class="status">Story not found</p> 36 + {:else} 37 + <div class="story-card"> 38 + <a class="creator" href="/profile/{story.creator.did}"> 39 + {#if story.creator.avatar} 40 + <img class="avatar" src={story.creator.avatar} alt="" /> 41 + {/if} 42 + <span class="name">{story.creator.displayName ?? story.creator.handle}</span> 43 + {#if story.creator.handle} 44 + <span class="handle">@{story.creator.handle}</span> 45 + {/if} 46 + </a> 47 + <div class="image-wrapper"> 48 + <img 49 + src={story.fullsize} 50 + alt="" 51 + style="aspect-ratio: {story.aspectRatio.width}/{story.aspectRatio.height}" 52 + /> 53 + </div> 54 + {#if story.location} 55 + <div class="location"> 56 + <MapPin size={14} /> 57 + <span>{story.location.name}</span> 58 + </div> 59 + {/if} 60 + <time class="time">{new Date(story.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' })}</time> 61 + </div> 62 + {/if} 63 + </div> 64 + 65 + <style> 66 + .detail-page { 67 + max-width: 600px; 68 + margin: 0 auto; 69 + } 70 + .status { 71 + text-align: center; 72 + color: var(--text-muted); 73 + padding: 48px 16px; 74 + font-size: 14px; 75 + } 76 + .story-card { 77 + display: flex; 78 + flex-direction: column; 79 + gap: 12px; 80 + padding: 16px; 81 + } 82 + .creator { 83 + display: flex; 84 + align-items: center; 85 + gap: 10px; 86 + text-decoration: none; 87 + color: var(--text-primary); 88 + } 89 + .avatar { 90 + width: 36px; 91 + height: 36px; 92 + border-radius: 50%; 93 + object-fit: cover; 94 + } 95 + .name { 96 + font-weight: 600; 97 + font-size: 15px; 98 + } 99 + .handle { 100 + color: var(--text-muted); 101 + font-size: 13px; 102 + } 103 + .image-wrapper img { 104 + width: 100%; 105 + object-fit: contain; 106 + background: var(--bg-hover); 107 + } 108 + .location { 109 + display: flex; 110 + align-items: center; 111 + gap: 6px; 112 + color: var(--text-secondary); 113 + font-size: 13px; 114 + } 115 + .time { 116 + color: var(--text-muted); 117 + font-size: 13px; 118 + } 119 + .bsky-link { 120 + color: var(--text-muted); 121 + display: flex; 122 + align-items: center; 123 + padding: 4px; 124 + transition: color 0.15s; 125 + } 126 + .bsky-link:hover { 127 + color: #0085ff; 128 + } 129 + </style>
+13
app/routes/profile/[did]/story/[rkey]/+page.ts
··· 1 + import { browser } from "$app/environment"; 2 + import { storyQuery } from "$lib/queries"; 3 + import type { PageLoad } from "./$types"; 4 + 5 + export const load: PageLoad = async ({ params, parent, fetch }) => { 6 + const did = decodeURIComponent(params.did); 7 + const rkey = params.rkey; 8 + const storyUri = `at://${did}/social.grain.story/${rkey}`; 9 + const { queryClient } = await parent(); 10 + const prefetch = queryClient.prefetchQuery(storyQuery(storyUri, fetch)); 11 + if (!browser) await prefetch; 12 + return { did, rkey, storyUri }; 13 + };
+2 -18
app/routes/settings/profile/+page.svelte
··· 12 12 import Input from '$lib/components/atoms/Input.svelte' 13 13 import RichTextarea from '$lib/components/atoms/RichTextarea.svelte' 14 14 import Toast from '$lib/components/atoms/Toast.svelte' 15 + import Checkbox from '$lib/components/atoms/Checkbox.svelte' 15 16 import { setIncludeExif } from '$lib/preferences' 16 17 import { Camera, LoaderCircle, Trash2 } from 'lucide-svelte' 17 18 ··· 179 180 </div> 180 181 181 182 <div class="preferences"> 182 - <label class="pref-toggle"> 183 - <input type="checkbox" bind:checked={localIncludeExif} /> 184 - <span class="pref-label">Include camera data (EXIF) when uploading photos</span> 185 - </label> 183 + <Checkbox bind:checked={localIncludeExif} label="Include camera data (EXIF) when uploading photos" /> 186 184 </div> 187 185 188 186 <div class="actions"> ··· 255 253 margin-top: 24px; 256 254 padding-top: 16px; 257 255 border-top: 1px solid var(--border); 258 - } 259 - .pref-toggle { 260 - display: flex; 261 - align-items: center; 262 - gap: 10px; 263 - cursor: pointer; 264 - font-size: 14px; 265 - color: var(--text-secondary); 266 - } 267 - .pref-toggle input[type="checkbox"] { 268 - width: 18px; 269 - height: 18px; 270 - accent-color: var(--grain-btn); 271 - cursor: pointer; 272 256 } 273 257 .actions { 274 258 margin-top: 24px;
+706
docs/plans/2026-03-24-story-crosspost-design.md
··· 1 + # Story Cross-Posting & Permalinks Implementation Plan 2 + 3 + > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. 4 + 5 + **Goal:** Add story permalinks, Bluesky cross-posting for stories, and extract shared Bluesky posting logic into a reusable utility. 6 + 7 + **Architecture:** Extract Bluesky post creation from gallery create into `$lib/utils/bsky-post.ts`. Add story permalink route at `/profile/[did]/story/[rkey]`. Add "Post to Bluesky" checkbox to StoryCreate. Add cross-post hydration to story views. 8 + 9 + **Tech Stack:** SvelteKit, TanStack Query, hatk (AT Protocol framework), Bluesky API 10 + 11 + --- 12 + 13 + ### Task 1: Extract shared Bluesky post utility 14 + 15 + **Files:** 16 + - Create: `app/lib/utils/bsky-post.ts` 17 + - Modify: `app/routes/create/+page.svelte` (lines 187-275) 18 + 19 + **Step 1: Create `app/lib/utils/bsky-post.ts`** 20 + 21 + This extracts the Bluesky posting logic from gallery create into a reusable function. 22 + 23 + ```ts 24 + import { callXrpc } from '$hatk/client' 25 + import { parseTextToFacets } from '$lib/utils/rich-text' 26 + 27 + interface BskyPostOptions { 28 + /** The grain permalink URL */ 29 + url: string 30 + /** Optional location data */ 31 + location?: { 32 + name: string 33 + address?: { 34 + locality?: string 35 + region?: string 36 + country?: string 37 + } 38 + } | null 39 + /** Optional description text (will be truncated to fit 300 grapheme limit) */ 40 + description?: string 41 + /** Images to embed (max 4 for Bluesky) */ 42 + images: Array<{ 43 + dataUrl: string 44 + alt?: string 45 + width: number 46 + height: number 47 + }> 48 + } 49 + 50 + export async function createBskyPost(options: BskyPostOptions): Promise<void> { 51 + const { url, location, description, images } = options 52 + 53 + const graphemeLength = (s: string) => [...new Intl.Segmenter().segment(s)].length 54 + 55 + const lines: string[] = [] 56 + if (location) { 57 + lines.push(`📍 ${location.name}`) 58 + if (location.address) { 59 + const parts: string[] = [] 60 + if (location.address.locality) parts.push(location.address.locality) 61 + if (location.address.region) parts.push(location.address.region) 62 + if (location.address.country) parts.push(location.address.country) 63 + if (parts.length > 0) lines.push(parts.join(', ')) 64 + } 65 + } 66 + 67 + const suffix = `\n\n${url}\n\n#grainsocial` 68 + const prefixText = lines.length > 0 ? lines.join('\n') + '\n' : '' 69 + const overhead = graphemeLength(prefixText + suffix) 70 + const maxDesc = 300 - overhead 71 + 72 + if (description?.trim()) { 73 + let desc = description.trim() 74 + if (graphemeLength(desc) > maxDesc) { 75 + const segments = [...new Intl.Segmenter().segment(desc)] 76 + desc = segments.slice(0, Math.max(0, maxDesc - 1)).map((s) => s.segment).join('') + '…' 77 + } 78 + if (desc) { 79 + lines.push('') 80 + lines.push(desc) 81 + } 82 + } 83 + lines.push('') 84 + lines.push(url) 85 + lines.push('') 86 + lines.push('#grainsocial') 87 + 88 + const postText = lines.join('\n') 89 + 90 + // Resolve Bluesky handles for mentions 91 + const resolveHandle = async (handle: string): Promise<string | null> => { 92 + try { 93 + const res = await fetch( 94 + `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}` 95 + ) 96 + if (!res.ok) return null 97 + const data = await res.json() 98 + return data.did ?? null 99 + } catch { 100 + return null 101 + } 102 + } 103 + const postFacets = (await parseTextToFacets(postText, resolveHandle)).facets 104 + 105 + // Upload images (max 4) 106 + const imageRefs: Array<{ image: any; alt: string; aspectRatio?: { width: number; height: number } }> = [] 107 + for (const img of images.slice(0, 4)) { 108 + const base64 = img.dataUrl.split(',')[1] 109 + const binary = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)) 110 + const blob = new Blob([binary], { type: 'image/jpeg' }) 111 + const uploadResult = await callXrpc('dev.hatk.uploadBlob', blob as any) 112 + imageRefs.push({ 113 + image: (uploadResult as any).blob, 114 + alt: img.alt || '', 115 + aspectRatio: { width: img.width, height: img.height }, 116 + }) 117 + } 118 + 119 + await callXrpc('dev.hatk.createRecord', { 120 + collection: 'app.bsky.feed.post', 121 + record: { 122 + $type: 'app.bsky.feed.post', 123 + text: postText, 124 + facets: postFacets.length > 0 ? postFacets : undefined, 125 + embed: imageRefs.length > 0 126 + ? { $type: 'app.bsky.embed.images', images: imageRefs } 127 + : undefined, 128 + tags: ['grainsocial'], 129 + createdAt: new Date().toISOString(), 130 + }, 131 + }) 132 + } 133 + ``` 134 + 135 + **Step 2: Refactor gallery create to use the shared utility** 136 + 137 + In `app/routes/create/+page.svelte`, replace lines 187-275 (the `if (postToBluesky && $viewer)` block) with: 138 + 139 + ```ts 140 + import { createBskyPost } from '$lib/utils/bsky-post' 141 + 142 + // ... inside publish(), after step 4 (gallery items created): 143 + 144 + // 5. Create Bluesky post if opted in 145 + if (postToBluesky && $viewer) { 146 + const galleryRkey = galleryUri.split('/').pop() 147 + const galleryUrl = `https://grain.social/profile/${$viewer.did}/gallery/${galleryRkey}` 148 + await createBskyPost({ 149 + url: galleryUrl, 150 + location: location ? { 151 + name: location.name, 152 + address: location.address, 153 + } : null, 154 + description: description.trim() || undefined, 155 + images: photos, 156 + }) 157 + } 158 + ``` 159 + 160 + **Step 3: Verify the gallery create flow still works** 161 + 162 + Run: `cd /Users/chadmiller/code/grain && node /Users/chadmiller/code/hatk/packages/hatk/dist/cli.js dev` 163 + Expected: Dev server starts, gallery creation with "Post to Bluesky" still functions. 164 + 165 + **Step 4: Commit** 166 + 167 + ```bash 168 + git add app/lib/utils/bsky-post.ts app/routes/create/+page.svelte 169 + git commit -m "refactor: extract shared Bluesky post utility from gallery create" 170 + ``` 171 + 172 + --- 173 + 174 + ### Task 2: Add story permalink route 175 + 176 + **Files:** 177 + - Create: `app/routes/profile/[did]/story/[rkey]/+page.ts` 178 + - Create: `app/routes/profile/[did]/story/[rkey]/+page.svelte` 179 + 180 + **Step 1: Create the page load file** 181 + 182 + `app/routes/profile/[did]/story/[rkey]/+page.ts` — follows same pattern as gallery `+page.ts`: 183 + 184 + ```ts 185 + import { browser } from "$app/environment" 186 + import { storyQuery } from "$lib/queries" 187 + import type { PageLoad } from "./$types" 188 + 189 + export const load: PageLoad = async ({ params, parent, fetch }) => { 190 + const did = decodeURIComponent(params.did) 191 + const rkey = params.rkey 192 + const storyUri = `at://${did}/social.grain.story/${rkey}` 193 + const { queryClient } = await parent() 194 + const prefetch = queryClient.prefetchQuery(storyQuery(storyUri, fetch)) 195 + if (!browser) await prefetch 196 + return { did, rkey, storyUri } 197 + } 198 + ``` 199 + 200 + **Step 2: Add `storyQuery` to `app/lib/queries.ts`** 201 + 202 + Add after the existing `storiesQuery`: 203 + 204 + ```ts 205 + export const storyQuery = (storyUri: string, f?: Fetch) => 206 + queryOptions({ 207 + queryKey: ["getStory", storyUri], 208 + queryFn: () => 209 + callXrpc("social.grain.unspecced.getStory", { story: storyUri }, f).then( 210 + (r) => r?.story ?? null, 211 + ), 212 + staleTime: 60_000, 213 + }); 214 + ``` 215 + 216 + **Step 3: Create the XRPC endpoint `server/xrpc/getStory.ts`** 217 + 218 + This is like `getStories.ts` but fetches a single story by URI with NO 24h filter: 219 + 220 + ```ts 221 + import { defineQuery } from "$hatk" 222 + import { views } from "$hatk" 223 + import type { GrainActorProfile, Story } from "$hatk" 224 + 225 + export default defineQuery("social.grain.unspecced.getStory", async (ctx) => { 226 + const { db, ok } = ctx 227 + const storyUri = ctx.params.story 228 + if (!storyUri) return ok({ story: null }) 229 + 230 + const rows = (await db.query( 231 + `SELECT uri, cid, did, media, aspect_ratio, location, address, created_at 232 + FROM "social.grain.story" 233 + WHERE uri = $1`, 234 + [storyUri], 235 + )) as { 236 + uri: string 237 + cid: string 238 + did: string 239 + media: string 240 + aspect_ratio: string 241 + location: string | null 242 + address: string | null 243 + created_at: string 244 + }[] 245 + 246 + const row = rows[0] 247 + if (!row) return ok({ story: null }) 248 + 249 + // Resolve author profile 250 + const profiles = await ctx.lookup<GrainActorProfile>("social.grain.actor.profile", "did", [row.did]) 251 + const author = profiles.get(row.did) 252 + const profileView = author 253 + ? views.grainActorDefsProfileView({ 254 + cid: author.cid, 255 + did: author.did, 256 + handle: author.handle ?? author.did, 257 + displayName: author.value.displayName, 258 + avatar: ctx.blobUrl(author.did, author.value.avatar) ?? undefined, 259 + }) 260 + : views.grainActorDefsProfileView({ 261 + cid: "", 262 + did: row.did, 263 + handle: row.did, 264 + }) 265 + 266 + let blobRef: any 267 + try { 268 + blobRef = typeof row.media === "string" ? JSON.parse(row.media) : row.media 269 + } catch { 270 + blobRef = row.media 271 + } 272 + 273 + let aspectRatio: { width: number; height: number } 274 + try { 275 + aspectRatio = typeof row.aspect_ratio === "string" ? JSON.parse(row.aspect_ratio) : row.aspect_ratio 276 + } catch { 277 + aspectRatio = { width: 4, height: 3 } 278 + } 279 + 280 + let location: Story["location"] | null = null 281 + if (row.location) { 282 + try { 283 + location = typeof row.location === "string" ? JSON.parse(row.location) : row.location 284 + } catch { 285 + location = null 286 + } 287 + } 288 + 289 + let address: Story["address"] | null = null 290 + if (row.address) { 291 + try { 292 + address = typeof row.address === "string" ? JSON.parse(row.address) : row.address 293 + } catch { 294 + address = null 295 + } 296 + } 297 + 298 + // Cross-post lookup 299 + let crossPost: { url: string } | undefined 300 + const rkey = row.uri.split("/").pop() 301 + const searchUrl = `grain.social/profile/${row.did}/story/${rkey}` 302 + const postRows = (await db.query( 303 + `SELECT uri FROM "app.bsky.feed.post" WHERE did = $1 AND "text" LIKE '%' || $2 || '%' LIMIT 1`, 304 + [row.did, searchUrl], 305 + )) as Array<{ uri: string }> 306 + if (postRows.length) { 307 + const postRkey = postRows[0].uri.split("/").pop() 308 + crossPost = { url: `https://bsky.app/profile/${row.did}/post/${postRkey}` } 309 + } 310 + 311 + const story = views.storyView({ 312 + uri: row.uri, 313 + cid: row.cid, 314 + creator: profileView, 315 + thumb: ctx.blobUrl(row.did, blobRef, "feed_thumbnail") ?? "", 316 + fullsize: ctx.blobUrl(row.did, blobRef, "feed_fullsize") ?? "", 317 + aspectRatio, 318 + ...(location 319 + ? { 320 + location: { name: location.name, value: location.value }, 321 + ...(address ? { address } : {}), 322 + } 323 + : {}), 324 + ...(crossPost ? { crossPost } : {}), 325 + createdAt: row.created_at, 326 + }) 327 + 328 + return ok({ story }) 329 + }) 330 + ``` 331 + 332 + **Step 4: Create the lexicon `lexicons/social/grain/unspecced/getStory.json`** 333 + 334 + ```json 335 + { 336 + "lexicon": 1, 337 + "id": "social.grain.unspecced.getStory", 338 + "defs": { 339 + "main": { 340 + "type": "query", 341 + "parameters": { 342 + "type": "params", 343 + "required": ["story"], 344 + "properties": { 345 + "story": { "type": "string", "format": "at-uri" } 346 + } 347 + }, 348 + "output": { 349 + "encoding": "application/json", 350 + "schema": { 351 + "type": "object", 352 + "properties": { 353 + "story": { "type": "ref", "ref": "social.grain.story.defs#storyView" } 354 + } 355 + } 356 + } 357 + } 358 + } 359 + } 360 + ``` 361 + 362 + **Step 5: Add `crossPost` to `storyView` in `lexicons/social/grain/story/defs.json`** 363 + 364 + Add to properties: 365 + 366 + ```json 367 + "crossPost": { "type": "ref", "ref": "social.grain.gallery.defs#crossPostInfo" } 368 + ``` 369 + 370 + **Step 6: Create the permalink page `app/routes/profile/[did]/story/[rkey]/+page.svelte`** 371 + 372 + ```svelte 373 + <script lang="ts"> 374 + import { createQuery } from '@tanstack/svelte-query' 375 + import { storyQuery } from '$lib/queries' 376 + import DetailHeader from '$lib/components/molecules/DetailHeader.svelte' 377 + import OGMeta from '$lib/components/atoms/OGMeta.svelte' 378 + import { MapPin } from 'lucide-svelte' 379 + import type { StoryView } from '$hatk/client' 380 + 381 + let { data } = $props() 382 + 383 + const storyUri = $derived(data.storyUri) 384 + const storyQ = createQuery(() => storyQuery(storyUri)) 385 + const story = $derived((storyQ.data as StoryView) ?? null) 386 + const bskyUrl = $derived((story as any)?.crossPost?.url ?? null) 387 + </script> 388 + 389 + <OGMeta 390 + title={story ? `Story by @${story.creator.handle} — Grain` : 'Story — Grain'} 391 + description="Photo story on Grain" 392 + image={story?.fullsize} 393 + /> 394 + <DetailHeader label="Story"> 395 + {#snippet actions()} 396 + {#if bskyUrl} 397 + <a class="bsky-link" href={bskyUrl} target="_blank" rel="noopener noreferrer" title="View on Bluesky"> 398 + <svg width="18" height="18" viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.889-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.135-1.612 123.121 33.664Z"/></svg> 399 + </a> 400 + {/if} 401 + {/snippet} 402 + </DetailHeader> 403 + <div class="detail-page"> 404 + {#if storyQ.isLoading} 405 + <p class="status">Loading...</p> 406 + {:else if !story} 407 + <p class="status">Story not found</p> 408 + {:else} 409 + <div class="story-card"> 410 + <a class="creator" href="/profile/{story.creator.did}"> 411 + {#if story.creator.avatar} 412 + <img class="avatar" src={story.creator.avatar} alt="" /> 413 + {/if} 414 + <span class="name">{story.creator.displayName ?? story.creator.handle}</span> 415 + </a> 416 + <div class="image-wrapper"> 417 + <img 418 + src={story.fullsize} 419 + alt="" 420 + style="aspect-ratio: {story.aspectRatio.width}/{story.aspectRatio.height}" 421 + /> 422 + </div> 423 + {#if story.location} 424 + <div class="location"> 425 + <MapPin size={14} /> 426 + <span>{story.location.name}</span> 427 + </div> 428 + {/if} 429 + <time class="time">{new Date(story.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' })}</time> 430 + </div> 431 + {/if} 432 + </div> 433 + 434 + <style> 435 + .detail-page { 436 + max-width: 600px; 437 + margin: 0 auto; 438 + } 439 + .status { 440 + text-align: center; 441 + color: var(--text-muted); 442 + padding: 48px 16px; 443 + font-size: 14px; 444 + } 445 + .story-card { 446 + display: flex; 447 + flex-direction: column; 448 + gap: 12px; 449 + padding: 16px; 450 + } 451 + .creator { 452 + display: flex; 453 + align-items: center; 454 + gap: 10px; 455 + text-decoration: none; 456 + color: var(--text-primary); 457 + } 458 + .avatar { 459 + width: 36px; 460 + height: 36px; 461 + border-radius: 50%; 462 + object-fit: cover; 463 + } 464 + .name { 465 + font-weight: 600; 466 + font-size: 15px; 467 + } 468 + .image-wrapper img { 469 + width: 100%; 470 + border-radius: 8px; 471 + object-fit: contain; 472 + background: var(--bg-hover); 473 + } 474 + .location { 475 + display: flex; 476 + align-items: center; 477 + gap: 6px; 478 + color: var(--text-secondary); 479 + font-size: 13px; 480 + } 481 + .time { 482 + color: var(--text-muted); 483 + font-size: 13px; 484 + } 485 + .bsky-link { 486 + color: var(--text-muted); 487 + display: flex; 488 + align-items: center; 489 + padding: 4px; 490 + transition: color 0.15s; 491 + } 492 + .bsky-link:hover { 493 + color: #0085ff; 494 + } 495 + </style> 496 + ``` 497 + 498 + **Step 7: Regenerate types and commit** 499 + 500 + ```bash 501 + node /Users/chadmiller/code/hatk/packages/hatk/dist/cli.js generate types 502 + git add app/routes/profile/\[did\]/story/\[rkey\]/ server/xrpc/getStory.ts lexicons/social/grain/unspecced/getStory.json lexicons/social/grain/story/defs.json app/lib/queries.ts hatk.generated.ts 503 + git commit -m "feat: add story permalink route with cross-post hydration" 504 + ``` 505 + 506 + --- 507 + 508 + ### Task 3: Add "Post to Bluesky" to StoryCreate 509 + 510 + **Files:** 511 + - Modify: `app/lib/components/molecules/StoryCreate.svelte` 512 + 513 + **Step 1: Add checkbox and Bluesky posting to StoryCreate** 514 + 515 + Add imports: 516 + ```ts 517 + import Checkbox from '$lib/components/atoms/Checkbox.svelte' 518 + import { createBskyPost } from '$lib/utils/bsky-post' 519 + import { viewer } from '$lib/stores' 520 + ``` 521 + 522 + Add state: 523 + ```ts 524 + let postToBluesky = $state(false) 525 + ``` 526 + 527 + After the existing `await callXrpc('dev.hatk.createRecord', ...)` in `publish()`, add: 528 + 529 + ```ts 530 + const storyUri = (result as any).uri as string 531 + 532 + // Post to Bluesky if opted in 533 + if (postToBluesky && $viewer) { 534 + const storyRkey = storyUri.split('/').pop() 535 + const storyUrl = `https://grain.social/profile/${$viewer.did}/story/${storyRkey}` 536 + await createBskyPost({ 537 + url: storyUrl, 538 + location: location ? { 539 + name: location.name, 540 + address: location.address, 541 + } : null, 542 + images: [{ 543 + dataUrl: photo.dataUrl, 544 + alt: '', 545 + width: photo.width, 546 + height: photo.height, 547 + }], 548 + }) 549 + } 550 + ``` 551 + 552 + Add checkbox in the template below the LocationInput: 553 + ```svelte 554 + <Checkbox bind:checked={postToBluesky} label="Post to Bluesky" /> 555 + ``` 556 + 557 + **Step 2: Commit** 558 + 559 + ```bash 560 + git add app/lib/components/molecules/StoryCreate.svelte 561 + git commit -m "feat: add Post to Bluesky option to story creation" 562 + ``` 563 + 564 + --- 565 + 566 + ### Task 4: OG image endpoint for stories (optional but recommended) 567 + 568 + **Files:** 569 + - Create: `server/og/story.ts` 570 + 571 + **Step 1: Create `server/og/story.ts`** 572 + 573 + Simpler than gallery OG — single image with author info: 574 + 575 + ```ts 576 + import { defineOG } from "$hatk" 577 + import type { GrainActorProfile, Story } from "$hatk" 578 + 579 + export default defineOG("/og/profile/:did/story/:rkey", async (ctx) => { 580 + const { db, params, fetchImage, lookup, blobUrl } = ctx 581 + const { did, rkey } = params 582 + 583 + const storyUri = `at://${did}/social.grain.story/${rkey}` 584 + 585 + const rows = (await db.query( 586 + `SELECT uri, did, cid, media, aspect_ratio FROM "social.grain.story" WHERE uri = $1`, 587 + [storyUri], 588 + )) as Array<{ 589 + uri: string 590 + did: string 591 + cid: string 592 + media: string 593 + aspect_ratio: string 594 + }> 595 + 596 + const row = rows[0] 597 + if (!row) { 598 + return { 599 + element: { 600 + type: "div", 601 + props: { 602 + style: { display: "flex", width: "100%", height: "100%", background: "#ffffff", color: "#171717", alignItems: "center", justifyContent: "center" }, 603 + children: "Story not found", 604 + }, 605 + }, 606 + } 607 + } 608 + 609 + let blobRef: any 610 + try { 611 + blobRef = typeof row.media === "string" ? JSON.parse(row.media) : row.media 612 + } catch { 613 + blobRef = row.media 614 + } 615 + 616 + const imageUrl = blobUrl(row.did, blobRef, "feed_fullsize") 617 + const imageDataUrl = imageUrl ? await fetchImage(imageUrl) : null 618 + 619 + const profiles = await lookup<GrainActorProfile>("social.grain.actor.profile", "did", [did]) 620 + const author = profiles.get(did) 621 + const avatarRef = author ? blobUrl(did, author.value.avatar) : null 622 + const avatarDataUrl = avatarRef ? await fetchImage(avatarRef) : null 623 + 624 + return { 625 + element: { 626 + type: "div", 627 + props: { 628 + style: { display: "flex", width: "100%", height: "100%", backgroundColor: "#000000", position: "relative" }, 629 + children: [ 630 + ...(imageDataUrl ? [{ 631 + type: "img", 632 + props: { 633 + src: imageDataUrl, 634 + style: { width: "100%", height: "100%", objectFit: "contain" }, 635 + }, 636 + }] : []), 637 + { 638 + type: "div", 639 + props: { 640 + style: { 641 + position: "absolute", bottom: "0", left: "0", right: "0", height: "80px", 642 + background: "linear-gradient(transparent, rgba(0,0,0,0.7))", 643 + display: "flex", alignItems: "flex-end", padding: "0 24px 16px 24px", gap: "12px", 644 + }, 645 + children: [ 646 + ...(avatarDataUrl ? [{ 647 + type: "img", 648 + props: { 649 + src: avatarDataUrl, 650 + style: { width: "44px", height: "44px", borderRadius: "22px", objectFit: "cover" as const }, 651 + }, 652 + }] : []), 653 + { 654 + type: "div", 655 + props: { 656 + style: { display: "flex", flexDirection: "column", gap: "2px" }, 657 + children: [ 658 + { 659 + type: "div", 660 + props: { 661 + children: author?.value.displayName || `@${author?.handle || did.slice(0, 24)}`, 662 + style: { fontSize: 24, fontWeight: 600, color: "#ffffff" }, 663 + }, 664 + }, 665 + { 666 + type: "div", 667 + props: { 668 + children: "Grain", 669 + style: { fontSize: 16, color: "rgba(255,255,255,0.7)" }, 670 + }, 671 + }, 672 + ], 673 + }, 674 + }, 675 + ], 676 + }, 677 + }, 678 + ], 679 + }, 680 + }, 681 + meta: { 682 + title: `Story by @${author?.handle || did.slice(0, 24)} — Grain`, 683 + description: "Photo story on Grain", 684 + }, 685 + } 686 + }) 687 + ``` 688 + 689 + **Step 2: Update story permalink OGMeta to use the OG endpoint** 690 + 691 + In `app/routes/profile/[did]/story/[rkey]/+page.svelte`, change the OGMeta image prop: 692 + 693 + ```svelte 694 + <OGMeta 695 + title={story ? `Story by @${story.creator.handle} — Grain` : 'Story — Grain'} 696 + description="Photo story on Grain" 697 + image="/og/profile/{data.did}/story/{data.rkey}" 698 + /> 699 + ``` 700 + 701 + **Step 3: Commit** 702 + 703 + ```bash 704 + git add server/og/story.ts app/routes/profile/\[did\]/story/\[rkey\]/+page.svelte 705 + git commit -m "feat: add OG image endpoint for story permalinks" 706 + ```
+1
hatk.config.ts
··· 15 15 "repo:social.grain.favorite", 16 16 "repo:social.grain.comment", 17 17 "repo:social.grain.story", 18 + "repo:app.bsky.feed.post?action=create", 18 19 ].join(" "); 19 20 20 21 export default defineConfig({
+1 -1
hatk.generated.client.ts
··· 3 3 // to avoid pulling in server-only dependencies. 4 4 export type { XrpcSchema } from './hatk.generated.ts' 5 5 import type { XrpcSchema } from './hatk.generated.ts' 6 - export type { BskyActorProfile, BskyGraphFollow, CreateReport, DescribeCollections, DescribeFeeds, DescribeLabels, GetFeed, GetPreferences, GetRecord, GetRecords, PutPreference, SearchRecords, UploadBlob, GrainActorProfile, Comment, Favorite, Gallery, Item, GrainGraphFollow, Photo, Exif, Story, DeleteGallery, GetActorProfile, GetCameras, GetFollowers, GetFollowing, GetGallery, GetGalleryThread, GetKnownFollowers, GetLocations, GetNotifications, GetStories, GetStoryAuthors, GetSuggestedFollows, SearchGalleries, SearchProfiles, RecordRegistry, CreateRecord, DeleteRecord, PutRecord, AdultContentPref, BskyAppProgressGuide, BskyAppStatePref, ContentLabelPref, DeclaredAgePref, FeedViewPref, HiddenPostsPref, InterestsPref, KnownFollowers, LabelerPrefItem, LabelersPref, LiveEventPreferences, MutedWord, MutedWordsPref, Nux, PersonalDetailsPref, PostInteractionSettingsPref, ProfileAssociated, ProfileAssociatedActivitySubscription, ProfileAssociatedChat, ProfileAssociatedGerm, BskyActorDefsProfileView, ProfileViewBasic, BskyActorDefsProfileViewDetailed, SavedFeed, SavedFeedsPref, SavedFeedsPrefV2, StatusView, ThreadViewPref, VerificationPrefs, VerificationState, VerificationView, BskyActorDefsViewerState, BlockedAuthor, BlockedPost, FeedViewPost, GeneratorView, GeneratorViewerState, Interaction, NotFoundPost, PostView, ReasonPin, ReasonRepost, ReplyRef, SkeletonFeedPost, SkeletonReasonPin, SkeletonReasonRepost, ThreadContext, ThreadViewPost, ThreadgateView, BskyFeedDefsViewerState, ListItemView, ListView, ListViewBasic, ListViewerState, NotFoundActor, Relationship, StarterPackView, StarterPackViewBasic, ActivitySubscription, ChatPreference, FilterablePreference, Preference, Preferences, RecordDeleted, SubjectActivitySubscription, ByteSlice, Link, Mention, Tag, Label, LabelValueDefinition, LabelValueDefinitionStrings, SelfLabel, SelfLabels, RepoRef, LabelDefinition, LabelLocale, GrainActorDefsProfileView, GrainActorDefsProfileViewDetailed, GrainActorDefsViewerState, CommentView, AspectRatio, GalleryView, GrainGalleryDefsViewerState, PhotoView, ExifView, GalleryState, StoryView, CameraItem, GetFollowersFollowerItem, FollowingItem, GetKnownFollowersFollowerItem, LocationItem, NotificationItem, StoryAuthor, SuggestedItem, ProfileSearchResult } from './hatk.generated.ts' 6 + export type { BskyActorProfile, Post, Postgate, Threadgate, BskyGraphFollow, CreateReport, DescribeCollections, DescribeFeeds, DescribeLabels, GetFeed, GetPreferences, GetRecord, GetRecords, PutPreference, SearchRecords, UploadBlob, GrainActorProfile, Comment, Favorite, Gallery, Item, GrainGraphFollow, Photo, Exif, Story, DeleteGallery, GetActorProfile, GetCameras, GetFollowers, GetFollowing, GetGallery, GetGalleryThread, GetKnownFollowers, GetLocations, GetNotifications, GetStories, GetStory, GetStoryAuthors, GetSuggestedFollows, SearchGalleries, SearchProfiles, RecordRegistry, CreateRecord, DeleteRecord, PutRecord, Nux, MutedWord, SavedFeed, StatusView, BskyActorDefsProfileView, BskyActorDefsViewerState, FeedViewPref, LabelersPref, InterestsPref, KnownFollowers, MutedWordsPref, SavedFeedsPref, ThreadViewPref, DeclaredAgePref, HiddenPostsPref, LabelerPrefItem, AdultContentPref, BskyAppStatePref, ContentLabelPref, ProfileViewBasic, SavedFeedsPrefV2, VerificationView, ProfileAssociated, VerificationPrefs, VerificationState, PersonalDetailsPref, BskyActorDefsProfileViewDetailed, BskyAppProgressGuide, LiveEventPreferences, ProfileAssociatedChat, ProfileAssociatedGerm, PostInteractionSettingsPref, ProfileAssociatedActivitySubscription, BskyEmbedDefsAspectRatio, ExternalView, External, ViewExternal, ImagesView, Image, ViewImage, RecordView, ViewRecord, ViewBlocked, ViewDetached, ViewNotFound, RecordWithMediaView, VideoView, Caption, PostView, BskyFeedDefsReplyRef, ReasonPin, BlockedPost, Interaction, BskyFeedDefsViewerState, FeedViewPost, NotFoundPost, ReasonRepost, BlockedAuthor, GeneratorView, ThreadContext, ThreadViewPost, ThreadgateView, SkeletonFeedPost, SkeletonReasonPin, GeneratorViewerState, SkeletonReasonRepost, Entity, PostReplyRef, TextSlice, DisableRule, ListRule, MentionRule, FollowerRule, FollowingRule, ListView, ListItemView, Relationship, ListViewBasic, NotFoundActor, ListViewerState, StarterPackView, StarterPackViewBasic, LabelerView, LabelerPolicies, LabelerViewerState, LabelerViewDetailed, Preference, Preferences, RecordDeleted, ChatPreference, ActivitySubscription, FilterablePreference, SubjectActivitySubscription, Tag, Link, Mention, ByteSlice, Label, SelfLabels, SelfLabel, LabelValueDefinition, LabelValueDefinitionStrings, RepoRef, LabelDefinition, LabelLocale, GrainActorDefsProfileView, GrainActorDefsProfileViewDetailed, GrainActorDefsViewerState, CommentView, GrainDefsAspectRatio, GalleryView, CrossPostInfo, GrainGalleryDefsViewerState, PhotoView, ExifView, GalleryState, StoryView, CameraItem, GetFollowersFollowerItem, FollowingItem, GetKnownFollowersFollowerItem, LocationItem, NotificationItem, StoryAuthor, SuggestedItem, ProfileSearchResult } from './hatk.generated.ts' 7 7 8 8 const _procedures = new Set(['dev.hatk.createRecord', 'dev.hatk.createReport', 'dev.hatk.deleteRecord', 'dev.hatk.putPreference', 'dev.hatk.putRecord', 'social.grain.unspecced.deleteGallery']) 9 9 const _blobInputs = new Set(['dev.hatk.uploadBlob'])
+122 -60
hatk.generated.ts
··· 7 7 8 8 // ─── Lexicon Definitions ──────────────────────────────────────────── 9 9 10 - const bskyActorDefsLex = {"lexicon":1,"id":"app.bsky.actor.defs","defs":{"adultContentPref":{"properties":{"enabled":{"default":false,"type":"boolean"}},"required":["enabled"],"type":"object"},"bskyAppProgressGuide":{"description":"If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress.","properties":{"guide":{"maxLength":100,"type":"string"}},"required":["guide"],"type":"object"},"bskyAppStatePref":{"description":"A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this.","properties":{"activeProgressGuide":{"ref":"#bskyAppProgressGuide","type":"ref"},"nuxs":{"description":"Storage for NUXs the user has encountered.","items":{"ref":"app.bsky.actor.defs#nux","type":"ref"},"maxLength":100,"type":"array"},"queuedNudges":{"description":"An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user.","items":{"maxLength":100,"type":"string"},"maxLength":1000,"type":"array"}},"type":"object"},"contentLabelPref":{"properties":{"label":{"type":"string"},"labelerDid":{"description":"Which labeler does this preference apply to? If undefined, applies globally.","format":"did","type":"string"},"visibility":{"knownValues":["ignore","show","warn","hide"],"type":"string"}},"required":["label","visibility"],"type":"object"},"declaredAgePref":{"description":"Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration.","properties":{"isOverAge13":{"description":"Indicates if the user has declared that they are over 13 years of age.","type":"boolean"},"isOverAge16":{"description":"Indicates if the user has declared that they are over 16 years of age.","type":"boolean"},"isOverAge18":{"description":"Indicates if the user has declared that they are over 18 years of age.","type":"boolean"}},"type":"object"},"feedViewPref":{"properties":{"feed":{"description":"The URI of the feed, or an identifier which describes the feed.","type":"string"},"hideQuotePosts":{"description":"Hide quote posts in the feed.","type":"boolean"},"hideReplies":{"description":"Hide replies in the feed.","type":"boolean"},"hideRepliesByLikeCount":{"description":"Hide replies in the feed if they do not have this number of likes.","type":"integer"},"hideRepliesByUnfollowed":{"default":true,"description":"Hide replies in the feed if they are not by followed users.","type":"boolean"},"hideReposts":{"description":"Hide reposts in the feed.","type":"boolean"}},"required":["feed"],"type":"object"},"hiddenPostsPref":{"properties":{"items":{"description":"A list of URIs of posts the account owner has hidden.","items":{"format":"at-uri","type":"string"},"type":"array"}},"required":["items"],"type":"object"},"interestsPref":{"properties":{"tags":{"description":"A list of tags which describe the account owner's interests gathered during onboarding.","items":{"maxGraphemes":64,"maxLength":640,"type":"string"},"maxLength":100,"type":"array"}},"required":["tags"],"type":"object"},"knownFollowers":{"description":"The subject's followers whom you also follow","properties":{"count":{"type":"integer"},"followers":{"items":{"ref":"#profileViewBasic","type":"ref"},"maxLength":5,"minLength":0,"type":"array"}},"required":["count","followers"],"type":"object"},"labelerPrefItem":{"properties":{"did":{"format":"did","type":"string"}},"required":["did"],"type":"object"},"labelersPref":{"properties":{"labelers":{"items":{"ref":"#labelerPrefItem","type":"ref"},"type":"array"}},"required":["labelers"],"type":"object"},"liveEventPreferences":{"description":"Preferences for live events.","properties":{"hiddenFeedIds":{"description":"A list of feed IDs that the user has hidden from live events.","items":{"type":"string"},"type":"array"},"hideAllFeeds":{"default":false,"description":"Whether to hide all feeds from live events.","type":"boolean"}},"type":"object"},"mutedWord":{"description":"A word that the account owner has muted.","properties":{"actorTarget":{"default":"all","description":"Groups of users to apply the muted word to. If undefined, applies to all users.","knownValues":["all","exclude-following"],"type":"string"},"expiresAt":{"description":"The date and time at which the muted word will expire and no longer be applied.","format":"datetime","type":"string"},"id":{"type":"string"},"targets":{"description":"The intended targets of the muted word.","items":{"ref":"app.bsky.actor.defs#mutedWordTarget","type":"ref"},"type":"array"},"value":{"description":"The muted word itself.","maxGraphemes":1000,"maxLength":10000,"type":"string"}},"required":["value","targets"],"type":"object"},"mutedWordTarget":{"knownValues":["content","tag"],"maxGraphemes":64,"maxLength":640,"type":"string"},"mutedWordsPref":{"properties":{"items":{"description":"A list of words the account owner has muted.","items":{"ref":"app.bsky.actor.defs#mutedWord","type":"ref"},"type":"array"}},"required":["items"],"type":"object"},"nux":{"description":"A new user experiences (NUX) storage object","properties":{"completed":{"default":false,"type":"boolean"},"data":{"description":"Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters.","maxGraphemes":300,"maxLength":3000,"type":"string"},"expiresAt":{"description":"The date and time at which the NUX will expire and should be considered completed.","format":"datetime","type":"string"},"id":{"maxLength":100,"type":"string"}},"required":["id","completed"],"type":"object"},"personalDetailsPref":{"properties":{"birthDate":{"description":"The birth date of account owner.","format":"datetime","type":"string"}},"type":"object"},"postInteractionSettingsPref":{"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.","properties":{"postgateEmbeddingRules":{"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.","items":{"refs":["app.bsky.feed.postgate#disableRule"],"type":"union"},"maxLength":5,"type":"array"},"threadgateAllowRules":{"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.","items":{"refs":["app.bsky.feed.threadgate#mentionRule","app.bsky.feed.threadgate#followerRule","app.bsky.feed.threadgate#followingRule","app.bsky.feed.threadgate#listRule"],"type":"union"},"maxLength":5,"type":"array"}},"required":[],"type":"object"},"preferences":{"items":{"refs":["#adultContentPref","#contentLabelPref","#savedFeedsPref","#savedFeedsPrefV2","#personalDetailsPref","#declaredAgePref","#feedViewPref","#threadViewPref","#interestsPref","#mutedWordsPref","#hiddenPostsPref","#bskyAppStatePref","#labelersPref","#postInteractionSettingsPref","#verificationPrefs","#liveEventPreferences"],"type":"union"},"type":"array"},"profileAssociated":{"properties":{"activitySubscription":{"ref":"#profileAssociatedActivitySubscription","type":"ref"},"chat":{"ref":"#profileAssociatedChat","type":"ref"},"feedgens":{"type":"integer"},"germ":{"ref":"#profileAssociatedGerm","type":"ref"},"labeler":{"type":"boolean"},"lists":{"type":"integer"},"starterPacks":{"type":"integer"}},"type":"object"},"profileAssociatedActivitySubscription":{"properties":{"allowSubscriptions":{"knownValues":["followers","mutuals","none"],"type":"string"}},"required":["allowSubscriptions"],"type":"object"},"profileAssociatedChat":{"properties":{"allowIncoming":{"knownValues":["all","none","following"],"type":"string"}},"required":["allowIncoming"],"type":"object"},"profileAssociatedGerm":{"properties":{"messageMeUrl":{"format":"uri","type":"string"},"showButtonTo":{"knownValues":["usersIFollow","everyone"],"type":"string"}},"required":["showButtonTo","messageMeUrl"],"type":"object"},"profileView":{"properties":{"associated":{"ref":"#profileAssociated","type":"ref"},"avatar":{"format":"uri","type":"string"},"createdAt":{"format":"datetime","type":"string"},"debug":{"description":"Debug information for internal development","type":"unknown"},"description":{"maxGraphemes":256,"maxLength":2560,"type":"string"},"did":{"format":"did","type":"string"},"displayName":{"maxGraphemes":64,"maxLength":640,"type":"string"},"handle":{"format":"handle","type":"string"},"indexedAt":{"format":"datetime","type":"string"},"labels":{"items":{"ref":"com.atproto.label.defs#label","type":"ref"},"type":"array"},"pronouns":{"type":"string"},"status":{"ref":"#statusView","type":"ref"},"verification":{"ref":"#verificationState","type":"ref"},"viewer":{"ref":"#viewerState","type":"ref"}},"required":["did","handle"],"type":"object"},"profileViewBasic":{"properties":{"associated":{"ref":"#profileAssociated","type":"ref"},"avatar":{"format":"uri","type":"string"},"createdAt":{"format":"datetime","type":"string"},"debug":{"description":"Debug information for internal development","type":"unknown"},"did":{"format":"did","type":"string"},"displayName":{"maxGraphemes":64,"maxLength":640,"type":"string"},"handle":{"format":"handle","type":"string"},"labels":{"items":{"ref":"com.atproto.label.defs#label","type":"ref"},"type":"array"},"pronouns":{"type":"string"},"status":{"ref":"#statusView","type":"ref"},"verification":{"ref":"#verificationState","type":"ref"},"viewer":{"ref":"#viewerState","type":"ref"}},"required":["did","handle"],"type":"object"},"profileViewDetailed":{"properties":{"associated":{"ref":"#profileAssociated","type":"ref"},"avatar":{"format":"uri","type":"string"},"banner":{"format":"uri","type":"string"},"createdAt":{"format":"datetime","type":"string"},"debug":{"description":"Debug information for internal development","type":"unknown"},"description":{"maxGraphemes":256,"maxLength":2560,"type":"string"},"did":{"format":"did","type":"string"},"displayName":{"maxGraphemes":64,"maxLength":640,"type":"string"},"followersCount":{"type":"integer"},"followsCount":{"type":"integer"},"handle":{"format":"handle","type":"string"},"indexedAt":{"format":"datetime","type":"string"},"joinedViaStarterPack":{"ref":"app.bsky.graph.defs#starterPackViewBasic","type":"ref"},"labels":{"items":{"ref":"com.atproto.label.defs#label","type":"ref"},"type":"array"},"pinnedPost":{"ref":"com.atproto.repo.strongRef","type":"ref"},"postsCount":{"type":"integer"},"pronouns":{"type":"string"},"status":{"ref":"#statusView","type":"ref"},"verification":{"ref":"#verificationState","type":"ref"},"viewer":{"ref":"#viewerState","type":"ref"},"website":{"format":"uri","type":"string"}},"required":["did","handle"],"type":"object"},"savedFeed":{"properties":{"id":{"type":"string"},"pinned":{"type":"boolean"},"type":{"knownValues":["feed","list","timeline"],"type":"string"},"value":{"type":"string"}},"required":["id","type","value","pinned"],"type":"object"},"savedFeedsPref":{"properties":{"pinned":{"items":{"format":"at-uri","type":"string"},"type":"array"},"saved":{"items":{"format":"at-uri","type":"string"},"type":"array"},"timelineIndex":{"type":"integer"}},"required":["pinned","saved"],"type":"object"},"savedFeedsPrefV2":{"properties":{"items":{"items":{"ref":"app.bsky.actor.defs#savedFeed","type":"ref"},"type":"array"}},"required":["items"],"type":"object"},"statusView":{"properties":{"cid":{"format":"cid","type":"string"},"embed":{"description":"An optional embed associated with the status.","refs":["app.bsky.embed.external#view"],"type":"union"},"expiresAt":{"description":"The date when this status will expire. The application might choose to no longer return the status after expiration.","format":"datetime","type":"string"},"isActive":{"description":"True if the status is not expired, false if it is expired. Only present if expiration was set.","type":"boolean"},"isDisabled":{"description":"True if the user's go-live access has been disabled by a moderator, false otherwise.","type":"boolean"},"record":{"type":"unknown"},"status":{"description":"The status for the account.","knownValues":["app.bsky.actor.status#live"],"type":"string"},"uri":{"format":"at-uri","type":"string"}},"required":["status","record"],"type":"object"},"threadViewPref":{"properties":{"sort":{"description":"Sorting mode for threads.","knownValues":["oldest","newest","most-likes","random","hotness"],"type":"string"}},"type":"object"},"verificationPrefs":{"description":"Preferences for how verified accounts appear in the app.","properties":{"hideBadges":{"default":false,"description":"Hide the blue check badges for verified accounts and trusted verifiers.","type":"boolean"}},"required":[],"type":"object"},"verificationState":{"description":"Represents the verification information about the user this object is attached to.","properties":{"trustedVerifierStatus":{"description":"The user's status as a trusted verifier.","knownValues":["valid","invalid","none"],"type":"string"},"verifications":{"description":"All verifications issued by trusted verifiers on behalf of this user. Verifications by untrusted verifiers are not included.","items":{"ref":"#verificationView","type":"ref"},"type":"array"},"verifiedStatus":{"description":"The user's status as a verified account.","knownValues":["valid","invalid","none"],"type":"string"}},"required":["verifications","verifiedStatus","trustedVerifierStatus"],"type":"object"},"verificationView":{"description":"An individual verification for an associated subject.","properties":{"createdAt":{"description":"Timestamp when the verification was created.","format":"datetime","type":"string"},"isValid":{"description":"True if the verification passes validation, otherwise false.","type":"boolean"},"issuer":{"description":"The user who issued this verification.","format":"did","type":"string"},"uri":{"description":"The AT-URI of the verification record.","format":"at-uri","type":"string"}},"required":["issuer","uri","isValid","createdAt"],"type":"object"},"viewerState":{"description":"Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.","properties":{"activitySubscription":{"description":"This property is present only in selected cases, as an optimization.","ref":"app.bsky.notification.defs#activitySubscription","type":"ref"},"blockedBy":{"type":"boolean"},"blocking":{"format":"at-uri","type":"string"},"blockingByList":{"ref":"app.bsky.graph.defs#listViewBasic","type":"ref"},"followedBy":{"format":"at-uri","type":"string"},"following":{"format":"at-uri","type":"string"},"knownFollowers":{"description":"This property is present only in selected cases, as an optimization.","ref":"#knownFollowers","type":"ref"},"muted":{"type":"boolean"},"mutedByList":{"ref":"app.bsky.graph.defs#listViewBasic","type":"ref"}},"type":"object"}}} as const 10 + const bskyActorDefsLex = {"id":"app.bsky.actor.defs","defs":{"nux":{"type":"object","required":["id","completed"],"properties":{"id":{"type":"string","maxLength":100},"data":{"type":"string","maxLength":3000,"description":"Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters.","maxGraphemes":300},"completed":{"type":"boolean","default":false},"expiresAt":{"type":"string","format":"datetime","description":"The date and time at which the NUX will expire and should be considered completed."}},"description":"A new user experiences (NUX) storage object"},"mutedWord":{"type":"object","required":["value","targets"],"properties":{"id":{"type":"string"},"value":{"type":"string","maxLength":10000,"description":"The muted word itself.","maxGraphemes":1000},"targets":{"type":"array","items":{"ref":"app.bsky.actor.defs#mutedWordTarget","type":"ref"},"description":"The intended targets of the muted word."},"expiresAt":{"type":"string","format":"datetime","description":"The date and time at which the muted word will expire and no longer be applied."},"actorTarget":{"type":"string","default":"all","description":"Groups of users to apply the muted word to. If undefined, applies to all users.","knownValues":["all","exclude-following"]}},"description":"A word that the account owner has muted."},"savedFeed":{"type":"object","required":["id","type","value","pinned"],"properties":{"id":{"type":"string"},"type":{"type":"string","knownValues":["feed","list","timeline"]},"value":{"type":"string"},"pinned":{"type":"boolean"}}},"statusView":{"type":"object","required":["status","record"],"properties":{"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"embed":{"refs":["app.bsky.embed.external#view"],"type":"union","description":"An optional embed associated with the status."},"record":{"type":"unknown"},"status":{"type":"string","description":"The status for the account.","knownValues":["app.bsky.actor.status#live"]},"isActive":{"type":"boolean","description":"True if the status is not expired, false if it is expired. Only present if expiration was set."},"expiresAt":{"type":"string","format":"datetime","description":"The date when this status will expire. The application might choose to no longer return the status after expiration."},"isDisabled":{"type":"boolean","description":"True if the user's go-live access has been disabled by a moderator, false otherwise."}}},"preferences":{"type":"array","items":{"refs":["#adultContentPref","#contentLabelPref","#savedFeedsPref","#savedFeedsPrefV2","#personalDetailsPref","#declaredAgePref","#feedViewPref","#threadViewPref","#interestsPref","#mutedWordsPref","#hiddenPostsPref","#bskyAppStatePref","#labelersPref","#postInteractionSettingsPref","#verificationPrefs","#liveEventPreferences"],"type":"union"}},"profileView":{"type":"object","required":["did","handle"],"properties":{"did":{"type":"string","format":"did"},"debug":{"type":"unknown","description":"Debug information for internal development"},"avatar":{"type":"string","format":"uri"},"handle":{"type":"string","format":"handle"},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"status":{"ref":"#statusView","type":"ref"},"viewer":{"ref":"#viewerState","type":"ref"},"pronouns":{"type":"string"},"createdAt":{"type":"string","format":"datetime"},"indexedAt":{"type":"string","format":"datetime"},"associated":{"ref":"#profileAssociated","type":"ref"},"description":{"type":"string","maxLength":2560,"maxGraphemes":256},"displayName":{"type":"string","maxLength":640,"maxGraphemes":64},"verification":{"ref":"#verificationState","type":"ref"}}},"viewerState":{"type":"object","properties":{"muted":{"type":"boolean"},"blocking":{"type":"string","format":"at-uri"},"blockedBy":{"type":"boolean"},"following":{"type":"string","format":"at-uri"},"followedBy":{"type":"string","format":"at-uri"},"mutedByList":{"ref":"app.bsky.graph.defs#listViewBasic","type":"ref"},"blockingByList":{"ref":"app.bsky.graph.defs#listViewBasic","type":"ref"},"knownFollowers":{"ref":"#knownFollowers","type":"ref","description":"This property is present only in selected cases, as an optimization."},"activitySubscription":{"ref":"app.bsky.notification.defs#activitySubscription","type":"ref","description":"This property is present only in selected cases, as an optimization."}},"description":"Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests."},"feedViewPref":{"type":"object","required":["feed"],"properties":{"feed":{"type":"string","description":"The URI of the feed, or an identifier which describes the feed."},"hideReplies":{"type":"boolean","description":"Hide replies in the feed."},"hideReposts":{"type":"boolean","description":"Hide reposts in the feed."},"hideQuotePosts":{"type":"boolean","description":"Hide quote posts in the feed."},"hideRepliesByLikeCount":{"type":"integer","description":"Hide replies in the feed if they do not have this number of likes."},"hideRepliesByUnfollowed":{"type":"boolean","default":true,"description":"Hide replies in the feed if they are not by followed users."}}},"labelersPref":{"type":"object","required":["labelers"],"properties":{"labelers":{"type":"array","items":{"ref":"#labelerPrefItem","type":"ref"}}}},"interestsPref":{"type":"object","required":["tags"],"properties":{"tags":{"type":"array","items":{"type":"string","maxLength":640,"maxGraphemes":64},"maxLength":100,"description":"A list of tags which describe the account owner's interests gathered during onboarding."}}},"knownFollowers":{"type":"object","required":["count","followers"],"properties":{"count":{"type":"integer"},"followers":{"type":"array","items":{"ref":"#profileViewBasic","type":"ref"},"maxLength":5,"minLength":0}},"description":"The subject's followers whom you also follow"},"mutedWordsPref":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"ref":"app.bsky.actor.defs#mutedWord","type":"ref"},"description":"A list of words the account owner has muted."}}},"savedFeedsPref":{"type":"object","required":["pinned","saved"],"properties":{"saved":{"type":"array","items":{"type":"string","format":"at-uri"}},"pinned":{"type":"array","items":{"type":"string","format":"at-uri"}},"timelineIndex":{"type":"integer"}}},"threadViewPref":{"type":"object","properties":{"sort":{"type":"string","description":"Sorting mode for threads.","knownValues":["oldest","newest","most-likes","random","hotness"]}}},"declaredAgePref":{"type":"object","properties":{"isOverAge13":{"type":"boolean","description":"Indicates if the user has declared that they are over 13 years of age."},"isOverAge16":{"type":"boolean","description":"Indicates if the user has declared that they are over 16 years of age."},"isOverAge18":{"type":"boolean","description":"Indicates if the user has declared that they are over 18 years of age."}},"description":"Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration."},"hiddenPostsPref":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"string","format":"at-uri"},"description":"A list of URIs of posts the account owner has hidden."}}},"labelerPrefItem":{"type":"object","required":["did"],"properties":{"did":{"type":"string","format":"did"}}},"mutedWordTarget":{"type":"string","maxLength":640,"knownValues":["content","tag"],"maxGraphemes":64},"adultContentPref":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean","default":false}}},"bskyAppStatePref":{"type":"object","properties":{"nuxs":{"type":"array","items":{"ref":"app.bsky.actor.defs#nux","type":"ref"},"maxLength":100,"description":"Storage for NUXs the user has encountered."},"queuedNudges":{"type":"array","items":{"type":"string","maxLength":100},"maxLength":1000,"description":"An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user."},"activeProgressGuide":{"ref":"#bskyAppProgressGuide","type":"ref"}},"description":"A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this."},"contentLabelPref":{"type":"object","required":["label","visibility"],"properties":{"label":{"type":"string"},"labelerDid":{"type":"string","format":"did","description":"Which labeler does this preference apply to? If undefined, applies globally."},"visibility":{"type":"string","knownValues":["ignore","show","warn","hide"]}}},"profileViewBasic":{"type":"object","required":["did","handle"],"properties":{"did":{"type":"string","format":"did"},"debug":{"type":"unknown","description":"Debug information for internal development"},"avatar":{"type":"string","format":"uri"},"handle":{"type":"string","format":"handle"},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"status":{"ref":"#statusView","type":"ref"},"viewer":{"ref":"#viewerState","type":"ref"},"pronouns":{"type":"string"},"createdAt":{"type":"string","format":"datetime"},"associated":{"ref":"#profileAssociated","type":"ref"},"displayName":{"type":"string","maxLength":640,"maxGraphemes":64},"verification":{"ref":"#verificationState","type":"ref"}}},"savedFeedsPrefV2":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"ref":"app.bsky.actor.defs#savedFeed","type":"ref"}}}},"verificationView":{"type":"object","required":["issuer","uri","isValid","createdAt"],"properties":{"uri":{"type":"string","format":"at-uri","description":"The AT-URI of the verification record."},"issuer":{"type":"string","format":"did","description":"The user who issued this verification."},"isValid":{"type":"boolean","description":"True if the verification passes validation, otherwise false."},"createdAt":{"type":"string","format":"datetime","description":"Timestamp when the verification was created."}},"description":"An individual verification for an associated subject."},"profileAssociated":{"type":"object","properties":{"chat":{"ref":"#profileAssociatedChat","type":"ref"},"germ":{"ref":"#profileAssociatedGerm","type":"ref"},"lists":{"type":"integer"},"labeler":{"type":"boolean"},"feedgens":{"type":"integer"},"starterPacks":{"type":"integer"},"activitySubscription":{"ref":"#profileAssociatedActivitySubscription","type":"ref"}}},"verificationPrefs":{"type":"object","required":[],"properties":{"hideBadges":{"type":"boolean","default":false,"description":"Hide the blue check badges for verified accounts and trusted verifiers."}},"description":"Preferences for how verified accounts appear in the app."},"verificationState":{"type":"object","required":["verifications","verifiedStatus","trustedVerifierStatus"],"properties":{"verifications":{"type":"array","items":{"ref":"#verificationView","type":"ref"},"description":"All verifications issued by trusted verifiers on behalf of this user. Verifications by untrusted verifiers are not included."},"verifiedStatus":{"type":"string","description":"The user's status as a verified account.","knownValues":["valid","invalid","none"]},"trustedVerifierStatus":{"type":"string","description":"The user's status as a trusted verifier.","knownValues":["valid","invalid","none"]}},"description":"Represents the verification information about the user this object is attached to."},"personalDetailsPref":{"type":"object","properties":{"birthDate":{"type":"string","format":"datetime","description":"The birth date of account owner."}}},"profileViewDetailed":{"type":"object","required":["did","handle"],"properties":{"did":{"type":"string","format":"did"},"debug":{"type":"unknown","description":"Debug information for internal development"},"avatar":{"type":"string","format":"uri"},"banner":{"type":"string","format":"uri"},"handle":{"type":"string","format":"handle"},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"status":{"ref":"#statusView","type":"ref"},"viewer":{"ref":"#viewerState","type":"ref"},"website":{"type":"string","format":"uri"},"pronouns":{"type":"string"},"createdAt":{"type":"string","format":"datetime"},"indexedAt":{"type":"string","format":"datetime"},"associated":{"ref":"#profileAssociated","type":"ref"},"pinnedPost":{"ref":"com.atproto.repo.strongRef","type":"ref"},"postsCount":{"type":"integer"},"description":{"type":"string","maxLength":2560,"maxGraphemes":256},"displayName":{"type":"string","maxLength":640,"maxGraphemes":64},"followsCount":{"type":"integer"},"verification":{"ref":"#verificationState","type":"ref"},"followersCount":{"type":"integer"},"joinedViaStarterPack":{"ref":"app.bsky.graph.defs#starterPackViewBasic","type":"ref"}}},"bskyAppProgressGuide":{"type":"object","required":["guide"],"properties":{"guide":{"type":"string","maxLength":100}},"description":"If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress."},"liveEventPreferences":{"type":"object","properties":{"hideAllFeeds":{"type":"boolean","default":false,"description":"Whether to hide all feeds from live events."},"hiddenFeedIds":{"type":"array","items":{"type":"string"},"description":"A list of feed IDs that the user has hidden from live events."}},"description":"Preferences for live events."},"profileAssociatedChat":{"type":"object","required":["allowIncoming"],"properties":{"allowIncoming":{"type":"string","knownValues":["all","none","following"]}}},"profileAssociatedGerm":{"type":"object","required":["showButtonTo","messageMeUrl"],"properties":{"messageMeUrl":{"type":"string","format":"uri"},"showButtonTo":{"type":"string","knownValues":["usersIFollow","everyone"]}}},"postInteractionSettingsPref":{"type":"object","required":[],"properties":{"threadgateAllowRules":{"type":"array","items":{"refs":["app.bsky.feed.threadgate#mentionRule","app.bsky.feed.threadgate#followerRule","app.bsky.feed.threadgate#followingRule","app.bsky.feed.threadgate#listRule"],"type":"union"},"maxLength":5,"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."},"postgateEmbeddingRules":{"type":"array","items":{"refs":["app.bsky.feed.postgate#disableRule"],"type":"union"},"maxLength":5,"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."}},"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."},"profileAssociatedActivitySubscription":{"type":"object","required":["allowSubscriptions"],"properties":{"allowSubscriptions":{"type":"string","knownValues":["followers","mutuals","none"]}}}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 11 11 const bskyActorProfileLex = {"lexicon":1,"id":"app.bsky.actor.profile","defs":{"main":{"type":"record","key":"literal:self","description":"A user profile record","record":{"type":"object","properties":{"displayName":{"type":"string","maxGraphemes":64,"maxLength":640},"description":{"type":"string","maxGraphemes":256,"maxLength":2560},"avatar":{"type":"blob","accept":["image/png","image/jpeg"],"maxSize":1000000},"banner":{"type":"blob","accept":["image/png","image/jpeg"],"maxSize":1000000},"createdAt":{"type":"string","format":"datetime"}}}}}} as const 12 - const bskyFeedDefsLex = {"$type":"com.atproto.lexicon.schema","defs":{"blockedAuthor":{"properties":{"did":{"format":"did","type":"string"},"viewer":{"ref":"app.bsky.actor.defs#viewerState","type":"ref"}},"required":["did"],"type":"object"},"blockedPost":{"properties":{"author":{"ref":"#blockedAuthor","type":"ref"},"blocked":{"const":true,"type":"boolean"},"uri":{"format":"at-uri","type":"string"}},"required":["uri","blocked","author"],"type":"object"},"clickthroughAuthor":{"description":"User clicked through to the author of the feed item","type":"token"},"clickthroughEmbed":{"description":"User clicked through to the embedded content of the feed item","type":"token"},"clickthroughItem":{"description":"User clicked through to the feed item","type":"token"},"clickthroughReposter":{"description":"User clicked through to the reposter of the feed item","type":"token"},"contentModeUnspecified":{"description":"Declares the feed generator returns any types of posts.","type":"token"},"contentModeVideo":{"description":"Declares the feed generator returns posts containing app.bsky.embed.video embeds.","type":"token"},"feedViewPost":{"properties":{"feedContext":{"description":"Context provided by feed generator that may be passed back alongside interactions.","maxLength":2000,"type":"string"},"post":{"ref":"#postView","type":"ref"},"reason":{"refs":["#reasonRepost","#reasonPin"],"type":"union"},"reply":{"ref":"#replyRef","type":"ref"},"reqId":{"description":"Unique identifier per request that may be passed back alongside interactions.","maxLength":100,"type":"string"}},"required":["post"],"type":"object"},"generatorView":{"properties":{"acceptsInteractions":{"type":"boolean"},"avatar":{"format":"uri","type":"string"},"cid":{"format":"cid","type":"string"},"contentMode":{"knownValues":["app.bsky.feed.defs#contentModeUnspecified","app.bsky.feed.defs#contentModeVideo"],"type":"string"},"creator":{"ref":"app.bsky.actor.defs#profileView","type":"ref"},"description":{"maxGraphemes":300,"maxLength":3000,"type":"string"},"descriptionFacets":{"items":{"ref":"app.bsky.richtext.facet","type":"ref"},"type":"array"},"did":{"format":"did","type":"string"},"displayName":{"type":"string"},"indexedAt":{"format":"datetime","type":"string"},"labels":{"items":{"ref":"com.atproto.label.defs#label","type":"ref"},"type":"array"},"likeCount":{"minimum":0,"type":"integer"},"uri":{"format":"at-uri","type":"string"},"viewer":{"ref":"#generatorViewerState","type":"ref"}},"required":["uri","cid","did","creator","displayName","indexedAt"],"type":"object"},"generatorViewerState":{"properties":{"like":{"format":"at-uri","type":"string"}},"type":"object"},"interaction":{"properties":{"event":{"knownValues":["app.bsky.feed.defs#requestLess","app.bsky.feed.defs#requestMore","app.bsky.feed.defs#clickthroughItem","app.bsky.feed.defs#clickthroughAuthor","app.bsky.feed.defs#clickthroughReposter","app.bsky.feed.defs#clickthroughEmbed","app.bsky.feed.defs#interactionSeen","app.bsky.feed.defs#interactionLike","app.bsky.feed.defs#interactionRepost","app.bsky.feed.defs#interactionReply","app.bsky.feed.defs#interactionQuote","app.bsky.feed.defs#interactionShare"],"type":"string"},"feedContext":{"description":"Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton.","maxLength":2000,"type":"string"},"item":{"format":"at-uri","type":"string"},"reqId":{"description":"Unique identifier per request that may be passed back alongside interactions.","maxLength":100,"type":"string"}},"type":"object"},"interactionLike":{"description":"User liked the feed item","type":"token"},"interactionQuote":{"description":"User quoted the feed item","type":"token"},"interactionReply":{"description":"User replied to the feed item","type":"token"},"interactionRepost":{"description":"User reposted the feed item","type":"token"},"interactionSeen":{"description":"Feed item was seen by user","type":"token"},"interactionShare":{"description":"User shared the feed item","type":"token"},"notFoundPost":{"properties":{"notFound":{"const":true,"type":"boolean"},"uri":{"format":"at-uri","type":"string"}},"required":["uri","notFound"],"type":"object"},"postView":{"properties":{"author":{"ref":"app.bsky.actor.defs#profileViewBasic","type":"ref"},"bookmarkCount":{"type":"integer"},"cid":{"format":"cid","type":"string"},"debug":{"description":"Debug information for internal development","type":"unknown"},"embed":{"refs":["app.bsky.embed.images#view","app.bsky.embed.video#view","app.bsky.embed.external#view","app.bsky.embed.record#view","app.bsky.embed.recordWithMedia#view"],"type":"union"},"indexedAt":{"format":"datetime","type":"string"},"labels":{"items":{"ref":"com.atproto.label.defs#label","type":"ref"},"type":"array"},"likeCount":{"type":"integer"},"quoteCount":{"type":"integer"},"record":{"type":"unknown"},"replyCount":{"type":"integer"},"repostCount":{"type":"integer"},"threadgate":{"ref":"#threadgateView","type":"ref"},"uri":{"format":"at-uri","type":"string"},"viewer":{"ref":"#viewerState","type":"ref"}},"required":["uri","cid","author","record","indexedAt"],"type":"object"},"reasonPin":{"properties":{},"type":"object"},"reasonRepost":{"properties":{"by":{"ref":"app.bsky.actor.defs#profileViewBasic","type":"ref"},"cid":{"format":"cid","type":"string"},"indexedAt":{"format":"datetime","type":"string"},"uri":{"format":"at-uri","type":"string"}},"required":["by","indexedAt"],"type":"object"},"replyRef":{"properties":{"grandparentAuthor":{"description":"When parent is a reply to another post, this is the author of that post.","ref":"app.bsky.actor.defs#profileViewBasic","type":"ref"},"parent":{"refs":["#postView","#notFoundPost","#blockedPost"],"type":"union"},"root":{"refs":["#postView","#notFoundPost","#blockedPost"],"type":"union"}},"required":["root","parent"],"type":"object"},"requestLess":{"description":"Request that less content like the given feed item be shown in the feed","type":"token"},"requestMore":{"description":"Request that more content like the given feed item be shown in the feed","type":"token"},"skeletonFeedPost":{"properties":{"feedContext":{"description":"Context that will be passed through to client and may be passed to feed generator back alongside interactions.","maxLength":2000,"type":"string"},"post":{"format":"at-uri","type":"string"},"reason":{"refs":["#skeletonReasonRepost","#skeletonReasonPin"],"type":"union"}},"required":["post"],"type":"object"},"skeletonReasonPin":{"properties":{},"type":"object"},"skeletonReasonRepost":{"properties":{"repost":{"format":"at-uri","type":"string"}},"required":["repost"],"type":"object"},"threadContext":{"description":"Metadata about this post within the context of the thread it is in.","properties":{"rootAuthorLike":{"format":"at-uri","type":"string"}},"type":"object"},"threadViewPost":{"properties":{"parent":{"refs":["#threadViewPost","#notFoundPost","#blockedPost"],"type":"union"},"post":{"ref":"#postView","type":"ref"},"replies":{"items":{"refs":["#threadViewPost","#notFoundPost","#blockedPost"],"type":"union"},"type":"array"},"threadContext":{"ref":"#threadContext","type":"ref"}},"required":["post"],"type":"object"},"threadgateView":{"properties":{"cid":{"format":"cid","type":"string"},"lists":{"items":{"ref":"app.bsky.graph.defs#listViewBasic","type":"ref"},"type":"array"},"record":{"type":"unknown"},"uri":{"format":"at-uri","type":"string"}},"type":"object"},"viewerState":{"description":"Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.","properties":{"bookmarked":{"type":"boolean"},"embeddingDisabled":{"type":"boolean"},"like":{"format":"at-uri","type":"string"},"pinned":{"type":"boolean"},"replyDisabled":{"type":"boolean"},"repost":{"format":"at-uri","type":"string"},"threadMuted":{"type":"boolean"}},"type":"object"}},"id":"app.bsky.feed.defs","lexicon":1} as const 13 - const bskyGraphDefsLex = {"$type":"com.atproto.lexicon.schema","defs":{"curatelist":{"description":"A list of actors used for curation purposes such as list feeds or interaction gating.","type":"token"},"listItemView":{"properties":{"subject":{"ref":"app.bsky.actor.defs#profileView","type":"ref"},"uri":{"format":"at-uri","type":"string"}},"required":["uri","subject"],"type":"object"},"listPurpose":{"knownValues":["app.bsky.graph.defs#modlist","app.bsky.graph.defs#curatelist","app.bsky.graph.defs#referencelist"],"type":"string"},"listView":{"properties":{"avatar":{"format":"uri","type":"string"},"cid":{"format":"cid","type":"string"},"creator":{"ref":"app.bsky.actor.defs#profileView","type":"ref"},"description":{"maxGraphemes":300,"maxLength":3000,"type":"string"},"descriptionFacets":{"items":{"ref":"app.bsky.richtext.facet","type":"ref"},"type":"array"},"indexedAt":{"format":"datetime","type":"string"},"labels":{"items":{"ref":"com.atproto.label.defs#label","type":"ref"},"type":"array"},"listItemCount":{"minimum":0,"type":"integer"},"name":{"maxLength":64,"minLength":1,"type":"string"},"purpose":{"ref":"#listPurpose","type":"ref"},"uri":{"format":"at-uri","type":"string"},"viewer":{"ref":"#listViewerState","type":"ref"}},"required":["uri","cid","creator","name","purpose","indexedAt"],"type":"object"},"listViewBasic":{"properties":{"avatar":{"format":"uri","type":"string"},"cid":{"format":"cid","type":"string"},"indexedAt":{"format":"datetime","type":"string"},"labels":{"items":{"ref":"com.atproto.label.defs#label","type":"ref"},"type":"array"},"listItemCount":{"minimum":0,"type":"integer"},"name":{"maxLength":64,"minLength":1,"type":"string"},"purpose":{"ref":"#listPurpose","type":"ref"},"uri":{"format":"at-uri","type":"string"},"viewer":{"ref":"#listViewerState","type":"ref"}},"required":["uri","cid","name","purpose"],"type":"object"},"listViewerState":{"properties":{"blocked":{"format":"at-uri","type":"string"},"muted":{"type":"boolean"}},"type":"object"},"modlist":{"description":"A list of actors to apply an aggregate moderation action (mute/block) on.","type":"token"},"notFoundActor":{"description":"indicates that a handle or DID could not be resolved","properties":{"actor":{"format":"at-identifier","type":"string"},"notFound":{"const":true,"type":"boolean"}},"required":["actor","notFound"],"type":"object"},"referencelist":{"description":"A list of actors used for only for reference purposes such as within a starter pack.","type":"token"},"relationship":{"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)","properties":{"blockedBy":{"description":"if the actor is blocked by this DID, contains the AT-URI of the block record","format":"at-uri","type":"string"},"blockedByList":{"description":"if the actor is blocked by this DID via a block list, contains the AT-URI of the listblock record","format":"at-uri","type":"string"},"blocking":{"description":"if the actor blocks this DID, this is the AT-URI of the block record","format":"at-uri","type":"string"},"blockingByList":{"description":"if the actor blocks this DID via a block list, this is the AT-URI of the listblock record","format":"at-uri","type":"string"},"did":{"format":"did","type":"string"},"followedBy":{"description":"if the actor is followed by this DID, contains the AT-URI of the follow record","format":"at-uri","type":"string"},"following":{"description":"if the actor follows this DID, this is the AT-URI of the follow record","format":"at-uri","type":"string"}},"required":["did"],"type":"object"},"starterPackView":{"properties":{"cid":{"format":"cid","type":"string"},"creator":{"ref":"app.bsky.actor.defs#profileViewBasic","type":"ref"},"feeds":{"items":{"ref":"app.bsky.feed.defs#generatorView","type":"ref"},"maxLength":3,"type":"array"},"indexedAt":{"format":"datetime","type":"string"},"joinedAllTimeCount":{"minimum":0,"type":"integer"},"joinedWeekCount":{"minimum":0,"type":"integer"},"labels":{"items":{"ref":"com.atproto.label.defs#label","type":"ref"},"type":"array"},"list":{"ref":"#listViewBasic","type":"ref"},"listItemsSample":{"items":{"ref":"#listItemView","type":"ref"},"maxLength":12,"type":"array"},"record":{"type":"unknown"},"uri":{"format":"at-uri","type":"string"}},"required":["uri","cid","record","creator","indexedAt"],"type":"object"},"starterPackViewBasic":{"properties":{"cid":{"format":"cid","type":"string"},"creator":{"ref":"app.bsky.actor.defs#profileViewBasic","type":"ref"},"indexedAt":{"format":"datetime","type":"string"},"joinedAllTimeCount":{"minimum":0,"type":"integer"},"joinedWeekCount":{"minimum":0,"type":"integer"},"labels":{"items":{"ref":"com.atproto.label.defs#label","type":"ref"},"type":"array"},"listItemCount":{"minimum":0,"type":"integer"},"record":{"type":"unknown"},"uri":{"format":"at-uri","type":"string"}},"required":["uri","cid","record","creator","indexedAt"],"type":"object"}},"id":"app.bsky.graph.defs","lexicon":1} as const 12 + const bskyEmbedDefsLex = {"id":"app.bsky.embed.defs","defs":{"aspectRatio":{"type":"object","required":["width","height"],"properties":{"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}},"description":"width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit."}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 13 + const externalLex = {"id":"app.bsky.embed.external","defs":{"main":{"type":"object","required":["external"],"properties":{"external":{"ref":"#external","type":"ref"}},"description":"A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post)."},"view":{"type":"object","required":["external"],"properties":{"external":{"ref":"#viewExternal","type":"ref"}}},"external":{"type":"object","required":["uri","title","description"],"properties":{"uri":{"type":"string","format":"uri"},"thumb":{"type":"blob","accept":["image/*"],"maxSize":1000000},"title":{"type":"string"},"description":{"type":"string"}}},"viewExternal":{"type":"object","required":["uri","title","description"],"properties":{"uri":{"type":"string","format":"uri"},"thumb":{"type":"string","format":"uri"},"title":{"type":"string"},"description":{"type":"string"}}}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 14 + const imagesLex = {"id":"app.bsky.embed.images","defs":{"main":{"type":"object","required":["images"],"properties":{"images":{"type":"array","items":{"ref":"#image","type":"ref"},"maxLength":4}}},"view":{"type":"object","required":["images"],"properties":{"images":{"type":"array","items":{"ref":"#viewImage","type":"ref"},"maxLength":4}}},"image":{"type":"object","required":["image","alt"],"properties":{"alt":{"type":"string","description":"Alt text description of the image, for accessibility."},"image":{"type":"blob","accept":["image/*"],"maxSize":1000000},"aspectRatio":{"ref":"app.bsky.embed.defs#aspectRatio","type":"ref"}}},"viewImage":{"type":"object","required":["thumb","fullsize","alt"],"properties":{"alt":{"type":"string","description":"Alt text description of the image, for accessibility."},"thumb":{"type":"string","format":"uri","description":"Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View."},"fullsize":{"type":"string","format":"uri","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."},"aspectRatio":{"ref":"app.bsky.embed.defs#aspectRatio","type":"ref"}}}},"$type":"com.atproto.lexicon.schema","lexicon":1,"description":"A set of images embedded in a Bluesky record (eg, a post)."} as const 15 + const recordLex = {"id":"app.bsky.embed.record","defs":{"main":{"type":"object","required":["record"],"properties":{"record":{"ref":"com.atproto.repo.strongRef","type":"ref"}}},"view":{"type":"object","required":["record"],"properties":{"record":{"refs":["#viewRecord","#viewNotFound","#viewBlocked","#viewDetached","app.bsky.feed.defs#generatorView","app.bsky.graph.defs#listView","app.bsky.labeler.defs#labelerView","app.bsky.graph.defs#starterPackViewBasic"],"type":"union"}}},"viewRecord":{"type":"object","required":["uri","cid","author","value","indexedAt"],"properties":{"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"value":{"type":"unknown","description":"The record data itself."},"author":{"ref":"app.bsky.actor.defs#profileViewBasic","type":"ref"},"embeds":{"type":"array","items":{"refs":["app.bsky.embed.images#view","app.bsky.embed.video#view","app.bsky.embed.external#view","app.bsky.embed.record#view","app.bsky.embed.recordWithMedia#view"],"type":"union"}},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"indexedAt":{"type":"string","format":"datetime"},"likeCount":{"type":"integer"},"quoteCount":{"type":"integer"},"replyCount":{"type":"integer"},"repostCount":{"type":"integer"}}},"viewBlocked":{"type":"object","required":["uri","blocked","author"],"properties":{"uri":{"type":"string","format":"at-uri"},"author":{"ref":"app.bsky.feed.defs#blockedAuthor","type":"ref"},"blocked":{"type":"boolean","const":true}}},"viewDetached":{"type":"object","required":["uri","detached"],"properties":{"uri":{"type":"string","format":"at-uri"},"detached":{"type":"boolean","const":true}}},"viewNotFound":{"type":"object","required":["uri","notFound"],"properties":{"uri":{"type":"string","format":"at-uri"},"notFound":{"type":"boolean","const":true}}}},"$type":"com.atproto.lexicon.schema","lexicon":1,"description":"A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record."} as const 16 + const recordWithMediaLex = {"id":"app.bsky.embed.recordWithMedia","defs":{"main":{"type":"object","required":["record","media"],"properties":{"media":{"refs":["app.bsky.embed.images","app.bsky.embed.video","app.bsky.embed.external"],"type":"union"},"record":{"ref":"app.bsky.embed.record","type":"ref"}}},"view":{"type":"object","required":["record","media"],"properties":{"media":{"refs":["app.bsky.embed.images#view","app.bsky.embed.video#view","app.bsky.embed.external#view"],"type":"union"},"record":{"ref":"app.bsky.embed.record#view","type":"ref"}}}},"$type":"com.atproto.lexicon.schema","lexicon":1,"description":"A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card."} as const 17 + const videoLex = {"id":"app.bsky.embed.video","defs":{"main":{"type":"object","required":["video"],"properties":{"alt":{"type":"string","maxLength":10000,"description":"Alt text description of the video, for accessibility.","maxGraphemes":1000},"video":{"type":"blob","accept":["video/mp4"],"maxSize":100000000,"description":"The mp4 video file. May be up to 100mb, formerly limited to 50mb."},"captions":{"type":"array","items":{"ref":"#caption","type":"ref"},"maxLength":20},"aspectRatio":{"ref":"app.bsky.embed.defs#aspectRatio","type":"ref"},"presentation":{"type":"string","description":"A hint to the client about how to present the video.","knownValues":["default","gif"]}}},"view":{"type":"object","required":["cid","playlist"],"properties":{"alt":{"type":"string","maxLength":10000,"maxGraphemes":1000},"cid":{"type":"string","format":"cid"},"playlist":{"type":"string","format":"uri"},"thumbnail":{"type":"string","format":"uri"},"aspectRatio":{"ref":"app.bsky.embed.defs#aspectRatio","type":"ref"},"presentation":{"type":"string","description":"A hint to the client about how to present the video.","knownValues":["default","gif"]}}},"caption":{"type":"object","required":["lang","file"],"properties":{"file":{"type":"blob","accept":["text/vtt"],"maxSize":20000},"lang":{"type":"string","format":"language"}}}},"$type":"com.atproto.lexicon.schema","lexicon":1,"description":"A video embedded in a Bluesky record (eg, a post)."} as const 18 + const bskyFeedDefsLex = {"id":"app.bsky.feed.defs","defs":{"postView":{"type":"object","required":["uri","cid","author","record","indexedAt"],"properties":{"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"debug":{"type":"unknown","description":"Debug information for internal development"},"embed":{"refs":["app.bsky.embed.images#view","app.bsky.embed.video#view","app.bsky.embed.external#view","app.bsky.embed.record#view","app.bsky.embed.recordWithMedia#view"],"type":"union"},"author":{"ref":"app.bsky.actor.defs#profileViewBasic","type":"ref"},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"record":{"type":"unknown"},"viewer":{"ref":"#viewerState","type":"ref"},"indexedAt":{"type":"string","format":"datetime"},"likeCount":{"type":"integer"},"quoteCount":{"type":"integer"},"replyCount":{"type":"integer"},"threadgate":{"ref":"#threadgateView","type":"ref"},"repostCount":{"type":"integer"},"bookmarkCount":{"type":"integer"}}},"replyRef":{"type":"object","required":["root","parent"],"properties":{"root":{"refs":["#postView","#notFoundPost","#blockedPost"],"type":"union"},"parent":{"refs":["#postView","#notFoundPost","#blockedPost"],"type":"union"},"grandparentAuthor":{"ref":"app.bsky.actor.defs#profileViewBasic","type":"ref","description":"When parent is a reply to another post, this is the author of that post."}}},"reasonPin":{"type":"object","properties":{}},"blockedPost":{"type":"object","required":["uri","blocked","author"],"properties":{"uri":{"type":"string","format":"at-uri"},"author":{"ref":"#blockedAuthor","type":"ref"},"blocked":{"type":"boolean","const":true}}},"interaction":{"type":"object","properties":{"item":{"type":"string","format":"at-uri"},"event":{"type":"string","knownValues":["app.bsky.feed.defs#requestLess","app.bsky.feed.defs#requestMore","app.bsky.feed.defs#clickthroughItem","app.bsky.feed.defs#clickthroughAuthor","app.bsky.feed.defs#clickthroughReposter","app.bsky.feed.defs#clickthroughEmbed","app.bsky.feed.defs#interactionSeen","app.bsky.feed.defs#interactionLike","app.bsky.feed.defs#interactionRepost","app.bsky.feed.defs#interactionReply","app.bsky.feed.defs#interactionQuote","app.bsky.feed.defs#interactionShare"]},"reqId":{"type":"string","maxLength":100,"description":"Unique identifier per request that may be passed back alongside interactions."},"feedContext":{"type":"string","maxLength":2000,"description":"Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton."}}},"requestLess":{"type":"token","description":"Request that less content like the given feed item be shown in the feed"},"requestMore":{"type":"token","description":"Request that more content like the given feed item be shown in the feed"},"viewerState":{"type":"object","properties":{"like":{"type":"string","format":"at-uri"},"pinned":{"type":"boolean"},"repost":{"type":"string","format":"at-uri"},"bookmarked":{"type":"boolean"},"threadMuted":{"type":"boolean"},"replyDisabled":{"type":"boolean"},"embeddingDisabled":{"type":"boolean"}},"description":"Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests."},"feedViewPost":{"type":"object","required":["post"],"properties":{"post":{"ref":"#postView","type":"ref"},"reply":{"ref":"#replyRef","type":"ref"},"reqId":{"type":"string","maxLength":100,"description":"Unique identifier per request that may be passed back alongside interactions."},"reason":{"refs":["#reasonRepost","#reasonPin"],"type":"union"},"feedContext":{"type":"string","maxLength":2000,"description":"Context provided by feed generator that may be passed back alongside interactions."}}},"notFoundPost":{"type":"object","required":["uri","notFound"],"properties":{"uri":{"type":"string","format":"at-uri"},"notFound":{"type":"boolean","const":true}}},"reasonRepost":{"type":"object","required":["by","indexedAt"],"properties":{"by":{"ref":"app.bsky.actor.defs#profileViewBasic","type":"ref"},"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"indexedAt":{"type":"string","format":"datetime"}}},"blockedAuthor":{"type":"object","required":["did"],"properties":{"did":{"type":"string","format":"did"},"viewer":{"ref":"app.bsky.actor.defs#viewerState","type":"ref"}}},"generatorView":{"type":"object","required":["uri","cid","did","creator","displayName","indexedAt"],"properties":{"cid":{"type":"string","format":"cid"},"did":{"type":"string","format":"did"},"uri":{"type":"string","format":"at-uri"},"avatar":{"type":"string","format":"uri"},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"viewer":{"ref":"#generatorViewerState","type":"ref"},"creator":{"ref":"app.bsky.actor.defs#profileView","type":"ref"},"indexedAt":{"type":"string","format":"datetime"},"likeCount":{"type":"integer","minimum":0},"contentMode":{"type":"string","knownValues":["app.bsky.feed.defs#contentModeUnspecified","app.bsky.feed.defs#contentModeVideo"]},"description":{"type":"string","maxLength":3000,"maxGraphemes":300},"displayName":{"type":"string"},"descriptionFacets":{"type":"array","items":{"ref":"app.bsky.richtext.facet","type":"ref"}},"acceptsInteractions":{"type":"boolean"}}},"threadContext":{"type":"object","properties":{"rootAuthorLike":{"type":"string","format":"at-uri"}},"description":"Metadata about this post within the context of the thread it is in."},"threadViewPost":{"type":"object","required":["post"],"properties":{"post":{"ref":"#postView","type":"ref"},"parent":{"refs":["#threadViewPost","#notFoundPost","#blockedPost"],"type":"union"},"replies":{"type":"array","items":{"refs":["#threadViewPost","#notFoundPost","#blockedPost"],"type":"union"}},"threadContext":{"ref":"#threadContext","type":"ref"}}},"threadgateView":{"type":"object","properties":{"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"lists":{"type":"array","items":{"ref":"app.bsky.graph.defs#listViewBasic","type":"ref"}},"record":{"type":"unknown"}}},"interactionLike":{"type":"token","description":"User liked the feed item"},"interactionSeen":{"type":"token","description":"Feed item was seen by user"},"clickthroughItem":{"type":"token","description":"User clicked through to the feed item"},"contentModeVideo":{"type":"token","description":"Declares the feed generator returns posts containing app.bsky.embed.video embeds."},"interactionQuote":{"type":"token","description":"User quoted the feed item"},"interactionReply":{"type":"token","description":"User replied to the feed item"},"interactionShare":{"type":"token","description":"User shared the feed item"},"skeletonFeedPost":{"type":"object","required":["post"],"properties":{"post":{"type":"string","format":"at-uri"},"reason":{"refs":["#skeletonReasonRepost","#skeletonReasonPin"],"type":"union"},"feedContext":{"type":"string","maxLength":2000,"description":"Context that will be passed through to client and may be passed to feed generator back alongside interactions."}}},"clickthroughEmbed":{"type":"token","description":"User clicked through to the embedded content of the feed item"},"interactionRepost":{"type":"token","description":"User reposted the feed item"},"skeletonReasonPin":{"type":"object","properties":{}},"clickthroughAuthor":{"type":"token","description":"User clicked through to the author of the feed item"},"clickthroughReposter":{"type":"token","description":"User clicked through to the reposter of the feed item"},"generatorViewerState":{"type":"object","properties":{"like":{"type":"string","format":"at-uri"}}},"skeletonReasonRepost":{"type":"object","required":["repost"],"properties":{"repost":{"type":"string","format":"at-uri"}}},"contentModeUnspecified":{"type":"token","description":"Declares the feed generator returns any types of posts."}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 19 + const postLex = {"id":"app.bsky.feed.post","defs":{"main":{"key":"tid","type":"record","record":{"type":"object","required":["text","createdAt"],"properties":{"tags":{"type":"array","items":{"type":"string","maxLength":640,"maxGraphemes":64},"maxLength":8,"description":"Additional hashtags, in addition to any included in post text and facets."},"text":{"type":"string","maxLength":3000,"description":"The primary post content. May be an empty string, if there are embeds.","maxGraphemes":300},"embed":{"refs":["app.bsky.embed.images","app.bsky.embed.video","app.bsky.embed.external","app.bsky.embed.record","app.bsky.embed.recordWithMedia"],"type":"union"},"langs":{"type":"array","items":{"type":"string","format":"language"},"maxLength":3,"description":"Indicates human language of post primary text content."},"reply":{"ref":"#replyRef","type":"ref"},"facets":{"type":"array","items":{"ref":"app.bsky.richtext.facet","type":"ref"},"description":"Annotations of text (mentions, URLs, hashtags, etc)"},"labels":{"refs":["com.atproto.label.defs#selfLabels"],"type":"union","description":"Self-label values for this post. Effectively content warnings."},"entities":{"type":"array","items":{"ref":"#entity","type":"ref"},"description":"DEPRECATED: replaced by app.bsky.richtext.facet."},"createdAt":{"type":"string","format":"datetime","description":"Client-declared timestamp when this post was originally created."}}},"description":"Record containing a Bluesky post."},"entity":{"type":"object","required":["index","type","value"],"properties":{"type":{"type":"string","description":"Expected values are 'mention' and 'link'."},"index":{"ref":"#textSlice","type":"ref"},"value":{"type":"string"}},"description":"Deprecated: use facets instead."},"replyRef":{"type":"object","required":["root","parent"],"properties":{"root":{"ref":"com.atproto.repo.strongRef","type":"ref"},"parent":{"ref":"com.atproto.repo.strongRef","type":"ref"}}},"textSlice":{"type":"object","required":["start","end"],"properties":{"end":{"type":"integer","minimum":0},"start":{"type":"integer","minimum":0}},"description":"Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings."}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 20 + const postgateLex = {"id":"app.bsky.feed.postgate","defs":{"main":{"key":"tid","type":"record","record":{"type":"object","required":["post","createdAt"],"properties":{"post":{"type":"string","format":"at-uri","description":"Reference (AT-URI) to the post record."},"createdAt":{"type":"string","format":"datetime"},"embeddingRules":{"type":"array","items":{"refs":["#disableRule"],"type":"union"},"maxLength":5,"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."},"detachedEmbeddingUris":{"type":"array","items":{"type":"string","format":"at-uri"},"maxLength":50,"description":"List of AT-URIs embedding this post that the author has detached from."}}},"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."},"disableRule":{"type":"object","properties":{},"description":"Disables embedding of this post."}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 21 + const threadgateLex = {"id":"app.bsky.feed.threadgate","defs":{"main":{"key":"tid","type":"record","record":{"type":"object","required":["post","createdAt"],"properties":{"post":{"type":"string","format":"at-uri","description":"Reference (AT-URI) to the post record."},"allow":{"type":"array","items":{"refs":["#mentionRule","#followerRule","#followingRule","#listRule"],"type":"union"},"maxLength":5,"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."},"createdAt":{"type":"string","format":"datetime"},"hiddenReplies":{"type":"array","items":{"type":"string","format":"at-uri"},"maxLength":300,"description":"List of hidden reply URIs."}}},"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."},"listRule":{"type":"object","required":["list"],"properties":{"list":{"type":"string","format":"at-uri"}},"description":"Allow replies from actors on a list."},"mentionRule":{"type":"object","properties":{},"description":"Allow replies from actors mentioned in your post."},"followerRule":{"type":"object","properties":{},"description":"Allow replies from actors who follow you."},"followingRule":{"type":"object","properties":{},"description":"Allow replies from actors you follow."}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 22 + const bskyGraphDefsLex = {"id":"app.bsky.graph.defs","defs":{"modlist":{"type":"token","description":"A list of actors to apply an aggregate moderation action (mute/block) on."},"listView":{"type":"object","required":["uri","cid","creator","name","purpose","indexedAt"],"properties":{"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"name":{"type":"string","maxLength":64,"minLength":1},"avatar":{"type":"string","format":"uri"},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"viewer":{"ref":"#listViewerState","type":"ref"},"creator":{"ref":"app.bsky.actor.defs#profileView","type":"ref"},"purpose":{"ref":"#listPurpose","type":"ref"},"indexedAt":{"type":"string","format":"datetime"},"description":{"type":"string","maxLength":3000,"maxGraphemes":300},"listItemCount":{"type":"integer","minimum":0},"descriptionFacets":{"type":"array","items":{"ref":"app.bsky.richtext.facet","type":"ref"}}}},"curatelist":{"type":"token","description":"A list of actors used for curation purposes such as list feeds or interaction gating."},"listPurpose":{"type":"string","knownValues":["app.bsky.graph.defs#modlist","app.bsky.graph.defs#curatelist","app.bsky.graph.defs#referencelist"]},"listItemView":{"type":"object","required":["uri","subject"],"properties":{"uri":{"type":"string","format":"at-uri"},"subject":{"ref":"app.bsky.actor.defs#profileView","type":"ref"}}},"relationship":{"type":"object","required":["did"],"properties":{"did":{"type":"string","format":"did"},"blocking":{"type":"string","format":"at-uri","description":"if the actor blocks this DID, this is the AT-URI of the block record"},"blockedBy":{"type":"string","format":"at-uri","description":"if the actor is blocked by this DID, contains the AT-URI of the block record"},"following":{"type":"string","format":"at-uri","description":"if the actor follows this DID, this is the AT-URI of the follow record"},"followedBy":{"type":"string","format":"at-uri","description":"if the actor is followed by this DID, contains the AT-URI of the follow record"},"blockedByList":{"type":"string","format":"at-uri","description":"if the actor is blocked by this DID via a block list, contains the AT-URI of the listblock record"},"blockingByList":{"type":"string","format":"at-uri","description":"if the actor blocks this DID via a block list, this is the AT-URI of the listblock record"}},"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)"},"listViewBasic":{"type":"object","required":["uri","cid","name","purpose"],"properties":{"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"name":{"type":"string","maxLength":64,"minLength":1},"avatar":{"type":"string","format":"uri"},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"viewer":{"ref":"#listViewerState","type":"ref"},"purpose":{"ref":"#listPurpose","type":"ref"},"indexedAt":{"type":"string","format":"datetime"},"listItemCount":{"type":"integer","minimum":0}}},"notFoundActor":{"type":"object","required":["actor","notFound"],"properties":{"actor":{"type":"string","format":"at-identifier"},"notFound":{"type":"boolean","const":true}},"description":"indicates that a handle or DID could not be resolved"},"referencelist":{"type":"token","description":"A list of actors used for only for reference purposes such as within a starter pack."},"listViewerState":{"type":"object","properties":{"muted":{"type":"boolean"},"blocked":{"type":"string","format":"at-uri"}}},"starterPackView":{"type":"object","required":["uri","cid","record","creator","indexedAt"],"properties":{"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"list":{"ref":"#listViewBasic","type":"ref"},"feeds":{"type":"array","items":{"ref":"app.bsky.feed.defs#generatorView","type":"ref"},"maxLength":3},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"record":{"type":"unknown"},"creator":{"ref":"app.bsky.actor.defs#profileViewBasic","type":"ref"},"indexedAt":{"type":"string","format":"datetime"},"joinedWeekCount":{"type":"integer","minimum":0},"listItemsSample":{"type":"array","items":{"ref":"#listItemView","type":"ref"},"maxLength":12},"joinedAllTimeCount":{"type":"integer","minimum":0}}},"starterPackViewBasic":{"type":"object","required":["uri","cid","record","creator","indexedAt"],"properties":{"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"record":{"type":"unknown"},"creator":{"ref":"app.bsky.actor.defs#profileViewBasic","type":"ref"},"indexedAt":{"type":"string","format":"datetime"},"listItemCount":{"type":"integer","minimum":0},"joinedWeekCount":{"type":"integer","minimum":0},"joinedAllTimeCount":{"type":"integer","minimum":0}}}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 14 23 const bskyGraphFollowLex = {"lexicon":1,"id":"app.bsky.graph.follow","defs":{"main":{"type":"record","key":"tid","record":{"type":"object","required":["subject","createdAt"],"properties":{"subject":{"type":"string"},"createdAt":{"type":"string","format":"datetime"}}}}}} as const 15 - const bskyNotificationDefsLex = {"$type":"com.atproto.lexicon.schema","defs":{"activitySubscription":{"properties":{"post":{"type":"boolean"},"reply":{"type":"boolean"}},"required":["post","reply"],"type":"object"},"chatPreference":{"properties":{"include":{"knownValues":["all","accepted"],"type":"string"},"push":{"type":"boolean"}},"required":["include","push"],"type":"object"},"filterablePreference":{"properties":{"include":{"knownValues":["all","follows"],"type":"string"},"list":{"type":"boolean"},"push":{"type":"boolean"}},"required":["include","list","push"],"type":"object"},"preference":{"properties":{"list":{"type":"boolean"},"push":{"type":"boolean"}},"required":["list","push"],"type":"object"},"preferences":{"properties":{"chat":{"ref":"#chatPreference","type":"ref"},"follow":{"ref":"#filterablePreference","type":"ref"},"like":{"ref":"#filterablePreference","type":"ref"},"likeViaRepost":{"ref":"#filterablePreference","type":"ref"},"mention":{"ref":"#filterablePreference","type":"ref"},"quote":{"ref":"#filterablePreference","type":"ref"},"reply":{"ref":"#filterablePreference","type":"ref"},"repost":{"ref":"#filterablePreference","type":"ref"},"repostViaRepost":{"ref":"#filterablePreference","type":"ref"},"starterpackJoined":{"ref":"#preference","type":"ref"},"subscribedPost":{"ref":"#preference","type":"ref"},"unverified":{"ref":"#preference","type":"ref"},"verified":{"ref":"#preference","type":"ref"}},"required":["chat","follow","like","likeViaRepost","mention","quote","reply","repost","repostViaRepost","starterpackJoined","subscribedPost","unverified","verified"],"type":"object"},"recordDeleted":{"properties":{},"type":"object"},"subjectActivitySubscription":{"description":"Object used to store activity subscription data in stash.","properties":{"activitySubscription":{"ref":"#activitySubscription","type":"ref"},"subject":{"format":"did","type":"string"}},"required":["subject","activitySubscription"],"type":"object"}},"id":"app.bsky.notification.defs","lexicon":1} as const 16 - const facetLex = {"$type":"com.atproto.lexicon.schema","defs":{"byteSlice":{"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.","properties":{"byteEnd":{"minimum":0,"type":"integer"},"byteStart":{"minimum":0,"type":"integer"}},"required":["byteStart","byteEnd"],"type":"object"},"link":{"description":"Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.","properties":{"uri":{"format":"uri","type":"string"}},"required":["uri"],"type":"object"},"main":{"description":"Annotation of a sub-string within rich text.","properties":{"features":{"items":{"refs":["#mention","#link","#tag"],"type":"union"},"type":"array"},"index":{"ref":"#byteSlice","type":"ref"}},"required":["index","features"],"type":"object"},"mention":{"description":"Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.","properties":{"did":{"format":"did","type":"string"}},"required":["did"],"type":"object"},"tag":{"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').","properties":{"tag":{"maxGraphemes":64,"maxLength":640,"type":"string"}},"required":["tag"],"type":"object"}},"id":"app.bsky.richtext.facet","lexicon":1} as const 17 - const atprotoLabelDefsLex = {"$type":"com.atproto.lexicon.schema","defs":{"label":{"description":"Metadata tag on an atproto resource (eg, repo or record).","properties":{"cid":{"description":"Optionally, CID specifying the specific version of 'uri' resource this label applies to.","format":"cid","type":"string"},"cts":{"description":"Timestamp when this label was created.","format":"datetime","type":"string"},"exp":{"description":"Timestamp at which this label expires (no longer applies).","format":"datetime","type":"string"},"neg":{"description":"If true, this is a negation label, overwriting a previous label.","type":"boolean"},"sig":{"description":"Signature of dag-cbor encoded label.","type":"bytes"},"src":{"description":"DID of the actor who created this label.","format":"did","type":"string"},"uri":{"description":"AT URI of the record, repository (account), or other resource that this label applies to.","format":"uri","type":"string"},"val":{"description":"The short string name of the value or type of this label.","maxLength":128,"type":"string"},"ver":{"description":"The AT Protocol version of the label object.","type":"integer"}},"required":["src","uri","val","cts"],"type":"object"},"labelValue":{"knownValues":["!hide","!no-promote","!warn","!no-unauthenticated","dmca-violation","doxxing","porn","sexual","nudity","nsfl","gore"],"type":"string"},"labelValueDefinition":{"description":"Declares a label value and its expected interpretations and behaviors.","properties":{"adultOnly":{"description":"Does the user need to have adult content enabled in order to configure this label?","type":"boolean"},"blurs":{"description":"What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.","knownValues":["content","media","none"],"type":"string"},"defaultSetting":{"default":"warn","description":"The default setting for this label.","knownValues":["ignore","warn","hide"],"type":"string"},"identifier":{"description":"The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).","maxGraphemes":100,"maxLength":100,"type":"string"},"locales":{"items":{"ref":"#labelValueDefinitionStrings","type":"ref"},"type":"array"},"severity":{"description":"How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.","knownValues":["inform","alert","none"],"type":"string"}},"required":["identifier","severity","blurs","locales"],"type":"object"},"labelValueDefinitionStrings":{"description":"Strings which describe the label in the UI, localized into a specific language.","properties":{"description":{"description":"A longer description of what the label means and why it might be applied.","maxGraphemes":10000,"maxLength":100000,"type":"string"},"lang":{"description":"The code of the language these strings are written in.","format":"language","type":"string"},"name":{"description":"A short human-readable name for the label.","maxGraphemes":64,"maxLength":640,"type":"string"}},"required":["lang","name","description"],"type":"object"},"selfLabel":{"description":"Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.","properties":{"val":{"description":"The short string name of the value or type of this label.","maxLength":128,"type":"string"}},"required":["val"],"type":"object"},"selfLabels":{"description":"Metadata tags on an atproto record, published by the author within the record.","properties":{"values":{"items":{"ref":"#selfLabel","type":"ref"},"maxLength":10,"type":"array"}},"required":["values"],"type":"object"}},"id":"com.atproto.label.defs","lexicon":1} as const 18 - const strongRefLex = {"$type":"com.atproto.lexicon.schema","defs":{"main":{"properties":{"cid":{"format":"cid","type":"string"},"uri":{"format":"at-uri","type":"string"}},"required":["uri","cid"],"type":"object"}},"description":"A URI with a content-hash fingerprint.","id":"com.atproto.repo.strongRef","lexicon":1} as const 24 + const bskyLabelerDefsLex = {"id":"app.bsky.labeler.defs","defs":{"labelerView":{"type":"object","required":["uri","cid","creator","indexedAt"],"properties":{"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"viewer":{"ref":"#labelerViewerState","type":"ref"},"creator":{"ref":"app.bsky.actor.defs#profileView","type":"ref"},"indexedAt":{"type":"string","format":"datetime"},"likeCount":{"type":"integer","minimum":0}}},"labelerPolicies":{"type":"object","required":["labelValues"],"properties":{"labelValues":{"type":"array","items":{"ref":"com.atproto.label.defs#labelValue","type":"ref"},"description":"The label values which this labeler publishes. May include global or custom labels."},"labelValueDefinitions":{"type":"array","items":{"ref":"com.atproto.label.defs#labelValueDefinition","type":"ref"},"description":"Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler."}}},"labelerViewerState":{"type":"object","properties":{"like":{"type":"string","format":"at-uri"}}},"labelerViewDetailed":{"type":"object","required":["uri","cid","creator","policies","indexedAt"],"properties":{"cid":{"type":"string","format":"cid"},"uri":{"type":"string","format":"at-uri"},"labels":{"type":"array","items":{"ref":"com.atproto.label.defs#label","type":"ref"}},"viewer":{"ref":"#labelerViewerState","type":"ref"},"creator":{"ref":"app.bsky.actor.defs#profileView","type":"ref"},"policies":{"ref":"app.bsky.labeler.defs#labelerPolicies","type":"ref"},"indexedAt":{"type":"string","format":"datetime"},"likeCount":{"type":"integer","minimum":0},"reasonTypes":{"type":"array","items":{"ref":"com.atproto.moderation.defs#reasonType","type":"ref"},"description":"The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed."},"subjectTypes":{"type":"array","items":{"ref":"com.atproto.moderation.defs#subjectType","type":"ref"},"description":"The set of subject types (account, record, etc) this service accepts reports on."},"subjectCollections":{"type":"array","items":{"type":"string","format":"nsid"},"description":"Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type."}}}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 25 + const bskyNotificationDefsLex = {"id":"app.bsky.notification.defs","defs":{"preference":{"type":"object","required":["list","push"],"properties":{"list":{"type":"boolean"},"push":{"type":"boolean"}}},"preferences":{"type":"object","required":["chat","follow","like","likeViaRepost","mention","quote","reply","repost","repostViaRepost","starterpackJoined","subscribedPost","unverified","verified"],"properties":{"chat":{"ref":"#chatPreference","type":"ref"},"like":{"ref":"#filterablePreference","type":"ref"},"quote":{"ref":"#filterablePreference","type":"ref"},"reply":{"ref":"#filterablePreference","type":"ref"},"follow":{"ref":"#filterablePreference","type":"ref"},"repost":{"ref":"#filterablePreference","type":"ref"},"mention":{"ref":"#filterablePreference","type":"ref"},"verified":{"ref":"#preference","type":"ref"},"unverified":{"ref":"#preference","type":"ref"},"likeViaRepost":{"ref":"#filterablePreference","type":"ref"},"subscribedPost":{"ref":"#preference","type":"ref"},"repostViaRepost":{"ref":"#filterablePreference","type":"ref"},"starterpackJoined":{"ref":"#preference","type":"ref"}}},"recordDeleted":{"type":"object","properties":{}},"chatPreference":{"type":"object","required":["include","push"],"properties":{"push":{"type":"boolean"},"include":{"type":"string","knownValues":["all","accepted"]}}},"activitySubscription":{"type":"object","required":["post","reply"],"properties":{"post":{"type":"boolean"},"reply":{"type":"boolean"}}},"filterablePreference":{"type":"object","required":["include","list","push"],"properties":{"list":{"type":"boolean"},"push":{"type":"boolean"},"include":{"type":"string","knownValues":["all","follows"]}}},"subjectActivitySubscription":{"type":"object","required":["subject","activitySubscription"],"properties":{"subject":{"type":"string","format":"did"},"activitySubscription":{"ref":"#activitySubscription","type":"ref"}},"description":"Object used to store activity subscription data in stash."}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 26 + const facetLex = {"id":"app.bsky.richtext.facet","defs":{"tag":{"type":"object","required":["tag"],"properties":{"tag":{"type":"string","maxLength":640,"maxGraphemes":64}},"description":"Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags')."},"link":{"type":"object","required":["uri"],"properties":{"uri":{"type":"string","format":"uri"}},"description":"Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL."},"main":{"type":"object","required":["index","features"],"properties":{"index":{"ref":"#byteSlice","type":"ref"},"features":{"type":"array","items":{"refs":["#mention","#link","#tag"],"type":"union"}}},"description":"Annotation of a sub-string within rich text."},"mention":{"type":"object","required":["did"],"properties":{"did":{"type":"string","format":"did"}},"description":"Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID."},"byteSlice":{"type":"object","required":["byteStart","byteEnd"],"properties":{"byteEnd":{"type":"integer","minimum":0},"byteStart":{"type":"integer","minimum":0}},"description":"Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets."}},"$type":"com.atproto.lexicon.schema","lexicon":1} as const 27 + const atprotoLabelDefsLex = {"lexicon":1,"id":"com.atproto.label.defs","defs":{"label":{"type":"object","description":"Metadata tag on an atproto resource (eg, repo or record).","required":["src","uri","val","cts"],"properties":{"ver":{"type":"integer"},"src":{"type":"string","format":"did"},"uri":{"type":"string","format":"uri"},"cid":{"type":"string","format":"cid"},"val":{"type":"string","maxLength":128},"neg":{"type":"boolean"},"cts":{"type":"string","format":"datetime"},"exp":{"type":"string","format":"datetime"},"sig":{"type":"bytes"}}},"selfLabels":{"type":"object","description":"Metadata tags on an atproto record, published by the author within the record.","required":["values"],"properties":{"values":{"type":"array","items":{"type":"ref","ref":"#selfLabel"},"maxLength":10}}},"selfLabel":{"type":"object","required":["val"],"properties":{"val":{"type":"string","maxLength":128}}},"labelValueDefinition":{"type":"object","description":"Declares a label value and its expected interpretations and behaviors.","required":["identifier","severity","blurs","locales"],"properties":{"identifier":{"type":"string","maxLength":100},"severity":{"type":"string","knownValues":["inform","alert","none"]},"blurs":{"type":"string","knownValues":["content","media","none"]},"defaultSetting":{"type":"string","knownValues":["ignore","warn","hide"]},"adultOnly":{"type":"boolean"},"locales":{"type":"array","items":{"type":"ref","ref":"#labelValueDefinitionStrings"}}}},"labelValueDefinitionStrings":{"type":"object","required":["lang","name","description"],"properties":{"lang":{"type":"string","format":"language"},"name":{"type":"string","maxLength":640},"description":{"type":"string","maxLength":100000}}},"labelValue":{"type":"string","knownValues":["!hide","!no-promote","!warn","!no-unauthenticated","dmca-violation","doxxing","porn","sexual","nudity","nsfl","gore"]}}} as const 28 + const atprotoModerationDefsLex = {"lexicon":1,"id":"com.atproto.moderation.defs","defs":{"reasonType":{"type":"string","knownValues":["com.atproto.moderation.defs#reasonSpam","com.atproto.moderation.defs#reasonViolation","com.atproto.moderation.defs#reasonMisleading","com.atproto.moderation.defs#reasonSexual","com.atproto.moderation.defs#reasonRude","com.atproto.moderation.defs#reasonOther","com.atproto.moderation.defs#reasonAppeal"]},"reasonSpam":{"type":"token","description":"Spam: frequent unwanted promotion, replies, mentions."},"reasonViolation":{"type":"token","description":"Direct violation of server rules, laws, terms of service."},"reasonMisleading":{"type":"token","description":"Misleading identity, affiliation, or content."},"reasonSexual":{"type":"token","description":"Unwanted or mislabeled sexual content."},"reasonRude":{"type":"token","description":"Rude, harassing, explicit, or otherwise unwelcoming behavior."},"reasonOther":{"type":"token","description":"Reports not falling under another report category."},"reasonAppeal":{"type":"token","description":"Appeal a previously taken moderation action."},"subjectType":{"type":"string","description":"Tag describing a type of subject that might be reported.","knownValues":["account","record","chat"]}}} as const 29 + const strongRefLex = {"lexicon":1,"id":"com.atproto.repo.strongRef","description":"A URI with a content-hash fingerprint.","defs":{"main":{"type":"object","required":["uri","cid"],"properties":{"uri":{"type":"string","format":"at-uri"},"cid":{"type":"string","format":"cid"}}}}} as const 19 30 const addressLex = {"lexicon":1,"id":"community.lexicon.location.address","defs":{"main":{"type":"object","description":"A physical location in the form of a street address.","required":["country"],"properties":{"country":{"type":"string","description":"The ISO 3166 country code. Preferably the 2-letter code.","minLength":2,"maxLength":10},"postalCode":{"type":"string","description":"The postal code of the location."},"region":{"type":"string","description":"The administrative region of the country. For example, a state in the USA."},"locality":{"type":"string","description":"The locality of the region. For example, a city in the USA."},"street":{"type":"string","description":"The street address."},"name":{"type":"string","description":"The name of the location."}}}}} as const 20 31 const geoLex = {"lexicon":1,"id":"community.lexicon.location.geo","defs":{"main":{"type":"object","description":"A physical location in the form of a WGS84 coordinate.","required":["latitude","longitude"],"properties":{"latitude":{"type":"string"},"longitude":{"type":"string"},"altitude":{"type":"string"},"name":{"type":"string","description":"The name of the location."}}}}} as const 21 32 const hthreeLex = {"lexicon":1,"id":"community.lexicon.location.hthree","defs":{"main":{"type":"object","description":"A physical location in the form of a H3 encoded location.","required":["value"],"properties":{"value":{"type":"string","description":"The h3 encoded location."},"name":{"type":"string","description":"The name of the location."}}}}} as const ··· 40 51 const grainDefsLex = {"lexicon":1,"id":"social.grain.defs","defs":{"aspectRatio":{"type":"object","description":"width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.","required":["width","height"],"properties":{"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}}}} as const 41 52 const favoriteLex = {"lexicon":1,"id":"social.grain.favorite","defs":{"main":{"type":"record","key":"tid","record":{"type":"object","required":["createdAt","subject"],"properties":{"createdAt":{"type":"string","format":"datetime"},"subject":{"type":"string","format":"at-uri"}}}}}} as const 42 53 const galleryLex = {"lexicon":1,"id":"social.grain.gallery","defs":{"main":{"type":"record","key":"tid","record":{"type":"object","required":["title","createdAt"],"properties":{"title":{"type":"string","maxLength":100},"description":{"type":"string","maxLength":1000},"facets":{"type":"array","description":"Annotations of description text (mentions, URLs, hashtags, etc)","items":{"type":"ref","ref":"app.bsky.richtext.facet"}},"labels":{"type":"union","description":"Self-label values for this post. Effectively content warnings.","refs":["com.atproto.label.defs#selfLabels"]},"location":{"type":"ref","ref":"community.lexicon.location.hthree"},"address":{"type":"ref","ref":"community.lexicon.location.address"},"updatedAt":{"type":"string","format":"datetime"},"createdAt":{"type":"string","format":"datetime"}}}}}} as const 43 - const grainGalleryDefsLex = {"lexicon":1,"id":"social.grain.gallery.defs","defs":{"galleryView":{"type":"object","required":["uri","cid","creator","record","indexedAt"],"properties":{"uri":{"type":"string","format":"at-uri"},"cid":{"type":"string","format":"cid"},"title":{"type":"string"},"description":{"type":"string"},"cameras":{"type":"array","description":"List of camera make and models used in this gallery derived from EXIF data.","items":{"type":"string"}},"location":{"type":"ref","ref":"community.lexicon.location.hthree"},"address":{"type":"ref","ref":"community.lexicon.location.address"},"facets":{"type":"array","description":"Annotations of description text (mentions, URLs, hashtags, etc)","items":{"type":"ref","ref":"app.bsky.richtext.facet"}},"creator":{"type":"ref","ref":"social.grain.actor.defs#profileView"},"record":{"type":"unknown"},"items":{"type":"array","items":{"type":"union","refs":["social.grain.photo.defs#photoView"]}},"favCount":{"type":"integer"},"commentCount":{"type":"integer"},"labels":{"type":"array","items":{"type":"ref","ref":"com.atproto.label.defs#label"}},"createdAt":{"type":"string","format":"datetime"},"indexedAt":{"type":"string","format":"datetime"},"viewer":{"type":"ref","ref":"#viewerState"}}},"viewerState":{"type":"object","description":"Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.","properties":{"fav":{"type":"string","format":"at-uri"}}}}} as const 54 + const grainGalleryDefsLex = {"lexicon":1,"id":"social.grain.gallery.defs","defs":{"galleryView":{"type":"object","required":["uri","cid","creator","record","indexedAt"],"properties":{"uri":{"type":"string","format":"at-uri"},"cid":{"type":"string","format":"cid"},"title":{"type":"string"},"description":{"type":"string"},"cameras":{"type":"array","description":"List of camera make and models used in this gallery derived from EXIF data.","items":{"type":"string"}},"location":{"type":"ref","ref":"community.lexicon.location.hthree"},"address":{"type":"ref","ref":"community.lexicon.location.address"},"facets":{"type":"array","description":"Annotations of description text (mentions, URLs, hashtags, etc)","items":{"type":"ref","ref":"app.bsky.richtext.facet"}},"creator":{"type":"ref","ref":"social.grain.actor.defs#profileView"},"record":{"type":"unknown"},"items":{"type":"array","items":{"type":"union","refs":["social.grain.photo.defs#photoView"]}},"favCount":{"type":"integer"},"commentCount":{"type":"integer"},"labels":{"type":"array","items":{"type":"ref","ref":"com.atproto.label.defs#label"}},"createdAt":{"type":"string","format":"datetime"},"indexedAt":{"type":"string","format":"datetime"},"viewer":{"type":"ref","ref":"#viewerState"},"crossPost":{"type":"ref","ref":"#crossPostInfo"}}},"crossPostInfo":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"URL to the cross-posted Bluesky post."}}},"viewerState":{"type":"object","description":"Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.","properties":{"fav":{"type":"string","format":"at-uri"}}}}} as const 44 55 const itemLex = {"lexicon":1,"id":"social.grain.gallery.item","defs":{"main":{"type":"record","key":"tid","record":{"type":"object","required":["createdAt","gallery","item"],"properties":{"createdAt":{"type":"string","format":"datetime"},"gallery":{"type":"string","format":"at-uri"},"item":{"type":"string","format":"at-uri"},"position":{"type":"integer","default":0}}}}}} as const 45 56 const grainGraphFollowLex = {"lexicon":1,"id":"social.grain.graph.follow","defs":{"main":{"key":"tid","type":"record","record":{"type":"object","required":["subject","createdAt"],"properties":{"subject":{"type":"string","format":"did"},"createdAt":{"type":"string","format":"datetime"}}}}}} as const 46 57 const photoLex = {"lexicon":1,"id":"social.grain.photo","defs":{"main":{"type":"record","key":"tid","record":{"type":"object","required":["photo","aspectRatio","createdAt"],"properties":{"photo":{"type":"blob","accept":["image/*"],"maxSize":1000000},"alt":{"type":"string","description":"Alt text description of the image, for accessibility."},"aspectRatio":{"type":"ref","ref":"social.grain.defs#aspectRatio"},"createdAt":{"type":"string","format":"datetime"}}}}}} as const 47 58 const grainPhotoDefsLex = {"lexicon":1,"id":"social.grain.photo.defs","defs":{"photoView":{"type":"object","required":["uri","cid","thumb","fullsize","aspectRatio"],"properties":{"uri":{"type":"string","format":"at-uri"},"cid":{"type":"string","format":"cid"},"thumb":{"type":"string","format":"uri","description":"Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View."},"fullsize":{"type":"string","format":"uri","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."},"alt":{"type":"string","description":"Alt text description of the image, for accessibility."},"aspectRatio":{"type":"ref","ref":"social.grain.defs#aspectRatio"},"exif":{"type":"ref","ref":"social.grain.photo.defs#exifView","description":"EXIF metadata for the photo, if available."},"gallery":{"type":"ref","ref":"#galleryState"}}},"exifView":{"type":"object","required":["uri","cid","photo","record","createdAt"],"properties":{"uri":{"type":"string","format":"at-uri"},"cid":{"type":"string","format":"cid"},"photo":{"type":"string","format":"at-uri"},"record":{"type":"unknown"},"createdAt":{"type":"string","format":"datetime"},"dateTimeOriginal":{"type":"string"},"exposureTime":{"type":"string"},"fNumber":{"type":"string"},"flash":{"type":"string"},"focalLengthIn35mmFormat":{"type":"string"},"iSO":{"type":"integer"},"lensMake":{"type":"string"},"lensModel":{"type":"string"},"make":{"type":"string"},"model":{"type":"string"}}},"galleryState":{"type":"object","required":["item","itemCreatedAt","itemPosition"],"description":"Metadata about the photo's relationship with the subject content. Only has meaningful content when photo is attached to a gallery.","properties":{"item":{"type":"string","format":"at-uri"},"itemCreatedAt":{"type":"string","format":"datetime"},"itemPosition":{"type":"integer"}}}}} as const 48 59 const exifLex = {"lexicon":1,"id":"social.grain.photo.exif","defs":{"main":{"type":"record","description":"Basic EXIF metadata for a photo. Integers are scaled by 1000000 to accommodate decimal values and potentially other tags in the future.","key":"tid","record":{"type":"object","required":["photo","createdAt"],"properties":{"photo":{"type":"string","format":"at-uri"},"createdAt":{"type":"string","format":"datetime"},"dateTimeOriginal":{"type":"string","format":"datetime"},"exposureTime":{"type":"integer"},"fNumber":{"type":"integer"},"flash":{"type":"string"},"focalLengthIn35mmFormat":{"type":"integer"},"iSO":{"type":"integer"},"lensMake":{"type":"string"},"lensModel":{"type":"string"},"make":{"type":"string"},"model":{"type":"string"}}}}}} as const 49 60 const storyLex = {"lexicon":1,"id":"social.grain.story","defs":{"main":{"type":"record","key":"tid","record":{"type":"object","required":["media","aspectRatio","createdAt"],"properties":{"media":{"type":"blob","accept":["image/*","video/*"],"maxSize":5000000},"aspectRatio":{"type":"ref","ref":"social.grain.defs#aspectRatio"},"location":{"type":"ref","ref":"community.lexicon.location.hthree"},"address":{"type":"ref","ref":"community.lexicon.location.address"},"createdAt":{"type":"string","format":"datetime"}}}}}} as const 50 - const grainStoryDefsLex = {"lexicon":1,"id":"social.grain.story.defs","defs":{"storyView":{"type":"object","required":["uri","cid","creator","thumb","fullsize","aspectRatio","createdAt"],"properties":{"uri":{"type":"string","format":"at-uri"},"cid":{"type":"string","format":"cid"},"creator":{"type":"ref","ref":"social.grain.actor.defs#profileView"},"thumb":{"type":"string","format":"uri","description":"Thumbnail URL for the story image."},"fullsize":{"type":"string","format":"uri","description":"Full-size URL for the story image."},"aspectRatio":{"type":"ref","ref":"social.grain.defs#aspectRatio"},"location":{"type":"ref","ref":"community.lexicon.location.hthree"},"address":{"type":"ref","ref":"community.lexicon.location.address"},"createdAt":{"type":"string","format":"datetime"}}}}} as const 61 + const grainStoryDefsLex = {"lexicon":1,"id":"social.grain.story.defs","defs":{"storyView":{"type":"object","required":["uri","cid","creator","thumb","fullsize","aspectRatio","createdAt"],"properties":{"uri":{"type":"string","format":"at-uri"},"cid":{"type":"string","format":"cid"},"creator":{"type":"ref","ref":"social.grain.actor.defs#profileView"},"thumb":{"type":"string","format":"uri","description":"Thumbnail URL for the story image."},"fullsize":{"type":"string","format":"uri","description":"Full-size URL for the story image."},"aspectRatio":{"type":"ref","ref":"social.grain.defs#aspectRatio"},"location":{"type":"ref","ref":"community.lexicon.location.hthree"},"address":{"type":"ref","ref":"community.lexicon.location.address"},"createdAt":{"type":"string","format":"datetime"},"crossPost":{"type":"ref","ref":"social.grain.gallery.defs#crossPostInfo"}}}}} as const 51 62 const deleteGalleryLex = {"lexicon":1,"id":"social.grain.unspecced.deleteGallery","defs":{"main":{"type":"procedure","description":"Delete a gallery and all associated records (items, photos, EXIF, favorites, comments).","input":{"encoding":"application/json","schema":{"type":"object","required":["rkey"],"properties":{"rkey":{"type":"string","description":"Record key of the gallery to delete."}}}},"output":{"encoding":"application/json","schema":{"type":"object","properties":{}}}}}} as const 52 63 const getActorProfileLex = {"lexicon":1,"id":"social.grain.unspecced.getActorProfile","defs":{"main":{"type":"query","description":"Get an actor's profile with gallery stats and follow relationships.","parameters":{"type":"params","required":["actor"],"properties":{"actor":{"type":"string","format":"did"},"viewer":{"type":"string","format":"did"}}},"output":{"encoding":"application/json","schema":{"type":"ref","ref":"social.grain.actor.defs#profileViewDetailed"}}}}} as const 53 64 const getCamerasLex = {"lexicon":1,"id":"social.grain.unspecced.getCameras","defs":{"main":{"type":"query","description":"Get top cameras by photo count.","output":{"encoding":"application/json","schema":{"type":"object","properties":{"cameras":{"type":"array","items":{"type":"ref","ref":"social.grain.unspecced.getCameras#cameraItem"}}}}}},"cameraItem":{"type":"object","required":["camera","photoCount"],"properties":{"camera":{"type":"string"},"photoCount":{"type":"integer"}}}}} as const ··· 59 70 const getLocationsLex = {"lexicon":1,"id":"social.grain.unspecced.getLocations","defs":{"main":{"type":"query","description":"Get top locations by gallery count.","output":{"encoding":"application/json","schema":{"type":"object","properties":{"locations":{"type":"array","items":{"type":"ref","ref":"social.grain.unspecced.getLocations#locationItem"}}}}}},"locationItem":{"type":"object","required":["name","h3Index","galleryCount"],"properties":{"name":{"type":"string"},"h3Index":{"type":"string"},"galleryCount":{"type":"integer"}}}}} as const 60 71 const getNotificationsLex = {"lexicon":1,"id":"social.grain.unspecced.getNotifications","defs":{"main":{"type":"query","description":"Get notifications for the authenticated user.","parameters":{"type":"params","properties":{"limit":{"type":"integer","minimum":1,"maximum":100,"default":20},"cursor":{"type":"string"},"countOnly":{"type":"boolean","description":"If true, only return unseenCount without hydrating notifications."}}},"output":{"encoding":"application/json","schema":{"type":"object","required":["notifications"],"properties":{"notifications":{"type":"array","items":{"type":"ref","ref":"#notificationItem"}},"cursor":{"type":"string"},"unseenCount":{"type":"integer"}}}}},"notificationItem":{"type":"object","required":["uri","reason","createdAt","author"],"properties":{"uri":{"type":"string","format":"at-uri"},"reason":{"type":"string","knownValues":["gallery-favorite","gallery-comment","gallery-comment-mention","gallery-mention","reply","follow"]},"createdAt":{"type":"string","format":"datetime"},"author":{"type":"ref","ref":"social.grain.actor.defs#profileView"},"galleryUri":{"type":"string","format":"at-uri"},"galleryTitle":{"type":"string"},"galleryThumb":{"type":"string"},"commentText":{"type":"string"},"replyToText":{"type":"string"}}}}} as const 61 72 const getStoriesLex = {"lexicon":1,"id":"social.grain.unspecced.getStories","defs":{"main":{"type":"query","description":"Get a user's active stories (posted within the last 24 hours).","parameters":{"type":"params","required":["actor"],"properties":{"actor":{"type":"string","format":"did"}}},"output":{"encoding":"application/json","schema":{"type":"object","required":["stories"],"properties":{"stories":{"type":"array","items":{"type":"ref","ref":"social.grain.story.defs#storyView"}}}}}}}} as const 73 + const getStoryLex = {"lexicon":1,"id":"social.grain.unspecced.getStory","defs":{"main":{"type":"query","parameters":{"type":"params","required":["story"],"properties":{"story":{"type":"string","format":"at-uri"}}},"output":{"encoding":"application/json","schema":{"type":"object","properties":{"story":{"type":"ref","ref":"social.grain.story.defs#storyView"}}}}}}} as const 62 74 const getStoryAuthorsLex = {"lexicon":1,"id":"social.grain.unspecced.getStoryAuthors","defs":{"main":{"type":"query","description":"Get authors who have active stories (posted within the last 24 hours).","output":{"encoding":"application/json","schema":{"type":"object","required":["authors"],"properties":{"authors":{"type":"array","items":{"type":"ref","ref":"social.grain.unspecced.getStoryAuthors#storyAuthor"}}}}}},"storyAuthor":{"type":"object","required":["profile","storyCount","latestAt"],"properties":{"profile":{"type":"ref","ref":"social.grain.actor.defs#profileView"},"storyCount":{"type":"integer"},"latestAt":{"type":"string","format":"datetime"}}}}} as const 63 75 const getSuggestedFollowsLex = {"lexicon":1,"id":"social.grain.unspecced.getSuggestedFollows","defs":{"main":{"type":"query","description":"Get suggested profiles to follow based on bsky follow graph.","parameters":{"type":"params","required":["actor"],"properties":{"actor":{"type":"string","format":"did"},"limit":{"type":"integer","minimum":1,"maximum":20,"default":10}}},"output":{"encoding":"application/json","schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"ref","ref":"social.grain.unspecced.getSuggestedFollows#suggestedItem"}}}}}},"suggestedItem":{"type":"object","required":["did"],"properties":{"did":{"type":"string","format":"did"},"handle":{"type":"string"},"displayName":{"type":"string"},"description":{"type":"string"},"avatar":{"type":"string"},"followersCount":{"type":"integer"}}}}} as const 64 76 const searchGalleriesLex = {"lexicon":1,"id":"social.grain.unspecced.searchGalleries","defs":{"main":{"type":"query","description":"Full-text search for galleries, returning full gallery views.","parameters":{"type":"params","required":["q"],"properties":{"q":{"type":"string","description":"Search query"},"limit":{"type":"integer","minimum":1,"maximum":100,"default":30},"cursor":{"type":"string"},"fuzzy":{"type":"boolean","default":true}}},"output":{"encoding":"application/json","schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"ref","ref":"social.grain.gallery.defs#galleryView"}},"cursor":{"type":"string"}}}}}}} as const ··· 69 81 type Registry = { 70 82 'app.bsky.actor.defs': typeof bskyActorDefsLex 71 83 'app.bsky.actor.profile': typeof bskyActorProfileLex 84 + 'app.bsky.embed.defs': typeof bskyEmbedDefsLex 85 + 'app.bsky.embed.external': typeof externalLex 86 + 'app.bsky.embed.images': typeof imagesLex 87 + 'app.bsky.embed.record': typeof recordLex 88 + 'app.bsky.embed.recordWithMedia': typeof recordWithMediaLex 89 + 'app.bsky.embed.video': typeof videoLex 72 90 'app.bsky.feed.defs': typeof bskyFeedDefsLex 91 + 'app.bsky.feed.post': typeof postLex 92 + 'app.bsky.feed.postgate': typeof postgateLex 93 + 'app.bsky.feed.threadgate': typeof threadgateLex 73 94 'app.bsky.graph.defs': typeof bskyGraphDefsLex 74 95 'app.bsky.graph.follow': typeof bskyGraphFollowLex 96 + 'app.bsky.labeler.defs': typeof bskyLabelerDefsLex 75 97 'app.bsky.notification.defs': typeof bskyNotificationDefsLex 76 98 'app.bsky.richtext.facet': typeof facetLex 77 99 'com.atproto.label.defs': typeof atprotoLabelDefsLex 100 + 'com.atproto.moderation.defs': typeof atprotoModerationDefsLex 78 101 'com.atproto.repo.strongRef': typeof strongRefLex 79 102 'community.lexicon.location.address': typeof addressLex 80 103 'community.lexicon.location.geo': typeof geoLex ··· 119 142 'social.grain.unspecced.getLocations': typeof getLocationsLex 120 143 'social.grain.unspecced.getNotifications': typeof getNotificationsLex 121 144 'social.grain.unspecced.getStories': typeof getStoriesLex 145 + 'social.grain.unspecced.getStory': typeof getStoryLex 122 146 'social.grain.unspecced.getStoryAuthors': typeof getStoryAuthorsLex 123 147 'social.grain.unspecced.getSuggestedFollows': typeof getSuggestedFollowsLex 124 148 'social.grain.unspecced.searchGalleries': typeof searchGalleriesLex ··· 128 152 // ─── Record & Method Types ────────────────────────────────────────── 129 153 130 154 export type BskyActorProfile = Prettify<LexRecord<typeof bskyActorProfileLex, Registry>> 155 + export type Post = Prettify<LexRecord<typeof postLex, Registry>> 156 + export type Postgate = Prettify<LexRecord<typeof postgateLex, Registry>> 157 + export type Threadgate = Prettify<LexRecord<typeof threadgateLex, Registry>> 131 158 export type BskyGraphFollow = Prettify<LexRecord<typeof bskyGraphFollowLex, Registry>> 132 159 export type CreateReport = Prettify<LexProcedure<typeof createReportLex, Registry>> 133 160 export type DescribeCollections = Prettify<LexQuery<typeof describeCollectionsLex, Registry>> ··· 160 187 export type GetLocations = Prettify<LexQuery<typeof getLocationsLex, Registry>> 161 188 export type GetNotifications = Prettify<LexQuery<typeof getNotificationsLex, Registry>> 162 189 export type GetStories = Prettify<LexQuery<typeof getStoriesLex, Registry>> 190 + export type GetStory = Prettify<LexQuery<typeof getStoryLex, Registry>> 163 191 export type GetStoryAuthors = Prettify<LexQuery<typeof getStoryAuthorsLex, Registry>> 164 192 export type GetSuggestedFollows = Prettify<LexQuery<typeof getSuggestedFollowsLex, Registry>> 165 193 export type SearchGalleries = Prettify<LexQuery<typeof searchGalleriesLex, Registry>> ··· 167 195 168 196 export type RecordRegistry = { 169 197 'app.bsky.actor.profile': BskyActorProfile 198 + 'app.bsky.feed.post': Post 199 + 'app.bsky.feed.postgate': Postgate 200 + 'app.bsky.feed.threadgate': Threadgate 170 201 'app.bsky.graph.follow': BskyGraphFollow 171 202 'social.grain.actor.profile': GrainActorProfile 172 203 'social.grain.comment': Comment ··· 199 230 200 231 // ─── Named Defs (Views, Objects) ──────────────────────────────────── 201 232 202 - export type AdultContentPref = Prettify<LexDef<typeof bskyActorDefsLex, 'adultContentPref', Registry>> 203 - export type BskyAppProgressGuide = Prettify<LexDef<typeof bskyActorDefsLex, 'bskyAppProgressGuide', Registry>> 204 - export type BskyAppStatePref = Prettify<LexDef<typeof bskyActorDefsLex, 'bskyAppStatePref', Registry>> 205 - export type ContentLabelPref = Prettify<LexDef<typeof bskyActorDefsLex, 'contentLabelPref', Registry>> 206 - export type DeclaredAgePref = Prettify<LexDef<typeof bskyActorDefsLex, 'declaredAgePref', Registry>> 233 + export type Nux = Prettify<LexDef<typeof bskyActorDefsLex, 'nux', Registry>> 234 + export type MutedWord = Prettify<LexDef<typeof bskyActorDefsLex, 'mutedWord', Registry>> 235 + export type SavedFeed = Prettify<LexDef<typeof bskyActorDefsLex, 'savedFeed', Registry>> 236 + export type StatusView = Prettify<LexDef<typeof bskyActorDefsLex, 'statusView', Registry>> 237 + export type BskyActorDefsProfileView = Prettify<LexDef<typeof bskyActorDefsLex, 'profileView', Registry>> 238 + export type BskyActorDefsViewerState = Prettify<LexDef<typeof bskyActorDefsLex, 'viewerState', Registry>> 207 239 export type FeedViewPref = Prettify<LexDef<typeof bskyActorDefsLex, 'feedViewPref', Registry>> 208 - export type HiddenPostsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'hiddenPostsPref', Registry>> 240 + export type LabelersPref = Prettify<LexDef<typeof bskyActorDefsLex, 'labelersPref', Registry>> 209 241 export type InterestsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'interestsPref', Registry>> 210 242 export type KnownFollowers = Prettify<LexDef<typeof bskyActorDefsLex, 'knownFollowers', Registry>> 243 + export type MutedWordsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'mutedWordsPref', Registry>> 244 + export type SavedFeedsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'savedFeedsPref', Registry>> 245 + export type ThreadViewPref = Prettify<LexDef<typeof bskyActorDefsLex, 'threadViewPref', Registry>> 246 + export type DeclaredAgePref = Prettify<LexDef<typeof bskyActorDefsLex, 'declaredAgePref', Registry>> 247 + export type HiddenPostsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'hiddenPostsPref', Registry>> 211 248 export type LabelerPrefItem = Prettify<LexDef<typeof bskyActorDefsLex, 'labelerPrefItem', Registry>> 212 - export type LabelersPref = Prettify<LexDef<typeof bskyActorDefsLex, 'labelersPref', Registry>> 213 - export type LiveEventPreferences = Prettify<LexDef<typeof bskyActorDefsLex, 'liveEventPreferences', Registry>> 214 - export type MutedWord = Prettify<LexDef<typeof bskyActorDefsLex, 'mutedWord', Registry>> 215 - export type MutedWordsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'mutedWordsPref', Registry>> 216 - export type Nux = Prettify<LexDef<typeof bskyActorDefsLex, 'nux', Registry>> 217 - export type PersonalDetailsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'personalDetailsPref', Registry>> 218 - export type PostInteractionSettingsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'postInteractionSettingsPref', Registry>> 219 - export type ProfileAssociated = Prettify<LexDef<typeof bskyActorDefsLex, 'profileAssociated', Registry>> 220 - export type ProfileAssociatedActivitySubscription = Prettify<LexDef<typeof bskyActorDefsLex, 'profileAssociatedActivitySubscription', Registry>> 221 - export type ProfileAssociatedChat = Prettify<LexDef<typeof bskyActorDefsLex, 'profileAssociatedChat', Registry>> 222 - export type ProfileAssociatedGerm = Prettify<LexDef<typeof bskyActorDefsLex, 'profileAssociatedGerm', Registry>> 223 - export type BskyActorDefsProfileView = Prettify<LexDef<typeof bskyActorDefsLex, 'profileView', Registry>> 249 + export type AdultContentPref = Prettify<LexDef<typeof bskyActorDefsLex, 'adultContentPref', Registry>> 250 + export type BskyAppStatePref = Prettify<LexDef<typeof bskyActorDefsLex, 'bskyAppStatePref', Registry>> 251 + export type ContentLabelPref = Prettify<LexDef<typeof bskyActorDefsLex, 'contentLabelPref', Registry>> 224 252 export type ProfileViewBasic = Prettify<LexDef<typeof bskyActorDefsLex, 'profileViewBasic', Registry>> 225 - export type BskyActorDefsProfileViewDetailed = Prettify<LexDef<typeof bskyActorDefsLex, 'profileViewDetailed', Registry>> 226 - export type SavedFeed = Prettify<LexDef<typeof bskyActorDefsLex, 'savedFeed', Registry>> 227 - export type SavedFeedsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'savedFeedsPref', Registry>> 228 253 export type SavedFeedsPrefV2 = Prettify<LexDef<typeof bskyActorDefsLex, 'savedFeedsPrefV2', Registry>> 229 - export type StatusView = Prettify<LexDef<typeof bskyActorDefsLex, 'statusView', Registry>> 230 - export type ThreadViewPref = Prettify<LexDef<typeof bskyActorDefsLex, 'threadViewPref', Registry>> 254 + export type VerificationView = Prettify<LexDef<typeof bskyActorDefsLex, 'verificationView', Registry>> 255 + export type ProfileAssociated = Prettify<LexDef<typeof bskyActorDefsLex, 'profileAssociated', Registry>> 231 256 export type VerificationPrefs = Prettify<LexDef<typeof bskyActorDefsLex, 'verificationPrefs', Registry>> 232 257 export type VerificationState = Prettify<LexDef<typeof bskyActorDefsLex, 'verificationState', Registry>> 233 - export type VerificationView = Prettify<LexDef<typeof bskyActorDefsLex, 'verificationView', Registry>> 234 - export type BskyActorDefsViewerState = Prettify<LexDef<typeof bskyActorDefsLex, 'viewerState', Registry>> 235 - export type BlockedAuthor = Prettify<LexDef<typeof bskyFeedDefsLex, 'blockedAuthor', Registry>> 258 + export type PersonalDetailsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'personalDetailsPref', Registry>> 259 + export type BskyActorDefsProfileViewDetailed = Prettify<LexDef<typeof bskyActorDefsLex, 'profileViewDetailed', Registry>> 260 + export type BskyAppProgressGuide = Prettify<LexDef<typeof bskyActorDefsLex, 'bskyAppProgressGuide', Registry>> 261 + export type LiveEventPreferences = Prettify<LexDef<typeof bskyActorDefsLex, 'liveEventPreferences', Registry>> 262 + export type ProfileAssociatedChat = Prettify<LexDef<typeof bskyActorDefsLex, 'profileAssociatedChat', Registry>> 263 + export type ProfileAssociatedGerm = Prettify<LexDef<typeof bskyActorDefsLex, 'profileAssociatedGerm', Registry>> 264 + export type PostInteractionSettingsPref = Prettify<LexDef<typeof bskyActorDefsLex, 'postInteractionSettingsPref', Registry>> 265 + export type ProfileAssociatedActivitySubscription = Prettify<LexDef<typeof bskyActorDefsLex, 'profileAssociatedActivitySubscription', Registry>> 266 + export type BskyEmbedDefsAspectRatio = Prettify<LexDef<typeof bskyEmbedDefsLex, 'aspectRatio', Registry>> 267 + export type ExternalView = Prettify<LexDef<typeof externalLex, 'view', Registry>> 268 + export type External = Prettify<LexDef<typeof externalLex, 'external', Registry>> 269 + export type ViewExternal = Prettify<LexDef<typeof externalLex, 'viewExternal', Registry>> 270 + export type ImagesView = Prettify<LexDef<typeof imagesLex, 'view', Registry>> 271 + export type Image = Prettify<LexDef<typeof imagesLex, 'image', Registry>> 272 + export type ViewImage = Prettify<LexDef<typeof imagesLex, 'viewImage', Registry>> 273 + export type RecordView = Prettify<LexDef<typeof recordLex, 'view', Registry>> 274 + export type ViewRecord = Prettify<LexDef<typeof recordLex, 'viewRecord', Registry>> 275 + export type ViewBlocked = Prettify<LexDef<typeof recordLex, 'viewBlocked', Registry>> 276 + export type ViewDetached = Prettify<LexDef<typeof recordLex, 'viewDetached', Registry>> 277 + export type ViewNotFound = Prettify<LexDef<typeof recordLex, 'viewNotFound', Registry>> 278 + export type RecordWithMediaView = Prettify<LexDef<typeof recordWithMediaLex, 'view', Registry>> 279 + export type VideoView = Prettify<LexDef<typeof videoLex, 'view', Registry>> 280 + export type Caption = Prettify<LexDef<typeof videoLex, 'caption', Registry>> 281 + export type PostView = Prettify<LexDef<typeof bskyFeedDefsLex, 'postView', Registry>> 282 + export type BskyFeedDefsReplyRef = Prettify<LexDef<typeof bskyFeedDefsLex, 'replyRef', Registry>> 283 + export type ReasonPin = Prettify<LexDef<typeof bskyFeedDefsLex, 'reasonPin', Registry>> 236 284 export type BlockedPost = Prettify<LexDef<typeof bskyFeedDefsLex, 'blockedPost', Registry>> 237 - export type FeedViewPost = Prettify<LexDef<typeof bskyFeedDefsLex, 'feedViewPost', Registry>> 238 - export type GeneratorView = Prettify<LexDef<typeof bskyFeedDefsLex, 'generatorView', Registry>> 239 - export type GeneratorViewerState = Prettify<LexDef<typeof bskyFeedDefsLex, 'generatorViewerState', Registry>> 240 285 export type Interaction = Prettify<LexDef<typeof bskyFeedDefsLex, 'interaction', Registry>> 286 + export type BskyFeedDefsViewerState = Prettify<LexDef<typeof bskyFeedDefsLex, 'viewerState', Registry>> 287 + export type FeedViewPost = Prettify<LexDef<typeof bskyFeedDefsLex, 'feedViewPost', Registry>> 241 288 export type NotFoundPost = Prettify<LexDef<typeof bskyFeedDefsLex, 'notFoundPost', Registry>> 242 - export type PostView = Prettify<LexDef<typeof bskyFeedDefsLex, 'postView', Registry>> 243 - export type ReasonPin = Prettify<LexDef<typeof bskyFeedDefsLex, 'reasonPin', Registry>> 244 289 export type ReasonRepost = Prettify<LexDef<typeof bskyFeedDefsLex, 'reasonRepost', Registry>> 245 - export type ReplyRef = Prettify<LexDef<typeof bskyFeedDefsLex, 'replyRef', Registry>> 290 + export type BlockedAuthor = Prettify<LexDef<typeof bskyFeedDefsLex, 'blockedAuthor', Registry>> 291 + export type GeneratorView = Prettify<LexDef<typeof bskyFeedDefsLex, 'generatorView', Registry>> 292 + export type ThreadContext = Prettify<LexDef<typeof bskyFeedDefsLex, 'threadContext', Registry>> 293 + export type ThreadViewPost = Prettify<LexDef<typeof bskyFeedDefsLex, 'threadViewPost', Registry>> 294 + export type ThreadgateView = Prettify<LexDef<typeof bskyFeedDefsLex, 'threadgateView', Registry>> 246 295 export type SkeletonFeedPost = Prettify<LexDef<typeof bskyFeedDefsLex, 'skeletonFeedPost', Registry>> 247 296 export type SkeletonReasonPin = Prettify<LexDef<typeof bskyFeedDefsLex, 'skeletonReasonPin', Registry>> 297 + export type GeneratorViewerState = Prettify<LexDef<typeof bskyFeedDefsLex, 'generatorViewerState', Registry>> 248 298 export type SkeletonReasonRepost = Prettify<LexDef<typeof bskyFeedDefsLex, 'skeletonReasonRepost', Registry>> 249 - export type ThreadContext = Prettify<LexDef<typeof bskyFeedDefsLex, 'threadContext', Registry>> 250 - export type ThreadViewPost = Prettify<LexDef<typeof bskyFeedDefsLex, 'threadViewPost', Registry>> 251 - export type ThreadgateView = Prettify<LexDef<typeof bskyFeedDefsLex, 'threadgateView', Registry>> 252 - export type BskyFeedDefsViewerState = Prettify<LexDef<typeof bskyFeedDefsLex, 'viewerState', Registry>> 253 - export type ListItemView = Prettify<LexDef<typeof bskyGraphDefsLex, 'listItemView', Registry>> 299 + export type Entity = Prettify<LexDef<typeof postLex, 'entity', Registry>> 300 + export type PostReplyRef = Prettify<LexDef<typeof postLex, 'replyRef', Registry>> 301 + export type TextSlice = Prettify<LexDef<typeof postLex, 'textSlice', Registry>> 302 + export type DisableRule = Prettify<LexDef<typeof postgateLex, 'disableRule', Registry>> 303 + export type ListRule = Prettify<LexDef<typeof threadgateLex, 'listRule', Registry>> 304 + export type MentionRule = Prettify<LexDef<typeof threadgateLex, 'mentionRule', Registry>> 305 + export type FollowerRule = Prettify<LexDef<typeof threadgateLex, 'followerRule', Registry>> 306 + export type FollowingRule = Prettify<LexDef<typeof threadgateLex, 'followingRule', Registry>> 254 307 export type ListView = Prettify<LexDef<typeof bskyGraphDefsLex, 'listView', Registry>> 308 + export type ListItemView = Prettify<LexDef<typeof bskyGraphDefsLex, 'listItemView', Registry>> 309 + export type Relationship = Prettify<LexDef<typeof bskyGraphDefsLex, 'relationship', Registry>> 255 310 export type ListViewBasic = Prettify<LexDef<typeof bskyGraphDefsLex, 'listViewBasic', Registry>> 311 + export type NotFoundActor = Prettify<LexDef<typeof bskyGraphDefsLex, 'notFoundActor', Registry>> 256 312 export type ListViewerState = Prettify<LexDef<typeof bskyGraphDefsLex, 'listViewerState', Registry>> 257 - export type NotFoundActor = Prettify<LexDef<typeof bskyGraphDefsLex, 'notFoundActor', Registry>> 258 - export type Relationship = Prettify<LexDef<typeof bskyGraphDefsLex, 'relationship', Registry>> 259 313 export type StarterPackView = Prettify<LexDef<typeof bskyGraphDefsLex, 'starterPackView', Registry>> 260 314 export type StarterPackViewBasic = Prettify<LexDef<typeof bskyGraphDefsLex, 'starterPackViewBasic', Registry>> 261 - export type ActivitySubscription = Prettify<LexDef<typeof bskyNotificationDefsLex, 'activitySubscription', Registry>> 262 - export type ChatPreference = Prettify<LexDef<typeof bskyNotificationDefsLex, 'chatPreference', Registry>> 263 - export type FilterablePreference = Prettify<LexDef<typeof bskyNotificationDefsLex, 'filterablePreference', Registry>> 315 + export type LabelerView = Prettify<LexDef<typeof bskyLabelerDefsLex, 'labelerView', Registry>> 316 + export type LabelerPolicies = Prettify<LexDef<typeof bskyLabelerDefsLex, 'labelerPolicies', Registry>> 317 + export type LabelerViewerState = Prettify<LexDef<typeof bskyLabelerDefsLex, 'labelerViewerState', Registry>> 318 + export type LabelerViewDetailed = Prettify<LexDef<typeof bskyLabelerDefsLex, 'labelerViewDetailed', Registry>> 264 319 export type Preference = Prettify<LexDef<typeof bskyNotificationDefsLex, 'preference', Registry>> 265 320 export type Preferences = Prettify<LexDef<typeof bskyNotificationDefsLex, 'preferences', Registry>> 266 321 export type RecordDeleted = Prettify<LexDef<typeof bskyNotificationDefsLex, 'recordDeleted', Registry>> 322 + export type ChatPreference = Prettify<LexDef<typeof bskyNotificationDefsLex, 'chatPreference', Registry>> 323 + export type ActivitySubscription = Prettify<LexDef<typeof bskyNotificationDefsLex, 'activitySubscription', Registry>> 324 + export type FilterablePreference = Prettify<LexDef<typeof bskyNotificationDefsLex, 'filterablePreference', Registry>> 267 325 export type SubjectActivitySubscription = Prettify<LexDef<typeof bskyNotificationDefsLex, 'subjectActivitySubscription', Registry>> 268 - export type ByteSlice = Prettify<LexDef<typeof facetLex, 'byteSlice', Registry>> 326 + export type Tag = Prettify<LexDef<typeof facetLex, 'tag', Registry>> 269 327 export type Link = Prettify<LexDef<typeof facetLex, 'link', Registry>> 270 328 export type Mention = Prettify<LexDef<typeof facetLex, 'mention', Registry>> 271 - export type Tag = Prettify<LexDef<typeof facetLex, 'tag', Registry>> 329 + export type ByteSlice = Prettify<LexDef<typeof facetLex, 'byteSlice', Registry>> 272 330 export type Label = Prettify<LexDef<typeof atprotoLabelDefsLex, 'label', Registry>> 331 + export type SelfLabels = Prettify<LexDef<typeof atprotoLabelDefsLex, 'selfLabels', Registry>> 332 + export type SelfLabel = Prettify<LexDef<typeof atprotoLabelDefsLex, 'selfLabel', Registry>> 273 333 export type LabelValueDefinition = Prettify<LexDef<typeof atprotoLabelDefsLex, 'labelValueDefinition', Registry>> 274 334 export type LabelValueDefinitionStrings = Prettify<LexDef<typeof atprotoLabelDefsLex, 'labelValueDefinitionStrings', Registry>> 275 - export type SelfLabel = Prettify<LexDef<typeof atprotoLabelDefsLex, 'selfLabel', Registry>> 276 - export type SelfLabels = Prettify<LexDef<typeof atprotoLabelDefsLex, 'selfLabels', Registry>> 277 335 export type RepoRef = Prettify<LexDef<typeof createReportLex, 'repoRef', Registry>> 278 336 export type LabelDefinition = Prettify<LexDef<typeof describeLabelsLex, 'labelDefinition', Registry>> 279 337 export type LabelLocale = Prettify<LexDef<typeof describeLabelsLex, 'labelLocale', Registry>> ··· 281 339 export type GrainActorDefsProfileViewDetailed = Prettify<LexDef<typeof grainActorDefsLex, 'profileViewDetailed', Registry>> 282 340 export type GrainActorDefsViewerState = Prettify<LexDef<typeof grainActorDefsLex, 'viewerState', Registry>> 283 341 export type CommentView = Prettify<LexDef<typeof grainCommentDefsLex, 'commentView', Registry>> 284 - export type AspectRatio = Prettify<LexDef<typeof grainDefsLex, 'aspectRatio', Registry>> 342 + export type GrainDefsAspectRatio = Prettify<LexDef<typeof grainDefsLex, 'aspectRatio', Registry>> 285 343 export type GalleryView = Prettify<LexDef<typeof grainGalleryDefsLex, 'galleryView', Registry>> 344 + export type CrossPostInfo = Prettify<LexDef<typeof grainGalleryDefsLex, 'crossPostInfo', Registry>> 286 345 export type GrainGalleryDefsViewerState = Prettify<LexDef<typeof grainGalleryDefsLex, 'viewerState', Registry>> 287 346 export type PhotoView = Prettify<LexDef<typeof grainPhotoDefsLex, 'photoView', Registry>> 288 347 export type ExifView = Prettify<LexDef<typeof grainPhotoDefsLex, 'exifView', Registry>> ··· 326 385 'social.grain.unspecced.getLocations': GetLocations 327 386 'social.grain.unspecced.getNotifications': GetNotifications 328 387 'social.grain.unspecced.getStories': GetStories 388 + 'social.grain.unspecced.getStory': GetStory 329 389 'social.grain.unspecced.getStoryAuthors': GetStoryAuthors 330 390 'social.grain.unspecced.getSuggestedFollows': GetSuggestedFollows 331 391 'social.grain.unspecced.searchGalleries': SearchGalleries ··· 389 449 // View identity helpers — wrap object literals to enable excess property checking. 390 450 // Usage: rows.map(r => views.statusView({ ...fields })) catches extra properties. 391 451 export const views = { 452 + statusView: (v: StatusView): StatusView => v, 392 453 bskyActorDefsProfileView: (v: BskyActorDefsProfileView): BskyActorDefsProfileView => v, 393 454 profileViewBasic: (v: ProfileViewBasic): ProfileViewBasic => v, 455 + verificationView: (v: VerificationView): VerificationView => v, 394 456 bskyActorDefsProfileViewDetailed: (v: BskyActorDefsProfileViewDetailed): BskyActorDefsProfileViewDetailed => v, 395 - statusView: (v: StatusView): StatusView => v, 396 - verificationView: (v: VerificationView): VerificationView => v, 397 - generatorView: (v: GeneratorView): GeneratorView => v, 398 457 postView: (v: PostView): PostView => v, 458 + generatorView: (v: GeneratorView): GeneratorView => v, 399 459 threadgateView: (v: ThreadgateView): ThreadgateView => v, 460 + listView: (v: ListView): ListView => v, 400 461 listItemView: (v: ListItemView): ListItemView => v, 401 - listView: (v: ListView): ListView => v, 402 462 listViewBasic: (v: ListViewBasic): ListViewBasic => v, 403 463 starterPackView: (v: StarterPackView): StarterPackView => v, 404 464 starterPackViewBasic: (v: StarterPackViewBasic): StarterPackViewBasic => v, 465 + labelerView: (v: LabelerView): LabelerView => v, 466 + labelerViewDetailed: (v: LabelerViewDetailed): LabelerViewDetailed => v, 405 467 grainActorDefsProfileView: (v: GrainActorDefsProfileView): GrainActorDefsProfileView => v, 406 468 grainActorDefsProfileViewDetailed: (v: GrainActorDefsProfileViewDetailed): GrainActorDefsProfileViewDetailed => v, 407 469 commentView: (v: CommentView): CommentView => v,
+731 -618
lexicons/app/bsky/actor/defs.json
··· 1 1 { 2 - "lexicon": 1, 3 2 "id": "app.bsky.actor.defs", 4 3 "defs": { 5 - "adultContentPref": { 4 + "nux": { 5 + "type": "object", 6 + "required": [ 7 + "id", 8 + "completed" 9 + ], 6 10 "properties": { 7 - "enabled": { 8 - "default": false, 9 - "type": "boolean" 11 + "id": { 12 + "type": "string", 13 + "maxLength": 100 14 + }, 15 + "data": { 16 + "type": "string", 17 + "maxLength": 3000, 18 + "description": "Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters.", 19 + "maxGraphemes": 300 20 + }, 21 + "completed": { 22 + "type": "boolean", 23 + "default": false 24 + }, 25 + "expiresAt": { 26 + "type": "string", 27 + "format": "datetime", 28 + "description": "The date and time at which the NUX will expire and should be considered completed." 10 29 } 11 30 }, 12 - "required": ["enabled"], 13 - "type": "object" 31 + "description": "A new user experiences (NUX) storage object" 14 32 }, 15 - "bskyAppProgressGuide": { 16 - "description": "If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress.", 33 + "mutedWord": { 34 + "type": "object", 35 + "required": [ 36 + "value", 37 + "targets" 38 + ], 17 39 "properties": { 18 - "guide": { 19 - "maxLength": 100, 40 + "id": { 20 41 "type": "string" 21 - } 22 - }, 23 - "required": ["guide"], 24 - "type": "object" 25 - }, 26 - "bskyAppStatePref": { 27 - "description": "A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this.", 28 - "properties": { 29 - "activeProgressGuide": { 30 - "ref": "#bskyAppProgressGuide", 31 - "type": "ref" 42 + }, 43 + "value": { 44 + "type": "string", 45 + "maxLength": 10000, 46 + "description": "The muted word itself.", 47 + "maxGraphemes": 1000 32 48 }, 33 - "nuxs": { 34 - "description": "Storage for NUXs the user has encountered.", 49 + "targets": { 50 + "type": "array", 35 51 "items": { 36 - "ref": "app.bsky.actor.defs#nux", 52 + "ref": "app.bsky.actor.defs#mutedWordTarget", 37 53 "type": "ref" 38 54 }, 39 - "maxLength": 100, 40 - "type": "array" 55 + "description": "The intended targets of the muted word." 56 + }, 57 + "expiresAt": { 58 + "type": "string", 59 + "format": "datetime", 60 + "description": "The date and time at which the muted word will expire and no longer be applied." 41 61 }, 42 - "queuedNudges": { 43 - "description": "An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user.", 44 - "items": { 45 - "maxLength": 100, 46 - "type": "string" 47 - }, 48 - "maxLength": 1000, 49 - "type": "array" 62 + "actorTarget": { 63 + "type": "string", 64 + "default": "all", 65 + "description": "Groups of users to apply the muted word to. If undefined, applies to all users.", 66 + "knownValues": [ 67 + "all", 68 + "exclude-following" 69 + ] 50 70 } 51 71 }, 52 - "type": "object" 72 + "description": "A word that the account owner has muted." 53 73 }, 54 - "contentLabelPref": { 74 + "savedFeed": { 75 + "type": "object", 76 + "required": [ 77 + "id", 78 + "type", 79 + "value", 80 + "pinned" 81 + ], 55 82 "properties": { 56 - "label": { 83 + "id": { 57 84 "type": "string" 58 85 }, 59 - "labelerDid": { 60 - "description": "Which labeler does this preference apply to? If undefined, applies globally.", 61 - "format": "did", 86 + "type": { 87 + "type": "string", 88 + "knownValues": [ 89 + "feed", 90 + "list", 91 + "timeline" 92 + ] 93 + }, 94 + "value": { 62 95 "type": "string" 63 96 }, 64 - "visibility": { 65 - "knownValues": ["ignore", "show", "warn", "hide"], 97 + "pinned": { 98 + "type": "boolean" 99 + } 100 + } 101 + }, 102 + "statusView": { 103 + "type": "object", 104 + "required": [ 105 + "status", 106 + "record" 107 + ], 108 + "properties": { 109 + "cid": { 110 + "type": "string", 111 + "format": "cid" 112 + }, 113 + "uri": { 114 + "type": "string", 115 + "format": "at-uri" 116 + }, 117 + "embed": { 118 + "refs": [ 119 + "app.bsky.embed.external#view" 120 + ], 121 + "type": "union", 122 + "description": "An optional embed associated with the status." 123 + }, 124 + "record": { 125 + "type": "unknown" 126 + }, 127 + "status": { 128 + "type": "string", 129 + "description": "The status for the account.", 130 + "knownValues": [ 131 + "app.bsky.actor.status#live" 132 + ] 133 + }, 134 + "isActive": { 135 + "type": "boolean", 136 + "description": "True if the status is not expired, false if it is expired. Only present if expiration was set." 137 + }, 138 + "expiresAt": { 139 + "type": "string", 140 + "format": "datetime", 141 + "description": "The date when this status will expire. The application might choose to no longer return the status after expiration." 142 + }, 143 + "isDisabled": { 144 + "type": "boolean", 145 + "description": "True if the user's go-live access has been disabled by a moderator, false otherwise." 146 + } 147 + } 148 + }, 149 + "preferences": { 150 + "type": "array", 151 + "items": { 152 + "refs": [ 153 + "#adultContentPref", 154 + "#contentLabelPref", 155 + "#savedFeedsPref", 156 + "#savedFeedsPrefV2", 157 + "#personalDetailsPref", 158 + "#declaredAgePref", 159 + "#feedViewPref", 160 + "#threadViewPref", 161 + "#interestsPref", 162 + "#mutedWordsPref", 163 + "#hiddenPostsPref", 164 + "#bskyAppStatePref", 165 + "#labelersPref", 166 + "#postInteractionSettingsPref", 167 + "#verificationPrefs", 168 + "#liveEventPreferences" 169 + ], 170 + "type": "union" 171 + } 172 + }, 173 + "profileView": { 174 + "type": "object", 175 + "required": [ 176 + "did", 177 + "handle" 178 + ], 179 + "properties": { 180 + "did": { 181 + "type": "string", 182 + "format": "did" 183 + }, 184 + "debug": { 185 + "type": "unknown", 186 + "description": "Debug information for internal development" 187 + }, 188 + "avatar": { 189 + "type": "string", 190 + "format": "uri" 191 + }, 192 + "handle": { 193 + "type": "string", 194 + "format": "handle" 195 + }, 196 + "labels": { 197 + "type": "array", 198 + "items": { 199 + "ref": "com.atproto.label.defs#label", 200 + "type": "ref" 201 + } 202 + }, 203 + "status": { 204 + "ref": "#statusView", 205 + "type": "ref" 206 + }, 207 + "viewer": { 208 + "ref": "#viewerState", 209 + "type": "ref" 210 + }, 211 + "pronouns": { 66 212 "type": "string" 213 + }, 214 + "createdAt": { 215 + "type": "string", 216 + "format": "datetime" 217 + }, 218 + "indexedAt": { 219 + "type": "string", 220 + "format": "datetime" 221 + }, 222 + "associated": { 223 + "ref": "#profileAssociated", 224 + "type": "ref" 225 + }, 226 + "description": { 227 + "type": "string", 228 + "maxLength": 2560, 229 + "maxGraphemes": 256 230 + }, 231 + "displayName": { 232 + "type": "string", 233 + "maxLength": 640, 234 + "maxGraphemes": 64 235 + }, 236 + "verification": { 237 + "ref": "#verificationState", 238 + "type": "ref" 67 239 } 68 - }, 69 - "required": ["label", "visibility"], 70 - "type": "object" 240 + } 71 241 }, 72 - "declaredAgePref": { 73 - "description": "Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration.", 242 + "viewerState": { 243 + "type": "object", 74 244 "properties": { 75 - "isOverAge13": { 76 - "description": "Indicates if the user has declared that they are over 13 years of age.", 245 + "muted": { 77 246 "type": "boolean" 78 247 }, 79 - "isOverAge16": { 80 - "description": "Indicates if the user has declared that they are over 16 years of age.", 81 - "type": "boolean" 248 + "blocking": { 249 + "type": "string", 250 + "format": "at-uri" 82 251 }, 83 - "isOverAge18": { 84 - "description": "Indicates if the user has declared that they are over 18 years of age.", 252 + "blockedBy": { 85 253 "type": "boolean" 254 + }, 255 + "following": { 256 + "type": "string", 257 + "format": "at-uri" 258 + }, 259 + "followedBy": { 260 + "type": "string", 261 + "format": "at-uri" 262 + }, 263 + "mutedByList": { 264 + "ref": "app.bsky.graph.defs#listViewBasic", 265 + "type": "ref" 266 + }, 267 + "blockingByList": { 268 + "ref": "app.bsky.graph.defs#listViewBasic", 269 + "type": "ref" 270 + }, 271 + "knownFollowers": { 272 + "ref": "#knownFollowers", 273 + "type": "ref", 274 + "description": "This property is present only in selected cases, as an optimization." 275 + }, 276 + "activitySubscription": { 277 + "ref": "app.bsky.notification.defs#activitySubscription", 278 + "type": "ref", 279 + "description": "This property is present only in selected cases, as an optimization." 86 280 } 87 281 }, 88 - "type": "object" 282 + "description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests." 89 283 }, 90 284 "feedViewPref": { 285 + "type": "object", 286 + "required": [ 287 + "feed" 288 + ], 91 289 "properties": { 92 290 "feed": { 93 - "description": "The URI of the feed, or an identifier which describes the feed.", 94 - "type": "string" 95 - }, 96 - "hideQuotePosts": { 97 - "description": "Hide quote posts in the feed.", 98 - "type": "boolean" 291 + "type": "string", 292 + "description": "The URI of the feed, or an identifier which describes the feed." 99 293 }, 100 294 "hideReplies": { 101 - "description": "Hide replies in the feed.", 102 - "type": "boolean" 295 + "type": "boolean", 296 + "description": "Hide replies in the feed." 297 + }, 298 + "hideReposts": { 299 + "type": "boolean", 300 + "description": "Hide reposts in the feed." 301 + }, 302 + "hideQuotePosts": { 303 + "type": "boolean", 304 + "description": "Hide quote posts in the feed." 103 305 }, 104 306 "hideRepliesByLikeCount": { 105 - "description": "Hide replies in the feed if they do not have this number of likes.", 106 - "type": "integer" 307 + "type": "integer", 308 + "description": "Hide replies in the feed if they do not have this number of likes." 107 309 }, 108 310 "hideRepliesByUnfollowed": { 311 + "type": "boolean", 109 312 "default": true, 110 - "description": "Hide replies in the feed if they are not by followed users.", 111 - "type": "boolean" 112 - }, 113 - "hideReposts": { 114 - "description": "Hide reposts in the feed.", 115 - "type": "boolean" 313 + "description": "Hide replies in the feed if they are not by followed users." 116 314 } 117 - }, 118 - "required": ["feed"], 119 - "type": "object" 315 + } 120 316 }, 121 - "hiddenPostsPref": { 317 + "labelersPref": { 318 + "type": "object", 319 + "required": [ 320 + "labelers" 321 + ], 122 322 "properties": { 123 - "items": { 124 - "description": "A list of URIs of posts the account owner has hidden.", 323 + "labelers": { 324 + "type": "array", 125 325 "items": { 126 - "format": "at-uri", 127 - "type": "string" 128 - }, 129 - "type": "array" 326 + "ref": "#labelerPrefItem", 327 + "type": "ref" 328 + } 130 329 } 131 - }, 132 - "required": ["items"], 133 - "type": "object" 330 + } 134 331 }, 135 332 "interestsPref": { 333 + "type": "object", 334 + "required": [ 335 + "tags" 336 + ], 136 337 "properties": { 137 338 "tags": { 138 - "description": "A list of tags which describe the account owner's interests gathered during onboarding.", 339 + "type": "array", 139 340 "items": { 140 - "maxGraphemes": 64, 341 + "type": "string", 141 342 "maxLength": 640, 142 - "type": "string" 343 + "maxGraphemes": 64 143 344 }, 144 345 "maxLength": 100, 145 - "type": "array" 346 + "description": "A list of tags which describe the account owner's interests gathered during onboarding." 146 347 } 147 - }, 148 - "required": ["tags"], 149 - "type": "object" 348 + } 150 349 }, 151 350 "knownFollowers": { 152 - "description": "The subject's followers whom you also follow", 351 + "type": "object", 352 + "required": [ 353 + "count", 354 + "followers" 355 + ], 153 356 "properties": { 154 357 "count": { 155 358 "type": "integer" 156 359 }, 157 360 "followers": { 361 + "type": "array", 158 362 "items": { 159 363 "ref": "#profileViewBasic", 160 364 "type": "ref" 161 365 }, 162 366 "maxLength": 5, 163 - "minLength": 0, 164 - "type": "array" 165 - } 166 - }, 167 - "required": ["count", "followers"], 168 - "type": "object" 169 - }, 170 - "labelerPrefItem": { 171 - "properties": { 172 - "did": { 173 - "format": "did", 174 - "type": "string" 367 + "minLength": 0 175 368 } 176 369 }, 177 - "required": ["did"], 178 - "type": "object" 370 + "description": "The subject's followers whom you also follow" 179 371 }, 180 - "labelersPref": { 372 + "mutedWordsPref": { 373 + "type": "object", 374 + "required": [ 375 + "items" 376 + ], 181 377 "properties": { 182 - "labelers": { 378 + "items": { 379 + "type": "array", 183 380 "items": { 184 - "ref": "#labelerPrefItem", 381 + "ref": "app.bsky.actor.defs#mutedWord", 185 382 "type": "ref" 186 383 }, 187 - "type": "array" 384 + "description": "A list of words the account owner has muted." 188 385 } 189 - }, 190 - "required": ["labelers"], 191 - "type": "object" 386 + } 192 387 }, 193 - "liveEventPreferences": { 194 - "description": "Preferences for live events.", 388 + "savedFeedsPref": { 389 + "type": "object", 390 + "required": [ 391 + "pinned", 392 + "saved" 393 + ], 195 394 "properties": { 196 - "hiddenFeedIds": { 197 - "description": "A list of feed IDs that the user has hidden from live events.", 395 + "saved": { 396 + "type": "array", 198 397 "items": { 199 - "type": "string" 200 - }, 201 - "type": "array" 398 + "type": "string", 399 + "format": "at-uri" 400 + } 202 401 }, 203 - "hideAllFeeds": { 204 - "default": false, 205 - "description": "Whether to hide all feeds from live events.", 206 - "type": "boolean" 402 + "pinned": { 403 + "type": "array", 404 + "items": { 405 + "type": "string", 406 + "format": "at-uri" 407 + } 408 + }, 409 + "timelineIndex": { 410 + "type": "integer" 207 411 } 208 - }, 209 - "type": "object" 412 + } 210 413 }, 211 - "mutedWord": { 212 - "description": "A word that the account owner has muted.", 414 + "threadViewPref": { 415 + "type": "object", 213 416 "properties": { 214 - "actorTarget": { 215 - "default": "all", 216 - "description": "Groups of users to apply the muted word to. If undefined, applies to all users.", 217 - "knownValues": ["all", "exclude-following"], 218 - "type": "string" 417 + "sort": { 418 + "type": "string", 419 + "description": "Sorting mode for threads.", 420 + "knownValues": [ 421 + "oldest", 422 + "newest", 423 + "most-likes", 424 + "random", 425 + "hotness" 426 + ] 427 + } 428 + } 429 + }, 430 + "declaredAgePref": { 431 + "type": "object", 432 + "properties": { 433 + "isOverAge13": { 434 + "type": "boolean", 435 + "description": "Indicates if the user has declared that they are over 13 years of age." 219 436 }, 220 - "expiresAt": { 221 - "description": "The date and time at which the muted word will expire and no longer be applied.", 222 - "format": "datetime", 223 - "type": "string" 437 + "isOverAge16": { 438 + "type": "boolean", 439 + "description": "Indicates if the user has declared that they are over 16 years of age." 224 440 }, 225 - "id": { 226 - "type": "string" 227 - }, 228 - "targets": { 229 - "description": "The intended targets of the muted word.", 441 + "isOverAge18": { 442 + "type": "boolean", 443 + "description": "Indicates if the user has declared that they are over 18 years of age." 444 + } 445 + }, 446 + "description": "Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration." 447 + }, 448 + "hiddenPostsPref": { 449 + "type": "object", 450 + "required": [ 451 + "items" 452 + ], 453 + "properties": { 454 + "items": { 455 + "type": "array", 230 456 "items": { 231 - "ref": "app.bsky.actor.defs#mutedWordTarget", 232 - "type": "ref" 457 + "type": "string", 458 + "format": "at-uri" 233 459 }, 234 - "type": "array" 235 - }, 236 - "value": { 237 - "description": "The muted word itself.", 238 - "maxGraphemes": 1000, 239 - "maxLength": 10000, 240 - "type": "string" 460 + "description": "A list of URIs of posts the account owner has hidden." 241 461 } 242 - }, 243 - "required": ["value", "targets"], 244 - "type": "object" 462 + } 463 + }, 464 + "labelerPrefItem": { 465 + "type": "object", 466 + "required": [ 467 + "did" 468 + ], 469 + "properties": { 470 + "did": { 471 + "type": "string", 472 + "format": "did" 473 + } 474 + } 245 475 }, 246 476 "mutedWordTarget": { 247 - "knownValues": ["content", "tag"], 248 - "maxGraphemes": 64, 477 + "type": "string", 249 478 "maxLength": 640, 250 - "type": "string" 479 + "knownValues": [ 480 + "content", 481 + "tag" 482 + ], 483 + "maxGraphemes": 64 251 484 }, 252 - "mutedWordsPref": { 485 + "adultContentPref": { 486 + "type": "object", 487 + "required": [ 488 + "enabled" 489 + ], 490 + "properties": { 491 + "enabled": { 492 + "type": "boolean", 493 + "default": false 494 + } 495 + } 496 + }, 497 + "bskyAppStatePref": { 498 + "type": "object", 253 499 "properties": { 254 - "items": { 255 - "description": "A list of words the account owner has muted.", 500 + "nuxs": { 501 + "type": "array", 256 502 "items": { 257 - "ref": "app.bsky.actor.defs#mutedWord", 503 + "ref": "app.bsky.actor.defs#nux", 258 504 "type": "ref" 259 505 }, 260 - "type": "array" 506 + "maxLength": 100, 507 + "description": "Storage for NUXs the user has encountered." 508 + }, 509 + "queuedNudges": { 510 + "type": "array", 511 + "items": { 512 + "type": "string", 513 + "maxLength": 100 514 + }, 515 + "maxLength": 1000, 516 + "description": "An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user." 517 + }, 518 + "activeProgressGuide": { 519 + "ref": "#bskyAppProgressGuide", 520 + "type": "ref" 261 521 } 262 522 }, 263 - "required": ["items"], 264 - "type": "object" 523 + "description": "A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this." 265 524 }, 266 - "nux": { 267 - "description": "A new user experiences (NUX) storage object", 525 + "contentLabelPref": { 526 + "type": "object", 527 + "required": [ 528 + "label", 529 + "visibility" 530 + ], 268 531 "properties": { 269 - "completed": { 270 - "default": false, 271 - "type": "boolean" 272 - }, 273 - "data": { 274 - "description": "Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters.", 275 - "maxGraphemes": 300, 276 - "maxLength": 3000, 532 + "label": { 277 533 "type": "string" 278 534 }, 279 - "expiresAt": { 280 - "description": "The date and time at which the NUX will expire and should be considered completed.", 281 - "format": "datetime", 282 - "type": "string" 535 + "labelerDid": { 536 + "type": "string", 537 + "format": "did", 538 + "description": "Which labeler does this preference apply to? If undefined, applies globally." 283 539 }, 284 - "id": { 285 - "maxLength": 100, 286 - "type": "string" 540 + "visibility": { 541 + "type": "string", 542 + "knownValues": [ 543 + "ignore", 544 + "show", 545 + "warn", 546 + "hide" 547 + ] 287 548 } 288 - }, 289 - "required": ["id", "completed"], 290 - "type": "object" 549 + } 291 550 }, 292 - "personalDetailsPref": { 551 + "profileViewBasic": { 552 + "type": "object", 553 + "required": [ 554 + "did", 555 + "handle" 556 + ], 293 557 "properties": { 294 - "birthDate": { 295 - "description": "The birth date of account owner.", 296 - "format": "datetime", 558 + "did": { 559 + "type": "string", 560 + "format": "did" 561 + }, 562 + "debug": { 563 + "type": "unknown", 564 + "description": "Debug information for internal development" 565 + }, 566 + "avatar": { 567 + "type": "string", 568 + "format": "uri" 569 + }, 570 + "handle": { 571 + "type": "string", 572 + "format": "handle" 573 + }, 574 + "labels": { 575 + "type": "array", 576 + "items": { 577 + "ref": "com.atproto.label.defs#label", 578 + "type": "ref" 579 + } 580 + }, 581 + "status": { 582 + "ref": "#statusView", 583 + "type": "ref" 584 + }, 585 + "viewer": { 586 + "ref": "#viewerState", 587 + "type": "ref" 588 + }, 589 + "pronouns": { 297 590 "type": "string" 591 + }, 592 + "createdAt": { 593 + "type": "string", 594 + "format": "datetime" 595 + }, 596 + "associated": { 597 + "ref": "#profileAssociated", 598 + "type": "ref" 599 + }, 600 + "displayName": { 601 + "type": "string", 602 + "maxLength": 640, 603 + "maxGraphemes": 64 604 + }, 605 + "verification": { 606 + "ref": "#verificationState", 607 + "type": "ref" 298 608 } 299 - }, 300 - "type": "object" 609 + } 301 610 }, 302 - "postInteractionSettingsPref": { 303 - "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.", 611 + "savedFeedsPrefV2": { 612 + "type": "object", 613 + "required": [ 614 + "items" 615 + ], 304 616 "properties": { 305 - "postgateEmbeddingRules": { 306 - "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.", 617 + "items": { 618 + "type": "array", 307 619 "items": { 308 - "refs": ["app.bsky.feed.postgate#disableRule"], 309 - "type": "union" 310 - }, 311 - "maxLength": 5, 312 - "type": "array" 620 + "ref": "app.bsky.actor.defs#savedFeed", 621 + "type": "ref" 622 + } 623 + } 624 + } 625 + }, 626 + "verificationView": { 627 + "type": "object", 628 + "required": [ 629 + "issuer", 630 + "uri", 631 + "isValid", 632 + "createdAt" 633 + ], 634 + "properties": { 635 + "uri": { 636 + "type": "string", 637 + "format": "at-uri", 638 + "description": "The AT-URI of the verification record." 313 639 }, 314 - "threadgateAllowRules": { 315 - "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.", 316 - "items": { 317 - "refs": [ 318 - "app.bsky.feed.threadgate#mentionRule", 319 - "app.bsky.feed.threadgate#followerRule", 320 - "app.bsky.feed.threadgate#followingRule", 321 - "app.bsky.feed.threadgate#listRule" 322 - ], 323 - "type": "union" 324 - }, 325 - "maxLength": 5, 326 - "type": "array" 640 + "issuer": { 641 + "type": "string", 642 + "format": "did", 643 + "description": "The user who issued this verification." 644 + }, 645 + "isValid": { 646 + "type": "boolean", 647 + "description": "True if the verification passes validation, otherwise false." 648 + }, 649 + "createdAt": { 650 + "type": "string", 651 + "format": "datetime", 652 + "description": "Timestamp when the verification was created." 327 653 } 328 654 }, 329 - "required": [], 330 - "type": "object" 331 - }, 332 - "preferences": { 333 - "items": { 334 - "refs": [ 335 - "#adultContentPref", 336 - "#contentLabelPref", 337 - "#savedFeedsPref", 338 - "#savedFeedsPrefV2", 339 - "#personalDetailsPref", 340 - "#declaredAgePref", 341 - "#feedViewPref", 342 - "#threadViewPref", 343 - "#interestsPref", 344 - "#mutedWordsPref", 345 - "#hiddenPostsPref", 346 - "#bskyAppStatePref", 347 - "#labelersPref", 348 - "#postInteractionSettingsPref", 349 - "#verificationPrefs", 350 - "#liveEventPreferences" 351 - ], 352 - "type": "union" 353 - }, 354 - "type": "array" 655 + "description": "An individual verification for an associated subject." 355 656 }, 356 657 "profileAssociated": { 658 + "type": "object", 357 659 "properties": { 358 - "activitySubscription": { 359 - "ref": "#profileAssociatedActivitySubscription", 360 - "type": "ref" 361 - }, 362 660 "chat": { 363 661 "ref": "#profileAssociatedChat", 364 662 "type": "ref" 365 - }, 366 - "feedgens": { 367 - "type": "integer" 368 663 }, 369 664 "germ": { 370 665 "ref": "#profileAssociatedGerm", 371 666 "type": "ref" 372 667 }, 668 + "lists": { 669 + "type": "integer" 670 + }, 373 671 "labeler": { 374 672 "type": "boolean" 375 673 }, 376 - "lists": { 674 + "feedgens": { 377 675 "type": "integer" 378 676 }, 379 677 "starterPacks": { 380 678 "type": "integer" 381 - } 382 - }, 383 - "type": "object" 384 - }, 385 - "profileAssociatedActivitySubscription": { 386 - "properties": { 387 - "allowSubscriptions": { 388 - "knownValues": ["followers", "mutuals", "none"], 389 - "type": "string" 679 + }, 680 + "activitySubscription": { 681 + "ref": "#profileAssociatedActivitySubscription", 682 + "type": "ref" 390 683 } 391 - }, 392 - "required": ["allowSubscriptions"], 393 - "type": "object" 684 + } 394 685 }, 395 - "profileAssociatedChat": { 686 + "verificationPrefs": { 687 + "type": "object", 688 + "required": [], 396 689 "properties": { 397 - "allowIncoming": { 398 - "knownValues": ["all", "none", "following"], 399 - "type": "string" 690 + "hideBadges": { 691 + "type": "boolean", 692 + "default": false, 693 + "description": "Hide the blue check badges for verified accounts and trusted verifiers." 400 694 } 401 695 }, 402 - "required": ["allowIncoming"], 403 - "type": "object" 696 + "description": "Preferences for how verified accounts appear in the app." 404 697 }, 405 - "profileAssociatedGerm": { 698 + "verificationState": { 699 + "type": "object", 700 + "required": [ 701 + "verifications", 702 + "verifiedStatus", 703 + "trustedVerifierStatus" 704 + ], 406 705 "properties": { 407 - "messageMeUrl": { 408 - "format": "uri", 409 - "type": "string" 410 - }, 411 - "showButtonTo": { 412 - "knownValues": ["usersIFollow", "everyone"], 413 - "type": "string" 414 - } 415 - }, 416 - "required": ["showButtonTo", "messageMeUrl"], 417 - "type": "object" 418 - }, 419 - "profileView": { 420 - "properties": { 421 - "associated": { 422 - "ref": "#profileAssociated", 423 - "type": "ref" 424 - }, 425 - "avatar": { 426 - "format": "uri", 427 - "type": "string" 428 - }, 429 - "createdAt": { 430 - "format": "datetime", 431 - "type": "string" 432 - }, 433 - "debug": { 434 - "description": "Debug information for internal development", 435 - "type": "unknown" 436 - }, 437 - "description": { 438 - "maxGraphemes": 256, 439 - "maxLength": 2560, 440 - "type": "string" 441 - }, 442 - "did": { 443 - "format": "did", 444 - "type": "string" 445 - }, 446 - "displayName": { 447 - "maxGraphemes": 64, 448 - "maxLength": 640, 449 - "type": "string" 450 - }, 451 - "handle": { 452 - "format": "handle", 453 - "type": "string" 454 - }, 455 - "indexedAt": { 456 - "format": "datetime", 457 - "type": "string" 458 - }, 459 - "labels": { 706 + "verifications": { 707 + "type": "array", 460 708 "items": { 461 - "ref": "com.atproto.label.defs#label", 709 + "ref": "#verificationView", 462 710 "type": "ref" 463 711 }, 464 - "type": "array" 712 + "description": "All verifications issued by trusted verifiers on behalf of this user. Verifications by untrusted verifiers are not included." 465 713 }, 466 - "pronouns": { 467 - "type": "string" 468 - }, 469 - "status": { 470 - "ref": "#statusView", 471 - "type": "ref" 472 - }, 473 - "verification": { 474 - "ref": "#verificationState", 475 - "type": "ref" 714 + "verifiedStatus": { 715 + "type": "string", 716 + "description": "The user's status as a verified account.", 717 + "knownValues": [ 718 + "valid", 719 + "invalid", 720 + "none" 721 + ] 476 722 }, 477 - "viewer": { 478 - "ref": "#viewerState", 479 - "type": "ref" 723 + "trustedVerifierStatus": { 724 + "type": "string", 725 + "description": "The user's status as a trusted verifier.", 726 + "knownValues": [ 727 + "valid", 728 + "invalid", 729 + "none" 730 + ] 480 731 } 481 732 }, 482 - "required": ["did", "handle"], 483 - "type": "object" 733 + "description": "Represents the verification information about the user this object is attached to." 484 734 }, 485 - "profileViewBasic": { 735 + "personalDetailsPref": { 736 + "type": "object", 486 737 "properties": { 487 - "associated": { 488 - "ref": "#profileAssociated", 489 - "type": "ref" 490 - }, 491 - "avatar": { 492 - "format": "uri", 493 - "type": "string" 494 - }, 495 - "createdAt": { 738 + "birthDate": { 739 + "type": "string", 496 740 "format": "datetime", 497 - "type": "string" 741 + "description": "The birth date of account owner." 742 + } 743 + } 744 + }, 745 + "profileViewDetailed": { 746 + "type": "object", 747 + "required": [ 748 + "did", 749 + "handle" 750 + ], 751 + "properties": { 752 + "did": { 753 + "type": "string", 754 + "format": "did" 498 755 }, 499 756 "debug": { 500 - "description": "Debug information for internal development", 501 - "type": "unknown" 757 + "type": "unknown", 758 + "description": "Debug information for internal development" 502 759 }, 503 - "did": { 504 - "format": "did", 505 - "type": "string" 760 + "avatar": { 761 + "type": "string", 762 + "format": "uri" 506 763 }, 507 - "displayName": { 508 - "maxGraphemes": 64, 509 - "maxLength": 640, 510 - "type": "string" 764 + "banner": { 765 + "type": "string", 766 + "format": "uri" 511 767 }, 512 768 "handle": { 513 - "format": "handle", 514 - "type": "string" 769 + "type": "string", 770 + "format": "handle" 515 771 }, 516 772 "labels": { 773 + "type": "array", 517 774 "items": { 518 775 "ref": "com.atproto.label.defs#label", 519 776 "type": "ref" 520 - }, 521 - "type": "array" 522 - }, 523 - "pronouns": { 524 - "type": "string" 777 + } 525 778 }, 526 779 "status": { 527 780 "ref": "#statusView", 528 781 "type": "ref" 529 782 }, 530 - "verification": { 531 - "ref": "#verificationState", 532 - "type": "ref" 533 - }, 534 783 "viewer": { 535 784 "ref": "#viewerState", 536 785 "type": "ref" 537 - } 538 - }, 539 - "required": ["did", "handle"], 540 - "type": "object" 541 - }, 542 - "profileViewDetailed": { 543 - "properties": { 544 - "associated": { 545 - "ref": "#profileAssociated", 546 - "type": "ref" 547 786 }, 548 - "avatar": { 549 - "format": "uri", 550 - "type": "string" 787 + "website": { 788 + "type": "string", 789 + "format": "uri" 551 790 }, 552 - "banner": { 553 - "format": "uri", 791 + "pronouns": { 554 792 "type": "string" 555 793 }, 556 794 "createdAt": { 557 - "format": "datetime", 558 - "type": "string" 559 - }, 560 - "debug": { 561 - "description": "Debug information for internal development", 562 - "type": "unknown" 563 - }, 564 - "description": { 565 - "maxGraphemes": 256, 566 - "maxLength": 2560, 567 - "type": "string" 568 - }, 569 - "did": { 570 - "format": "did", 571 - "type": "string" 572 - }, 573 - "displayName": { 574 - "maxGraphemes": 64, 575 - "maxLength": 640, 576 - "type": "string" 577 - }, 578 - "followersCount": { 579 - "type": "integer" 580 - }, 581 - "followsCount": { 582 - "type": "integer" 583 - }, 584 - "handle": { 585 - "format": "handle", 586 - "type": "string" 795 + "type": "string", 796 + "format": "datetime" 587 797 }, 588 798 "indexedAt": { 589 - "format": "datetime", 590 - "type": "string" 799 + "type": "string", 800 + "format": "datetime" 591 801 }, 592 - "joinedViaStarterPack": { 593 - "ref": "app.bsky.graph.defs#starterPackViewBasic", 802 + "associated": { 803 + "ref": "#profileAssociated", 594 804 "type": "ref" 595 805 }, 596 - "labels": { 597 - "items": { 598 - "ref": "com.atproto.label.defs#label", 599 - "type": "ref" 600 - }, 601 - "type": "array" 602 - }, 603 806 "pinnedPost": { 604 807 "ref": "com.atproto.repo.strongRef", 605 808 "type": "ref" ··· 607 810 "postsCount": { 608 811 "type": "integer" 609 812 }, 610 - "pronouns": { 611 - "type": "string" 813 + "description": { 814 + "type": "string", 815 + "maxLength": 2560, 816 + "maxGraphemes": 256 817 + }, 818 + "displayName": { 819 + "type": "string", 820 + "maxLength": 640, 821 + "maxGraphemes": 64 612 822 }, 613 - "status": { 614 - "ref": "#statusView", 615 - "type": "ref" 823 + "followsCount": { 824 + "type": "integer" 616 825 }, 617 826 "verification": { 618 827 "ref": "#verificationState", 619 828 "type": "ref" 620 829 }, 621 - "viewer": { 622 - "ref": "#viewerState", 623 - "type": "ref" 830 + "followersCount": { 831 + "type": "integer" 624 832 }, 625 - "website": { 626 - "format": "uri", 627 - "type": "string" 833 + "joinedViaStarterPack": { 834 + "ref": "app.bsky.graph.defs#starterPackViewBasic", 835 + "type": "ref" 628 836 } 629 - }, 630 - "required": ["did", "handle"], 631 - "type": "object" 837 + } 632 838 }, 633 - "savedFeed": { 839 + "bskyAppProgressGuide": { 840 + "type": "object", 841 + "required": [ 842 + "guide" 843 + ], 634 844 "properties": { 635 - "id": { 636 - "type": "string" 637 - }, 638 - "pinned": { 639 - "type": "boolean" 640 - }, 641 - "type": { 642 - "knownValues": ["feed", "list", "timeline"], 643 - "type": "string" 644 - }, 645 - "value": { 646 - "type": "string" 845 + "guide": { 846 + "type": "string", 847 + "maxLength": 100 647 848 } 648 849 }, 649 - "required": ["id", "type", "value", "pinned"], 650 - "type": "object" 850 + "description": "If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress." 651 851 }, 652 - "savedFeedsPref": { 852 + "liveEventPreferences": { 853 + "type": "object", 653 854 "properties": { 654 - "pinned": { 655 - "items": { 656 - "format": "at-uri", 657 - "type": "string" 658 - }, 659 - "type": "array" 855 + "hideAllFeeds": { 856 + "type": "boolean", 857 + "default": false, 858 + "description": "Whether to hide all feeds from live events." 660 859 }, 661 - "saved": { 860 + "hiddenFeedIds": { 861 + "type": "array", 662 862 "items": { 663 - "format": "at-uri", 664 863 "type": "string" 665 864 }, 666 - "type": "array" 667 - }, 668 - "timelineIndex": { 669 - "type": "integer" 865 + "description": "A list of feed IDs that the user has hidden from live events." 670 866 } 671 867 }, 672 - "required": ["pinned", "saved"], 673 - "type": "object" 868 + "description": "Preferences for live events." 674 869 }, 675 - "savedFeedsPrefV2": { 870 + "profileAssociatedChat": { 871 + "type": "object", 872 + "required": [ 873 + "allowIncoming" 874 + ], 676 875 "properties": { 677 - "items": { 678 - "items": { 679 - "ref": "app.bsky.actor.defs#savedFeed", 680 - "type": "ref" 681 - }, 682 - "type": "array" 876 + "allowIncoming": { 877 + "type": "string", 878 + "knownValues": [ 879 + "all", 880 + "none", 881 + "following" 882 + ] 683 883 } 684 - }, 685 - "required": ["items"], 686 - "type": "object" 884 + } 687 885 }, 688 - "statusView": { 886 + "profileAssociatedGerm": { 887 + "type": "object", 888 + "required": [ 889 + "showButtonTo", 890 + "messageMeUrl" 891 + ], 689 892 "properties": { 690 - "cid": { 691 - "format": "cid", 692 - "type": "string" 893 + "messageMeUrl": { 894 + "type": "string", 895 + "format": "uri" 693 896 }, 694 - "embed": { 695 - "description": "An optional embed associated with the status.", 696 - "refs": ["app.bsky.embed.external#view"], 697 - "type": "union" 698 - }, 699 - "expiresAt": { 700 - "description": "The date when this status will expire. The application might choose to no longer return the status after expiration.", 701 - "format": "datetime", 702 - "type": "string" 703 - }, 704 - "isActive": { 705 - "description": "True if the status is not expired, false if it is expired. Only present if expiration was set.", 706 - "type": "boolean" 707 - }, 708 - "isDisabled": { 709 - "description": "True if the user's go-live access has been disabled by a moderator, false otherwise.", 710 - "type": "boolean" 711 - }, 712 - "record": { 713 - "type": "unknown" 714 - }, 715 - "status": { 716 - "description": "The status for the account.", 717 - "knownValues": ["app.bsky.actor.status#live"], 718 - "type": "string" 719 - }, 720 - "uri": { 721 - "format": "at-uri", 722 - "type": "string" 897 + "showButtonTo": { 898 + "type": "string", 899 + "knownValues": [ 900 + "usersIFollow", 901 + "everyone" 902 + ] 723 903 } 724 - }, 725 - "required": ["status", "record"], 726 - "type": "object" 904 + } 727 905 }, 728 - "threadViewPref": { 729 - "properties": { 730 - "sort": { 731 - "description": "Sorting mode for threads.", 732 - "knownValues": ["oldest", "newest", "most-likes", "random", "hotness"], 733 - "type": "string" 734 - } 735 - }, 736 - "type": "object" 737 - }, 738 - "verificationPrefs": { 739 - "description": "Preferences for how verified accounts appear in the app.", 740 - "properties": { 741 - "hideBadges": { 742 - "default": false, 743 - "description": "Hide the blue check badges for verified accounts and trusted verifiers.", 744 - "type": "boolean" 745 - } 746 - }, 906 + "postInteractionSettingsPref": { 907 + "type": "object", 747 908 "required": [], 748 - "type": "object" 749 - }, 750 - "verificationState": { 751 - "description": "Represents the verification information about the user this object is attached to.", 752 909 "properties": { 753 - "trustedVerifierStatus": { 754 - "description": "The user's status as a trusted verifier.", 755 - "knownValues": ["valid", "invalid", "none"], 756 - "type": "string" 757 - }, 758 - "verifications": { 759 - "description": "All verifications issued by trusted verifiers on behalf of this user. Verifications by untrusted verifiers are not included.", 910 + "threadgateAllowRules": { 911 + "type": "array", 760 912 "items": { 761 - "ref": "#verificationView", 762 - "type": "ref" 913 + "refs": [ 914 + "app.bsky.feed.threadgate#mentionRule", 915 + "app.bsky.feed.threadgate#followerRule", 916 + "app.bsky.feed.threadgate#followingRule", 917 + "app.bsky.feed.threadgate#listRule" 918 + ], 919 + "type": "union" 763 920 }, 764 - "type": "array" 765 - }, 766 - "verifiedStatus": { 767 - "description": "The user's status as a verified account.", 768 - "knownValues": ["valid", "invalid", "none"], 769 - "type": "string" 770 - } 771 - }, 772 - "required": ["verifications", "verifiedStatus", "trustedVerifierStatus"], 773 - "type": "object" 774 - }, 775 - "verificationView": { 776 - "description": "An individual verification for an associated subject.", 777 - "properties": { 778 - "createdAt": { 779 - "description": "Timestamp when the verification was created.", 780 - "format": "datetime", 781 - "type": "string" 921 + "maxLength": 5, 922 + "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." 782 923 }, 783 - "isValid": { 784 - "description": "True if the verification passes validation, otherwise false.", 785 - "type": "boolean" 786 - }, 787 - "issuer": { 788 - "description": "The user who issued this verification.", 789 - "format": "did", 790 - "type": "string" 791 - }, 792 - "uri": { 793 - "description": "The AT-URI of the verification record.", 794 - "format": "at-uri", 795 - "type": "string" 924 + "postgateEmbeddingRules": { 925 + "type": "array", 926 + "items": { 927 + "refs": [ 928 + "app.bsky.feed.postgate#disableRule" 929 + ], 930 + "type": "union" 931 + }, 932 + "maxLength": 5, 933 + "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." 796 934 } 797 935 }, 798 - "required": ["issuer", "uri", "isValid", "createdAt"], 799 - "type": "object" 936 + "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." 800 937 }, 801 - "viewerState": { 802 - "description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.", 938 + "profileAssociatedActivitySubscription": { 939 + "type": "object", 940 + "required": [ 941 + "allowSubscriptions" 942 + ], 803 943 "properties": { 804 - "activitySubscription": { 805 - "description": "This property is present only in selected cases, as an optimization.", 806 - "ref": "app.bsky.notification.defs#activitySubscription", 807 - "type": "ref" 808 - }, 809 - "blockedBy": { 810 - "type": "boolean" 811 - }, 812 - "blocking": { 813 - "format": "at-uri", 814 - "type": "string" 815 - }, 816 - "blockingByList": { 817 - "ref": "app.bsky.graph.defs#listViewBasic", 818 - "type": "ref" 819 - }, 820 - "followedBy": { 821 - "format": "at-uri", 822 - "type": "string" 823 - }, 824 - "following": { 825 - "format": "at-uri", 826 - "type": "string" 827 - }, 828 - "knownFollowers": { 829 - "description": "This property is present only in selected cases, as an optimization.", 830 - "ref": "#knownFollowers", 831 - "type": "ref" 832 - }, 833 - "muted": { 834 - "type": "boolean" 835 - }, 836 - "mutedByList": { 837 - "ref": "app.bsky.graph.defs#listViewBasic", 838 - "type": "ref" 944 + "allowSubscriptions": { 945 + "type": "string", 946 + "knownValues": [ 947 + "followers", 948 + "mutuals", 949 + "none" 950 + ] 839 951 } 840 - }, 841 - "type": "object" 952 + } 842 953 } 843 - } 954 + }, 955 + "$type": "com.atproto.lexicon.schema", 956 + "lexicon": 1 844 957 }
+25
lexicons/app/bsky/embed/defs.json
··· 1 + { 2 + "id": "app.bsky.embed.defs", 3 + "defs": { 4 + "aspectRatio": { 5 + "type": "object", 6 + "required": [ 7 + "width", 8 + "height" 9 + ], 10 + "properties": { 11 + "width": { 12 + "type": "integer", 13 + "minimum": 1 14 + }, 15 + "height": { 16 + "type": "integer", 17 + "minimum": 1 18 + } 19 + }, 20 + "description": "width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit." 21 + } 22 + }, 23 + "$type": "com.atproto.lexicon.schema", 24 + "lexicon": 1 25 + }
+83
lexicons/app/bsky/embed/external.json
··· 1 + { 2 + "id": "app.bsky.embed.external", 3 + "defs": { 4 + "main": { 5 + "type": "object", 6 + "required": [ 7 + "external" 8 + ], 9 + "properties": { 10 + "external": { 11 + "ref": "#external", 12 + "type": "ref" 13 + } 14 + }, 15 + "description": "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post)." 16 + }, 17 + "view": { 18 + "type": "object", 19 + "required": [ 20 + "external" 21 + ], 22 + "properties": { 23 + "external": { 24 + "ref": "#viewExternal", 25 + "type": "ref" 26 + } 27 + } 28 + }, 29 + "external": { 30 + "type": "object", 31 + "required": [ 32 + "uri", 33 + "title", 34 + "description" 35 + ], 36 + "properties": { 37 + "uri": { 38 + "type": "string", 39 + "format": "uri" 40 + }, 41 + "thumb": { 42 + "type": "blob", 43 + "accept": [ 44 + "image/*" 45 + ], 46 + "maxSize": 1000000 47 + }, 48 + "title": { 49 + "type": "string" 50 + }, 51 + "description": { 52 + "type": "string" 53 + } 54 + } 55 + }, 56 + "viewExternal": { 57 + "type": "object", 58 + "required": [ 59 + "uri", 60 + "title", 61 + "description" 62 + ], 63 + "properties": { 64 + "uri": { 65 + "type": "string", 66 + "format": "uri" 67 + }, 68 + "thumb": { 69 + "type": "string", 70 + "format": "uri" 71 + }, 72 + "title": { 73 + "type": "string" 74 + }, 75 + "description": { 76 + "type": "string" 77 + } 78 + } 79 + } 80 + }, 81 + "$type": "com.atproto.lexicon.schema", 82 + "lexicon": 1 83 + }
+92
lexicons/app/bsky/embed/images.json
··· 1 + { 2 + "id": "app.bsky.embed.images", 3 + "defs": { 4 + "main": { 5 + "type": "object", 6 + "required": [ 7 + "images" 8 + ], 9 + "properties": { 10 + "images": { 11 + "type": "array", 12 + "items": { 13 + "ref": "#image", 14 + "type": "ref" 15 + }, 16 + "maxLength": 4 17 + } 18 + } 19 + }, 20 + "view": { 21 + "type": "object", 22 + "required": [ 23 + "images" 24 + ], 25 + "properties": { 26 + "images": { 27 + "type": "array", 28 + "items": { 29 + "ref": "#viewImage", 30 + "type": "ref" 31 + }, 32 + "maxLength": 4 33 + } 34 + } 35 + }, 36 + "image": { 37 + "type": "object", 38 + "required": [ 39 + "image", 40 + "alt" 41 + ], 42 + "properties": { 43 + "alt": { 44 + "type": "string", 45 + "description": "Alt text description of the image, for accessibility." 46 + }, 47 + "image": { 48 + "type": "blob", 49 + "accept": [ 50 + "image/*" 51 + ], 52 + "maxSize": 1000000 53 + }, 54 + "aspectRatio": { 55 + "ref": "app.bsky.embed.defs#aspectRatio", 56 + "type": "ref" 57 + } 58 + } 59 + }, 60 + "viewImage": { 61 + "type": "object", 62 + "required": [ 63 + "thumb", 64 + "fullsize", 65 + "alt" 66 + ], 67 + "properties": { 68 + "alt": { 69 + "type": "string", 70 + "description": "Alt text description of the image, for accessibility." 71 + }, 72 + "thumb": { 73 + "type": "string", 74 + "format": "uri", 75 + "description": "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View." 76 + }, 77 + "fullsize": { 78 + "type": "string", 79 + "format": "uri", 80 + "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." 81 + }, 82 + "aspectRatio": { 83 + "ref": "app.bsky.embed.defs#aspectRatio", 84 + "type": "ref" 85 + } 86 + } 87 + } 88 + }, 89 + "$type": "com.atproto.lexicon.schema", 90 + "lexicon": 1, 91 + "description": "A set of images embedded in a Bluesky record (eg, a post)." 92 + }
+161
lexicons/app/bsky/embed/record.json
··· 1 + { 2 + "id": "app.bsky.embed.record", 3 + "defs": { 4 + "main": { 5 + "type": "object", 6 + "required": [ 7 + "record" 8 + ], 9 + "properties": { 10 + "record": { 11 + "ref": "com.atproto.repo.strongRef", 12 + "type": "ref" 13 + } 14 + } 15 + }, 16 + "view": { 17 + "type": "object", 18 + "required": [ 19 + "record" 20 + ], 21 + "properties": { 22 + "record": { 23 + "refs": [ 24 + "#viewRecord", 25 + "#viewNotFound", 26 + "#viewBlocked", 27 + "#viewDetached", 28 + "app.bsky.feed.defs#generatorView", 29 + "app.bsky.graph.defs#listView", 30 + "app.bsky.labeler.defs#labelerView", 31 + "app.bsky.graph.defs#starterPackViewBasic" 32 + ], 33 + "type": "union" 34 + } 35 + } 36 + }, 37 + "viewRecord": { 38 + "type": "object", 39 + "required": [ 40 + "uri", 41 + "cid", 42 + "author", 43 + "value", 44 + "indexedAt" 45 + ], 46 + "properties": { 47 + "cid": { 48 + "type": "string", 49 + "format": "cid" 50 + }, 51 + "uri": { 52 + "type": "string", 53 + "format": "at-uri" 54 + }, 55 + "value": { 56 + "type": "unknown", 57 + "description": "The record data itself." 58 + }, 59 + "author": { 60 + "ref": "app.bsky.actor.defs#profileViewBasic", 61 + "type": "ref" 62 + }, 63 + "embeds": { 64 + "type": "array", 65 + "items": { 66 + "refs": [ 67 + "app.bsky.embed.images#view", 68 + "app.bsky.embed.video#view", 69 + "app.bsky.embed.external#view", 70 + "app.bsky.embed.record#view", 71 + "app.bsky.embed.recordWithMedia#view" 72 + ], 73 + "type": "union" 74 + } 75 + }, 76 + "labels": { 77 + "type": "array", 78 + "items": { 79 + "ref": "com.atproto.label.defs#label", 80 + "type": "ref" 81 + } 82 + }, 83 + "indexedAt": { 84 + "type": "string", 85 + "format": "datetime" 86 + }, 87 + "likeCount": { 88 + "type": "integer" 89 + }, 90 + "quoteCount": { 91 + "type": "integer" 92 + }, 93 + "replyCount": { 94 + "type": "integer" 95 + }, 96 + "repostCount": { 97 + "type": "integer" 98 + } 99 + } 100 + }, 101 + "viewBlocked": { 102 + "type": "object", 103 + "required": [ 104 + "uri", 105 + "blocked", 106 + "author" 107 + ], 108 + "properties": { 109 + "uri": { 110 + "type": "string", 111 + "format": "at-uri" 112 + }, 113 + "author": { 114 + "ref": "app.bsky.feed.defs#blockedAuthor", 115 + "type": "ref" 116 + }, 117 + "blocked": { 118 + "type": "boolean", 119 + "const": true 120 + } 121 + } 122 + }, 123 + "viewDetached": { 124 + "type": "object", 125 + "required": [ 126 + "uri", 127 + "detached" 128 + ], 129 + "properties": { 130 + "uri": { 131 + "type": "string", 132 + "format": "at-uri" 133 + }, 134 + "detached": { 135 + "type": "boolean", 136 + "const": true 137 + } 138 + } 139 + }, 140 + "viewNotFound": { 141 + "type": "object", 142 + "required": [ 143 + "uri", 144 + "notFound" 145 + ], 146 + "properties": { 147 + "uri": { 148 + "type": "string", 149 + "format": "at-uri" 150 + }, 151 + "notFound": { 152 + "type": "boolean", 153 + "const": true 154 + } 155 + } 156 + } 157 + }, 158 + "$type": "com.atproto.lexicon.schema", 159 + "lexicon": 1, 160 + "description": "A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record." 161 + }
+50
lexicons/app/bsky/embed/recordWithMedia.json
··· 1 + { 2 + "id": "app.bsky.embed.recordWithMedia", 3 + "defs": { 4 + "main": { 5 + "type": "object", 6 + "required": [ 7 + "record", 8 + "media" 9 + ], 10 + "properties": { 11 + "media": { 12 + "refs": [ 13 + "app.bsky.embed.images", 14 + "app.bsky.embed.video", 15 + "app.bsky.embed.external" 16 + ], 17 + "type": "union" 18 + }, 19 + "record": { 20 + "ref": "app.bsky.embed.record", 21 + "type": "ref" 22 + } 23 + } 24 + }, 25 + "view": { 26 + "type": "object", 27 + "required": [ 28 + "record", 29 + "media" 30 + ], 31 + "properties": { 32 + "media": { 33 + "refs": [ 34 + "app.bsky.embed.images#view", 35 + "app.bsky.embed.video#view", 36 + "app.bsky.embed.external#view" 37 + ], 38 + "type": "union" 39 + }, 40 + "record": { 41 + "ref": "app.bsky.embed.record#view", 42 + "type": "ref" 43 + } 44 + } 45 + } 46 + }, 47 + "$type": "com.atproto.lexicon.schema", 48 + "lexicon": 1, 49 + "description": "A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card." 50 + }
+108
lexicons/app/bsky/embed/video.json
··· 1 + { 2 + "id": "app.bsky.embed.video", 3 + "defs": { 4 + "main": { 5 + "type": "object", 6 + "required": [ 7 + "video" 8 + ], 9 + "properties": { 10 + "alt": { 11 + "type": "string", 12 + "maxLength": 10000, 13 + "description": "Alt text description of the video, for accessibility.", 14 + "maxGraphemes": 1000 15 + }, 16 + "video": { 17 + "type": "blob", 18 + "accept": [ 19 + "video/mp4" 20 + ], 21 + "maxSize": 100000000, 22 + "description": "The mp4 video file. May be up to 100mb, formerly limited to 50mb." 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 + "presentation": { 37 + "type": "string", 38 + "description": "A hint to the client about how to present the video.", 39 + "knownValues": [ 40 + "default", 41 + "gif" 42 + ] 43 + } 44 + } 45 + }, 46 + "view": { 47 + "type": "object", 48 + "required": [ 49 + "cid", 50 + "playlist" 51 + ], 52 + "properties": { 53 + "alt": { 54 + "type": "string", 55 + "maxLength": 10000, 56 + "maxGraphemes": 1000 57 + }, 58 + "cid": { 59 + "type": "string", 60 + "format": "cid" 61 + }, 62 + "playlist": { 63 + "type": "string", 64 + "format": "uri" 65 + }, 66 + "thumbnail": { 67 + "type": "string", 68 + "format": "uri" 69 + }, 70 + "aspectRatio": { 71 + "ref": "app.bsky.embed.defs#aspectRatio", 72 + "type": "ref" 73 + }, 74 + "presentation": { 75 + "type": "string", 76 + "description": "A hint to the client about how to present the video.", 77 + "knownValues": [ 78 + "default", 79 + "gif" 80 + ] 81 + } 82 + } 83 + }, 84 + "caption": { 85 + "type": "object", 86 + "required": [ 87 + "lang", 88 + "file" 89 + ], 90 + "properties": { 91 + "file": { 92 + "type": "blob", 93 + "accept": [ 94 + "text/vtt" 95 + ], 96 + "maxSize": 20000 97 + }, 98 + "lang": { 99 + "type": "string", 100 + "format": "language" 101 + } 102 + } 103 + } 104 + }, 105 + "$type": "com.atproto.lexicon.schema", 106 + "lexicon": 1, 107 + "description": "A video embedded in a Bluesky record (eg, a post)." 108 + }
+408 -350
lexicons/app/bsky/feed/defs.json
··· 1 1 { 2 - "$type": "com.atproto.lexicon.schema", 2 + "id": "app.bsky.feed.defs", 3 3 "defs": { 4 - "blockedAuthor": { 5 - "properties": { 6 - "did": { 7 - "format": "did", 8 - "type": "string" 9 - }, 10 - "viewer": { 11 - "ref": "app.bsky.actor.defs#viewerState", 12 - "type": "ref" 13 - } 14 - }, 15 - "required": ["did"], 16 - "type": "object" 17 - }, 18 - "blockedPost": { 4 + "postView": { 5 + "type": "object", 6 + "required": [ 7 + "uri", 8 + "cid", 9 + "author", 10 + "record", 11 + "indexedAt" 12 + ], 19 13 "properties": { 20 - "author": { 21 - "ref": "#blockedAuthor", 22 - "type": "ref" 23 - }, 24 - "blocked": { 25 - "const": true, 26 - "type": "boolean" 14 + "cid": { 15 + "type": "string", 16 + "format": "cid" 27 17 }, 28 18 "uri": { 29 - "format": "at-uri", 30 - "type": "string" 31 - } 32 - }, 33 - "required": ["uri", "blocked", "author"], 34 - "type": "object" 35 - }, 36 - "clickthroughAuthor": { 37 - "description": "User clicked through to the author of the feed item", 38 - "type": "token" 39 - }, 40 - "clickthroughEmbed": { 41 - "description": "User clicked through to the embedded content of the feed item", 42 - "type": "token" 43 - }, 44 - "clickthroughItem": { 45 - "description": "User clicked through to the feed item", 46 - "type": "token" 47 - }, 48 - "clickthroughReposter": { 49 - "description": "User clicked through to the reposter of the feed item", 50 - "type": "token" 51 - }, 52 - "contentModeUnspecified": { 53 - "description": "Declares the feed generator returns any types of posts.", 54 - "type": "token" 55 - }, 56 - "contentModeVideo": { 57 - "description": "Declares the feed generator returns posts containing app.bsky.embed.video embeds.", 58 - "type": "token" 59 - }, 60 - "feedViewPost": { 61 - "properties": { 62 - "feedContext": { 63 - "description": "Context provided by feed generator that may be passed back alongside interactions.", 64 - "maxLength": 2000, 65 - "type": "string" 19 + "type": "string", 20 + "format": "at-uri" 66 21 }, 67 - "post": { 68 - "ref": "#postView", 69 - "type": "ref" 22 + "debug": { 23 + "type": "unknown", 24 + "description": "Debug information for internal development" 70 25 }, 71 - "reason": { 72 - "refs": ["#reasonRepost", "#reasonPin"], 26 + "embed": { 27 + "refs": [ 28 + "app.bsky.embed.images#view", 29 + "app.bsky.embed.video#view", 30 + "app.bsky.embed.external#view", 31 + "app.bsky.embed.record#view", 32 + "app.bsky.embed.recordWithMedia#view" 33 + ], 73 34 "type": "union" 74 35 }, 75 - "reply": { 76 - "ref": "#replyRef", 36 + "author": { 37 + "ref": "app.bsky.actor.defs#profileViewBasic", 77 38 "type": "ref" 78 39 }, 79 - "reqId": { 80 - "description": "Unique identifier per request that may be passed back alongside interactions.", 81 - "maxLength": 100, 82 - "type": "string" 83 - } 84 - }, 85 - "required": ["post"], 86 - "type": "object" 87 - }, 88 - "generatorView": { 89 - "properties": { 90 - "acceptsInteractions": { 91 - "type": "boolean" 92 - }, 93 - "avatar": { 94 - "format": "uri", 95 - "type": "string" 96 - }, 97 - "cid": { 98 - "format": "cid", 99 - "type": "string" 40 + "labels": { 41 + "type": "array", 42 + "items": { 43 + "ref": "com.atproto.label.defs#label", 44 + "type": "ref" 45 + } 100 46 }, 101 - "contentMode": { 102 - "knownValues": [ 103 - "app.bsky.feed.defs#contentModeUnspecified", 104 - "app.bsky.feed.defs#contentModeVideo" 105 - ], 106 - "type": "string" 47 + "record": { 48 + "type": "unknown" 107 49 }, 108 - "creator": { 109 - "ref": "app.bsky.actor.defs#profileView", 50 + "viewer": { 51 + "ref": "#viewerState", 110 52 "type": "ref" 111 53 }, 112 - "description": { 113 - "maxGraphemes": 300, 114 - "maxLength": 3000, 115 - "type": "string" 54 + "indexedAt": { 55 + "type": "string", 56 + "format": "datetime" 116 57 }, 117 - "descriptionFacets": { 118 - "items": { 119 - "ref": "app.bsky.richtext.facet", 120 - "type": "ref" 121 - }, 122 - "type": "array" 58 + "likeCount": { 59 + "type": "integer" 123 60 }, 124 - "did": { 125 - "format": "did", 126 - "type": "string" 61 + "quoteCount": { 62 + "type": "integer" 127 63 }, 128 - "displayName": { 129 - "type": "string" 64 + "replyCount": { 65 + "type": "integer" 130 66 }, 131 - "indexedAt": { 132 - "format": "datetime", 133 - "type": "string" 67 + "threadgate": { 68 + "ref": "#threadgateView", 69 + "type": "ref" 134 70 }, 135 - "labels": { 136 - "items": { 137 - "ref": "com.atproto.label.defs#label", 138 - "type": "ref" 139 - }, 140 - "type": "array" 71 + "repostCount": { 72 + "type": "integer" 141 73 }, 142 - "likeCount": { 143 - "minimum": 0, 74 + "bookmarkCount": { 144 75 "type": "integer" 76 + } 77 + } 78 + }, 79 + "replyRef": { 80 + "type": "object", 81 + "required": [ 82 + "root", 83 + "parent" 84 + ], 85 + "properties": { 86 + "root": { 87 + "refs": [ 88 + "#postView", 89 + "#notFoundPost", 90 + "#blockedPost" 91 + ], 92 + "type": "union" 145 93 }, 146 - "uri": { 147 - "format": "at-uri", 148 - "type": "string" 94 + "parent": { 95 + "refs": [ 96 + "#postView", 97 + "#notFoundPost", 98 + "#blockedPost" 99 + ], 100 + "type": "union" 149 101 }, 150 - "viewer": { 151 - "ref": "#generatorViewerState", 152 - "type": "ref" 102 + "grandparentAuthor": { 103 + "ref": "app.bsky.actor.defs#profileViewBasic", 104 + "type": "ref", 105 + "description": "When parent is a reply to another post, this is the author of that post." 153 106 } 154 - }, 155 - "required": ["uri", "cid", "did", "creator", "displayName", "indexedAt"], 156 - "type": "object" 107 + } 157 108 }, 158 - "generatorViewerState": { 109 + "reasonPin": { 110 + "type": "object", 111 + "properties": {} 112 + }, 113 + "blockedPost": { 114 + "type": "object", 115 + "required": [ 116 + "uri", 117 + "blocked", 118 + "author" 119 + ], 159 120 "properties": { 160 - "like": { 161 - "format": "at-uri", 162 - "type": "string" 121 + "uri": { 122 + "type": "string", 123 + "format": "at-uri" 124 + }, 125 + "author": { 126 + "ref": "#blockedAuthor", 127 + "type": "ref" 128 + }, 129 + "blocked": { 130 + "type": "boolean", 131 + "const": true 163 132 } 164 - }, 165 - "type": "object" 133 + } 166 134 }, 167 135 "interaction": { 136 + "type": "object", 168 137 "properties": { 138 + "item": { 139 + "type": "string", 140 + "format": "at-uri" 141 + }, 169 142 "event": { 143 + "type": "string", 170 144 "knownValues": [ 171 145 "app.bsky.feed.defs#requestLess", 172 146 "app.bsky.feed.defs#requestMore", ··· 180 154 "app.bsky.feed.defs#interactionReply", 181 155 "app.bsky.feed.defs#interactionQuote", 182 156 "app.bsky.feed.defs#interactionShare" 183 - ], 184 - "type": "string" 157 + ] 158 + }, 159 + "reqId": { 160 + "type": "string", 161 + "maxLength": 100, 162 + "description": "Unique identifier per request that may be passed back alongside interactions." 185 163 }, 186 164 "feedContext": { 187 - "description": "Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton.", 165 + "type": "string", 188 166 "maxLength": 2000, 189 - "type": "string" 190 - }, 191 - "item": { 192 - "format": "at-uri", 193 - "type": "string" 194 - }, 195 - "reqId": { 196 - "description": "Unique identifier per request that may be passed back alongside interactions.", 197 - "maxLength": 100, 198 - "type": "string" 167 + "description": "Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton." 199 168 } 200 - }, 201 - "type": "object" 169 + } 202 170 }, 203 - "interactionLike": { 204 - "description": "User liked the feed item", 205 - "type": "token" 171 + "requestLess": { 172 + "type": "token", 173 + "description": "Request that less content like the given feed item be shown in the feed" 206 174 }, 207 - "interactionQuote": { 208 - "description": "User quoted the feed item", 209 - "type": "token" 175 + "requestMore": { 176 + "type": "token", 177 + "description": "Request that more content like the given feed item be shown in the feed" 210 178 }, 211 - "interactionReply": { 212 - "description": "User replied to the feed item", 213 - "type": "token" 214 - }, 215 - "interactionRepost": { 216 - "description": "User reposted the feed item", 217 - "type": "token" 218 - }, 219 - "interactionSeen": { 220 - "description": "Feed item was seen by user", 221 - "type": "token" 222 - }, 223 - "interactionShare": { 224 - "description": "User shared the feed item", 225 - "type": "token" 226 - }, 227 - "notFoundPost": { 179 + "viewerState": { 180 + "type": "object", 228 181 "properties": { 229 - "notFound": { 230 - "const": true, 182 + "like": { 183 + "type": "string", 184 + "format": "at-uri" 185 + }, 186 + "pinned": { 187 + "type": "boolean" 188 + }, 189 + "repost": { 190 + "type": "string", 191 + "format": "at-uri" 192 + }, 193 + "bookmarked": { 194 + "type": "boolean" 195 + }, 196 + "threadMuted": { 197 + "type": "boolean" 198 + }, 199 + "replyDisabled": { 231 200 "type": "boolean" 232 201 }, 233 - "uri": { 234 - "format": "at-uri", 235 - "type": "string" 202 + "embeddingDisabled": { 203 + "type": "boolean" 236 204 } 237 205 }, 238 - "required": ["uri", "notFound"], 239 - "type": "object" 206 + "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests." 240 207 }, 241 - "postView": { 208 + "feedViewPost": { 209 + "type": "object", 210 + "required": [ 211 + "post" 212 + ], 242 213 "properties": { 243 - "author": { 244 - "ref": "app.bsky.actor.defs#profileViewBasic", 214 + "post": { 215 + "ref": "#postView", 245 216 "type": "ref" 246 217 }, 247 - "bookmarkCount": { 248 - "type": "integer" 218 + "reply": { 219 + "ref": "#replyRef", 220 + "type": "ref" 249 221 }, 250 - "cid": { 251 - "format": "cid", 252 - "type": "string" 222 + "reqId": { 223 + "type": "string", 224 + "maxLength": 100, 225 + "description": "Unique identifier per request that may be passed back alongside interactions." 253 226 }, 254 - "debug": { 255 - "description": "Debug information for internal development", 256 - "type": "unknown" 257 - }, 258 - "embed": { 227 + "reason": { 259 228 "refs": [ 260 - "app.bsky.embed.images#view", 261 - "app.bsky.embed.video#view", 262 - "app.bsky.embed.external#view", 263 - "app.bsky.embed.record#view", 264 - "app.bsky.embed.recordWithMedia#view" 229 + "#reasonRepost", 230 + "#reasonPin" 265 231 ], 266 232 "type": "union" 267 233 }, 268 - "indexedAt": { 269 - "format": "datetime", 270 - "type": "string" 271 - }, 272 - "labels": { 273 - "items": { 274 - "ref": "com.atproto.label.defs#label", 275 - "type": "ref" 276 - }, 277 - "type": "array" 278 - }, 279 - "likeCount": { 280 - "type": "integer" 281 - }, 282 - "quoteCount": { 283 - "type": "integer" 284 - }, 285 - "record": { 286 - "type": "unknown" 287 - }, 288 - "replyCount": { 289 - "type": "integer" 290 - }, 291 - "repostCount": { 292 - "type": "integer" 293 - }, 294 - "threadgate": { 295 - "ref": "#threadgateView", 296 - "type": "ref" 297 - }, 234 + "feedContext": { 235 + "type": "string", 236 + "maxLength": 2000, 237 + "description": "Context provided by feed generator that may be passed back alongside interactions." 238 + } 239 + } 240 + }, 241 + "notFoundPost": { 242 + "type": "object", 243 + "required": [ 244 + "uri", 245 + "notFound" 246 + ], 247 + "properties": { 298 248 "uri": { 299 - "format": "at-uri", 300 - "type": "string" 249 + "type": "string", 250 + "format": "at-uri" 301 251 }, 302 - "viewer": { 303 - "ref": "#viewerState", 304 - "type": "ref" 252 + "notFound": { 253 + "type": "boolean", 254 + "const": true 305 255 } 306 - }, 307 - "required": ["uri", "cid", "author", "record", "indexedAt"], 308 - "type": "object" 309 - }, 310 - "reasonPin": { 311 - "properties": {}, 312 - "type": "object" 256 + } 313 257 }, 314 258 "reasonRepost": { 259 + "type": "object", 260 + "required": [ 261 + "by", 262 + "indexedAt" 263 + ], 315 264 "properties": { 316 265 "by": { 317 266 "ref": "app.bsky.actor.defs#profileViewBasic", 318 267 "type": "ref" 319 268 }, 320 269 "cid": { 321 - "format": "cid", 322 - "type": "string" 323 - }, 324 - "indexedAt": { 325 - "format": "datetime", 326 - "type": "string" 270 + "type": "string", 271 + "format": "cid" 327 272 }, 328 273 "uri": { 329 - "format": "at-uri", 330 - "type": "string" 274 + "type": "string", 275 + "format": "at-uri" 276 + }, 277 + "indexedAt": { 278 + "type": "string", 279 + "format": "datetime" 331 280 } 332 - }, 333 - "required": ["by", "indexedAt"], 334 - "type": "object" 281 + } 335 282 }, 336 - "replyRef": { 283 + "blockedAuthor": { 284 + "type": "object", 285 + "required": [ 286 + "did" 287 + ], 337 288 "properties": { 338 - "grandparentAuthor": { 339 - "description": "When parent is a reply to another post, this is the author of that post.", 340 - "ref": "app.bsky.actor.defs#profileViewBasic", 341 - "type": "ref" 289 + "did": { 290 + "type": "string", 291 + "format": "did" 342 292 }, 343 - "parent": { 344 - "refs": ["#postView", "#notFoundPost", "#blockedPost"], 345 - "type": "union" 346 - }, 347 - "root": { 348 - "refs": ["#postView", "#notFoundPost", "#blockedPost"], 349 - "type": "union" 293 + "viewer": { 294 + "ref": "app.bsky.actor.defs#viewerState", 295 + "type": "ref" 350 296 } 351 - }, 352 - "required": ["root", "parent"], 353 - "type": "object" 354 - }, 355 - "requestLess": { 356 - "description": "Request that less content like the given feed item be shown in the feed", 357 - "type": "token" 358 - }, 359 - "requestMore": { 360 - "description": "Request that more content like the given feed item be shown in the feed", 361 - "type": "token" 297 + } 362 298 }, 363 - "skeletonFeedPost": { 299 + "generatorView": { 300 + "type": "object", 301 + "required": [ 302 + "uri", 303 + "cid", 304 + "did", 305 + "creator", 306 + "displayName", 307 + "indexedAt" 308 + ], 364 309 "properties": { 365 - "feedContext": { 366 - "description": "Context that will be passed through to client and may be passed to feed generator back alongside interactions.", 367 - "maxLength": 2000, 310 + "cid": { 311 + "type": "string", 312 + "format": "cid" 313 + }, 314 + "did": { 315 + "type": "string", 316 + "format": "did" 317 + }, 318 + "uri": { 319 + "type": "string", 320 + "format": "at-uri" 321 + }, 322 + "avatar": { 323 + "type": "string", 324 + "format": "uri" 325 + }, 326 + "labels": { 327 + "type": "array", 328 + "items": { 329 + "ref": "com.atproto.label.defs#label", 330 + "type": "ref" 331 + } 332 + }, 333 + "viewer": { 334 + "ref": "#generatorViewerState", 335 + "type": "ref" 336 + }, 337 + "creator": { 338 + "ref": "app.bsky.actor.defs#profileView", 339 + "type": "ref" 340 + }, 341 + "indexedAt": { 342 + "type": "string", 343 + "format": "datetime" 344 + }, 345 + "likeCount": { 346 + "type": "integer", 347 + "minimum": 0 348 + }, 349 + "contentMode": { 350 + "type": "string", 351 + "knownValues": [ 352 + "app.bsky.feed.defs#contentModeUnspecified", 353 + "app.bsky.feed.defs#contentModeVideo" 354 + ] 355 + }, 356 + "description": { 357 + "type": "string", 358 + "maxLength": 3000, 359 + "maxGraphemes": 300 360 + }, 361 + "displayName": { 368 362 "type": "string" 369 363 }, 370 - "post": { 371 - "format": "at-uri", 372 - "type": "string" 364 + "descriptionFacets": { 365 + "type": "array", 366 + "items": { 367 + "ref": "app.bsky.richtext.facet", 368 + "type": "ref" 369 + } 373 370 }, 374 - "reason": { 375 - "refs": ["#skeletonReasonRepost", "#skeletonReasonPin"], 376 - "type": "union" 371 + "acceptsInteractions": { 372 + "type": "boolean" 377 373 } 378 - }, 379 - "required": ["post"], 380 - "type": "object" 381 - }, 382 - "skeletonReasonPin": { 383 - "properties": {}, 384 - "type": "object" 385 - }, 386 - "skeletonReasonRepost": { 387 - "properties": { 388 - "repost": { 389 - "format": "at-uri", 390 - "type": "string" 391 - } 392 - }, 393 - "required": ["repost"], 394 - "type": "object" 374 + } 395 375 }, 396 376 "threadContext": { 397 - "description": "Metadata about this post within the context of the thread it is in.", 377 + "type": "object", 398 378 "properties": { 399 379 "rootAuthorLike": { 400 - "format": "at-uri", 401 - "type": "string" 380 + "type": "string", 381 + "format": "at-uri" 402 382 } 403 383 }, 404 - "type": "object" 384 + "description": "Metadata about this post within the context of the thread it is in." 405 385 }, 406 386 "threadViewPost": { 387 + "type": "object", 388 + "required": [ 389 + "post" 390 + ], 407 391 "properties": { 408 - "parent": { 409 - "refs": ["#threadViewPost", "#notFoundPost", "#blockedPost"], 410 - "type": "union" 411 - }, 412 392 "post": { 413 393 "ref": "#postView", 414 394 "type": "ref" 415 395 }, 396 + "parent": { 397 + "refs": [ 398 + "#threadViewPost", 399 + "#notFoundPost", 400 + "#blockedPost" 401 + ], 402 + "type": "union" 403 + }, 416 404 "replies": { 405 + "type": "array", 417 406 "items": { 418 - "refs": ["#threadViewPost", "#notFoundPost", "#blockedPost"], 407 + "refs": [ 408 + "#threadViewPost", 409 + "#notFoundPost", 410 + "#blockedPost" 411 + ], 419 412 "type": "union" 420 - }, 421 - "type": "array" 413 + } 422 414 }, 423 415 "threadContext": { 424 416 "ref": "#threadContext", 425 417 "type": "ref" 426 418 } 427 - }, 428 - "required": ["post"], 429 - "type": "object" 419 + } 430 420 }, 431 421 "threadgateView": { 422 + "type": "object", 432 423 "properties": { 433 424 "cid": { 434 - "format": "cid", 435 - "type": "string" 425 + "type": "string", 426 + "format": "cid" 427 + }, 428 + "uri": { 429 + "type": "string", 430 + "format": "at-uri" 436 431 }, 437 432 "lists": { 433 + "type": "array", 438 434 "items": { 439 435 "ref": "app.bsky.graph.defs#listViewBasic", 440 436 "type": "ref" 441 - }, 442 - "type": "array" 437 + } 443 438 }, 444 439 "record": { 445 440 "type": "unknown" 446 - }, 447 - "uri": { 448 - "format": "at-uri", 449 - "type": "string" 450 441 } 451 - }, 452 - "type": "object" 442 + } 443 + }, 444 + "interactionLike": { 445 + "type": "token", 446 + "description": "User liked the feed item" 447 + }, 448 + "interactionSeen": { 449 + "type": "token", 450 + "description": "Feed item was seen by user" 451 + }, 452 + "clickthroughItem": { 453 + "type": "token", 454 + "description": "User clicked through to the feed item" 455 + }, 456 + "contentModeVideo": { 457 + "type": "token", 458 + "description": "Declares the feed generator returns posts containing app.bsky.embed.video embeds." 459 + }, 460 + "interactionQuote": { 461 + "type": "token", 462 + "description": "User quoted the feed item" 463 + }, 464 + "interactionReply": { 465 + "type": "token", 466 + "description": "User replied to the feed item" 453 467 }, 454 - "viewerState": { 455 - "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", 468 + "interactionShare": { 469 + "type": "token", 470 + "description": "User shared the feed item" 471 + }, 472 + "skeletonFeedPost": { 473 + "type": "object", 474 + "required": [ 475 + "post" 476 + ], 456 477 "properties": { 457 - "bookmarked": { 458 - "type": "boolean" 478 + "post": { 479 + "type": "string", 480 + "format": "at-uri" 459 481 }, 460 - "embeddingDisabled": { 461 - "type": "boolean" 482 + "reason": { 483 + "refs": [ 484 + "#skeletonReasonRepost", 485 + "#skeletonReasonPin" 486 + ], 487 + "type": "union" 462 488 }, 489 + "feedContext": { 490 + "type": "string", 491 + "maxLength": 2000, 492 + "description": "Context that will be passed through to client and may be passed to feed generator back alongside interactions." 493 + } 494 + } 495 + }, 496 + "clickthroughEmbed": { 497 + "type": "token", 498 + "description": "User clicked through to the embedded content of the feed item" 499 + }, 500 + "interactionRepost": { 501 + "type": "token", 502 + "description": "User reposted the feed item" 503 + }, 504 + "skeletonReasonPin": { 505 + "type": "object", 506 + "properties": {} 507 + }, 508 + "clickthroughAuthor": { 509 + "type": "token", 510 + "description": "User clicked through to the author of the feed item" 511 + }, 512 + "clickthroughReposter": { 513 + "type": "token", 514 + "description": "User clicked through to the reposter of the feed item" 515 + }, 516 + "generatorViewerState": { 517 + "type": "object", 518 + "properties": { 463 519 "like": { 464 - "format": "at-uri", 465 - "type": "string" 466 - }, 467 - "pinned": { 468 - "type": "boolean" 469 - }, 470 - "replyDisabled": { 471 - "type": "boolean" 472 - }, 520 + "type": "string", 521 + "format": "at-uri" 522 + } 523 + } 524 + }, 525 + "skeletonReasonRepost": { 526 + "type": "object", 527 + "required": [ 528 + "repost" 529 + ], 530 + "properties": { 473 531 "repost": { 474 - "format": "at-uri", 475 - "type": "string" 476 - }, 477 - "threadMuted": { 478 - "type": "boolean" 532 + "type": "string", 533 + "format": "at-uri" 479 534 } 480 - }, 481 - "type": "object" 535 + } 536 + }, 537 + "contentModeUnspecified": { 538 + "type": "token", 539 + "description": "Declares the feed generator returns any types of posts." 482 540 } 483 541 }, 484 - "id": "app.bsky.feed.defs", 542 + "$type": "com.atproto.lexicon.schema", 485 543 "lexicon": 1 486 544 }
+145
lexicons/app/bsky/feed/post.json
··· 1 + { 2 + "id": "app.bsky.feed.post", 3 + "defs": { 4 + "main": { 5 + "key": "tid", 6 + "type": "record", 7 + "record": { 8 + "type": "object", 9 + "required": [ 10 + "text", 11 + "createdAt" 12 + ], 13 + "properties": { 14 + "tags": { 15 + "type": "array", 16 + "items": { 17 + "type": "string", 18 + "maxLength": 640, 19 + "maxGraphemes": 64 20 + }, 21 + "maxLength": 8, 22 + "description": "Additional hashtags, in addition to any included in post text and facets." 23 + }, 24 + "text": { 25 + "type": "string", 26 + "maxLength": 3000, 27 + "description": "The primary post content. May be an empty string, if there are embeds.", 28 + "maxGraphemes": 300 29 + }, 30 + "embed": { 31 + "refs": [ 32 + "app.bsky.embed.images", 33 + "app.bsky.embed.video", 34 + "app.bsky.embed.external", 35 + "app.bsky.embed.record", 36 + "app.bsky.embed.recordWithMedia" 37 + ], 38 + "type": "union" 39 + }, 40 + "langs": { 41 + "type": "array", 42 + "items": { 43 + "type": "string", 44 + "format": "language" 45 + }, 46 + "maxLength": 3, 47 + "description": "Indicates human language of post primary text content." 48 + }, 49 + "reply": { 50 + "ref": "#replyRef", 51 + "type": "ref" 52 + }, 53 + "facets": { 54 + "type": "array", 55 + "items": { 56 + "ref": "app.bsky.richtext.facet", 57 + "type": "ref" 58 + }, 59 + "description": "Annotations of text (mentions, URLs, hashtags, etc)" 60 + }, 61 + "labels": { 62 + "refs": [ 63 + "com.atproto.label.defs#selfLabels" 64 + ], 65 + "type": "union", 66 + "description": "Self-label values for this post. Effectively content warnings." 67 + }, 68 + "entities": { 69 + "type": "array", 70 + "items": { 71 + "ref": "#entity", 72 + "type": "ref" 73 + }, 74 + "description": "DEPRECATED: replaced by app.bsky.richtext.facet." 75 + }, 76 + "createdAt": { 77 + "type": "string", 78 + "format": "datetime", 79 + "description": "Client-declared timestamp when this post was originally created." 80 + } 81 + } 82 + }, 83 + "description": "Record containing a Bluesky post." 84 + }, 85 + "entity": { 86 + "type": "object", 87 + "required": [ 88 + "index", 89 + "type", 90 + "value" 91 + ], 92 + "properties": { 93 + "type": { 94 + "type": "string", 95 + "description": "Expected values are 'mention' and 'link'." 96 + }, 97 + "index": { 98 + "ref": "#textSlice", 99 + "type": "ref" 100 + }, 101 + "value": { 102 + "type": "string" 103 + } 104 + }, 105 + "description": "Deprecated: use facets instead." 106 + }, 107 + "replyRef": { 108 + "type": "object", 109 + "required": [ 110 + "root", 111 + "parent" 112 + ], 113 + "properties": { 114 + "root": { 115 + "ref": "com.atproto.repo.strongRef", 116 + "type": "ref" 117 + }, 118 + "parent": { 119 + "ref": "com.atproto.repo.strongRef", 120 + "type": "ref" 121 + } 122 + } 123 + }, 124 + "textSlice": { 125 + "type": "object", 126 + "required": [ 127 + "start", 128 + "end" 129 + ], 130 + "properties": { 131 + "end": { 132 + "type": "integer", 133 + "minimum": 0 134 + }, 135 + "start": { 136 + "type": "integer", 137 + "minimum": 0 138 + } 139 + }, 140 + "description": "Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings." 141 + } 142 + }, 143 + "$type": "com.atproto.lexicon.schema", 144 + "lexicon": 1 145 + }
+55
lexicons/app/bsky/feed/postgate.json
··· 1 + { 2 + "id": "app.bsky.feed.postgate", 3 + "defs": { 4 + "main": { 5 + "key": "tid", 6 + "type": "record", 7 + "record": { 8 + "type": "object", 9 + "required": [ 10 + "post", 11 + "createdAt" 12 + ], 13 + "properties": { 14 + "post": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "Reference (AT-URI) to the post record." 18 + }, 19 + "createdAt": { 20 + "type": "string", 21 + "format": "datetime" 22 + }, 23 + "embeddingRules": { 24 + "type": "array", 25 + "items": { 26 + "refs": [ 27 + "#disableRule" 28 + ], 29 + "type": "union" 30 + }, 31 + "maxLength": 5, 32 + "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." 33 + }, 34 + "detachedEmbeddingUris": { 35 + "type": "array", 36 + "items": { 37 + "type": "string", 38 + "format": "at-uri" 39 + }, 40 + "maxLength": 50, 41 + "description": "List of AT-URIs embedding this post that the author has detached from." 42 + } 43 + } 44 + }, 45 + "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." 46 + }, 47 + "disableRule": { 48 + "type": "object", 49 + "properties": {}, 50 + "description": "Disables embedding of this post." 51 + } 52 + }, 53 + "$type": "com.atproto.lexicon.schema", 54 + "lexicon": 1 55 + }
+81
lexicons/app/bsky/feed/threadgate.json
··· 1 + { 2 + "id": "app.bsky.feed.threadgate", 3 + "defs": { 4 + "main": { 5 + "key": "tid", 6 + "type": "record", 7 + "record": { 8 + "type": "object", 9 + "required": [ 10 + "post", 11 + "createdAt" 12 + ], 13 + "properties": { 14 + "post": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "Reference (AT-URI) to the post record." 18 + }, 19 + "allow": { 20 + "type": "array", 21 + "items": { 22 + "refs": [ 23 + "#mentionRule", 24 + "#followerRule", 25 + "#followingRule", 26 + "#listRule" 27 + ], 28 + "type": "union" 29 + }, 30 + "maxLength": 5, 31 + "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." 32 + }, 33 + "createdAt": { 34 + "type": "string", 35 + "format": "datetime" 36 + }, 37 + "hiddenReplies": { 38 + "type": "array", 39 + "items": { 40 + "type": "string", 41 + "format": "at-uri" 42 + }, 43 + "maxLength": 300, 44 + "description": "List of hidden reply URIs." 45 + } 46 + } 47 + }, 48 + "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." 49 + }, 50 + "listRule": { 51 + "type": "object", 52 + "required": [ 53 + "list" 54 + ], 55 + "properties": { 56 + "list": { 57 + "type": "string", 58 + "format": "at-uri" 59 + } 60 + }, 61 + "description": "Allow replies from actors on a list." 62 + }, 63 + "mentionRule": { 64 + "type": "object", 65 + "properties": {}, 66 + "description": "Allow replies from actors mentioned in your post." 67 + }, 68 + "followerRule": { 69 + "type": "object", 70 + "properties": {}, 71 + "description": "Allow replies from actors who follow you." 72 + }, 73 + "followingRule": { 74 + "type": "object", 75 + "properties": {}, 76 + "description": "Allow replies from actors you follow." 77 + } 78 + }, 79 + "$type": "com.atproto.lexicon.schema", 80 + "lexicon": 1 81 + }
+245 -213
lexicons/app/bsky/graph/defs.json
··· 1 1 { 2 - "$type": "com.atproto.lexicon.schema", 2 + "id": "app.bsky.graph.defs", 3 3 "defs": { 4 - "curatelist": { 5 - "description": "A list of actors used for curation purposes such as list feeds or interaction gating.", 6 - "type": "token" 4 + "modlist": { 5 + "type": "token", 6 + "description": "A list of actors to apply an aggregate moderation action (mute/block) on." 7 7 }, 8 - "listItemView": { 8 + "listView": { 9 + "type": "object", 10 + "required": [ 11 + "uri", 12 + "cid", 13 + "creator", 14 + "name", 15 + "purpose", 16 + "indexedAt" 17 + ], 9 18 "properties": { 10 - "subject": { 11 - "ref": "app.bsky.actor.defs#profileView", 12 - "type": "ref" 19 + "cid": { 20 + "type": "string", 21 + "format": "cid" 13 22 }, 14 23 "uri": { 15 - "format": "at-uri", 16 - "type": "string" 17 - } 18 - }, 19 - "required": ["uri", "subject"], 20 - "type": "object" 21 - }, 22 - "listPurpose": { 23 - "knownValues": [ 24 - "app.bsky.graph.defs#modlist", 25 - "app.bsky.graph.defs#curatelist", 26 - "app.bsky.graph.defs#referencelist" 27 - ], 28 - "type": "string" 29 - }, 30 - "listView": { 31 - "properties": { 24 + "type": "string", 25 + "format": "at-uri" 26 + }, 27 + "name": { 28 + "type": "string", 29 + "maxLength": 64, 30 + "minLength": 1 31 + }, 32 32 "avatar": { 33 - "format": "uri", 34 - "type": "string" 33 + "type": "string", 34 + "format": "uri" 35 + }, 36 + "labels": { 37 + "type": "array", 38 + "items": { 39 + "ref": "com.atproto.label.defs#label", 40 + "type": "ref" 41 + } 35 42 }, 36 - "cid": { 37 - "format": "cid", 38 - "type": "string" 43 + "viewer": { 44 + "ref": "#listViewerState", 45 + "type": "ref" 39 46 }, 40 47 "creator": { 41 48 "ref": "app.bsky.actor.defs#profileView", 42 49 "type": "ref" 43 50 }, 51 + "purpose": { 52 + "ref": "#listPurpose", 53 + "type": "ref" 54 + }, 55 + "indexedAt": { 56 + "type": "string", 57 + "format": "datetime" 58 + }, 44 59 "description": { 45 - "maxGraphemes": 300, 60 + "type": "string", 46 61 "maxLength": 3000, 47 - "type": "string" 62 + "maxGraphemes": 300 63 + }, 64 + "listItemCount": { 65 + "type": "integer", 66 + "minimum": 0 48 67 }, 49 68 "descriptionFacets": { 69 + "type": "array", 50 70 "items": { 51 71 "ref": "app.bsky.richtext.facet", 52 72 "type": "ref" 53 - }, 54 - "type": "array" 73 + } 74 + } 75 + } 76 + }, 77 + "curatelist": { 78 + "type": "token", 79 + "description": "A list of actors used for curation purposes such as list feeds or interaction gating." 80 + }, 81 + "listPurpose": { 82 + "type": "string", 83 + "knownValues": [ 84 + "app.bsky.graph.defs#modlist", 85 + "app.bsky.graph.defs#curatelist", 86 + "app.bsky.graph.defs#referencelist" 87 + ] 88 + }, 89 + "listItemView": { 90 + "type": "object", 91 + "required": [ 92 + "uri", 93 + "subject" 94 + ], 95 + "properties": { 96 + "uri": { 97 + "type": "string", 98 + "format": "at-uri" 55 99 }, 56 - "indexedAt": { 57 - "format": "datetime", 58 - "type": "string" 100 + "subject": { 101 + "ref": "app.bsky.actor.defs#profileView", 102 + "type": "ref" 103 + } 104 + } 105 + }, 106 + "relationship": { 107 + "type": "object", 108 + "required": [ 109 + "did" 110 + ], 111 + "properties": { 112 + "did": { 113 + "type": "string", 114 + "format": "did" 59 115 }, 60 - "labels": { 61 - "items": { 62 - "ref": "com.atproto.label.defs#label", 63 - "type": "ref" 64 - }, 65 - "type": "array" 116 + "blocking": { 117 + "type": "string", 118 + "format": "at-uri", 119 + "description": "if the actor blocks this DID, this is the AT-URI of the block record" 66 120 }, 67 - "listItemCount": { 68 - "minimum": 0, 69 - "type": "integer" 121 + "blockedBy": { 122 + "type": "string", 123 + "format": "at-uri", 124 + "description": "if the actor is blocked by this DID, contains the AT-URI of the block record" 70 125 }, 71 - "name": { 72 - "maxLength": 64, 73 - "minLength": 1, 74 - "type": "string" 126 + "following": { 127 + "type": "string", 128 + "format": "at-uri", 129 + "description": "if the actor follows this DID, this is the AT-URI of the follow record" 75 130 }, 76 - "purpose": { 77 - "ref": "#listPurpose", 78 - "type": "ref" 131 + "followedBy": { 132 + "type": "string", 133 + "format": "at-uri", 134 + "description": "if the actor is followed by this DID, contains the AT-URI of the follow record" 79 135 }, 80 - "uri": { 136 + "blockedByList": { 137 + "type": "string", 81 138 "format": "at-uri", 82 - "type": "string" 139 + "description": "if the actor is blocked by this DID via a block list, contains the AT-URI of the listblock record" 83 140 }, 84 - "viewer": { 85 - "ref": "#listViewerState", 86 - "type": "ref" 141 + "blockingByList": { 142 + "type": "string", 143 + "format": "at-uri", 144 + "description": "if the actor blocks this DID via a block list, this is the AT-URI of the listblock record" 87 145 } 88 146 }, 89 - "required": ["uri", "cid", "creator", "name", "purpose", "indexedAt"], 90 - "type": "object" 147 + "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)" 91 148 }, 92 149 "listViewBasic": { 150 + "type": "object", 151 + "required": [ 152 + "uri", 153 + "cid", 154 + "name", 155 + "purpose" 156 + ], 93 157 "properties": { 94 - "avatar": { 95 - "format": "uri", 96 - "type": "string" 158 + "cid": { 159 + "type": "string", 160 + "format": "cid" 97 161 }, 98 - "cid": { 99 - "format": "cid", 100 - "type": "string" 162 + "uri": { 163 + "type": "string", 164 + "format": "at-uri" 165 + }, 166 + "name": { 167 + "type": "string", 168 + "maxLength": 64, 169 + "minLength": 1 101 170 }, 102 - "indexedAt": { 103 - "format": "datetime", 104 - "type": "string" 171 + "avatar": { 172 + "type": "string", 173 + "format": "uri" 105 174 }, 106 175 "labels": { 176 + "type": "array", 107 177 "items": { 108 178 "ref": "com.atproto.label.defs#label", 109 179 "type": "ref" 110 - }, 111 - "type": "array" 112 - }, 113 - "listItemCount": { 114 - "minimum": 0, 115 - "type": "integer" 180 + } 116 181 }, 117 - "name": { 118 - "maxLength": 64, 119 - "minLength": 1, 120 - "type": "string" 182 + "viewer": { 183 + "ref": "#listViewerState", 184 + "type": "ref" 121 185 }, 122 186 "purpose": { 123 187 "ref": "#listPurpose", 124 188 "type": "ref" 125 189 }, 126 - "uri": { 127 - "format": "at-uri", 128 - "type": "string" 129 - }, 130 - "viewer": { 131 - "ref": "#listViewerState", 132 - "type": "ref" 133 - } 134 - }, 135 - "required": ["uri", "cid", "name", "purpose"], 136 - "type": "object" 137 - }, 138 - "listViewerState": { 139 - "properties": { 140 - "blocked": { 141 - "format": "at-uri", 142 - "type": "string" 190 + "indexedAt": { 191 + "type": "string", 192 + "format": "datetime" 143 193 }, 144 - "muted": { 145 - "type": "boolean" 194 + "listItemCount": { 195 + "type": "integer", 196 + "minimum": 0 146 197 } 147 - }, 148 - "type": "object" 149 - }, 150 - "modlist": { 151 - "description": "A list of actors to apply an aggregate moderation action (mute/block) on.", 152 - "type": "token" 198 + } 153 199 }, 154 200 "notFoundActor": { 155 - "description": "indicates that a handle or DID could not be resolved", 201 + "type": "object", 202 + "required": [ 203 + "actor", 204 + "notFound" 205 + ], 156 206 "properties": { 157 207 "actor": { 158 - "format": "at-identifier", 159 - "type": "string" 208 + "type": "string", 209 + "format": "at-identifier" 160 210 }, 161 211 "notFound": { 162 - "const": true, 163 - "type": "boolean" 212 + "type": "boolean", 213 + "const": true 164 214 } 165 215 }, 166 - "required": ["actor", "notFound"], 167 - "type": "object" 216 + "description": "indicates that a handle or DID could not be resolved" 168 217 }, 169 218 "referencelist": { 170 - "description": "A list of actors used for only for reference purposes such as within a starter pack.", 171 - "type": "token" 219 + "type": "token", 220 + "description": "A list of actors used for only for reference purposes such as within a starter pack." 172 221 }, 173 - "relationship": { 174 - "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)", 222 + "listViewerState": { 223 + "type": "object", 175 224 "properties": { 176 - "blockedBy": { 177 - "description": "if the actor is blocked by this DID, contains the AT-URI of the block record", 178 - "format": "at-uri", 179 - "type": "string" 225 + "muted": { 226 + "type": "boolean" 180 227 }, 181 - "blockedByList": { 182 - "description": "if the actor is blocked by this DID via a block list, contains the AT-URI of the listblock record", 183 - "format": "at-uri", 184 - "type": "string" 185 - }, 186 - "blocking": { 187 - "description": "if the actor blocks this DID, this is the AT-URI of the block record", 188 - "format": "at-uri", 189 - "type": "string" 190 - }, 191 - "blockingByList": { 192 - "description": "if the actor blocks this DID via a block list, this is the AT-URI of the listblock record", 193 - "format": "at-uri", 194 - "type": "string" 195 - }, 196 - "did": { 197 - "format": "did", 198 - "type": "string" 199 - }, 200 - "followedBy": { 201 - "description": "if the actor is followed by this DID, contains the AT-URI of the follow record", 202 - "format": "at-uri", 203 - "type": "string" 204 - }, 205 - "following": { 206 - "description": "if the actor follows this DID, this is the AT-URI of the follow record", 207 - "format": "at-uri", 208 - "type": "string" 228 + "blocked": { 229 + "type": "string", 230 + "format": "at-uri" 209 231 } 210 - }, 211 - "required": ["did"], 212 - "type": "object" 232 + } 213 233 }, 214 234 "starterPackView": { 235 + "type": "object", 236 + "required": [ 237 + "uri", 238 + "cid", 239 + "record", 240 + "creator", 241 + "indexedAt" 242 + ], 215 243 "properties": { 216 244 "cid": { 217 - "format": "cid", 218 - "type": "string" 245 + "type": "string", 246 + "format": "cid" 219 247 }, 220 - "creator": { 221 - "ref": "app.bsky.actor.defs#profileViewBasic", 248 + "uri": { 249 + "type": "string", 250 + "format": "at-uri" 251 + }, 252 + "list": { 253 + "ref": "#listViewBasic", 222 254 "type": "ref" 223 255 }, 224 256 "feeds": { 257 + "type": "array", 225 258 "items": { 226 259 "ref": "app.bsky.feed.defs#generatorView", 227 260 "type": "ref" 228 261 }, 229 - "maxLength": 3, 230 - "type": "array" 231 - }, 232 - "indexedAt": { 233 - "format": "datetime", 234 - "type": "string" 235 - }, 236 - "joinedAllTimeCount": { 237 - "minimum": 0, 238 - "type": "integer" 239 - }, 240 - "joinedWeekCount": { 241 - "minimum": 0, 242 - "type": "integer" 262 + "maxLength": 3 243 263 }, 244 264 "labels": { 265 + "type": "array", 245 266 "items": { 246 267 "ref": "com.atproto.label.defs#label", 247 268 "type": "ref" 248 - }, 249 - "type": "array" 269 + } 270 + }, 271 + "record": { 272 + "type": "unknown" 250 273 }, 251 - "list": { 252 - "ref": "#listViewBasic", 274 + "creator": { 275 + "ref": "app.bsky.actor.defs#profileViewBasic", 253 276 "type": "ref" 254 277 }, 278 + "indexedAt": { 279 + "type": "string", 280 + "format": "datetime" 281 + }, 282 + "joinedWeekCount": { 283 + "type": "integer", 284 + "minimum": 0 285 + }, 255 286 "listItemsSample": { 287 + "type": "array", 256 288 "items": { 257 289 "ref": "#listItemView", 258 290 "type": "ref" 259 291 }, 260 - "maxLength": 12, 261 - "type": "array" 262 - }, 263 - "record": { 264 - "type": "unknown" 292 + "maxLength": 12 265 293 }, 266 - "uri": { 267 - "format": "at-uri", 268 - "type": "string" 294 + "joinedAllTimeCount": { 295 + "type": "integer", 296 + "minimum": 0 269 297 } 270 - }, 271 - "required": ["uri", "cid", "record", "creator", "indexedAt"], 272 - "type": "object" 298 + } 273 299 }, 274 300 "starterPackViewBasic": { 301 + "type": "object", 302 + "required": [ 303 + "uri", 304 + "cid", 305 + "record", 306 + "creator", 307 + "indexedAt" 308 + ], 275 309 "properties": { 276 310 "cid": { 277 - "format": "cid", 278 - "type": "string" 311 + "type": "string", 312 + "format": "cid" 313 + }, 314 + "uri": { 315 + "type": "string", 316 + "format": "at-uri" 317 + }, 318 + "labels": { 319 + "type": "array", 320 + "items": { 321 + "ref": "com.atproto.label.defs#label", 322 + "type": "ref" 323 + } 324 + }, 325 + "record": { 326 + "type": "unknown" 279 327 }, 280 328 "creator": { 281 329 "ref": "app.bsky.actor.defs#profileViewBasic", 282 330 "type": "ref" 283 331 }, 284 332 "indexedAt": { 285 - "format": "datetime", 286 - "type": "string" 333 + "type": "string", 334 + "format": "datetime" 287 335 }, 288 - "joinedAllTimeCount": { 289 - "minimum": 0, 290 - "type": "integer" 336 + "listItemCount": { 337 + "type": "integer", 338 + "minimum": 0 291 339 }, 292 340 "joinedWeekCount": { 293 - "minimum": 0, 294 - "type": "integer" 341 + "type": "integer", 342 + "minimum": 0 295 343 }, 296 - "labels": { 297 - "items": { 298 - "ref": "com.atproto.label.defs#label", 299 - "type": "ref" 300 - }, 301 - "type": "array" 302 - }, 303 - "listItemCount": { 304 - "minimum": 0, 305 - "type": "integer" 306 - }, 307 - "record": { 308 - "type": "unknown" 309 - }, 310 - "uri": { 311 - "format": "at-uri", 312 - "type": "string" 344 + "joinedAllTimeCount": { 345 + "type": "integer", 346 + "minimum": 0 313 347 } 314 - }, 315 - "required": ["uri", "cid", "record", "creator", "indexedAt"], 316 - "type": "object" 348 + } 317 349 } 318 350 }, 319 - "id": "app.bsky.graph.defs", 351 + "$type": "com.atproto.lexicon.schema", 320 352 "lexicon": 1 321 353 }
+153
lexicons/app/bsky/labeler/defs.json
··· 1 + { 2 + "id": "app.bsky.labeler.defs", 3 + "defs": { 4 + "labelerView": { 5 + "type": "object", 6 + "required": [ 7 + "uri", 8 + "cid", 9 + "creator", 10 + "indexedAt" 11 + ], 12 + "properties": { 13 + "cid": { 14 + "type": "string", 15 + "format": "cid" 16 + }, 17 + "uri": { 18 + "type": "string", 19 + "format": "at-uri" 20 + }, 21 + "labels": { 22 + "type": "array", 23 + "items": { 24 + "ref": "com.atproto.label.defs#label", 25 + "type": "ref" 26 + } 27 + }, 28 + "viewer": { 29 + "ref": "#labelerViewerState", 30 + "type": "ref" 31 + }, 32 + "creator": { 33 + "ref": "app.bsky.actor.defs#profileView", 34 + "type": "ref" 35 + }, 36 + "indexedAt": { 37 + "type": "string", 38 + "format": "datetime" 39 + }, 40 + "likeCount": { 41 + "type": "integer", 42 + "minimum": 0 43 + } 44 + } 45 + }, 46 + "labelerPolicies": { 47 + "type": "object", 48 + "required": [ 49 + "labelValues" 50 + ], 51 + "properties": { 52 + "labelValues": { 53 + "type": "array", 54 + "items": { 55 + "ref": "com.atproto.label.defs#labelValue", 56 + "type": "ref" 57 + }, 58 + "description": "The label values which this labeler publishes. May include global or custom labels." 59 + }, 60 + "labelValueDefinitions": { 61 + "type": "array", 62 + "items": { 63 + "ref": "com.atproto.label.defs#labelValueDefinition", 64 + "type": "ref" 65 + }, 66 + "description": "Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler." 67 + } 68 + } 69 + }, 70 + "labelerViewerState": { 71 + "type": "object", 72 + "properties": { 73 + "like": { 74 + "type": "string", 75 + "format": "at-uri" 76 + } 77 + } 78 + }, 79 + "labelerViewDetailed": { 80 + "type": "object", 81 + "required": [ 82 + "uri", 83 + "cid", 84 + "creator", 85 + "policies", 86 + "indexedAt" 87 + ], 88 + "properties": { 89 + "cid": { 90 + "type": "string", 91 + "format": "cid" 92 + }, 93 + "uri": { 94 + "type": "string", 95 + "format": "at-uri" 96 + }, 97 + "labels": { 98 + "type": "array", 99 + "items": { 100 + "ref": "com.atproto.label.defs#label", 101 + "type": "ref" 102 + } 103 + }, 104 + "viewer": { 105 + "ref": "#labelerViewerState", 106 + "type": "ref" 107 + }, 108 + "creator": { 109 + "ref": "app.bsky.actor.defs#profileView", 110 + "type": "ref" 111 + }, 112 + "policies": { 113 + "ref": "app.bsky.labeler.defs#labelerPolicies", 114 + "type": "ref" 115 + }, 116 + "indexedAt": { 117 + "type": "string", 118 + "format": "datetime" 119 + }, 120 + "likeCount": { 121 + "type": "integer", 122 + "minimum": 0 123 + }, 124 + "reasonTypes": { 125 + "type": "array", 126 + "items": { 127 + "ref": "com.atproto.moderation.defs#reasonType", 128 + "type": "ref" 129 + }, 130 + "description": "The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed." 131 + }, 132 + "subjectTypes": { 133 + "type": "array", 134 + "items": { 135 + "ref": "com.atproto.moderation.defs#subjectType", 136 + "type": "ref" 137 + }, 138 + "description": "The set of subject types (account, record, etc) this service accepts reports on." 139 + }, 140 + "subjectCollections": { 141 + "type": "array", 142 + "items": { 143 + "type": "string", 144 + "format": "nsid" 145 + }, 146 + "description": "Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type." 147 + } 148 + } 149 + } 150 + }, 151 + "$type": "com.atproto.lexicon.schema", 152 + "lexicon": 1 153 + }
+108 -86
lexicons/app/bsky/notification/defs.json
··· 1 1 { 2 - "$type": "com.atproto.lexicon.schema", 2 + "id": "app.bsky.notification.defs", 3 3 "defs": { 4 - "activitySubscription": { 5 - "properties": { 6 - "post": { 7 - "type": "boolean" 8 - }, 9 - "reply": { 10 - "type": "boolean" 11 - } 12 - }, 13 - "required": ["post", "reply"], 14 - "type": "object" 15 - }, 16 - "chatPreference": { 17 - "properties": { 18 - "include": { 19 - "knownValues": ["all", "accepted"], 20 - "type": "string" 21 - }, 22 - "push": { 23 - "type": "boolean" 24 - } 25 - }, 26 - "required": ["include", "push"], 27 - "type": "object" 28 - }, 29 - "filterablePreference": { 30 - "properties": { 31 - "include": { 32 - "knownValues": ["all", "follows"], 33 - "type": "string" 34 - }, 35 - "list": { 36 - "type": "boolean" 37 - }, 38 - "push": { 39 - "type": "boolean" 40 - } 41 - }, 42 - "required": ["include", "list", "push"], 43 - "type": "object" 44 - }, 45 4 "preference": { 5 + "type": "object", 6 + "required": [ 7 + "list", 8 + "push" 9 + ], 46 10 "properties": { 47 11 "list": { 48 12 "type": "boolean" ··· 50 14 "push": { 51 15 "type": "boolean" 52 16 } 53 - }, 54 - "required": ["list", "push"], 55 - "type": "object" 17 + } 56 18 }, 57 19 "preferences": { 20 + "type": "object", 21 + "required": [ 22 + "chat", 23 + "follow", 24 + "like", 25 + "likeViaRepost", 26 + "mention", 27 + "quote", 28 + "reply", 29 + "repost", 30 + "repostViaRepost", 31 + "starterpackJoined", 32 + "subscribedPost", 33 + "unverified", 34 + "verified" 35 + ], 58 36 "properties": { 59 37 "chat": { 60 38 "ref": "#chatPreference", 61 39 "type": "ref" 62 40 }, 63 - "follow": { 64 - "ref": "#filterablePreference", 65 - "type": "ref" 66 - }, 67 41 "like": { 68 42 "ref": "#filterablePreference", 69 43 "type": "ref" 70 44 }, 71 - "likeViaRepost": { 45 + "quote": { 72 46 "ref": "#filterablePreference", 73 47 "type": "ref" 74 48 }, 75 - "mention": { 49 + "reply": { 76 50 "ref": "#filterablePreference", 77 51 "type": "ref" 78 52 }, 79 - "quote": { 53 + "follow": { 80 54 "ref": "#filterablePreference", 81 55 "type": "ref" 82 56 }, 83 - "reply": { 57 + "repost": { 84 58 "ref": "#filterablePreference", 85 59 "type": "ref" 86 60 }, 87 - "repost": { 61 + "mention": { 88 62 "ref": "#filterablePreference", 89 63 "type": "ref" 90 64 }, 91 - "repostViaRepost": { 92 - "ref": "#filterablePreference", 65 + "verified": { 66 + "ref": "#preference", 93 67 "type": "ref" 94 68 }, 95 - "starterpackJoined": { 69 + "unverified": { 96 70 "ref": "#preference", 97 71 "type": "ref" 98 72 }, 73 + "likeViaRepost": { 74 + "ref": "#filterablePreference", 75 + "type": "ref" 76 + }, 99 77 "subscribedPost": { 100 78 "ref": "#preference", 101 79 "type": "ref" 102 80 }, 103 - "unverified": { 104 - "ref": "#preference", 81 + "repostViaRepost": { 82 + "ref": "#filterablePreference", 105 83 "type": "ref" 106 84 }, 107 - "verified": { 85 + "starterpackJoined": { 108 86 "ref": "#preference", 109 87 "type": "ref" 110 88 } 111 - }, 89 + } 90 + }, 91 + "recordDeleted": { 92 + "type": "object", 93 + "properties": {} 94 + }, 95 + "chatPreference": { 96 + "type": "object", 97 + "required": [ 98 + "include", 99 + "push" 100 + ], 101 + "properties": { 102 + "push": { 103 + "type": "boolean" 104 + }, 105 + "include": { 106 + "type": "string", 107 + "knownValues": [ 108 + "all", 109 + "accepted" 110 + ] 111 + } 112 + } 113 + }, 114 + "activitySubscription": { 115 + "type": "object", 112 116 "required": [ 113 - "chat", 114 - "follow", 115 - "like", 116 - "likeViaRepost", 117 - "mention", 118 - "quote", 119 - "reply", 120 - "repost", 121 - "repostViaRepost", 122 - "starterpackJoined", 123 - "subscribedPost", 124 - "unverified", 125 - "verified" 117 + "post", 118 + "reply" 126 119 ], 127 - "type": "object" 120 + "properties": { 121 + "post": { 122 + "type": "boolean" 123 + }, 124 + "reply": { 125 + "type": "boolean" 126 + } 127 + } 128 128 }, 129 - "recordDeleted": { 130 - "properties": {}, 131 - "type": "object" 129 + "filterablePreference": { 130 + "type": "object", 131 + "required": [ 132 + "include", 133 + "list", 134 + "push" 135 + ], 136 + "properties": { 137 + "list": { 138 + "type": "boolean" 139 + }, 140 + "push": { 141 + "type": "boolean" 142 + }, 143 + "include": { 144 + "type": "string", 145 + "knownValues": [ 146 + "all", 147 + "follows" 148 + ] 149 + } 150 + } 132 151 }, 133 152 "subjectActivitySubscription": { 134 - "description": "Object used to store activity subscription data in stash.", 153 + "type": "object", 154 + "required": [ 155 + "subject", 156 + "activitySubscription" 157 + ], 135 158 "properties": { 159 + "subject": { 160 + "type": "string", 161 + "format": "did" 162 + }, 136 163 "activitySubscription": { 137 164 "ref": "#activitySubscription", 138 165 "type": "ref" 139 - }, 140 - "subject": { 141 - "format": "did", 142 - "type": "string" 143 166 } 144 167 }, 145 - "required": ["subject", "activitySubscription"], 146 - "type": "object" 168 + "description": "Object used to store activity subscription data in stash." 147 169 } 148 170 }, 149 - "id": "app.bsky.notification.defs", 171 + "$type": "com.atproto.lexicon.schema", 150 172 "lexicon": 1 151 173 }
+57 -41
lexicons/app/bsky/richtext/facet.json
··· 1 1 { 2 - "$type": "com.atproto.lexicon.schema", 2 + "id": "app.bsky.richtext.facet", 3 3 "defs": { 4 - "byteSlice": { 5 - "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.", 4 + "tag": { 5 + "type": "object", 6 + "required": [ 7 + "tag" 8 + ], 6 9 "properties": { 7 - "byteEnd": { 8 - "minimum": 0, 9 - "type": "integer" 10 - }, 11 - "byteStart": { 12 - "minimum": 0, 13 - "type": "integer" 10 + "tag": { 11 + "type": "string", 12 + "maxLength": 640, 13 + "maxGraphemes": 64 14 14 } 15 15 }, 16 - "required": ["byteStart", "byteEnd"], 17 - "type": "object" 16 + "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 17 }, 19 18 "link": { 20 - "description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.", 19 + "type": "object", 20 + "required": [ 21 + "uri" 22 + ], 21 23 "properties": { 22 24 "uri": { 23 - "format": "uri", 24 - "type": "string" 25 + "type": "string", 26 + "format": "uri" 25 27 } 26 28 }, 27 - "required": ["uri"], 28 - "type": "object" 29 + "description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL." 29 30 }, 30 31 "main": { 31 - "description": "Annotation of a sub-string within rich text.", 32 + "type": "object", 33 + "required": [ 34 + "index", 35 + "features" 36 + ], 32 37 "properties": { 33 - "features": { 34 - "items": { 35 - "refs": ["#mention", "#link", "#tag"], 36 - "type": "union" 37 - }, 38 - "type": "array" 39 - }, 40 38 "index": { 41 39 "ref": "#byteSlice", 42 40 "type": "ref" 41 + }, 42 + "features": { 43 + "type": "array", 44 + "items": { 45 + "refs": [ 46 + "#mention", 47 + "#link", 48 + "#tag" 49 + ], 50 + "type": "union" 51 + } 43 52 } 44 53 }, 45 - "required": ["index", "features"], 46 - "type": "object" 54 + "description": "Annotation of a sub-string within rich text." 47 55 }, 48 56 "mention": { 49 - "description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.", 57 + "type": "object", 58 + "required": [ 59 + "did" 60 + ], 50 61 "properties": { 51 62 "did": { 52 - "format": "did", 53 - "type": "string" 63 + "type": "string", 64 + "format": "did" 54 65 } 55 66 }, 56 - "required": ["did"], 57 - "type": "object" 67 + "description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID." 58 68 }, 59 - "tag": { 60 - "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').", 69 + "byteSlice": { 70 + "type": "object", 71 + "required": [ 72 + "byteStart", 73 + "byteEnd" 74 + ], 61 75 "properties": { 62 - "tag": { 63 - "maxGraphemes": 64, 64 - "maxLength": 640, 65 - "type": "string" 76 + "byteEnd": { 77 + "type": "integer", 78 + "minimum": 0 79 + }, 80 + "byteStart": { 81 + "type": "integer", 82 + "minimum": 0 66 83 } 67 84 }, 68 - "required": ["tag"], 69 - "type": "object" 85 + "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." 70 86 } 71 87 }, 72 - "id": "app.bsky.richtext.facet", 88 + "$type": "com.atproto.lexicon.schema", 73 89 "lexicon": 1 74 90 }
+129 -124
lexicons/com/atproto/label/defs.json
··· 1 1 { 2 - "$type": "com.atproto.lexicon.schema", 2 + "lexicon": 1, 3 + "id": "com.atproto.label.defs", 3 4 "defs": { 4 5 "label": { 6 + "type": "object", 5 7 "description": "Metadata tag on an atproto resource (eg, repo or record).", 8 + "required": [ 9 + "src", 10 + "uri", 11 + "val", 12 + "cts" 13 + ], 6 14 "properties": { 7 - "cid": { 8 - "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to.", 9 - "format": "cid", 10 - "type": "string" 15 + "ver": { 16 + "type": "integer" 11 17 }, 12 - "cts": { 13 - "description": "Timestamp when this label was created.", 14 - "format": "datetime", 15 - "type": "string" 18 + "src": { 19 + "type": "string", 20 + "format": "did" 16 21 }, 17 - "exp": { 18 - "description": "Timestamp at which this label expires (no longer applies).", 19 - "format": "datetime", 20 - "type": "string" 22 + "uri": { 23 + "type": "string", 24 + "format": "uri" 25 + }, 26 + "cid": { 27 + "type": "string", 28 + "format": "cid" 29 + }, 30 + "val": { 31 + "type": "string", 32 + "maxLength": 128 21 33 }, 22 34 "neg": { 23 - "description": "If true, this is a negation label, overwriting a previous label.", 24 35 "type": "boolean" 25 36 }, 37 + "cts": { 38 + "type": "string", 39 + "format": "datetime" 40 + }, 41 + "exp": { 42 + "type": "string", 43 + "format": "datetime" 44 + }, 26 45 "sig": { 27 - "description": "Signature of dag-cbor encoded label.", 28 46 "type": "bytes" 29 - }, 30 - "src": { 31 - "description": "DID of the actor who created this label.", 32 - "format": "did", 33 - "type": "string" 34 - }, 35 - "uri": { 36 - "description": "AT URI of the record, repository (account), or other resource that this label applies to.", 37 - "format": "uri", 38 - "type": "string" 39 - }, 40 - "val": { 41 - "description": "The short string name of the value or type of this label.", 42 - "maxLength": 128, 43 - "type": "string" 44 - }, 45 - "ver": { 46 - "description": "The AT Protocol version of the label object.", 47 - "type": "integer" 47 + } 48 + } 49 + }, 50 + "selfLabels": { 51 + "type": "object", 52 + "description": "Metadata tags on an atproto record, published by the author within the record.", 53 + "required": [ 54 + "values" 55 + ], 56 + "properties": { 57 + "values": { 58 + "type": "array", 59 + "items": { 60 + "type": "ref", 61 + "ref": "#selfLabel" 62 + }, 63 + "maxLength": 10 48 64 } 49 - }, 50 - "required": ["src", "uri", "val", "cts"], 51 - "type": "object" 65 + } 52 66 }, 53 - "labelValue": { 54 - "knownValues": [ 55 - "!hide", 56 - "!no-promote", 57 - "!warn", 58 - "!no-unauthenticated", 59 - "dmca-violation", 60 - "doxxing", 61 - "porn", 62 - "sexual", 63 - "nudity", 64 - "nsfl", 65 - "gore" 67 + "selfLabel": { 68 + "type": "object", 69 + "required": [ 70 + "val" 66 71 ], 67 - "type": "string" 72 + "properties": { 73 + "val": { 74 + "type": "string", 75 + "maxLength": 128 76 + } 77 + } 68 78 }, 69 79 "labelValueDefinition": { 80 + "type": "object", 70 81 "description": "Declares a label value and its expected interpretations and behaviors.", 82 + "required": [ 83 + "identifier", 84 + "severity", 85 + "blurs", 86 + "locales" 87 + ], 71 88 "properties": { 72 - "adultOnly": { 73 - "description": "Does the user need to have adult content enabled in order to configure this label?", 74 - "type": "boolean" 89 + "identifier": { 90 + "type": "string", 91 + "maxLength": 100 92 + }, 93 + "severity": { 94 + "type": "string", 95 + "knownValues": [ 96 + "inform", 97 + "alert", 98 + "none" 99 + ] 75 100 }, 76 101 "blurs": { 77 - "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.", 78 - "knownValues": ["content", "media", "none"], 79 - "type": "string" 102 + "type": "string", 103 + "knownValues": [ 104 + "content", 105 + "media", 106 + "none" 107 + ] 80 108 }, 81 109 "defaultSetting": { 82 - "default": "warn", 83 - "description": "The default setting for this label.", 84 - "knownValues": ["ignore", "warn", "hide"], 85 - "type": "string" 110 + "type": "string", 111 + "knownValues": [ 112 + "ignore", 113 + "warn", 114 + "hide" 115 + ] 86 116 }, 87 - "identifier": { 88 - "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 89 - "maxGraphemes": 100, 90 - "maxLength": 100, 91 - "type": "string" 117 + "adultOnly": { 118 + "type": "boolean" 92 119 }, 93 120 "locales": { 121 + "type": "array", 94 122 "items": { 95 - "ref": "#labelValueDefinitionStrings", 96 - "type": "ref" 97 - }, 98 - "type": "array" 99 - }, 100 - "severity": { 101 - "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 102 - "knownValues": ["inform", "alert", "none"], 103 - "type": "string" 123 + "type": "ref", 124 + "ref": "#labelValueDefinitionStrings" 125 + } 104 126 } 105 - }, 106 - "required": ["identifier", "severity", "blurs", "locales"], 107 - "type": "object" 127 + } 108 128 }, 109 129 "labelValueDefinitionStrings": { 110 - "description": "Strings which describe the label in the UI, localized into a specific language.", 130 + "type": "object", 131 + "required": [ 132 + "lang", 133 + "name", 134 + "description" 135 + ], 111 136 "properties": { 112 - "description": { 113 - "description": "A longer description of what the label means and why it might be applied.", 114 - "maxGraphemes": 10000, 115 - "maxLength": 100000, 116 - "type": "string" 117 - }, 118 137 "lang": { 119 - "description": "The code of the language these strings are written in.", 120 - "format": "language", 121 - "type": "string" 138 + "type": "string", 139 + "format": "language" 122 140 }, 123 141 "name": { 124 - "description": "A short human-readable name for the label.", 125 - "maxGraphemes": 64, 126 - "maxLength": 640, 127 - "type": "string" 142 + "type": "string", 143 + "maxLength": 640 144 + }, 145 + "description": { 146 + "type": "string", 147 + "maxLength": 100000 128 148 } 129 - }, 130 - "required": ["lang", "name", "description"], 131 - "type": "object" 149 + } 132 150 }, 133 - "selfLabel": { 134 - "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.", 135 - "properties": { 136 - "val": { 137 - "description": "The short string name of the value or type of this label.", 138 - "maxLength": 128, 139 - "type": "string" 140 - } 141 - }, 142 - "required": ["val"], 143 - "type": "object" 144 - }, 145 - "selfLabels": { 146 - "description": "Metadata tags on an atproto record, published by the author within the record.", 147 - "properties": { 148 - "values": { 149 - "items": { 150 - "ref": "#selfLabel", 151 - "type": "ref" 152 - }, 153 - "maxLength": 10, 154 - "type": "array" 155 - } 156 - }, 157 - "required": ["values"], 158 - "type": "object" 151 + "labelValue": { 152 + "type": "string", 153 + "knownValues": [ 154 + "!hide", 155 + "!no-promote", 156 + "!warn", 157 + "!no-unauthenticated", 158 + "dmca-violation", 159 + "doxxing", 160 + "porn", 161 + "sexual", 162 + "nudity", 163 + "nsfl", 164 + "gore" 165 + ] 159 166 } 160 - }, 161 - "id": "com.atproto.label.defs", 162 - "lexicon": 1 167 + } 163 168 }
+55
lexicons/com/atproto/moderation/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.moderation.defs", 4 + "defs": { 5 + "reasonType": { 6 + "type": "string", 7 + "knownValues": [ 8 + "com.atproto.moderation.defs#reasonSpam", 9 + "com.atproto.moderation.defs#reasonViolation", 10 + "com.atproto.moderation.defs#reasonMisleading", 11 + "com.atproto.moderation.defs#reasonSexual", 12 + "com.atproto.moderation.defs#reasonRude", 13 + "com.atproto.moderation.defs#reasonOther", 14 + "com.atproto.moderation.defs#reasonAppeal" 15 + ] 16 + }, 17 + "reasonSpam": { 18 + "type": "token", 19 + "description": "Spam: frequent unwanted promotion, replies, mentions." 20 + }, 21 + "reasonViolation": { 22 + "type": "token", 23 + "description": "Direct violation of server rules, laws, terms of service." 24 + }, 25 + "reasonMisleading": { 26 + "type": "token", 27 + "description": "Misleading identity, affiliation, or content." 28 + }, 29 + "reasonSexual": { 30 + "type": "token", 31 + "description": "Unwanted or mislabeled sexual content." 32 + }, 33 + "reasonRude": { 34 + "type": "token", 35 + "description": "Rude, harassing, explicit, or otherwise unwelcoming behavior." 36 + }, 37 + "reasonOther": { 38 + "type": "token", 39 + "description": "Reports not falling under another report category." 40 + }, 41 + "reasonAppeal": { 42 + "type": "token", 43 + "description": "Appeal a previously taken moderation action." 44 + }, 45 + "subjectType": { 46 + "type": "string", 47 + "description": "Tag describing a type of subject that might be reported.", 48 + "knownValues": [ 49 + "account", 50 + "record", 51 + "chat" 52 + ] 53 + } 54 + } 55 + }
+16 -14
lexicons/com/atproto/repo/strongRef.json
··· 1 1 { 2 - "$type": "com.atproto.lexicon.schema", 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.strongRef", 4 + "description": "A URI with a content-hash fingerprint.", 3 5 "defs": { 4 6 "main": { 7 + "type": "object", 8 + "required": [ 9 + "uri", 10 + "cid" 11 + ], 5 12 "properties": { 6 - "cid": { 7 - "format": "cid", 8 - "type": "string" 9 - }, 10 13 "uri": { 11 - "format": "at-uri", 12 - "type": "string" 14 + "type": "string", 15 + "format": "at-uri" 16 + }, 17 + "cid": { 18 + "type": "string", 19 + "format": "cid" 13 20 } 14 - }, 15 - "required": ["uri", "cid"], 16 - "type": "object" 21 + } 17 22 } 18 - }, 19 - "description": "A URI with a content-hash fingerprint.", 20 - "id": "com.atproto.repo.strongRef", 21 - "lexicon": 1 23 + } 22 24 }
+9 -1
lexicons/social/grain/gallery/defs.json
··· 42 42 }, 43 43 "createdAt": { "type": "string", "format": "datetime" }, 44 44 "indexedAt": { "type": "string", "format": "datetime" }, 45 - "viewer": { "type": "ref", "ref": "#viewerState" } 45 + "viewer": { "type": "ref", "ref": "#viewerState" }, 46 + "crossPost": { "type": "ref", "ref": "#crossPostInfo" } 47 + } 48 + }, 49 + "crossPostInfo": { 50 + "type": "object", 51 + "required": ["url"], 52 + "properties": { 53 + "url": { "type": "string", "format": "uri", "description": "URL to the cross-posted Bluesky post." } 46 54 } 47 55 }, 48 56 "viewerState": {
+2 -1
lexicons/social/grain/story/defs.json
··· 26 26 "labels": { 27 27 "type": "array", 28 28 "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 29 - } 29 + }, 30 + "crossPost": { "type": "ref", "ref": "social.grain.gallery.defs#crossPostInfo" } 30 31 } 31 32 } 32 33 }
+25
lexicons/social/grain/unspecced/getStory.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.unspecced.getStory", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "parameters": { 8 + "type": "params", 9 + "required": ["story"], 10 + "properties": { 11 + "story": { "type": "string", "format": "at-uri" } 12 + } 13 + }, 14 + "output": { 15 + "encoding": "application/json", 16 + "schema": { 17 + "type": "object", 18 + "properties": { 19 + "story": { "type": "ref", "ref": "social.grain.story.defs#storyView" } 20 + } 21 + } 22 + } 23 + } 24 + } 25 + }
+21 -1
server/feeds/_hydrate.ts
··· 77 77 for (const row of favRows) viewerFavs.set(row.subject, row.uri); 78 78 } 79 79 80 - const [profiles, favCounts, commentCounts, labelsByUri, galleryItemRows] = await Promise.all([ 80 + const [profiles, favCounts, commentCounts, labelsByUri, galleryItemRows, crossPosts] = await Promise.all([ 81 81 ctx.lookup<GrainActorProfile>("social.grain.actor.profile", "did", dids), 82 82 galleryUris.length > 0 83 83 ? (ctx.db.query( ··· 111 111 }> 112 112 >) 113 113 : Promise.resolve([]), 114 + // Cross-post lookup: find Bluesky posts that link back to these galleries 115 + galleryUris.length > 0 116 + ? (async () => { 117 + const map = new Map<string, string>(); 118 + for (const item of items) { 119 + const rkey = item.uri.split("/").pop(); 120 + const url = `grain.social/profile/${item.did}/gallery/${rkey}`; 121 + const rows = (await ctx.db.query( 122 + `SELECT uri FROM "app.bsky.feed.post" WHERE did = $1 AND "text" LIKE '%' || $2 || '%' LIMIT 1`, 123 + [item.did, url], 124 + )) as Array<{ uri: string }>; 125 + if (rows.length) { 126 + const postRkey = rows[0].uri.split("/").pop(); 127 + map.set(item.uri, `https://bsky.app/profile/${item.did}/post/${postRkey}`); 128 + } 129 + } 130 + return map; 131 + })() 132 + : Promise.resolve(new Map<string, string>()), 114 133 ]); 115 134 116 135 // Group gallery items by gallery URI ··· 198 217 : {}), 199 218 ...(labelsByUri.has(item.uri) ? { labels: labelsByUri.get(item.uri) } : {}), 200 219 ...(viewerFavs.has(item.uri) ? { viewer: { fav: viewerFavs.get(item.uri) } } : {}), 220 + ...(crossPosts.has(item.uri) ? { crossPost: { url: crossPosts.get(item.uri) } } : {}), 201 221 }); 202 222 }); 203 223 }
+146
server/og/story.ts
··· 1 + import { defineOG } from "$hatk"; 2 + import type { GrainActorProfile } from "$hatk"; 3 + 4 + export default defineOG("/og/profile/:did/story/:rkey", async (ctx) => { 5 + const { db, params, fetchImage, lookup, blobUrl } = ctx; 6 + const { did, rkey } = params; 7 + 8 + const storyUri = `at://${did}/social.grain.story/${rkey}`; 9 + 10 + const rows = (await db.query( 11 + `SELECT uri, did, cid, media FROM "social.grain.story" WHERE uri = $1`, 12 + [storyUri], 13 + )) as Array<{ 14 + uri: string; 15 + did: string; 16 + cid: string; 17 + media: string; 18 + }>; 19 + 20 + const row = rows[0]; 21 + if (!row) { 22 + return { 23 + element: { 24 + type: "div", 25 + props: { 26 + style: { 27 + display: "flex", 28 + width: "100%", 29 + height: "100%", 30 + background: "#ffffff", 31 + color: "#171717", 32 + alignItems: "center", 33 + justifyContent: "center", 34 + }, 35 + children: "Story not found", 36 + }, 37 + }, 38 + }; 39 + } 40 + 41 + let blobRef: any; 42 + try { 43 + blobRef = typeof row.media === "string" ? JSON.parse(row.media) : row.media; 44 + } catch { 45 + blobRef = row.media; 46 + } 47 + 48 + const imageUrl = blobUrl(row.did, blobRef, "feed_fullsize"); 49 + const imageDataUrl = imageUrl ? await fetchImage(imageUrl) : null; 50 + 51 + const profiles = await lookup<GrainActorProfile>("social.grain.actor.profile", "did", [did]); 52 + const author = profiles.get(did); 53 + const avatarRef = author ? blobUrl(did, author.value.avatar) : null; 54 + const avatarDataUrl = avatarRef ? await fetchImage(avatarRef) : null; 55 + 56 + return { 57 + element: { 58 + type: "div", 59 + props: { 60 + style: { 61 + display: "flex", 62 + width: "100%", 63 + height: "100%", 64 + backgroundColor: "#000000", 65 + position: "relative", 66 + }, 67 + children: [ 68 + ...(imageDataUrl 69 + ? [ 70 + { 71 + type: "img", 72 + props: { 73 + src: imageDataUrl, 74 + style: { width: "100%", height: "100%", objectFit: "contain" as const }, 75 + }, 76 + }, 77 + ] 78 + : []), 79 + { 80 + type: "div", 81 + props: { 82 + style: { 83 + position: "absolute", 84 + bottom: "0", 85 + left: "0", 86 + right: "0", 87 + height: "80px", 88 + background: "linear-gradient(transparent, rgba(0,0,0,0.7))", 89 + display: "flex", 90 + alignItems: "flex-end", 91 + padding: "0 24px 16px 24px", 92 + gap: "12px", 93 + }, 94 + children: [ 95 + ...(avatarDataUrl 96 + ? [ 97 + { 98 + type: "img", 99 + props: { 100 + src: avatarDataUrl, 101 + style: { 102 + width: "44px", 103 + height: "44px", 104 + borderRadius: "22px", 105 + objectFit: "cover" as const, 106 + }, 107 + }, 108 + }, 109 + ] 110 + : []), 111 + { 112 + type: "div", 113 + props: { 114 + style: { display: "flex", flexDirection: "column", gap: "2px" }, 115 + children: [ 116 + { 117 + type: "div", 118 + props: { 119 + children: 120 + author?.value.displayName || 121 + `@${author?.handle || did.slice(0, 24)}`, 122 + style: { fontSize: 24, fontWeight: 600, color: "#ffffff" }, 123 + }, 124 + }, 125 + { 126 + type: "div", 127 + props: { 128 + children: "Grain", 129 + style: { fontSize: 16, color: "rgba(255,255,255,0.7)" }, 130 + }, 131 + }, 132 + ], 133 + }, 134 + }, 135 + ], 136 + }, 137 + }, 138 + ], 139 + }, 140 + }, 141 + meta: { 142 + title: `Story by @${author?.handle || did.slice(0, 24)} — Grain`, 143 + description: "Photo story on Grain", 144 + }, 145 + }; 146 + });
+112
server/xrpc/getStory.ts
··· 1 + import { defineQuery } from "$hatk"; 2 + import { views } from "$hatk"; 3 + import type { GrainActorProfile, Story } from "$hatk"; 4 + 5 + export default defineQuery("social.grain.unspecced.getStory", async (ctx) => { 6 + const { db, ok } = ctx; 7 + const storyUri = ctx.params.story; 8 + if (!storyUri) return ok({ story: null }); 9 + 10 + const rows = (await db.query( 11 + `SELECT uri, cid, did, media, aspect_ratio, location, address, created_at 12 + FROM "social.grain.story" 13 + WHERE uri = $1`, 14 + [storyUri], 15 + )) as { 16 + uri: string; 17 + cid: string; 18 + did: string; 19 + media: string; 20 + aspect_ratio: string; 21 + location: string | null; 22 + address: string | null; 23 + created_at: string; 24 + }[]; 25 + 26 + const row = rows[0]; 27 + if (!row) return ok({ story: null }); 28 + 29 + // Resolve author profile 30 + const profiles = await ctx.lookup<GrainActorProfile>("social.grain.actor.profile", "did", [ 31 + row.did, 32 + ]); 33 + const author = profiles.get(row.did); 34 + const profileView = author 35 + ? views.grainActorDefsProfileView({ 36 + cid: author.cid, 37 + did: author.did, 38 + handle: author.handle ?? author.did, 39 + displayName: author.value.displayName, 40 + avatar: ctx.blobUrl(author.did, author.value.avatar) ?? undefined, 41 + }) 42 + : views.grainActorDefsProfileView({ 43 + cid: "", 44 + did: row.did, 45 + handle: row.did, 46 + }); 47 + 48 + let blobRef: any; 49 + try { 50 + blobRef = typeof row.media === "string" ? JSON.parse(row.media) : row.media; 51 + } catch { 52 + blobRef = row.media; 53 + } 54 + 55 + let aspectRatio: { width: number; height: number }; 56 + try { 57 + aspectRatio = 58 + typeof row.aspect_ratio === "string" ? JSON.parse(row.aspect_ratio) : row.aspect_ratio; 59 + } catch { 60 + aspectRatio = { width: 4, height: 3 }; 61 + } 62 + 63 + let location: Story["location"] | null = null; 64 + if (row.location) { 65 + try { 66 + location = typeof row.location === "string" ? JSON.parse(row.location) : row.location; 67 + } catch { 68 + location = null; 69 + } 70 + } 71 + 72 + let address: Story["address"] | null = null; 73 + if (row.address) { 74 + try { 75 + address = typeof row.address === "string" ? JSON.parse(row.address) : row.address; 76 + } catch { 77 + address = null; 78 + } 79 + } 80 + 81 + // Cross-post lookup 82 + let crossPost: { url: string } | undefined; 83 + const rkey = row.uri.split("/").pop(); 84 + const searchUrl = `grain.social/profile/${row.did}/story/${rkey}`; 85 + const postRows = (await db.query( 86 + `SELECT uri FROM "app.bsky.feed.post" WHERE did = $1 AND "text" LIKE '%' || $2 || '%' LIMIT 1`, 87 + [row.did, searchUrl], 88 + )) as Array<{ uri: string }>; 89 + if (postRows.length) { 90 + const postRkey = postRows[0].uri.split("/").pop(); 91 + crossPost = { url: `https://bsky.app/profile/${row.did}/post/${postRkey}` }; 92 + } 93 + 94 + const story = views.storyView({ 95 + uri: row.uri, 96 + cid: row.cid, 97 + creator: profileView, 98 + thumb: ctx.blobUrl(row.did, blobRef, "feed_thumbnail") ?? "", 99 + fullsize: ctx.blobUrl(row.did, blobRef, "feed_fullsize") ?? "", 100 + aspectRatio, 101 + ...(location 102 + ? { 103 + location: { name: location.name, value: location.value }, 104 + ...(address ? { address } : {}), 105 + } 106 + : {}), 107 + ...(crossPost ? { crossPost } : {}), 108 + createdAt: row.created_at, 109 + }); 110 + 111 + return ok({ story }); 112 + });