[READ ONLY MIRROR] Spark Social AppView Server github.com/sprksocial/server
atproto deno hono lexicon
5
fork

Configure Feed

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

Breaking lexicon changes (#45)

authored by

Roscoe Rubin-Rottenberg and committed by
GitHub
7b79eda0 abade5fa

+2875 -6970
+2 -2
api/index.ts
··· 16 16 import searchActors from "./so/sprk/actor/searchActors.ts"; 17 17 import getRecord from "./com/atproto/repo/getRecord.ts"; 18 18 import resolveHandle from "./com/atproto/identity/resolveHandle.ts"; 19 - import getStories from "./so/sprk/feed/getStories.ts"; 20 - import getStoriesTimeline from "./so/sprk/feed/getStoriesTimeline.ts"; 19 + import getStories from "./so/sprk/story/getStories.ts"; 20 + import getStoriesTimeline from "./so/sprk/story/getTimeline.ts"; 21 21 import getProfiles from "./so/sprk/actor/getProfiles.ts"; 22 22 import searchPosts from "./so/sprk/feed/searchPosts.ts"; 23 23 import getActorAudios from "./so/sprk/sound/getActorAudios.ts";
+6 -22
api/so/sprk/feed/getAuthorFeed.ts
··· 3 3 import { AppContext } from "../../../../context.ts"; 4 4 import { DataPlane } from "../../../../data-plane/index.ts"; 5 5 import { Actor } from "../../../../hydration/actor.ts"; 6 - import { FeedItem, Post } from "../../../../hydration/feed.ts"; 6 + import { FeedItem } from "../../../../hydration/feed.ts"; 7 7 import { 8 8 HydrateCtx, 9 9 HydrationState, ··· 130 130 }): Skeleton => { 131 131 const { ctx, skeleton, hydration } = inputs; 132 132 const relationship = hydration.profileViewers?.get(skeleton.actor.did); 133 - if ( 134 - relationship && 135 - (relationship.blocking) 136 - ) { 133 + if (relationship && relationship.blocking) { 137 134 throw new InvalidRequestError( 138 135 `Requester has blocked actor: ${skeleton.actor.did}`, 139 136 "BlockedActor", 140 137 ); 141 138 } 142 - if ( 143 - relationship && 144 - (relationship.blockedBy) 145 - ) { 139 + if (relationship && relationship.blockedBy) { 146 140 throw new InvalidRequestError( 147 141 `Requester is blocked by actor: ${skeleton.actor.did}`, 148 142 "BlockedByActor", ··· 233 227 loop.add(uri); 234 228 } 235 229 // cache through the result 236 - const result = this._ok(uri, loop); 230 + const result = this._ok(uri); 237 231 this.cache.set(uri, result); 238 232 return result; 239 233 } 240 234 241 - private _ok(uri: string, loop: Set<string>): boolean { 235 + private _ok(uri: string): boolean { 242 236 // must be in the feed to be in a self-thread 243 237 if (!this.feedUris.has(uri)) { 244 238 return false; ··· 248 242 if (!post) { 249 243 return false; 250 244 } 251 - // root posts (no parent) are trivial case of self-thread 252 - const parentUri = getParentUri(post); 253 - if (parentUri === null) { 254 - return true; 255 - } 256 - // recurse w/ cache: this post is in a self-thread if its parent is. 257 - return this.ok(parentUri, loop); 245 + return true; 258 246 } 259 247 } 260 - 261 - function getParentUri(post: Post) { 262 - return post.record.reply?.parent.uri ?? null; 263 - }
+119 -485
api/so/sprk/feed/getPostThread.ts
··· 1 - import { Server } from "../../../../lex/index.ts"; 1 + import { InvalidRequestError } from "@atp/xrpc-server"; 2 + import { ServerConfig } from "../../../../config.ts"; 2 3 import { AppContext } from "../../../../context.ts"; 3 - import { OutputSchema } from "../../../../lex/types/so/sprk/feed/getPostThread.ts"; 4 - import type * as SoSprkFeedDefs from "../../../../lex/types/so/sprk/feed/defs.ts"; 5 - import { transformPostsToPostViews } from "../../../../utils/post-transformer.ts"; 6 - import { PostDocument } from "../../../../data-plane/db/models.ts"; 7 - import { type $Typed } from "../../../../lex/util.ts"; 8 - 9 - // Constants 10 - const MAX_DEPTH = 10; 11 - const MAX_PARENT_HEIGHT = 100; 12 - const DEFAULT_DEPTH = 6; 13 - const DEFAULT_PARENT_HEIGHT = 80; 14 - const MAX_URI_LENGTH = 3000; 15 - 16 - // Helper function to validate URI 17 - function validateUri(uri: string): boolean { 18 - return typeof uri === "string" && 19 - uri.length > 0 && 20 - uri.length <= MAX_URI_LENGTH && 21 - uri.startsWith("at://"); 22 - } 23 - 24 - // Ultra-optimized function to get entire thread structure in minimal queries 25 - async function getCompleteThreadStructure( 26 - ctx: AppContext, 27 - rootUri: string, 28 - maxDepth: number, 29 - maxParentHeight: number, 30 - ) { 31 - const allUris = new Set<string>([rootUri]); 32 - const posts = new Map<string, PostDocument>(); 33 - const parentToChildren = new Map<string, string[]>(); 34 - const childToParent = new Map<string, string>(); 35 - 36 - // Step 1: Get the root post first 37 - const rootPosts = await ctx.db.models.Post.find({ uri: rootUri }); 38 - if (rootPosts.length === 0) { 39 - return { 40 - posts, 41 - parentToChildren, 42 - childToParent, 43 - allUris: new Set<string>(), 44 - }; 45 - } 46 - 47 - const rootPost = rootPosts[0]; 48 - posts.set(rootUri, rootPost); 49 - 50 - // Step 2: Build ancestor chain with single query if root is a reply 51 - const ancestorUris: string[] = []; 52 - if (rootPost.reply?.parent?.uri) { 53 - // Use aggregation to get entire ancestor chain in one query 54 - const ancestorChain = await ctx.db.models.Post.aggregate([ 55 - { $match: { uri: rootPost.reply.parent.uri } }, 56 - { 57 - $graphLookup: { 58 - from: "posts", 59 - startWith: "$reply.parent.uri", 60 - connectFromField: "reply.parent.uri", 61 - connectToField: "uri", 62 - as: "ancestors", 63 - maxDepth: maxParentHeight - 1, 64 - }, 65 - }, 66 - { 67 - $project: { 68 - chain: { 69 - $concatArrays: [ 70 - [{ 71 - uri: "$uri", 72 - reply: "$reply", 73 - authorDid: "$authorDid", 74 - cid: "$cid", 75 - createdAt: "$createdAt", 76 - text: "$text", 77 - embed: "$embed", 78 - facets: "$facets", 79 - langs: "$langs", 80 - labels: "$labels", 81 - tags: "$tags", 82 - indexedAt: "$indexedAt", 83 - }], 84 - "$ancestors", 85 - ], 86 - }, 87 - }, 88 - }, 89 - ]); 90 - 91 - if (ancestorChain.length > 0) { 92 - const chain = ancestorChain[0].chain as PostDocument[]; 93 - for (const ancestor of chain) { 94 - allUris.add(ancestor.uri); 95 - posts.set(ancestor.uri, ancestor); 96 - ancestorUris.push(ancestor.uri); 97 - 98 - if (ancestor.reply?.parent?.uri) { 99 - childToParent.set(ancestor.uri, ancestor.reply.parent.uri); 100 - } 101 - } 102 - } 103 - } 104 - 105 - // Step 3: Get entire descendant tree with single aggregation query 106 - const descendants = await ctx.db.models.Post.aggregate([ 107 - { $match: { "reply.parent.uri": rootUri } }, 108 - { 109 - $graphLookup: { 110 - from: "posts", 111 - startWith: "$uri", 112 - connectFromField: "uri", 113 - connectToField: "reply.parent.uri", 114 - as: "descendants", 115 - maxDepth: maxDepth - 1, 116 - }, 117 - }, 118 - { 119 - $project: { 120 - allPosts: { 121 - $concatArrays: [ 122 - [{ 123 - uri: "$uri", 124 - reply: "$reply", 125 - authorDid: "$authorDid", 126 - cid: "$cid", 127 - createdAt: "$createdAt", 128 - text: "$text", 129 - embed: "$embed", 130 - facets: "$facets", 131 - langs: "$langs", 132 - labels: "$labels", 133 - tags: "$tags", 134 - indexedAt: "$indexedAt", 135 - }], 136 - "$descendants", 137 - ], 138 - }, 139 - }, 140 - }, 141 - ]); 142 - 143 - // Process all descendants 144 - for (const doc of descendants) { 145 - const allPosts = doc.allPosts as PostDocument[]; 146 - for (const post of allPosts) { 147 - allUris.add(post.uri); 148 - posts.set(post.uri, post); 4 + import { DataPlane } from "../../../../data-plane/index.ts"; 5 + import { Code, isDataPlaneError } from "../../../../data-plane/util.ts"; 6 + import { HydrateCtx, Hydrator } from "../../../../hydration/index.ts"; 7 + import { Server } from "../../../../lex/index.ts"; 8 + import { isNotFoundPost } from "../../../../lex/types/app/bsky/feed/defs.ts"; 9 + import { 10 + OutputSchema, 11 + QueryParams, 12 + } from "../../../../lex/types/so/sprk/feed/getPostThread.ts"; 13 + import { 14 + createPipeline, 15 + HydrationFnInput, 16 + noRules, 17 + PresentationFnInput, 18 + SkeletonFnInput, 19 + } from "../../../../pipeline.ts"; 20 + import { Views } from "../../../../views/index.ts"; 21 + import { ATPROTO_REPO_REV, resHeaders } from "../../../util.ts"; 149 22 150 - if (post.reply?.parent?.uri) { 151 - const parentUri = post.reply.parent.uri; 152 - childToParent.set(post.uri, parentUri); 23 + export default function (server: Server, ctx: AppContext) { 24 + const getPostThread = createPipeline( 25 + skeleton, 26 + hydration, 27 + noRules, // handled in presentation: 3p block-violating replies are turned to #blockedPost, viewer blocks turned to #notFoundPost. 28 + presentation, 29 + ); 30 + server.so.sprk.feed.getPostThread({ 31 + auth: ctx.authVerifier.optionalStandardOrRole, 32 + handler: async ({ params, auth, res }) => { 33 + const { viewer, includeTakedowns, include3pBlocks } = ctx.authVerifier 34 + .parseCreds(auth); 35 + const hydrateCtx = ctx.hydrator.createContext({ 36 + viewer, 37 + includeTakedowns, 38 + include3pBlocks, 39 + }); 153 40 154 - if (!parentToChildren.has(parentUri)) { 155 - parentToChildren.set(parentUri, []); 41 + let result: OutputSchema; 42 + try { 43 + result = await getPostThread({ ...params, hydrateCtx }, ctx); 44 + } catch (err) { 45 + const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer); 46 + if (repoRev) { 47 + res.headers.set(ATPROTO_REPO_REV, repoRev); 156 48 } 157 - parentToChildren.get(parentUri)!.push(post.uri); 49 + throw err; 158 50 } 159 - } 160 - } 161 51 162 - // Sort children by creation time for consistent ordering 163 - for (const [_parentUri, children] of parentToChildren.entries()) { 164 - children.sort((a, b) => { 165 - const postA = posts.get(a); 166 - const postB = posts.get(b); 167 - if (!postA || !postB) return 0; 168 - return new Date(postA.createdAt).getTime() - 169 - new Date(postB.createdAt).getTime(); 170 - }); 171 - } 52 + const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer); 172 53 173 - return { posts, parentToChildren, childToParent, allUris }; 54 + return { 55 + encoding: "application/json", 56 + body: result, 57 + headers: resHeaders({ 58 + repoRev, 59 + }), 60 + }; 61 + }, 62 + }); 174 63 } 175 64 176 - // Batch check all block relationships in single query 177 - async function batchCheckBlockedPosts( 178 - ctx: AppContext, 179 - authorDids: string[], 180 - userDid?: string, 181 - ): Promise<Set<string>> { 182 - if (!userDid || authorDids.length === 0) { 183 - return new Set(); 184 - } 185 - 186 - // Single query to get all block relationships 187 - const [userBlocking, userBlocked] = await Promise.all([ 188 - ctx.db.models.Block.find({ 189 - authorDid: userDid, 190 - subject: { $in: authorDids }, 191 - }).select("subject").lean(), 192 - ctx.db.models.Block.find({ 193 - authorDid: { $in: authorDids }, 194 - subject: userDid, 195 - }).select("authorDid").lean(), 196 - ]); 197 - 198 - const blockedAuthorDids = new Set([ 199 - ...userBlocking.map((b) => b.subject), 200 - ...userBlocked.map((b) => b.authorDid), 201 - ]); 202 - 203 - return blockedAuthorDids; 204 - } 205 - 206 - // Build thread structure from cached data 207 - function buildThreadFromCache( 208 - uri: string, 209 - posts: Map<string, PostDocument>, 210 - postViews: Map<string, SoSprkFeedDefs.PostView>, 211 - parentToChildren: Map<string, string[]>, 212 - blockedAuthorDids: Set<string>, 213 - processedUris = new Set<string>(), 214 - ): 215 - | $Typed<SoSprkFeedDefs.ThreadViewPost> 216 - | $Typed<SoSprkFeedDefs.NotFoundPost> 217 - | $Typed<SoSprkFeedDefs.BlockedPost> { 218 - if (processedUris.has(uri)) { 65 + const skeleton = async (inputs: SkeletonFnInput<Context, Params>) => { 66 + const { ctx, params } = inputs; 67 + const anchor = await ctx.hydrator.resolveUri(params.anchor); 68 + try { 69 + const res = await ctx.dataplane.threads.getThread( 70 + anchor, 71 + params.parentHeight, 72 + getDepth(ctx, anchor, params), 73 + ); 219 74 return { 220 - $type: "so.sprk.feed.defs#notFoundPost", 221 - uri, 222 - notFound: true, 223 - } as $Typed<SoSprkFeedDefs.NotFoundPost>; 75 + anchor, 76 + uris: res.uris, 77 + }; 78 + } catch (err) { 79 + if (isDataPlaneError(err, Code.NotFound)) { 80 + return { 81 + anchor, 82 + uris: [], 83 + }; 84 + } else { 85 + throw err; 86 + } 224 87 } 225 - 226 - processedUris.add(uri); 227 - 228 - const post = posts.get(uri); 229 - const postView = postViews.get(uri); 88 + }; 230 89 231 - if (!post || !postView) { 232 - return { 233 - $type: "so.sprk.feed.defs#notFoundPost", 234 - uri, 235 - notFound: true, 236 - } as $Typed<SoSprkFeedDefs.NotFoundPost>; 237 - } 90 + const hydration = ( 91 + inputs: HydrationFnInput<Context, Params, Skeleton>, 92 + ) => { 93 + const { ctx, params, skeleton } = inputs; 94 + return ctx.hydrator.hydrateThreadPosts( 95 + skeleton.uris.map((uri) => ({ uri })), 96 + params.hydrateCtx, 97 + ); 98 + }; 238 99 239 - if (blockedAuthorDids.has(post.authorDid)) { 240 - return { 241 - $type: "so.sprk.feed.defs#blockedPost", 242 - uri, 243 - blocked: true, 244 - author: { 245 - $type: "so.sprk.feed.defs#blockedAuthor", 246 - did: post.authorDid, 247 - }, 248 - } as $Typed<SoSprkFeedDefs.BlockedPost>; 100 + const presentation = ( 101 + inputs: PresentationFnInput<Context, Params, Skeleton>, 102 + ) => { 103 + const { ctx, params, skeleton, hydration } = inputs; 104 + const thread = ctx.views.thread(skeleton, hydration, { 105 + depth: getDepth(ctx, skeleton.anchor, params), 106 + }); 107 + if (isNotFoundPost(thread)) { 108 + // @TODO technically this could be returned as a NotFoundPost based on lexicon 109 + throw new InvalidRequestError( 110 + `Post not found: ${skeleton.anchor}`, 111 + "NotFound", 112 + ); 249 113 } 250 - 251 - const childUris = parentToChildren.get(uri) || []; 252 - const replies = childUris 253 - .map((childUri) => 254 - buildThreadFromCache( 255 - childUri, 256 - posts, 257 - postViews, 258 - parentToChildren, 259 - blockedAuthorDids, 260 - processedUris, 261 - ) 262 - ) 263 - .filter((reply) => reply !== null); 264 - 265 - return { 266 - $type: "so.sprk.feed.defs#threadViewPost", 267 - post: postView, 268 - replies: replies as Array< 269 - | $Typed<SoSprkFeedDefs.ThreadViewPost> 270 - | $Typed<SoSprkFeedDefs.NotFoundPost> 271 - | $Typed<SoSprkFeedDefs.BlockedPost> 272 - | { $type: string } 273 - >, 274 - threadContext: {}, 275 - } as $Typed<SoSprkFeedDefs.ThreadViewPost>; 276 - } 277 - 278 - export default function (server: Server, ctx: AppContext) { 279 - server.so.sprk.feed.getPostThread({ 280 - auth: ctx.authVerifier.standardOptional, 281 - handler: async ({ params, auth }) => { 282 - try { 283 - const { 284 - uri, 285 - depth = DEFAULT_DEPTH, 286 - parentHeight = DEFAULT_PARENT_HEIGHT, 287 - } = params; 288 - const userDid = auth.credentials.type === "standard" 289 - ? auth.credentials.iss 290 - : undefined; 291 - 292 - // Validate input parameters 293 - if (!uri) { 294 - return { 295 - status: 400, 296 - message: "URI parameter is required", 297 - }; 298 - } 299 - 300 - if (!validateUri(uri)) { 301 - return { 302 - status: 400, 303 - message: "Invalid URI format", 304 - }; 305 - } 306 - 307 - const validatedDepth = Math.min(Math.max(depth, 0), MAX_DEPTH); 308 - const validatedParentHeight = Math.min( 309 - Math.max(parentHeight, 0), 310 - MAX_PARENT_HEIGHT, 311 - ); 312 - 313 - // Get complete thread structure with minimal database queries 314 - const { posts, parentToChildren } = await getCompleteThreadStructure( 315 - ctx, 316 - uri, 317 - validatedDepth, 318 - validatedParentHeight, 319 - ); 320 - 321 - if (posts.size === 0) { 322 - return { 323 - encoding: "application/json", 324 - body: { 325 - thread: { 326 - $type: "so.sprk.feed.defs#notFoundPost", 327 - uri, 328 - notFound: true, 329 - }, 330 - } as OutputSchema, 331 - }; 332 - } 333 - 334 - // Get all author DIDs for block checking 335 - const authorDids = Array.from( 336 - new Set( 337 - Array.from(posts.values()).map((post) => post.authorDid), 338 - ), 339 - ); 340 - 341 - // Batch check all block relationships 342 - const blockedAuthorDids = await batchCheckBlockedPosts( 343 - ctx, 344 - authorDids, 345 - userDid, 346 - ); 347 - 348 - // Check if main post is blocked 349 - const mainPost = posts.get(uri); 350 - if (!mainPost) { 351 - return { 352 - encoding: "application/json", 353 - body: { 354 - thread: { 355 - $type: "so.sprk.feed.defs#notFoundPost", 356 - uri, 357 - notFound: true, 358 - }, 359 - } as OutputSchema, 360 - }; 361 - } 362 - 363 - if (blockedAuthorDids.has(mainPost.authorDid)) { 364 - return { 365 - encoding: "application/json", 366 - body: { 367 - thread: { 368 - $type: "so.sprk.feed.defs#blockedPost", 369 - uri, 370 - blocked: true, 371 - author: { 372 - $type: "so.sprk.feed.defs#blockedAuthor", 373 - did: mainPost.authorDid, 374 - }, 375 - }, 376 - } as OutputSchema, 377 - }; 378 - } 379 - 380 - // Filter out blocked posts and transform remaining posts in batch 381 - const accessiblePosts = Array.from(posts.values()).filter( 382 - (post) => !blockedAuthorDids.has(post.authorDid), 383 - ); 384 - 385 - // Batch transform all accessible posts to PostViews 386 - const postViews = accessiblePosts.length > 0 387 - ? await transformPostsToPostViews(accessiblePosts, ctx, userDid) 388 - : []; 389 - 390 - // Create postViews map for quick lookup 391 - const postViewsMap = new Map( 392 - postViews.map((view) => [view.uri, view]), 393 - ); 114 + return { thread }; 115 + }; 394 116 395 - // Build thread structure from cached data 396 - const thread = buildThreadFromCache( 397 - uri, 398 - posts, 399 - postViewsMap, 400 - parentToChildren, 401 - blockedAuthorDids, 402 - ); 403 - 404 - // Handle parent chain efficiently 405 - if (mainPost.reply?.parent?.uri) { 406 - let currentThread = thread; 407 - let parentUri: string | undefined = mainPost.reply.parent.uri; 117 + type Context = { 118 + dataplane: DataPlane; 119 + hydrator: Hydrator; 120 + views: Views; 121 + cfg: ServerConfig; 122 + }; 408 123 409 - // Build parent chain iteratively 410 - while (parentUri && posts.has(parentUri)) { 411 - const parentPost: PostDocument = posts.get(parentUri)!; 412 - const parentView = postViewsMap.get(parentUri); 124 + type Params = QueryParams & { hydrateCtx: HydrateCtx }; 413 125 414 - if (!parentView || blockedAuthorDids.has(parentPost.authorDid)) { 415 - // Create blocked parent 416 - const blockedParent = { 417 - $type: "so.sprk.feed.defs#blockedPost", 418 - uri: parentUri, 419 - blocked: true, 420 - author: { 421 - $type: "so.sprk.feed.defs#blockedAuthor", 422 - did: parentPost.authorDid, 423 - }, 424 - } as $Typed<SoSprkFeedDefs.BlockedPost>; 126 + type Skeleton = { 127 + anchor: string; 128 + uris: string[]; 129 + }; 425 130 426 - if (currentThread.$type === "so.sprk.feed.defs#threadViewPost") { 427 - (currentThread as $Typed<SoSprkFeedDefs.ThreadViewPost>) 428 - .parent = blockedParent; 429 - } 430 - break; 431 - } 432 - 433 - // Create parent thread 434 - const parentThread = { 435 - $type: "so.sprk.feed.defs#threadViewPost", 436 - post: parentView, 437 - replies: [currentThread], 438 - threadContext: {}, 439 - } as $Typed<SoSprkFeedDefs.ThreadViewPost>; 440 - 441 - if (currentThread.$type === "so.sprk.feed.defs#threadViewPost") { 442 - (currentThread as $Typed<SoSprkFeedDefs.ThreadViewPost>).parent = 443 - parentThread; 444 - } 445 - 446 - currentThread = parentThread; 447 - parentUri = parentPost.reply?.parent?.uri; 448 - } 449 - } 450 - 451 - const response: OutputSchema = { 452 - thread, 453 - }; 454 - 455 - return { 456 - encoding: "application/json", 457 - body: response, 458 - }; 459 - } catch (error) { 460 - console.error("Error in getPostThread:", error); 461 - 462 - // Handle specific error cases 463 - if (error instanceof Error) { 464 - const message = error.message; 465 - 466 - if (message.includes("not found") || message.includes("Not found")) { 467 - return { 468 - status: 404, 469 - error: "NotFound" as const, 470 - message: "Post not found", 471 - }; 472 - } 473 - 474 - if (message.includes("connection") || message.includes("timeout")) { 475 - return { 476 - status: 503, 477 - message: "Database temporarily unavailable", 478 - }; 479 - } 480 - 481 - if (message.includes("validation") || message.includes("invalid")) { 482 - return { 483 - status: 400, 484 - message: "Invalid request parameters", 485 - }; 486 - } 487 - 488 - if (message.includes("limit") || message.includes("quota")) { 489 - return { 490 - status: 429, 491 - message: "Rate limit exceeded", 492 - }; 493 - } 494 - } 495 - 496 - return { 497 - status: 500, 498 - message: "Internal server error", 499 - }; 500 - } 501 - }, 502 - }); 503 - } 131 + const getDepth = (ctx: Context, anchor: string, params: Params) => { 132 + let maxDepth = ctx.cfg.maxThreadDepth; 133 + if (ctx.cfg.bigThreadUris.has(anchor) && ctx.cfg.bigThreadDepth) { 134 + maxDepth = ctx.cfg.bigThreadDepth; 135 + } 136 + return maxDepth ? Math.min(maxDepth, params.depth) : params.depth; 137 + };
+2 -2
api/so/sprk/feed/getStories.ts api/so/sprk/story/getStories.ts
··· 1 1 import { Server } from "../../../../lex/index.ts"; 2 2 import { AppContext } from "../../../../context.ts"; 3 - import { OutputSchema } from "../../../../lex/types/so/sprk/feed/getStories.ts"; 3 + import { OutputSchema } from "../../../../lex/types/so/sprk/story/getStories.ts"; 4 4 import { transformStoriesToStoryViews } from "../../../../utils/story-transformer.ts"; 5 5 import { StoryDocument } from "../../../../data-plane/db/models.ts"; 6 6 ··· 119 119 } 120 120 121 121 export default function (server: Server, ctx: AppContext) { 122 - server.so.sprk.feed.getStories({ 122 + server.so.sprk.story.getStories({ 123 123 auth: ctx.authVerifier.standardOptional, 124 124 handler: async ({ params, auth }) => { 125 125 try {
+5 -5
api/so/sprk/feed/getStoriesTimeline.ts api/so/sprk/story/getTimeline.ts
··· 4 4 import { transformStoriesToStoryViews } from "../../../../utils/story-transformer.ts"; 5 5 import { decodeBase64, encodeBase64 } from "@std/encoding"; 6 6 import type { ProfileViewBasic } from "../../../../lex/types/so/sprk/actor/defs.ts"; 7 - import type * as SoSprkFeedDefs from "../../../../lex/types/so/sprk/feed/defs.ts"; 7 + import type * as SoSprkStoryDefs from "../../../../lex/types/so/sprk/story/defs.ts"; 8 8 9 9 // Constants 10 10 const MAX_LIMIT = 100; ··· 18 18 19 19 interface AuthorStoryGroup { 20 20 author: ProfileViewBasic; 21 - stories: SoSprkFeedDefs.StoryView[]; 21 + stories: SoSprkStoryDefs.StoryView[]; 22 22 } 23 23 24 24 // Helper function to parse cursor ··· 117 117 118 118 // Efficiently group stories by author with proper sorting 119 119 function groupStoriesByAuthor( 120 - storyViews: SoSprkFeedDefs.StoryView[], 121 - ): SoSprkFeedDefs.StoriesByAuthor[] { 120 + storyViews: SoSprkStoryDefs.StoryView[], 121 + ): SoSprkStoryDefs.StoriesByAuthor[] { 122 122 if (storyViews.length === 0) { 123 123 return []; 124 124 } ··· 165 165 } 166 166 167 167 export default function (server: Server, ctx: AppContext) { 168 - server.so.sprk.feed.getStoriesTimeline({ 168 + server.so.sprk.story.getTimeline({ 169 169 auth: ctx.authVerifier.standard, 170 170 handler: async ({ params, auth }) => { 171 171 const { limit: limitParam = DEFAULT_LIMIT, cursor } = params;
+4 -27
api/so/sprk/feed/getSuggestedFeeds.ts
··· 1 1 import { Server } from "../../../../lex/index.ts"; 2 2 import { AppContext } from "../../../../context.ts"; 3 - import { 4 - BskyGeneratorDocument, 5 - SprkGeneratorDocument, 6 - } from "../../../../data-plane/db/models.ts"; 3 + import { GeneratorDocument } from "../../../../data-plane/db/models.ts"; 7 4 import { getProfileView } from "../../../../utils/profile-helper.ts"; 8 5 import type * as SoSprkFeedDefs from "../../../../lex/types/so/sprk/feed/defs.ts"; 9 6 import { decodeBase64, encodeBase64 } from "@std/encoding"; ··· 43 40 44 41 // Transform GeneratorDocument to GeneratorView 45 42 async function transformGeneratorToView( 46 - generator: BskyGeneratorDocument | SprkGeneratorDocument, 43 + generator: GeneratorDocument, 47 44 ctx: AppContext, 48 45 viewerDid?: string, 49 46 ): Promise<SoSprkFeedDefs.GeneratorView> { ··· 118 115 ]; 119 116 } 120 117 121 - // Get both BskyGenerator and SprkGenerator documents 122 - const [bskyGenerators, sprkGenerators] = await Promise.all([ 123 - ctx.db.models.BskyGenerator.find(query) 124 - .sort({ likeCount: -1, _id: -1 }), 125 - ctx.db.models.SprkGenerator.find(query) 126 - .sort({ likeCount: -1, _id: -1 }), 127 - ]); 128 - 129 - // Combine and sort all generators by like count 130 - const allGenerators = [...bskyGenerators, ...sprkGenerators] 131 - .sort((a, b) => { 132 - const aLikes = a.likeCount || 0; 133 - const bLikes = b.likeCount || 0; 134 - if (aLikes !== bLikes) { 135 - return bLikes - aLikes; // Sort by like count descending 136 - } 137 - // If like counts are equal, sort by _id descending for consistency 138 - return String(b._id).localeCompare(String(a._id)); 139 - }); 140 - 141 - // Apply limit and check for more results 142 - const generators = allGenerators.slice(0, limit + 1); 118 + const generators = await ctx.db.models.Generator.find(query) 119 + .sort({ likeCount: -1, _id: -1 }); 143 120 144 121 // Check if there are more results 145 122 const hasMore = generators.length > limit;
+6 -10
data-plane/db/index.ts
··· 62 62 "Post", 63 63 models.postSchema, 64 64 ), 65 + Reply: this.connection.model<models.ReplyDocument>( 66 + "Reply", 67 + models.replySchema, 68 + ), 65 69 Story: this.connection.model<models.StoryDocument>( 66 70 "Story", 67 71 models.storySchema, ··· 90 94 "Repost", 91 95 models.repostSchema, 92 96 ), 93 - Music: this.connection.model<models.MusicDocument>( 94 - "Music", 95 - models.musicSchema, 96 - ), 97 - BskyGenerator: this.connection.model<models.BskyGeneratorDocument>( 98 - "Generator", 99 - models.bskyGeneratorSchema, 100 - ), 101 - SprkGenerator: this.connection.model<models.SprkGeneratorDocument>( 97 + Generator: this.connection.model<models.GeneratorDocument>( 102 98 "SprkGenerator", 103 - models.sprkGeneratorSchema, 99 + models.generatorSchema, 104 100 ), 105 101 Takedown: this.connection.model<models.TakedownDocument>( 106 102 "Takedown",
+80 -166
data-plane/db/models.ts
··· 40 40 ref: { $link: string }; 41 41 } 42 42 43 - export interface EmbedImage { 43 + export interface ImageMedia extends MediaRef { 44 44 alt: string; 45 - image: MediaRef; 46 45 aspectRatio: { 47 46 width: number; 48 47 height: number; 49 48 }; 50 49 } 51 50 52 - export interface EmbedVideo extends MediaRef { 51 + export interface VideoMedia extends MediaRef { 53 52 alt: string; 54 53 aspectRatio: { 55 54 width: number; ··· 165 164 subject: { type: String, required: true, index: true }, 166 165 }); 167 166 168 - interface PinnedPost { 167 + interface RecordRef { 169 168 uri: string; 170 169 cid: string; 171 170 } ··· 176 175 avatar?: MediaRef; 177 176 banner?: MediaRef; 178 177 labels?: Label[]; 179 - pinnedPost?: PinnedPost; 178 + pinnedPost?: RecordRef; 180 179 postsCount: number; 181 180 followersCount: number; 182 181 followsCount: number; ··· 203 202 204 203 export interface AudioDocument extends AuthoredDocument { 205 204 sound: MediaRef; 206 - origin?: { 207 - uri: string; 208 - cid: string; 209 - }; 205 + origin?: RecordRef; 210 206 title: string; 211 207 details?: { 212 208 artist?: string; ··· 219 215 export const audioSchema = new Schema<AudioDocument>({ 220 216 ...authoredSchema, 221 217 sound: { type: Object, required: true }, 222 - origin: { 223 - uri: { type: String, required: true }, 224 - cid: { type: String, required: true }, 225 - }, 218 + origin: { type: Object, required: false }, 226 219 title: { type: String, required: true }, 227 220 details: { type: Object, required: false }, 228 221 labels: { type: [Object], required: false }, ··· 230 223 }); 231 224 232 225 export interface RepostDocument extends AuthoredDocument { 233 - subject: { 234 - uri: string; 235 - cid: string; 236 - }; 226 + subject: RecordRef; 237 227 via?: string | null; 238 228 viaCid?: string | null; 239 229 } 240 230 241 231 export const repostSchema = new Schema<RepostDocument>({ 242 232 ...authoredSchema, 243 - subject: { 244 - uri: { type: String, required: true }, 245 - cid: { type: String, required: true }, 246 - }, 233 + subject: { type: Object, required: true }, 247 234 via: { type: String, required: false }, 248 235 viaCid: { type: String, required: false }, 249 236 }); 250 237 251 - export interface MusicDocument extends AuthoredDocument { 252 - sound: string; 253 - title: string; 254 - author: string; 255 - releaseDate: string; 256 - album?: string; 257 - recordLabel?: string; 258 - cover?: string; 259 - text?: string; 260 - copyright?: string[]; 238 + export interface PostMedia { 239 + $type: string; 240 + video?: VideoMedia; 241 + images?: ImageMedia[]; 242 + } 243 + 244 + export interface StoryMedia { 245 + $type: string; 246 + video?: VideoMedia; 247 + image?: ImageMedia; 248 + } 249 + 250 + export interface Caption { 251 + text: string; 261 252 facets?: Facet[]; 253 + } 254 + 255 + export interface PostDocument extends AuthoredDocument { 256 + caption?: Caption; 257 + media?: PostMedia; 258 + sound?: RecordRef; 259 + langs?: string[]; 262 260 labels?: Label[]; 263 261 tags?: string[]; 262 + likeCount: number; 263 + replyCount: number; 264 + repostCount: number; 264 265 } 265 266 266 - export const musicSchema = new Schema<MusicDocument>({ 267 + export const postSchema = new Schema<PostDocument>({ 267 268 ...authoredSchema, 268 - sound: { type: String, required: true }, 269 - title: { type: String, required: true }, 270 - author: { type: String, required: true }, 271 - releaseDate: { type: String, required: true }, 272 - album: { type: String, required: false }, 273 - recordLabel: { type: String, required: false }, 274 - cover: { type: String, required: false }, 275 - text: { type: String, required: false }, 276 - copyright: { type: [String], required: false }, 277 - facets: { type: [Object], required: false }, 278 - labels: { type: [Object], required: false }, 279 - tags: { type: [String], required: false }, 269 + caption: { 270 + type: { 271 + text: { type: String, required: true }, 272 + facets: { type: [Object], required: false, default: [] }, 273 + }, 274 + required: false, 275 + }, 276 + media: { type: Object, required: false }, 277 + sound: { 278 + type: { 279 + uri: { type: String, required: true }, 280 + cid: { type: String, required: true }, 281 + }, 282 + required: false, 283 + }, 284 + langs: { type: [String], required: false, default: [] }, 285 + labels: { type: [Object], required: false, default: [] }, 286 + tags: { type: [String], required: false, default: [] }, 287 + likeCount: { type: Number, required: true, default: 0 }, 288 + replyCount: { type: Number, required: true, default: 0 }, 289 + repostCount: { type: Number, required: true, default: 0 }, 280 290 }); 281 291 282 - export interface PostEmbed { 283 - $type: string; 284 - record?: { 285 - uri: string; 286 - cid: string; 287 - }; 288 - alt?: string; 289 - video?: EmbedVideo; 290 - images?: Array<EmbedImage>; 291 - external?: { 292 - uri: string; 293 - title: string; 294 - description: string; 295 - thumb?: MediaRef; 296 - }; 297 - recordWithMedia?: { 298 - record: { 299 - uri: string; 300 - cid: string; 301 - }; 302 - media: { 303 - $type: string; 304 - images?: Array<{ 305 - alt: string; 306 - image: MediaRef; 307 - }>; 308 - }; 309 - }; 310 - } 292 + // Compound indexes for more efficient queries 293 + postSchema.index({ authorDid: 1, createdAt: -1 }); 294 + postSchema.index({ tags: 1, createdAt: -1 }); 311 295 312 - export interface PostDocument extends AuthoredDocument { 296 + export interface ReplyDocument extends AuthoredDocument { 313 297 text?: string; 314 298 facets?: Facet[]; 315 - reply: { 316 - root: { 317 - uri: string; 318 - cid: string; 319 - }; 320 - parent: { 321 - uri: string; 322 - cid: string; 323 - }; 324 - } | null; 325 - embed: PostEmbed | null; 326 - sound: { 327 - uri: string; 328 - cid: string; 329 - } | null; 299 + reply?: { 300 + root: RecordRef; 301 + parent: RecordRef; 302 + }; 303 + media?: ImageMedia; 330 304 langs?: string[]; 331 - labels: Label[] | null; 332 - tags?: string[]; 305 + labels?: Label[]; 333 306 likeCount: number; 334 307 replyCount: number; 335 - repostCount: number; 336 308 } 337 309 338 - export const postSchema = new Schema<PostDocument>({ 310 + export const replySchema = new Schema<ReplyDocument>({ 339 311 ...authoredSchema, 340 312 text: { type: String, required: false }, 341 313 facets: { type: [Object], required: false, default: [] }, ··· 351 323 }, 352 324 }, 353 325 required: false, 354 - default: null, 355 326 }, 356 - embed: { type: Object, required: false, default: null }, 357 - sound: { 358 - type: { 359 - uri: { type: String, required: true }, 360 - cid: { type: String, required: true }, 361 - }, 362 - required: false, 363 - default: null, 364 - }, 327 + media: { type: Object, required: false }, 365 328 langs: { type: [String], required: false, default: [] }, 366 - labels: { type: [Object], required: false, default: null }, 367 - tags: { type: [String], required: false, default: [] }, 329 + labels: { type: [Object], required: false, default: [] }, 368 330 likeCount: { type: Number, required: true, default: 0 }, 369 331 replyCount: { type: Number, required: true, default: 0 }, 370 - repostCount: { type: Number, required: true, default: 0 }, 371 332 }); 372 333 373 - // Compound indexes for more efficient queries 374 - postSchema.index({ authorDid: 1, createdAt: -1 }); 375 - postSchema.index({ tags: 1, createdAt: -1 }); 334 + replySchema.index({ reply: 1, createdAt: -1 }); 376 335 377 336 export interface StoryDocument extends AuthoredDocument { 378 - media: PostEmbed | null; 379 - sound: { 380 - uri: string; 381 - cid: string; 382 - } | null; 383 - labels: Label[] | null; 384 - tags: string[]; 337 + media: StoryMedia; 338 + sound?: RecordRef; 339 + labels?: Label[]; 385 340 } 386 341 387 342 export const storySchema = new Schema<StoryDocument>({ 388 343 ...authoredSchema, 389 - media: { type: Object, required: false, default: null }, 344 + media: { type: Object, required: true }, 390 345 sound: { 391 346 type: { 392 347 uri: { type: String, required: true }, 393 348 cid: { type: String, required: true }, 394 349 }, 395 350 required: false, 396 - default: null, 397 351 }, 398 - labels: { type: [Object], required: false, default: null }, 399 - tags: { type: [String], required: false, default: [] }, 352 + labels: { type: [Object], required: false, default: [] }, 400 353 }); 401 354 402 355 storySchema.index({ authorDid: 1, createdAt: -1 }); ··· 414 367 repostSchema.index({ authorDid: 1, createdAt: -1 }); 415 368 repostSchema.index({ "subject.uri": 1, createdAt: -1 }); 416 369 417 - musicSchema.index({ authorDid: 1, createdAt: -1 }); 418 - musicSchema.index({ tags: 1, createdAt: -1 }); 419 - 420 - export interface BskyGeneratorDocument extends AuthoredDocument { 370 + export interface GeneratorDocument extends AuthoredDocument { 421 371 displayName: string; 422 372 description?: string; 423 373 descriptionFacets?: Facet[]; 424 374 avatar?: MediaRef; 425 375 acceptsInteractions?: boolean; 426 - contentMode?: "video" | "unspecified"; 427 376 labels?: Label[]; 428 377 likeCount: number; 429 378 } 430 379 431 - export const bskyGeneratorSchema = new Schema<BskyGeneratorDocument>({ 380 + export const generatorSchema = new Schema<GeneratorDocument>({ 432 381 ...authoredSchema, 433 382 displayName: { type: String, required: true }, 434 383 description: { type: String, required: false }, 435 384 descriptionFacets: { type: [Object], required: false }, 436 385 avatar: { type: Object, required: false }, 437 386 acceptsInteractions: { type: Boolean, required: false }, 438 - contentMode: { 439 - type: String, 440 - enum: ["video", "unspecified"], 441 - required: false, 442 - }, 443 387 labels: { type: [Object], required: false }, 444 388 likeCount: { type: Number, required: false, default: 0 }, 445 389 }); 446 390 447 391 // Add compound indexes for Generator 448 - bskyGeneratorSchema.index({ authorDid: 1, createdAt: -1 }); 449 - 450 - // @TODO: Currently this is almost identical to bskyGeneratorSchema but 451 - // as we make the feed lex meaningfully different from Bsky's feed lex, 452 - // we will add more fields and different behavior such as feed modes & 453 - // custom user values. 454 - export interface SprkGeneratorDocument extends AuthoredDocument { 455 - displayName: string; 456 - description?: string; 457 - descriptionFacets?: Facet[]; 458 - avatar?: MediaRef; 459 - acceptsInteractions?: boolean; 460 - labels?: Label[]; 461 - likeCount: number; 462 - } 463 - 464 - export const sprkGeneratorSchema = new Schema<SprkGeneratorDocument>({ 465 - ...authoredSchema, 466 - displayName: { type: String, required: true }, 467 - description: { type: String, required: false }, 468 - descriptionFacets: { type: [Object], required: false }, 469 - avatar: { type: Object, required: false }, 470 - acceptsInteractions: { type: Boolean, required: false }, 471 - labels: { type: [Object], required: false }, 472 - likeCount: { type: Number, required: false, default: 0 }, 473 - }); 474 - 475 - // Add compound indexes for Generator 476 - sprkGeneratorSchema.index({ authorDid: 1, createdAt: -1 }); 392 + generatorSchema.index({ authorDid: 1, createdAt: -1 }); 477 393 478 394 export interface TakedownDocument extends Document { 479 395 targetUri: string; ··· 608 524 profileSchema, 609 525 likeSchema, 610 526 postSchema, 527 + replySchema, 611 528 repostSchema, 612 529 followSchema, 613 530 blockSchema, 614 - bskyGeneratorSchema, 615 - sprkGeneratorSchema, 531 + generatorSchema, 616 532 audioSchema, 617 - musicSchema, 618 533 storySchema, 619 534 ] as Schema[]).forEach((s) => s.plugin(addAuthor)); 620 535 ··· 623 538 DuplicateRecord: Model<DuplicateRecordDocument>; 624 539 Like: Model<LikeDocument>; 625 540 Post: Model<PostDocument>; 541 + Reply: Model<ReplyDocument>; 626 542 Story: Model<StoryDocument>; 627 543 Follow: Model<FollowDocument>; 628 544 Block: Model<BlockDocument>; 629 545 Profile: Model<ProfileDocument>; 630 546 Audio: Model<AudioDocument>; 631 547 Repost: Model<RepostDocument>; 632 - Music: Model<MusicDocument>; 633 - BskyGenerator: Model<BskyGeneratorDocument>; 634 - SprkGenerator: Model<SprkGeneratorDocument>; 548 + Generator: Model<GeneratorDocument>; 635 549 Takedown: Model<TakedownDocument>; 636 550 RepoTakedown: Model<RepoTakedownDocument>; 637 551 BlobTakedown: Model<BlobTakedownDocument>;
+11 -11
data-plane/indexing/index.ts
··· 16 16 import { Database } from "../db/index.ts"; 17 17 import { ActorDocument } from "../db/models.ts"; 18 18 import * as Block from "./plugins/block.ts"; 19 - import * as Generator from "./plugins/generator/index.ts"; 19 + import * as Generator from "./plugins/generator.ts"; 20 20 import * as Follow from "./plugins/follow.ts"; 21 21 import * as Like from "./plugins/like.ts"; 22 22 import * as Post from "./plugins/post.ts"; 23 + import * as Reply from "./plugins/reply.ts"; 23 24 import * as Profile from "./plugins/profile.ts"; 24 25 import * as Repost from "./plugins/repost.ts"; 25 26 import * as Story from "./plugins/story.ts"; 26 27 import * as Audio from "./plugins/audio.ts"; 27 - import * as Music from "./plugins/music.ts"; 28 28 import { RecordProcessor } from "./processor.ts"; 29 29 import { Logger } from "@logtape/logtape"; 30 + import { ServerConfig } from "../../config.ts"; 30 31 31 32 export class IndexingService { 32 33 records: { 33 34 post: Post.PluginType; 35 + reply: Reply.PluginType; 34 36 like: Like.PluginType; 35 37 repost: Repost.PluginType; 36 38 follow: Follow.PluginType; 37 39 profile: Profile.PluginType; 38 40 block: Block.PluginType; 39 - bskyGenerator: Generator.Bsky.PluginType; 40 - sprkGenerator: Generator.Sprk.PluginType; 41 + generator: Generator.PluginType; 41 42 story: Story.PluginType; 42 43 audio: Audio.PluginType; 43 - music: Music.PluginType; 44 44 }; 45 45 46 46 constructor( 47 47 public db: Database, 48 + public cfg: ServerConfig, 48 49 public idResolver: IdResolver, 49 50 public background: BackgroundQueue, 50 51 public logger: Logger, 51 52 ) { 52 53 this.records = { 53 54 post: Post.makePlugin(this.db, this.background), 55 + reply: Reply.makePlugin(this.db, this.background), 54 56 like: Like.makePlugin(this.db, this.background), 55 57 repost: Repost.makePlugin(this.db, this.background), 56 58 follow: Follow.makePlugin(this.db, this.background), 57 59 profile: Profile.makePlugin(this.db, this.background), 58 60 block: Block.makePlugin(this.db, this.background), 59 - bskyGenerator: Generator.Bsky.makePlugin(this.db, this.background), 60 - sprkGenerator: Generator.Sprk.makePlugin(this.db, this.background), 61 + generator: Generator.makePlugin(this.db, this.background), 61 62 story: Story.makePlugin(this.db, this.background), 62 63 audio: Audio.makePlugin(this.db, this.background), 63 - music: Music.makePlugin(this.db, this.background), 64 64 }; 65 65 } 66 66 67 67 transact(txn: Database) { 68 68 return new IndexingService( 69 69 txn, 70 + this.cfg, 70 71 this.idResolver, 71 72 this.background, 72 73 this.logger, ··· 303 304 await this.db.models.Follow.deleteMany({ authorDid: did }); 304 305 await this.db.models.Repost.deleteMany({ authorDid: did }); 305 306 await this.db.models.Like.deleteMany({ authorDid: did }); 306 - await this.db.models.BskyGenerator.deleteMany({ authorDid: did }); 307 - await this.db.models.SprkGenerator.deleteMany({ authorDid: did }); 307 + await this.db.models.Generator.deleteMany({ authorDid: did }); 308 308 await this.db.models.Story.deleteMany({ authorDid: did }); 309 309 await this.db.models.Audio.deleteMany({ authorDid: did }); 310 - await this.db.models.Music.deleteMany({ authorDid: did }); 311 310 await this.db.models.Block.deleteMany({ authorDid: did }); 312 311 await this.db.models.Post.deleteMany({ authorDid: did }); 312 + await this.db.models.Reply.deleteMany({ authorDid: did }); 313 313 } 314 314 } 315 315
+1 -2
data-plane/indexing/plugins/audio.ts
··· 6 6 import { Database } from "../../db/index.ts"; 7 7 import { AudioDocument } from "../../db/models.ts"; 8 8 import { RecordProcessor } from "../processor.ts"; 9 - import { normalizeObject } from "../../../utils/embed-normalizer.ts"; 10 9 11 10 const lexId = lex.ids.SoSprkSoundAudio; 12 11 type IndexedAudio = AudioDocument; ··· 22 21 uri: uri.toString(), 23 22 cid: cid.toString(), 24 23 authorDid: uri.host, 25 - sound: normalizeObject(obj.sound) || null, 24 + sound: obj.sound || null, 26 25 origin: obj.origin ? { uri: obj.origin.uri, cid: obj.origin.cid } : null, 27 26 title: obj.title, 28 27 labels: obj.labels || null,
+2 -2
data-plane/indexing/plugins/block.ts
··· 1 1 import { CID } from "multiformats/cid"; 2 2 import { AtUri, normalizeDatetimeAlways } from "@atp/syntax"; 3 3 import * as lex from "../../../lex/lexicons.ts"; 4 - import * as Block from "../../../lex/types/app/bsky/graph/block.ts"; 4 + import * as Block from "../../../lex/types/so/sprk/graph/block.ts"; 5 5 import { BackgroundQueue } from "../../background.ts"; 6 6 import { Database } from "../../db/index.ts"; 7 7 import { BlockDocument } from "../../db/models.ts"; 8 8 import { RecordProcessor } from "../processor.ts"; 9 9 10 - const lexId = lex.ids.AppBskyGraphBlock; 10 + const lexId = lex.ids.SoSprkGraphBlock; 11 11 type IndexedBlock = BlockDocument; 12 12 13 13 const insertFn = async (
+2 -2
data-plane/indexing/plugins/follow.ts
··· 1 1 import { CID } from "multiformats/cid"; 2 2 import { AtUri, normalizeDatetimeAlways } from "@atp/syntax"; 3 3 import * as lex from "../../../lex/lexicons.ts"; 4 - import * as Follow from "../../../lex/types/app/bsky/graph/follow.ts"; 4 + import * as Follow from "../../../lex/types/so/sprk/graph/follow.ts"; 5 5 import { BackgroundQueue } from "../../background.ts"; 6 6 import { Database } from "../../db/index.ts"; 7 7 import { FollowDocument } from "../../db/models.ts"; 8 8 import { RecordProcessor } from "../processor.ts"; 9 9 10 - const lexId = lex.ids.AppBskyGraphFollow; 10 + const lexId = lex.ids.SoSprkGraphFollow; 11 11 type IndexedFollow = FollowDocument; 12 12 13 13 const insertFn = async (
-103
data-plane/indexing/plugins/generator/bsky.ts
··· 1 - import { CID } from "multiformats/cid"; 2 - import { AtUri, normalizeDatetimeAlways } from "@atp/syntax"; 3 - import * as lex from "../../../../lex/lexicons.ts"; 4 - import * as FeedGenerator from "../../../../lex/types/app/bsky/feed/generator.ts"; 5 - import { BackgroundQueue } from "../../../background.ts"; 6 - import { Database } from "../../../db/index.ts"; 7 - import { BskyGeneratorDocument } from "../../../db/models.ts"; 8 - import { RecordProcessor } from "../../processor.ts"; 9 - 10 - const lexId = lex.ids.AppBskyFeedGenerator; 11 - type IndexedFeedGenerator = BskyGeneratorDocument; 12 - 13 - const insertFn = async ( 14 - db: Database, 15 - uri: AtUri, 16 - cid: CID, 17 - obj: FeedGenerator.Record, 18 - timestamp: string, 19 - ): Promise<IndexedFeedGenerator | null> => { 20 - // Extract and clean avatar to ensure it matches MediaRef format 21 - const avatar = obj.avatar 22 - ? { 23 - $type: "blob", 24 - ref: (obj.avatar as unknown as Record<string, unknown>)?.ref || null, 25 - } 26 - : null; 27 - 28 - const generator = { 29 - uri: uri.toString(), 30 - cid: cid.toString(), 31 - authorDid: uri.host, 32 - did: obj.did, 33 - displayName: obj.displayName, 34 - description: obj.description || null, 35 - descriptionFacets: obj.descriptionFacets || null, 36 - avatar, 37 - acceptsInteractions: obj.acceptsInteractions || null, 38 - labels: null, // Will be populated by label processing 39 - contentMode: obj.contentMode, 40 - createdAt: normalizeDatetimeAlways(obj.createdAt), 41 - indexedAt: timestamp, 42 - }; 43 - 44 - // Use findOneAndUpdate with upsert to handle potential duplicate key errors 45 - try { 46 - const insertedGenerator = await db.models.BskyGenerator.findOneAndUpdate( 47 - { uri: generator.uri }, 48 - generator, 49 - { upsert: true, new: true }, 50 - ); 51 - return insertedGenerator; 52 - } catch (err) { 53 - // Handle duplicate key errors gracefully 54 - const mongoError = err as { code?: number }; 55 - if (mongoError.code === 11000) { 56 - return null; // Silently skip duplicates 57 - } 58 - throw err; 59 - } 60 - }; 61 - 62 - const findDuplicate = (): AtUri | null => { 63 - return null; 64 - }; 65 - 66 - const notifsForInsert = () => { 67 - return []; 68 - }; 69 - 70 - const deleteFn = async ( 71 - db: Database, 72 - uri: AtUri, 73 - ): Promise<IndexedFeedGenerator | null> => { 74 - const deleted = await db.models.BskyGenerator.findOneAndDelete({ 75 - uri: uri.toString(), 76 - }); 77 - return deleted; 78 - }; 79 - 80 - const notifsForDelete = () => { 81 - return { notifs: [], toDelete: [] }; 82 - }; 83 - 84 - export type PluginType = RecordProcessor< 85 - FeedGenerator.Record, 86 - IndexedFeedGenerator 87 - >; 88 - 89 - export const makePlugin = ( 90 - db: Database, 91 - background: BackgroundQueue, 92 - ): PluginType => { 93 - return new RecordProcessor(db, background, { 94 - lexId, 95 - insertFn, 96 - findDuplicate, 97 - deleteFn, 98 - notifsForInsert, 99 - notifsForDelete, 100 - }); 101 - }; 102 - 103 - export default makePlugin;
-2
data-plane/indexing/plugins/generator/index.ts
··· 1 - export * as Bsky from "./bsky.ts"; 2 - export * as Sprk from "./sprk.ts";
+9 -9
data-plane/indexing/plugins/generator/sprk.ts data-plane/indexing/plugins/generator.ts
··· 1 1 import { CID } from "multiformats/cid"; 2 2 import { AtUri, normalizeDatetimeAlways } from "@atp/syntax"; 3 - import * as lex from "../../../../lex/lexicons.ts"; 4 - import * as FeedGenerator from "../../../../lex/types/so/sprk/feed/generator.ts"; 5 - import { BackgroundQueue } from "../../../background.ts"; 6 - import { Database } from "../../../db/index.ts"; 7 - import { SprkGeneratorDocument } from "../../../db/models.ts"; 8 - import { RecordProcessor } from "../../processor.ts"; 3 + import * as lex from "../../../lex/lexicons.ts"; 4 + import * as FeedGenerator from "../../../lex/types/so/sprk/feed/generator.ts"; 5 + import { BackgroundQueue } from "../../background.ts"; 6 + import { Database } from "../../db/index.ts"; 7 + import { GeneratorDocument } from "../../db/models.ts"; 8 + import { RecordProcessor } from "../processor.ts"; 9 9 10 10 const lexId = lex.ids.SoSprkFeedGenerator; 11 - type IndexedFeedGenerator = SprkGeneratorDocument; 11 + type IndexedFeedGenerator = GeneratorDocument; 12 12 13 13 const insertFn = async ( 14 14 db: Database, ··· 42 42 43 43 // Use findOneAndUpdate with upsert to handle potential duplicate key errors 44 44 try { 45 - const insertedGenerator = await db.models.SprkGenerator.findOneAndUpdate( 45 + const insertedGenerator = await db.models.Generator.findOneAndUpdate( 46 46 { uri: generator.uri }, 47 47 generator, 48 48 { upsert: true, new: true }, ··· 70 70 db: Database, 71 71 uri: AtUri, 72 72 ): Promise<IndexedFeedGenerator | null> => { 73 - const deleted = await db.models.SprkGenerator.findOneAndDelete({ 73 + const deleted = await db.models.Generator.findOneAndDelete({ 74 74 uri: uri.toString(), 75 75 }); 76 76 return deleted;
+3 -15
data-plane/indexing/plugins/like.ts
··· 147 147 const subjectUri = new AtUri(like.subject); 148 148 149 149 // Check if this is a feed generator 150 - if (subjectUri.collection === "app.bsky.feed.generator") { 151 - const existingGenerator = await db.models.BskyGenerator.findOne({ 150 + if (subjectUri.collection === "so.sprk.feed.generator") { 151 + const existingGenerator = await db.models.Generator.findOne({ 152 152 uri: like.subject, 153 153 }); 154 154 155 155 if (existingGenerator) { 156 - await db.models.BskyGenerator.findOneAndUpdate( 157 - { uri: like.subject }, 158 - { $set: { likeCount } }, 159 - { new: true }, 160 - ); 161 - } 162 - } else if (subjectUri.collection === "so.sprk.feed.generator") { 163 - const existingSprkGenerator = await db.models.SprkGenerator.findOne({ 164 - uri: like.subject, 165 - }); 166 - 167 - if (existingSprkGenerator) { 168 - await db.models.SprkGenerator.findOneAndUpdate( 156 + await db.models.Generator.findOneAndUpdate( 169 157 { uri: like.subject }, 170 158 { $set: { likeCount } }, 171 159 { new: true },
-97
data-plane/indexing/plugins/music.ts
··· 1 - import { CID } from "multiformats/cid"; 2 - import { AtUri, normalizeDatetimeAlways } from "@atp/syntax"; 3 - import * as lex from "../../../lex/lexicons.ts"; 4 - import * as Music from "../../../lex/types/so/sprk/feed/music.ts"; 5 - import { BackgroundQueue } from "../../background.ts"; 6 - import { Database } from "../../db/index.ts"; 7 - import { MusicDocument } from "../../db/models.ts"; 8 - import { RecordProcessor } from "../processor.ts"; 9 - import { normalizeObject } from "../../../utils/embed-normalizer.ts"; 10 - 11 - const lexId = lex.ids.SoSprkFeedMusic; 12 - type IndexedMusic = MusicDocument; 13 - 14 - const insertFn = async ( 15 - db: Database, 16 - uri: AtUri, 17 - cid: CID, 18 - obj: Music.Record, 19 - timestamp: string, 20 - ): Promise<IndexedMusic | null> => { 21 - const music = { 22 - uri: uri.toString(), 23 - cid: cid.toString(), 24 - authorDid: uri.host, 25 - sound: normalizeObject(obj.sound?.ref) || null, 26 - title: obj.title, 27 - author: obj.author, 28 - releaseDate: obj.releaseDate, 29 - album: obj.album || null, 30 - recordLabel: obj.recordLabel || null, 31 - cover: normalizeObject(obj.cover?.ref) || null, 32 - text: obj.text || null, 33 - copyright: obj.copyright || null, 34 - facets: obj.facets || [], 35 - labels: obj.labels || null, 36 - tags: obj.tags || [], 37 - createdAt: normalizeDatetimeAlways(obj.createdAt), 38 - indexedAt: timestamp, 39 - }; 40 - 41 - // Use findOneAndUpdate with upsert to handle potential duplicate key errors 42 - try { 43 - const insertedMusic = await db.models.Music.findOneAndUpdate( 44 - { uri: music.uri }, 45 - music, 46 - { upsert: true, new: true }, 47 - ); 48 - return insertedMusic; 49 - } catch (err) { 50 - // Handle duplicate key errors gracefully 51 - const mongoError = err as { code?: number }; 52 - if (mongoError.code === 11000) { 53 - return null; // Silently skip duplicates 54 - } 55 - throw err; 56 - } 57 - }; 58 - 59 - const findDuplicate = (): AtUri | null => { 60 - return null; 61 - }; 62 - 63 - const notifsForInsert = () => { 64 - return []; 65 - }; 66 - 67 - const deleteFn = async ( 68 - db: Database, 69 - uri: AtUri, 70 - ): Promise<IndexedMusic | null> => { 71 - const deleted = await db.models.Music.findOneAndDelete({ 72 - uri: uri.toString(), 73 - }); 74 - return deleted; 75 - }; 76 - 77 - const notifsForDelete = () => { 78 - return { notifs: [], toDelete: [] }; 79 - }; 80 - 81 - export type PluginType = RecordProcessor<Music.Record, IndexedMusic>; 82 - 83 - export const makePlugin = ( 84 - db: Database, 85 - background: BackgroundQueue, 86 - ): PluginType => { 87 - return new RecordProcessor(db, background, { 88 - lexId, 89 - insertFn, 90 - findDuplicate, 91 - deleteFn, 92 - notifsForInsert, 93 - notifsForDelete, 94 - }); 95 - }; 96 - 97 - export default makePlugin;
+39 -166
data-plane/indexing/plugins/post.ts
··· 1 1 import { CID } from "multiformats/cid"; 2 2 import { AtUri } from "@atp/syntax"; 3 3 import * as lex from "../../../lex/lexicons.ts"; 4 - import { isMain as isEmbedImage } from "../../../lex/types/so/sprk/embed/images.ts"; 5 - import { isMain as isEmbedVideo } from "../../../lex/types/so/sprk/embed/video.ts"; 4 + import { 5 + isMain as isMediaImages, 6 + Main as MediaImages, 7 + } from "../../../lex/types/so/sprk/media/images.ts"; 8 + import { Main as MediaImage } from "../../../lex/types/so/sprk/media/image.ts"; 6 9 import { 7 - Record as PostRecord, 8 - ReplyRef, 9 - } from "../../../lex/types/so/sprk/feed/post.ts"; 10 + isMain as isMediaVideo, 11 + Main as MediaVideo, 12 + } from "../../../lex/types/so/sprk/media/video.ts"; 13 + import { Record as PostRecord } from "../../../lex/types/so/sprk/feed/post.ts"; 10 14 import { Record as GateRecord } from "../../../lex/types/so/sprk/feed/threadgate.ts"; 11 15 import { 12 16 isLink, ··· 15 19 import { BackgroundQueue } from "../../background.ts"; 16 20 import { Database } from "../../db/index.ts"; 17 21 import { PostDocument } from "../../db/models.ts"; 18 - import { 19 - getAncestorsAndSelf, 20 - getDescendents, 21 - invalidReplyRoot as checkInvalidReplyRoot, 22 - } from "../../util.ts"; 22 + import { getDescendents } from "../../util.ts"; 23 23 import { RecordProcessor } from "../processor.ts"; 24 - import { 25 - normalizeEmbed, 26 - normalizeObject, 27 - } from "../../../utils/embed-normalizer.ts"; 28 - import { jsonToLex } from "@atp/lexicon"; 29 24 30 25 type PostAncestor = { 31 26 uri: string; ··· 41 36 type IndexedPost = { 42 37 post: PostDocument; 43 38 facets?: { type: "mention" | "link"; value: string }[]; 44 - embeds?: Array<{ 45 - postUri: string; 39 + medias?: Array<{ 46 40 position?: number; 47 41 imageCid?: string; 48 42 alt?: string | null; 49 - uri?: string; 50 - title?: string; 51 - description?: string; 52 43 thumbCid?: string | null; 53 - embedUri?: string; 54 - embedCid?: string; 55 44 videoCid?: string; 56 45 }>; 57 46 ancestors?: PostAncestor[]; ··· 80 69 ); 81 70 } 82 71 83 - console.log("DEBUG: Post embed:", JSON.stringify(obj.embed, null, 2)); 84 - 85 - const normalizedEmbed = normalizeEmbed(obj.embed); 86 - console.log( 87 - "DEBUG: Normalized embed:", 88 - JSON.stringify(normalizedEmbed, null, 2), 89 - ); 72 + console.log("DEBUG: Post media:", JSON.stringify(obj.media, null, 2)); 90 73 91 74 const post = { 92 75 uri: uri.toString(), 93 76 cid: cid.toString(), 94 77 authorDid: uri.host, 95 - text: obj.text || "", 96 - facets: obj.facets || [], 97 - reply: obj.reply 98 - ? { 99 - root: { 100 - uri: obj.reply.root.uri, 101 - cid: obj.reply.root.cid, 102 - }, 103 - parent: { 104 - uri: obj.reply.parent.uri, 105 - cid: obj.reply.parent.cid, 106 - }, 107 - } 108 - : null, 109 - embed: normalizedEmbed || null, 110 - sound: normalizeObject(obj.sound) || null, 78 + text: obj.caption?.text || "", 79 + facets: obj.caption?.facets || [], 80 + media: obj.media || null, 81 + sound: obj.sound || null, 111 82 langs: obj.langs || [], 112 83 labels: obj.labels || null, 113 84 tags: obj.tags || [], ··· 123 94 { upsert: true, new: true }, 124 95 ); 125 96 126 - if (obj.reply) { 127 - const { invalidReplyRoot } = await validateReply( 128 - db, 129 - obj.reply, 130 - ); 131 - if (invalidReplyRoot) { 132 - Object.assign(insertedPost, { invalidReplyRoot }); 133 - await db.models.Post.updateOne( 134 - { uri: post.uri }, 135 - { invalidReplyRoot }, 136 - ); 137 - } 138 - } 139 - 140 - const facets = (obj.facets || []) 97 + const facets = (obj.caption?.facets || []) 141 98 .flatMap((facet) => facet.features) 142 99 .flatMap((feature) => { 143 100 if (isMention(feature)) { ··· 155 112 return []; 156 113 }); 157 114 158 - // Embed processing - embeds are stored inline in the Post model 159 - const embeds: Array<{ 160 - postUri: string; 115 + // Media processing - medias are stored inline in the Post model 116 + const medias: Array<{ 161 117 position?: number; 162 118 imageCid?: string; 163 119 alt?: string | null; 164 - uri?: string; 165 - title?: string; 166 - description?: string; 167 120 thumbCid?: string | null; 168 - embedUri?: string; 169 - embedCid?: string; 170 121 videoCid?: string; 171 122 }> = []; 172 - const postEmbeds = separateEmbeds(obj.embed); 173 - for (const postEmbed of postEmbeds) { 174 - if (isEmbedImage(postEmbed)) { 175 - const { images } = postEmbed; 176 - const imagesEmbed = images.map(( 177 - img: { image: { ref: { toString: () => string } }; alt: string }, 123 + const postMedias = separateMedia(obj.media); 124 + for (const postMedia of postMedias) { 125 + if (isMediaImages(postMedia)) { 126 + const { images } = postMedia as MediaImages; 127 + const imagesMedia = images.map(( 128 + img: MediaImage, 178 129 i: number, 179 130 ) => ({ 180 - postUri: uri.toString(), 181 131 position: i, 182 132 imageCid: img.image.ref.toString(), 183 133 alt: img.alt, 184 134 })); 185 - embeds.push(...imagesEmbed); 186 - } else if (isEmbedVideo(postEmbed)) { 187 - const embed = postEmbed as { 188 - video: { ref: { toString: () => string } }; 189 - alt?: string; 190 - }; 191 - const videoEmbed = { 135 + medias.push(...imagesMedia); 136 + } else if (isMediaVideo(postMedia)) { 137 + const media = postMedia as MediaVideo; 138 + const videoMedia = { 192 139 postUri: uri.toString(), 193 - videoCid: embed.video.ref.toString(), 194 - alt: embed.alt ?? null, 140 + videoCid: media.video.ref.toString(), 141 + alt: media.alt ?? null, 195 142 }; 196 - embeds.push(videoEmbed); 143 + medias.push(videoMedia); 197 144 } 198 145 } 199 146 200 - const ancestors = await getAncestorsAndSelf(db, { 201 - uri: post.uri, 202 - parentHeight: REPLY_NOTIF_DEPTH, 203 - }); 204 147 const descendents = await getDescendents(db, { 205 148 uri: post.uri, 206 149 depth: REPLY_NOTIF_DEPTH, ··· 209 152 return { 210 153 post: insertedPost, 211 154 facets, 212 - embeds, 213 - ancestors, 155 + medias, 214 156 descendents, 215 157 }; 216 158 } catch (err) { ··· 320 262 return null; 321 263 } 322 264 323 - const deletedEmbeds: Array<{ 324 - postUri: string; 325 - position?: number; 326 - imageCid?: string; 327 - alt?: string | null; 328 - uri?: string; 329 - title?: string; 330 - description?: string; 331 - thumbCid?: string | null; 332 - embedUri?: string; 333 - embedCid?: string; 334 - videoCid?: string; 335 - }> = []; 336 - // Note: Embed tables not present in current schemas, embeds are stored inline 337 - 338 265 return { 339 266 post: deleted, 340 - facets: [], // Not used 341 - embeds: deletedEmbeds, 267 + facets: [], 342 268 }; 343 269 }; 344 270 ··· 354 280 }; 355 281 356 282 const updateAggregates = async (db: Database, postIdx: IndexedPost) => { 357 - // Update reply count for parent post 358 - if (postIdx.post.reply?.parent?.uri) { 359 - const replyCount = await db.models.Post.countDocuments({ 360 - "reply.parent.uri": postIdx.post.reply.parent.uri, 361 - violatesThreadGate: { $ne: true }, 362 - }); 363 - 364 - await db.models.Post.findOneAndUpdate( 365 - { uri: postIdx.post.reply.parent.uri }, 366 - { replyCount }, 367 - { upsert: true, new: true }, 368 - ); 369 - } 370 - 371 283 try { 372 284 // Update posts count for author 373 285 const postsCount = await db.models.Post.countDocuments({ ··· 412 324 413 325 export default makePlugin; 414 326 415 - function separateEmbeds( 416 - embed: PostRecord["embed"], 417 - ): Array<NonNullable<PostRecord["embed"]>> { 418 - if (!embed) { 327 + function separateMedia( 328 + media: PostRecord["media"], 329 + ): Array<NonNullable<PostRecord["media"]>> { 330 + if (!media) { 419 331 return []; 420 332 } 421 - return [embed]; 422 - } 423 - 424 - async function validateReply( 425 - db: Database, 426 - reply: ReplyRef, 427 - ) { 428 - const replyRefs = await getReplyRefs(db, reply); 429 - const invalidReplyRoot = !replyRefs.parent || 430 - checkInvalidReplyRoot(reply, replyRefs.parent); 431 - return { 432 - invalidReplyRoot, 433 - }; 434 - } 435 - 436 - async function getReplyRefs(db: Database, reply: ReplyRef) { 437 - const replyRoot = reply.root.uri; 438 - const replyParent = reply.parent.uri; 439 - 440 - const [root, parent] = await Promise.all([ 441 - db.models.Record.findOne({ uri: replyRoot }).lean(), 442 - db.models.Record.findOne({ uri: replyParent }).lean(), 443 - ]); 444 - 445 - return { 446 - root: root && root.json 447 - ? { 448 - uri: root.uri, 449 - invalidReplyRoot: root.invalidReplyRoot ?? null, 450 - record: jsonToLex(root.json) as PostRecord, 451 - } 452 - : null, 453 - parent: parent && parent.json 454 - ? { 455 - uri: parent.uri, 456 - invalidReplyRoot: parent.invalidReplyRoot ?? null, 457 - record: jsonToLex(parent.json) as PostRecord, 458 - } 459 - : null, 460 - }; 333 + return [media]; 461 334 }
+6 -11
data-plane/indexing/plugins/profile.ts
··· 6 6 import { Database } from "../../db/index.ts"; 7 7 import { ProfileDocument } from "../../db/models.ts"; 8 8 import { RecordProcessor } from "../processor.ts"; 9 - import { normalizeProfile } from "../../../utils/embed-normalizer.ts"; 10 9 11 10 const lexId = lex.ids.SoSprkActorProfile; 12 11 type IndexedProfile = ProfileDocument; ··· 20 19 ): Promise<IndexedProfile | null> => { 21 20 if (uri.rkey !== "self") return null; 22 21 23 - const normalizedProfile = normalizeProfile(obj) as 24 - | Record<string, unknown> 25 - | null; 26 - 27 22 const profile = { 28 23 uri: uri.toString(), 29 24 cid: cid.toString(), 30 25 authorDid: uri.host, 31 - displayName: normalizedProfile?.displayName || null, 32 - description: normalizedProfile?.description || null, 33 - avatar: normalizedProfile?.avatar || null, 34 - banner: normalizedProfile?.banner || null, 35 - pinnedPost: normalizedProfile?.pinnedPost || null, 36 - createdAt: normalizedProfile?.createdAt || new Date().toISOString(), 26 + displayName: obj?.displayName, 27 + description: obj?.description, 28 + avatar: obj?.avatar, 29 + banner: obj?.banner, 30 + pinnedPost: obj?.pinnedPost, 31 + createdAt: obj?.createdAt || new Date().toISOString(), 37 32 indexedAt: timestamp, 38 33 }; 39 34
+376
data-plane/indexing/plugins/reply.ts
··· 1 + import { CID } from "multiformats/cid"; 2 + import { AtUri } from "@atp/syntax"; 3 + import * as lex from "../../../lex/lexicons.ts"; 4 + import { isMain as isMediaImage } from "../../../lex/types/so/sprk/media/image.ts"; 5 + import { 6 + Record as ReplyRecord, 7 + ReplyRef, 8 + } from "../../../lex/types/so/sprk/feed/reply.ts"; 9 + import { Record as GateRecord } from "../../../lex/types/so/sprk/feed/threadgate.ts"; 10 + import { 11 + isLink, 12 + isMention, 13 + } from "../../../lex/types/so/sprk/richtext/facet.ts"; 14 + import { BackgroundQueue } from "../../background.ts"; 15 + import { Database } from "../../db/index.ts"; 16 + import { ReplyDocument } from "../../db/models.ts"; 17 + import { 18 + getAncestorsAndSelf, 19 + getDescendents, 20 + invalidReplyRoot as checkInvalidReplyRoot, 21 + } from "../../util.ts"; 22 + import { RecordProcessor } from "../processor.ts"; 23 + import { jsonToLex } from "@atp/lexicon"; 24 + 25 + type Ancestor = { 26 + uri: string; 27 + height: number; 28 + }; 29 + type Descendent = { 30 + uri: string; 31 + depth: number; 32 + cid: string; 33 + creator: string; 34 + sortAt: string; 35 + }; 36 + type IndexedReply = { 37 + reply: ReplyDocument; 38 + facets?: { type: "mention" | "link"; value: string }[]; 39 + media?: { 40 + cid?: string; 41 + alt?: string | null; 42 + }; 43 + ancestors?: Ancestor[]; 44 + descendents?: Descendent[]; 45 + threadgate?: GateRecord; 46 + }; 47 + 48 + const lexId = lex.ids.SoSprkFeedReply; 49 + 50 + const REPLY_NOTIF_DEPTH = 5; 51 + 52 + const insertFn = async ( 53 + db: Database, 54 + uri: AtUri, 55 + cid: CID, 56 + obj: ReplyRecord, 57 + timestamp: string, 58 + ): Promise<IndexedReply | null> => { 59 + console.log("DEBUG: Post indexing started"); 60 + // Ensure actor record exists before creating post 61 + const actorExists = await db.models.Actor.findOne({ did: uri.host }).lean(); 62 + if (!actorExists) { 63 + // This should trigger actor indexing, but for now we'll just log 64 + console.log( 65 + `Post indexing: No actor record found for ${uri.host}, post may have missing handle`, 66 + ); 67 + } 68 + 69 + const reply = { 70 + uri: uri.toString(), 71 + cid: cid.toString(), 72 + authorDid: uri.host, 73 + text: obj.text || "", 74 + facets: obj.facets || [], 75 + reply: obj.reply 76 + ? { 77 + root: { 78 + uri: obj.reply.root.uri, 79 + cid: obj.reply.root.cid, 80 + }, 81 + parent: { 82 + uri: obj.reply.parent.uri, 83 + cid: obj.reply.parent.cid, 84 + }, 85 + } 86 + : null, 87 + media: obj.media, 88 + langs: obj.langs || [], 89 + labels: obj.labels || null, 90 + tags: obj.tags || [], 91 + createdAt: obj.createdAt, 92 + indexedAt: timestamp, 93 + }; 94 + 95 + // Use findOneAndUpdate with upsert to handle potential duplicate key errors 96 + try { 97 + const insertedReply = await db.models.Reply.findOneAndUpdate( 98 + { uri: reply.uri }, 99 + reply, 100 + { upsert: true, new: true }, 101 + ); 102 + 103 + if (obj.reply) { 104 + const { invalidReplyRoot } = await validateReply( 105 + db, 106 + obj.reply, 107 + ); 108 + if (invalidReplyRoot) { 109 + Object.assign(insertedReply, { invalidReplyRoot }); 110 + await db.models.Reply.updateOne( 111 + { uri: reply.uri }, 112 + { invalidReplyRoot }, 113 + ); 114 + } 115 + } 116 + 117 + const facets = (obj.facets || []) 118 + .flatMap((facet) => facet.features) 119 + .flatMap((feature) => { 120 + if (isMention(feature)) { 121 + return { 122 + type: "mention" as const, 123 + value: feature.did, 124 + }; 125 + } 126 + if (isLink(feature)) { 127 + return { 128 + type: "link" as const, 129 + value: feature.uri, 130 + }; 131 + } 132 + return []; 133 + }); 134 + 135 + // Embed processing - embeds are stored inline in the Post model 136 + let media: { 137 + postUri?: string; 138 + cid?: string; 139 + alt?: string; 140 + } = {}; 141 + if (isMediaImage(obj.media)) { 142 + const imageMedia = { 143 + postUri: uri.toString(), 144 + cid: obj.media.image.ref.toString(), 145 + alt: obj.media.alt as string, 146 + }; 147 + media = imageMedia; 148 + } 149 + 150 + const ancestors = await getAncestorsAndSelf(db, { 151 + uri: reply.uri, 152 + parentHeight: REPLY_NOTIF_DEPTH, 153 + }); 154 + const descendents = await getDescendents(db, { 155 + uri: reply.uri, 156 + depth: REPLY_NOTIF_DEPTH, 157 + }); 158 + 159 + return { 160 + reply: insertedReply, 161 + facets, 162 + media, 163 + ancestors, 164 + descendents, 165 + }; 166 + } catch (err) { 167 + // Handle duplicate key errors gracefully 168 + const mongoError = err as { code?: number }; 169 + if (mongoError.code === 11000) { 170 + return null; // Silently skip duplicates 171 + } 172 + throw err; 173 + } 174 + }; 175 + 176 + const findDuplicate = (): AtUri | null => { 177 + return null; 178 + }; 179 + 180 + const notifsForInsert = (obj: IndexedReply) => { 181 + const notifs: Array<{ 182 + did: string; 183 + reason: string; 184 + author: string; 185 + recordUri: string; 186 + recordCid: string; 187 + sortAt: string; 188 + reasonSubject?: string; 189 + }> = []; 190 + const notified = new Set([obj.reply.authorDid]); 191 + const maybeNotify = (notif: { 192 + did: string; 193 + reason: string; 194 + author: string; 195 + recordUri: string; 196 + recordCid: string; 197 + sortAt: string; 198 + reasonSubject?: string; 199 + }) => { 200 + if (!notified.has(notif.did)) { 201 + notified.add(notif.did); 202 + notifs.push(notif); 203 + } 204 + }; 205 + for (const facet of obj.facets ?? []) { 206 + if (facet.type === "mention") { 207 + maybeNotify({ 208 + did: facet.value, 209 + reason: "mention", 210 + author: obj.reply.authorDid, 211 + recordUri: obj.reply.uri, 212 + recordCid: obj.reply.cid, 213 + sortAt: obj.reply.createdAt, 214 + }); 215 + } 216 + } 217 + 218 + const threadgateHiddenReplies = obj.threadgate?.hiddenReplies || []; 219 + 220 + // reply notifications 221 + for (const ancestor of obj.ancestors ?? []) { 222 + if (ancestor.uri === obj.reply.uri) continue; 223 + if (ancestor.height < REPLY_NOTIF_DEPTH) { 224 + const ancestorUri = new AtUri(ancestor.uri); 225 + maybeNotify({ 226 + did: ancestorUri.host, 227 + reason: "reply", 228 + reasonSubject: ancestorUri.toString(), 229 + author: obj.reply.authorDid, 230 + recordUri: obj.reply.uri, 231 + recordCid: obj.reply.cid, 232 + sortAt: obj.reply.createdAt, 233 + }); 234 + // found hidden reply, don't notify any higher ancestors 235 + if (threadgateHiddenReplies.includes(ancestorUri.toString())) break; 236 + } 237 + } 238 + 239 + // descendents indicate out-of-order indexing: need to notify 240 + // everything upwards of the current reply 241 + for (const descendent of obj.descendents ?? []) { 242 + for (const ancestor of obj.ancestors ?? []) { 243 + const totalHeight = descendent.depth + ancestor.height; 244 + if (totalHeight < REPLY_NOTIF_DEPTH) { 245 + const ancestorUri = new AtUri(ancestor.uri); 246 + maybeNotify({ 247 + did: ancestorUri.host, 248 + reason: "reply", 249 + reasonSubject: ancestorUri.toString(), 250 + author: descendent.creator, 251 + recordUri: descendent.uri, 252 + recordCid: descendent.cid, 253 + sortAt: descendent.sortAt, 254 + }); 255 + } 256 + } 257 + } 258 + 259 + return notifs; 260 + }; 261 + 262 + const deleteFn = async ( 263 + db: Database, 264 + uri: AtUri, 265 + ): Promise<IndexedReply | null> => { 266 + const uriStr = uri.toString(); 267 + const deleted = await db.models.Reply.findOneAndDelete({ uri: uriStr }); 268 + 269 + if (!deleted) { 270 + return null; 271 + } 272 + 273 + return { 274 + reply: deleted, 275 + facets: [], // Not used 276 + }; 277 + }; 278 + 279 + const notifsForDelete = ( 280 + deleted: IndexedReply, 281 + replacedBy: IndexedReply | null, 282 + ) => { 283 + const notifs = replacedBy ? notifsForInsert(replacedBy) : []; 284 + return { 285 + notifs, 286 + toDelete: [deleted.reply.uri], 287 + }; 288 + }; 289 + 290 + const updateAggregates = async (db: Database, replyIdx: IndexedReply) => { 291 + // Update reply count for parent post 292 + if (replyIdx.reply.reply?.parent?.uri) { 293 + const parentPost = await db.models.Post.findOne({ 294 + uri: replyIdx.reply.reply?.parent.uri, 295 + }); 296 + const parentReply = await db.models.Reply.findOne({ 297 + uri: replyIdx.reply.reply?.parent.uri, 298 + }); 299 + 300 + const replyCount = await db.models.Reply.countDocuments({ 301 + "reply.parent.uri": replyIdx.reply.reply.parent.uri, 302 + }); 303 + 304 + if (parentPost) { 305 + await db.models.Post.findOneAndUpdate( 306 + { uri: replyIdx.reply.reply?.parent.uri }, 307 + { replyCount }, 308 + { upsert: true, new: true }, 309 + ); 310 + } else if (parentReply) { 311 + await db.models.Reply.findOneAndUpdate( 312 + { uri: replyIdx.reply.reply?.parent.uri }, 313 + { replyCount }, 314 + { upsert: true, new: true }, 315 + ); 316 + } 317 + } 318 + }; 319 + 320 + export type PluginType = RecordProcessor<ReplyRecord, IndexedReply>; 321 + 322 + export const makePlugin = ( 323 + db: Database, 324 + background: BackgroundQueue, 325 + ): PluginType => { 326 + return new RecordProcessor(db, background, { 327 + lexId, 328 + insertFn, 329 + findDuplicate, 330 + deleteFn, 331 + notifsForInsert, 332 + notifsForDelete, 333 + updateAggregates, 334 + }); 335 + }; 336 + 337 + export default makePlugin; 338 + 339 + async function validateReply( 340 + db: Database, 341 + reply: ReplyRef, 342 + ) { 343 + const replyRefs = await getReplyRefs(db, reply); 344 + const invalidReplyRoot = !replyRefs.parent || 345 + checkInvalidReplyRoot(reply, replyRefs.parent); 346 + return { 347 + invalidReplyRoot, 348 + }; 349 + } 350 + 351 + async function getReplyRefs(db: Database, reply: ReplyRef) { 352 + const replyRoot = reply.root.uri; 353 + const replyParent = reply.parent.uri; 354 + 355 + const [root, parent] = await Promise.all([ 356 + db.models.Record.findOne({ uri: replyRoot }).lean(), 357 + db.models.Record.findOne({ uri: replyParent }).lean(), 358 + ]); 359 + 360 + return { 361 + root: root && root.json 362 + ? { 363 + uri: root.uri, 364 + invalidReplyRoot: root.invalidReplyRoot ?? null, 365 + record: jsonToLex(root.json) as ReplyRecord, 366 + } 367 + : null, 368 + parent: parent && parent.json 369 + ? { 370 + uri: parent.uri, 371 + invalidReplyRoot: parent.invalidReplyRoot ?? null, 372 + record: jsonToLex(parent.json) as ReplyRecord, 373 + } 374 + : null, 375 + }; 376 + }
+2 -2
data-plane/indexing/plugins/story.ts
··· 1 1 import { CID } from "multiformats/cid"; 2 2 import { AtUri, normalizeDatetimeAlways } from "@atp/syntax"; 3 3 import * as lex from "../../../lex/lexicons.ts"; 4 - import * as Story from "../../../lex/types/so/sprk/feed/story.ts"; 4 + import * as Story from "../../../lex/types/so/sprk/story/post.ts"; 5 5 import { BackgroundQueue } from "../../background.ts"; 6 6 import { Database } from "../../db/index.ts"; 7 7 import { StoryDocument } from "../../db/models.ts"; ··· 11 11 normalizeObject, 12 12 } from "../../../utils/embed-normalizer.ts"; 13 13 14 - const lexId = lex.ids.SoSprkFeedStory; 14 + const lexId = lex.ids.SoSprkStoryPost; 15 15 type IndexedStory = StoryDocument; 16 16 17 17 const insertFn = async (
+12 -11
data-plane/routes/identity.ts
··· 1 1 import { DidDocument, getDid, getHandle, IdResolver } from "@atp/identity"; 2 + import { Code, DataPlaneError } from "../util.ts"; 2 3 3 4 // Helper function to format DID document result 4 5 function getResultFromDoc(doc: DidDocument) { ··· 41 42 42 43 async getByDid(did: string) { 43 44 if (!this.idResolver) { 44 - throw new Error("ID resolver not available"); 45 + throw new DataPlaneError(Code.InternalError); 45 46 } 46 47 47 48 try { 48 49 const doc = await this.idResolver.did.resolve(did); 49 50 if (!doc) { 50 - throw new Error("Identity not found"); 51 + throw new DataPlaneError(Code.NotFound); 51 52 } 52 53 53 54 const result = getResultFromDoc(doc); 54 55 return result; 55 56 } catch (error) { 56 57 console.error("Error resolving DID:", error); 57 - throw new Error("Failed to resolve identity"); 58 + throw new DataPlaneError(Code.InternalError); 58 59 } 59 60 } 60 61 61 62 async getByHandle(handle: string) { 62 63 if (!this.idResolver) { 63 - throw new Error("ID resolver not available"); 64 + throw new DataPlaneError(Code.InternalError); 64 65 } 65 66 66 67 try { 67 68 const did = await this.idResolver.handle.resolve(handle); 68 69 if (!did) { 69 - throw new Error("Identity not found"); 70 + throw new DataPlaneError(Code.NotFound); 70 71 } 71 72 72 73 const doc = await this.idResolver.did.resolve(did); 73 74 if (!doc || did !== getDid(doc)) { 74 - throw new Error("Identity not found"); 75 + throw new DataPlaneError(Code.NotFound); 75 76 } 76 77 77 78 const result = getResultFromDoc(doc); 78 79 return result; 79 80 } catch (error) { 80 81 console.error("Error resolving handle:", error); 81 - throw new Error("Failed to resolve identity"); 82 + throw new DataPlaneError(Code.InternalError); 82 83 } 83 84 } 84 85 85 86 async resolve(identifier: string, type?: "did" | "handle") { 86 87 if (!this.idResolver) { 87 - throw new Error("ID resolver not available"); 88 + throw new DataPlaneError(Code.InternalError); 88 89 } 89 90 90 91 try { ··· 106 107 } 107 108 108 109 if (!doc || (resolvedDid && resolvedDid !== getDid(doc))) { 109 - throw new Error("Identity not found"); 110 + throw new DataPlaneError(Code.NotFound); 110 111 } 111 112 112 113 const result = getResultFromDoc(doc); ··· 119 120 }; 120 121 } catch (error) { 121 122 console.error("Error resolving identity:", error); 122 - throw new Error("Failed to resolve identity"); 123 + throw new DataPlaneError(Code.InternalError); 123 124 } 124 125 } 125 126 ··· 127 128 identifiers: Array<{ value: string; type?: "did" | "handle" }>, 128 129 ) { 129 130 if (!this.idResolver) { 130 - throw new Error("ID resolver not available"); 131 + throw new DataPlaneError(Code.InternalError); 131 132 } 132 133 133 134 const results = await Promise.allSettled(
+21 -1
data-plane/routes/interactions.ts
··· 94 94 { $group: { _id: "$authorDid", count: { $sum: 1 } } }, 95 95 ]), 96 96 // Count generators for each DID 97 - this.db.models.BskyGenerator.aggregate([ 97 + this.db.models.Generator.aggregate([ 98 98 { $match: { authorDid: { $in: dids } } }, 99 99 { $group: { _id: "$authorDid", count: { $sum: 1 } } }, 100 100 ]), ··· 119 119 following: dids.map((did) => followingMap.get(did) ?? 0), 120 120 posts: dids.map((did) => postsMap.get(did) ?? 0), 121 121 feeds: dids.map((did) => feedsMap.get(did) ?? 0), 122 + }; 123 + } 124 + 125 + async getSoundUsageCounts(uris: string[]) { 126 + if (uris.length === 0) { 127 + return { uses: [] }; 128 + } 129 + 130 + // Count how many posts reference each sound URI 131 + const usageAgg = await this.db.models.Post.aggregate([ 132 + { $match: { "sound.uri": { $in: uris } } }, 133 + { $group: { _id: "$sound.uri", count: { $sum: 1 } } }, 134 + ]); 135 + 136 + const usageMap = new Map( 137 + usageAgg.map((item: AggregationResult) => [item._id, item.count]), 138 + ); 139 + 140 + return { 141 + uses: uris.map((uri) => usageMap.get(uri) ?? 0), 122 142 }; 123 143 } 124 144 }
+36 -6
data-plane/routes/records.ts
··· 2 2 import { AtUri } from "@atp/syntax"; 3 3 import { ids } from "../../lex/lexicons.ts"; 4 4 import { keyBy } from "@atp/common"; 5 + import { Code, DataPlaneError } from "../util.ts"; 5 6 6 7 export type Record = { 7 8 record: string; ··· 78 79 return { records }; 79 80 } 80 81 82 + // Helper function to get reply records with metadata 83 + async function getReplyRecords( 84 + db: Database, 85 + uris: string[], 86 + ): Promise<{ 87 + records: Array<Record>; 88 + }> { 89 + const [{ records }] = await Promise.all([ 90 + getRecords(db, uris, ids.SoSprkFeedReply), 91 + uris.length 92 + ? db.models.Reply.find({ 93 + uri: { $in: uris }, 94 + }) 95 + : [], 96 + ]); 97 + 98 + return { records }; 99 + } 100 + 81 101 // Helper function for composite time 82 102 export function compositeTime(ts1?: string, ts2?: string): string | undefined { 83 103 if (!ts1) return ts2; ··· 106 126 return result; 107 127 } catch (error) { 108 128 console.error("Error fetching feed generator records:", error); 109 - throw new Error("Failed to fetch records"); 129 + throw new DataPlaneError(Code.InternalError); 110 130 } 111 131 } 112 132 ··· 121 141 return result; 122 142 } catch (error) { 123 143 console.error("Error fetching like records:", error); 124 - throw new Error("Failed to fetch records"); 144 + throw new DataPlaneError(Code.InternalError); 125 145 } 126 146 } 127 147 ··· 131 151 return result; 132 152 } catch (error) { 133 153 console.error("Error fetching post records:", error); 134 - throw new Error("Failed to fetch records"); 154 + throw new DataPlaneError(Code.InternalError); 155 + } 156 + } 157 + 158 + async getReplyRecords(uris: string[]) { 159 + try { 160 + const result = await getReplyRecords(this.db, uris); 161 + return result; 162 + } catch (error) { 163 + console.error("Error fetching reply records:", error); 164 + throw new DataPlaneError(Code.InternalError); 135 165 } 136 166 } 137 167 ··· 141 171 return result; 142 172 } catch (error) { 143 173 console.error("Error fetching profile records:", error); 144 - throw new Error("Failed to fetch records"); 174 + throw new DataPlaneError(Code.InternalError); 145 175 } 146 176 } 147 177 ··· 151 181 return result; 152 182 } catch (error) { 153 183 console.error("Error fetching repost records:", error); 154 - throw new Error("Failed to fetch records"); 184 + throw new DataPlaneError(Code.InternalError); 155 185 } 156 186 } 157 187 ··· 161 191 return result; 162 192 } catch (error) { 163 193 console.error("Error fetching records:", error); 164 - throw new Error("Failed to fetch records"); 194 + throw new DataPlaneError(Code.InternalError); 165 195 } 166 196 } 167 197 }
+76 -69
data-plane/routes/threads.ts
··· 1 1 import { Database } from "../db/index.ts"; 2 + import { Code, DataPlaneError } from "../util.ts"; 2 3 3 4 // Parameter validation 4 5 function validateThreadParams(above: number, below: number) { ··· 11 12 } 12 13 } 13 14 14 - // Helper function to get ancestors (parent posts going up the thread) 15 - async function getAncestors( 16 - db: Database, 17 - postUri: string, 18 - maxDepth: number, 19 - ): Promise<string[]> { 20 - const ancestors: string[] = []; 21 - let currentUri = postUri; 22 - let depth = 0; 23 - 24 - while (depth < maxDepth) { 25 - const post = await db.models.Post.findOne({ uri: currentUri }); 26 - 27 - if (!post || !post.reply?.parent?.uri) { 28 - break; 29 - } 30 - 31 - const parentUri = post.reply.parent.uri; 32 - ancestors.unshift(parentUri); // Add to beginning to maintain order 33 - currentUri = parentUri; 34 - depth++; 35 - } 36 - 37 - return ancestors; 38 - } 39 - 40 - // Helper function to get descendants (child posts going down the thread) 15 + // Helper function to get descendants (child replies going down the thread) 41 16 async function getDescendants( 42 17 db: Database, 43 - postUri: string, 18 + parentUri: string, 44 19 maxDepth: number, 45 20 ): Promise<string[]> { 46 21 const descendants: string[] = []; ··· 48 23 49 24 // Use BFS to traverse descendants 50 25 const queue: Array<{ uri: string; depth: number }> = [{ 51 - uri: postUri, 26 + uri: parentUri, 52 27 depth: 0, 53 28 }]; 54 29 ··· 61 36 62 37 visited.add(currentUri); 63 38 64 - // Find all replies to this post 65 - const replies = await db.models.Post.find({ 39 + // Find all replies to this post/reply 40 + const replies = await db.models.Reply.find({ 66 41 "reply.parent.uri": currentUri, 67 42 }) 68 43 .sort({ createdAt: -1 }); // Most recent first ··· 93 68 validateThreadParams(above, below); 94 69 95 70 try { 96 - // Get ancestors (parents going up) and descendants (replies going down) in parallel 97 - const [ancestors, descendants] = await Promise.all([ 98 - getAncestors(this.db, postUri, above), 99 - getDescendants(this.db, postUri, below), 100 - ]); 101 - 102 - // Verify the original post exists 71 + // Verify the original post exists and is a root post (posts can't have ancestors) 103 72 const originalPost = await this.db.models.Post.findOne({ uri: postUri }); 104 73 105 74 if (!originalPost) { 106 - throw new Error("Post not found"); 75 + throw new DataPlaneError(Code.NotFound); 107 76 } 108 77 109 - // Combine all URIs: ancestors + self + descendants 78 + // Posts are always root - they don't have ancestors by design 79 + // So we only get descendants (replies) 80 + const descendants = await getDescendants(this.db, postUri, below); 81 + 82 + // The thread is just the root post + all its descendant replies 110 83 const uris = [ 111 - ...ancestors, 112 - postUri, // The original post 84 + postUri, // The original post (always root) 113 85 ...descendants, 114 86 ]; 115 87 ··· 119 91 return { 120 92 uris: uniqueUris, 121 93 meta: { 122 - ancestorCount: ancestors.length, 94 + ancestorCount: 0, // Posts never have ancestors 123 95 descendantCount: descendants.length, 124 96 totalCount: uniqueUris.length, 125 97 }, 126 98 }; 127 99 } catch (error) { 128 100 console.error("Error fetching thread:", error); 129 - throw new Error("Failed to fetch thread"); 101 + throw new DataPlaneError(Code.InternalError); 130 102 } 131 103 } 132 104 ··· 142 114 const originalPost = await this.db.models.Post.findOne({ uri: postUri }); 143 115 144 116 if (!originalPost) { 145 - throw new Error("Post not found"); 117 + throw new DataPlaneError(Code.NotFound); 146 118 } 147 119 148 - // Get ancestors with metadata 120 + // Posts don't have ancestors - they are always roots 149 121 const ancestors: Array<{ uri: string; depth: number }> = []; 150 - let currentUri = postUri; 151 - let depth = 0; 152 - 153 - while (depth < above) { 154 - const post = await this.db.models.Post.findOne({ uri: currentUri }); 155 - 156 - if (!post || !post.reply?.parent?.uri) { 157 - break; 158 - } 159 - 160 - const parentUri = post.reply.parent.uri; 161 - ancestors.unshift({ uri: parentUri, depth: depth + 1 }); 162 - currentUri = parentUri; 163 - depth++; 164 - } 165 122 166 123 // Get descendants with metadata using BFS 167 124 const descendants: Array< ··· 179 136 continue; 180 137 } 181 138 182 - const replies = await this.db.models.Post.find({ 139 + // Find replies to this post/reply 140 + const replies = await this.db.models.Reply.find({ 183 141 "reply.parent.uri": currentUri, 184 142 }) 185 143 .sort({ createdAt: -1 }); ··· 209 167 return { 210 168 root: { 211 169 uri: postUri, 212 - isRoot: !originalPost.reply?.parent?.uri, 170 + isRoot: true, // Posts are always roots 213 171 }, 214 - ancestors: ancestors.reverse(), // Top ancestor first 172 + ancestors, // Always empty for posts 215 173 descendants, 216 174 meta: { 217 - ancestorCount: ancestors.length, 175 + ancestorCount: 0, // Posts never have ancestors 218 176 descendantCount: descendants.length, 219 - maxAncestorDepth: ancestors.length > 0 220 - ? Math.max(...ancestors.map((a) => a.depth)) 221 - : 0, 177 + maxAncestorDepth: 0, // Posts never have ancestors 222 178 maxDescendantDepth: descendants.length > 0 223 179 ? Math.max(...descendants.map((d) => d.depth)) 224 180 : 0, ··· 226 182 }; 227 183 } catch (error) { 228 184 console.error("Error fetching thread structure:", error); 229 - throw new Error("Failed to fetch thread structure"); 185 + throw new DataPlaneError(Code.InternalError); 186 + } 187 + } 188 + 189 + // New method: Get thread starting from a reply (if needed for UI purposes) 190 + // This would find the root post and then build the full thread 191 + async getThreadFromReply(replyUri: string, below: number = 50) { 192 + validateThreadParams(0, below); // No ancestors needed 193 + 194 + try { 195 + // Find the reply 196 + const reply = await this.db.models.Reply.findOne({ uri: replyUri }); 197 + 198 + if (!reply) { 199 + throw new DataPlaneError(Code.NotFound); 200 + } 201 + 202 + // Walk up to find the root post 203 + let currentUri = replyUri; 204 + let rootUri: string | null = null; 205 + 206 + // Keep going up until we find a post (not a reply) 207 + while (rootUri === null) { 208 + const currentReply = await this.db.models.Reply.findOne({ 209 + uri: currentUri, 210 + }); 211 + 212 + if (!currentReply || !currentReply.reply?.parent?.uri) { 213 + // This shouldn't happen if data integrity is maintained 214 + throw new DataPlaneError(Code.NotFound); 215 + } 216 + 217 + const parentUri = currentReply.reply.parent.uri; 218 + 219 + // Check if parent is a post (root) or another reply 220 + const parentPost = await this.db.models.Post.findOne({ 221 + uri: parentUri, 222 + }); 223 + 224 + if (parentPost) { 225 + rootUri = parentUri; 226 + } else { 227 + // Parent is another reply, keep going up 228 + currentUri = parentUri; 229 + } 230 + } 231 + 232 + // Now get the full thread starting from the root post 233 + return this.getThread(rootUri, 0, below); 234 + } catch (error) { 235 + console.error("Error fetching thread from reply:", error); 236 + throw new Error("Failed to fetch thread from reply"); 230 237 } 231 238 } 232 239 }
+1 -2
data-plane/subscription.ts
··· 28 28 this.background = new BackgroundQueue(db, this.logger); 29 29 this.indexingSvc = new IndexingService( 30 30 db, 31 + cfg, 31 32 idResolver, 32 33 this.background, 33 34 this.logger, ··· 199 200 }, 200 201 filterCollections: [ 201 202 "so.sprk.*", 202 - "app.bsky.graph.follow", 203 203 "app.bsky.graph.block", 204 - "app.bsky.feed.generator", 205 204 ], 206 205 }); 207 206 return { firehose, runner };
+33 -12
data-plane/util.ts
··· 1 1 import { 2 - Record as PostRecord, 2 + Record as ReplyRecord, 3 3 ReplyRef, 4 - } from "../lex/types/so/sprk/feed/post.ts"; 4 + } from "../lex/types/so/sprk/feed/reply.ts"; 5 5 import { Database } from "./db/index.ts"; 6 6 import { DidDocument } from "@atp/identity"; 7 7 ··· 22 22 }> = []; 23 23 24 24 // Get direct replies (depth 1) 25 - const directReplies = await db.models.Post.find({ 25 + const directReplies = await db.models.Reply.find({ 26 26 "reply.parent.uri": uri, 27 - }).select(["uri", "cid", "authorDid", "createdAt"]).lean(); 27 + }).lean(); 28 28 29 29 for (const reply of directReplies) { 30 30 descendents.push({ ··· 45 45 const current = toProcess.shift()!; 46 46 if (current.depth >= depth) continue; 47 47 48 - const nestedReplies = await db.models.Post.find({ 48 + const nestedReplies = await db.models.Reply.find({ 49 49 "reply.parent.uri": current.uri, 50 - }).select(["uri", "cid", "authorDid", "createdAt"]).lean(); 50 + }).lean(); 51 51 52 52 for (const reply of nestedReplies) { 53 53 if (processedUris.has(reply.uri)) continue; ··· 83 83 }> = []; 84 84 85 85 // Start with the current post 86 - const currentPost = await db.models.Post.findOne({ uri }).lean(); 86 + const currentPost = await db.models.Reply.findOne({ uri }).lean(); 87 87 if (!currentPost) return ancestors; 88 88 89 89 ancestors.push({ ··· 96 96 let height = 1; 97 97 98 98 while (currentUri && height <= parentHeight) { 99 - const parentPost = await db.models.Post.findOne({ uri: currentUri }).lean(); 100 - if (!parentPost) break; 99 + const parentReply = await db.models.Reply.findOne({ uri: currentUri }) 100 + .lean(); 101 + if (!parentReply) break; 101 102 102 103 ancestors.push({ 103 - uri: parentPost.uri, 104 + uri: parentReply.uri, 104 105 height, 105 106 }); 106 107 107 - currentUri = parentPost.reply?.parent?.uri; 108 + currentUri = parentReply.reply?.parent?.uri; 108 109 height++; 109 110 } 110 111 ··· 114 115 export const invalidReplyRoot = ( 115 116 reply: ReplyRef, 116 117 parent: { 117 - record: PostRecord; 118 + record: ReplyRecord; 118 119 invalidReplyRoot: boolean | null; 119 120 }, 120 121 ) => { ··· 164 165 updated: new Date(), 165 166 }; 166 167 }; 168 + 169 + export enum Code { 170 + NotFound = "Not Found", 171 + InvalidRequest = "Invalid Request", 172 + InternalError = "Internal Error", 173 + } 174 + 175 + export class DataPlaneError extends Error { 176 + public code: Code; 177 + 178 + constructor(message: Code) { 179 + super(); 180 + this.name = "DataPlaneError"; 181 + this.code = message; 182 + } 183 + } 184 + 185 + export function isDataPlaneError(error: unknown, code?: Code): boolean { 186 + return error instanceof DataPlaneError && (!code || error.code === code); 187 + }
+5 -5
deno.json
··· 35 35 "multiformats": "npm:multiformats@^13.4.1", 36 36 "p-queue": "npm:p-queue@^8.1.1" 37 37 }, 38 - "compilerOptions": { 39 - "jsx": "precompile", 40 - "jsxImportSource": "hono/jsx" 41 - }, 42 - "unstable": ["sloppy-imports"] 38 + "test": { 39 + "permissions": { 40 + "env": true 41 + } 42 + } 43 43 }
+90 -1
hydration/feed.ts
··· 2 2 import { Record as BskyFeedGenRecord } from "../lex/types/app/bsky/feed/generator.ts"; 3 3 import { Record as LikeRecord } from "../lex/types/so/sprk/feed/like.ts"; 4 4 import { Record as PostRecord } from "../lex/types/so/sprk/feed/post.ts"; 5 + import { Record as ReplyRecord } from "../lex/types/so/sprk/feed/reply.ts"; 5 6 import { Record as RepostRecord } from "../lex/types/app/bsky/feed/repost.ts"; 7 + import { Record as AudioRecord } from "../lex/types/so/sprk/sound/audio.ts"; 6 8 import { VideoMappingDocument } from "../data-plane/db/models.ts"; 7 9 import { uriToDid as didFromUri } from "../utils/uris.ts"; 8 10 import { ··· 17 19 18 20 export type Post = RecordInfo<PostRecord>; 19 21 export type Posts = HydrationMap<Post>; 22 + export type Reply = RecordInfo<ReplyRecord>; 23 + export type Replies = HydrationMap<Reply>; 24 + 25 + export type Sound = RecordInfo<AudioRecord>; 26 + export type Sounds = HydrationMap<Sound>; 27 + 28 + export type SoundAgg = { 29 + uses: number; 30 + }; 31 + 32 + export type SoundAggs = HydrationMap<SoundAgg>; 20 33 21 34 export type VideoMapping = VideoMappingDocument; 22 35 export type VideoMappings = HydrationMap<VideoMapping>; ··· 24 37 export type PostViewerState = { 25 38 like?: string; 26 39 repost?: string; 27 - threadMuted?: boolean; 28 40 }; 29 41 30 42 export type PostViewerStates = HydrationMap<PostViewerState>; ··· 43 55 }; 44 56 45 57 export type PostAggs = HydrationMap<PostAgg>; 58 + 59 + export type ReplyAgg = { 60 + likes: number; 61 + replies: number; 62 + }; 63 + 64 + export type ReplyAggs = HydrationMap<ReplyAgg>; 46 65 47 66 export type Like = RecordInfo<LikeRecord>; 48 67 export type Likes = HydrationMap<Like>; ··· 114 133 }, base); 115 134 } 116 135 136 + async getReplies( 137 + uris: string[], 138 + includeTakedowns = false, 139 + given = new HydrationMap<Reply>(), 140 + ): Promise<Replies> { 141 + const [have, need] = split(uris, (uri) => given.has(uri)); 142 + const base = have.reduce( 143 + (acc, uri) => acc.set(uri, given.get(uri) ?? null), 144 + new HydrationMap<Reply>(), 145 + ); 146 + if (!need.length) return base; 147 + const res = await this.dataplane.records.getReplyRecords(need); 148 + 149 + return need.reduce((acc, uri, i) => { 150 + const record = parseRecord<ReplyRecord>(res.records[i], includeTakedowns); 151 + return acc.set( 152 + uri, 153 + record ? record : null, 154 + ); 155 + }, base); 156 + } 157 + 158 + async getSounds( 159 + uris: string[], 160 + includeTakedowns = false, 161 + given = new HydrationMap<Sound>(), 162 + ): Promise<Sounds> { 163 + const [have, need] = split(uris, (uri) => given.has(uri)); 164 + const base = have.reduce( 165 + (acc, uri) => acc.set(uri, given.get(uri) ?? null), 166 + new HydrationMap<Sound>(), 167 + ); 168 + if (!need.length) return base; 169 + const res = await this.dataplane.records.getRecords(need); 170 + 171 + return need.reduce((acc, uri, i) => { 172 + const record = parseRecord<AudioRecord>(res.records[i], includeTakedowns); 173 + return acc.set( 174 + uri, 175 + record ? record : null, 176 + ); 177 + }, base); 178 + } 179 + 117 180 async getPostViewerStates( 118 181 refs: ThreadRef[], 119 182 viewer: string, ··· 185 248 replies: counts.replies[i] ?? 0, 186 249 }); 187 250 }, new HydrationMap<PostAgg>()); 251 + } 252 + 253 + async getReplyAggregates( 254 + refs: ItemRef[], 255 + ): Promise<ReplyAggs> { 256 + if (!refs.length) return new HydrationMap<ReplyAgg>(); 257 + const counts = await this.dataplane.interactions.getInteractionCounts(refs); 258 + return refs.reduce((acc, { uri }, i) => { 259 + return acc.set(uri, { 260 + likes: counts.likes[i] ?? 0, 261 + replies: counts.replies[i] ?? 0, 262 + }); 263 + }, new HydrationMap<ReplyAgg>()); 264 + } 265 + 266 + async getSoundAggregates( 267 + refs: ItemRef[], 268 + ): Promise<SoundAggs> { 269 + if (!refs.length) return new HydrationMap<SoundAgg>(); 270 + const uris = refs.map((ref) => ref.uri); 271 + const counts = await this.dataplane.interactions.getSoundUsageCounts(uris); 272 + return refs.reduce((acc, { uri }, i) => { 273 + return acc.set(uri, { 274 + uses: counts.uses[i] ?? 0, 275 + }); 276 + }, new HydrationMap<SoundAgg>()); 188 277 } 189 278 190 279 async getFeedGens(
+196 -117
hydration/index.ts
··· 24 24 PostAggs, 25 25 Posts, 26 26 PostViewerStates, 27 + Replies, 28 + Reply, 29 + ReplyAggs, 27 30 Reposts, 31 + SoundAggs, 32 + Sounds, 28 33 ThreadContexts, 29 34 ThreadRef, 30 35 VideoMappings, 31 36 } from "./feed.ts"; 37 + 32 38 import { 33 39 BlockEntry, 34 40 Follows, 35 41 GraphHydrator, 36 42 RelationshipPair, 37 43 } from "./graph.ts"; 38 - import { 39 - HydrationMap, 40 - ItemRef, 41 - mergeManyMaps, 42 - mergeMaps, 43 - RecordInfo, 44 - } from "./util.ts"; 44 + import { HydrationMap, ItemRef, mergeMaps, RecordInfo } from "./util.ts"; 45 45 import { getLogger } from "@logtape/logtape"; 46 46 47 47 export class HydrateCtx { ··· 81 81 profileViewers?: ProfileViewerStates; 82 82 profileAggs?: ProfileAggs; 83 83 posts?: Posts; 84 + replies?: Replies; 84 85 postAggs?: PostAggs; 86 + replyAggs?: ReplyAggs; 85 87 postViewers?: PostViewerStates; 86 88 threadContexts?: ThreadContexts; 89 + sounds?: Sounds; 90 + soundAggs?: SoundAggs; 91 + 87 92 postBlocks?: PostBlocks; 88 93 reposts?: Reposts; 89 94 follows?: Follows; ··· 247 252 ctx: HydrateCtx, 248 253 state: HydrationState = {}, 249 254 ): Promise<HydrationState> { 250 - const uris = refs.map((ref) => ref.uri); 255 + const postRefs = refs.filter((ref) => 256 + new AtUri(ref.uri).collection === ids.SoSprkFeedPost 257 + ); 258 + const replyRefs = refs.filter((ref) => 259 + new AtUri(ref.uri).collection === ids.SoSprkFeedReply 260 + ); 251 261 252 262 state.posts ??= new HydrationMap<Post>(); 253 - const addPostsToHydrationState = (posts: Posts) => { 254 - posts.forEach((post, uri) => { 255 - state.posts ??= new HydrationMap<Post>(); 256 - state.posts.set(uri, post); 257 - }); 258 - }; 263 + state.replies ??= new HydrationMap<Reply>(); 259 264 260 - // layer 0: the posts in the thread 261 - const postsLayer0 = await this.feed.getPosts( 262 - uris, 265 + const [postsLayer0, repliesLayer0] = await Promise.all([ 266 + this.feed.getPosts( 267 + postRefs.map((ref) => ref.uri), 268 + ctx.includeTakedowns, 269 + state.posts, 270 + ), 271 + this.feed.getReplies( 272 + replyRefs.map((ref) => ref.uri), 273 + ctx.includeTakedowns, 274 + state.replies, 275 + ), 276 + ]); 277 + 278 + postsLayer0.forEach((post, uri) => { 279 + state.posts!.set(uri, post); 280 + }); 281 + repliesLayer0.forEach((reply, uri) => { 282 + state.replies!.set(uri, reply); 283 + }); 284 + 285 + const additionalRootUris = rootUrisFromReplies(repliesLayer0).filter( 286 + (uri) => !state.posts!.has(uri), 287 + ); 288 + 289 + const postsLayer1 = await this.feed.getPosts( 290 + additionalRootUris, 263 291 ctx.includeTakedowns, 264 292 state.posts, 265 293 ); 266 - addPostsToHydrationState(postsLayer0); 294 + postsLayer1.forEach((post, uri) => { 295 + state.posts!.set(uri, post); 296 + }); 267 297 268 - const additionalRootUris = rootUrisFromPosts(postsLayer0); // supports computing threadgates 269 - const threadRootUris = new Set<string>(); 270 - for (const [uri, post] of postsLayer0) { 271 - if (post) { 272 - threadRootUris.add(rootUriFromPost(post) ?? uri); 298 + const threadRefs: ThreadRef[] = []; 299 + for (const ref of refs) { 300 + const collection = new AtUri(ref.uri).collection; 301 + if (collection === ids.SoSprkFeedPost) { 302 + const post = state.posts!.get(ref.uri); 303 + if (!post) continue; 304 + threadRefs.push({ 305 + uri: ref.uri, 306 + cid: post.cid, 307 + threadRoot: ref.uri, 308 + }); 309 + } else if (collection === ids.SoSprkFeedReply) { 310 + const reply = state.replies!.get(ref.uri); 311 + if (!reply) continue; 312 + const rootUri = reply.record.reply?.root.uri ?? ref.uri; 313 + threadRefs.push({ 314 + uri: ref.uri, 315 + cid: reply.cid, 316 + threadRoot: rootUri, 317 + }); 273 318 } 274 319 } 275 320 276 - // fetch additional root URIs for threadgates 277 - const postsLayer1 = await this.feed.getPosts( 278 - additionalRootUris, 279 - ctx.includeTakedowns, 280 - state.posts, 321 + const authorUris = Array.from( 322 + new Set<string>([ 323 + ...state.posts!.keys(), 324 + ...state.replies!.keys(), 325 + ]), 281 326 ); 282 - addPostsToHydrationState(postsLayer1); 283 - 284 - const posts = mergeManyMaps(postsLayer0, postsLayer1) ?? postsLayer0; 285 - const allPostUris = [...posts.keys()]; 286 - const allRefs = refs; 287 - const threadRefs = allRefs.map((ref) => ({ 288 - ...ref, 289 - threadRoot: posts.get(ref.uri)?.record.reply?.root.uri ?? ref.uri, 290 - })); 327 + const authorDids = authorUris.map(didFromUri); 291 328 292 329 const [ 293 330 postAggs, 331 + replyAggs, 294 332 postViewers, 295 333 postBlocks, 296 334 profileState, 297 335 feedGenState, 336 + threadContexts, 298 337 ] = await Promise.all([ 299 - this.feed.getPostAggregates(allRefs), 338 + this.feed.getPostAggregates(postRefs), 339 + this.feed.getReplyAggregates(replyRefs), 300 340 ctx.viewer 301 341 ? this.feed.getPostViewerStates(threadRefs, ctx.viewer) 302 - : undefined, 303 - this.hydratePostBlocks(posts), 304 - this.hydrateProfiles(allPostUris.map(didFromUri), ctx), 342 + : Promise.resolve<PostViewerStates | undefined>(undefined), 343 + this.hydratePostBlocks(state.posts!, state.replies!), 344 + this.hydrateProfiles(authorDids, ctx), 305 345 this.hydrateFeedGens([], ctx), 346 + this.feed.getThreadContexts(threadRefs), 306 347 ]); 307 - // combine all hydration state 348 + 308 349 return mergeManyStates( 309 350 profileState, 310 351 feedGenState, 311 352 { 312 - posts, 353 + posts: state.posts, 354 + replies: state.replies, 313 355 postAggs, 356 + replyAggs, 314 357 postViewers, 315 358 postBlocks, 359 + threadContexts, 316 360 ctx, 317 361 }, 318 362 ); ··· 320 364 321 365 private async hydratePostBlocks( 322 366 posts: Posts, 367 + replies: Replies, 323 368 ): Promise<PostBlocks> { 324 369 const postBlocks = new HydrationMap<PostBlock>(); 325 370 const postBlocksPairs = new Map<string, PostBlockPairs>(); 326 371 const relationships: RelationshipPair[] = []; 372 + 327 373 for (const [uri, item] of posts) { 328 374 if (!item) continue; 329 - const post = item.record; 375 + postBlocksPairs.set(uri, {}); 376 + } 377 + 378 + for (const [uri, item] of replies) { 379 + if (!item) continue; 380 + const reply = item.record; 330 381 const creator = didFromUri(uri); 331 - const postBlockPairs: PostBlockPairs = {}; 332 - postBlocksPairs.set(uri, postBlockPairs); 333 - // 3p block for replies 334 - const parentUri = post.reply?.parent.uri; 382 + const pairs = postBlocksPairs.get(uri) ?? {}; 383 + postBlocksPairs.set(uri, pairs); 384 + 385 + const parentUri = reply.reply?.parent.uri; 335 386 const parentDid = parentUri && didFromUri(parentUri); 336 387 if (parentDid && parentDid !== creator) { 337 388 const pair: RelationshipPair = [creator, parentDid]; 338 389 relationships.push(pair); 339 - postBlockPairs.parent = pair; 390 + pairs.parent = pair; 340 391 } 341 - const rootUri = post.reply?.root.uri; 392 + 393 + const rootUri = reply.reply?.root.uri; 342 394 const rootDid = rootUri && didFromUri(rootUri); 343 395 if (rootDid && rootDid !== creator) { 344 396 const pair: RelationshipPair = [creator, rootDid]; 345 397 relationships.push(pair); 346 - postBlockPairs.root = pair; 398 + pairs.root = pair; 347 399 } 348 - // No embed blocking - nested record logic removed 349 400 } 350 - // replace embed/parent/root pairs with block state 401 + 351 402 const blocks = await this.hydrateBidirectionalBlocks( 352 403 pairsToMap(relationships), 353 404 ); 405 + 354 406 for (const [uri, { embed, parent, root }] of postBlocksPairs) { 355 407 postBlocks.set(uri, { 356 408 embed: !!embed && !!isBlocked(blocks, embed), ··· 358 410 root: !!root && !!isBlocked(blocks, root), 359 411 }); 360 412 } 413 + 361 414 return postBlocks; 362 415 } 363 416 ··· 380 433 items: FeedItem[], 381 434 ctx: HydrateCtx, 382 435 ): Promise<HydrationState> { 383 - // get posts, collect reply refs 384 - const posts = await this.feed.getPosts( 385 - items.map((item) => item.post.uri), 386 - ctx.includeTakedowns, 387 - ); 388 - const rootUris: string[] = []; 389 - const parentUris: string[] = []; 390 - const postAndReplyRefs: ItemRef[] = []; 391 - posts.forEach((post, uri) => { 392 - if (!post) return; 393 - postAndReplyRefs.push({ uri, cid: post.cid }); 394 - if (post.record.reply) { 395 - rootUris.push(post.record.reply.root.uri); 396 - parentUris.push(post.record.reply.parent.uri); 397 - postAndReplyRefs.push(post.record.reply.root, post.record.reply.parent); 436 + const postUris: string[] = []; 437 + const replyUris: string[] = []; 438 + const replyRefs: ItemRef[] = []; 439 + 440 + for (const { post } of items) { 441 + const collection = new AtUri(post.uri).collection; 442 + if (collection === ids.SoSprkFeedPost) { 443 + postUris.push(post.uri); 444 + } else if (collection === ids.SoSprkFeedReply) { 445 + replyUris.push(post.uri); 446 + replyRefs.push(post); 398 447 } 448 + } 449 + 450 + const [posts, replies] = await Promise.all([ 451 + this.feed.getPosts(postUris, ctx.includeTakedowns), 452 + this.feed.getReplies(replyUris, ctx.includeTakedowns), 453 + ]); 454 + 455 + const postAndReplyRefsMap = new Map<string, ItemRef>(); 456 + items.forEach((item) => { 457 + postAndReplyRefsMap.set(item.post.uri, item.post); 399 458 }); 400 - // get replies, collect reply parent authors 401 - const replies = await this.feed.getPosts( 402 - [...rootUris, ...parentUris], 403 - ctx.includeTakedowns, 404 - ); 405 - const replyParentAuthors: string[] = []; 406 - parentUris.forEach((uri) => { 407 - const parent = replies.get(uri); 408 - if (!parent?.record.reply) return; 409 - replyParentAuthors.push(didFromUri(parent.record.reply.parent.uri)); 459 + 460 + replies.forEach((reply) => { 461 + if (!reply?.record.reply) return; 462 + const { root, parent } = reply.record.reply; 463 + postAndReplyRefsMap.set(root.uri, root); 464 + postAndReplyRefsMap.set(parent.uri, parent); 410 465 }); 411 - // hydrate state for all posts, reposts, authors of reposts + reply parent authors 466 + 467 + const postAndReplyRefs = Array.from(postAndReplyRefsMap.values()); 412 468 const repostUris = mapDefined(items, (item) => item.repost?.uri); 413 - const [postState, repostProfileState, reposts] = await Promise.all([ 414 - this.hydratePosts(postAndReplyRefs, ctx, { 415 - posts: posts.merge(replies), // avoids refetches of posts 416 - }), 469 + 470 + const postState = await this.hydratePosts(postAndReplyRefs, ctx, { 471 + posts, 472 + replies, 473 + }); 474 + 475 + const replyParentAuthors = Array.from( 476 + new Set( 477 + replyRefs 478 + .map((ref) => 479 + postState.replies?.get(ref.uri)?.record.reply?.parent.uri 480 + ) 481 + .filter((uri): uri is string => !!uri) 482 + .map(didFromUri), 483 + ), 484 + ); 485 + 486 + const [repostProfileState, reposts] = await Promise.all([ 417 487 this.hydrateProfiles( 418 488 [...repostUris.map(didFromUri), ...replyParentAuthors], 419 489 ctx, 420 490 ), 421 491 this.feed.getReposts(repostUris, ctx.includeTakedowns), 422 492 ]); 493 + 423 494 return mergeManyStates(postState, repostProfileState, { 424 495 reposts, 425 496 ctx, 426 497 }); 427 498 } 428 499 429 - // so.sprk.feed.defs#threadViewPost 430 - // - post 500 + // so.sprk.feed.defs#threadViewReply 501 + // - reply 431 502 // - profile 432 503 // - list basic 433 504 // - list ··· 436 507 // - feedgen 437 508 // - profile 438 509 // - list basic 439 - async hydrateThreadPosts( 510 + hydrateThreadPosts( 440 511 refs: ItemRef[], 441 512 ctx: HydrateCtx, 442 513 ): Promise<HydrationState> { 443 - const postsState = await this.hydratePosts(refs, ctx); 444 - 445 - const { posts } = postsState; 446 - const postsList = posts ? Array.from(posts.entries()) : []; 447 - 448 - const isDefined = ( 449 - entry: [string, Post | null], 450 - ): entry is [string, Post] => { 451 - const [, post] = entry; 452 - return !!post; 453 - }; 454 - 455 - const threadRefs: ThreadRef[] = postsList 456 - .filter(isDefined) 457 - .map(([uri, post]) => ({ 458 - uri, 459 - cid: post.cid, 460 - threadRoot: post.record.reply?.root.uri ?? uri, 461 - })); 462 - 463 - const threadContexts = await this.feed.getThreadContexts(threadRefs); 464 - 465 - return mergeStates(postsState, { threadContexts }); 514 + return this.hydratePosts(refs, ctx); 466 515 } 467 516 468 517 // so.sprk.feed.defs#generatorView ··· 539 588 return mergeStates(profileState, { reposts, ctx }); 540 589 } 541 590 591 + // so.sprk.sound.defs#audioView 592 + // - sound 593 + // - profile 594 + // - list basic 595 + async hydrateSounds( 596 + uris: string[], 597 + ctx: HydrateCtx, 598 + ): Promise<HydrationState> { 599 + const [sounds, soundAggs, profileState] = await Promise.all([ 600 + this.feed.getSounds(uris, ctx.includeTakedowns), 601 + this.feed.getSoundAggregates(uris.map((uri) => ({ uri }))), 602 + this.hydrateProfiles(uris.map(didFromUri), ctx), 603 + ]); 604 + return mergeStates(profileState, { sounds, soundAggs, ctx }); 605 + } 606 + 542 607 // provides partial hydration state within getFollows / getFollowers, mainly for applying rules 543 608 async hydrateFollows( 544 609 uris: string[], ··· 612 677 (await this.feed.getPosts([uri], includeTakedowns)).get(uri) ?? 613 678 undefined 614 679 ); 680 + } else if (collection === ids.SoSprkFeedReply) { 681 + return ( 682 + (await this.feed.getReplies([uri], includeTakedowns)).get(uri) ?? 683 + undefined 684 + ); 615 685 } else if (collection === ids.AppBskyFeedRepost) { 616 686 return ( 617 687 (await this.feed.getReposts([uri], includeTakedowns)).get(uri) ?? ··· 620 690 } else if (collection === ids.SoSprkFeedLike) { 621 691 return ( 622 692 (await this.feed.getLikes([uri], includeTakedowns)).get(uri) ?? 693 + undefined 694 + ); 695 + } else if (collection === ids.SoSprkSoundAudio) { 696 + return ( 697 + (await this.feed.getSounds([uri], includeTakedowns)).get(uri) ?? 623 698 undefined 624 699 ); 625 700 } else if (collection === ids.AppBskyGraphFollow) { ··· 681 756 return idx !== -1 ? serviceRef.slice(0, idx) : serviceRef; 682 757 }; 683 758 684 - const rootUrisFromPosts = (posts: Posts): string[] => { 685 - const uris: string[] = []; 686 - for (const item of posts.values()) { 687 - const rootUri = item && rootUriFromPost(item); 759 + const rootUrisFromReplies = (replies: Replies): string[] => { 760 + const uris = new Set<string>(); 761 + for (const item of replies.values()) { 762 + const rootUri = item && rootUriFromReply(item); 688 763 if (rootUri) { 689 - uris.push(rootUri); 764 + uris.add(rootUri); 690 765 } 691 766 } 692 - return uris; 767 + return Array.from(uris); 693 768 }; 694 769 695 - const rootUriFromPost = (post: Post): string | undefined => { 696 - return post.record.reply?.root.uri; 770 + const rootUriFromReply = (reply: Reply): string | undefined => { 771 + return reply.record.reply?.root.uri; 697 772 }; 698 773 699 774 const isBlocked = (blocks: BidirectionalBlocks, [a, b]: RelationshipPair) => { ··· 726 801 profileAggs: mergeMaps(stateA.profileAggs, stateB.profileAggs), 727 802 profileViewers: mergeMaps(stateA.profileViewers, stateB.profileViewers), 728 803 posts: mergeMaps(stateA.posts, stateB.posts), 804 + replies: mergeMaps(stateA.replies, stateB.replies), 729 805 postAggs: mergeMaps(stateA.postAggs, stateB.postAggs), 806 + replyAggs: mergeMaps(stateA.replyAggs, stateB.replyAggs), 730 807 postViewers: mergeMaps(stateA.postViewers, stateB.postViewers), 731 808 threadContexts: mergeMaps(stateA.threadContexts, stateB.threadContexts), 809 + sounds: mergeMaps(stateA.sounds, stateB.sounds), 810 + soundAggs: mergeMaps(stateA.soundAggs, stateB.soundAggs), 732 811 postBlocks: mergeMaps(stateA.postBlocks, stateB.postBlocks), 733 812 reposts: mergeMaps(stateA.reposts, stateB.reposts), 734 813 follows: mergeMaps(stateA.follows, stateB.follows),
-1
ingest.ts
··· 23 23 idResolver, 24 24 startCursor, 25 25 }); 26 - await sub.indexingSvc.indexRepo("did:plc:6hbqm2oftpotwuw7gvvrui3i"); 27 26 28 27 sub.start(); 29 28 logger.info("Subscription started");
+46 -314
lex/index.ts
··· 132 132 import * as SoSprkNotificationUpdateSeen from "./types/so/sprk/notification/updateSeen.ts"; 133 133 import * as SoSprkNotificationListNotifications from "./types/so/sprk/notification/listNotifications.ts"; 134 134 import * as SoSprkNotificationGetUnreadCount from "./types/so/sprk/notification/getUnreadCount.ts"; 135 - import * as SoSprkUnspeccedSearchStarterPacksSkeleton from "./types/so/sprk/unspecced/searchStarterPacksSkeleton.ts"; 136 - import * as SoSprkUnspeccedSearchActorsSkeleton from "./types/so/sprk/unspecced/searchActorsSkeleton.ts"; 137 - import * as SoSprkUnspeccedGetSuggestionsSkeleton from "./types/so/sprk/unspecced/getSuggestionsSkeleton.ts"; 138 - import * as SoSprkUnspeccedSearchPostsSkeleton from "./types/so/sprk/unspecced/searchPostsSkeleton.ts"; 139 - import * as SoSprkUnspeccedGetPopularFeedGenerators from "./types/so/sprk/unspecced/getPopularFeedGenerators.ts"; 140 - import * as SoSprkUnspeccedGetTrendingTopics from "./types/so/sprk/unspecced/getTrendingTopics.ts"; 141 - import * as SoSprkUnspeccedGetTaggedSuggestions from "./types/so/sprk/unspecced/getTaggedSuggestions.ts"; 142 - import * as SoSprkUnspeccedGetConfig from "./types/so/sprk/unspecced/getConfig.ts"; 143 - import * as SoSprkGraphGetStarterPacks from "./types/so/sprk/graph/getStarterPacks.ts"; 144 135 import * as SoSprkGraphGetSuggestedFollowsByActor from "./types/so/sprk/graph/getSuggestedFollowsByActor.ts"; 145 - import * as SoSprkGraphUnmuteActorList from "./types/so/sprk/graph/unmuteActorList.ts"; 146 - import * as SoSprkGraphGetListBlocks from "./types/so/sprk/graph/getListBlocks.ts"; 147 - import * as SoSprkGraphGetStarterPack from "./types/so/sprk/graph/getStarterPack.ts"; 148 - import * as SoSprkGraphMuteActorList from "./types/so/sprk/graph/muteActorList.ts"; 149 136 import * as SoSprkGraphMuteThread from "./types/so/sprk/graph/muteThread.ts"; 150 - import * as SoSprkGraphSearchStarterPacks from "./types/so/sprk/graph/searchStarterPacks.ts"; 151 - import * as SoSprkGraphGetActorStarterPacks from "./types/so/sprk/graph/getActorStarterPacks.ts"; 152 - import * as SoSprkGraphGetLists from "./types/so/sprk/graph/getLists.ts"; 153 137 import * as SoSprkGraphGetFollowers from "./types/so/sprk/graph/getFollowers.ts"; 154 138 import * as SoSprkGraphUnmuteThread from "./types/so/sprk/graph/unmuteThread.ts"; 155 139 import * as SoSprkGraphMuteActor from "./types/so/sprk/graph/muteActor.ts"; 156 140 import * as SoSprkGraphGetMutes from "./types/so/sprk/graph/getMutes.ts"; 157 141 import * as SoSprkGraphGetKnownFollowers from "./types/so/sprk/graph/getKnownFollowers.ts"; 158 - import * as SoSprkGraphGetListMutes from "./types/so/sprk/graph/getListMutes.ts"; 159 142 import * as SoSprkGraphGetFollows from "./types/so/sprk/graph/getFollows.ts"; 160 143 import * as SoSprkGraphGetBlocks from "./types/so/sprk/graph/getBlocks.ts"; 161 144 import * as SoSprkGraphGetRelationships from "./types/so/sprk/graph/getRelationships.ts"; 162 145 import * as SoSprkGraphUnmuteActor from "./types/so/sprk/graph/unmuteActor.ts"; 163 - import * as SoSprkGraphGetList from "./types/so/sprk/graph/getList.ts"; 164 146 import * as SoSprkFeedSendInteractions from "./types/so/sprk/feed/sendInteractions.ts"; 165 147 import * as SoSprkFeedGetFeedGenerators from "./types/so/sprk/feed/getFeedGenerators.ts"; 166 148 import * as SoSprkFeedGetTimeline from "./types/so/sprk/feed/getTimeline.ts"; ··· 174 156 import * as SoSprkFeedSearchPosts from "./types/so/sprk/feed/searchPosts.ts"; 175 157 import * as SoSprkFeedGetPosts from "./types/so/sprk/feed/getPosts.ts"; 176 158 import * as SoSprkFeedGetFeed from "./types/so/sprk/feed/getFeed.ts"; 177 - import * as SoSprkFeedGetStories from "./types/so/sprk/feed/getStories.ts"; 178 - import * as SoSprkFeedGetQuotes from "./types/so/sprk/feed/getQuotes.ts"; 179 - import * as SoSprkFeedGetStoriesTimeline from "./types/so/sprk/feed/getStoriesTimeline.ts"; 180 159 import * as SoSprkFeedGetFeedSkeleton from "./types/so/sprk/feed/getFeedSkeleton.ts"; 181 - import * as SoSprkFeedGetListFeed from "./types/so/sprk/feed/getListFeed.ts"; 182 160 import * as SoSprkFeedGetSuggestedFeeds from "./types/so/sprk/feed/getSuggestedFeeds.ts"; 183 161 import * as SoSprkFeedGetActorFeeds from "./types/so/sprk/feed/getActorFeeds.ts"; 184 162 import * as SoSprkSoundGetActorAudios from "./types/so/sprk/sound/getActorAudios.ts"; ··· 192 170 import * as SoSprkActorSearchActors from "./types/so/sprk/actor/searchActors.ts"; 193 171 import * as SoSprkActorGetProfiles from "./types/so/sprk/actor/getProfiles.ts"; 194 172 import * as SoSprkActorGetPreferences from "./types/so/sprk/actor/getPreferences.ts"; 173 + import * as SoSprkStoryGetTimeline from "./types/so/sprk/story/getTimeline.ts"; 174 + import * as SoSprkStoryGetStories from "./types/so/sprk/story/getStories.ts"; 195 175 import * as SoSprkLabelerGetServices from "./types/so/sprk/labeler/getServices.ts"; 196 176 import * as ComAtprotoTempAddReservedHandle from "./types/com/atproto/temp/addReservedHandle.ts"; 197 177 import * as ComAtprotoTempCheckSignupQueue from "./types/com/atproto/temp/checkSignupQueue.ts"; ··· 302 282 DefsInteractionQuote: "app.bsky.feed.defs#interactionQuote", 303 283 DefsInteractionShare: "app.bsky.feed.defs#interactionShare", 304 284 }; 305 - export const SO_SPRK_GRAPH = { 306 - DefsModlist: "so.sprk.graph.defs#modlist", 307 - DefsCuratelist: "so.sprk.graph.defs#curatelist", 308 - DefsReferencelist: "so.sprk.graph.defs#referencelist", 309 - }; 310 285 export const SO_SPRK_FEED = { 311 286 DefsRequestLess: "so.sprk.feed.defs#requestLess", 312 287 DefsRequestMore: "so.sprk.feed.defs#requestMore", ··· 314 289 DefsClickthroughAuthor: "so.sprk.feed.defs#clickthroughAuthor", 315 290 DefsClickthroughReposter: "so.sprk.feed.defs#clickthroughReposter", 316 291 DefsClickthroughEmbed: "so.sprk.feed.defs#clickthroughEmbed", 317 - DefsContentModeUnspecified: "so.sprk.feed.defs#contentModeUnspecified", 318 - DefsContentModeVideo: "so.sprk.feed.defs#contentModeVideo", 319 292 DefsInteractionSeen: "so.sprk.feed.defs#interactionSeen", 320 293 DefsInteractionLike: "so.sprk.feed.defs#interactionLike", 321 294 DefsInteractionRepost: "so.sprk.feed.defs#interactionRepost", 322 295 DefsInteractionReply: "so.sprk.feed.defs#interactionReply", 323 - DefsInteractionQuote: "so.sprk.feed.defs#interactionQuote", 324 296 DefsInteractionShare: "so.sprk.feed.defs#interactionShare", 325 297 }; 326 298 export const COM_ATPROTO_MODERATION = { ··· 1980 1952 export class SoSprkNS { 1981 1953 _server: Server; 1982 1954 video: SoSprkVideoNS; 1983 - embed: SoSprkEmbedNS; 1984 1955 notification: SoSprkNotificationNS; 1985 - unspecced: SoSprkUnspeccedNS; 1986 1956 graph: SoSprkGraphNS; 1987 1957 feed: SoSprkFeedNS; 1988 1958 richtext: SoSprkRichtextNS; 1989 1959 sound: SoSprkSoundNS; 1990 1960 actor: SoSprkActorNS; 1961 + story: SoSprkStoryNS; 1991 1962 labeler: SoSprkLabelerNS; 1963 + media: SoSprkMediaNS; 1992 1964 1993 1965 constructor(server: Server) { 1994 1966 this._server = server; 1995 1967 this.video = new SoSprkVideoNS(server); 1996 - this.embed = new SoSprkEmbedNS(server); 1997 1968 this.notification = new SoSprkNotificationNS(server); 1998 - this.unspecced = new SoSprkUnspeccedNS(server); 1999 1969 this.graph = new SoSprkGraphNS(server); 2000 1970 this.feed = new SoSprkFeedNS(server); 2001 1971 this.richtext = new SoSprkRichtextNS(server); 2002 1972 this.sound = new SoSprkSoundNS(server); 2003 1973 this.actor = new SoSprkActorNS(server); 1974 + this.story = new SoSprkStoryNS(server); 2004 1975 this.labeler = new SoSprkLabelerNS(server); 1976 + this.media = new SoSprkMediaNS(server); 2005 1977 } 2006 1978 } 2007 1979 ··· 2049 2021 } 2050 2022 } 2051 2023 2052 - export class SoSprkEmbedNS { 2053 - _server: Server; 2054 - 2055 - constructor(server: Server) { 2056 - this._server = server; 2057 - } 2058 - } 2059 - 2060 2024 export class SoSprkNotificationNS { 2061 2025 _server: Server; 2062 2026 ··· 2125 2089 } 2126 2090 } 2127 2091 2128 - export class SoSprkUnspeccedNS { 2129 - _server: Server; 2130 - 2131 - constructor(server: Server) { 2132 - this._server = server; 2133 - } 2134 - 2135 - searchStarterPacksSkeleton<A extends Auth = void>( 2136 - cfg: MethodConfigOrHandler< 2137 - A, 2138 - SoSprkUnspeccedSearchStarterPacksSkeleton.QueryParams, 2139 - SoSprkUnspeccedSearchStarterPacksSkeleton.HandlerInput, 2140 - SoSprkUnspeccedSearchStarterPacksSkeleton.HandlerOutput 2141 - >, 2142 - ) { 2143 - const nsid = "so.sprk.unspecced.searchStarterPacksSkeleton"; // @ts-ignore - dynamically generated 2144 - return this._server.xrpc.method(nsid, cfg); 2145 - } 2146 - 2147 - searchActorsSkeleton<A extends Auth = void>( 2148 - cfg: MethodConfigOrHandler< 2149 - A, 2150 - SoSprkUnspeccedSearchActorsSkeleton.QueryParams, 2151 - SoSprkUnspeccedSearchActorsSkeleton.HandlerInput, 2152 - SoSprkUnspeccedSearchActorsSkeleton.HandlerOutput 2153 - >, 2154 - ) { 2155 - const nsid = "so.sprk.unspecced.searchActorsSkeleton"; // @ts-ignore - dynamically generated 2156 - return this._server.xrpc.method(nsid, cfg); 2157 - } 2158 - 2159 - getSuggestionsSkeleton<A extends Auth = void>( 2160 - cfg: MethodConfigOrHandler< 2161 - A, 2162 - SoSprkUnspeccedGetSuggestionsSkeleton.QueryParams, 2163 - SoSprkUnspeccedGetSuggestionsSkeleton.HandlerInput, 2164 - SoSprkUnspeccedGetSuggestionsSkeleton.HandlerOutput 2165 - >, 2166 - ) { 2167 - const nsid = "so.sprk.unspecced.getSuggestionsSkeleton"; // @ts-ignore - dynamically generated 2168 - return this._server.xrpc.method(nsid, cfg); 2169 - } 2170 - 2171 - searchPostsSkeleton<A extends Auth = void>( 2172 - cfg: MethodConfigOrHandler< 2173 - A, 2174 - SoSprkUnspeccedSearchPostsSkeleton.QueryParams, 2175 - SoSprkUnspeccedSearchPostsSkeleton.HandlerInput, 2176 - SoSprkUnspeccedSearchPostsSkeleton.HandlerOutput 2177 - >, 2178 - ) { 2179 - const nsid = "so.sprk.unspecced.searchPostsSkeleton"; // @ts-ignore - dynamically generated 2180 - return this._server.xrpc.method(nsid, cfg); 2181 - } 2182 - 2183 - getPopularFeedGenerators<A extends Auth = void>( 2184 - cfg: MethodConfigOrHandler< 2185 - A, 2186 - SoSprkUnspeccedGetPopularFeedGenerators.QueryParams, 2187 - SoSprkUnspeccedGetPopularFeedGenerators.HandlerInput, 2188 - SoSprkUnspeccedGetPopularFeedGenerators.HandlerOutput 2189 - >, 2190 - ) { 2191 - const nsid = "so.sprk.unspecced.getPopularFeedGenerators"; // @ts-ignore - dynamically generated 2192 - return this._server.xrpc.method(nsid, cfg); 2193 - } 2194 - 2195 - getTrendingTopics<A extends Auth = void>( 2196 - cfg: MethodConfigOrHandler< 2197 - A, 2198 - SoSprkUnspeccedGetTrendingTopics.QueryParams, 2199 - SoSprkUnspeccedGetTrendingTopics.HandlerInput, 2200 - SoSprkUnspeccedGetTrendingTopics.HandlerOutput 2201 - >, 2202 - ) { 2203 - const nsid = "so.sprk.unspecced.getTrendingTopics"; // @ts-ignore - dynamically generated 2204 - return this._server.xrpc.method(nsid, cfg); 2205 - } 2206 - 2207 - getTaggedSuggestions<A extends Auth = void>( 2208 - cfg: MethodConfigOrHandler< 2209 - A, 2210 - SoSprkUnspeccedGetTaggedSuggestions.QueryParams, 2211 - SoSprkUnspeccedGetTaggedSuggestions.HandlerInput, 2212 - SoSprkUnspeccedGetTaggedSuggestions.HandlerOutput 2213 - >, 2214 - ) { 2215 - const nsid = "so.sprk.unspecced.getTaggedSuggestions"; // @ts-ignore - dynamically generated 2216 - return this._server.xrpc.method(nsid, cfg); 2217 - } 2218 - 2219 - getConfig<A extends Auth = void>( 2220 - cfg: MethodConfigOrHandler< 2221 - A, 2222 - SoSprkUnspeccedGetConfig.QueryParams, 2223 - SoSprkUnspeccedGetConfig.HandlerInput, 2224 - SoSprkUnspeccedGetConfig.HandlerOutput 2225 - >, 2226 - ) { 2227 - const nsid = "so.sprk.unspecced.getConfig"; // @ts-ignore - dynamically generated 2228 - return this._server.xrpc.method(nsid, cfg); 2229 - } 2230 - } 2231 - 2232 2092 export class SoSprkGraphNS { 2233 2093 _server: Server; 2234 2094 ··· 2236 2096 this._server = server; 2237 2097 } 2238 2098 2239 - getStarterPacks<A extends Auth = void>( 2240 - cfg: MethodConfigOrHandler< 2241 - A, 2242 - SoSprkGraphGetStarterPacks.QueryParams, 2243 - SoSprkGraphGetStarterPacks.HandlerInput, 2244 - SoSprkGraphGetStarterPacks.HandlerOutput 2245 - >, 2246 - ) { 2247 - const nsid = "so.sprk.graph.getStarterPacks"; // @ts-ignore - dynamically generated 2248 - return this._server.xrpc.method(nsid, cfg); 2249 - } 2250 - 2251 2099 getSuggestedFollowsByActor<A extends Auth = void>( 2252 2100 cfg: MethodConfigOrHandler< 2253 2101 A, ··· 2260 2108 return this._server.xrpc.method(nsid, cfg); 2261 2109 } 2262 2110 2263 - unmuteActorList<A extends Auth = void>( 2264 - cfg: MethodConfigOrHandler< 2265 - A, 2266 - SoSprkGraphUnmuteActorList.QueryParams, 2267 - SoSprkGraphUnmuteActorList.HandlerInput, 2268 - SoSprkGraphUnmuteActorList.HandlerOutput 2269 - >, 2270 - ) { 2271 - const nsid = "so.sprk.graph.unmuteActorList"; // @ts-ignore - dynamically generated 2272 - return this._server.xrpc.method(nsid, cfg); 2273 - } 2274 - 2275 - getListBlocks<A extends Auth = void>( 2276 - cfg: MethodConfigOrHandler< 2277 - A, 2278 - SoSprkGraphGetListBlocks.QueryParams, 2279 - SoSprkGraphGetListBlocks.HandlerInput, 2280 - SoSprkGraphGetListBlocks.HandlerOutput 2281 - >, 2282 - ) { 2283 - const nsid = "so.sprk.graph.getListBlocks"; // @ts-ignore - dynamically generated 2284 - return this._server.xrpc.method(nsid, cfg); 2285 - } 2286 - 2287 - getStarterPack<A extends Auth = void>( 2288 - cfg: MethodConfigOrHandler< 2289 - A, 2290 - SoSprkGraphGetStarterPack.QueryParams, 2291 - SoSprkGraphGetStarterPack.HandlerInput, 2292 - SoSprkGraphGetStarterPack.HandlerOutput 2293 - >, 2294 - ) { 2295 - const nsid = "so.sprk.graph.getStarterPack"; // @ts-ignore - dynamically generated 2296 - return this._server.xrpc.method(nsid, cfg); 2297 - } 2298 - 2299 - muteActorList<A extends Auth = void>( 2300 - cfg: MethodConfigOrHandler< 2301 - A, 2302 - SoSprkGraphMuteActorList.QueryParams, 2303 - SoSprkGraphMuteActorList.HandlerInput, 2304 - SoSprkGraphMuteActorList.HandlerOutput 2305 - >, 2306 - ) { 2307 - const nsid = "so.sprk.graph.muteActorList"; // @ts-ignore - dynamically generated 2308 - return this._server.xrpc.method(nsid, cfg); 2309 - } 2310 - 2311 2111 muteThread<A extends Auth = void>( 2312 2112 cfg: MethodConfigOrHandler< 2313 2113 A, ··· 2320 2120 return this._server.xrpc.method(nsid, cfg); 2321 2121 } 2322 2122 2323 - searchStarterPacks<A extends Auth = void>( 2324 - cfg: MethodConfigOrHandler< 2325 - A, 2326 - SoSprkGraphSearchStarterPacks.QueryParams, 2327 - SoSprkGraphSearchStarterPacks.HandlerInput, 2328 - SoSprkGraphSearchStarterPacks.HandlerOutput 2329 - >, 2330 - ) { 2331 - const nsid = "so.sprk.graph.searchStarterPacks"; // @ts-ignore - dynamically generated 2332 - return this._server.xrpc.method(nsid, cfg); 2333 - } 2334 - 2335 - getActorStarterPacks<A extends Auth = void>( 2336 - cfg: MethodConfigOrHandler< 2337 - A, 2338 - SoSprkGraphGetActorStarterPacks.QueryParams, 2339 - SoSprkGraphGetActorStarterPacks.HandlerInput, 2340 - SoSprkGraphGetActorStarterPacks.HandlerOutput 2341 - >, 2342 - ) { 2343 - const nsid = "so.sprk.graph.getActorStarterPacks"; // @ts-ignore - dynamically generated 2344 - return this._server.xrpc.method(nsid, cfg); 2345 - } 2346 - 2347 - getLists<A extends Auth = void>( 2348 - cfg: MethodConfigOrHandler< 2349 - A, 2350 - SoSprkGraphGetLists.QueryParams, 2351 - SoSprkGraphGetLists.HandlerInput, 2352 - SoSprkGraphGetLists.HandlerOutput 2353 - >, 2354 - ) { 2355 - const nsid = "so.sprk.graph.getLists"; // @ts-ignore - dynamically generated 2356 - return this._server.xrpc.method(nsid, cfg); 2357 - } 2358 - 2359 2123 getFollowers<A extends Auth = void>( 2360 2124 cfg: MethodConfigOrHandler< 2361 2125 A, ··· 2416 2180 return this._server.xrpc.method(nsid, cfg); 2417 2181 } 2418 2182 2419 - getListMutes<A extends Auth = void>( 2420 - cfg: MethodConfigOrHandler< 2421 - A, 2422 - SoSprkGraphGetListMutes.QueryParams, 2423 - SoSprkGraphGetListMutes.HandlerInput, 2424 - SoSprkGraphGetListMutes.HandlerOutput 2425 - >, 2426 - ) { 2427 - const nsid = "so.sprk.graph.getListMutes"; // @ts-ignore - dynamically generated 2428 - return this._server.xrpc.method(nsid, cfg); 2429 - } 2430 - 2431 2183 getFollows<A extends Auth = void>( 2432 2184 cfg: MethodConfigOrHandler< 2433 2185 A, ··· 2473 2225 >, 2474 2226 ) { 2475 2227 const nsid = "so.sprk.graph.unmuteActor"; // @ts-ignore - dynamically generated 2476 - return this._server.xrpc.method(nsid, cfg); 2477 - } 2478 - 2479 - getList<A extends Auth = void>( 2480 - cfg: MethodConfigOrHandler< 2481 - A, 2482 - SoSprkGraphGetList.QueryParams, 2483 - SoSprkGraphGetList.HandlerInput, 2484 - SoSprkGraphGetList.HandlerOutput 2485 - >, 2486 - ) { 2487 - const nsid = "so.sprk.graph.getList"; // @ts-ignore - dynamically generated 2488 2228 return this._server.xrpc.method(nsid, cfg); 2489 2229 } 2490 2230 } ··· 2652 2392 return this._server.xrpc.method(nsid, cfg); 2653 2393 } 2654 2394 2655 - getStories<A extends Auth = void>( 2656 - cfg: MethodConfigOrHandler< 2657 - A, 2658 - SoSprkFeedGetStories.QueryParams, 2659 - SoSprkFeedGetStories.HandlerInput, 2660 - SoSprkFeedGetStories.HandlerOutput 2661 - >, 2662 - ) { 2663 - const nsid = "so.sprk.feed.getStories"; // @ts-ignore - dynamically generated 2664 - return this._server.xrpc.method(nsid, cfg); 2665 - } 2666 - 2667 - getQuotes<A extends Auth = void>( 2668 - cfg: MethodConfigOrHandler< 2669 - A, 2670 - SoSprkFeedGetQuotes.QueryParams, 2671 - SoSprkFeedGetQuotes.HandlerInput, 2672 - SoSprkFeedGetQuotes.HandlerOutput 2673 - >, 2674 - ) { 2675 - const nsid = "so.sprk.feed.getQuotes"; // @ts-ignore - dynamically generated 2676 - return this._server.xrpc.method(nsid, cfg); 2677 - } 2678 - 2679 - getStoriesTimeline<A extends Auth = void>( 2680 - cfg: MethodConfigOrHandler< 2681 - A, 2682 - SoSprkFeedGetStoriesTimeline.QueryParams, 2683 - SoSprkFeedGetStoriesTimeline.HandlerInput, 2684 - SoSprkFeedGetStoriesTimeline.HandlerOutput 2685 - >, 2686 - ) { 2687 - const nsid = "so.sprk.feed.getStoriesTimeline"; // @ts-ignore - dynamically generated 2688 - return this._server.xrpc.method(nsid, cfg); 2689 - } 2690 - 2691 2395 getFeedSkeleton<A extends Auth = void>( 2692 2396 cfg: MethodConfigOrHandler< 2693 2397 A, ··· 2697 2401 >, 2698 2402 ) { 2699 2403 const nsid = "so.sprk.feed.getFeedSkeleton"; // @ts-ignore - dynamically generated 2700 - return this._server.xrpc.method(nsid, cfg); 2701 - } 2702 - 2703 - getListFeed<A extends Auth = void>( 2704 - cfg: MethodConfigOrHandler< 2705 - A, 2706 - SoSprkFeedGetListFeed.QueryParams, 2707 - SoSprkFeedGetListFeed.HandlerInput, 2708 - SoSprkFeedGetListFeed.HandlerOutput 2709 - >, 2710 - ) { 2711 - const nsid = "so.sprk.feed.getListFeed"; // @ts-ignore - dynamically generated 2712 2404 return this._server.xrpc.method(nsid, cfg); 2713 2405 } 2714 2406 ··· 2893 2585 } 2894 2586 } 2895 2587 2588 + export class SoSprkStoryNS { 2589 + _server: Server; 2590 + 2591 + constructor(server: Server) { 2592 + this._server = server; 2593 + } 2594 + 2595 + getTimeline<A extends Auth = void>( 2596 + cfg: MethodConfigOrHandler< 2597 + A, 2598 + SoSprkStoryGetTimeline.QueryParams, 2599 + SoSprkStoryGetTimeline.HandlerInput, 2600 + SoSprkStoryGetTimeline.HandlerOutput 2601 + >, 2602 + ) { 2603 + const nsid = "so.sprk.story.getTimeline"; // @ts-ignore - dynamically generated 2604 + return this._server.xrpc.method(nsid, cfg); 2605 + } 2606 + 2607 + getStories<A extends Auth = void>( 2608 + cfg: MethodConfigOrHandler< 2609 + A, 2610 + SoSprkStoryGetStories.QueryParams, 2611 + SoSprkStoryGetStories.HandlerInput, 2612 + SoSprkStoryGetStories.HandlerOutput 2613 + >, 2614 + ) { 2615 + const nsid = "so.sprk.story.getStories"; // @ts-ignore - dynamically generated 2616 + return this._server.xrpc.method(nsid, cfg); 2617 + } 2618 + } 2619 + 2896 2620 export class SoSprkLabelerNS { 2897 2621 _server: Server; 2898 2622 ··· 2910 2634 ) { 2911 2635 const nsid = "so.sprk.labeler.getServices"; // @ts-ignore - dynamically generated 2912 2636 return this._server.xrpc.method(nsid, cfg); 2637 + } 2638 + } 2639 + 2640 + export class SoSprkMediaNS { 2641 + _server: Server; 2642 + 2643 + constructor(server: Server) { 2644 + this._server = server; 2913 2645 } 2914 2646 } 2915 2647
+635 -2285
lex/lexicons.ts
··· 11182 11182 }, 11183 11183 }, 11184 11184 }, 11185 - "SoSprkEmbedDefs": { 11186 - "lexicon": 1, 11187 - "id": "so.sprk.embed.defs", 11188 - "defs": { 11189 - "aspectRatio": { 11190 - "type": "object", 11191 - "description": 11192 - "width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.", 11193 - "required": [ 11194 - "width", 11195 - "height", 11196 - ], 11197 - "properties": { 11198 - "width": { 11199 - "type": "integer", 11200 - "minimum": 1, 11201 - }, 11202 - "height": { 11203 - "type": "integer", 11204 - "minimum": 1, 11205 - }, 11206 - }, 11207 - }, 11208 - }, 11209 - }, 11210 - "SoSprkEmbedImages": { 11211 - "lexicon": 1, 11212 - "id": "so.sprk.embed.images", 11213 - "description": "A set of images embedded in a Spark record (eg, a post).", 11214 - "defs": { 11215 - "main": { 11216 - "type": "object", 11217 - "required": [ 11218 - "images", 11219 - ], 11220 - "properties": { 11221 - "images": { 11222 - "type": "array", 11223 - "items": { 11224 - "type": "ref", 11225 - "ref": "lex:so.sprk.embed.images#image", 11226 - }, 11227 - "maxLength": 12, 11228 - }, 11229 - }, 11230 - }, 11231 - "image": { 11232 - "type": "object", 11233 - "required": [ 11234 - "image", 11235 - "alt", 11236 - ], 11237 - "properties": { 11238 - "image": { 11239 - "type": "blob", 11240 - "accept": [ 11241 - "image/*", 11242 - ], 11243 - "maxSize": 5242880, 11244 - }, 11245 - "alt": { 11246 - "type": "string", 11247 - "description": 11248 - "Alt text description of the image, for accessibility.", 11249 - }, 11250 - "aspectRatio": { 11251 - "type": "ref", 11252 - "ref": "lex:so.sprk.embed.defs#aspectRatio", 11253 - }, 11254 - }, 11255 - }, 11256 - "view": { 11257 - "type": "object", 11258 - "required": [ 11259 - "images", 11260 - ], 11261 - "properties": { 11262 - "images": { 11263 - "type": "array", 11264 - "items": { 11265 - "type": "ref", 11266 - "ref": "lex:so.sprk.embed.images#viewImage", 11267 - }, 11268 - "maxLength": 12, 11269 - }, 11270 - }, 11271 - }, 11272 - "viewImage": { 11273 - "type": "object", 11274 - "required": [ 11275 - "thumb", 11276 - "fullsize", 11277 - "alt", 11278 - ], 11279 - "properties": { 11280 - "thumb": { 11281 - "type": "string", 11282 - "format": "uri", 11283 - "description": 11284 - "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View.", 11285 - }, 11286 - "fullsize": { 11287 - "type": "string", 11288 - "format": "uri", 11289 - "description": 11290 - "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.", 11291 - }, 11292 - "alt": { 11293 - "type": "string", 11294 - "description": 11295 - "Alt text description of the image, for accessibility.", 11296 - }, 11297 - "aspectRatio": { 11298 - "type": "ref", 11299 - "ref": "lex:so.sprk.embed.defs#aspectRatio", 11300 - }, 11301 - }, 11302 - }, 11303 - }, 11304 - }, 11305 - "SoSprkEmbedVideo": { 11306 - "lexicon": 1, 11307 - "id": "so.sprk.embed.video", 11308 - "description": "A video embedded in a Spark record (eg, a post).", 11309 - "defs": { 11310 - "main": { 11311 - "type": "object", 11312 - "required": [ 11313 - "video", 11314 - ], 11315 - "properties": { 11316 - "video": { 11317 - "type": "blob", 11318 - "accept": [ 11319 - "video/mp4", 11320 - ], 11321 - "maxSize": 314572800, 11322 - }, 11323 - "captions": { 11324 - "type": "array", 11325 - "items": { 11326 - "type": "ref", 11327 - "ref": "lex:so.sprk.embed.video#caption", 11328 - }, 11329 - "maxLength": 20, 11330 - }, 11331 - "alt": { 11332 - "type": "string", 11333 - "description": 11334 - "Alt text description of the video, for accessibility.", 11335 - "maxGraphemes": 1000, 11336 - "maxLength": 10000, 11337 - }, 11338 - "aspectRatio": { 11339 - "type": "ref", 11340 - "ref": "lex:so.sprk.embed.defs#aspectRatio", 11341 - }, 11342 - }, 11343 - }, 11344 - "caption": { 11345 - "type": "object", 11346 - "required": [ 11347 - "lang", 11348 - "file", 11349 - ], 11350 - "properties": { 11351 - "lang": { 11352 - "type": "string", 11353 - "format": "language", 11354 - }, 11355 - "file": { 11356 - "type": "blob", 11357 - "accept": [ 11358 - "text/vtt", 11359 - ], 11360 - "maxSize": 20000, 11361 - }, 11362 - }, 11363 - }, 11364 - "view": { 11365 - "type": "object", 11366 - "required": [ 11367 - "cid", 11368 - "playlist", 11369 - ], 11370 - "properties": { 11371 - "cid": { 11372 - "type": "string", 11373 - "format": "cid", 11374 - }, 11375 - "playlist": { 11376 - "type": "string", 11377 - "format": "uri", 11378 - }, 11379 - "thumbnail": { 11380 - "type": "string", 11381 - "format": "uri", 11382 - }, 11383 - "alt": { 11384 - "type": "string", 11385 - "maxGraphemes": 1000, 11386 - "maxLength": 10000, 11387 - }, 11388 - "aspectRatio": { 11389 - "type": "ref", 11390 - "ref": "lex:so.sprk.embed.defs#aspectRatio", 11391 - }, 11392 - }, 11393 - }, 11394 - }, 11395 - }, 11396 11185 "SoSprkNotificationRegisterPush": { 11397 11186 "lexicon": 1, 11398 11187 "id": "so.sprk.notification.registerPush", ··· 11655 11444 }, 11656 11445 }, 11657 11446 }, 11658 - "SoSprkUnspeccedSearchStarterPacksSkeleton": { 11659 - "lexicon": 1, 11660 - "id": "so.sprk.unspecced.searchStarterPacksSkeleton", 11661 - "defs": { 11662 - "main": { 11663 - "type": "query", 11664 - "description": "Backend Starter Pack search, returns only skeleton.", 11665 - "parameters": { 11666 - "type": "params", 11667 - "required": [ 11668 - "q", 11669 - ], 11670 - "properties": { 11671 - "q": { 11672 - "type": "string", 11673 - "description": 11674 - "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.", 11675 - }, 11676 - "viewer": { 11677 - "type": "string", 11678 - "format": "did", 11679 - "description": 11680 - "DID of the account making the request (not included for public/unauthenticated queries).", 11681 - }, 11682 - "limit": { 11683 - "type": "integer", 11684 - "minimum": 1, 11685 - "maximum": 100, 11686 - "default": 25, 11687 - }, 11688 - "cursor": { 11689 - "type": "string", 11690 - "description": 11691 - "Optional pagination mechanism; may not necessarily allow scrolling through entire result set.", 11692 - }, 11693 - }, 11694 - }, 11695 - "output": { 11696 - "encoding": "application/json", 11697 - "schema": { 11698 - "type": "object", 11699 - "required": [ 11700 - "starterPacks", 11701 - ], 11702 - "properties": { 11703 - "cursor": { 11704 - "type": "string", 11705 - }, 11706 - "hitsTotal": { 11707 - "type": "integer", 11708 - "description": 11709 - "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits.", 11710 - }, 11711 - "starterPacks": { 11712 - "type": "array", 11713 - "items": { 11714 - "type": "ref", 11715 - "ref": "lex:so.sprk.unspecced.defs#skeletonSearchStarterPack", 11716 - }, 11717 - }, 11718 - }, 11719 - }, 11720 - }, 11721 - "errors": [ 11722 - { 11723 - "name": "BadQueryString", 11724 - }, 11725 - ], 11726 - }, 11727 - }, 11728 - }, 11729 - "SoSprkUnspeccedDefs": { 11730 - "lexicon": 1, 11731 - "id": "so.sprk.unspecced.defs", 11732 - "defs": { 11733 - "skeletonSearchPost": { 11734 - "type": "object", 11735 - "required": [ 11736 - "uri", 11737 - ], 11738 - "properties": { 11739 - "uri": { 11740 - "type": "string", 11741 - "format": "at-uri", 11742 - }, 11743 - }, 11744 - }, 11745 - "skeletonSearchActor": { 11746 - "type": "object", 11747 - "required": [ 11748 - "did", 11749 - ], 11750 - "properties": { 11751 - "did": { 11752 - "type": "string", 11753 - "format": "did", 11754 - }, 11755 - }, 11756 - }, 11757 - "skeletonSearchStarterPack": { 11758 - "type": "object", 11759 - "required": [ 11760 - "uri", 11761 - ], 11762 - "properties": { 11763 - "uri": { 11764 - "type": "string", 11765 - "format": "at-uri", 11766 - }, 11767 - }, 11768 - }, 11769 - "trendingTopic": { 11770 - "type": "object", 11771 - "required": [ 11772 - "topic", 11773 - "link", 11774 - ], 11775 - "properties": { 11776 - "topic": { 11777 - "type": "string", 11778 - }, 11779 - "displayName": { 11780 - "type": "string", 11781 - }, 11782 - "description": { 11783 - "type": "string", 11784 - }, 11785 - "link": { 11786 - "type": "string", 11787 - }, 11788 - }, 11789 - }, 11790 - }, 11791 - }, 11792 - "SoSprkUnspeccedSearchActorsSkeleton": { 11793 - "lexicon": 1, 11794 - "id": "so.sprk.unspecced.searchActorsSkeleton", 11795 - "defs": { 11796 - "main": { 11797 - "type": "query", 11798 - "description": 11799 - "Backend Actors (profile) search, returns only skeleton.", 11800 - "parameters": { 11801 - "type": "params", 11802 - "required": [ 11803 - "q", 11804 - ], 11805 - "properties": { 11806 - "q": { 11807 - "type": "string", 11808 - "description": 11809 - "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. For typeahead search, only simple term match is supported, not full syntax.", 11810 - }, 11811 - "viewer": { 11812 - "type": "string", 11813 - "format": "did", 11814 - "description": 11815 - "DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking.", 11816 - }, 11817 - "typeahead": { 11818 - "type": "boolean", 11819 - "description": "If true, acts as fast/simple 'typeahead' query.", 11820 - }, 11821 - "limit": { 11822 - "type": "integer", 11823 - "minimum": 1, 11824 - "maximum": 100, 11825 - "default": 25, 11826 - }, 11827 - "cursor": { 11828 - "type": "string", 11829 - "description": 11830 - "Optional pagination mechanism; may not necessarily allow scrolling through entire result set.", 11831 - }, 11832 - }, 11833 - }, 11834 - "output": { 11835 - "encoding": "application/json", 11836 - "schema": { 11837 - "type": "object", 11838 - "required": [ 11839 - "actors", 11840 - ], 11841 - "properties": { 11842 - "cursor": { 11843 - "type": "string", 11844 - }, 11845 - "hitsTotal": { 11846 - "type": "integer", 11847 - "description": 11848 - "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits.", 11849 - }, 11850 - "actors": { 11851 - "type": "array", 11852 - "items": { 11853 - "type": "ref", 11854 - "ref": "lex:so.sprk.unspecced.defs#skeletonSearchActor", 11855 - }, 11856 - }, 11857 - }, 11858 - }, 11859 - }, 11860 - "errors": [ 11861 - { 11862 - "name": "BadQueryString", 11863 - }, 11864 - ], 11865 - }, 11866 - }, 11867 - }, 11868 - "SoSprkUnspeccedGetSuggestionsSkeleton": { 11869 - "lexicon": 1, 11870 - "id": "so.sprk.unspecced.getSuggestionsSkeleton", 11871 - "defs": { 11872 - "main": { 11873 - "type": "query", 11874 - "description": 11875 - "Get a skeleton of suggested actors. Intended to be called and then hydrated through so.sprk.actor.getSuggestions", 11876 - "parameters": { 11877 - "type": "params", 11878 - "properties": { 11879 - "viewer": { 11880 - "type": "string", 11881 - "format": "did", 11882 - "description": 11883 - "DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking.", 11884 - }, 11885 - "limit": { 11886 - "type": "integer", 11887 - "minimum": 1, 11888 - "maximum": 100, 11889 - "default": 50, 11890 - }, 11891 - "cursor": { 11892 - "type": "string", 11893 - }, 11894 - "relativeToDid": { 11895 - "type": "string", 11896 - "format": "did", 11897 - "description": 11898 - "DID of the account to get suggestions relative to. If not provided, suggestions will be based on the viewer.", 11899 - }, 11900 - }, 11901 - }, 11902 - "output": { 11903 - "encoding": "application/json", 11904 - "schema": { 11905 - "type": "object", 11906 - "required": [ 11907 - "actors", 11908 - ], 11909 - "properties": { 11910 - "cursor": { 11911 - "type": "string", 11912 - }, 11913 - "actors": { 11914 - "type": "array", 11915 - "items": { 11916 - "type": "ref", 11917 - "ref": "lex:so.sprk.unspecced.defs#skeletonSearchActor", 11918 - }, 11919 - }, 11920 - "relativeToDid": { 11921 - "type": "string", 11922 - "format": "did", 11923 - "description": 11924 - "DID of the account these suggestions are relative to. If this is returned undefined, suggestions are based on the viewer.", 11925 - }, 11926 - "recId": { 11927 - "type": "integer", 11928 - "description": 11929 - "Snowflake for this recommendation, use when submitting recommendation events.", 11930 - }, 11931 - }, 11932 - }, 11933 - }, 11934 - }, 11935 - }, 11936 - }, 11937 - "SoSprkUnspeccedSearchPostsSkeleton": { 11938 - "lexicon": 1, 11939 - "id": "so.sprk.unspecced.searchPostsSkeleton", 11940 - "defs": { 11941 - "main": { 11942 - "type": "query", 11943 - "description": "Backend Posts search, returns only skeleton", 11944 - "parameters": { 11945 - "type": "params", 11946 - "required": [ 11947 - "q", 11948 - ], 11949 - "properties": { 11950 - "q": { 11951 - "type": "string", 11952 - "description": 11953 - "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.", 11954 - }, 11955 - "sort": { 11956 - "type": "string", 11957 - "knownValues": [ 11958 - "top", 11959 - "latest", 11960 - ], 11961 - "default": "latest", 11962 - "description": "Specifies the ranking order of results.", 11963 - }, 11964 - "since": { 11965 - "type": "string", 11966 - "description": 11967 - "Filter results for posts after the indicated datetime (inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYYY-MM-DD).", 11968 - }, 11969 - "until": { 11970 - "type": "string", 11971 - "description": 11972 - "Filter results for posts before the indicated datetime (not inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYY-MM-DD).", 11973 - }, 11974 - "mentions": { 11975 - "type": "string", 11976 - "format": "at-identifier", 11977 - "description": 11978 - "Filter to posts which mention the given account. Handles are resolved to DID before query-time. Only matches rich-text facet mentions.", 11979 - }, 11980 - "author": { 11981 - "type": "string", 11982 - "format": "at-identifier", 11983 - "description": 11984 - "Filter to posts by the given account. Handles are resolved to DID before query-time.", 11985 - }, 11986 - "lang": { 11987 - "type": "string", 11988 - "format": "language", 11989 - "description": 11990 - "Filter to posts in the given language. Expected to be based on post language field, though server may override language detection.", 11991 - }, 11992 - "domain": { 11993 - "type": "string", 11994 - "description": 11995 - "Filter to posts with URLs (facet links or embeds) linking to the given domain (hostname). Server may apply hostname normalization.", 11996 - }, 11997 - "url": { 11998 - "type": "string", 11999 - "format": "uri", 12000 - "description": 12001 - "Filter to posts with links (facet links or embeds) pointing to this URL. Server may apply URL normalization or fuzzy matching.", 12002 - }, 12003 - "tag": { 12004 - "type": "array", 12005 - "items": { 12006 - "type": "string", 12007 - "maxLength": 640, 12008 - "maxGraphemes": 64, 12009 - }, 12010 - "description": 12011 - "Filter to posts with the given tag (hashtag), based on rich-text facet or tag field. Do not include the hash (#) prefix. Multiple tags can be specified, with 'AND' matching.", 12012 - }, 12013 - "viewer": { 12014 - "type": "string", 12015 - "format": "did", 12016 - "description": 12017 - "DID of the account making the request (not included for public/unauthenticated queries). Used for 'from:me' queries.", 12018 - }, 12019 - "limit": { 12020 - "type": "integer", 12021 - "minimum": 1, 12022 - "maximum": 100, 12023 - "default": 25, 12024 - }, 12025 - "cursor": { 12026 - "type": "string", 12027 - "description": 12028 - "Optional pagination mechanism; may not necessarily allow scrolling through entire result set.", 12029 - }, 12030 - }, 12031 - }, 12032 - "output": { 12033 - "encoding": "application/json", 12034 - "schema": { 12035 - "type": "object", 12036 - "required": [ 12037 - "posts", 12038 - ], 12039 - "properties": { 12040 - "cursor": { 12041 - "type": "string", 12042 - }, 12043 - "hitsTotal": { 12044 - "type": "integer", 12045 - "description": 12046 - "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits.", 12047 - }, 12048 - "posts": { 12049 - "type": "array", 12050 - "items": { 12051 - "type": "ref", 12052 - "ref": "lex:so.sprk.unspecced.defs#skeletonSearchPost", 12053 - }, 12054 - }, 12055 - }, 12056 - }, 12057 - }, 12058 - "errors": [ 12059 - { 12060 - "name": "BadQueryString", 12061 - }, 12062 - ], 12063 - }, 12064 - }, 12065 - }, 12066 - "SoSprkUnspeccedGetPopularFeedGenerators": { 12067 - "lexicon": 1, 12068 - "id": "so.sprk.unspecced.getPopularFeedGenerators", 12069 - "defs": { 12070 - "main": { 12071 - "type": "query", 12072 - "description": "An unspecced view of globally popular feed generators.", 12073 - "parameters": { 12074 - "type": "params", 12075 - "properties": { 12076 - "limit": { 12077 - "type": "integer", 12078 - "minimum": 1, 12079 - "maximum": 100, 12080 - "default": 50, 12081 - }, 12082 - "cursor": { 12083 - "type": "string", 12084 - }, 12085 - "query": { 12086 - "type": "string", 12087 - }, 12088 - }, 12089 - }, 12090 - "output": { 12091 - "encoding": "application/json", 12092 - "schema": { 12093 - "type": "object", 12094 - "required": [ 12095 - "feeds", 12096 - ], 12097 - "properties": { 12098 - "cursor": { 12099 - "type": "string", 12100 - }, 12101 - "feeds": { 12102 - "type": "array", 12103 - "items": { 12104 - "type": "ref", 12105 - "ref": "lex:so.sprk.feed.defs#generatorView", 12106 - }, 12107 - }, 12108 - }, 12109 - }, 12110 - }, 12111 - }, 12112 - }, 12113 - }, 12114 - "SoSprkUnspeccedGetTrendingTopics": { 12115 - "lexicon": 1, 12116 - "id": "so.sprk.unspecced.getTrendingTopics", 12117 - "defs": { 12118 - "main": { 12119 - "type": "query", 12120 - "description": "Get a list of trending topics", 12121 - "parameters": { 12122 - "type": "params", 12123 - "properties": { 12124 - "viewer": { 12125 - "type": "string", 12126 - "format": "did", 12127 - "description": 12128 - "DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking.", 12129 - }, 12130 - "limit": { 12131 - "type": "integer", 12132 - "minimum": 1, 12133 - "maximum": 25, 12134 - "default": 10, 12135 - }, 12136 - }, 12137 - }, 12138 - "output": { 12139 - "encoding": "application/json", 12140 - "schema": { 12141 - "type": "object", 12142 - "required": [ 12143 - "topics", 12144 - "suggested", 12145 - ], 12146 - "properties": { 12147 - "topics": { 12148 - "type": "array", 12149 - "items": { 12150 - "type": "ref", 12151 - "ref": "lex:so.sprk.unspecced.defs#trendingTopic", 12152 - }, 12153 - }, 12154 - "suggested": { 12155 - "type": "array", 12156 - "items": { 12157 - "type": "ref", 12158 - "ref": "lex:so.sprk.unspecced.defs#trendingTopic", 12159 - }, 12160 - }, 12161 - }, 12162 - }, 12163 - }, 12164 - }, 12165 - }, 12166 - }, 12167 - "SoSprkUnspeccedGetTaggedSuggestions": { 12168 - "lexicon": 1, 12169 - "id": "so.sprk.unspecced.getTaggedSuggestions", 12170 - "defs": { 12171 - "main": { 12172 - "type": "query", 12173 - "description": 12174 - "Get a list of suggestions (feeds and users) tagged with categories", 12175 - "parameters": { 12176 - "type": "params", 12177 - "properties": {}, 12178 - }, 12179 - "output": { 12180 - "encoding": "application/json", 12181 - "schema": { 12182 - "type": "object", 12183 - "required": [ 12184 - "suggestions", 12185 - ], 12186 - "properties": { 12187 - "suggestions": { 12188 - "type": "array", 12189 - "items": { 12190 - "type": "ref", 12191 - "ref": 12192 - "lex:so.sprk.unspecced.getTaggedSuggestions#suggestion", 12193 - }, 12194 - }, 12195 - }, 12196 - }, 12197 - }, 12198 - }, 12199 - "suggestion": { 12200 - "type": "object", 12201 - "required": [ 12202 - "tag", 12203 - "subjectType", 12204 - "subject", 12205 - ], 12206 - "properties": { 12207 - "tag": { 12208 - "type": "string", 12209 - }, 12210 - "subjectType": { 12211 - "type": "string", 12212 - "knownValues": [ 12213 - "actor", 12214 - "feed", 12215 - ], 12216 - }, 12217 - "subject": { 12218 - "type": "string", 12219 - "format": "uri", 12220 - }, 12221 - }, 12222 - }, 12223 - }, 12224 - }, 12225 - "SoSprkUnspeccedGetConfig": { 12226 - "lexicon": 1, 12227 - "id": "so.sprk.unspecced.getConfig", 12228 - "defs": { 12229 - "main": { 12230 - "type": "query", 12231 - "description": "Get miscellaneous runtime configuration.", 12232 - "output": { 12233 - "encoding": "application/json", 12234 - "schema": { 12235 - "type": "object", 12236 - "required": [], 12237 - "properties": { 12238 - "checkEmailConfirmed": { 12239 - "type": "boolean", 12240 - }, 12241 - }, 12242 - }, 12243 - }, 12244 - }, 12245 - }, 12246 - }, 12247 - "SoSprkGraphGetStarterPacks": { 12248 - "lexicon": 1, 12249 - "id": "so.sprk.graph.getStarterPacks", 12250 - "defs": { 12251 - "main": { 12252 - "type": "query", 12253 - "description": "Get views for a list of starter packs.", 12254 - "parameters": { 12255 - "type": "params", 12256 - "required": [ 12257 - "uris", 12258 - ], 12259 - "properties": { 12260 - "uris": { 12261 - "type": "array", 12262 - "items": { 12263 - "type": "string", 12264 - "format": "at-uri", 12265 - }, 12266 - "maxLength": 25, 12267 - }, 12268 - }, 12269 - }, 12270 - "output": { 12271 - "encoding": "application/json", 12272 - "schema": { 12273 - "type": "object", 12274 - "required": [ 12275 - "starterPacks", 12276 - ], 12277 - "properties": { 12278 - "starterPacks": { 12279 - "type": "array", 12280 - "items": { 12281 - "type": "ref", 12282 - "ref": "lex:so.sprk.graph.defs#starterPackViewBasic", 12283 - }, 12284 - }, 12285 - }, 12286 - }, 12287 - }, 12288 - }, 12289 - }, 12290 - }, 12291 11447 "SoSprkGraphGetSuggestedFollowsByActor": { 12292 11448 "lexicon": 1, 12293 11449 "id": "so.sprk.graph.getSuggestedFollowsByActor", ··· 12403 11559 "lexicon": 1, 12404 11560 "id": "so.sprk.graph.defs", 12405 11561 "defs": { 12406 - "listViewBasic": { 12407 - "type": "object", 12408 - "required": [ 12409 - "uri", 12410 - "cid", 12411 - "name", 12412 - "purpose", 12413 - ], 12414 - "properties": { 12415 - "uri": { 12416 - "type": "string", 12417 - "format": "at-uri", 12418 - }, 12419 - "cid": { 12420 - "type": "string", 12421 - "format": "cid", 12422 - }, 12423 - "name": { 12424 - "type": "string", 12425 - "maxLength": 64, 12426 - "minLength": 1, 12427 - }, 12428 - "purpose": { 12429 - "type": "ref", 12430 - "ref": "lex:so.sprk.graph.defs#listPurpose", 12431 - }, 12432 - "avatar": { 12433 - "type": "string", 12434 - "format": "uri", 12435 - }, 12436 - "listItemCount": { 12437 - "type": "integer", 12438 - "minimum": 0, 12439 - }, 12440 - "labels": { 12441 - "type": "array", 12442 - "items": { 12443 - "type": "ref", 12444 - "ref": "lex:com.atproto.label.defs#label", 12445 - }, 12446 - }, 12447 - "viewer": { 12448 - "type": "ref", 12449 - "ref": "lex:so.sprk.graph.defs#listViewerState", 12450 - }, 12451 - "indexedAt": { 12452 - "type": "string", 12453 - "format": "datetime", 12454 - }, 12455 - }, 12456 - }, 12457 - "listView": { 12458 - "type": "object", 12459 - "required": [ 12460 - "uri", 12461 - "cid", 12462 - "creator", 12463 - "name", 12464 - "purpose", 12465 - "indexedAt", 12466 - ], 12467 - "properties": { 12468 - "uri": { 12469 - "type": "string", 12470 - "format": "at-uri", 12471 - }, 12472 - "cid": { 12473 - "type": "string", 12474 - "format": "cid", 12475 - }, 12476 - "creator": { 12477 - "type": "ref", 12478 - "ref": "lex:so.sprk.actor.defs#profileView", 12479 - }, 12480 - "name": { 12481 - "type": "string", 12482 - "maxLength": 64, 12483 - "minLength": 1, 12484 - }, 12485 - "purpose": { 12486 - "type": "ref", 12487 - "ref": "lex:so.sprk.graph.defs#listPurpose", 12488 - }, 12489 - "description": { 12490 - "type": "string", 12491 - "maxGraphemes": 300, 12492 - "maxLength": 3000, 12493 - }, 12494 - "descriptionFacets": { 12495 - "type": "array", 12496 - "items": { 12497 - "type": "ref", 12498 - "ref": "lex:so.sprk.richtext.facet", 12499 - }, 12500 - }, 12501 - "avatar": { 12502 - "type": "string", 12503 - "format": "uri", 12504 - }, 12505 - "listItemCount": { 12506 - "type": "integer", 12507 - "minimum": 0, 12508 - }, 12509 - "labels": { 12510 - "type": "array", 12511 - "items": { 12512 - "type": "ref", 12513 - "ref": "lex:com.atproto.label.defs#label", 12514 - }, 12515 - }, 12516 - "viewer": { 12517 - "type": "ref", 12518 - "ref": "lex:so.sprk.graph.defs#listViewerState", 12519 - }, 12520 - "indexedAt": { 12521 - "type": "string", 12522 - "format": "datetime", 12523 - }, 12524 - }, 12525 - }, 12526 - "listItemView": { 12527 - "type": "object", 12528 - "required": [ 12529 - "uri", 12530 - "subject", 12531 - ], 12532 - "properties": { 12533 - "uri": { 12534 - "type": "string", 12535 - "format": "at-uri", 12536 - }, 12537 - "subject": { 12538 - "type": "ref", 12539 - "ref": "lex:so.sprk.actor.defs#profileView", 12540 - }, 12541 - }, 12542 - }, 12543 - "starterPackView": { 12544 - "type": "object", 12545 - "required": [ 12546 - "uri", 12547 - "cid", 12548 - "record", 12549 - "creator", 12550 - "indexedAt", 12551 - ], 12552 - "properties": { 12553 - "uri": { 12554 - "type": "string", 12555 - "format": "at-uri", 12556 - }, 12557 - "cid": { 12558 - "type": "string", 12559 - "format": "cid", 12560 - }, 12561 - "record": { 12562 - "type": "unknown", 12563 - }, 12564 - "creator": { 12565 - "type": "ref", 12566 - "ref": "lex:so.sprk.actor.defs#profileViewBasic", 12567 - }, 12568 - "list": { 12569 - "type": "ref", 12570 - "ref": "lex:so.sprk.graph.defs#listViewBasic", 12571 - }, 12572 - "listItemsSample": { 12573 - "type": "array", 12574 - "maxLength": 12, 12575 - "items": { 12576 - "type": "ref", 12577 - "ref": "lex:so.sprk.graph.defs#listItemView", 12578 - }, 12579 - }, 12580 - "feeds": { 12581 - "type": "array", 12582 - "maxLength": 3, 12583 - "items": { 12584 - "type": "ref", 12585 - "ref": "lex:so.sprk.feed.defs#generatorView", 12586 - }, 12587 - }, 12588 - "joinedWeekCount": { 12589 - "type": "integer", 12590 - "minimum": 0, 12591 - }, 12592 - "joinedAllTimeCount": { 12593 - "type": "integer", 12594 - "minimum": 0, 12595 - }, 12596 - "labels": { 12597 - "type": "array", 12598 - "items": { 12599 - "type": "ref", 12600 - "ref": "lex:com.atproto.label.defs#label", 12601 - }, 12602 - }, 12603 - "indexedAt": { 12604 - "type": "string", 12605 - "format": "datetime", 12606 - }, 12607 - }, 12608 - }, 12609 - "starterPackViewBasic": { 12610 - "type": "object", 12611 - "required": [ 12612 - "uri", 12613 - "cid", 12614 - "record", 12615 - "creator", 12616 - "indexedAt", 12617 - ], 12618 - "properties": { 12619 - "uri": { 12620 - "type": "string", 12621 - "format": "at-uri", 12622 - }, 12623 - "cid": { 12624 - "type": "string", 12625 - "format": "cid", 12626 - }, 12627 - "record": { 12628 - "type": "unknown", 12629 - }, 12630 - "creator": { 12631 - "type": "ref", 12632 - "ref": "lex:so.sprk.actor.defs#profileViewBasic", 12633 - }, 12634 - "listItemCount": { 12635 - "type": "integer", 12636 - "minimum": 0, 12637 - }, 12638 - "joinedWeekCount": { 12639 - "type": "integer", 12640 - "minimum": 0, 12641 - }, 12642 - "joinedAllTimeCount": { 12643 - "type": "integer", 12644 - "minimum": 0, 12645 - }, 12646 - "labels": { 12647 - "type": "array", 12648 - "items": { 12649 - "type": "ref", 12650 - "ref": "lex:com.atproto.label.defs#label", 12651 - }, 12652 - }, 12653 - "indexedAt": { 12654 - "type": "string", 12655 - "format": "datetime", 12656 - }, 12657 - }, 12658 - }, 12659 - "listPurpose": { 12660 - "type": "string", 12661 - "knownValues": [ 12662 - "so.sprk.graph.defs#modlist", 12663 - "so.sprk.graph.defs#curatelist", 12664 - "so.sprk.graph.defs#referencelist", 12665 - ], 12666 - }, 12667 - "modlist": { 12668 - "type": "token", 12669 - "description": 12670 - "A list of actors to apply an aggregate moderation action (mute/block) on.", 12671 - }, 12672 - "curatelist": { 12673 - "type": "token", 12674 - "description": 12675 - "A list of actors used for curation purposes such as list feeds or interaction gating.", 12676 - }, 12677 - "referencelist": { 12678 - "type": "token", 12679 - "description": 12680 - "A list of actors used for only for reference purposes such as within a starter pack.", 12681 - }, 12682 - "listViewerState": { 12683 - "type": "object", 12684 - "properties": { 12685 - "muted": { 12686 - "type": "boolean", 12687 - }, 12688 - "blocked": { 12689 - "type": "string", 12690 - "format": "at-uri", 12691 - }, 12692 - }, 12693 - }, 12694 11562 "notFoundActor": { 12695 11563 "type": "object", 12696 11564 "description": "indicates that a handle or DID could not be resolved", ··· 12737 11605 }, 12738 11606 }, 12739 11607 }, 12740 - "SoSprkGraphUnmuteActorList": { 12741 - "lexicon": 1, 12742 - "id": "so.sprk.graph.unmuteActorList", 12743 - "defs": { 12744 - "main": { 12745 - "type": "procedure", 12746 - "description": "Unmutes the specified list of accounts. Requires auth.", 12747 - "input": { 12748 - "encoding": "application/json", 12749 - "schema": { 12750 - "type": "object", 12751 - "required": [ 12752 - "list", 12753 - ], 12754 - "properties": { 12755 - "list": { 12756 - "type": "string", 12757 - "format": "at-uri", 12758 - }, 12759 - }, 12760 - }, 12761 - }, 12762 - }, 12763 - }, 12764 - }, 12765 - "SoSprkGraphGetListBlocks": { 12766 - "lexicon": 1, 12767 - "id": "so.sprk.graph.getListBlocks", 12768 - "defs": { 12769 - "main": { 12770 - "type": "query", 12771 - "description": 12772 - "Get mod lists that the requesting account (actor) is blocking. Requires auth.", 12773 - "parameters": { 12774 - "type": "params", 12775 - "properties": { 12776 - "limit": { 12777 - "type": "integer", 12778 - "minimum": 1, 12779 - "maximum": 100, 12780 - "default": 50, 12781 - }, 12782 - "cursor": { 12783 - "type": "string", 12784 - }, 12785 - }, 12786 - }, 12787 - "output": { 12788 - "encoding": "application/json", 12789 - "schema": { 12790 - "type": "object", 12791 - "required": [ 12792 - "lists", 12793 - ], 12794 - "properties": { 12795 - "cursor": { 12796 - "type": "string", 12797 - }, 12798 - "lists": { 12799 - "type": "array", 12800 - "items": { 12801 - "type": "ref", 12802 - "ref": "lex:so.sprk.graph.defs#listView", 12803 - }, 12804 - }, 12805 - }, 12806 - }, 12807 - }, 12808 - }, 12809 - }, 12810 - }, 12811 - "SoSprkGraphListblock": { 12812 - "lexicon": 1, 12813 - "id": "so.sprk.graph.listblock", 12814 - "defs": { 12815 - "main": { 12816 - "type": "record", 12817 - "description": 12818 - "Record representing a block relationship against an entire an entire list of accounts (actors).", 12819 - "key": "tid", 12820 - "record": { 12821 - "type": "object", 12822 - "required": [ 12823 - "subject", 12824 - "createdAt", 12825 - ], 12826 - "properties": { 12827 - "subject": { 12828 - "type": "string", 12829 - "format": "at-uri", 12830 - "description": "Reference (AT-URI) to the mod list record.", 12831 - }, 12832 - "createdAt": { 12833 - "type": "string", 12834 - "format": "datetime", 12835 - }, 12836 - }, 12837 - }, 12838 - }, 12839 - }, 12840 - }, 12841 - "SoSprkGraphGetStarterPack": { 12842 - "lexicon": 1, 12843 - "id": "so.sprk.graph.getStarterPack", 12844 - "defs": { 12845 - "main": { 12846 - "type": "query", 12847 - "description": "Gets a view of a starter pack.", 12848 - "parameters": { 12849 - "type": "params", 12850 - "required": [ 12851 - "starterPack", 12852 - ], 12853 - "properties": { 12854 - "starterPack": { 12855 - "type": "string", 12856 - "format": "at-uri", 12857 - "description": "Reference (AT-URI) of the starter pack record.", 12858 - }, 12859 - }, 12860 - }, 12861 - "output": { 12862 - "encoding": "application/json", 12863 - "schema": { 12864 - "type": "object", 12865 - "required": [ 12866 - "starterPack", 12867 - ], 12868 - "properties": { 12869 - "starterPack": { 12870 - "type": "ref", 12871 - "ref": "lex:so.sprk.graph.defs#starterPackView", 12872 - }, 12873 - }, 12874 - }, 12875 - }, 12876 - }, 12877 - }, 12878 - }, 12879 - "SoSprkGraphStarterpack": { 12880 - "lexicon": 1, 12881 - "id": "so.sprk.graph.starterpack", 12882 - "defs": { 12883 - "main": { 12884 - "type": "record", 12885 - "description": 12886 - "Record defining a starter pack of actors and feeds for new users.", 12887 - "key": "tid", 12888 - "record": { 12889 - "type": "object", 12890 - "required": [ 12891 - "name", 12892 - "list", 12893 - "createdAt", 12894 - ], 12895 - "properties": { 12896 - "name": { 12897 - "type": "string", 12898 - "maxGraphemes": 50, 12899 - "maxLength": 500, 12900 - "minLength": 1, 12901 - "description": "Display name for starter pack; can not be empty.", 12902 - }, 12903 - "description": { 12904 - "type": "string", 12905 - "maxGraphemes": 300, 12906 - "maxLength": 3000, 12907 - }, 12908 - "descriptionFacets": { 12909 - "type": "array", 12910 - "items": { 12911 - "type": "ref", 12912 - "ref": "lex:so.sprk.richtext.facet", 12913 - }, 12914 - }, 12915 - "list": { 12916 - "type": "string", 12917 - "format": "at-uri", 12918 - "description": "Reference (AT-URI) to the list record.", 12919 - }, 12920 - "feeds": { 12921 - "type": "array", 12922 - "maxLength": 3, 12923 - "items": { 12924 - "type": "ref", 12925 - "ref": "lex:so.sprk.graph.starterpack#feedItem", 12926 - }, 12927 - }, 12928 - "createdAt": { 12929 - "type": "string", 12930 - "format": "datetime", 12931 - }, 12932 - }, 12933 - }, 12934 - }, 12935 - "feedItem": { 12936 - "type": "object", 12937 - "required": [ 12938 - "uri", 12939 - ], 12940 - "properties": { 12941 - "uri": { 12942 - "type": "string", 12943 - "format": "at-uri", 12944 - }, 12945 - }, 12946 - }, 12947 - }, 12948 - }, 12949 - "SoSprkGraphMuteActorList": { 12950 - "lexicon": 1, 12951 - "id": "so.sprk.graph.muteActorList", 12952 - "defs": { 12953 - "main": { 12954 - "type": "procedure", 12955 - "description": 12956 - "Creates a mute relationship for the specified list of accounts. Mutes are private in Spark. Requires auth.", 12957 - "input": { 12958 - "encoding": "application/json", 12959 - "schema": { 12960 - "type": "object", 12961 - "required": [ 12962 - "list", 12963 - ], 12964 - "properties": { 12965 - "list": { 12966 - "type": "string", 12967 - "format": "at-uri", 12968 - }, 12969 - }, 12970 - }, 12971 - }, 12972 - }, 12973 - }, 12974 - }, 12975 11608 "SoSprkGraphMuteThread": { 12976 11609 "lexicon": 1, 12977 11610 "id": "so.sprk.graph.muteThread", ··· 12998 11631 }, 12999 11632 }, 13000 11633 }, 13001 - "SoSprkGraphSearchStarterPacks": { 13002 - "lexicon": 1, 13003 - "id": "so.sprk.graph.searchStarterPacks", 13004 - "defs": { 13005 - "main": { 13006 - "type": "query", 13007 - "description": 13008 - "Find starter packs matching search criteria. Does not require auth.", 13009 - "parameters": { 13010 - "type": "params", 13011 - "required": [ 13012 - "q", 13013 - ], 13014 - "properties": { 13015 - "q": { 13016 - "type": "string", 13017 - "description": 13018 - "Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.", 13019 - }, 13020 - "limit": { 13021 - "type": "integer", 13022 - "minimum": 1, 13023 - "maximum": 100, 13024 - "default": 25, 13025 - }, 13026 - "cursor": { 13027 - "type": "string", 13028 - }, 13029 - }, 13030 - }, 13031 - "output": { 13032 - "encoding": "application/json", 13033 - "schema": { 13034 - "type": "object", 13035 - "required": [ 13036 - "starterPacks", 13037 - ], 13038 - "properties": { 13039 - "cursor": { 13040 - "type": "string", 13041 - }, 13042 - "starterPacks": { 13043 - "type": "array", 13044 - "items": { 13045 - "type": "ref", 13046 - "ref": "lex:so.sprk.graph.defs#starterPackViewBasic", 13047 - }, 13048 - }, 13049 - }, 13050 - }, 13051 - }, 13052 - }, 13053 - }, 13054 - }, 13055 - "SoSprkGraphGetActorStarterPacks": { 13056 - "lexicon": 1, 13057 - "id": "so.sprk.graph.getActorStarterPacks", 13058 - "defs": { 13059 - "main": { 13060 - "type": "query", 13061 - "description": "Get a list of starter packs created by the actor.", 13062 - "parameters": { 13063 - "type": "params", 13064 - "required": [ 13065 - "actor", 13066 - ], 13067 - "properties": { 13068 - "actor": { 13069 - "type": "string", 13070 - "format": "at-identifier", 13071 - }, 13072 - "limit": { 13073 - "type": "integer", 13074 - "minimum": 1, 13075 - "maximum": 100, 13076 - "default": 50, 13077 - }, 13078 - "cursor": { 13079 - "type": "string", 13080 - }, 13081 - }, 13082 - }, 13083 - "output": { 13084 - "encoding": "application/json", 13085 - "schema": { 13086 - "type": "object", 13087 - "required": [ 13088 - "starterPacks", 13089 - ], 13090 - "properties": { 13091 - "cursor": { 13092 - "type": "string", 13093 - }, 13094 - "starterPacks": { 13095 - "type": "array", 13096 - "items": { 13097 - "type": "ref", 13098 - "ref": "lex:so.sprk.graph.defs#starterPackViewBasic", 13099 - }, 13100 - }, 13101 - }, 13102 - }, 13103 - }, 13104 - }, 13105 - }, 13106 - }, 13107 - "SoSprkGraphGetLists": { 13108 - "lexicon": 1, 13109 - "id": "so.sprk.graph.getLists", 13110 - "defs": { 13111 - "main": { 13112 - "type": "query", 13113 - "description": 13114 - "Enumerates the lists created by a specified account (actor).", 13115 - "parameters": { 13116 - "type": "params", 13117 - "required": [ 13118 - "actor", 13119 - ], 13120 - "properties": { 13121 - "actor": { 13122 - "type": "string", 13123 - "format": "at-identifier", 13124 - "description": "The account (actor) to enumerate lists from.", 13125 - }, 13126 - "limit": { 13127 - "type": "integer", 13128 - "minimum": 1, 13129 - "maximum": 100, 13130 - "default": 50, 13131 - }, 13132 - "cursor": { 13133 - "type": "string", 13134 - }, 13135 - }, 13136 - }, 13137 - "output": { 13138 - "encoding": "application/json", 13139 - "schema": { 13140 - "type": "object", 13141 - "required": [ 13142 - "lists", 13143 - ], 13144 - "properties": { 13145 - "cursor": { 13146 - "type": "string", 13147 - }, 13148 - "lists": { 13149 - "type": "array", 13150 - "items": { 13151 - "type": "ref", 13152 - "ref": "lex:so.sprk.graph.defs#listView", 13153 - }, 13154 - }, 13155 - }, 13156 - }, 13157 - }, 13158 - }, 13159 - }, 13160 - }, 13161 11634 "SoSprkGraphGetFollowers": { 13162 11635 "lexicon": 1, 13163 11636 "id": "so.sprk.graph.getFollowers", ··· 13313 11786 }, 13314 11787 }, 13315 11788 }, 13316 - "SoSprkGraphListitem": { 13317 - "lexicon": 1, 13318 - "id": "so.sprk.graph.listitem", 13319 - "defs": { 13320 - "main": { 13321 - "type": "record", 13322 - "description": 13323 - "Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records.", 13324 - "key": "tid", 13325 - "record": { 13326 - "type": "object", 13327 - "required": [ 13328 - "subject", 13329 - "list", 13330 - "createdAt", 13331 - ], 13332 - "properties": { 13333 - "subject": { 13334 - "type": "string", 13335 - "format": "did", 13336 - "description": "The account which is included on the list.", 13337 - }, 13338 - "list": { 13339 - "type": "string", 13340 - "format": "at-uri", 13341 - "description": 13342 - "Reference (AT-URI) to the list record (so.sprk.graph.list).", 13343 - }, 13344 - "createdAt": { 13345 - "type": "string", 13346 - "format": "datetime", 13347 - }, 13348 - }, 13349 - }, 13350 - }, 13351 - }, 13352 - }, 13353 - "SoSprkGraphList": { 13354 - "lexicon": 1, 13355 - "id": "so.sprk.graph.list", 13356 - "defs": { 13357 - "main": { 13358 - "type": "record", 13359 - "description": 13360 - "Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists.", 13361 - "key": "tid", 13362 - "record": { 13363 - "type": "object", 13364 - "required": [ 13365 - "name", 13366 - "purpose", 13367 - "createdAt", 13368 - ], 13369 - "properties": { 13370 - "purpose": { 13371 - "type": "ref", 13372 - "description": 13373 - "Defines the purpose of the list (aka, moderation-oriented or curration-oriented)", 13374 - "ref": "lex:so.sprk.graph.defs#listPurpose", 13375 - }, 13376 - "name": { 13377 - "type": "string", 13378 - "maxLength": 64, 13379 - "minLength": 1, 13380 - "description": "Display name for list; can not be empty.", 13381 - }, 13382 - "description": { 13383 - "type": "string", 13384 - "maxGraphemes": 300, 13385 - "maxLength": 3000, 13386 - }, 13387 - "descriptionFacets": { 13388 - "type": "array", 13389 - "items": { 13390 - "type": "ref", 13391 - "ref": "lex:so.sprk.richtext.facet", 13392 - }, 13393 - }, 13394 - "avatar": { 13395 - "type": "blob", 13396 - "accept": [ 13397 - "image/png", 13398 - "image/jpeg", 13399 - ], 13400 - "maxSize": 1048576, 13401 - }, 13402 - "labels": { 13403 - "type": "union", 13404 - "refs": [ 13405 - "lex:com.atproto.label.defs#selfLabels", 13406 - ], 13407 - }, 13408 - "createdAt": { 13409 - "type": "string", 13410 - "format": "datetime", 13411 - }, 13412 - }, 13413 - }, 13414 - }, 13415 - }, 13416 - }, 13417 11789 "SoSprkGraphGetKnownFollowers": { 13418 11790 "lexicon": 1, 13419 11791 "id": "so.sprk.graph.getKnownFollowers", ··· 13472 11844 }, 13473 11845 }, 13474 11846 }, 13475 - "SoSprkGraphGetListMutes": { 13476 - "lexicon": 1, 13477 - "id": "so.sprk.graph.getListMutes", 13478 - "defs": { 13479 - "main": { 13480 - "type": "query", 13481 - "description": 13482 - "Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth.", 13483 - "parameters": { 13484 - "type": "params", 13485 - "properties": { 13486 - "limit": { 13487 - "type": "integer", 13488 - "minimum": 1, 13489 - "maximum": 100, 13490 - "default": 50, 13491 - }, 13492 - "cursor": { 13493 - "type": "string", 13494 - }, 13495 - }, 13496 - }, 13497 - "output": { 13498 - "encoding": "application/json", 13499 - "schema": { 13500 - "type": "object", 13501 - "required": [ 13502 - "lists", 13503 - ], 13504 - "properties": { 13505 - "cursor": { 13506 - "type": "string", 13507 - }, 13508 - "lists": { 13509 - "type": "array", 13510 - "items": { 13511 - "type": "ref", 13512 - "ref": "lex:so.sprk.graph.defs#listView", 13513 - }, 13514 - }, 13515 - }, 13516 - }, 13517 - }, 13518 - }, 13519 - }, 13520 - }, 13521 11847 "SoSprkGraphGetFollows": { 13522 11848 "lexicon": 1, 13523 11849 "id": "so.sprk.graph.getFollows", ··· 13706 12032 "actor": { 13707 12033 "type": "string", 13708 12034 "format": "at-identifier", 13709 - }, 13710 - }, 13711 - }, 13712 - }, 13713 - }, 13714 - }, 13715 - }, 13716 - "SoSprkGraphGetList": { 13717 - "lexicon": 1, 13718 - "id": "so.sprk.graph.getList", 13719 - "defs": { 13720 - "main": { 13721 - "type": "query", 13722 - "description": 13723 - "Gets a 'view' (with additional context) of a specified list.", 13724 - "parameters": { 13725 - "type": "params", 13726 - "required": [ 13727 - "list", 13728 - ], 13729 - "properties": { 13730 - "list": { 13731 - "type": "string", 13732 - "format": "at-uri", 13733 - "description": 13734 - "Reference (AT-URI) of the list record to hydrate.", 13735 - }, 13736 - "limit": { 13737 - "type": "integer", 13738 - "minimum": 1, 13739 - "maximum": 100, 13740 - "default": 50, 13741 - }, 13742 - "cursor": { 13743 - "type": "string", 13744 - }, 13745 - }, 13746 - }, 13747 - "output": { 13748 - "encoding": "application/json", 13749 - "schema": { 13750 - "type": "object", 13751 - "required": [ 13752 - "list", 13753 - "items", 13754 - ], 13755 - "properties": { 13756 - "cursor": { 13757 - "type": "string", 13758 - }, 13759 - "list": { 13760 - "type": "ref", 13761 - "ref": "lex:so.sprk.graph.defs#listView", 13762 - }, 13763 - "items": { 13764 - "type": "array", 13765 - "items": { 13766 - "type": "ref", 13767 - "ref": "lex:so.sprk.graph.defs#listItemView", 13768 - }, 13769 12035 }, 13770 12036 }, 13771 12037 }, ··· 13840 12106 }, 13841 12107 }, 13842 12108 }, 13843 - "SoSprkFeedMusic": { 13844 - "lexicon": 1, 13845 - "id": "so.sprk.feed.music", 13846 - "description": "A music record usable in a Spark record (e.g, a post)", 13847 - "defs": { 13848 - "main": { 13849 - "type": "record", 13850 - "key": "tid", 13851 - "record": { 13852 - "type": "object", 13853 - "required": [ 13854 - "sound", 13855 - "title", 13856 - "author", 13857 - "releaseDate", 13858 - "createdAt", 13859 - ], 13860 - "properties": { 13861 - "sound": { 13862 - "type": "blob", 13863 - "accept": [ 13864 - "audio/mp3", 13865 - ], 13866 - "maxSize": 10485760, 13867 - }, 13868 - "title": { 13869 - "type": "string", 13870 - "maxLength": 1000, 13871 - "maxGraphemes": 100, 13872 - "description": "The music's title.", 13873 - }, 13874 - "releaseDate": { 13875 - "type": "string", 13876 - "format": "datetime", 13877 - }, 13878 - "album": { 13879 - "type": "string", 13880 - "maxLength": 1000, 13881 - "maxGraphemes": 100, 13882 - "description": "The music's album name.", 13883 - }, 13884 - "recordLabel": { 13885 - "type": "string", 13886 - "maxLength": 1000, 13887 - "maxGraphemes": 100, 13888 - "description": "The music's record label.", 13889 - }, 13890 - "cover": { 13891 - "type": "blob", 13892 - "description": 13893 - "Image to be displayed in music's page. AKA, 'cover image'", 13894 - "accept": [ 13895 - "image/png", 13896 - "image/jpeg", 13897 - ], 13898 - "maxSize": 5242880, 13899 - }, 13900 - "author": { 13901 - "type": "string", 13902 - "maxLength": 1000, 13903 - "maxGraphemes": 100, 13904 - "description": "The music's author.", 13905 - }, 13906 - "text": { 13907 - "type": "string", 13908 - "maxLength": 3000, 13909 - "maxGraphemes": 300, 13910 - "description": "The music's description.", 13911 - }, 13912 - "copyright": { 13913 - "type": "array", 13914 - "items": { 13915 - "type": "string", 13916 - "maxLength": 3000, 13917 - "maxGraphemes": 300, 13918 - }, 13919 - "minLength": 1, 13920 - }, 13921 - "facets": { 13922 - "type": "array", 13923 - "description": 13924 - "Annotations of text (mentions, URLs, hashtags, etc)", 13925 - "items": { 13926 - "type": "ref", 13927 - "ref": "lex:so.sprk.richtext.facet", 13928 - }, 13929 - }, 13930 - "labels": { 13931 - "type": "union", 13932 - "description": 13933 - "Self-label values for this music. Effectively content warnings.", 13934 - "refs": [ 13935 - "lex:com.atproto.label.defs#selfLabels", 13936 - ], 13937 - }, 13938 - "tags": { 13939 - "type": "array", 13940 - "description": "The music's Hashtags", 13941 - "maxLength": 8, 13942 - "items": { 13943 - "type": "string", 13944 - "maxLength": 640, 13945 - "maxGraphemes": 64, 13946 - }, 13947 - }, 13948 - "createdAt": { 13949 - "type": "string", 13950 - "format": "datetime", 13951 - "description": 13952 - "Client-declared timestamp when this post was originally created.", 13953 - }, 13954 - }, 13955 - }, 13956 - }, 13957 - }, 13958 - }, 13959 12109 "SoSprkFeedSendInteractions": { 13960 12110 "lexicon": 1, 13961 12111 "id": "so.sprk.feed.sendInteractions", ··· 13996 12146 "lexicon": 1, 13997 12147 "id": "so.sprk.feed.defs", 13998 12148 "defs": { 13999 - "storyView": { 12149 + "postView": { 14000 12150 "type": "object", 14001 12151 "required": [ 14002 12152 "uri", ··· 14024 12174 "media": { 14025 12175 "type": "union", 14026 12176 "refs": [ 14027 - "lex:so.sprk.embed.images#view", 14028 - "lex:so.sprk.embed.video#view", 12177 + "lex:so.sprk.media.images#view", 12178 + "lex:so.sprk.media.video#view", 14029 12179 ], 14030 12180 }, 12181 + "sound": { 12182 + "type": "ref", 12183 + "ref": "lex:so.sprk.sound.defs#audioView", 12184 + }, 12185 + "replyCount": { 12186 + "type": "integer", 12187 + }, 12188 + "repostCount": { 12189 + "type": "integer", 12190 + }, 12191 + "likeCount": { 12192 + "type": "integer", 12193 + }, 14031 12194 "indexedAt": { 14032 12195 "type": "string", 14033 12196 "format": "datetime", 14034 12197 }, 12198 + "viewer": { 12199 + "type": "ref", 12200 + "ref": "lex:so.sprk.feed.defs#viewerState", 12201 + }, 12202 + "labels": { 12203 + "type": "array", 12204 + "items": { 12205 + "type": "ref", 12206 + "ref": "lex:com.atproto.label.defs#label", 12207 + }, 12208 + }, 12209 + "threadgate": { 12210 + "type": "ref", 12211 + "ref": "lex:so.sprk.feed.defs#threadgateView", 12212 + }, 14035 12213 }, 14036 12214 }, 14037 - "postView": { 12215 + "replyView": { 14038 12216 "type": "object", 14039 12217 "required": [ 14040 12218 "uri", ··· 14059 12237 "record": { 14060 12238 "type": "unknown", 14061 12239 }, 14062 - "embed": { 12240 + "media": { 14063 12241 "type": "union", 14064 12242 "refs": [ 14065 - "lex:so.sprk.embed.images#view", 14066 - "lex:so.sprk.embed.video#view", 12243 + "lex:so.sprk.media.image#view", 14067 12244 ], 14068 12245 }, 14069 - "sound": { 14070 - "type": "ref", 14071 - "ref": "lex:so.sprk.sound.defs#audioView", 14072 - }, 14073 12246 "replyCount": { 14074 - "type": "integer", 14075 - }, 14076 - "repostCount": { 14077 12247 "type": "integer", 14078 12248 }, 14079 12249 "likeCount": { ··· 14093 12263 "type": "ref", 14094 12264 "ref": "lex:com.atproto.label.defs#label", 14095 12265 }, 14096 - }, 14097 - "threadgate": { 14098 - "type": "ref", 14099 - "ref": "lex:so.sprk.feed.defs#threadgateView", 14100 12266 }, 14101 12267 }, 14102 12268 }, ··· 14147 12313 "post": { 14148 12314 "type": "ref", 14149 12315 "ref": "lex:so.sprk.feed.defs#postView", 14150 - }, 14151 - "reply": { 14152 - "type": "ref", 14153 - "ref": "lex:so.sprk.feed.defs#replyRef", 14154 12316 }, 14155 12317 "reason": { 14156 12318 "type": "union", ··· 14167 12329 }, 14168 12330 }, 14169 12331 }, 14170 - "feedViewStory": { 14171 - "type": "object", 14172 - "required": [ 14173 - "story", 14174 - ], 14175 - "properties": { 14176 - "story": { 14177 - "type": "ref", 14178 - "ref": "lex:so.sprk.feed.defs#storyView", 14179 - }, 14180 - }, 14181 - }, 14182 - "storiesByAuthor": { 14183 - "type": "object", 14184 - "required": [ 14185 - "author", 14186 - "stories", 14187 - ], 14188 - "properties": { 14189 - "author": { 14190 - "type": "ref", 14191 - "ref": "lex:so.sprk.actor.defs#profileViewBasic", 14192 - }, 14193 - "stories": { 14194 - "type": "array", 14195 - "items": { 14196 - "type": "ref", 14197 - "ref": "lex:so.sprk.feed.defs#storyView", 14198 - }, 14199 - }, 14200 - }, 14201 - }, 14202 12332 "replyRef": { 14203 12333 "type": "object", 14204 12334 "required": [ ··· 14218 12348 "type": "union", 14219 12349 "refs": [ 14220 12350 "lex:so.sprk.feed.defs#postView", 12351 + "lex:so.sprk.feed.defs#replyView", 14221 12352 "lex:so.sprk.feed.defs#notFoundPost", 14222 12353 "lex:so.sprk.feed.defs#blockedPost", 14223 12354 ], ··· 14258 12389 ], 14259 12390 "properties": { 14260 12391 "post": { 14261 - "type": "ref", 14262 - "ref": "lex:so.sprk.feed.defs#postView", 12392 + "type": "union", 12393 + "refs": [ 12394 + "lex:so.sprk.feed.defs#postView", 12395 + "lex:so.sprk.feed.defs#replyView", 12396 + ], 14263 12397 }, 14264 12398 "parent": { 14265 12399 "type": "union", ··· 14475 12609 "record": { 14476 12610 "type": "unknown", 14477 12611 }, 14478 - "lists": { 14479 - "type": "array", 14480 - "items": { 14481 - "type": "ref", 14482 - "ref": "lex:so.sprk.graph.defs#listViewBasic", 14483 - }, 14484 - }, 14485 12612 }, 14486 12613 }, 14487 12614 "interaction": { ··· 14504 12631 "so.sprk.feed.defs#interactionLike", 14505 12632 "so.sprk.feed.defs#interactionRepost", 14506 12633 "so.sprk.feed.defs#interactionReply", 14507 - "so.sprk.feed.defs#interactionQuote", 14508 12634 "so.sprk.feed.defs#interactionShare", 14509 12635 ], 14510 12636 }, ··· 14543 12669 "description": 14544 12670 "User clicked through to the embedded content of the feed item", 14545 12671 }, 14546 - "contentModeUnspecified": { 14547 - "type": "token", 14548 - "description": 14549 - "Declares the feed generator returns any types of posts.", 14550 - }, 14551 - "contentModeVideo": { 14552 - "type": "token", 14553 - "description": 14554 - "Declares the feed generator returns posts containing so.sprk.embed.video embeds.", 14555 - }, 14556 12672 "interactionSeen": { 14557 12673 "type": "token", 14558 12674 "description": "Feed item was seen by user", ··· 14568 12684 "interactionReply": { 14569 12685 "type": "token", 14570 12686 "description": "User replied to the feed item", 14571 - }, 14572 - "interactionQuote": { 14573 - "type": "token", 14574 - "description": "User quoted the feed item", 14575 12687 }, 14576 12688 "interactionShare": { 14577 12689 "type": "token", ··· 14982 13094 "lex:so.sprk.feed.threadgate#mentionRule", 14983 13095 "lex:so.sprk.feed.threadgate#followerRule", 14984 13096 "lex:so.sprk.feed.threadgate#followingRule", 14985 - "lex:so.sprk.feed.threadgate#listRule", 14986 13097 ], 14987 13098 }, 14988 13099 }, ··· 15017 13128 "description": "Allow replies from actors you follow.", 15018 13129 "properties": {}, 15019 13130 }, 15020 - "listRule": { 15021 - "type": "object", 15022 - "description": "Allow replies from actors on a list.", 15023 - "required": [ 15024 - "list", 15025 - ], 15026 - "properties": { 15027 - "list": { 15028 - "type": "string", 15029 - "format": "at-uri", 15030 - }, 15031 - }, 15032 - }, 15033 13131 }, 15034 13132 }, 15035 13133 "SoSprkFeedGetPostThread": { ··· 15043 13141 "parameters": { 15044 13142 "type": "params", 15045 13143 "required": [ 15046 - "uri", 13144 + "anchor", 15047 13145 ], 15048 13146 "properties": { 15049 - "uri": { 13147 + "anchor": { 15050 13148 "type": "string", 15051 13149 "format": "at-uri", 15052 - "description": "Reference (AT-URI) to post record.", 13150 + "description": "Reference (AT-URI) to anchor post record.", 13151 + }, 13152 + "limit": { 13153 + "type": "integer", 13154 + "minimum": 1, 13155 + "maximum": 100, 13156 + "default": 50, 13157 + }, 13158 + "cursor": { 13159 + "type": "string", 15053 13160 }, 15054 13161 "depth": { 15055 13162 "type": "integer", ··· 15067 13174 "minimum": 0, 15068 13175 "maximum": 1000, 15069 13176 }, 13177 + "prioritizeFollowedUsers": { 13178 + "type": "boolean", 13179 + "description": 13180 + "Whether to prioritize posts from followed users. It only has effect when the user is authenticated.", 13181 + "default": false, 13182 + }, 13183 + "sort": { 13184 + "type": "string", 13185 + "description": "Sorting for the thread replies.", 13186 + "knownValues": [ 13187 + "newest", 13188 + "oldest", 13189 + "top", 13190 + ], 13191 + "default": "oldest", 13192 + }, 15070 13193 }, 15071 13194 }, 15072 13195 "output": { ··· 15077 13200 "thread", 15078 13201 ], 15079 13202 "properties": { 13203 + "cursor": { 13204 + "type": "string", 13205 + }, 15080 13206 "thread": { 15081 - "type": "union", 15082 - "refs": [ 15083 - "lex:so.sprk.feed.defs#threadViewPost", 15084 - "lex:so.sprk.feed.defs#notFoundPost", 15085 - "lex:so.sprk.feed.defs#blockedPost", 15086 - ], 13207 + "type": "array", 13208 + "description": 13209 + "A flat list of thread items. The depth of each item is indicated by the depth property inside the item.", 13210 + "items": { 13211 + "type": "ref", 13212 + "ref": "lex:so.sprk.feed.getPostThread#threadItem", 13213 + }, 15087 13214 }, 15088 13215 "threadgate": { 15089 13216 "type": "ref", ··· 15097 13224 "name": "NotFound", 15098 13225 }, 15099 13226 ], 13227 + }, 13228 + "threadItem": { 13229 + "type": "object", 13230 + "required": [ 13231 + "uri", 13232 + "depth", 13233 + "value", 13234 + ], 13235 + "properties": { 13236 + "uri": { 13237 + "type": "string", 13238 + "format": "at-uri", 13239 + }, 13240 + "depth": { 13241 + "type": "integer", 13242 + "description": 13243 + "The nesting level of this item in the thread. Depth 0 means the anchor item.", 13244 + }, 13245 + "value": { 13246 + "type": "union", 13247 + "refs": [ 13248 + "lex:so.sprk.feed.defs#threadViewPost", 13249 + "lex:so.sprk.feed.defs#NotFoundPost", 13250 + "lex:so.sprk.feed.defs#BlockedPost", 13251 + ], 13252 + }, 13253 + }, 15100 13254 }, 15101 13255 }, 15102 13256 }, ··· 15295 13449 }, 15296 13450 }, 15297 13451 }, 15298 - "SoSprkFeedStory": { 15299 - "lexicon": 1, 15300 - "id": "so.sprk.feed.story", 15301 - "defs": { 15302 - "main": { 15303 - "type": "record", 15304 - "description": "Record containing a Spark story.", 15305 - "key": "tid", 15306 - "record": { 15307 - "type": "object", 15308 - "required": [ 15309 - "createdAt", 15310 - "media", 15311 - ], 15312 - "properties": { 15313 - "media": { 15314 - "type": "union", 15315 - "refs": [ 15316 - "lex:so.sprk.embed.images", 15317 - "lex:so.sprk.embed.video", 15318 - ], 15319 - }, 15320 - "sound": { 15321 - "type": "ref", 15322 - "ref": "lex:com.atproto.repo.strongRef", 15323 - }, 15324 - "labels": { 15325 - "type": "union", 15326 - "description": 15327 - "Self-label values for this story. Effectively content warnings.", 15328 - "refs": [ 15329 - "lex:com.atproto.label.defs#selfLabels", 15330 - ], 15331 - }, 15332 - "tags": { 15333 - "type": "array", 15334 - "description": 15335 - "Additional hashtags, in addition to any included in story text and facets.", 15336 - "maxLength": 8, 15337 - "items": { 15338 - "type": "string", 15339 - "maxLength": 640, 15340 - "maxGraphemes": 64, 15341 - }, 15342 - }, 15343 - "createdAt": { 15344 - "type": "string", 15345 - "format": "datetime", 15346 - "description": 15347 - "Client-declared timestamp when this story was originally created.", 15348 - }, 15349 - }, 15350 - }, 15351 - }, 15352 - }, 15353 - }, 15354 13452 "SoSprkFeedDescribeFeedGenerator": { 15355 13453 "lexicon": 1, 15356 13454 "id": "so.sprk.feed.describeFeedGenerator", ··· 15592 13690 }, 15593 13691 }, 15594 13692 }, 15595 - "SoSprkFeedGetStories": { 13693 + "SoSprkFeedReply": { 15596 13694 "lexicon": 1, 15597 - "id": "so.sprk.feed.getStories", 13695 + "id": "so.sprk.feed.reply", 15598 13696 "defs": { 15599 13697 "main": { 15600 - "type": "query", 15601 - "description": 15602 - "Gets story views for a specified list of stories (by AT-URI). This is sometimes referred to as 'hydrating' a story reference list.", 15603 - "parameters": { 15604 - "type": "params", 13698 + "type": "record", 13699 + "description": "Record containing a Spark reply.", 13700 + "key": "tid", 13701 + "record": { 13702 + "type": "object", 15605 13703 "required": [ 15606 - "uris", 13704 + "createdAt", 13705 + "reply", 15607 13706 ], 15608 13707 "properties": { 15609 - "uris": { 13708 + "text": { 13709 + "type": "string", 13710 + "maxLength": 3000, 13711 + "maxGraphemes": 300, 13712 + "description": "The reply text.", 13713 + }, 13714 + "facets": { 15610 13715 "type": "array", 15611 13716 "description": 15612 - "List of story AT-URIs to return hydrated views for.", 13717 + "Annotations of text (mentions, URLs, hashtags, etc)", 15613 13718 "items": { 15614 - "type": "string", 15615 - "format": "at-uri", 13719 + "type": "ref", 13720 + "ref": "lex:so.sprk.richtext.facet", 15616 13721 }, 15617 13722 }, 15618 - }, 15619 - }, 15620 - "output": { 15621 - "encoding": "application/json", 15622 - "schema": { 15623 - "type": "object", 15624 - "required": [ 15625 - "stories", 15626 - ], 15627 - "properties": { 15628 - "stories": { 15629 - "type": "array", 15630 - "items": { 15631 - "type": "ref", 15632 - "ref": "lex:so.sprk.feed.defs#storyView", 15633 - }, 15634 - }, 13723 + "reply": { 13724 + "type": "ref", 13725 + "ref": "lex:so.sprk.feed.reply#replyRef", 15635 13726 }, 15636 - }, 15637 - }, 15638 - }, 15639 - }, 15640 - }, 15641 - "SoSprkFeedGetQuotes": { 15642 - "lexicon": 1, 15643 - "id": "so.sprk.feed.getQuotes", 15644 - "defs": { 15645 - "main": { 15646 - "type": "query", 15647 - "description": "Get a list of quotes for a given post.", 15648 - "parameters": { 15649 - "type": "params", 15650 - "required": [ 15651 - "uri", 15652 - ], 15653 - "properties": { 15654 - "uri": { 15655 - "type": "string", 15656 - "format": "at-uri", 15657 - "description": "Reference (AT-URI) of post record", 13727 + "media": { 13728 + "type": "union", 13729 + "refs": [ 13730 + "lex:so.sprk.media.image", 13731 + ], 15658 13732 }, 15659 - "cid": { 15660 - "type": "string", 15661 - "format": "cid", 13733 + "langs": { 13734 + "type": "array", 15662 13735 "description": 15663 - "If supplied, filters to quotes of specific version (by CID) of the post record.", 15664 - }, 15665 - "limit": { 15666 - "type": "integer", 15667 - "minimum": 1, 15668 - "maximum": 100, 15669 - "default": 50, 15670 - }, 15671 - "cursor": { 15672 - "type": "string", 15673 - }, 15674 - }, 15675 - }, 15676 - "output": { 15677 - "encoding": "application/json", 15678 - "schema": { 15679 - "type": "object", 15680 - "required": [ 15681 - "uri", 15682 - "posts", 15683 - ], 15684 - "properties": { 15685 - "uri": { 13736 + "Indicates human language of post primary text content.", 13737 + "maxLength": 3, 13738 + "items": { 15686 13739 "type": "string", 15687 - "format": "at-uri", 15688 - }, 15689 - "cid": { 15690 - "type": "string", 15691 - "format": "cid", 15692 - }, 15693 - "cursor": { 15694 - "type": "string", 15695 - }, 15696 - "posts": { 15697 - "type": "array", 15698 - "items": { 15699 - "type": "ref", 15700 - "ref": "lex:so.sprk.feed.defs#postView", 15701 - }, 13740 + "format": "language", 15702 13741 }, 15703 13742 }, 15704 - }, 15705 - }, 15706 - }, 15707 - }, 15708 - }, 15709 - "SoSprkFeedGetStoriesTimeline": { 15710 - "lexicon": 1, 15711 - "id": "so.sprk.feed.getStoriesTimeline", 15712 - "defs": { 15713 - "main": { 15714 - "type": "query", 15715 - "description": 15716 - "Get a view of the requesting account's stories timeline grouped by author.", 15717 - "parameters": { 15718 - "type": "params", 15719 - "properties": { 15720 - "limit": { 15721 - "type": "integer", 15722 - "minimum": 1, 15723 - "maximum": 100, 15724 - "default": 50, 13743 + "labels": { 13744 + "type": "union", 13745 + "description": 13746 + "Self-label values for this post. Effectively content warnings.", 13747 + "refs": [ 13748 + "lex:com.atproto.label.defs#selfLabels", 13749 + ], 15725 13750 }, 15726 - "cursor": { 13751 + "createdAt": { 15727 13752 "type": "string", 13753 + "format": "datetime", 13754 + "description": 13755 + "Client-declared timestamp when this post was originally created.", 15728 13756 }, 15729 13757 }, 15730 13758 }, 15731 - "output": { 15732 - "encoding": "application/json", 15733 - "schema": { 15734 - "type": "object", 15735 - "required": [ 15736 - "storiesByAuthor", 15737 - ], 15738 - "properties": { 15739 - "cursor": { 15740 - "type": "string", 15741 - }, 15742 - "storiesByAuthor": { 15743 - "type": "array", 15744 - "items": { 15745 - "type": "ref", 15746 - "ref": "lex:so.sprk.feed.defs#storiesByAuthor", 15747 - }, 15748 - }, 15749 - }, 13759 + }, 13760 + "replyRef": { 13761 + "type": "object", 13762 + "required": [ 13763 + "root", 13764 + "parent", 13765 + ], 13766 + "properties": { 13767 + "root": { 13768 + "type": "ref", 13769 + "ref": "lex:com.atproto.repo.strongRef", 13770 + }, 13771 + "parent": { 13772 + "type": "ref", 13773 + "ref": "lex:com.atproto.repo.strongRef", 15750 13774 }, 15751 13775 }, 15752 13776 }, ··· 15812 13836 }, 15813 13837 }, 15814 13838 }, 15815 - "SoSprkFeedGetListFeed": { 15816 - "lexicon": 1, 15817 - "id": "so.sprk.feed.getListFeed", 15818 - "defs": { 15819 - "main": { 15820 - "type": "query", 15821 - "description": 15822 - "Get a feed of recent posts from a list (posts and reposts from any actors on the list). Does not require auth.", 15823 - "parameters": { 15824 - "type": "params", 15825 - "required": [ 15826 - "list", 15827 - ], 15828 - "properties": { 15829 - "list": { 15830 - "type": "string", 15831 - "format": "at-uri", 15832 - "description": "Reference (AT-URI) to the list record.", 15833 - }, 15834 - "limit": { 15835 - "type": "integer", 15836 - "minimum": 1, 15837 - "maximum": 100, 15838 - "default": 50, 15839 - }, 15840 - "cursor": { 15841 - "type": "string", 15842 - }, 15843 - }, 15844 - }, 15845 - "output": { 15846 - "encoding": "application/json", 15847 - "schema": { 15848 - "type": "object", 15849 - "required": [ 15850 - "feed", 15851 - ], 15852 - "properties": { 15853 - "cursor": { 15854 - "type": "string", 15855 - }, 15856 - "feed": { 15857 - "type": "array", 15858 - "items": { 15859 - "type": "ref", 15860 - "ref": "lex:so.sprk.feed.defs#feedViewPost", 15861 - }, 15862 - }, 15863 - }, 15864 - }, 15865 - }, 15866 - "errors": [ 15867 - { 15868 - "name": "UnknownList", 15869 - }, 15870 - ], 15871 - }, 15872 - }, 15873 - }, 15874 13839 "SoSprkFeedGetSuggestedFeeds": { 15875 13840 "lexicon": 1, 15876 13841 "id": "so.sprk.feed.getSuggestedFeeds", ··· 15982 13947 "type": "object", 15983 13948 "required": [ 15984 13949 "createdAt", 13950 + "media", 15985 13951 ], 15986 13952 "properties": { 15987 - "text": { 15988 - "type": "string", 15989 - "maxLength": 3000, 15990 - "maxGraphemes": 300, 15991 - "description": "The post description.", 15992 - }, 15993 - "facets": { 15994 - "type": "array", 15995 - "description": 15996 - "Annotations of text (mentions, URLs, hashtags, etc)", 15997 - "items": { 15998 - "type": "ref", 15999 - "ref": "lex:so.sprk.richtext.facet", 16000 - }, 16001 - }, 16002 - "reply": { 13953 + "caption": { 16003 13954 "type": "ref", 16004 - "ref": "lex:so.sprk.feed.post#replyRef", 13955 + "ref": "lex:so.sprk.feed.post#captionRef", 16005 13956 }, 16006 - "embed": { 13957 + "media": { 16007 13958 "type": "union", 16008 13959 "refs": [ 16009 - "lex:so.sprk.embed.images", 16010 - "lex:so.sprk.embed.video", 13960 + "lex:so.sprk.media.images", 13961 + "lex:so.sprk.media.video", 16011 13962 ], 16012 13963 }, 16013 13964 "sound": { ··· 16052 14003 }, 16053 14004 }, 16054 14005 }, 16055 - "replyRef": { 14006 + "captionRef": { 16056 14007 "type": "object", 16057 - "required": [ 16058 - "root", 16059 - "parent", 16060 - ], 16061 14008 "properties": { 16062 - "root": { 16063 - "type": "ref", 16064 - "ref": "lex:com.atproto.repo.strongRef", 14009 + "text": { 14010 + "type": "string", 14011 + "maxLength": 3000, 14012 + "maxGraphemes": 300, 14013 + "description": "The post description.", 16065 14014 }, 16066 - "parent": { 16067 - "type": "ref", 16068 - "ref": "lex:com.atproto.repo.strongRef", 14015 + "facets": { 14016 + "type": "array", 14017 + "description": 14018 + "Annotations of text (mentions, URLs, hashtags, etc)", 14019 + "items": { 14020 + "type": "ref", 14021 + "ref": "lex:so.sprk.richtext.facet", 14022 + }, 16069 14023 }, 16070 14024 }, 16071 14025 }, ··· 16696 14650 "type": "ref", 16697 14651 "ref": "lex:so.sprk.actor.defs#profileAssociated", 16698 14652 }, 16699 - "joinedViaStarterPack": { 16700 - "type": "ref", 16701 - "ref": "lex:so.sprk.graph.defs#starterPackViewBasic", 16702 - }, 16703 14653 "indexedAt": { 16704 14654 "type": "string", 16705 14655 "format": "datetime", ··· 16736 14686 "profileAssociated": { 16737 14687 "type": "object", 16738 14688 "properties": { 16739 - "lists": { 16740 - "type": "integer", 16741 - }, 16742 14689 "feedgens": { 16743 14690 "type": "integer", 16744 14691 }, 16745 - "starterPacks": { 16746 - "type": "integer", 16747 - }, 16748 14692 "labeler": { 16749 14693 "type": "boolean", 16750 14694 }, ··· 16778 14722 "muted": { 16779 14723 "type": "boolean", 16780 14724 }, 16781 - "mutedByList": { 16782 - "type": "ref", 16783 - "ref": "lex:so.sprk.graph.defs#listViewBasic", 16784 - }, 16785 14725 "blockedBy": { 16786 14726 "type": "boolean", 16787 14727 }, 16788 14728 "blocking": { 16789 14729 "type": "string", 16790 14730 "format": "at-uri", 16791 - }, 16792 - "blockingByList": { 16793 - "type": "ref", 16794 - "ref": "lex:so.sprk.graph.defs#listViewBasic", 16795 14731 }, 16796 14732 "following": { 16797 14733 "type": "string", ··· 16903 14839 "type": "string", 16904 14840 "knownValues": [ 16905 14841 "feed", 16906 - "list", 16907 14842 "timeline", 16908 14843 ], 16909 14844 }, ··· 17150 15085 "lex:so.sprk.feed.threadgate#mentionRule", 17151 15086 "lex:so.sprk.feed.threadgate#followerRule", 17152 15087 "lex:so.sprk.feed.threadgate#followingRule", 17153 - "lex:so.sprk.feed.threadgate#listRule", 17154 15088 ], 17155 15089 }, 17156 15090 }, ··· 17457 15391 }, 17458 15392 }, 17459 15393 }, 15394 + "SoSprkStoryDefs": { 15395 + "lexicon": 1, 15396 + "id": "so.sprk.story.defs", 15397 + "defs": { 15398 + "storyView": { 15399 + "type": "object", 15400 + "required": [ 15401 + "uri", 15402 + "cid", 15403 + "author", 15404 + "record", 15405 + "indexedAt", 15406 + ], 15407 + "properties": { 15408 + "uri": { 15409 + "type": "string", 15410 + "format": "at-uri", 15411 + }, 15412 + "cid": { 15413 + "type": "string", 15414 + "format": "cid", 15415 + }, 15416 + "author": { 15417 + "type": "ref", 15418 + "ref": "lex:so.sprk.actor.defs#profileViewBasic", 15419 + }, 15420 + "record": { 15421 + "type": "unknown", 15422 + }, 15423 + "media": { 15424 + "type": "union", 15425 + "refs": [ 15426 + "lex:so.sprk.media.image#view", 15427 + "lex:so.sprk.media.video#view", 15428 + ], 15429 + }, 15430 + "indexedAt": { 15431 + "type": "string", 15432 + "format": "datetime", 15433 + }, 15434 + }, 15435 + }, 15436 + "storiesByAuthor": { 15437 + "type": "object", 15438 + "required": [ 15439 + "author", 15440 + "stories", 15441 + ], 15442 + "properties": { 15443 + "author": { 15444 + "type": "ref", 15445 + "ref": "lex:so.sprk.actor.defs#profileViewBasic", 15446 + }, 15447 + "stories": { 15448 + "type": "array", 15449 + "items": { 15450 + "type": "ref", 15451 + "ref": "lex:so.sprk.story.defs#storyView", 15452 + }, 15453 + }, 15454 + }, 15455 + }, 15456 + "interaction": { 15457 + "type": "object", 15458 + "properties": { 15459 + "item": { 15460 + "type": "string", 15461 + "format": "at-uri", 15462 + }, 15463 + "event": { 15464 + "type": "string", 15465 + "knownValues": [ 15466 + "so.sprk.feed.defs#clickthroughItem", 15467 + "so.sprk.feed.defs#clickthroughAuthor", 15468 + "so.sprk.feed.defs#clickthroughReposter", 15469 + "so.sprk.feed.defs#clickthroughEmbed", 15470 + "so.sprk.feed.defs#interactionSeen", 15471 + "so.sprk.feed.defs#interactionLike", 15472 + "so.sprk.feed.defs#interactionRepost", 15473 + "so.sprk.feed.defs#interactionShare", 15474 + ], 15475 + }, 15476 + }, 15477 + }, 15478 + }, 15479 + }, 15480 + "SoSprkStoryGetTimeline": { 15481 + "lexicon": 1, 15482 + "id": "so.sprk.story.getTimeline", 15483 + "defs": { 15484 + "main": { 15485 + "type": "query", 15486 + "description": 15487 + "Get a view of the requesting account's stories timeline grouped by author.", 15488 + "parameters": { 15489 + "type": "params", 15490 + "properties": { 15491 + "limit": { 15492 + "type": "integer", 15493 + "minimum": 1, 15494 + "maximum": 100, 15495 + "default": 50, 15496 + }, 15497 + "cursor": { 15498 + "type": "string", 15499 + }, 15500 + }, 15501 + }, 15502 + "output": { 15503 + "encoding": "application/json", 15504 + "schema": { 15505 + "type": "object", 15506 + "required": [ 15507 + "storiesByAuthor", 15508 + ], 15509 + "properties": { 15510 + "cursor": { 15511 + "type": "string", 15512 + }, 15513 + "storiesByAuthor": { 15514 + "type": "array", 15515 + "items": { 15516 + "type": "ref", 15517 + "ref": "lex:so.sprk.story.defs#storiesByAuthor", 15518 + }, 15519 + }, 15520 + }, 15521 + }, 15522 + }, 15523 + }, 15524 + }, 15525 + }, 15526 + "SoSprkStoryGetStories": { 15527 + "lexicon": 1, 15528 + "id": "so.sprk.story.getStories", 15529 + "defs": { 15530 + "main": { 15531 + "type": "query", 15532 + "description": 15533 + "Gets story views for a specified list of stories (by AT-URI). This is sometimes referred to as 'hydrating' a story reference list.", 15534 + "parameters": { 15535 + "type": "params", 15536 + "required": [ 15537 + "uris", 15538 + ], 15539 + "properties": { 15540 + "uris": { 15541 + "type": "array", 15542 + "description": 15543 + "List of story AT-URIs to return hydrated views for.", 15544 + "items": { 15545 + "type": "string", 15546 + "format": "at-uri", 15547 + }, 15548 + }, 15549 + }, 15550 + }, 15551 + "output": { 15552 + "encoding": "application/json", 15553 + "schema": { 15554 + "type": "object", 15555 + "required": [ 15556 + "stories", 15557 + ], 15558 + "properties": { 15559 + "stories": { 15560 + "type": "array", 15561 + "items": { 15562 + "type": "ref", 15563 + "ref": "lex:so.sprk.story.defs#storyView", 15564 + }, 15565 + }, 15566 + }, 15567 + }, 15568 + }, 15569 + }, 15570 + }, 15571 + }, 15572 + "SoSprkStoryPost": { 15573 + "lexicon": 1, 15574 + "id": "so.sprk.story.post", 15575 + "defs": { 15576 + "main": { 15577 + "type": "record", 15578 + "description": "Record containing a Spark story.", 15579 + "key": "tid", 15580 + "record": { 15581 + "type": "object", 15582 + "required": [ 15583 + "createdAt", 15584 + "media", 15585 + ], 15586 + "properties": { 15587 + "media": { 15588 + "type": "union", 15589 + "refs": [ 15590 + "lex:so.sprk.media.image", 15591 + "lex:so.sprk.media.video", 15592 + ], 15593 + }, 15594 + "sound": { 15595 + "type": "ref", 15596 + "ref": "lex:com.atproto.repo.strongRef", 15597 + }, 15598 + "labels": { 15599 + "type": "union", 15600 + "description": 15601 + "Self-label values for this story. Effectively content warnings.", 15602 + "refs": [ 15603 + "lex:com.atproto.label.defs#selfLabels", 15604 + ], 15605 + }, 15606 + "createdAt": { 15607 + "type": "string", 15608 + "format": "datetime", 15609 + "description": 15610 + "Client-declared timestamp when this story was originally created.", 15611 + }, 15612 + }, 15613 + }, 15614 + }, 15615 + }, 15616 + }, 17460 15617 "SoSprkLabelerDefs": { 17461 15618 "lexicon": 1, 17462 15619 "id": "so.sprk.labeler.defs", ··· 17678 15835 }, 17679 15836 }, 17680 15837 }, 15838 + }, 15839 + }, 15840 + }, 15841 + }, 15842 + }, 15843 + "SoSprkMediaDefs": { 15844 + "lexicon": 1, 15845 + "id": "so.sprk.media.defs", 15846 + "defs": { 15847 + "aspectRatio": { 15848 + "type": "object", 15849 + "description": 15850 + "width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.", 15851 + "required": [ 15852 + "width", 15853 + "height", 15854 + ], 15855 + "properties": { 15856 + "width": { 15857 + "type": "integer", 15858 + "minimum": 1, 15859 + }, 15860 + "height": { 15861 + "type": "integer", 15862 + "minimum": 1, 15863 + }, 15864 + }, 15865 + }, 15866 + }, 15867 + }, 15868 + "SoSprkMediaImages": { 15869 + "lexicon": 1, 15870 + "id": "so.sprk.media.images", 15871 + "description": "A set of multiple images in a Spark post.", 15872 + "defs": { 15873 + "main": { 15874 + "type": "object", 15875 + "required": [ 15876 + "images", 15877 + ], 15878 + "properties": { 15879 + "images": { 15880 + "type": "array", 15881 + "items": { 15882 + "type": "ref", 15883 + "ref": "lex:so.sprk.media.image", 15884 + }, 15885 + "maxLength": 12, 15886 + }, 15887 + }, 15888 + }, 15889 + "view": { 15890 + "type": "object", 15891 + "required": [ 15892 + "images", 15893 + ], 15894 + "properties": { 15895 + "images": { 15896 + "type": "array", 15897 + "items": { 15898 + "type": "ref", 15899 + "ref": "lex:so.sprk.media.image#view", 15900 + }, 15901 + "maxLength": 12, 15902 + }, 15903 + }, 15904 + }, 15905 + }, 15906 + }, 15907 + "SoSprkMediaVideo": { 15908 + "lexicon": 1, 15909 + "id": "so.sprk.media.video", 15910 + "description": "A video in a Spark post.", 15911 + "defs": { 15912 + "main": { 15913 + "type": "object", 15914 + "required": [ 15915 + "video", 15916 + ], 15917 + "properties": { 15918 + "video": { 15919 + "type": "blob", 15920 + "accept": [ 15921 + "video/mp4", 15922 + ], 15923 + "maxSize": 314572800, 15924 + }, 15925 + "captions": { 15926 + "type": "array", 15927 + "items": { 15928 + "type": "ref", 15929 + "ref": "lex:so.sprk.media.video#caption", 15930 + }, 15931 + "maxLength": 20, 15932 + }, 15933 + "alt": { 15934 + "type": "string", 15935 + "description": 15936 + "Alt text description of the video, for accessibility.", 15937 + "maxGraphemes": 1000, 15938 + "maxLength": 10000, 15939 + }, 15940 + "aspectRatio": { 15941 + "type": "ref", 15942 + "ref": "lex:so.sprk.media.defs#aspectRatio", 15943 + }, 15944 + }, 15945 + }, 15946 + "caption": { 15947 + "type": "object", 15948 + "required": [ 15949 + "lang", 15950 + "file", 15951 + ], 15952 + "properties": { 15953 + "lang": { 15954 + "type": "string", 15955 + "format": "language", 15956 + }, 15957 + "file": { 15958 + "type": "blob", 15959 + "accept": [ 15960 + "text/vtt", 15961 + ], 15962 + "maxSize": 20000, 15963 + }, 15964 + }, 15965 + }, 15966 + "view": { 15967 + "type": "object", 15968 + "required": [ 15969 + "cid", 15970 + "playlist", 15971 + ], 15972 + "properties": { 15973 + "cid": { 15974 + "type": "string", 15975 + "format": "cid", 15976 + }, 15977 + "playlist": { 15978 + "type": "string", 15979 + "format": "uri", 15980 + }, 15981 + "thumbnail": { 15982 + "type": "string", 15983 + "format": "uri", 15984 + }, 15985 + "alt": { 15986 + "type": "string", 15987 + "maxGraphemes": 1000, 15988 + "maxLength": 10000, 15989 + }, 15990 + "aspectRatio": { 15991 + "type": "ref", 15992 + "ref": "lex:so.sprk.media.defs#aspectRatio", 15993 + }, 15994 + }, 15995 + }, 15996 + }, 15997 + }, 15998 + "SoSprkMediaImage": { 15999 + "lexicon": 1, 16000 + "id": "so.sprk.media.image", 16001 + "description": "An image in a Spark record.", 16002 + "defs": { 16003 + "main": { 16004 + "type": "object", 16005 + "required": [ 16006 + "image", 16007 + "alt", 16008 + ], 16009 + "properties": { 16010 + "image": { 16011 + "type": "blob", 16012 + "accept": [ 16013 + "image/*", 16014 + ], 16015 + "maxSize": 5242880, 16016 + }, 16017 + "alt": { 16018 + "type": "string", 16019 + "description": 16020 + "Alt text description of the image, for accessibility.", 16021 + }, 16022 + "aspectRatio": { 16023 + "type": "ref", 16024 + "ref": "lex:so.sprk.media.defs#aspectRatio", 16025 + }, 16026 + }, 16027 + }, 16028 + "view": { 16029 + "type": "object", 16030 + "required": [ 16031 + "thumb", 16032 + "fullsize", 16033 + "alt", 16034 + ], 16035 + "properties": { 16036 + "thumb": { 16037 + "type": "string", 16038 + "format": "uri", 16039 + "description": 16040 + "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View.", 16041 + }, 16042 + "fullsize": { 16043 + "type": "string", 16044 + "format": "uri", 16045 + "description": 16046 + "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.", 16047 + }, 16048 + "alt": { 16049 + "type": "string", 16050 + "description": 16051 + "Alt text description of the image, for accessibility.", 16052 + }, 16053 + "aspectRatio": { 16054 + "type": "ref", 16055 + "ref": "lex:so.sprk.media.defs#aspectRatio", 17681 16056 }, 17682 16057 }, 17683 16058 }, ··· 21784 20159 "rkey": { 21785 20160 "type": "string", 21786 20161 "description": "The Record Key.", 20162 + "format": "record-key", 21787 20163 }, 21788 20164 "cid": { 21789 20165 "type": "string", ··· 22463 20839 SoSprkVideoDefs: "so.sprk.video.defs", 22464 20840 SoSprkVideoGetJobStatus: "so.sprk.video.getJobStatus", 22465 20841 SoSprkVideoGetUploadLimits: "so.sprk.video.getUploadLimits", 22466 - SoSprkEmbedDefs: "so.sprk.embed.defs", 22467 - SoSprkEmbedImages: "so.sprk.embed.images", 22468 - SoSprkEmbedVideo: "so.sprk.embed.video", 22469 20842 SoSprkNotificationRegisterPush: "so.sprk.notification.registerPush", 22470 20843 SoSprkNotificationPutPreferences: "so.sprk.notification.putPreferences", 22471 20844 SoSprkNotificationUpdateSeen: "so.sprk.notification.updateSeen", 22472 20845 SoSprkNotificationListNotifications: "so.sprk.notification.listNotifications", 22473 20846 SoSprkNotificationGetUnreadCount: "so.sprk.notification.getUnreadCount", 22474 - SoSprkUnspeccedSearchStarterPacksSkeleton: 22475 - "so.sprk.unspecced.searchStarterPacksSkeleton", 22476 - SoSprkUnspeccedDefs: "so.sprk.unspecced.defs", 22477 - SoSprkUnspeccedSearchActorsSkeleton: "so.sprk.unspecced.searchActorsSkeleton", 22478 - SoSprkUnspeccedGetSuggestionsSkeleton: 22479 - "so.sprk.unspecced.getSuggestionsSkeleton", 22480 - SoSprkUnspeccedSearchPostsSkeleton: "so.sprk.unspecced.searchPostsSkeleton", 22481 - SoSprkUnspeccedGetPopularFeedGenerators: 22482 - "so.sprk.unspecced.getPopularFeedGenerators", 22483 - SoSprkUnspeccedGetTrendingTopics: "so.sprk.unspecced.getTrendingTopics", 22484 - SoSprkUnspeccedGetTaggedSuggestions: "so.sprk.unspecced.getTaggedSuggestions", 22485 - SoSprkUnspeccedGetConfig: "so.sprk.unspecced.getConfig", 22486 - SoSprkGraphGetStarterPacks: "so.sprk.graph.getStarterPacks", 22487 20847 SoSprkGraphGetSuggestedFollowsByActor: 22488 20848 "so.sprk.graph.getSuggestedFollowsByActor", 22489 20849 SoSprkGraphBlock: "so.sprk.graph.block", 22490 20850 SoSprkGraphFollow: "so.sprk.graph.follow", 22491 20851 SoSprkGraphDefs: "so.sprk.graph.defs", 22492 - SoSprkGraphUnmuteActorList: "so.sprk.graph.unmuteActorList", 22493 - SoSprkGraphGetListBlocks: "so.sprk.graph.getListBlocks", 22494 - SoSprkGraphListblock: "so.sprk.graph.listblock", 22495 - SoSprkGraphGetStarterPack: "so.sprk.graph.getStarterPack", 22496 - SoSprkGraphStarterpack: "so.sprk.graph.starterpack", 22497 - SoSprkGraphMuteActorList: "so.sprk.graph.muteActorList", 22498 20852 SoSprkGraphMuteThread: "so.sprk.graph.muteThread", 22499 - SoSprkGraphSearchStarterPacks: "so.sprk.graph.searchStarterPacks", 22500 - SoSprkGraphGetActorStarterPacks: "so.sprk.graph.getActorStarterPacks", 22501 - SoSprkGraphGetLists: "so.sprk.graph.getLists", 22502 20853 SoSprkGraphGetFollowers: "so.sprk.graph.getFollowers", 22503 20854 SoSprkGraphUnmuteThread: "so.sprk.graph.unmuteThread", 22504 20855 SoSprkGraphMuteActor: "so.sprk.graph.muteActor", 22505 20856 SoSprkGraphGetMutes: "so.sprk.graph.getMutes", 22506 - SoSprkGraphListitem: "so.sprk.graph.listitem", 22507 - SoSprkGraphList: "so.sprk.graph.list", 22508 20857 SoSprkGraphGetKnownFollowers: "so.sprk.graph.getKnownFollowers", 22509 - SoSprkGraphGetListMutes: "so.sprk.graph.getListMutes", 22510 20858 SoSprkGraphGetFollows: "so.sprk.graph.getFollows", 22511 20859 SoSprkGraphGetBlocks: "so.sprk.graph.getBlocks", 22512 20860 SoSprkGraphGetRelationships: "so.sprk.graph.getRelationships", 22513 20861 SoSprkGraphUnmuteActor: "so.sprk.graph.unmuteActor", 22514 - SoSprkGraphGetList: "so.sprk.graph.getList", 22515 20862 SoSprkFeedGenerator: "so.sprk.feed.generator", 22516 - SoSprkFeedMusic: "so.sprk.feed.music", 22517 20863 SoSprkFeedSendInteractions: "so.sprk.feed.sendInteractions", 22518 20864 SoSprkFeedDefs: "so.sprk.feed.defs", 22519 20865 SoSprkFeedGetFeedGenerators: "so.sprk.feed.getFeedGenerators", ··· 22528 20874 SoSprkFeedLike: "so.sprk.feed.like", 22529 20875 SoSprkFeedGetRepostedBy: "so.sprk.feed.getRepostedBy", 22530 20876 SoSprkFeedRepost: "so.sprk.feed.repost", 22531 - SoSprkFeedStory: "so.sprk.feed.story", 22532 20877 SoSprkFeedDescribeFeedGenerator: "so.sprk.feed.describeFeedGenerator", 22533 20878 SoSprkFeedSearchPosts: "so.sprk.feed.searchPosts", 22534 20879 SoSprkFeedGetPosts: "so.sprk.feed.getPosts", 22535 20880 SoSprkFeedGetFeed: "so.sprk.feed.getFeed", 22536 - SoSprkFeedGetStories: "so.sprk.feed.getStories", 22537 - SoSprkFeedGetQuotes: "so.sprk.feed.getQuotes", 22538 - SoSprkFeedGetStoriesTimeline: "so.sprk.feed.getStoriesTimeline", 20881 + SoSprkFeedReply: "so.sprk.feed.reply", 22539 20882 SoSprkFeedGetFeedSkeleton: "so.sprk.feed.getFeedSkeleton", 22540 - SoSprkFeedGetListFeed: "so.sprk.feed.getListFeed", 22541 20883 SoSprkFeedGetSuggestedFeeds: "so.sprk.feed.getSuggestedFeeds", 22542 20884 SoSprkFeedGetActorFeeds: "so.sprk.feed.getActorFeeds", 22543 20885 SoSprkFeedPost: "so.sprk.feed.post", ··· 22557 20899 SoSprkActorGetProfiles: "so.sprk.actor.getProfiles", 22558 20900 SoSprkActorGetPreferences: "so.sprk.actor.getPreferences", 22559 20901 SoSprkActorProfile: "so.sprk.actor.profile", 20902 + SoSprkStoryDefs: "so.sprk.story.defs", 20903 + SoSprkStoryGetTimeline: "so.sprk.story.getTimeline", 20904 + SoSprkStoryGetStories: "so.sprk.story.getStories", 20905 + SoSprkStoryPost: "so.sprk.story.post", 22560 20906 SoSprkLabelerDefs: "so.sprk.labeler.defs", 22561 20907 SoSprkLabelerService: "so.sprk.labeler.service", 22562 20908 SoSprkLabelerGetServices: "so.sprk.labeler.getServices", 20909 + SoSprkMediaDefs: "so.sprk.media.defs", 20910 + SoSprkMediaImages: "so.sprk.media.images", 20911 + SoSprkMediaVideo: "so.sprk.media.video", 20912 + SoSprkMediaImage: "so.sprk.media.image", 22563 20913 ComAtprotoTempAddReservedHandle: "com.atproto.temp.addReservedHandle", 22564 20914 ComAtprotoTempCheckSignupQueue: "com.atproto.temp.checkSignupQueue", 22565 20915 ComAtprotoTempRequestPhoneVerification:
+1 -12
lex/types/so/sprk/actor/defs.ts
··· 6 6 import { type $Typed } from "../../../../util.ts"; 7 7 import type * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs.ts"; 8 8 import type * as ComAtprotoRepoStrongRef from "../../../com/atproto/repo/strongRef.ts"; 9 - import type * as SoSprkGraphDefs from "../graph/defs.ts"; 10 9 import type * as SoSprkFeedThreadgate from "../feed/threadgate.ts"; 11 10 12 11 const is$typed = _is$typed, validate = _validate; ··· 72 71 followsCount?: number; 73 72 postsCount?: number; 74 73 associated?: ProfileAssociated; 75 - joinedViaStarterPack?: SoSprkGraphDefs.StarterPackViewBasic; 76 74 indexedAt?: string; 77 75 createdAt?: string; 78 76 viewer?: ViewerState; ··· 94 92 95 93 export interface ProfileAssociated { 96 94 $type?: "so.sprk.actor.defs#profileAssociated"; 97 - lists?: number; 98 95 feedgens?: number; 99 - starterPacks?: number; 100 96 labeler?: boolean; 101 97 chat?: ProfileAssociatedChat; 102 98 } ··· 134 130 export interface ViewerState { 135 131 $type?: "so.sprk.actor.defs#viewerState"; 136 132 muted?: boolean; 137 - mutedByList?: SoSprkGraphDefs.ListViewBasic; 138 133 blockedBy?: boolean; 139 134 blocking?: string; 140 - blockingByList?: SoSprkGraphDefs.ListViewBasic; 141 135 following?: string; 142 136 followedBy?: string; 143 137 knownFollowers?: KnownFollowers; ··· 226 220 export interface SavedFeed { 227 221 $type?: "so.sprk.actor.defs#savedFeed"; 228 222 id: string; 229 - type: 230 - | "feed" 231 - | "list" 232 - | "timeline" 233 - | (string & globalThis.Record<PropertyKey, never>); 223 + type: "feed" | "timeline" | (string & globalThis.Record<PropertyKey, never>); 234 224 value: string; 235 225 pinned: boolean; 236 226 } ··· 447 437 | $Typed<SoSprkFeedThreadgate.MentionRule> 448 438 | $Typed<SoSprkFeedThreadgate.FollowerRule> 449 439 | $Typed<SoSprkFeedThreadgate.FollowingRule> 450 - | $Typed<SoSprkFeedThreadgate.ListRule> 451 440 | { $type: string } 452 441 )[]; 453 442 }
+2 -2
lex/types/so/sprk/embed/defs.ts lex/types/so/sprk/media/defs.ts
··· 5 5 import { is$typed as _is$typed } from "../../../../util.ts"; 6 6 7 7 const is$typed = _is$typed, validate = _validate; 8 - const id = "so.sprk.embed.defs"; 8 + const id = "so.sprk.media.defs"; 9 9 10 10 /** width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit. */ 11 11 export interface AspectRatio { 12 - $type?: "so.sprk.embed.defs#aspectRatio"; 12 + $type?: "so.sprk.media.defs#aspectRatio"; 13 13 width: number; 14 14 height: number; 15 15 }
-79
lex/types/so/sprk/embed/images.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { BlobRef } from "@atp/lexicon"; 5 - import { validate as _validate } from "../../../../lexicons.ts"; 6 - import { is$typed as _is$typed } from "../../../../util.ts"; 7 - import type * as SoSprkEmbedDefs from "./defs.ts"; 8 - 9 - const is$typed = _is$typed, validate = _validate; 10 - const id = "so.sprk.embed.images"; 11 - 12 - export interface Main { 13 - $type?: "so.sprk.embed.images"; 14 - images: (Image)[]; 15 - } 16 - 17 - const hashMain = "main"; 18 - 19 - export function isMain<V>(v: V) { 20 - return is$typed(v, id, hashMain); 21 - } 22 - 23 - export function validateMain<V>(v: V) { 24 - return validate<Main & V>(v, id, hashMain); 25 - } 26 - 27 - export interface Image { 28 - $type?: "so.sprk.embed.images#image"; 29 - image: BlobRef; 30 - /** Alt text description of the image, for accessibility. */ 31 - alt: string; 32 - aspectRatio?: SoSprkEmbedDefs.AspectRatio; 33 - } 34 - 35 - const hashImage = "image"; 36 - 37 - export function isImage<V>(v: V) { 38 - return is$typed(v, id, hashImage); 39 - } 40 - 41 - export function validateImage<V>(v: V) { 42 - return validate<Image & V>(v, id, hashImage); 43 - } 44 - 45 - export interface View { 46 - $type?: "so.sprk.embed.images#view"; 47 - images: (ViewImage)[]; 48 - } 49 - 50 - const hashView = "view"; 51 - 52 - export function isView<V>(v: V) { 53 - return is$typed(v, id, hashView); 54 - } 55 - 56 - export function validateView<V>(v: V) { 57 - return validate<View & V>(v, id, hashView); 58 - } 59 - 60 - export interface ViewImage { 61 - $type?: "so.sprk.embed.images#viewImage"; 62 - /** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. */ 63 - thumb: string; 64 - /** 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. */ 65 - fullsize: string; 66 - /** Alt text description of the image, for accessibility. */ 67 - alt: string; 68 - aspectRatio?: SoSprkEmbedDefs.AspectRatio; 69 - } 70 - 71 - const hashViewImage = "viewImage"; 72 - 73 - export function isViewImage<V>(v: V) { 74 - return is$typed(v, id, hashViewImage); 75 - } 76 - 77 - export function validateViewImage<V>(v: V) { 78 - return validate<ViewImage & V>(v, id, hashViewImage); 79 - }
+7 -7
lex/types/so/sprk/embed/video.ts lex/types/so/sprk/media/video.ts
··· 4 4 import { BlobRef } from "@atp/lexicon"; 5 5 import { validate as _validate } from "../../../../lexicons.ts"; 6 6 import { is$typed as _is$typed } from "../../../../util.ts"; 7 - import type * as SoSprkEmbedDefs from "./defs.ts"; 7 + import type * as SoSprkMediaDefs from "./defs.ts"; 8 8 9 9 const is$typed = _is$typed, validate = _validate; 10 - const id = "so.sprk.embed.video"; 10 + const id = "so.sprk.media.video"; 11 11 12 12 export interface Main { 13 - $type?: "so.sprk.embed.video"; 13 + $type?: "so.sprk.media.video"; 14 14 video: BlobRef; 15 15 captions?: (Caption)[]; 16 16 /** Alt text description of the video, for accessibility. */ 17 17 alt?: string; 18 - aspectRatio?: SoSprkEmbedDefs.AspectRatio; 18 + aspectRatio?: SoSprkMediaDefs.AspectRatio; 19 19 } 20 20 21 21 const hashMain = "main"; ··· 29 29 } 30 30 31 31 export interface Caption { 32 - $type?: "so.sprk.embed.video#caption"; 32 + $type?: "so.sprk.media.video#caption"; 33 33 lang: string; 34 34 file: BlobRef; 35 35 } ··· 45 45 } 46 46 47 47 export interface View { 48 - $type?: "so.sprk.embed.video#view"; 48 + $type?: "so.sprk.media.video#view"; 49 49 cid: string; 50 50 playlist: string; 51 51 thumbnail?: string; 52 52 alt?: string; 53 - aspectRatio?: SoSprkEmbedDefs.AspectRatio; 53 + aspectRatio?: SoSprkMediaDefs.AspectRatio; 54 54 } 55 55 56 56 const hashView = "view";
+33 -68
lex/types/so/sprk/feed/defs.ts
··· 5 5 import { is$typed as _is$typed } from "../../../../util.ts"; 6 6 import { type $Typed } from "../../../../util.ts"; 7 7 import type * as SoSprkActorDefs from "../actor/defs.ts"; 8 - import type * as SoSprkEmbedImages from "../embed/images.ts"; 9 - import type * as SoSprkEmbedVideo from "../embed/video.ts"; 8 + import type * as SoSprkMediaImages from "../media/images.ts"; 9 + import type * as SoSprkMediaVideo from "../media/video.ts"; 10 10 import type * as SoSprkSoundDefs from "../sound/defs.ts"; 11 11 import type * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs.ts"; 12 + import type * as SoSprkMediaImage from "../media/image.ts"; 12 13 import type * as SoSprkRichtextFacet from "../richtext/facet.ts"; 13 - import type * as SoSprkGraphDefs from "../graph/defs.ts"; 14 14 15 15 const is$typed = _is$typed, validate = _validate; 16 16 const id = "so.sprk.feed.defs"; 17 17 18 - export interface StoryView { 19 - $type?: "so.sprk.feed.defs#storyView"; 18 + export interface PostView { 19 + $type?: "so.sprk.feed.defs#postView"; 20 20 uri: string; 21 21 cid: string; 22 22 author: SoSprkActorDefs.ProfileViewBasic; 23 23 record: { [_ in string]: unknown }; 24 - media?: $Typed<SoSprkEmbedImages.View> | $Typed<SoSprkEmbedVideo.View> | { 24 + media?: $Typed<SoSprkMediaImages.View> | $Typed<SoSprkMediaVideo.View> | { 25 25 $type: string; 26 26 }; 27 + sound?: SoSprkSoundDefs.AudioView; 28 + replyCount?: number; 29 + repostCount?: number; 30 + likeCount?: number; 27 31 indexedAt: string; 32 + viewer?: ViewerState; 33 + labels?: (ComAtprotoLabelDefs.Label)[]; 34 + threadgate?: ThreadgateView; 28 35 } 29 36 30 - const hashStoryView = "storyView"; 37 + const hashPostView = "postView"; 31 38 32 - export function isStoryView<V>(v: V) { 33 - return is$typed(v, id, hashStoryView); 39 + export function isPostView<V>(v: V) { 40 + return is$typed(v, id, hashPostView); 34 41 } 35 42 36 - export function validateStoryView<V>(v: V) { 37 - return validate<StoryView & V>(v, id, hashStoryView); 43 + export function validatePostView<V>(v: V) { 44 + return validate<PostView & V>(v, id, hashPostView); 38 45 } 39 46 40 - export interface PostView { 41 - $type?: "so.sprk.feed.defs#postView"; 47 + export interface ReplyView { 48 + $type?: "so.sprk.feed.defs#replyView"; 42 49 uri: string; 43 50 cid: string; 44 51 author: SoSprkActorDefs.ProfileViewBasic; 45 52 record: { [_ in string]: unknown }; 46 - embed?: $Typed<SoSprkEmbedImages.View> | $Typed<SoSprkEmbedVideo.View> | { 47 - $type: string; 48 - }; 49 - sound?: SoSprkSoundDefs.AudioView; 53 + media?: $Typed<SoSprkMediaImage.View> | { $type: string }; 50 54 replyCount?: number; 51 - repostCount?: number; 52 55 likeCount?: number; 53 56 indexedAt: string; 54 57 viewer?: ViewerState; 55 58 labels?: (ComAtprotoLabelDefs.Label)[]; 56 - threadgate?: ThreadgateView; 57 59 } 58 60 59 - const hashPostView = "postView"; 61 + const hashReplyView = "replyView"; 60 62 61 - export function isPostView<V>(v: V) { 62 - return is$typed(v, id, hashPostView); 63 + export function isReplyView<V>(v: V) { 64 + return is$typed(v, id, hashReplyView); 63 65 } 64 66 65 - export function validatePostView<V>(v: V) { 66 - return validate<PostView & V>(v, id, hashPostView); 67 + export function validateReplyView<V>(v: V) { 68 + return validate<ReplyView & V>(v, id, hashReplyView); 67 69 } 68 70 69 71 /** Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests. */ ··· 106 108 export interface FeedViewPost { 107 109 $type?: "so.sprk.feed.defs#feedViewPost"; 108 110 post: PostView; 109 - reply?: ReplyRef; 110 111 reason?: $Typed<ReasonRepost> | $Typed<ReasonPin> | { $type: string }; 111 112 /** Context provided by feed generator that may be passed back alongside interactions. */ 112 113 feedContext?: string; ··· 122 123 return validate<FeedViewPost & V>(v, id, hashFeedViewPost); 123 124 } 124 125 125 - export interface FeedViewStory { 126 - $type?: "so.sprk.feed.defs#feedViewStory"; 127 - story: StoryView; 128 - } 129 - 130 - const hashFeedViewStory = "feedViewStory"; 131 - 132 - export function isFeedViewStory<V>(v: V) { 133 - return is$typed(v, id, hashFeedViewStory); 134 - } 135 - 136 - export function validateFeedViewStory<V>(v: V) { 137 - return validate<FeedViewStory & V>(v, id, hashFeedViewStory); 138 - } 139 - 140 - export interface StoriesByAuthor { 141 - $type?: "so.sprk.feed.defs#storiesByAuthor"; 142 - author: SoSprkActorDefs.ProfileViewBasic; 143 - stories: (StoryView)[]; 144 - } 145 - 146 - const hashStoriesByAuthor = "storiesByAuthor"; 147 - 148 - export function isStoriesByAuthor<V>(v: V) { 149 - return is$typed(v, id, hashStoriesByAuthor); 150 - } 151 - 152 - export function validateStoriesByAuthor<V>(v: V) { 153 - return validate<StoriesByAuthor & V>(v, id, hashStoriesByAuthor); 154 - } 155 - 156 126 export interface ReplyRef { 157 127 $type?: "so.sprk.feed.defs#replyRef"; 158 128 root: $Typed<PostView> | $Typed<NotFoundPost> | $Typed<BlockedPost> | { 159 129 $type: string; 160 130 }; 161 - parent: $Typed<PostView> | $Typed<NotFoundPost> | $Typed<BlockedPost> | { 162 - $type: string; 163 - }; 131 + parent: 132 + | $Typed<PostView> 133 + | $Typed<ReplyView> 134 + | $Typed<NotFoundPost> 135 + | $Typed<BlockedPost> 136 + | { $type: string }; 164 137 grandparentAuthor?: SoSprkActorDefs.ProfileViewBasic; 165 138 } 166 139 ··· 206 179 207 180 export interface ThreadViewPost { 208 181 $type?: "so.sprk.feed.defs#threadViewPost"; 209 - post: PostView; 182 + post: $Typed<PostView> | $Typed<ReplyView> | { $type: string }; 210 183 parent?: 211 184 | $Typed<ThreadViewPost> 212 185 | $Typed<NotFoundPost> ··· 374 347 uri?: string; 375 348 cid?: string; 376 349 record?: { [_ in string]: unknown }; 377 - lists?: (SoSprkGraphDefs.ListViewBasic)[]; 378 350 } 379 351 380 352 const hashThreadgateView = "threadgateView"; ··· 401 373 | "so.sprk.feed.defs#interactionLike" 402 374 | "so.sprk.feed.defs#interactionRepost" 403 375 | "so.sprk.feed.defs#interactionReply" 404 - | "so.sprk.feed.defs#interactionQuote" 405 376 | "so.sprk.feed.defs#interactionShare" 406 377 | (string & globalThis.Record<PropertyKey, never>); 407 378 /** Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton. */ ··· 430 401 export const CLICKTHROUGHREPOSTER = `${id}#clickthroughReposter`; 431 402 /** User clicked through to the embedded content of the feed item */ 432 403 export const CLICKTHROUGHEMBED = `${id}#clickthroughEmbed`; 433 - /** Declares the feed generator returns any types of posts. */ 434 - export const CONTENTMODEUNSPECIFIED = `${id}#contentModeUnspecified`; 435 - /** Declares the feed generator returns posts containing so.sprk.embed.video embeds. */ 436 - export const CONTENTMODEVIDEO = `${id}#contentModeVideo`; 437 404 /** Feed item was seen by user */ 438 405 export const INTERACTIONSEEN = `${id}#interactionSeen`; 439 406 /** User liked the feed item */ ··· 442 409 export const INTERACTIONREPOST = `${id}#interactionRepost`; 443 410 /** User replied to the feed item */ 444 411 export const INTERACTIONREPLY = `${id}#interactionReply`; 445 - /** User quoted the feed item */ 446 - export const INTERACTIONQUOTE = `${id}#interactionQuote`; 447 412 /** User shared the feed item */ 448 413 export const INTERACTIONSHARE = `${id}#interactionShare`;
-33
lex/types/so/sprk/feed/getListFeed.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkFeedDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** Reference (AT-URI) to the list record. */ 8 - list: string; 9 - limit: number; 10 - cursor?: string; 11 - }; 12 - export type InputSchema = undefined; 13 - 14 - export interface OutputSchema { 15 - cursor?: string; 16 - feed: (SoSprkFeedDefs.FeedViewPost)[]; 17 - } 18 - 19 - export type HandlerInput = void; 20 - 21 - export interface HandlerSuccess { 22 - encoding: "application/json"; 23 - body: OutputSchema; 24 - headers?: { [key: string]: string }; 25 - } 26 - 27 - export interface HandlerError { 28 - status: number; 29 - message?: string; 30 - error?: "UnknownList"; 31 - } 32 - 33 - export type HandlerOutput = HandlerError | HandlerSuccess;
+42 -7
lex/types/so/sprk/feed/getPostThread.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 + import { validate as _validate } from "../../../../lexicons.ts"; 5 + import { is$typed as _is$typed } from "../../../../util.ts"; 4 6 import { type $Typed } from "../../../../util.ts"; 5 7 import type * as SoSprkFeedDefs from "./defs.ts"; 6 8 9 + const is$typed = _is$typed, validate = _validate; 10 + const id = "so.sprk.feed.getPostThread"; 11 + 7 12 export type QueryParams = { 8 - /** Reference (AT-URI) to post record. */ 9 - uri: string; 13 + /** Reference (AT-URI) to anchor post record. */ 14 + anchor: string; 15 + limit: number; 16 + cursor?: string; 10 17 /** How many levels of reply depth should be included in response. */ 11 18 depth: number; 12 19 /** How many levels of parent (and grandparent, etc) post to include. */ 13 20 parentHeight: number; 21 + /** Whether to prioritize posts from followed users. It only has effect when the user is authenticated. */ 22 + prioritizeFollowedUsers: boolean; 23 + /** Sorting for the thread replies. */ 24 + sort: 25 + | "newest" 26 + | "oldest" 27 + | "top" 28 + | (string & globalThis.Record<PropertyKey, never>); 14 29 }; 15 30 export type InputSchema = undefined; 16 31 17 32 export interface OutputSchema { 18 - thread: 19 - | $Typed<SoSprkFeedDefs.ThreadViewPost> 20 - | $Typed<SoSprkFeedDefs.NotFoundPost> 21 - | $Typed<SoSprkFeedDefs.BlockedPost> 22 - | { $type: string }; 33 + cursor?: string; 34 + /** A flat list of thread items. The depth of each item is indicated by the depth property inside the item. */ 35 + thread: (ThreadItem)[]; 23 36 threadgate?: SoSprkFeedDefs.ThreadgateView; 24 37 } 25 38 ··· 38 51 } 39 52 40 53 export type HandlerOutput = HandlerError | HandlerSuccess; 54 + 55 + export interface ThreadItem { 56 + $type?: "so.sprk.feed.getPostThread#threadItem"; 57 + uri: string; 58 + /** The nesting level of this item in the thread. Depth 0 means the anchor item. */ 59 + depth: number; 60 + value: 61 + | $Typed<SoSprkFeedDefs.ThreadViewPost> 62 + | $Typed<SoSprkFeedDefs.NotFoundPost> 63 + | $Typed<SoSprkFeedDefs.BlockedPost> 64 + | { $type: string }; 65 + } 66 + 67 + const hashThreadItem = "threadItem"; 68 + 69 + export function isThreadItem<V>(v: V) { 70 + return is$typed(v, id, hashThreadItem); 71 + } 72 + 73 + export function validateThreadItem<V>(v: V) { 74 + return validate<ThreadItem & V>(v, id, hashThreadItem); 75 + }
-36
lex/types/so/sprk/feed/getQuotes.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkFeedDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** Reference (AT-URI) of post record */ 8 - uri: string; 9 - /** If supplied, filters to quotes of specific version (by CID) of the post record. */ 10 - cid?: string; 11 - limit: number; 12 - cursor?: string; 13 - }; 14 - export type InputSchema = undefined; 15 - 16 - export interface OutputSchema { 17 - uri: string; 18 - cid?: string; 19 - cursor?: string; 20 - posts: (SoSprkFeedDefs.PostView)[]; 21 - } 22 - 23 - export type HandlerInput = void; 24 - 25 - export interface HandlerSuccess { 26 - encoding: "application/json"; 27 - body: OutputSchema; 28 - headers?: { [key: string]: string }; 29 - } 30 - 31 - export interface HandlerError { 32 - status: number; 33 - message?: string; 34 - } 35 - 36 - export type HandlerOutput = HandlerError | HandlerSuccess;
+2 -2
lex/types/so/sprk/feed/getStories.ts lex/types/so/sprk/story/getStories.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import type * as SoSprkFeedDefs from "./defs.ts"; 4 + import type * as SoSprkStoryDefs from "./defs.ts"; 5 5 6 6 export type QueryParams = { 7 7 /** List of story AT-URIs to return hydrated views for. */ ··· 10 10 export type InputSchema = undefined; 11 11 12 12 export interface OutputSchema { 13 - stories: (SoSprkFeedDefs.StoryView)[]; 13 + stories: (SoSprkStoryDefs.StoryView)[]; 14 14 } 15 15 16 16 export type HandlerInput = void;
-30
lex/types/so/sprk/feed/getStoriesTimeline.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkFeedDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - limit: number; 8 - cursor?: string; 9 - }; 10 - export type InputSchema = undefined; 11 - 12 - export interface OutputSchema { 13 - cursor?: string; 14 - storiesByAuthor: (SoSprkFeedDefs.StoriesByAuthor)[]; 15 - } 16 - 17 - export type HandlerInput = void; 18 - 19 - export interface HandlerSuccess { 20 - encoding: "application/json"; 21 - body: OutputSchema; 22 - headers?: { [key: string]: string }; 23 - } 24 - 25 - export interface HandlerError { 26 - status: number; 27 - message?: string; 28 - } 29 - 30 - export type HandlerOutput = HandlerError | HandlerSuccess;
-49
lex/types/so/sprk/feed/music.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { BlobRef } from "@atp/lexicon"; 5 - import { validate as _validate } from "../../../../lexicons.ts"; 6 - import { is$typed as _is$typed } from "../../../../util.ts"; 7 - import { type $Typed } from "../../../../util.ts"; 8 - import type * as SoSprkRichtextFacet from "../richtext/facet.ts"; 9 - import type * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs.ts"; 10 - 11 - const is$typed = _is$typed, validate = _validate; 12 - const id = "so.sprk.feed.music"; 13 - 14 - export interface Record { 15 - $type: "so.sprk.feed.music"; 16 - sound: BlobRef; 17 - /** The music's title. */ 18 - title: string; 19 - releaseDate: string; 20 - /** The music's album name. */ 21 - album?: string; 22 - /** The music's record label. */ 23 - recordLabel?: string; 24 - /** Image to be displayed in music's page. AKA, 'cover image' */ 25 - cover?: BlobRef; 26 - /** The music's author. */ 27 - author: string; 28 - /** The music's description. */ 29 - text?: string; 30 - copyright?: (string)[]; 31 - /** Annotations of text (mentions, URLs, hashtags, etc) */ 32 - facets?: (SoSprkRichtextFacet.Main)[]; 33 - labels?: $Typed<ComAtprotoLabelDefs.SelfLabels> | { $type: string }; 34 - /** The music's Hashtags */ 35 - tags?: (string)[]; 36 - /** Client-declared timestamp when this post was originally created. */ 37 - createdAt: string; 38 - [k: string]: unknown; 39 - } 40 - 41 - const hashRecord = "main"; 42 - 43 - export function isRecord<V>(v: V) { 44 - return is$typed(v, id, hashRecord); 45 - } 46 - 47 - export function validateRecord<V>(v: V) { 48 - return validate<Record & V>(v, id, hashRecord, true); 49 - }
+16 -18
lex/types/so/sprk/feed/post.ts
··· 4 4 import { validate as _validate } from "../../../../lexicons.ts"; 5 5 import { is$typed as _is$typed } from "../../../../util.ts"; 6 6 import { type $Typed } from "../../../../util.ts"; 7 - import type * as SoSprkRichtextFacet from "../richtext/facet.ts"; 8 - import type * as SoSprkEmbedImages from "../embed/images.ts"; 9 - import type * as SoSprkEmbedVideo from "../embed/video.ts"; 7 + import type * as SoSprkMediaImages from "../media/images.ts"; 8 + import type * as SoSprkMediaVideo from "../media/video.ts"; 10 9 import type * as ComAtprotoRepoStrongRef from "../../../com/atproto/repo/strongRef.ts"; 11 10 import type * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs.ts"; 11 + import type * as SoSprkRichtextFacet from "../richtext/facet.ts"; 12 12 13 13 const is$typed = _is$typed, validate = _validate; 14 14 const id = "so.sprk.feed.post"; 15 15 16 16 export interface Record { 17 17 $type: "so.sprk.feed.post"; 18 - /** The post description. */ 19 - text?: string; 20 - /** Annotations of text (mentions, URLs, hashtags, etc) */ 21 - facets?: (SoSprkRichtextFacet.Main)[]; 22 - reply?: ReplyRef; 23 - embed?: $Typed<SoSprkEmbedImages.Main> | $Typed<SoSprkEmbedVideo.Main> | { 18 + caption?: CaptionRef; 19 + media: $Typed<SoSprkMediaImages.Main> | $Typed<SoSprkMediaVideo.Main> | { 24 20 $type: string; 25 21 }; 26 22 sound?: ComAtprotoRepoStrongRef.Main; ··· 44 40 return validate<Record & V>(v, id, hashRecord, true); 45 41 } 46 42 47 - export interface ReplyRef { 48 - $type?: "so.sprk.feed.post#replyRef"; 49 - root: ComAtprotoRepoStrongRef.Main; 50 - parent: ComAtprotoRepoStrongRef.Main; 43 + export interface CaptionRef { 44 + $type?: "so.sprk.feed.post#captionRef"; 45 + /** The post description. */ 46 + text?: string; 47 + /** Annotations of text (mentions, URLs, hashtags, etc) */ 48 + facets?: (SoSprkRichtextFacet.Main)[]; 51 49 } 52 50 53 - const hashReplyRef = "replyRef"; 51 + const hashCaptionRef = "captionRef"; 54 52 55 - export function isReplyRef<V>(v: V) { 56 - return is$typed(v, id, hashReplyRef); 53 + export function isCaptionRef<V>(v: V) { 54 + return is$typed(v, id, hashCaptionRef); 57 55 } 58 56 59 - export function validateReplyRef<V>(v: V) { 60 - return validate<ReplyRef & V>(v, id, hashReplyRef); 57 + export function validateCaptionRef<V>(v: V) { 58 + return validate<CaptionRef & V>(v, id, hashCaptionRef); 61 59 }
+55
lex/types/so/sprk/feed/reply.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { validate as _validate } from "../../../../lexicons.ts"; 5 + import { is$typed as _is$typed } from "../../../../util.ts"; 6 + import { type $Typed } from "../../../../util.ts"; 7 + import type * as SoSprkRichtextFacet from "../richtext/facet.ts"; 8 + import type * as SoSprkMediaImage from "../media/image.ts"; 9 + import type * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs.ts"; 10 + import type * as ComAtprotoRepoStrongRef from "../../../com/atproto/repo/strongRef.ts"; 11 + 12 + const is$typed = _is$typed, validate = _validate; 13 + const id = "so.sprk.feed.reply"; 14 + 15 + export interface Record { 16 + $type: "so.sprk.feed.reply"; 17 + /** The reply text. */ 18 + text?: string; 19 + /** Annotations of text (mentions, URLs, hashtags, etc) */ 20 + facets?: (SoSprkRichtextFacet.Main)[]; 21 + reply: ReplyRef; 22 + media?: $Typed<SoSprkMediaImage.Main> | { $type: string }; 23 + /** Indicates human language of post primary text content. */ 24 + langs?: (string)[]; 25 + labels?: $Typed<ComAtprotoLabelDefs.SelfLabels> | { $type: string }; 26 + /** Client-declared timestamp when this post was originally created. */ 27 + createdAt: string; 28 + [k: string]: unknown; 29 + } 30 + 31 + const hashRecord = "main"; 32 + 33 + export function isRecord<V>(v: V) { 34 + return is$typed(v, id, hashRecord); 35 + } 36 + 37 + export function validateRecord<V>(v: V) { 38 + return validate<Record & V>(v, id, hashRecord, true); 39 + } 40 + 41 + export interface ReplyRef { 42 + $type?: "so.sprk.feed.reply#replyRef"; 43 + root: ComAtprotoRepoStrongRef.Main; 44 + parent: ComAtprotoRepoStrongRef.Main; 45 + } 46 + 47 + const hashReplyRef = "replyRef"; 48 + 49 + export function isReplyRef<V>(v: V) { 50 + return is$typed(v, id, hashReplyRef); 51 + } 52 + 53 + export function validateReplyRef<V>(v: V) { 54 + return validate<ReplyRef & V>(v, id, hashReplyRef); 55 + }
+5 -7
lex/types/so/sprk/feed/story.ts lex/types/so/sprk/story/post.ts
··· 4 4 import { validate as _validate } from "../../../../lexicons.ts"; 5 5 import { is$typed as _is$typed } from "../../../../util.ts"; 6 6 import { type $Typed } from "../../../../util.ts"; 7 - import type * as SoSprkEmbedImages from "../embed/images.ts"; 8 - import type * as SoSprkEmbedVideo from "../embed/video.ts"; 7 + import type * as SoSprkMediaImage from "../media/image.ts"; 8 + import type * as SoSprkMediaVideo from "../media/video.ts"; 9 9 import type * as ComAtprotoRepoStrongRef from "../../../com/atproto/repo/strongRef.ts"; 10 10 import type * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs.ts"; 11 11 12 12 const is$typed = _is$typed, validate = _validate; 13 - const id = "so.sprk.feed.story"; 13 + const id = "so.sprk.story.post"; 14 14 15 15 export interface Record { 16 - $type: "so.sprk.feed.story"; 17 - media: $Typed<SoSprkEmbedImages.Main> | $Typed<SoSprkEmbedVideo.Main> | { 16 + $type: "so.sprk.story.post"; 17 + media: $Typed<SoSprkMediaImage.Main> | $Typed<SoSprkMediaVideo.Main> | { 18 18 $type: string; 19 19 }; 20 20 sound?: ComAtprotoRepoStrongRef.Main; 21 21 labels?: $Typed<ComAtprotoLabelDefs.SelfLabels> | { $type: string }; 22 - /** Additional hashtags, in addition to any included in story text and facets. */ 23 - tags?: (string)[]; 24 22 /** Client-declared timestamp when this story was originally created. */ 25 23 createdAt: string; 26 24 [k: string]: unknown;
+4 -23
lex/types/so/sprk/feed/threadgate.ts
··· 13 13 /** Reference (AT-URI) to the post record. */ 14 14 post: string; 15 15 /** 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. */ 16 - allow?: ( 17 - | $Typed<MentionRule> 18 - | $Typed<FollowerRule> 19 - | $Typed<FollowingRule> 20 - | $Typed<ListRule> 21 - | { $type: string } 22 - )[]; 16 + allow?: 17 + ($Typed<MentionRule> | $Typed<FollowerRule> | $Typed<FollowingRule> | { 18 + $type: string; 19 + })[]; 23 20 createdAt: string; 24 21 /** List of hidden reply URIs. */ 25 22 hiddenReplies?: (string)[]; ··· 80 77 export function validateFollowingRule<V>(v: V) { 81 78 return validate<FollowingRule & V>(v, id, hashFollowingRule); 82 79 } 83 - 84 - /** Allow replies from actors on a list. */ 85 - export interface ListRule { 86 - $type?: "so.sprk.feed.threadgate#listRule"; 87 - list: string; 88 - } 89 - 90 - const hashListRule = "listRule"; 91 - 92 - export function isListRule<V>(v: V) { 93 - return is$typed(v, id, hashListRule); 94 - } 95 - 96 - export function validateListRule<V>(v: V) { 97 - return validate<ListRule & V>(v, id, hashListRule); 98 - }
-146
lex/types/so/sprk/graph/defs.ts
··· 3 3 */ 4 4 import { validate as _validate } from "../../../../lexicons.ts"; 5 5 import { is$typed as _is$typed } from "../../../../util.ts"; 6 - import type * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs.ts"; 7 - import type * as SoSprkActorDefs from "../actor/defs.ts"; 8 - import type * as SoSprkRichtextFacet from "../richtext/facet.ts"; 9 - import type * as SoSprkFeedDefs from "../feed/defs.ts"; 10 6 11 7 const is$typed = _is$typed, validate = _validate; 12 8 const id = "so.sprk.graph.defs"; 13 - 14 - export interface ListViewBasic { 15 - $type?: "so.sprk.graph.defs#listViewBasic"; 16 - uri: string; 17 - cid: string; 18 - name: string; 19 - purpose: ListPurpose; 20 - avatar?: string; 21 - listItemCount?: number; 22 - labels?: (ComAtprotoLabelDefs.Label)[]; 23 - viewer?: ListViewerState; 24 - indexedAt?: string; 25 - } 26 - 27 - const hashListViewBasic = "listViewBasic"; 28 - 29 - export function isListViewBasic<V>(v: V) { 30 - return is$typed(v, id, hashListViewBasic); 31 - } 32 - 33 - export function validateListViewBasic<V>(v: V) { 34 - return validate<ListViewBasic & V>(v, id, hashListViewBasic); 35 - } 36 - 37 - export interface ListView { 38 - $type?: "so.sprk.graph.defs#listView"; 39 - uri: string; 40 - cid: string; 41 - creator: SoSprkActorDefs.ProfileView; 42 - name: string; 43 - purpose: ListPurpose; 44 - description?: string; 45 - descriptionFacets?: (SoSprkRichtextFacet.Main)[]; 46 - avatar?: string; 47 - listItemCount?: number; 48 - labels?: (ComAtprotoLabelDefs.Label)[]; 49 - viewer?: ListViewerState; 50 - indexedAt: string; 51 - } 52 - 53 - const hashListView = "listView"; 54 - 55 - export function isListView<V>(v: V) { 56 - return is$typed(v, id, hashListView); 57 - } 58 - 59 - export function validateListView<V>(v: V) { 60 - return validate<ListView & V>(v, id, hashListView); 61 - } 62 - 63 - export interface ListItemView { 64 - $type?: "so.sprk.graph.defs#listItemView"; 65 - uri: string; 66 - subject: SoSprkActorDefs.ProfileView; 67 - } 68 - 69 - const hashListItemView = "listItemView"; 70 - 71 - export function isListItemView<V>(v: V) { 72 - return is$typed(v, id, hashListItemView); 73 - } 74 - 75 - export function validateListItemView<V>(v: V) { 76 - return validate<ListItemView & V>(v, id, hashListItemView); 77 - } 78 - 79 - export interface StarterPackView { 80 - $type?: "so.sprk.graph.defs#starterPackView"; 81 - uri: string; 82 - cid: string; 83 - record: { [_ in string]: unknown }; 84 - creator: SoSprkActorDefs.ProfileViewBasic; 85 - list?: ListViewBasic; 86 - listItemsSample?: (ListItemView)[]; 87 - feeds?: (SoSprkFeedDefs.GeneratorView)[]; 88 - joinedWeekCount?: number; 89 - joinedAllTimeCount?: number; 90 - labels?: (ComAtprotoLabelDefs.Label)[]; 91 - indexedAt: string; 92 - } 93 - 94 - const hashStarterPackView = "starterPackView"; 95 - 96 - export function isStarterPackView<V>(v: V) { 97 - return is$typed(v, id, hashStarterPackView); 98 - } 99 - 100 - export function validateStarterPackView<V>(v: V) { 101 - return validate<StarterPackView & V>(v, id, hashStarterPackView); 102 - } 103 - 104 - export interface StarterPackViewBasic { 105 - $type?: "so.sprk.graph.defs#starterPackViewBasic"; 106 - uri: string; 107 - cid: string; 108 - record: { [_ in string]: unknown }; 109 - creator: SoSprkActorDefs.ProfileViewBasic; 110 - listItemCount?: number; 111 - joinedWeekCount?: number; 112 - joinedAllTimeCount?: number; 113 - labels?: (ComAtprotoLabelDefs.Label)[]; 114 - indexedAt: string; 115 - } 116 - 117 - const hashStarterPackViewBasic = "starterPackViewBasic"; 118 - 119 - export function isStarterPackViewBasic<V>(v: V) { 120 - return is$typed(v, id, hashStarterPackViewBasic); 121 - } 122 - 123 - export function validateStarterPackViewBasic<V>(v: V) { 124 - return validate<StarterPackViewBasic & V>(v, id, hashStarterPackViewBasic); 125 - } 126 - 127 - export type ListPurpose = 128 - | "so.sprk.graph.defs#modlist" 129 - | "so.sprk.graph.defs#curatelist" 130 - | "so.sprk.graph.defs#referencelist" 131 - | (string & globalThis.Record<PropertyKey, never>); 132 - 133 - /** A list of actors to apply an aggregate moderation action (mute/block) on. */ 134 - export const MODLIST = `${id}#modlist`; 135 - /** A list of actors used for curation purposes such as list feeds or interaction gating. */ 136 - export const CURATELIST = `${id}#curatelist`; 137 - /** A list of actors used for only for reference purposes such as within a starter pack. */ 138 - export const REFERENCELIST = `${id}#referencelist`; 139 - 140 - export interface ListViewerState { 141 - $type?: "so.sprk.graph.defs#listViewerState"; 142 - muted?: boolean; 143 - blocked?: string; 144 - } 145 - 146 - const hashListViewerState = "listViewerState"; 147 - 148 - export function isListViewerState<V>(v: V) { 149 - return is$typed(v, id, hashListViewerState); 150 - } 151 - 152 - export function validateListViewerState<V>(v: V) { 153 - return validate<ListViewerState & V>(v, id, hashListViewerState); 154 - } 155 9 156 10 /** indicates that a handle or DID could not be resolved */ 157 11 export interface NotFoundActor {
-31
lex/types/so/sprk/graph/getActorStarterPacks.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkGraphDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - actor: string; 8 - limit: number; 9 - cursor?: string; 10 - }; 11 - export type InputSchema = undefined; 12 - 13 - export interface OutputSchema { 14 - cursor?: string; 15 - starterPacks: (SoSprkGraphDefs.StarterPackViewBasic)[]; 16 - } 17 - 18 - export type HandlerInput = void; 19 - 20 - export interface HandlerSuccess { 21 - encoding: "application/json"; 22 - body: OutputSchema; 23 - headers?: { [key: string]: string }; 24 - } 25 - 26 - export interface HandlerError { 27 - status: number; 28 - message?: string; 29 - } 30 - 31 - export type HandlerOutput = HandlerError | HandlerSuccess;
-33
lex/types/so/sprk/graph/getList.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkGraphDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** Reference (AT-URI) of the list record to hydrate. */ 8 - list: string; 9 - limit: number; 10 - cursor?: string; 11 - }; 12 - export type InputSchema = undefined; 13 - 14 - export interface OutputSchema { 15 - cursor?: string; 16 - list: SoSprkGraphDefs.ListView; 17 - items: (SoSprkGraphDefs.ListItemView)[]; 18 - } 19 - 20 - export type HandlerInput = void; 21 - 22 - export interface HandlerSuccess { 23 - encoding: "application/json"; 24 - body: OutputSchema; 25 - headers?: { [key: string]: string }; 26 - } 27 - 28 - export interface HandlerError { 29 - status: number; 30 - message?: string; 31 - } 32 - 33 - export type HandlerOutput = HandlerError | HandlerSuccess;
-30
lex/types/so/sprk/graph/getListBlocks.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkGraphDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - limit: number; 8 - cursor?: string; 9 - }; 10 - export type InputSchema = undefined; 11 - 12 - export interface OutputSchema { 13 - cursor?: string; 14 - lists: (SoSprkGraphDefs.ListView)[]; 15 - } 16 - 17 - export type HandlerInput = void; 18 - 19 - export interface HandlerSuccess { 20 - encoding: "application/json"; 21 - body: OutputSchema; 22 - headers?: { [key: string]: string }; 23 - } 24 - 25 - export interface HandlerError { 26 - status: number; 27 - message?: string; 28 - } 29 - 30 - export type HandlerOutput = HandlerError | HandlerSuccess;
+2 -2
lex/types/so/sprk/graph/getListMutes.ts lex/types/so/sprk/story/getTimeline.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import type * as SoSprkGraphDefs from "./defs.ts"; 4 + import type * as SoSprkStoryDefs from "./defs.ts"; 5 5 6 6 export type QueryParams = { 7 7 limit: number; ··· 11 11 12 12 export interface OutputSchema { 13 13 cursor?: string; 14 - lists: (SoSprkGraphDefs.ListView)[]; 14 + storiesByAuthor: (SoSprkStoryDefs.StoriesByAuthor)[]; 15 15 } 16 16 17 17 export type HandlerInput = void;
-32
lex/types/so/sprk/graph/getLists.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkGraphDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** The account (actor) to enumerate lists from. */ 8 - actor: string; 9 - limit: number; 10 - cursor?: string; 11 - }; 12 - export type InputSchema = undefined; 13 - 14 - export interface OutputSchema { 15 - cursor?: string; 16 - lists: (SoSprkGraphDefs.ListView)[]; 17 - } 18 - 19 - export type HandlerInput = void; 20 - 21 - export interface HandlerSuccess { 22 - encoding: "application/json"; 23 - body: OutputSchema; 24 - headers?: { [key: string]: string }; 25 - } 26 - 27 - export interface HandlerError { 28 - status: number; 29 - message?: string; 30 - } 31 - 32 - export type HandlerOutput = HandlerError | HandlerSuccess;
-29
lex/types/so/sprk/graph/getStarterPack.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkGraphDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** Reference (AT-URI) of the starter pack record. */ 8 - starterPack: string; 9 - }; 10 - export type InputSchema = undefined; 11 - 12 - export interface OutputSchema { 13 - starterPack: SoSprkGraphDefs.StarterPackView; 14 - } 15 - 16 - export type HandlerInput = void; 17 - 18 - export interface HandlerSuccess { 19 - encoding: "application/json"; 20 - body: OutputSchema; 21 - headers?: { [key: string]: string }; 22 - } 23 - 24 - export interface HandlerError { 25 - status: number; 26 - message?: string; 27 - } 28 - 29 - export type HandlerOutput = HandlerError | HandlerSuccess;
-28
lex/types/so/sprk/graph/getStarterPacks.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkGraphDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - uris: string[]; 8 - }; 9 - export type InputSchema = undefined; 10 - 11 - export interface OutputSchema { 12 - starterPacks: (SoSprkGraphDefs.StarterPackViewBasic)[]; 13 - } 14 - 15 - export type HandlerInput = void; 16 - 17 - export interface HandlerSuccess { 18 - encoding: "application/json"; 19 - body: OutputSchema; 20 - headers?: { [key: string]: string }; 21 - } 22 - 23 - export interface HandlerError { 24 - status: number; 25 - message?: string; 26 - } 27 - 28 - export type HandlerOutput = HandlerError | HandlerSuccess;
-36
lex/types/so/sprk/graph/list.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { BlobRef } from "@atp/lexicon"; 5 - import { validate as _validate } from "../../../../lexicons.ts"; 6 - import { is$typed as _is$typed } from "../../../../util.ts"; 7 - import { type $Typed } from "../../../../util.ts"; 8 - import type * as SoSprkGraphDefs from "./defs.ts"; 9 - import type * as SoSprkRichtextFacet from "../richtext/facet.ts"; 10 - import type * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs.ts"; 11 - 12 - const is$typed = _is$typed, validate = _validate; 13 - const id = "so.sprk.graph.list"; 14 - 15 - export interface Record { 16 - $type: "so.sprk.graph.list"; 17 - purpose: SoSprkGraphDefs.ListPurpose; 18 - /** Display name for list; can not be empty. */ 19 - name: string; 20 - description?: string; 21 - descriptionFacets?: (SoSprkRichtextFacet.Main)[]; 22 - avatar?: BlobRef; 23 - labels?: $Typed<ComAtprotoLabelDefs.SelfLabels> | { $type: string }; 24 - createdAt: string; 25 - [k: string]: unknown; 26 - } 27 - 28 - const hashRecord = "main"; 29 - 30 - export function isRecord<V>(v: V) { 31 - return is$typed(v, id, hashRecord); 32 - } 33 - 34 - export function validateRecord<V>(v: V) { 35 - return validate<Record & V>(v, id, hashRecord, true); 36 - }
-26
lex/types/so/sprk/graph/listblock.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { validate as _validate } from "../../../../lexicons.ts"; 5 - import { is$typed as _is$typed } from "../../../../util.ts"; 6 - 7 - const is$typed = _is$typed, validate = _validate; 8 - const id = "so.sprk.graph.listblock"; 9 - 10 - export interface Record { 11 - $type: "so.sprk.graph.listblock"; 12 - /** Reference (AT-URI) to the mod list record. */ 13 - subject: string; 14 - createdAt: string; 15 - [k: string]: unknown; 16 - } 17 - 18 - const hashRecord = "main"; 19 - 20 - export function isRecord<V>(v: V) { 21 - return is$typed(v, id, hashRecord); 22 - } 23 - 24 - export function validateRecord<V>(v: V) { 25 - return validate<Record & V>(v, id, hashRecord, true); 26 - }
-28
lex/types/so/sprk/graph/listitem.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { validate as _validate } from "../../../../lexicons.ts"; 5 - import { is$typed as _is$typed } from "../../../../util.ts"; 6 - 7 - const is$typed = _is$typed, validate = _validate; 8 - const id = "so.sprk.graph.listitem"; 9 - 10 - export interface Record { 11 - $type: "so.sprk.graph.listitem"; 12 - /** The account which is included on the list. */ 13 - subject: string; 14 - /** Reference (AT-URI) to the list record (so.sprk.graph.list). */ 15 - list: string; 16 - createdAt: string; 17 - [k: string]: unknown; 18 - } 19 - 20 - const hashRecord = "main"; 21 - 22 - export function isRecord<V>(v: V) { 23 - return is$typed(v, id, hashRecord); 24 - } 25 - 26 - export function validateRecord<V>(v: V) { 27 - return validate<Record & V>(v, id, hashRecord, true); 28 - }
-20
lex/types/so/sprk/graph/muteActorList.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - export type QueryParams = globalThis.Record<PropertyKey, never>; 5 - 6 - export interface InputSchema { 7 - list: string; 8 - } 9 - 10 - export interface HandlerInput { 11 - encoding: "application/json"; 12 - body: InputSchema; 13 - } 14 - 15 - export interface HandlerError { 16 - status: number; 17 - message?: string; 18 - } 19 - 20 - export type HandlerOutput = HandlerError | void;
-32
lex/types/so/sprk/graph/searchStarterPacks.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkGraphDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ 8 - q: string; 9 - limit: number; 10 - cursor?: string; 11 - }; 12 - export type InputSchema = undefined; 13 - 14 - export interface OutputSchema { 15 - cursor?: string; 16 - starterPacks: (SoSprkGraphDefs.StarterPackViewBasic)[]; 17 - } 18 - 19 - export type HandlerInput = void; 20 - 21 - export interface HandlerSuccess { 22 - encoding: "application/json"; 23 - body: OutputSchema; 24 - headers?: { [key: string]: string }; 25 - } 26 - 27 - export interface HandlerError { 28 - status: number; 29 - message?: string; 30 - } 31 - 32 - export type HandlerOutput = HandlerError | HandlerSuccess;
-47
lex/types/so/sprk/graph/starterpack.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { validate as _validate } from "../../../../lexicons.ts"; 5 - import { is$typed as _is$typed } from "../../../../util.ts"; 6 - import type * as SoSprkRichtextFacet from "../richtext/facet.ts"; 7 - 8 - const is$typed = _is$typed, validate = _validate; 9 - const id = "so.sprk.graph.starterpack"; 10 - 11 - export interface Record { 12 - $type: "so.sprk.graph.starterpack"; 13 - /** Display name for starter pack; can not be empty. */ 14 - name: string; 15 - description?: string; 16 - descriptionFacets?: (SoSprkRichtextFacet.Main)[]; 17 - /** Reference (AT-URI) to the list record. */ 18 - list: string; 19 - feeds?: (FeedItem)[]; 20 - createdAt: string; 21 - [k: string]: unknown; 22 - } 23 - 24 - const hashRecord = "main"; 25 - 26 - export function isRecord<V>(v: V) { 27 - return is$typed(v, id, hashRecord); 28 - } 29 - 30 - export function validateRecord<V>(v: V) { 31 - return validate<Record & V>(v, id, hashRecord, true); 32 - } 33 - 34 - export interface FeedItem { 35 - $type?: "so.sprk.graph.starterpack#feedItem"; 36 - uri: string; 37 - } 38 - 39 - const hashFeedItem = "feedItem"; 40 - 41 - export function isFeedItem<V>(v: V) { 42 - return is$typed(v, id, hashFeedItem); 43 - } 44 - 45 - export function validateFeedItem<V>(v: V) { 46 - return validate<FeedItem & V>(v, id, hashFeedItem); 47 - }
-20
lex/types/so/sprk/graph/unmuteActorList.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - export type QueryParams = globalThis.Record<PropertyKey, never>; 5 - 6 - export interface InputSchema { 7 - list: string; 8 - } 9 - 10 - export interface HandlerInput { 11 - encoding: "application/json"; 12 - body: InputSchema; 13 - } 14 - 15 - export interface HandlerError { 16 - status: number; 17 - message?: string; 18 - } 19 - 20 - export type HandlerOutput = HandlerError | void;
+49
lex/types/so/sprk/media/image.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef } from "@atp/lexicon"; 5 + import { validate as _validate } from "../../../../lexicons.ts"; 6 + import { is$typed as _is$typed } from "../../../../util.ts"; 7 + import type * as SoSprkMediaDefs from "./defs.ts"; 8 + 9 + const is$typed = _is$typed, validate = _validate; 10 + const id = "so.sprk.media.image"; 11 + 12 + export interface Main { 13 + $type?: "so.sprk.media.image"; 14 + image: BlobRef; 15 + /** Alt text description of the image, for accessibility. */ 16 + alt: string; 17 + aspectRatio?: SoSprkMediaDefs.AspectRatio; 18 + } 19 + 20 + const hashMain = "main"; 21 + 22 + export function isMain<V>(v: V) { 23 + return is$typed(v, id, hashMain); 24 + } 25 + 26 + export function validateMain<V>(v: V) { 27 + return validate<Main & V>(v, id, hashMain); 28 + } 29 + 30 + export interface View { 31 + $type?: "so.sprk.media.image#view"; 32 + /** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. */ 33 + thumb: string; 34 + /** 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. */ 35 + fullsize: string; 36 + /** Alt text description of the image, for accessibility. */ 37 + alt: string; 38 + aspectRatio?: SoSprkMediaDefs.AspectRatio; 39 + } 40 + 41 + const hashView = "view"; 42 + 43 + export function isView<V>(v: V) { 44 + return is$typed(v, id, hashView); 45 + } 46 + 47 + export function validateView<V>(v: V) { 48 + return validate<View & V>(v, id, hashView); 49 + }
+39
lex/types/so/sprk/media/images.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { validate as _validate } from "../../../../lexicons.ts"; 5 + import { is$typed as _is$typed } from "../../../../util.ts"; 6 + import type * as SoSprkMediaImage from "./image.ts"; 7 + 8 + const is$typed = _is$typed, validate = _validate; 9 + const id = "so.sprk.media.images"; 10 + 11 + export interface Main { 12 + $type?: "so.sprk.media.images"; 13 + images: (SoSprkMediaImage.Main)[]; 14 + } 15 + 16 + const hashMain = "main"; 17 + 18 + export function isMain<V>(v: V) { 19 + return is$typed(v, id, hashMain); 20 + } 21 + 22 + export function validateMain<V>(v: V) { 23 + return validate<Main & V>(v, id, hashMain); 24 + } 25 + 26 + export interface View { 27 + $type?: "so.sprk.media.images#view"; 28 + images: (SoSprkMediaImage.View)[]; 29 + } 30 + 31 + const hashView = "view"; 32 + 33 + export function isView<V>(v: V) { 34 + return is$typed(v, id, hashView); 35 + } 36 + 37 + export function validateView<V>(v: V) { 38 + return validate<View & V>(v, id, hashView); 39 + }
+75
lex/types/so/sprk/story/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { validate as _validate } from "../../../../lexicons.ts"; 5 + import { is$typed as _is$typed } from "../../../../util.ts"; 6 + import { type $Typed } from "../../../../util.ts"; 7 + import type * as SoSprkActorDefs from "../actor/defs.ts"; 8 + import type * as SoSprkMediaImage from "../media/image.ts"; 9 + import type * as SoSprkMediaVideo from "../media/video.ts"; 10 + 11 + const is$typed = _is$typed, validate = _validate; 12 + const id = "so.sprk.story.defs"; 13 + 14 + export interface StoryView { 15 + $type?: "so.sprk.story.defs#storyView"; 16 + uri: string; 17 + cid: string; 18 + author: SoSprkActorDefs.ProfileViewBasic; 19 + record: { [_ in string]: unknown }; 20 + media?: $Typed<SoSprkMediaImage.View> | $Typed<SoSprkMediaVideo.View> | { 21 + $type: string; 22 + }; 23 + indexedAt: string; 24 + } 25 + 26 + const hashStoryView = "storyView"; 27 + 28 + export function isStoryView<V>(v: V) { 29 + return is$typed(v, id, hashStoryView); 30 + } 31 + 32 + export function validateStoryView<V>(v: V) { 33 + return validate<StoryView & V>(v, id, hashStoryView); 34 + } 35 + 36 + export interface StoriesByAuthor { 37 + $type?: "so.sprk.story.defs#storiesByAuthor"; 38 + author: SoSprkActorDefs.ProfileViewBasic; 39 + stories: (StoryView)[]; 40 + } 41 + 42 + const hashStoriesByAuthor = "storiesByAuthor"; 43 + 44 + export function isStoriesByAuthor<V>(v: V) { 45 + return is$typed(v, id, hashStoriesByAuthor); 46 + } 47 + 48 + export function validateStoriesByAuthor<V>(v: V) { 49 + return validate<StoriesByAuthor & V>(v, id, hashStoriesByAuthor); 50 + } 51 + 52 + export interface Interaction { 53 + $type?: "so.sprk.story.defs#interaction"; 54 + item?: string; 55 + event?: 56 + | "so.sprk.feed.defs#clickthroughItem" 57 + | "so.sprk.feed.defs#clickthroughAuthor" 58 + | "so.sprk.feed.defs#clickthroughReposter" 59 + | "so.sprk.feed.defs#clickthroughEmbed" 60 + | "so.sprk.feed.defs#interactionSeen" 61 + | "so.sprk.feed.defs#interactionLike" 62 + | "so.sprk.feed.defs#interactionRepost" 63 + | "so.sprk.feed.defs#interactionShare" 64 + | (string & globalThis.Record<PropertyKey, never>); 65 + } 66 + 67 + const hashInteraction = "interaction"; 68 + 69 + export function isInteraction<V>(v: V) { 70 + return is$typed(v, id, hashInteraction); 71 + } 72 + 73 + export function validateInteraction<V>(v: V) { 74 + return validate<Interaction & V>(v, id, hashInteraction); 75 + }
-75
lex/types/so/sprk/unspecced/defs.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { validate as _validate } from "../../../../lexicons.ts"; 5 - import { is$typed as _is$typed } from "../../../../util.ts"; 6 - 7 - const is$typed = _is$typed, validate = _validate; 8 - const id = "so.sprk.unspecced.defs"; 9 - 10 - export interface SkeletonSearchPost { 11 - $type?: "so.sprk.unspecced.defs#skeletonSearchPost"; 12 - uri: string; 13 - } 14 - 15 - const hashSkeletonSearchPost = "skeletonSearchPost"; 16 - 17 - export function isSkeletonSearchPost<V>(v: V) { 18 - return is$typed(v, id, hashSkeletonSearchPost); 19 - } 20 - 21 - export function validateSkeletonSearchPost<V>(v: V) { 22 - return validate<SkeletonSearchPost & V>(v, id, hashSkeletonSearchPost); 23 - } 24 - 25 - export interface SkeletonSearchActor { 26 - $type?: "so.sprk.unspecced.defs#skeletonSearchActor"; 27 - did: string; 28 - } 29 - 30 - const hashSkeletonSearchActor = "skeletonSearchActor"; 31 - 32 - export function isSkeletonSearchActor<V>(v: V) { 33 - return is$typed(v, id, hashSkeletonSearchActor); 34 - } 35 - 36 - export function validateSkeletonSearchActor<V>(v: V) { 37 - return validate<SkeletonSearchActor & V>(v, id, hashSkeletonSearchActor); 38 - } 39 - 40 - export interface SkeletonSearchStarterPack { 41 - $type?: "so.sprk.unspecced.defs#skeletonSearchStarterPack"; 42 - uri: string; 43 - } 44 - 45 - const hashSkeletonSearchStarterPack = "skeletonSearchStarterPack"; 46 - 47 - export function isSkeletonSearchStarterPack<V>(v: V) { 48 - return is$typed(v, id, hashSkeletonSearchStarterPack); 49 - } 50 - 51 - export function validateSkeletonSearchStarterPack<V>(v: V) { 52 - return validate<SkeletonSearchStarterPack & V>( 53 - v, 54 - id, 55 - hashSkeletonSearchStarterPack, 56 - ); 57 - } 58 - 59 - export interface TrendingTopic { 60 - $type?: "so.sprk.unspecced.defs#trendingTopic"; 61 - topic: string; 62 - displayName?: string; 63 - description?: string; 64 - link: string; 65 - } 66 - 67 - const hashTrendingTopic = "trendingTopic"; 68 - 69 - export function isTrendingTopic<V>(v: V) { 70 - return is$typed(v, id, hashTrendingTopic); 71 - } 72 - 73 - export function validateTrendingTopic<V>(v: V) { 74 - return validate<TrendingTopic & V>(v, id, hashTrendingTopic); 75 - }
-24
lex/types/so/sprk/unspecced/getConfig.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - export type QueryParams = globalThis.Record<PropertyKey, never>; 5 - export type InputSchema = undefined; 6 - 7 - export interface OutputSchema { 8 - checkEmailConfirmed?: boolean; 9 - } 10 - 11 - export type HandlerInput = void; 12 - 13 - export interface HandlerSuccess { 14 - encoding: "application/json"; 15 - body: OutputSchema; 16 - headers?: { [key: string]: string }; 17 - } 18 - 19 - export interface HandlerError { 20 - status: number; 21 - message?: string; 22 - } 23 - 24 - export type HandlerOutput = HandlerError | HandlerSuccess;
-31
lex/types/so/sprk/unspecced/getPopularFeedGenerators.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkFeedDefs from "../feed/defs.ts"; 5 - 6 - export type QueryParams = { 7 - limit: number; 8 - cursor?: string; 9 - query?: string; 10 - }; 11 - export type InputSchema = undefined; 12 - 13 - export interface OutputSchema { 14 - cursor?: string; 15 - feeds: (SoSprkFeedDefs.GeneratorView)[]; 16 - } 17 - 18 - export type HandlerInput = void; 19 - 20 - export interface HandlerSuccess { 21 - encoding: "application/json"; 22 - body: OutputSchema; 23 - headers?: { [key: string]: string }; 24 - } 25 - 26 - export interface HandlerError { 27 - status: number; 28 - message?: string; 29 - } 30 - 31 - export type HandlerOutput = HandlerError | HandlerSuccess;
-38
lex/types/so/sprk/unspecced/getSuggestionsSkeleton.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkUnspeccedDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. */ 8 - viewer?: string; 9 - limit: number; 10 - cursor?: string; 11 - /** DID of the account to get suggestions relative to. If not provided, suggestions will be based on the viewer. */ 12 - relativeToDid?: string; 13 - }; 14 - export type InputSchema = undefined; 15 - 16 - export interface OutputSchema { 17 - cursor?: string; 18 - actors: (SoSprkUnspeccedDefs.SkeletonSearchActor)[]; 19 - /** DID of the account these suggestions are relative to. If this is returned undefined, suggestions are based on the viewer. */ 20 - relativeToDid?: string; 21 - /** Snowflake for this recommendation, use when submitting recommendation events. */ 22 - recId?: number; 23 - } 24 - 25 - export type HandlerInput = void; 26 - 27 - export interface HandlerSuccess { 28 - encoding: "application/json"; 29 - body: OutputSchema; 30 - headers?: { [key: string]: string }; 31 - } 32 - 33 - export interface HandlerError { 34 - status: number; 35 - message?: string; 36 - } 37 - 38 - export type HandlerOutput = HandlerError | HandlerSuccess;
-50
lex/types/so/sprk/unspecced/getTaggedSuggestions.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { validate as _validate } from "../../../../lexicons.ts"; 5 - import { is$typed as _is$typed } from "../../../../util.ts"; 6 - 7 - const is$typed = _is$typed, validate = _validate; 8 - const id = "so.sprk.unspecced.getTaggedSuggestions"; 9 - 10 - export type QueryParams = globalThis.Record<PropertyKey, never>; 11 - export type InputSchema = undefined; 12 - 13 - export interface OutputSchema { 14 - suggestions: (Suggestion)[]; 15 - } 16 - 17 - export type HandlerInput = void; 18 - 19 - export interface HandlerSuccess { 20 - encoding: "application/json"; 21 - body: OutputSchema; 22 - headers?: { [key: string]: string }; 23 - } 24 - 25 - export interface HandlerError { 26 - status: number; 27 - message?: string; 28 - } 29 - 30 - export type HandlerOutput = HandlerError | HandlerSuccess; 31 - 32 - export interface Suggestion { 33 - $type?: "so.sprk.unspecced.getTaggedSuggestions#suggestion"; 34 - tag: string; 35 - subjectType: 36 - | "actor" 37 - | "feed" 38 - | (string & globalThis.Record<PropertyKey, never>); 39 - subject: string; 40 - } 41 - 42 - const hashSuggestion = "suggestion"; 43 - 44 - export function isSuggestion<V>(v: V) { 45 - return is$typed(v, id, hashSuggestion); 46 - } 47 - 48 - export function validateSuggestion<V>(v: V) { 49 - return validate<Suggestion & V>(v, id, hashSuggestion); 50 - }
-31
lex/types/so/sprk/unspecced/getTrendingTopics.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkUnspeccedDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. */ 8 - viewer?: string; 9 - limit: number; 10 - }; 11 - export type InputSchema = undefined; 12 - 13 - export interface OutputSchema { 14 - topics: (SoSprkUnspeccedDefs.TrendingTopic)[]; 15 - suggested: (SoSprkUnspeccedDefs.TrendingTopic)[]; 16 - } 17 - 18 - export type HandlerInput = void; 19 - 20 - export interface HandlerSuccess { 21 - encoding: "application/json"; 22 - body: OutputSchema; 23 - headers?: { [key: string]: string }; 24 - } 25 - 26 - export interface HandlerError { 27 - status: number; 28 - message?: string; 29 - } 30 - 31 - export type HandlerOutput = HandlerError | HandlerSuccess;
-40
lex/types/so/sprk/unspecced/searchActorsSkeleton.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkUnspeccedDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. For typeahead search, only simple term match is supported, not full syntax. */ 8 - q: string; 9 - /** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. */ 10 - viewer?: string; 11 - /** If true, acts as fast/simple 'typeahead' query. */ 12 - typeahead?: boolean; 13 - limit: number; 14 - /** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. */ 15 - cursor?: string; 16 - }; 17 - export type InputSchema = undefined; 18 - 19 - export interface OutputSchema { 20 - cursor?: string; 21 - /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ 22 - hitsTotal?: number; 23 - actors: (SoSprkUnspeccedDefs.SkeletonSearchActor)[]; 24 - } 25 - 26 - export type HandlerInput = void; 27 - 28 - export interface HandlerSuccess { 29 - encoding: "application/json"; 30 - body: OutputSchema; 31 - headers?: { [key: string]: string }; 32 - } 33 - 34 - export interface HandlerError { 35 - status: number; 36 - message?: string; 37 - error?: "BadQueryString"; 38 - } 39 - 40 - export type HandlerOutput = HandlerError | HandlerSuccess;
-56
lex/types/so/sprk/unspecced/searchPostsSkeleton.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkUnspeccedDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ 8 - q: string; 9 - /** Specifies the ranking order of results. */ 10 - sort: "top" | "latest" | (string & globalThis.Record<PropertyKey, never>); 11 - /** Filter results for posts after the indicated datetime (inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYYY-MM-DD). */ 12 - since?: string; 13 - /** Filter results for posts before the indicated datetime (not inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYY-MM-DD). */ 14 - until?: string; 15 - /** Filter to posts which mention the given account. Handles are resolved to DID before query-time. Only matches rich-text facet mentions. */ 16 - mentions?: string; 17 - /** Filter to posts by the given account. Handles are resolved to DID before query-time. */ 18 - author?: string; 19 - /** Filter to posts in the given language. Expected to be based on post language field, though server may override language detection. */ 20 - lang?: string; 21 - /** Filter to posts with URLs (facet links or embeds) linking to the given domain (hostname). Server may apply hostname normalization. */ 22 - domain?: string; 23 - /** Filter to posts with links (facet links or embeds) pointing to this URL. Server may apply URL normalization or fuzzy matching. */ 24 - url?: string; 25 - /** Filter to posts with the given tag (hashtag), based on rich-text facet or tag field. Do not include the hash (#) prefix. Multiple tags can be specified, with 'AND' matching. */ 26 - tag?: string[]; 27 - /** DID of the account making the request (not included for public/unauthenticated queries). Used for 'from:me' queries. */ 28 - viewer?: string; 29 - limit: number; 30 - /** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. */ 31 - cursor?: string; 32 - }; 33 - export type InputSchema = undefined; 34 - 35 - export interface OutputSchema { 36 - cursor?: string; 37 - /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ 38 - hitsTotal?: number; 39 - posts: (SoSprkUnspeccedDefs.SkeletonSearchPost)[]; 40 - } 41 - 42 - export type HandlerInput = void; 43 - 44 - export interface HandlerSuccess { 45 - encoding: "application/json"; 46 - body: OutputSchema; 47 - headers?: { [key: string]: string }; 48 - } 49 - 50 - export interface HandlerError { 51 - status: number; 52 - message?: string; 53 - error?: "BadQueryString"; 54 - } 55 - 56 - export type HandlerOutput = HandlerError | HandlerSuccess;
-38
lex/types/so/sprk/unspecced/searchStarterPacksSkeleton.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import type * as SoSprkUnspeccedDefs from "./defs.ts"; 5 - 6 - export type QueryParams = { 7 - /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ 8 - q: string; 9 - /** DID of the account making the request (not included for public/unauthenticated queries). */ 10 - viewer?: string; 11 - limit: number; 12 - /** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. */ 13 - cursor?: string; 14 - }; 15 - export type InputSchema = undefined; 16 - 17 - export interface OutputSchema { 18 - cursor?: string; 19 - /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ 20 - hitsTotal?: number; 21 - starterPacks: (SoSprkUnspeccedDefs.SkeletonSearchStarterPack)[]; 22 - } 23 - 24 - export type HandlerInput = void; 25 - 26 - export interface HandlerSuccess { 27 - encoding: "application/json"; 28 - body: OutputSchema; 29 - headers?: { [key: string]: string }; 30 - } 31 - 32 - export interface HandlerError { 33 - status: number; 34 - message?: string; 35 - error?: "BadQueryString"; 36 - } 37 - 38 - export type HandlerOutput = HandlerError | HandlerSuccess;
+2 -17
lexicons/so/sprk/actor/defs.json
··· 89 89 "type": "ref", 90 90 "ref": "#profileAssociated" 91 91 }, 92 - "joinedViaStarterPack": { 93 - "type": "ref", 94 - "ref": "so.sprk.graph.defs#starterPackViewBasic" 95 - }, 96 92 "indexedAt": { "type": "string", "format": "datetime" }, 97 93 "createdAt": { "type": "string", "format": "datetime" }, 98 94 "viewer": { "type": "ref", "ref": "#viewerState" }, ··· 117 113 "profileAssociated": { 118 114 "type": "object", 119 115 "properties": { 120 - "lists": { "type": "integer" }, 121 116 "feedgens": { "type": "integer" }, 122 - "starterPacks": { "type": "integer" }, 123 117 "labeler": { "type": "boolean" }, 124 118 "chat": { "type": "ref", "ref": "#profileAssociatedChat" } 125 119 } ··· 139 133 "description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.", 140 134 "properties": { 141 135 "muted": { "type": "boolean" }, 142 - "mutedByList": { 143 - "type": "ref", 144 - "ref": "so.sprk.graph.defs#listViewBasic" 145 - }, 146 136 "blockedBy": { "type": "boolean" }, 147 137 "blocking": { "type": "string", "format": "at-uri" }, 148 - "blockingByList": { 149 - "type": "ref", 150 - "ref": "so.sprk.graph.defs#listViewBasic" 151 - }, 152 138 "following": { "type": "string", "format": "at-uri" }, 153 139 "followedBy": { "type": "string", "format": "at-uri" }, 154 140 "knownFollowers": { ··· 225 211 }, 226 212 "type": { 227 213 "type": "string", 228 - "knownValues": ["feed", "list", "timeline"] 214 + "knownValues": ["feed", "timeline"] 229 215 }, 230 216 "value": { 231 217 "type": "string" ··· 427 413 "refs": [ 428 414 "so.sprk.feed.threadgate#mentionRule", 429 415 "so.sprk.feed.threadgate#followerRule", 430 - "so.sprk.feed.threadgate#followingRule", 431 - "so.sprk.feed.threadgate#listRule" 416 + "so.sprk.feed.threadgate#followingRule" 432 417 ] 433 418 } 434 419 }
+1 -1
lexicons/so/sprk/embed/defs.json lexicons/so/sprk/media/defs.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "so.sprk.embed.defs", 3 + "id": "so.sprk.media.defs", 4 4 "defs": { 5 5 "aspectRatio": { 6 6 "type": "object",
+4 -26
lexicons/so/sprk/embed/images.json lexicons/so/sprk/media/image.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "so.sprk.embed.images", 4 - "description": "A set of images embedded in a Spark record (eg, a post).", 3 + "id": "so.sprk.media.image", 4 + "description": "An image in a Spark record.", 5 5 "defs": { 6 6 "main": { 7 - "type": "object", 8 - "required": ["images"], 9 - "properties": { 10 - "images": { 11 - "type": "array", 12 - "items": { "type": "ref", "ref": "#image" }, 13 - "maxLength": 12 14 - } 15 - } 16 - }, 17 - "image": { 18 7 "type": "object", 19 8 "required": ["image", "alt"], 20 9 "properties": { ··· 29 18 }, 30 19 "aspectRatio": { 31 20 "type": "ref", 32 - "ref": "so.sprk.embed.defs#aspectRatio" 21 + "ref": "so.sprk.media.defs#aspectRatio" 33 22 } 34 23 } 35 24 }, 36 25 "view": { 37 - "type": "object", 38 - "required": ["images"], 39 - "properties": { 40 - "images": { 41 - "type": "array", 42 - "items": { "type": "ref", "ref": "#viewImage" }, 43 - "maxLength": 12 44 - } 45 - } 46 - }, 47 - "viewImage": { 48 26 "type": "object", 49 27 "required": ["thumb", "fullsize", "alt"], 50 28 "properties": { ··· 64 42 }, 65 43 "aspectRatio": { 66 44 "type": "ref", 67 - "ref": "so.sprk.embed.defs#aspectRatio" 45 + "ref": "so.sprk.media.defs#aspectRatio" 68 46 } 69 47 } 70 48 }
+4 -4
lexicons/so/sprk/embed/video.json lexicons/so/sprk/media/video.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "so.sprk.embed.video", 4 - "description": "A video embedded in a Spark record (eg, a post).", 3 + "id": "so.sprk.media.video", 4 + "description": "A video in a Spark post.", 5 5 "defs": { 6 6 "main": { 7 7 "type": "object", ··· 25 25 }, 26 26 "aspectRatio": { 27 27 "type": "ref", 28 - "ref": "so.sprk.embed.defs#aspectRatio" 28 + "ref": "so.sprk.media.defs#aspectRatio" 29 29 } 30 30 } 31 31 }, ··· 58 58 }, 59 59 "aspectRatio": { 60 60 "type": "ref", 61 - "ref": "so.sprk.embed.defs#aspectRatio" 61 + "ref": "so.sprk.media.defs#aspectRatio" 62 62 } 63 63 } 64 64 }
+25 -53
lexicons/so/sprk/feed/defs.json
··· 2 2 "lexicon": 1, 3 3 "id": "so.sprk.feed.defs", 4 4 "defs": { 5 - "storyView": { 5 + "postView": { 6 6 "type": "object", 7 7 "required": ["uri", "cid", "author", "record", "indexedAt"], 8 8 "properties": { ··· 15 15 "record": { "type": "unknown" }, 16 16 "media": { 17 17 "type": "union", 18 - "refs": ["so.sprk.embed.images#view", "so.sprk.embed.video#view"] 18 + "refs": ["so.sprk.media.images#view", "so.sprk.media.video#view"] 19 19 }, 20 - "indexedAt": { "type": "string", "format": "datetime" } 20 + "sound": { "type": "ref", "ref": "so.sprk.sound.defs#audioView" }, 21 + "replyCount": { "type": "integer" }, 22 + "repostCount": { "type": "integer" }, 23 + "likeCount": { "type": "integer" }, 24 + "indexedAt": { "type": "string", "format": "datetime" }, 25 + "viewer": { "type": "ref", "ref": "#viewerState" }, 26 + "labels": { 27 + "type": "array", 28 + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 29 + }, 30 + "threadgate": { "type": "ref", "ref": "#threadgateView" } 21 31 } 22 32 }, 23 - "postView": { 33 + "replyView": { 24 34 "type": "object", 25 35 "required": ["uri", "cid", "author", "record", "indexedAt"], 26 36 "properties": { ··· 31 41 "ref": "so.sprk.actor.defs#profileViewBasic" 32 42 }, 33 43 "record": { "type": "unknown" }, 34 - "embed": { 44 + "media": { 35 45 "type": "union", 36 - "refs": ["so.sprk.embed.images#view", "so.sprk.embed.video#view"] 46 + "refs": ["so.sprk.media.image#view"] 37 47 }, 38 - "sound": { "type": "ref", "ref": "so.sprk.sound.defs#audioView" }, 39 48 "replyCount": { "type": "integer" }, 40 - "repostCount": { "type": "integer" }, 41 49 "likeCount": { "type": "integer" }, 42 50 "indexedAt": { "type": "string", "format": "datetime" }, 43 51 "viewer": { "type": "ref", "ref": "#viewerState" }, 44 52 "labels": { 45 53 "type": "array", 46 54 "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 47 - }, 48 - "threadgate": { "type": "ref", "ref": "#threadgateView" } 55 + } 49 56 } 50 57 }, 51 58 "viewerState": { ··· 72 79 "required": ["post"], 73 80 "properties": { 74 81 "post": { "type": "ref", "ref": "#postView" }, 75 - "reply": { "type": "ref", "ref": "#replyRef" }, 76 82 "reason": { "type": "union", "refs": ["#reasonRepost", "#reasonPin"] }, 77 83 "feedContext": { 78 84 "type": "string", ··· 81 87 } 82 88 } 83 89 }, 84 - "feedViewStory": { 85 - "type": "object", 86 - "required": ["story"], 87 - "properties": { 88 - "story": { "type": "ref", "ref": "#storyView" } 89 - } 90 - }, 91 - "storiesByAuthor": { 92 - "type": "object", 93 - "required": ["author", "stories"], 94 - "properties": { 95 - "author": { 96 - "type": "ref", 97 - "ref": "so.sprk.actor.defs#profileViewBasic" 98 - }, 99 - "stories": { 100 - "type": "array", 101 - "items": { "type": "ref", "ref": "#storyView" } 102 - } 103 - } 104 - }, 105 90 "replyRef": { 106 91 "type": "object", 107 92 "required": ["root", "parent"], ··· 112 97 }, 113 98 "parent": { 114 99 "type": "union", 115 - "refs": ["#postView", "#notFoundPost", "#blockedPost"] 100 + "refs": ["#postView", "#replyView", "#notFoundPost", "#blockedPost"] 116 101 }, 117 102 "grandparentAuthor": { 118 103 "type": "ref", ··· 137 122 "type": "object", 138 123 "required": ["post"], 139 124 "properties": { 140 - "post": { "type": "ref", "ref": "#postView" }, 125 + "post": { "type": "union", "refs": ["#postView", "#replyView"] }, 141 126 "parent": { 142 127 "type": "union", 143 - "refs": ["#threadViewPost", "#notFoundPost", "#blockedPost"] 128 + "refs": [ 129 + "#threadViewPost", 130 + "#notFoundPost", 131 + "#blockedPost" 132 + ] 144 133 }, 145 134 "replies": { 146 135 "type": "array", ··· 244 233 "properties": { 245 234 "uri": { "type": "string", "format": "at-uri" }, 246 235 "cid": { "type": "string", "format": "cid" }, 247 - "record": { "type": "unknown" }, 248 - "lists": { 249 - "type": "array", 250 - "items": { "type": "ref", "ref": "so.sprk.graph.defs#listViewBasic" } 251 - } 236 + "record": { "type": "unknown" } 252 237 } 253 238 }, 254 239 "interaction": { ··· 268 253 "so.sprk.feed.defs#interactionLike", 269 254 "so.sprk.feed.defs#interactionRepost", 270 255 "so.sprk.feed.defs#interactionReply", 271 - "so.sprk.feed.defs#interactionQuote", 272 256 "so.sprk.feed.defs#interactionShare" 273 257 ] 274 258 }, ··· 303 287 "type": "token", 304 288 "description": "User clicked through to the embedded content of the feed item" 305 289 }, 306 - "contentModeUnspecified": { 307 - "type": "token", 308 - "description": "Declares the feed generator returns any types of posts." 309 - }, 310 - "contentModeVideo": { 311 - "type": "token", 312 - "description": "Declares the feed generator returns posts containing so.sprk.embed.video embeds." 313 - }, 314 290 "interactionSeen": { 315 291 "type": "token", 316 292 "description": "Feed item was seen by user" ··· 326 302 "interactionReply": { 327 303 "type": "token", 328 304 "description": "User replied to the feed item" 329 - }, 330 - "interactionQuote": { 331 - "type": "token", 332 - "description": "User quoted the feed item" 333 305 }, 334 306 "interactionShare": { 335 307 "type": "token",
-46
lexicons/so/sprk/feed/getListFeed.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.feed.getListFeed", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get a feed of recent posts from a list (posts and reposts from any actors on the list). Does not require auth.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["list"], 11 - "properties": { 12 - "list": { 13 - "type": "string", 14 - "format": "at-uri", 15 - "description": "Reference (AT-URI) to the list record." 16 - }, 17 - "limit": { 18 - "type": "integer", 19 - "minimum": 1, 20 - "maximum": 100, 21 - "default": 50 22 - }, 23 - "cursor": { "type": "string" } 24 - } 25 - }, 26 - "output": { 27 - "encoding": "application/json", 28 - "schema": { 29 - "type": "object", 30 - "required": ["feed"], 31 - "properties": { 32 - "cursor": { "type": "string" }, 33 - "feed": { 34 - "type": "array", 35 - "items": { 36 - "type": "ref", 37 - "ref": "so.sprk.feed.defs#feedViewPost" 38 - } 39 - } 40 - } 41 - } 42 - }, 43 - "errors": [{ "name": "UnknownList" }] 44 - } 45 - } 46 - }
+50 -9
lexicons/so/sprk/feed/getPostThread.json
··· 7 7 "description": "Get posts in a thread. Does not require auth, but additional metadata and filtering will be applied for authed requests.", 8 8 "parameters": { 9 9 "type": "params", 10 - "required": ["uri"], 10 + "required": ["anchor"], 11 11 "properties": { 12 - "uri": { 12 + "anchor": { 13 13 "type": "string", 14 14 "format": "at-uri", 15 - "description": "Reference (AT-URI) to post record." 15 + "description": "Reference (AT-URI) to anchor post record." 16 + }, 17 + "limit": { 18 + "type": "integer", 19 + "minimum": 1, 20 + "maximum": 100, 21 + "default": 50 16 22 }, 23 + "cursor": { "type": "string" }, 17 24 "depth": { 18 25 "type": "integer", 19 26 "description": "How many levels of reply depth should be included in response.", ··· 27 34 "default": 80, 28 35 "minimum": 0, 29 36 "maximum": 1000 37 + }, 38 + "prioritizeFollowedUsers": { 39 + "type": "boolean", 40 + "description": "Whether to prioritize posts from followed users. It only has effect when the user is authenticated.", 41 + "default": false 42 + }, 43 + "sort": { 44 + "type": "string", 45 + "description": "Sorting for the thread replies.", 46 + "knownValues": ["newest", "oldest", "top"], 47 + "default": "oldest" 30 48 } 31 49 } 32 50 }, ··· 36 54 "type": "object", 37 55 "required": ["thread"], 38 56 "properties": { 57 + "cursor": { "type": "string" }, 39 58 "thread": { 40 - "type": "union", 41 - "refs": [ 42 - "so.sprk.feed.defs#threadViewPost", 43 - "so.sprk.feed.defs#notFoundPost", 44 - "so.sprk.feed.defs#blockedPost" 45 - ] 59 + "type": "array", 60 + "description": "A flat list of thread items. The depth of each item is indicated by the depth property inside the item.", 61 + "items": { 62 + "type": "ref", 63 + "ref": "#threadItem" 64 + } 46 65 }, 47 66 "threadgate": { 48 67 "type": "ref", ··· 52 71 } 53 72 }, 54 73 "errors": [{ "name": "NotFound" }] 74 + }, 75 + "threadItem": { 76 + "type": "object", 77 + "required": ["uri", "depth", "value"], 78 + "properties": { 79 + "uri": { 80 + "type": "string", 81 + "format": "at-uri" 82 + }, 83 + "depth": { 84 + "type": "integer", 85 + "description": "The nesting level of this item in the thread. Depth 0 means the anchor item." 86 + }, 87 + "value": { 88 + "type": "union", 89 + "refs": [ 90 + "so.sprk.feed.defs#threadViewPost", 91 + "so.sprk.feed.defs#NotFoundPost", 92 + "so.sprk.feed.defs#BlockedPost" 93 + ] 94 + } 95 + } 55 96 } 56 97 } 57 98 }
-52
lexicons/so/sprk/feed/getQuotes.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.feed.getQuotes", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get a list of quotes for a given post.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["uri"], 11 - "properties": { 12 - "uri": { 13 - "type": "string", 14 - "format": "at-uri", 15 - "description": "Reference (AT-URI) of post record" 16 - }, 17 - "cid": { 18 - "type": "string", 19 - "format": "cid", 20 - "description": "If supplied, filters to quotes of specific version (by CID) of the post record." 21 - }, 22 - "limit": { 23 - "type": "integer", 24 - "minimum": 1, 25 - "maximum": 100, 26 - "default": 50 27 - }, 28 - "cursor": { "type": "string" } 29 - } 30 - }, 31 - "output": { 32 - "encoding": "application/json", 33 - "schema": { 34 - "type": "object", 35 - "required": ["uri", "posts"], 36 - "properties": { 37 - "uri": { "type": "string", "format": "at-uri" }, 38 - "cid": { "type": "string", "format": "cid" }, 39 - "cursor": { "type": "string" }, 40 - "posts": { 41 - "type": "array", 42 - "items": { 43 - "type": "ref", 44 - "ref": "so.sprk.feed.defs#postView" 45 - } 46 - } 47 - } 48 - } 49 - } 50 - } 51 - } 52 - }
+2 -2
lexicons/so/sprk/feed/getStories.json lexicons/so/sprk/story/getStories.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "so.sprk.feed.getStories", 3 + "id": "so.sprk.story.getStories", 4 4 "defs": { 5 5 "main": { 6 6 "type": "query", ··· 24 24 "properties": { 25 25 "stories": { 26 26 "type": "array", 27 - "items": { "type": "ref", "ref": "so.sprk.feed.defs#storyView" } 27 + "items": { "type": "ref", "ref": "so.sprk.story.defs#storyView" } 28 28 } 29 29 } 30 30 }
+2 -2
lexicons/so/sprk/feed/getStoriesTimeline.json lexicons/so/sprk/story/getTimeline.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "so.sprk.feed.getStoriesTimeline", 3 + "id": "so.sprk.story.getTimeline", 4 4 "defs": { 5 5 "main": { 6 6 "type": "query", ··· 28 28 "type": "array", 29 29 "items": { 30 30 "type": "ref", 31 - "ref": "so.sprk.feed.defs#storiesByAuthor" 31 + "ref": "so.sprk.story.defs#storiesByAuthor" 32 32 } 33 33 } 34 34 }
-89
lexicons/so/sprk/feed/music.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.feed.music", 4 - "description": "A music record usable in a Spark record (e.g, a post)", 5 - "defs": { 6 - "main": { 7 - "type": "record", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["sound", "title", "author", "releaseDate", "createdAt"], 12 - "properties": { 13 - "sound": { 14 - "type": "blob", 15 - "accept": ["audio/mp3"], 16 - "maxSize": 10485760 17 - }, 18 - "title": { 19 - "type": "string", 20 - "maxLength": 1000, 21 - "maxGraphemes": 100, 22 - "description": "The music's title." 23 - }, 24 - "releaseDate": { "type": "string", "format": "datetime" }, 25 - "album": { 26 - "type": "string", 27 - "maxLength": 1000, 28 - "maxGraphemes": 100, 29 - "description": "The music's album name." 30 - }, 31 - "recordLabel": { 32 - "type": "string", 33 - "maxLength": 1000, 34 - "maxGraphemes": 100, 35 - "description": "The music's record label." 36 - }, 37 - "cover": { 38 - "type": "blob", 39 - "description": "Image to be displayed in music's page. AKA, 'cover image'", 40 - "accept": ["image/png", "image/jpeg"], 41 - "maxSize": 5242880 42 - }, 43 - "author": { 44 - "type": "string", 45 - "maxLength": 1000, 46 - "maxGraphemes": 100, 47 - "description": "The music's author." 48 - }, 49 - "text": { 50 - "type": "string", 51 - "maxLength": 3000, 52 - "maxGraphemes": 300, 53 - "description": "The music's description." 54 - }, 55 - "copyright": { 56 - "type": "array", 57 - "items": { 58 - "type": "string", 59 - "maxLength": 3000, 60 - "maxGraphemes": 300 61 - }, 62 - "minLength": 1 63 - }, 64 - "facets": { 65 - "type": "array", 66 - "description": "Annotations of text (mentions, URLs, hashtags, etc)", 67 - "items": { "type": "ref", "ref": "so.sprk.richtext.facet" } 68 - }, 69 - "labels": { 70 - "type": "union", 71 - "description": "Self-label values for this music. Effectively content warnings.", 72 - "refs": ["com.atproto.label.defs#selfLabels"] 73 - }, 74 - "tags": { 75 - "type": "array", 76 - "description": "The music's Hashtags", 77 - "maxLength": 8, 78 - "items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 } 79 - }, 80 - "createdAt": { 81 - "type": "string", 82 - "format": "datetime", 83 - "description": "Client-declared timestamp when this post was originally created." 84 - } 85 - } 86 - } 87 - } 88 - } 89 - }
+16 -22
lexicons/so/sprk/feed/post.json
··· 8 8 "key": "tid", 9 9 "record": { 10 10 "type": "object", 11 - "required": ["createdAt"], 11 + "required": ["createdAt", "media"], 12 12 "properties": { 13 - "text": { 14 - "type": "string", 15 - "maxLength": 3000, 16 - "maxGraphemes": 300, 17 - "description": "The post description." 18 - }, 19 - "facets": { 20 - "type": "array", 21 - "description": "Annotations of text (mentions, URLs, hashtags, etc)", 22 - "items": { "type": "ref", "ref": "so.sprk.richtext.facet" } 23 - }, 24 - "reply": { "type": "ref", "ref": "#replyRef" }, 25 - "embed": { 13 + "caption": { "type": "ref", "ref": "#captionRef" }, 14 + "media": { 26 15 "type": "union", 27 - "refs": [ 28 - "so.sprk.embed.images", 29 - "so.sprk.embed.video" 30 - ] 16 + "refs": ["so.sprk.media.images", "so.sprk.media.video"] 31 17 }, 32 18 "sound": { 33 19 "type": "ref", ··· 58 44 } 59 45 } 60 46 }, 61 - "replyRef": { 47 + "captionRef": { 62 48 "type": "object", 63 - "required": ["root", "parent"], 64 49 "properties": { 65 - "root": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, 66 - "parent": { "type": "ref", "ref": "com.atproto.repo.strongRef" } 50 + "text": { 51 + "type": "string", 52 + "maxLength": 3000, 53 + "maxGraphemes": 300, 54 + "description": "The post description." 55 + }, 56 + "facets": { 57 + "type": "array", 58 + "description": "Annotations of text (mentions, URLs, hashtags, etc)", 59 + "items": { "type": "ref", "ref": "so.sprk.richtext.facet" } 60 + } 67 61 } 68 62 } 69 63 }
+54
lexicons/so/sprk/feed/reply.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "so.sprk.feed.reply", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Record containing a Spark reply.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["createdAt", "reply"], 12 + "properties": { 13 + "text": { 14 + "type": "string", 15 + "maxLength": 3000, 16 + "maxGraphemes": 300, 17 + "description": "The reply text." 18 + }, 19 + "facets": { 20 + "type": "array", 21 + "description": "Annotations of text (mentions, URLs, hashtags, etc)", 22 + "items": { "type": "ref", "ref": "so.sprk.richtext.facet" } 23 + }, 24 + "reply": { "type": "ref", "ref": "#replyRef" }, 25 + "media": { "type": "union", "refs": ["so.sprk.media.image"] }, 26 + "langs": { 27 + "type": "array", 28 + "description": "Indicates human language of post primary text content.", 29 + "maxLength": 3, 30 + "items": { "type": "string", "format": "language" } 31 + }, 32 + "labels": { 33 + "type": "union", 34 + "description": "Self-label values for this post. Effectively content warnings.", 35 + "refs": ["com.atproto.label.defs#selfLabels"] 36 + }, 37 + "createdAt": { 38 + "type": "string", 39 + "format": "datetime", 40 + "description": "Client-declared timestamp when this post was originally created." 41 + } 42 + } 43 + } 44 + }, 45 + "replyRef": { 46 + "type": "object", 47 + "required": ["root", "parent"], 48 + "properties": { 49 + "root": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, 50 + "parent": { "type": "ref", "ref": "com.atproto.repo.strongRef" } 51 + } 52 + } 53 + } 54 + }
+2 -11
lexicons/so/sprk/feed/story.json lexicons/so/sprk/story/post.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "so.sprk.feed.story", 3 + "id": "so.sprk.story.post", 4 4 "defs": { 5 5 "main": { 6 6 "type": "record", ··· 12 12 "properties": { 13 13 "media": { 14 14 "type": "union", 15 - "refs": [ 16 - "so.sprk.embed.images", 17 - "so.sprk.embed.video" 18 - ] 15 + "refs": ["so.sprk.media.image", "so.sprk.media.video"] 19 16 }, 20 17 "sound": { 21 18 "type": "ref", ··· 25 22 "type": "union", 26 23 "description": "Self-label values for this story. Effectively content warnings.", 27 24 "refs": ["com.atproto.label.defs#selfLabels"] 28 - }, 29 - "tags": { 30 - "type": "array", 31 - "description": "Additional hashtags, in addition to any included in story text and facets.", 32 - "maxLength": 8, 33 - "items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 } 34 25 }, 35 26 "createdAt": { 36 27 "type": "string",
+1 -14
lexicons/so/sprk/feed/threadgate.json
··· 21 21 "maxLength": 5, 22 22 "items": { 23 23 "type": "union", 24 - "refs": [ 25 - "#mentionRule", 26 - "#followerRule", 27 - "#followingRule", 28 - "#listRule" 29 - ] 24 + "refs": ["#mentionRule", "#followerRule", "#followingRule"] 30 25 } 31 26 }, 32 27 "createdAt": { "type": "string", "format": "datetime" }, ··· 56 51 "type": "object", 57 52 "description": "Allow replies from actors you follow.", 58 53 "properties": {} 59 - }, 60 - "listRule": { 61 - "type": "object", 62 - "description": "Allow replies from actors on a list.", 63 - "required": ["list"], 64 - "properties": { 65 - "list": { "type": "string", "format": "at-uri" } 66 - } 67 54 } 68 55 } 69 56 }
-133
lexicons/so/sprk/graph/defs.json
··· 2 2 "lexicon": 1, 3 3 "id": "so.sprk.graph.defs", 4 4 "defs": { 5 - "listViewBasic": { 6 - "type": "object", 7 - "required": ["uri", "cid", "name", "purpose"], 8 - "properties": { 9 - "uri": { "type": "string", "format": "at-uri" }, 10 - "cid": { "type": "string", "format": "cid" }, 11 - "name": { "type": "string", "maxLength": 64, "minLength": 1 }, 12 - "purpose": { "type": "ref", "ref": "#listPurpose" }, 13 - "avatar": { "type": "string", "format": "uri" }, 14 - "listItemCount": { "type": "integer", "minimum": 0 }, 15 - "labels": { 16 - "type": "array", 17 - "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 18 - }, 19 - "viewer": { "type": "ref", "ref": "#listViewerState" }, 20 - "indexedAt": { "type": "string", "format": "datetime" } 21 - } 22 - }, 23 - "listView": { 24 - "type": "object", 25 - "required": ["uri", "cid", "creator", "name", "purpose", "indexedAt"], 26 - "properties": { 27 - "uri": { "type": "string", "format": "at-uri" }, 28 - "cid": { "type": "string", "format": "cid" }, 29 - "creator": { "type": "ref", "ref": "so.sprk.actor.defs#profileView" }, 30 - "name": { "type": "string", "maxLength": 64, "minLength": 1 }, 31 - "purpose": { "type": "ref", "ref": "#listPurpose" }, 32 - "description": { 33 - "type": "string", 34 - "maxGraphemes": 300, 35 - "maxLength": 3000 36 - }, 37 - "descriptionFacets": { 38 - "type": "array", 39 - "items": { "type": "ref", "ref": "so.sprk.richtext.facet" } 40 - }, 41 - "avatar": { "type": "string", "format": "uri" }, 42 - "listItemCount": { "type": "integer", "minimum": 0 }, 43 - "labels": { 44 - "type": "array", 45 - "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 46 - }, 47 - "viewer": { "type": "ref", "ref": "#listViewerState" }, 48 - "indexedAt": { "type": "string", "format": "datetime" } 49 - } 50 - }, 51 - "listItemView": { 52 - "type": "object", 53 - "required": ["uri", "subject"], 54 - "properties": { 55 - "uri": { "type": "string", "format": "at-uri" }, 56 - "subject": { "type": "ref", "ref": "so.sprk.actor.defs#profileView" } 57 - } 58 - }, 59 - "starterPackView": { 60 - "type": "object", 61 - "required": ["uri", "cid", "record", "creator", "indexedAt"], 62 - "properties": { 63 - "uri": { "type": "string", "format": "at-uri" }, 64 - "cid": { "type": "string", "format": "cid" }, 65 - "record": { "type": "unknown" }, 66 - "creator": { 67 - "type": "ref", 68 - "ref": "so.sprk.actor.defs#profileViewBasic" 69 - }, 70 - "list": { "type": "ref", "ref": "#listViewBasic" }, 71 - "listItemsSample": { 72 - "type": "array", 73 - "maxLength": 12, 74 - "items": { "type": "ref", "ref": "#listItemView" } 75 - }, 76 - "feeds": { 77 - "type": "array", 78 - "maxLength": 3, 79 - "items": { "type": "ref", "ref": "so.sprk.feed.defs#generatorView" } 80 - }, 81 - "joinedWeekCount": { "type": "integer", "minimum": 0 }, 82 - "joinedAllTimeCount": { "type": "integer", "minimum": 0 }, 83 - "labels": { 84 - "type": "array", 85 - "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 86 - }, 87 - "indexedAt": { "type": "string", "format": "datetime" } 88 - } 89 - }, 90 - "starterPackViewBasic": { 91 - "type": "object", 92 - "required": ["uri", "cid", "record", "creator", "indexedAt"], 93 - "properties": { 94 - "uri": { "type": "string", "format": "at-uri" }, 95 - "cid": { "type": "string", "format": "cid" }, 96 - "record": { "type": "unknown" }, 97 - "creator": { 98 - "type": "ref", 99 - "ref": "so.sprk.actor.defs#profileViewBasic" 100 - }, 101 - "listItemCount": { "type": "integer", "minimum": 0 }, 102 - "joinedWeekCount": { "type": "integer", "minimum": 0 }, 103 - "joinedAllTimeCount": { "type": "integer", "minimum": 0 }, 104 - "labels": { 105 - "type": "array", 106 - "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 107 - }, 108 - "indexedAt": { "type": "string", "format": "datetime" } 109 - } 110 - }, 111 - "listPurpose": { 112 - "type": "string", 113 - "knownValues": [ 114 - "so.sprk.graph.defs#modlist", 115 - "so.sprk.graph.defs#curatelist", 116 - "so.sprk.graph.defs#referencelist" 117 - ] 118 - }, 119 - "modlist": { 120 - "type": "token", 121 - "description": "A list of actors to apply an aggregate moderation action (mute/block) on." 122 - }, 123 - "curatelist": { 124 - "type": "token", 125 - "description": "A list of actors used for curation purposes such as list feeds or interaction gating." 126 - }, 127 - "referencelist": { 128 - "type": "token", 129 - "description": "A list of actors used for only for reference purposes such as within a starter pack." 130 - }, 131 - "listViewerState": { 132 - "type": "object", 133 - "properties": { 134 - "muted": { "type": "boolean" }, 135 - "blocked": { "type": "string", "format": "at-uri" } 136 - } 137 - }, 138 5 "notFoundActor": { 139 6 "type": "object", 140 7 "description": "indicates that a handle or DID could not be resolved",
-41
lexicons/so/sprk/graph/getActorStarterPacks.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.getActorStarterPacks", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get a list of starter packs created by the actor.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["actor"], 11 - "properties": { 12 - "actor": { "type": "string", "format": "at-identifier" }, 13 - "limit": { 14 - "type": "integer", 15 - "minimum": 1, 16 - "maximum": 100, 17 - "default": 50 18 - }, 19 - "cursor": { "type": "string" } 20 - } 21 - }, 22 - "output": { 23 - "encoding": "application/json", 24 - "schema": { 25 - "type": "object", 26 - "required": ["starterPacks"], 27 - "properties": { 28 - "cursor": { "type": "string" }, 29 - "starterPacks": { 30 - "type": "array", 31 - "items": { 32 - "type": "ref", 33 - "ref": "so.sprk.graph.defs#starterPackViewBasic" 34 - } 35 - } 36 - } 37 - } 38 - } 39 - } 40 - } 41 - }
-46
lexicons/so/sprk/graph/getList.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.getList", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Gets a 'view' (with additional context) of a specified list.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["list"], 11 - "properties": { 12 - "list": { 13 - "type": "string", 14 - "format": "at-uri", 15 - "description": "Reference (AT-URI) of the list record to hydrate." 16 - }, 17 - "limit": { 18 - "type": "integer", 19 - "minimum": 1, 20 - "maximum": 100, 21 - "default": 50 22 - }, 23 - "cursor": { "type": "string" } 24 - } 25 - }, 26 - "output": { 27 - "encoding": "application/json", 28 - "schema": { 29 - "type": "object", 30 - "required": ["list", "items"], 31 - "properties": { 32 - "cursor": { "type": "string" }, 33 - "list": { "type": "ref", "ref": "so.sprk.graph.defs#listView" }, 34 - "items": { 35 - "type": "array", 36 - "items": { 37 - "type": "ref", 38 - "ref": "so.sprk.graph.defs#listItemView" 39 - } 40 - } 41 - } 42 - } 43 - } 44 - } 45 - } 46 - }
-36
lexicons/so/sprk/graph/getListBlocks.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.getListBlocks", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get mod lists that the requesting account (actor) is blocking. Requires auth.", 8 - "parameters": { 9 - "type": "params", 10 - "properties": { 11 - "limit": { 12 - "type": "integer", 13 - "minimum": 1, 14 - "maximum": 100, 15 - "default": 50 16 - }, 17 - "cursor": { "type": "string" } 18 - } 19 - }, 20 - "output": { 21 - "encoding": "application/json", 22 - "schema": { 23 - "type": "object", 24 - "required": ["lists"], 25 - "properties": { 26 - "cursor": { "type": "string" }, 27 - "lists": { 28 - "type": "array", 29 - "items": { "type": "ref", "ref": "so.sprk.graph.defs#listView" } 30 - } 31 - } 32 - } 33 - } 34 - } 35 - } 36 - }
-36
lexicons/so/sprk/graph/getListMutes.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.getListMutes", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth.", 8 - "parameters": { 9 - "type": "params", 10 - "properties": { 11 - "limit": { 12 - "type": "integer", 13 - "minimum": 1, 14 - "maximum": 100, 15 - "default": 50 16 - }, 17 - "cursor": { "type": "string" } 18 - } 19 - }, 20 - "output": { 21 - "encoding": "application/json", 22 - "schema": { 23 - "type": "object", 24 - "required": ["lists"], 25 - "properties": { 26 - "cursor": { "type": "string" }, 27 - "lists": { 28 - "type": "array", 29 - "items": { "type": "ref", "ref": "so.sprk.graph.defs#listView" } 30 - } 31 - } 32 - } 33 - } 34 - } 35 - } 36 - }
-42
lexicons/so/sprk/graph/getLists.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.getLists", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Enumerates the lists created by a specified account (actor).", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["actor"], 11 - "properties": { 12 - "actor": { 13 - "type": "string", 14 - "format": "at-identifier", 15 - "description": "The account (actor) to enumerate lists from." 16 - }, 17 - "limit": { 18 - "type": "integer", 19 - "minimum": 1, 20 - "maximum": 100, 21 - "default": 50 22 - }, 23 - "cursor": { "type": "string" } 24 - } 25 - }, 26 - "output": { 27 - "encoding": "application/json", 28 - "schema": { 29 - "type": "object", 30 - "required": ["lists"], 31 - "properties": { 32 - "cursor": { "type": "string" }, 33 - "lists": { 34 - "type": "array", 35 - "items": { "type": "ref", "ref": "so.sprk.graph.defs#listView" } 36 - } 37 - } 38 - } 39 - } 40 - } 41 - } 42 - }
-34
lexicons/so/sprk/graph/getStarterPack.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.getStarterPack", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Gets a view of a starter pack.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["starterPack"], 11 - "properties": { 12 - "starterPack": { 13 - "type": "string", 14 - "format": "at-uri", 15 - "description": "Reference (AT-URI) of the starter pack record." 16 - } 17 - } 18 - }, 19 - "output": { 20 - "encoding": "application/json", 21 - "schema": { 22 - "type": "object", 23 - "required": ["starterPack"], 24 - "properties": { 25 - "starterPack": { 26 - "type": "ref", 27 - "ref": "so.sprk.graph.defs#starterPackView" 28 - } 29 - } 30 - } 31 - } 32 - } 33 - } 34 - }
-37
lexicons/so/sprk/graph/getStarterPacks.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.getStarterPacks", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get views for a list of starter packs.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["uris"], 11 - "properties": { 12 - "uris": { 13 - "type": "array", 14 - "items": { "type": "string", "format": "at-uri" }, 15 - "maxLength": 25 16 - } 17 - } 18 - }, 19 - "output": { 20 - "encoding": "application/json", 21 - "schema": { 22 - "type": "object", 23 - "required": ["starterPacks"], 24 - "properties": { 25 - "starterPacks": { 26 - "type": "array", 27 - "items": { 28 - "type": "ref", 29 - "ref": "so.sprk.graph.defs#starterPackViewBasic" 30 - } 31 - } 32 - } 33 - } 34 - } 35 - } 36 - } 37 - }
-47
lexicons/so/sprk/graph/list.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.list", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists.", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["name", "purpose", "createdAt"], 12 - "properties": { 13 - "purpose": { 14 - "type": "ref", 15 - "description": "Defines the purpose of the list (aka, moderation-oriented or curration-oriented)", 16 - "ref": "so.sprk.graph.defs#listPurpose" 17 - }, 18 - "name": { 19 - "type": "string", 20 - "maxLength": 64, 21 - "minLength": 1, 22 - "description": "Display name for list; can not be empty." 23 - }, 24 - "description": { 25 - "type": "string", 26 - "maxGraphemes": 300, 27 - "maxLength": 3000 28 - }, 29 - "descriptionFacets": { 30 - "type": "array", 31 - "items": { "type": "ref", "ref": "so.sprk.richtext.facet" } 32 - }, 33 - "avatar": { 34 - "type": "blob", 35 - "accept": ["image/png", "image/jpeg"], 36 - "maxSize": 1048576 37 - }, 38 - "labels": { 39 - "type": "union", 40 - "refs": ["com.atproto.label.defs#selfLabels"] 41 - }, 42 - "createdAt": { "type": "string", "format": "datetime" } 43 - } 44 - } 45 - } 46 - } 47 - }
-23
lexicons/so/sprk/graph/listblock.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.listblock", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "Record representing a block relationship against an entire an entire list of accounts (actors).", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["subject", "createdAt"], 12 - "properties": { 13 - "subject": { 14 - "type": "string", 15 - "format": "at-uri", 16 - "description": "Reference (AT-URI) to the mod list record." 17 - }, 18 - "createdAt": { "type": "string", "format": "datetime" } 19 - } 20 - } 21 - } 22 - } 23 - }
-28
lexicons/so/sprk/graph/listitem.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.listitem", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records.", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["subject", "list", "createdAt"], 12 - "properties": { 13 - "subject": { 14 - "type": "string", 15 - "format": "did", 16 - "description": "The account which is included on the list." 17 - }, 18 - "list": { 19 - "type": "string", 20 - "format": "at-uri", 21 - "description": "Reference (AT-URI) to the list record (so.sprk.graph.list)." 22 - }, 23 - "createdAt": { "type": "string", "format": "datetime" } 24 - } 25 - } 26 - } 27 - } 28 - }
-20
lexicons/so/sprk/graph/muteActorList.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.muteActorList", 4 - "defs": { 5 - "main": { 6 - "type": "procedure", 7 - "description": "Creates a mute relationship for the specified list of accounts. Mutes are private in Spark. Requires auth.", 8 - "input": { 9 - "encoding": "application/json", 10 - "schema": { 11 - "type": "object", 12 - "required": ["list"], 13 - "properties": { 14 - "list": { "type": "string", "format": "at-uri" } 15 - } 16 - } 17 - } 18 - } 19 - } 20 - }
-48
lexicons/so/sprk/graph/searchStarterPacks.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.searchStarterPacks", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Find starter packs matching search criteria. Does not require auth.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["q"], 11 - "properties": { 12 - "q": { 13 - "type": "string", 14 - "description": "Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." 15 - }, 16 - "limit": { 17 - "type": "integer", 18 - "minimum": 1, 19 - "maximum": 100, 20 - "default": 25 21 - }, 22 - "cursor": { 23 - "type": "string" 24 - } 25 - } 26 - }, 27 - "output": { 28 - "encoding": "application/json", 29 - "schema": { 30 - "type": "object", 31 - "required": ["starterPacks"], 32 - "properties": { 33 - "cursor": { 34 - "type": "string" 35 - }, 36 - "starterPacks": { 37 - "type": "array", 38 - "items": { 39 - "type": "ref", 40 - "ref": "so.sprk.graph.defs#starterPackViewBasic" 41 - } 42 - } 43 - } 44 - } 45 - } 46 - } 47 - } 48 - }
-51
lexicons/so/sprk/graph/starterpack.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.starterpack", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "Record defining a starter pack of actors and feeds for new users.", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["name", "list", "createdAt"], 12 - "properties": { 13 - "name": { 14 - "type": "string", 15 - "maxGraphemes": 50, 16 - "maxLength": 500, 17 - "minLength": 1, 18 - "description": "Display name for starter pack; can not be empty." 19 - }, 20 - "description": { 21 - "type": "string", 22 - "maxGraphemes": 300, 23 - "maxLength": 3000 24 - }, 25 - "descriptionFacets": { 26 - "type": "array", 27 - "items": { "type": "ref", "ref": "so.sprk.richtext.facet" } 28 - }, 29 - "list": { 30 - "type": "string", 31 - "format": "at-uri", 32 - "description": "Reference (AT-URI) to the list record." 33 - }, 34 - "feeds": { 35 - "type": "array", 36 - "maxLength": 3, 37 - "items": { "type": "ref", "ref": "#feedItem" } 38 - }, 39 - "createdAt": { "type": "string", "format": "datetime" } 40 - } 41 - } 42 - }, 43 - "feedItem": { 44 - "type": "object", 45 - "required": ["uri"], 46 - "properties": { 47 - "uri": { "type": "string", "format": "at-uri" } 48 - } 49 - } 50 - } 51 - }
-20
lexicons/so/sprk/graph/unmuteActorList.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.graph.unmuteActorList", 4 - "defs": { 5 - "main": { 6 - "type": "procedure", 7 - "description": "Unmutes the specified list of accounts. Requires auth.", 8 - "input": { 9 - "encoding": "application/json", 10 - "schema": { 11 - "type": "object", 12 - "required": ["list"], 13 - "properties": { 14 - "list": { "type": "string", "format": "at-uri" } 15 - } 16 - } 17 - } 18 - } 19 - } 20 - }
+29
lexicons/so/sprk/media/images.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "so.sprk.media.images", 4 + "description": "A set of multiple images in a Spark post.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": ["images"], 9 + "properties": { 10 + "images": { 11 + "type": "array", 12 + "items": { "type": "ref", "ref": "so.sprk.media.image" }, 13 + "maxLength": 12 14 + } 15 + } 16 + }, 17 + "view": { 18 + "type": "object", 19 + "required": ["images"], 20 + "properties": { 21 + "images": { 22 + "type": "array", 23 + "items": { "type": "ref", "ref": "so.sprk.media.image#view" }, 24 + "maxLength": 12 25 + } 26 + } 27 + } 28 + } 29 + }
+57
lexicons/so/sprk/story/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "so.sprk.story.defs", 4 + "defs": { 5 + "storyView": { 6 + "type": "object", 7 + "required": ["uri", "cid", "author", "record", "indexedAt"], 8 + "properties": { 9 + "uri": { "type": "string", "format": "at-uri" }, 10 + "cid": { "type": "string", "format": "cid" }, 11 + "author": { 12 + "type": "ref", 13 + "ref": "so.sprk.actor.defs#profileViewBasic" 14 + }, 15 + "record": { "type": "unknown" }, 16 + "media": { 17 + "type": "union", 18 + "refs": ["so.sprk.media.image#view", "so.sprk.media.video#view"] 19 + }, 20 + "indexedAt": { "type": "string", "format": "datetime" } 21 + } 22 + }, 23 + "storiesByAuthor": { 24 + "type": "object", 25 + "required": ["author", "stories"], 26 + "properties": { 27 + "author": { 28 + "type": "ref", 29 + "ref": "so.sprk.actor.defs#profileViewBasic" 30 + }, 31 + "stories": { 32 + "type": "array", 33 + "items": { "type": "ref", "ref": "#storyView" } 34 + } 35 + } 36 + }, 37 + "interaction": { 38 + "type": "object", 39 + "properties": { 40 + "item": { "type": "string", "format": "at-uri" }, 41 + "event": { 42 + "type": "string", 43 + "knownValues": [ 44 + "so.sprk.feed.defs#clickthroughItem", 45 + "so.sprk.feed.defs#clickthroughAuthor", 46 + "so.sprk.feed.defs#clickthroughReposter", 47 + "so.sprk.feed.defs#clickthroughEmbed", 48 + "so.sprk.feed.defs#interactionSeen", 49 + "so.sprk.feed.defs#interactionLike", 50 + "so.sprk.feed.defs#interactionRepost", 51 + "so.sprk.feed.defs#interactionShare" 52 + ] 53 + } 54 + } 55 + } 56 + } 57 + }
-37
lexicons/so/sprk/unspecced/defs.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.unspecced.defs", 4 - "defs": { 5 - "skeletonSearchPost": { 6 - "type": "object", 7 - "required": ["uri"], 8 - "properties": { 9 - "uri": { "type": "string", "format": "at-uri" } 10 - } 11 - }, 12 - "skeletonSearchActor": { 13 - "type": "object", 14 - "required": ["did"], 15 - "properties": { 16 - "did": { "type": "string", "format": "did" } 17 - } 18 - }, 19 - "skeletonSearchStarterPack": { 20 - "type": "object", 21 - "required": ["uri"], 22 - "properties": { 23 - "uri": { "type": "string", "format": "at-uri" } 24 - } 25 - }, 26 - "trendingTopic": { 27 - "type": "object", 28 - "required": ["topic", "link"], 29 - "properties": { 30 - "topic": { "type": "string" }, 31 - "displayName": { "type": "string" }, 32 - "description": { "type": "string" }, 33 - "link": { "type": "string" } 34 - } 35 - } 36 - } 37 - }
-20
lexicons/so/sprk/unspecced/getConfig.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.unspecced.getConfig", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get miscellaneous runtime configuration.", 8 - "output": { 9 - "encoding": "application/json", 10 - "schema": { 11 - "type": "object", 12 - "required": [], 13 - "properties": { 14 - "checkEmailConfirmed": { "type": "boolean" } 15 - } 16 - } 17 - } 18 - } 19 - } 20 - }
-40
lexicons/so/sprk/unspecced/getPopularFeedGenerators.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.unspecced.getPopularFeedGenerators", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "An unspecced view of globally popular feed generators.", 8 - "parameters": { 9 - "type": "params", 10 - "properties": { 11 - "limit": { 12 - "type": "integer", 13 - "minimum": 1, 14 - "maximum": 100, 15 - "default": 50 16 - }, 17 - "cursor": { "type": "string" }, 18 - "query": { "type": "string" } 19 - } 20 - }, 21 - "output": { 22 - "encoding": "application/json", 23 - "schema": { 24 - "type": "object", 25 - "required": ["feeds"], 26 - "properties": { 27 - "cursor": { "type": "string" }, 28 - "feeds": { 29 - "type": "array", 30 - "items": { 31 - "type": "ref", 32 - "ref": "so.sprk.feed.defs#generatorView" 33 - } 34 - } 35 - } 36 - } 37 - } 38 - } 39 - } 40 - }
-58
lexicons/so/sprk/unspecced/getSuggestionsSkeleton.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.unspecced.getSuggestionsSkeleton", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get a skeleton of suggested actors. Intended to be called and then hydrated through so.sprk.actor.getSuggestions", 8 - "parameters": { 9 - "type": "params", 10 - "properties": { 11 - "viewer": { 12 - "type": "string", 13 - "format": "did", 14 - "description": "DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking." 15 - }, 16 - "limit": { 17 - "type": "integer", 18 - "minimum": 1, 19 - "maximum": 100, 20 - "default": 50 21 - }, 22 - "cursor": { "type": "string" }, 23 - "relativeToDid": { 24 - "type": "string", 25 - "format": "did", 26 - "description": "DID of the account to get suggestions relative to. If not provided, suggestions will be based on the viewer." 27 - } 28 - } 29 - }, 30 - "output": { 31 - "encoding": "application/json", 32 - "schema": { 33 - "type": "object", 34 - "required": ["actors"], 35 - "properties": { 36 - "cursor": { "type": "string" }, 37 - "actors": { 38 - "type": "array", 39 - "items": { 40 - "type": "ref", 41 - "ref": "so.sprk.unspecced.defs#skeletonSearchActor" 42 - } 43 - }, 44 - "relativeToDid": { 45 - "type": "string", 46 - "format": "did", 47 - "description": "DID of the account these suggestions are relative to. If this is returned undefined, suggestions are based on the viewer." 48 - }, 49 - "recId": { 50 - "type": "integer", 51 - "description": "Snowflake for this recommendation, use when submitting recommendation events." 52 - } 53 - } 54 - } 55 - } 56 - } 57 - } 58 - }
-42
lexicons/so/sprk/unspecced/getTaggedSuggestions.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.unspecced.getTaggedSuggestions", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get a list of suggestions (feeds and users) tagged with categories", 8 - "parameters": { 9 - "type": "params", 10 - "properties": {} 11 - }, 12 - "output": { 13 - "encoding": "application/json", 14 - "schema": { 15 - "type": "object", 16 - "required": ["suggestions"], 17 - "properties": { 18 - "suggestions": { 19 - "type": "array", 20 - "items": { 21 - "type": "ref", 22 - "ref": "#suggestion" 23 - } 24 - } 25 - } 26 - } 27 - } 28 - }, 29 - "suggestion": { 30 - "type": "object", 31 - "required": ["tag", "subjectType", "subject"], 32 - "properties": { 33 - "tag": { "type": "string" }, 34 - "subjectType": { 35 - "type": "string", 36 - "knownValues": ["actor", "feed"] 37 - }, 38 - "subject": { "type": "string", "format": "uri" } 39 - } 40 - } 41 - } 42 - }
-49
lexicons/so/sprk/unspecced/getTrendingTopics.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.unspecced.getTrendingTopics", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get a list of trending topics", 8 - "parameters": { 9 - "type": "params", 10 - "properties": { 11 - "viewer": { 12 - "type": "string", 13 - "format": "did", 14 - "description": "DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking." 15 - }, 16 - "limit": { 17 - "type": "integer", 18 - "minimum": 1, 19 - "maximum": 25, 20 - "default": 10 21 - } 22 - } 23 - }, 24 - "output": { 25 - "encoding": "application/json", 26 - "schema": { 27 - "type": "object", 28 - "required": ["topics", "suggested"], 29 - "properties": { 30 - "topics": { 31 - "type": "array", 32 - "items": { 33 - "type": "ref", 34 - "ref": "so.sprk.unspecced.defs#trendingTopic" 35 - } 36 - }, 37 - "suggested": { 38 - "type": "array", 39 - "items": { 40 - "type": "ref", 41 - "ref": "so.sprk.unspecced.defs#trendingTopic" 42 - } 43 - } 44 - } 45 - } 46 - } 47 - } 48 - } 49 - }
-61
lexicons/so/sprk/unspecced/searchActorsSkeleton.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.unspecced.searchActorsSkeleton", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Backend Actors (profile) search, returns only skeleton.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["q"], 11 - "properties": { 12 - "q": { 13 - "type": "string", 14 - "description": "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. For typeahead search, only simple term match is supported, not full syntax." 15 - }, 16 - "viewer": { 17 - "type": "string", 18 - "format": "did", 19 - "description": "DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking." 20 - }, 21 - "typeahead": { 22 - "type": "boolean", 23 - "description": "If true, acts as fast/simple 'typeahead' query." 24 - }, 25 - "limit": { 26 - "type": "integer", 27 - "minimum": 1, 28 - "maximum": 100, 29 - "default": 25 30 - }, 31 - "cursor": { 32 - "type": "string", 33 - "description": "Optional pagination mechanism; may not necessarily allow scrolling through entire result set." 34 - } 35 - } 36 - }, 37 - "output": { 38 - "encoding": "application/json", 39 - "schema": { 40 - "type": "object", 41 - "required": ["actors"], 42 - "properties": { 43 - "cursor": { "type": "string" }, 44 - "hitsTotal": { 45 - "type": "integer", 46 - "description": "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits." 47 - }, 48 - "actors": { 49 - "type": "array", 50 - "items": { 51 - "type": "ref", 52 - "ref": "so.sprk.unspecced.defs#skeletonSearchActor" 53 - } 54 - } 55 - } 56 - } 57 - }, 58 - "errors": [{ "name": "BadQueryString" }] 59 - } 60 - } 61 - }
-104
lexicons/so/sprk/unspecced/searchPostsSkeleton.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.unspecced.searchPostsSkeleton", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Backend Posts search, returns only skeleton", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["q"], 11 - "properties": { 12 - "q": { 13 - "type": "string", 14 - "description": "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." 15 - }, 16 - "sort": { 17 - "type": "string", 18 - "knownValues": ["top", "latest"], 19 - "default": "latest", 20 - "description": "Specifies the ranking order of results." 21 - }, 22 - "since": { 23 - "type": "string", 24 - "description": "Filter results for posts after the indicated datetime (inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYYY-MM-DD)." 25 - }, 26 - "until": { 27 - "type": "string", 28 - "description": "Filter results for posts before the indicated datetime (not inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYY-MM-DD)." 29 - }, 30 - "mentions": { 31 - "type": "string", 32 - "format": "at-identifier", 33 - "description": "Filter to posts which mention the given account. Handles are resolved to DID before query-time. Only matches rich-text facet mentions." 34 - }, 35 - "author": { 36 - "type": "string", 37 - "format": "at-identifier", 38 - "description": "Filter to posts by the given account. Handles are resolved to DID before query-time." 39 - }, 40 - "lang": { 41 - "type": "string", 42 - "format": "language", 43 - "description": "Filter to posts in the given language. Expected to be based on post language field, though server may override language detection." 44 - }, 45 - "domain": { 46 - "type": "string", 47 - "description": "Filter to posts with URLs (facet links or embeds) linking to the given domain (hostname). Server may apply hostname normalization." 48 - }, 49 - "url": { 50 - "type": "string", 51 - "format": "uri", 52 - "description": "Filter to posts with links (facet links or embeds) pointing to this URL. Server may apply URL normalization or fuzzy matching." 53 - }, 54 - "tag": { 55 - "type": "array", 56 - "items": { 57 - "type": "string", 58 - "maxLength": 640, 59 - "maxGraphemes": 64 60 - }, 61 - "description": "Filter to posts with the given tag (hashtag), based on rich-text facet or tag field. Do not include the hash (#) prefix. Multiple tags can be specified, with 'AND' matching." 62 - }, 63 - "viewer": { 64 - "type": "string", 65 - "format": "did", 66 - "description": "DID of the account making the request (not included for public/unauthenticated queries). Used for 'from:me' queries." 67 - }, 68 - "limit": { 69 - "type": "integer", 70 - "minimum": 1, 71 - "maximum": 100, 72 - "default": 25 73 - }, 74 - "cursor": { 75 - "type": "string", 76 - "description": "Optional pagination mechanism; may not necessarily allow scrolling through entire result set." 77 - } 78 - } 79 - }, 80 - "output": { 81 - "encoding": "application/json", 82 - "schema": { 83 - "type": "object", 84 - "required": ["posts"], 85 - "properties": { 86 - "cursor": { "type": "string" }, 87 - "hitsTotal": { 88 - "type": "integer", 89 - "description": "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits." 90 - }, 91 - "posts": { 92 - "type": "array", 93 - "items": { 94 - "type": "ref", 95 - "ref": "so.sprk.unspecced.defs#skeletonSearchPost" 96 - } 97 - } 98 - } 99 - } 100 - }, 101 - "errors": [{ "name": "BadQueryString" }] 102 - } 103 - } 104 - }
-63
lexicons/so/sprk/unspecced/searchStarterPacksSkeleton.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "so.sprk.unspecced.searchStarterPacksSkeleton", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Backend Starter Pack search, returns only skeleton.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["q"], 11 - "properties": { 12 - "q": { 13 - "type": "string", 14 - "description": "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." 15 - }, 16 - "viewer": { 17 - "type": "string", 18 - "format": "did", 19 - "description": "DID of the account making the request (not included for public/unauthenticated queries)." 20 - }, 21 - "limit": { 22 - "type": "integer", 23 - "minimum": 1, 24 - "maximum": 100, 25 - "default": 25 26 - }, 27 - "cursor": { 28 - "type": "string", 29 - "description": "Optional pagination mechanism; may not necessarily allow scrolling through entire result set." 30 - } 31 - } 32 - }, 33 - "output": { 34 - "encoding": "application/json", 35 - "schema": { 36 - "type": "object", 37 - "required": ["starterPacks"], 38 - "properties": { 39 - "cursor": { 40 - "type": "string" 41 - }, 42 - "hitsTotal": { 43 - "type": "integer", 44 - "description": "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits." 45 - }, 46 - "starterPacks": { 47 - "type": "array", 48 - "items": { 49 - "type": "ref", 50 - "ref": "so.sprk.unspecced.defs#skeletonSearchStarterPack" 51 - } 52 - } 53 - } 54 - } 55 - }, 56 - "errors": [ 57 - { 58 - "name": "BadQueryString" 59 - } 60 - ] 61 - } 62 - } 63 - }
+5 -5
utils/embed-normalizer.ts
··· 59 59 const embedObj = embed as Record<string, unknown>; 60 60 61 61 if ( 62 - embedObj.$type === "so.sprk.embed.video" && embedObj.video && 62 + embedObj.$type === "so.sprk.media.video" && embedObj.video && 63 63 typeof embedObj.video === "object" 64 64 ) { 65 65 const video = embedObj.video as Record<string, unknown>; ··· 77 77 const cidString = toStringFn.call(ref); 78 78 // Return cleaned up structure without 'original' field 79 79 return { 80 - $type: "so.sprk.embed.video", 80 + $type: "so.sprk.media.video", 81 81 video: { 82 82 $type: "blob", 83 83 ref: { $link: cidString }, ··· 92 92 } else if ((ref as CidRef).$link) { 93 93 // Already normalized, return cleaned up structure 94 94 return { 95 - $type: "so.sprk.embed.video", 95 + $type: "so.sprk.media.video", 96 96 video: { 97 97 $type: "blob", 98 98 ref: { $link: (ref as CidRef).$link }, ··· 105 105 } 106 106 107 107 if ( 108 - embedObj.$type === "so.sprk.embed.images" && Array.isArray(embedObj.images) 108 + embedObj.$type === "so.sprk.media.images" && Array.isArray(embedObj.images) 109 109 ) { 110 110 const normalizedImages = embedObj.images.map((img: unknown) => { 111 111 if ( ··· 162 162 }); 163 163 164 164 return { 165 - $type: "so.sprk.embed.images", 165 + $type: "so.sprk.media.images", 166 166 images: normalizedImages, 167 167 }; 168 168 }
-109
utils/embed-transformer.ts
··· 1 - import type * as SoSprkEmbedImages from "../lex/types/so/sprk/embed/images.ts"; 2 - import { 3 - EmbedImage, 4 - PostEmbed, 5 - VideoMappingDocument, 6 - } from "../data-plane/db/models.ts"; 7 - import { ServerConfig } from "../config.ts"; 8 - 9 - interface ImageTransformOptions { 10 - /** If true, only return the first image (useful for stories) */ 11 - firstImageOnly?: boolean; 12 - } 13 - 14 - export function transformImagesEmbed( 15 - embed: PostEmbed, 16 - authorDid: string, 17 - options: ImageTransformOptions = {}, 18 - ) { 19 - const { firstImageOnly = false } = options; 20 - 21 - if (!embed.images) { 22 - return undefined; 23 - } 24 - 25 - const imagesToProcess = firstImageOnly 26 - ? [embed.images[0]].filter(Boolean) 27 - : embed.images; 28 - 29 - return { 30 - $type: "so.sprk.embed.images#view", 31 - images: imagesToProcess.map( 32 - (img: EmbedImage): SoSprkEmbedImages.ViewImage => ({ 33 - thumb: 34 - `https://media.sprk.so/img/medium/${authorDid}/${img.image.ref.$link}/webp`, 35 - fullsize: 36 - `https://media.sprk.so/img/full/${authorDid}/${img.image.ref.$link}/webp`, 37 - alt: img.alt ?? "", 38 - aspectRatio: img.aspectRatio || undefined, 39 - }), 40 - ), 41 - } as const; 42 - } 43 - 44 - export function transformVideoEmbed( 45 - embed: PostEmbed, 46 - authorDid: string, 47 - cfg: ServerConfig, 48 - videoMapping?: VideoMappingDocument | null, 49 - isStory = false, 50 - ) { 51 - if (!embed.video) { 52 - return undefined; 53 - } 54 - 55 - let playlist: string; 56 - let thumbnail: string; 57 - 58 - if (videoMapping) { 59 - playlist = `${cfg.hlsCdn}/${videoMapping.bunnyGuid}/playlist.m3u8`; 60 - thumbnail = `${cfg.hlsCdn}/${videoMapping.bunnyGuid}/thumbnail.jpg`; 61 - } else if (isStory) { 62 - playlist = 63 - `https://media.sprk.so/video/${authorDid}/${embed.video.ref.$link}`; 64 - thumbnail = 65 - `https://thumb.sprk.so/${authorDid}/${embed.video.ref.$link}/thumbnail`; 66 - } else { 67 - playlist = 68 - `${cfg.videoCdn}/watch/${authorDid}/${embed.video.ref.$link}/playlist.m3u8`; 69 - thumbnail = 70 - `https://thumb.sprk.so/${authorDid}/${embed.video.ref.$link}/thumbnail`; 71 - } 72 - 73 - return { 74 - $type: "so.sprk.embed.video#view", 75 - cid: embed.video.ref.$link, 76 - alt: embed.alt, 77 - playlist, 78 - thumbnail, 79 - } as const; 80 - } 81 - 82 - export function transformEmbed( 83 - embed: PostEmbed | null, 84 - authorDid: string, 85 - cfg: ServerConfig, 86 - videoMapping?: VideoMappingDocument | null, 87 - options: ImageTransformOptions = {}, 88 - isStory = false, 89 - ) { 90 - if (!embed) { 91 - return undefined; 92 - } 93 - 94 - if (embed.$type === "so.sprk.embed.images") { 95 - return transformImagesEmbed(embed, authorDid, options); 96 - } 97 - 98 - if (embed.$type === "so.sprk.embed.video") { 99 - return transformVideoEmbed( 100 - embed, 101 - authorDid, 102 - cfg, 103 - videoMapping, 104 - isStory, 105 - ); 106 - } 107 - 108 - return undefined; 109 - }
+109
utils/media-transformer.ts
··· 1 + import type * as SoSprkMediaImage from "../lex/types/so/sprk/media/image.ts"; 2 + import { 3 + ImageMedia, 4 + PostMedia, 5 + VideoMappingDocument, 6 + } from "../data-plane/db/models.ts"; 7 + import { ServerConfig } from "../config.ts"; 8 + 9 + interface ImageTransformOptions { 10 + /** If true, only return the first image (useful for stories) */ 11 + firstImageOnly?: boolean; 12 + } 13 + 14 + export function transformImagesMedia( 15 + media: PostMedia, 16 + authorDid: string, 17 + options: ImageTransformOptions = {}, 18 + ) { 19 + const { firstImageOnly = false } = options; 20 + 21 + if (!media.images) { 22 + return undefined; 23 + } 24 + 25 + const imagesToProcess = firstImageOnly 26 + ? [media.images[0]].filter(Boolean) 27 + : media.images; 28 + 29 + return { 30 + $type: "so.sprk.media.images#view", 31 + images: imagesToProcess.map( 32 + (img: ImageMedia): SoSprkMediaImage.View => ({ 33 + thumb: 34 + `https://media.sprk.so/img/medium/${authorDid}/${img.ref.$link}/webp`, 35 + fullsize: 36 + `https://media.sprk.so/img/full/${authorDid}/${img.ref.$link}/webp`, 37 + alt: img.alt ?? "", 38 + aspectRatio: img.aspectRatio || undefined, 39 + }), 40 + ), 41 + } as const; 42 + } 43 + 44 + export function transformVideoMedia( 45 + media: PostMedia, 46 + authorDid: string, 47 + cfg: ServerConfig, 48 + videoMapping?: VideoMappingDocument | null, 49 + isStory = false, 50 + ) { 51 + if (!media.video) { 52 + return undefined; 53 + } 54 + 55 + let playlist: string; 56 + let thumbnail: string; 57 + 58 + if (videoMapping) { 59 + playlist = `${cfg.hlsCdn}/${videoMapping.bunnyGuid}/playlist.m3u8`; 60 + thumbnail = `${cfg.hlsCdn}/${videoMapping.bunnyGuid}/thumbnail.jpg`; 61 + } else if (isStory) { 62 + playlist = 63 + `https://media.sprk.so/video/${authorDid}/${media.video.ref.$link}`; 64 + thumbnail = 65 + `https://thumb.sprk.so/${authorDid}/${media.video.ref.$link}/thumbnail`; 66 + } else { 67 + playlist = 68 + `${cfg.videoCdn}/watch/${authorDid}/${media.video.ref.$link}/playlist.m3u8`; 69 + thumbnail = 70 + `https://thumb.sprk.so/${authorDid}/${media.video.ref.$link}/thumbnail`; 71 + } 72 + 73 + return { 74 + $type: "so.sprk.media.video#view", 75 + cid: media.video.ref.$link, 76 + alt: media.video.alt, 77 + playlist, 78 + thumbnail, 79 + } as const; 80 + } 81 + 82 + export function transformMedia( 83 + media: PostMedia | undefined, 84 + authorDid: string, 85 + cfg: ServerConfig, 86 + videoMapping?: VideoMappingDocument | null, 87 + options: ImageTransformOptions = {}, 88 + isStory = false, 89 + ) { 90 + if (!media) { 91 + return undefined; 92 + } 93 + 94 + if (media.$type === "so.sprk.media.images") { 95 + return transformImagesMedia(media, authorDid, options); 96 + } 97 + 98 + if (media.$type === "so.sprk.media.video") { 99 + return transformVideoMedia( 100 + media, 101 + authorDid, 102 + cfg, 103 + videoMapping, 104 + isStory, 105 + ); 106 + } 107 + 108 + return undefined; 109 + }
+12 -10
utils/post-transformer.ts
··· 4 4 import type * as SoSprkFeedPost from "../lex/types/so/sprk/feed/post.ts"; 5 5 import { AppContext } from "../context.ts"; 6 6 import { transformAudiosToAudioViews } from "./audio-transformer.ts"; 7 - import { transformEmbed } from "./embed-transformer.ts"; 7 + import { transformMedia } from "./media-transformer.ts"; 8 8 import { createProfileViewBasic } from "./profile-helper.ts"; 9 9 10 10 // Transform DB posts to PostView format ··· 61 61 ctx.db.models.VideoMapping.find({ 62 62 key: { 63 63 $in: posts 64 - .filter((p) => p.embed?.$type === "so.sprk.embed.video") 65 - .map((p) => `${p.authorDid}-${p.embed?.video?.ref.$link}`), 64 + .filter((p) => p.media?.$type === "so.sprk.embed.video") 65 + .map((p) => `${p.authorDid}-${p.media?.video?.ref.$link}`), 66 66 }, 67 67 }), 68 68 // Get viewer likes ··· 111 111 const audioViewsMap = new Map(audioViews.map((av) => [av.uri, av])); 112 112 113 113 return posts.map((post) => { 114 - const videoMapping = post.embed?.$type === "so.sprk.embed.video" 114 + const videoMapping = post.media?.$type === "so.sprk.media.video" 115 115 ? videoMappingsMap.get( 116 - `${post.authorDid}-${post.embed.video?.ref.$link}`, 116 + `${post.authorDid}-${post.media.video?.ref.$link}`, 117 117 ) || null 118 118 : null; 119 119 120 - const embed = transformEmbed( 121 - post.embed, 120 + const embed = transformMedia( 121 + post.media, 122 122 post.authorDid, 123 123 ctx.cfg, 124 124 videoMapping, ··· 140 140 author: authorsMap.get(post.authorDid)!, 141 141 record: { 142 142 $type: "so.sprk.feed.post", 143 - text: post.text, 144 - embed: post.embed as SoSprkFeedPost.Record["embed"], 145 - facets: post.facets, 143 + caption: { 144 + text: post.caption?.text, 145 + facets: post.caption?.facets, 146 + }, 147 + media: post.media as SoSprkFeedPost.Record["media"], 146 148 langs: post.langs, 147 149 tags: post.tags, 148 150 createdAt: post.createdAt,
+4 -17
utils/profile-helper.ts
··· 356 356 }), 357 357 358 358 // Count posts 359 - ctx.db.models.Post.countDocuments({ 359 + await ctx.db.models.Post.countDocuments({ 360 360 authorDid: actorDid, 361 - reply: null, 362 361 }), 363 362 364 363 // Check for feed generators (bsky + sprk combined) 365 - (async () => { 366 - try { 367 - const [bskyCount, sprkCount] = await Promise.all([ 368 - ctx.db.models.BskyGenerator.countDocuments({ 369 - authorDid: actorDid, 370 - }), 371 - ctx.db.models.SprkGenerator.countDocuments({ 372 - authorDid: actorDid, 373 - }), 374 - ]); 375 - return bskyCount + sprkCount; 376 - } catch (_error) { 377 - return 0; 378 - } 379 - })(), 364 + await ctx.db.models.Generator.countDocuments({ 365 + authorDid: actorDid, 366 + }), 380 367 381 368 // Viewer state queries (only if viewer is authenticated) 382 369 viewerDid
+6 -8
utils/story-transformer.ts
··· 1 - import type * as SoSprkFeedDefs from "../lex/types/so/sprk/feed/defs.ts"; 1 + import type * as SoSprkStoryDefs from "../lex/types/so/sprk/story/defs.ts"; 2 2 import { StoryDocument } from "../data-plane/db/models.ts"; 3 - import { transformEmbed } from "./embed-transformer.ts"; 3 + import { transformMedia } from "./media-transformer.ts"; 4 4 import { createProfileViewBasic } from "./profile-helper.ts"; 5 5 import { AppContext } from "../context.ts"; 6 6 ··· 8 8 export async function transformStoryToStoryView( 9 9 story: StoryDocument, 10 10 ctx: AppContext, 11 - ): Promise<SoSprkFeedDefs.StoryView> { 11 + ): Promise<SoSprkStoryDefs.StoryView> { 12 12 // Create the author object with stories 13 13 const authorView = await createProfileViewBasic( 14 14 story.authorDid, 15 15 ctx, 16 16 ); 17 17 18 - const embedView = transformEmbed( 18 + const embedView = transformMedia( 19 19 story.media, 20 20 story.authorDid, 21 21 ctx.cfg, ··· 35 35 media: story.media, 36 36 sound: story.sound, 37 37 labels: story.labels, 38 - tags: story.tags, 39 38 createdAt: story.createdAt, 40 39 }, 41 40 indexedAt: story.indexedAt, ··· 46 45 export async function transformStoriesToStoryViews( 47 46 stories: StoryDocument[], 48 47 ctx: AppContext, 49 - ): Promise<SoSprkFeedDefs.StoryView[]> { 48 + ): Promise<SoSprkStoryDefs.StoryView[]> { 50 49 if (stories.length === 0) { 51 50 return []; 52 51 } ··· 66 65 return stories.map((story) => { 67 66 const authorView = authorsMap.get(story.authorDid)!; 68 67 69 - const embedView = transformEmbed( 68 + const embedView = transformMedia( 70 69 story.media, 71 70 story.authorDid, 72 71 ctx.cfg, ··· 86 85 media: story.media, 87 86 sound: story.sound, 88 87 labels: story.labels, 89 - tags: story.tags, 90 88 createdAt: story.createdAt, 91 89 }, 92 90 indexedAt: story.indexedAt,
+307 -72
views/index.ts
··· 3 3 import { 4 4 FeedViewPost, 5 5 isPostView, 6 + isReplyView, 6 7 PostView, 7 8 ReasonPin, 8 9 ReasonRepost, 9 10 ReplyRef, 11 + ReplyView, 12 + ThreadContext, 13 + ThreadViewPost, 10 14 } from "../lex/types/so/sprk/feed/defs.ts"; 11 - import { isRecord as isPostRecord } from "../lex/types/so/sprk/feed/post.ts"; 15 + import { 16 + isRecord as isReplyRecord, 17 + Record as ReplyRecord, 18 + } from "../lex/types/so/sprk/feed/reply.ts"; 19 + import { ids } from "../lex/lexicons.ts"; 12 20 import { 13 21 KnownFollowers, 14 22 ProfileView, ··· 18 26 } from "../lex/types/so/sprk/actor/defs.ts"; 19 27 import { 20 28 BlockedPost, 21 - Embed, 22 - EmbedView, 23 - ImagesEmbed, 24 - ImagesEmbedView, 25 - isImagesEmbed, 26 - isVideoEmbed, 29 + ImagesMedia, 30 + ImagesMediaView, 31 + isImagesMedia, 32 + isVideoMedia, 27 33 MaybePostView, 34 + Media, 35 + MediaView, 28 36 NotFoundPost, 29 - VideoEmbed, 30 - VideoEmbedView, 37 + VideoMedia, 38 + VideoMediaView, 31 39 } from "./types.ts"; 40 + import { 41 + Main as ImageMedia, 42 + View as ImageView, 43 + } from "../lex/types/so/sprk/media/image.ts"; 44 + import { AudioView } from "../lex/types/so/sprk/sound/defs.ts"; 32 45 import { INVALID_HANDLE } from "@atp/syntax"; 33 46 import { cidFromBlobJson } from "./util.ts"; 34 47 import { uriToDid } from "../utils/uris.ts"; 35 48 import { mapDefined } from "@atp/common"; 36 49 import { FeedItem, Repost } from "../hydration/feed.ts"; 37 - import { $Typed } from "../lex/util.ts"; 50 + import { 51 + QueryParams as GetThreadQueryParams, 52 + ThreadItem, 53 + } from "../lex/types/so/sprk/feed/getPostThread.ts"; 54 + import { $Typed, Un$Typed } from "../lex/util.ts"; 55 + import { BlobRef } from "@atp/lexicon"; 38 56 39 57 export class Views { 40 58 public indexedAtEpoch?: Date | undefined; ··· 59 77 this.mediaCdn = opts?.mediaCdn; 60 78 } 61 79 80 + thread( 81 + skeleton: { anchor: string; uris: string[] }, 82 + state: HydrationState, 83 + opts: { 84 + depth?: GetThreadQueryParams["depth"]; 85 + } = {}, 86 + ): ThreadItem[] { 87 + const maxDepth = opts.depth !== undefined && opts.depth >= 0 88 + ? opts.depth 89 + : undefined; 90 + const depthCache = new Map<string, number>(); 91 + depthCache.set(skeleton.anchor, 0); 92 + 93 + const computeDepth = (uri: string, stack = new Set<string>()): number => { 94 + const cached = depthCache.get(uri); 95 + if (cached !== undefined) return cached; 96 + 97 + if (stack.has(uri)) { 98 + depthCache.set(uri, 0); 99 + return 0; 100 + } 101 + 102 + stack.add(uri); 103 + 104 + const replyInfo = state.replies?.get(uri) ?? undefined; 105 + const parentUri = replyInfo?.record.reply?.parent.uri; 106 + if (!parentUri) { 107 + depthCache.set(uri, 0); 108 + stack.delete(uri); 109 + return 0; 110 + } 111 + 112 + const depth = computeDepth(parentUri, stack) + 1; 113 + depthCache.set(uri, depth); 114 + stack.delete(uri); 115 + return depth; 116 + }; 117 + 118 + const items: ThreadItem[] = []; 119 + for (const uri of skeleton.uris) { 120 + const depth = computeDepth(uri); 121 + if (!Number.isFinite(depth)) continue; 122 + if (maxDepth !== undefined && depth > maxDepth) continue; 123 + 124 + const value = this.threadItemValue(uri, state); 125 + items.push({ 126 + $type: "so.sprk.feed.getPostThread#threadItem", 127 + uri, 128 + depth, 129 + value, 130 + }); 131 + } 132 + 133 + return items; 134 + } 135 + 136 + threadItemValue( 137 + uri: string, 138 + state: HydrationState, 139 + ): ThreadItem["value"] { 140 + const authorDid = uriToDid(uri); 141 + 142 + if (!state.ctx?.include3pBlocks) { 143 + const blockInfo = state.postBlocks?.get(uri) ?? undefined; 144 + if (blockInfo && (blockInfo.parent || blockInfo.root)) { 145 + return this.blockedPost(uri, authorDid, state); 146 + } 147 + } 148 + 149 + if (this.viewerBlockExists(authorDid, state)) { 150 + return this.blockedPost(uri, authorDid, state); 151 + } 152 + 153 + const threadViewPost = this.threadViewPost(uri, state); 154 + if (threadViewPost) { 155 + return threadViewPost; 156 + } 157 + return this.notFoundPost(uri); 158 + } 159 + 160 + threadViewPost( 161 + uri: string, 162 + state: HydrationState, 163 + ): $Typed<ThreadViewPost> | undefined { 164 + const postView = this.post(uri, state) ?? 165 + this.reply(uri, state); 166 + if (postView) { 167 + return { 168 + $type: "so.sprk.feed.defs#threadViewPost", 169 + post: postView, 170 + threadContext: this.threadContext(uri, state), 171 + } as $Typed<ThreadViewPost>; 172 + } 173 + } 174 + 175 + threadContext( 176 + uri: string, 177 + state: HydrationState, 178 + ): ThreadContext | undefined { 179 + const context = state.threadContexts?.get(uri) ?? undefined; 180 + if (!context) return undefined; 181 + const { like } = context; 182 + return { 183 + $type: "so.sprk.feed.defs#threadContext", 184 + ...(like ? { rootAuthorLike: like } : {}), 185 + }; 186 + } 187 + 62 188 post( 63 189 uri: string, 64 190 state: HydrationState, 65 - depth = 0, 66 - ): PostView | undefined { 67 - const post = state.posts?.get(uri); 68 - if (!post) return; 191 + ): Un$Typed<PostView> | undefined { 192 + const recordInfo = state.posts?.get(uri) ?? state.replies?.get(uri); 193 + if (!recordInfo) return; 194 + 195 + const parsedUri = new AtUri(uri); 196 + const collection = parsedUri.collection; 197 + const isReply = collection === ids.SoSprkFeedReply; 198 + const authorDid = parsedUri.hostname; 199 + const author = this.profileBasic(authorDid, state); 200 + if (!author) return; 201 + 202 + const postAgg = state.postAggs?.get(uri); 203 + const replyAgg = state.replyAggs?.get(uri); 204 + const repliesCount = isReply 205 + ? replyAgg?.replies ?? 0 206 + : postAgg?.replies ?? 0; 207 + const likeCount = isReply ? replyAgg?.likes ?? 0 : postAgg?.likes ?? 0; 208 + const repostCount = !isReply ? postAgg?.reposts ?? 0 : undefined; 209 + const viewer = state.postViewers?.get(uri); 210 + const mediaRecord = !isReply && "media" in recordInfo.record 211 + ? recordInfo.record.media 212 + : undefined; 213 + 214 + return { 215 + uri, 216 + cid: recordInfo.cid, 217 + author, 218 + record: recordInfo.record, 219 + media: mediaRecord 220 + ? this.media(uri, mediaRecord as Media, state) 221 + : undefined, 222 + replyCount: repliesCount, 223 + repostCount, 224 + likeCount, 225 + indexedAt: this.indexedAt(recordInfo)?.toISOString() ?? 226 + new Date().toISOString(), 227 + viewer: viewer 228 + ? { 229 + repost: viewer.repost, 230 + like: viewer.like, 231 + } 232 + : undefined, 233 + }; 234 + } 235 + 236 + reply( 237 + uri: string, 238 + state: HydrationState, 239 + ): Un$Typed<ReplyView> | undefined { 240 + const replyInfo = state.replies?.get(uri); 241 + if (!replyInfo) return; 242 + 69 243 const parsedUri = new AtUri(uri); 70 244 const authorDid = parsedUri.hostname; 71 245 const author = this.profileBasic(authorDid, state); 72 246 if (!author) return; 73 - const aggs = state.postAggs?.get(uri); 247 + 248 + const aggs = state.replyAggs?.get(uri); 74 249 const viewer = state.postViewers?.get(uri); 250 + 75 251 return { 76 252 uri, 77 - cid: post.cid, 253 + cid: replyInfo.cid, 78 254 author, 79 - record: post.record, 80 - embed: depth < 2 && post.record.embed 81 - ? this.embed(uri, post.record.embed, state) 255 + record: replyInfo.record, 256 + media: replyInfo.record.media 257 + ? this.imageMedia(uri, replyInfo.record.media as ImageMedia) 82 258 : undefined, 83 259 replyCount: aggs?.replies ?? 0, 84 - repostCount: aggs?.reposts ?? 0, 85 260 likeCount: aggs?.likes ?? 0, 86 - indexedAt: this.indexedAt(post)?.toISOString() ?? 261 + indexedAt: this.indexedAt(replyInfo)?.toISOString() ?? 87 262 new Date().toISOString(), 88 263 viewer: viewer 89 264 ? { ··· 110 285 } 111 286 const post = this.post(item.post.uri, state); 112 287 if (!post) return; 113 - const reply = this.replyRef(item.post.uri, state); 114 288 return { 115 289 post, 116 290 reason, 117 - reply, 118 291 }; 119 292 } 120 293 121 294 replyRef(uri: string, state: HydrationState): ReplyRef | undefined { 122 - const postRecord = state.posts?.get(uri.toString())?.record; 123 - if (!postRecord?.reply) return; 124 - let root = this.maybePost(postRecord.reply.root.uri, state); 125 - let parent = this.maybePost(postRecord.reply.parent.uri, state); 295 + const replyRecord = state.replies?.get(uri)?.record; 296 + if (!replyRecord?.reply) return; 297 + 298 + let root = this.maybePost(replyRecord.reply.root.uri, state); 299 + let parent = this.maybePost(replyRecord.reply.parent.uri, state); 300 + const parentForContext = parent; 301 + 126 302 if (!state.ctx?.include3pBlocks) { 127 - const childBlocks = state.postBlocks?.get(uri); 128 - const parentBlocks = state.postBlocks?.get(parent.uri); 129 - // if child blocks parent, block parent 130 - if (isPostView(parent) && childBlocks?.parent) { 303 + const childBlocks = state.postBlocks?.get(uri) ?? undefined; 304 + const parentBlocks = state.postBlocks?.get(parent.uri) ?? undefined; 305 + 306 + if ( 307 + (isPostView(parent) || isReplyView(parent)) && 308 + childBlocks?.parent 309 + ) { 131 310 parent = this.blockedPost(parent.uri, parent.author.did, state); 132 311 } 133 - // if child or parent blocks root, block root 134 - if (isPostView(root) && (childBlocks?.root || parentBlocks?.root)) { 312 + 313 + if ( 314 + (isPostView(root) || isReplyView(root)) && 315 + (childBlocks?.root || parentBlocks?.root) 316 + ) { 135 317 root = this.blockedPost(root.uri, root.author.did, state); 136 318 } 137 319 } 320 + 138 321 let grandparentAuthor: ProfileViewBasic | undefined; 139 322 if ( 140 - isPostView(parent) && 141 - isPostRecord(parent.record) && 142 - parent.record.reply 323 + isReplyView(parentForContext) && 324 + isReplyRecord(parentForContext.record) 143 325 ) { 144 - grandparentAuthor = this.profileBasic( 145 - // @ts-expect-error isValidPostRecord(parent.record) should be used but the "parent" is not IPDL decoded 146 - creatorFromUri(parent.record.reply.parent.uri), 147 - state, 148 - ); 326 + const grandparentUri = 327 + (parentForContext.record as ReplyRecord).reply.parent.uri; 328 + if (grandparentUri) { 329 + grandparentAuthor = this.profileBasic( 330 + uriToDid(grandparentUri), 331 + state, 332 + ); 333 + } 149 334 } 335 + 150 336 return { 151 337 root, 152 338 parent, ··· 176 362 } 177 363 178 364 maybePost(uri: string, state: HydrationState): $Typed<MaybePostView> { 365 + const reply = this.reply(uri, state); 366 + if (reply) { 367 + if (this.viewerBlockExists(reply.author.did, state)) { 368 + return this.blockedPost(uri, reply.author.did, state); 369 + } 370 + return { 371 + ...reply, 372 + $type: "so.sprk.feed.defs#replyView", 373 + }; 374 + } 375 + 179 376 const post = this.post(uri, state); 180 377 if (!post) { 181 378 return this.notFoundPost(uri); ··· 221 418 originatorBlocked: boolean; 222 419 authorMuted: boolean; 223 420 authorBlocked: boolean; 224 - ancestorAuthorBlocked: boolean; 225 421 } { 226 422 const authorDid = uriToDid(item.post.uri); 227 423 const originatorDid = item.repost ? uriToDid(item.repost.uri) : authorDid; 228 - const post = state.posts?.get(item.post.uri); 229 - const parentUri = post?.record.reply?.parent.uri; 230 - const parentAuthorDid = parentUri && uriToDid(parentUri); 231 - const parent = parentUri ? state.posts?.get(parentUri) : undefined; 232 - const grandparentUri = parent?.record.reply?.parent.uri; 233 - const grandparentAuthorDid = grandparentUri && uriToDid(grandparentUri); 424 + 234 425 return { 235 426 originatorMuted: this.viewerMuteExists(originatorDid, state), 236 427 originatorBlocked: this.viewerBlockExists(originatorDid, state), 237 428 authorMuted: this.viewerMuteExists(authorDid, state), 238 429 authorBlocked: this.viewerBlockExists(authorDid, state), 239 - ancestorAuthorBlocked: 240 - (!!parentAuthorDid && this.viewerBlockExists(parentAuthorDid, state)) || 241 - (!!grandparentAuthorDid && 242 - this.viewerBlockExists(grandparentAuthorDid, state)), 243 430 }; 244 431 } 245 432 ··· 368 555 return { count: knownFollowers.count, followers }; 369 556 } 370 557 371 - embed( 558 + media( 372 559 postUri: string, 373 - embed: Embed | { $type: string }, 560 + media: Media | { $type: string }, 374 561 state?: HydrationState, 375 - ): (EmbedView & { $type: string }) | undefined { 376 - if (isImagesEmbed(embed)) { 377 - return this.imagesEmbed(uriToDid(postUri), embed); 378 - } else if (isVideoEmbed(embed)) { 562 + ): (MediaView & { $type: string }) | undefined { 563 + if (isImagesMedia(media)) { 564 + return this.imagesMedia(uriToDid(postUri), media); 565 + } else if (isVideoMedia(media)) { 379 566 const authorDid = uriToDid(postUri); 380 - const videoCid = embed.video ? cidFromBlobJson(embed.video) : ""; 567 + const videoCid = media.video ? cidFromBlobJson(media.video) : ""; 381 568 const videoMappingKey = `${authorDid}-${videoCid}`; 382 569 const videoMapping = state?.videoMappings?.get(videoMappingKey) || null; 383 - return this.videoEmbed(authorDid, embed, videoMapping); 570 + return this.videoMedia(authorDid, media, videoMapping); 384 571 } else { 385 572 return undefined; 386 573 } 387 574 } 388 575 389 - imagesEmbed( 576 + imageMedia( 390 577 did: string, 391 - embed: ImagesEmbed, 392 - ): ImagesEmbedView & { $type: string } { 393 - const imgViews = embed.images.map((img) => ({ 578 + image: ImageMedia, 579 + ): ImageView & { $type: string } { 580 + const cid = cidFromBlobJson(image.image); 581 + return { 582 + $type: "so.sprk.media.image#view" as const, 583 + thumb: `${this.mediaCdn}/img/medium/${did}/${cid}/webp`, 584 + fullsize: `${this.mediaCdn}/img/full/${did}/${cid}/webp`, 585 + alt: image.alt, 586 + aspectRatio: image.aspectRatio, 587 + }; 588 + } 589 + 590 + imagesMedia( 591 + did: string, 592 + media: ImagesMedia, 593 + ): ImagesMediaView & { $type: string } { 594 + const imgViews = media.images.map((img) => ({ 394 595 thumb: `${this.mediaCdn}/img/medium/${did}/${ 395 596 cidFromBlobJson(img.image) 396 597 }/webp`, ··· 401 602 aspectRatio: img.aspectRatio, 402 603 })); 403 604 return { 404 - $type: "so.sprk.embed.images#view" as const, 605 + $type: "so.sprk.media.images#view" as const, 405 606 images: imgViews, 406 607 }; 407 608 } 408 609 409 - videoEmbed( 610 + videoMedia( 410 611 did: string, 411 - embed: VideoEmbed, 612 + media: VideoMedia, 412 613 videoMapping?: { bunnyGuid: string } | null, 413 - ): VideoEmbedView & { $type: string } { 414 - const cid = cidFromBlobJson(embed.video); 614 + ): VideoMediaView & { $type: string } { 615 + const cid = cidFromBlobJson(media.video); 415 616 416 617 let playlist: string; 417 618 let thumbnail: string; ··· 425 626 } 426 627 427 628 return { 428 - $type: "so.sprk.embed.video#view" as const, 629 + $type: "so.sprk.media.video#view", 429 630 cid, 430 631 playlist, 431 632 thumbnail, 432 - alt: embed.alt, 433 - aspectRatio: embed.aspectRatio, 633 + alt: media.alt, 634 + aspectRatio: media.aspectRatio, 635 + }; 636 + } 637 + 638 + soundView( 639 + uri: string, 640 + state: HydrationState, 641 + ): Un$Typed<AudioView> | undefined { 642 + const soundInfo = state.sounds?.get(uri); 643 + if (!soundInfo) return; 644 + 645 + const parsedUri = new AtUri(uri); 646 + const authorDid = parsedUri.hostname; 647 + const author = this.profileBasic(authorDid, state); 648 + if (!author) return; 649 + 650 + const soundAgg = state.soundAggs?.get(uri); 651 + const coverArtCid = cidFromBlobJson(soundInfo.record.coverArt as BlobRef); 652 + 653 + return { 654 + uri, 655 + cid: soundInfo.cid, 656 + author, 657 + record: soundInfo.record, 658 + useCount: soundAgg?.uses ?? 0, 659 + title: soundInfo.record.title, 660 + coverArt: `${this.mediaCdn}/img/medium/${authorDid}/${coverArtCid}/webp`, 661 + details: soundInfo.record.details 662 + ? { 663 + artist: soundInfo.record.details.artist, 664 + title: soundInfo.record.details.title, 665 + } 666 + : undefined, 667 + indexedAt: this.indexedAt(soundInfo)?.toISOString() ?? 668 + new Date().toISOString(), 434 669 }; 435 670 } 436 671 indexedAt({ sortedAt, indexedAt }: { sortedAt: Date; indexedAt: Date }) {
+25 -24
views/types.ts
··· 1 1 import { 2 - Main as ImagesEmbed, 3 - View as ImagesEmbedView, 4 - } from "../lex/types/so/sprk/embed/images.ts"; 2 + Main as ImagesMedia, 3 + View as ImagesMediaView, 4 + } from "../lex/types/so/sprk/media/images.ts"; 5 5 import { 6 - Main as VideoEmbed, 7 - View as VideoEmbedView, 8 - } from "../lex/types/so/sprk/embed/video.ts"; 6 + Main as VideoMedia, 7 + View as VideoMediaView, 8 + } from "../lex/types/so/sprk/media/video.ts"; 9 9 import { 10 10 BlockedPost, 11 11 GeneratorView, 12 12 NotFoundPost, 13 13 PostView, 14 + ReplyView, 14 15 } from "../lex/types/so/sprk/feed/defs.ts"; 15 16 import { LabelerView } from "../lex/types/app/bsky/labeler/defs.ts"; 16 17 17 - export type { 18 - Main as ImagesEmbed, 19 - View as ImagesEmbedView, 20 - } from "../lex/types/so/sprk/embed/images.ts"; 21 - export { isMain as isImagesEmbed } from "../lex/types/so/sprk/embed/images.ts"; 22 - export type { 23 - Main as VideoEmbed, 24 - View as VideoEmbedView, 25 - } from "../lex/types/so/sprk/embed/video.ts"; 26 - export { isMain as isVideoEmbed } from "../lex/types/so/sprk/embed/video.ts"; 18 + export { 19 + isMain as isImagesMedia, 20 + type Main as ImagesMedia, 21 + type View as ImagesMediaView, 22 + } from "../lex/types/so/sprk/media/images.ts"; 23 + export { 24 + isMain as isVideoMedia, 25 + type Main as VideoMedia, 26 + type View as VideoMediaView, 27 + } from "../lex/types/so/sprk/media/video.ts"; 27 28 export type { 28 29 BlockedPost, 29 30 GeneratorView, ··· 31 32 PostView, 32 33 } from "../lex/types/so/sprk/feed/defs.ts"; 33 34 34 - export type Embed = 35 - | ImagesEmbed 36 - | VideoEmbed; 35 + export type Media = 36 + | ImagesMedia 37 + | VideoMedia; 37 38 38 - export type EmbedView = 39 - | ImagesEmbedView 40 - | VideoEmbedView; 39 + export type MediaView = 40 + | ImagesMediaView 41 + | VideoMediaView; 41 42 42 - export type MaybePostView = PostView | NotFoundPost | BlockedPost; 43 + export type MaybePostView = PostView | ReplyView | NotFoundPost | BlockedPost; 43 44 44 - export type RecordEmbedViewInternal = 45 + export type RecordMediaViewInternal = 45 46 | GeneratorView 46 47 | LabelerView;