[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.

add labeler getServices

+173 -3
+46
api/so/sprk/labeler/getServices.ts
··· 1 + import { mapDefined } from "@atp/common"; 2 + import { AppContext } from "../../../../context.ts"; 3 + import { Server } from "../../../../lex/index.ts"; 4 + import { resHeaders } from "../../../util.ts"; 5 + 6 + export default function (server: Server, ctx: AppContext) { 7 + server.app.bsky.labeler.getServices({ 8 + auth: ctx.authVerifier.standardOptional, 9 + handler: async ({ params, auth, req }) => { 10 + const { dids, detailed } = params; 11 + const viewer = auth.credentials.iss; 12 + const labelers = ctx.reqLabelers(req); 13 + const hydrateCtx = await ctx.hydrator.createContext({ 14 + viewer, 15 + labelers, 16 + }); 17 + const hydration = await ctx.hydrator.hydrateLabelers(dids, hydrateCtx); 18 + 19 + const views = mapDefined(dids, (did) => { 20 + if (detailed) { 21 + const view = ctx.views.labelerDetailed(did, hydration); 22 + if (!view) return; 23 + return { 24 + ...view, 25 + $type: "app.bsky.labeler.defs#labelerViewDetailed", 26 + }; 27 + } else { 28 + const view = ctx.views.labeler(did, hydration); 29 + if (!view) return; 30 + return { 31 + ...view, 32 + $type: "app.bsky.labeler.defs#labelerView", 33 + }; 34 + } 35 + }); 36 + 37 + return { 38 + encoding: "application/json", 39 + body: { 40 + views, 41 + }, 42 + headers: resHeaders({ labelers: hydrateCtx.labelers }), 43 + }; 44 + }, 45 + }); 46 + }
+1 -1
hydration/label.ts
··· 1 1 import { AtUri } from "@atp/syntax"; 2 2 import { DataPlane } from "../data-plane/index.ts"; 3 3 import { ids } from "../lex/lexicons.ts"; 4 - import { Record as LabelerRecord } from "../lex/types/app/bsky/labeler/service.ts"; 4 + import { Record as LabelerRecord } from "../lex/types/so/sprk/labeler/service.ts"; 5 5 import { Label } from "../lex/types/com/atproto/label/defs.ts"; 6 6 import { ParsedLabelers } from "../util.ts"; 7 7 import {
+125 -1
views/index.ts
··· 1 1 import { HydrationState } from "../hydration/index.ts"; 2 2 import { 3 + isRecord as isPostRecord, 4 + Record as PostRecord, 5 + } from "../lex/types/so/sprk/feed/post.ts"; 6 + import { Record as LikeRecord } from "../lex/types/so/sprk/feed/like.ts"; 7 + import { Record as RepostRecord } from "../lex/types/so/sprk/feed/repost.ts"; 8 + import { Record as FollowRecord } from "../lex/types/so/sprk/graph/follow.ts"; 9 + import { 10 + isRecord as isProfileRecord, 11 + Record as ProfileRecord, 12 + } from "../lex/types/so/sprk/actor/profile.ts"; 13 + import { Label } from "../lex/types/com/atproto/label/defs.ts"; 14 + import { 3 15 FeedViewPost, 4 16 isPostView, 5 17 isReplyView, ··· 48 60 } from "../lex/types/so/sprk/media/video.ts"; 49 61 import type { Main as VideoMediaMainType } from "../lex/types/so/sprk/media/video.ts"; 50 62 import { AudioView } from "../lex/types/so/sprk/sound/defs.ts"; 51 - import { AtUri, INVALID_HANDLE } from "@atp/syntax"; 63 + import { AtUri, INVALID_HANDLE, normalizeDatetimeAlways } from "@atp/syntax"; 52 64 import { Main as StrongRef } from "../lex/types/com/atproto/repo/strongRef.ts"; 53 65 import { cidFromBlobJson } from "./util.ts"; 54 66 import { uriToDid } from "../utils/uris.ts"; ··· 60 72 } from "../lex/types/so/sprk/feed/getPostThread.ts"; 61 73 import { $Typed, Un$Typed } from "../lex/util.ts"; 62 74 import { BlobRef } from "@atp/lexicon"; 75 + import { 76 + isRecord as isLabelerRecord, 77 + Record as LabelerRecord, 78 + } from "../lex/types/so/sprk/labeler/service.ts"; 79 + import { 80 + LabelerView, 81 + LabelerViewDetailed, 82 + } from "../lex/types/so/sprk/labeler/defs.ts"; 83 + import { isSelfLabels } from "../lex/types/com/atproto/label/defs.ts"; 63 84 64 85 export class Views { 65 86 public indexedAtEpoch?: Date | undefined; ··· 82 103 this.thumbCdn = opts?.thumbCdn; 83 104 } 84 105 106 + // Labels 107 + // ------------ 108 + 109 + selfLabels({ 110 + uri, 111 + cid, 112 + record, 113 + }: { 114 + uri?: string; 115 + cid?: string; 116 + record?: 117 + | PostRecord 118 + | LikeRecord 119 + | RepostRecord 120 + | FollowRecord 121 + | ProfileRecord 122 + | LabelerRecord; 123 + }): Label[] { 124 + if (!uri || !cid || !record) return []; 125 + 126 + // Only these have a "labels" property: 127 + if ( 128 + !isPostRecord(record) && 129 + !isProfileRecord(record) && 130 + !isLabelerRecord(record) 131 + ) { 132 + return []; 133 + } 134 + 135 + // Ignore if no labels defines 136 + if (!isSelfLabels(record.labels) || !record.labels.values.length) { 137 + return []; 138 + } 139 + 140 + const src = uriToDid(uri); // record creator 141 + const cts = typeof record.createdAt === "string" 142 + ? normalizeDatetimeAlways(record.createdAt) 143 + : new Date(0).toISOString(); 144 + return record.labels.values.map(({ val }) => { 145 + return { src, uri, cid, val, cts }; 146 + }); 147 + } 148 + 149 + labeler( 150 + did: string, 151 + state: HydrationState, 152 + ): Un$Typed<LabelerView> | undefined { 153 + const labeler = state.labelers?.get(did); 154 + if (!labeler) return; 155 + const creator = this.profile(did, state); 156 + if (!creator) return; 157 + const viewer = state.labelerViewers?.get(did); 158 + const aggs = state.labelerAggs?.get(did); 159 + 160 + const uri = AtUri.make(did, ids.SoSprkLabelerService, "self").toString(); 161 + const labels = [ 162 + ...(state.labels?.getBySubject(uri) ?? []), 163 + ...this.selfLabels({ 164 + uri, 165 + cid: labeler.cid.toString(), 166 + record: labeler.record, 167 + }), 168 + ]; 169 + 170 + return { 171 + uri, 172 + cid: labeler.cid.toString(), 173 + creator, 174 + likeCount: aggs?.likes ?? 0, 175 + viewer: viewer 176 + ? { 177 + like: viewer.like, 178 + } 179 + : undefined, 180 + indexedAt: this.indexedAt(labeler).toISOString(), 181 + labels, 182 + }; 183 + } 184 + 185 + labelerDetailed( 186 + did: string, 187 + state: HydrationState, 188 + ): Un$Typed<LabelerViewDetailed> | undefined { 189 + const baseView = this.labeler(did, state); 190 + if (!baseView) return; 191 + const labeler = state.labelers?.get(did); 192 + if (!labeler) return; 193 + 194 + return { 195 + ...baseView, 196 + policies: labeler.record.policies, 197 + reasonTypes: labeler.record.reasonTypes, 198 + subjectTypes: labeler.record.subjectTypes, 199 + subjectCollections: labeler.record.subjectCollections, 200 + }; 201 + } 202 + 203 + // Threads 204 + // ------------ 205 + 85 206 thread( 86 207 skeleton: { anchor: string; uris: string[] }, 87 208 state: HydrationState, ··· 188 309 ...(like ? { rootAuthorLike: like } : {}), 189 310 }; 190 311 } 312 + 313 + // Feed 314 + // ------------ 191 315 192 316 post( 193 317 uri: string,
+1 -1
views/types.ts
··· 13 13 PostView, 14 14 ReplyView, 15 15 } from "../lex/types/so/sprk/feed/defs.ts"; 16 - import { LabelerView } from "../lex/types/app/bsky/labeler/defs.ts"; 16 + import { LabelerView } from "../lex/types/so/sprk/labeler/defs.ts"; 17 17 18 18 export { 19 19 isMain as isImagesMedia,