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

no videomapping

+12 -99
-4
data-plane/db/index.ts
··· 88 88 "Profile", 89 89 models.profileSchema, 90 90 ), 91 - VideoMapping: this.connection.model<models.VideoMappingDocument>( 92 - "VideoMapping", 93 - models.videoMappingSchema, 94 - ), 95 91 Audio: this.connection.model<models.AudioDocument>( 96 92 "Audio", 97 93 models.audioSchema,
-13
data-plane/db/models.ts
··· 507 507 updatedAt: { type: Date, default: Date.now }, 508 508 }); 509 509 510 - export interface VideoMappingDocument extends Document { 511 - key: string; // did-cid 512 - bunnyGuid: string; 513 - postMongoId: string; 514 - } 515 - 516 - export const videoMappingSchema = new Schema<VideoMappingDocument>({ 517 - key: { type: String, required: true, unique: true, index: true }, 518 - bunnyGuid: { type: String, required: true, index: true }, 519 - postMongoId: { type: String, required: true, index: true }, 520 - }); 521 - 522 510 // Apply plugin to schemas that extend AuthoredDocument 523 511 ([ 524 512 profileSchema, ··· 553 541 ActorSync: Model<ActorSyncDocument>; 554 542 UserPreference: Model<UserPreferenceDocument>; 555 543 CursorState: Model<CursorStateDocument>; 556 - VideoMapping: Model<VideoMappingDocument>; 557 544 }
-14
hydration/feed.ts
··· 5 5 import { Record as ReplyRecord } from "../lex/types/so/sprk/feed/reply.ts"; 6 6 import { Record as RepostRecord } from "../lex/types/app/bsky/feed/repost.ts"; 7 7 import { Record as AudioRecord } from "../lex/types/so/sprk/sound/audio.ts"; 8 - import { VideoMappingDocument } from "../data-plane/db/models.ts"; 9 8 import { uriToDid as didFromUri } from "../utils/uris.ts"; 10 9 import { 11 10 HydrationMap, ··· 29 28 }; 30 29 31 30 export type SoundAggs = HydrationMap<SoundAgg>; 32 - 33 - export type VideoMapping = VideoMappingDocument; 34 - export type VideoMappings = HydrationMap<VideoMapping>; 35 31 36 32 export type PostViewerState = { 37 33 like?: string; ··· 99 95 100 96 export class FeedHydrator { 101 97 constructor(public dataplane: DataPlane) {} 102 - 103 - getVideoMappings( 104 - keys: string[], 105 - ): VideoMappings { 106 - if (!keys.length) return new HydrationMap<VideoMapping>(); 107 - 108 - // This would need to be implemented in the dataplane client 109 - // For now, return empty mappings 110 - return new HydrationMap<VideoMapping>(); 111 - } 112 98 113 99 async getPosts( 114 100 uris: string[],
-3
hydration/index.ts
··· 32 32 Sounds, 33 33 ThreadContexts, 34 34 ThreadRef, 35 - VideoMappings, 36 35 } from "./feed.ts"; 37 36 import { Stories, StoryHydrator } from "./story.ts"; 38 37 ··· 103 102 knownFollowers?: KnownFollowersStates; 104 103 activitySubscriptions?: ActivitySubscriptionStates; 105 104 bidirectionalBlocks?: BidirectionalBlocks; 106 - videoMappings?: VideoMappings; 107 105 }; 108 106 109 107 export type PostBlock = { embed: boolean; parent: boolean; root: boolean }; ··· 846 844 feedgenAggs: mergeMaps(stateA.feedgenAggs, stateB.feedgenAggs), 847 845 feedgenViewers: mergeMaps(stateA.feedgenViewers, stateB.feedgenViewers), 848 846 knownFollowers: mergeMaps(stateA.knownFollowers, stateB.knownFollowers), 849 - videoMappings: mergeMaps(stateA.videoMappings, stateB.videoMappings), 850 847 }; 851 848 }; 852 849
+5 -17
utils/media-transformer.ts
··· 1 1 import type * as SoSprkMediaImage from "../lex/types/so/sprk/media/image.ts"; 2 - import { 3 - ImageMedia, 4 - PostMedia, 5 - StoryMedia, 6 - VideoMappingDocument, 7 - } from "../data-plane/db/models.ts"; 2 + import { ImageMedia, PostMedia, StoryMedia } from "../data-plane/db/models.ts"; 8 3 import { ServerConfig } from "../config.ts"; 9 4 10 5 interface ImageTransformOptions { ··· 46 41 media: PostMedia, 47 42 authorDid: string, 48 43 cfg: ServerConfig, 49 - videoMapping?: VideoMappingDocument | null, 50 44 isStory = false, 51 45 ) { 52 46 if (!media.video) { ··· 56 50 let playlist: string; 57 51 let thumbnail: string; 58 52 59 - if (videoMapping) { 60 - playlist = `${cfg.videoCdn}/${videoMapping.bunnyGuid}/playlist.m3u8`; 61 - thumbnail = `${cfg.videoCdn}/${videoMapping.bunnyGuid}/thumbnail.jpg`; 62 - } else if (isStory) { 63 - playlist = 64 - `https://media.sprk.so/video/${authorDid}/${media.video.ref.$link}`; 53 + if (isStory) { 54 + playlist = `${cfg.mediaCdn}/video/${authorDid}/${media.video.ref.$link}`; 65 55 thumbnail = 66 - `https://thumb.sprk.so/${authorDid}/${media.video.ref.$link}/thumbnail`; 56 + `${cfg.thumbCdn}/${authorDid}/${media.video.ref.$link}/thumbnail`; 67 57 } else { 68 58 playlist = 69 59 `${cfg.videoCdn}/watch/${authorDid}/${media.video.ref.$link}/playlist.m3u8`; 70 60 thumbnail = 71 - `https://thumb.sprk.so/${authorDid}/${media.video.ref.$link}/thumbnail`; 61 + `${cfg.thumbCdn}/${authorDid}/${media.video.ref.$link}/thumbnail`; 72 62 } 73 63 74 64 return { ··· 84 74 media: PostMedia | StoryMedia | undefined, 85 75 authorDid: string, 86 76 cfg: ServerConfig, 87 - videoMapping?: VideoMappingDocument | null, 88 77 options: ImageTransformOptions = {}, 89 78 isStory = false, 90 79 ) { ··· 119 108 media as PostMedia, 120 109 authorDid, 121 110 cfg, 122 - videoMapping, 123 111 isStory, 124 112 ); 125 113 }
+1 -19
utils/post-transformer.ts
··· 28 28 replyCounts, 29 29 repostCounts, 30 30 authors, 31 - videoMappings, 32 31 viewerLikes, 33 32 viewerReposts, 34 33 ] = await Promise.all([ ··· 57 56 ); 58 57 }), 59 58 ), 60 - // Get video mappings 61 - ctx.db.models.VideoMapping.find({ 62 - key: { 63 - $in: posts 64 - .filter((p) => p.media?.$type === "so.sprk.embed.video") 65 - .map((p) => `${p.authorDid}-${p.media?.video?.ref.$link}`), 66 - }, 67 - }), 68 59 // Get viewer likes 69 60 userDid 70 61 ? ctx.db.models.Like.find({ ··· 93 84 ); 94 85 95 86 const authorsMap = new Map(authors.map((author) => [author.did, author])); 96 - const videoMappingsMap = new Map( 97 - videoMappings.map((item) => [item.key, item]), 98 - ); 87 + 99 88 const viewerLikesMap = new Map( 100 89 viewerLikes.map((like) => [like.subject, like.uri]), 101 90 ); ··· 111 100 const audioViewsMap = new Map(audioViews.map((av) => [av.uri, av])); 112 101 113 102 return posts.map((post) => { 114 - const videoMapping = post.media?.$type === "so.sprk.media.video" 115 - ? videoMappingsMap.get( 116 - `${post.authorDid}-${post.media.video?.ref.$link}`, 117 - ) || null 118 - : null; 119 - 120 103 const embed = transformMedia( 121 104 post.media, 122 105 post.authorDid, 123 106 ctx.cfg, 124 - videoMapping, 125 107 ); 126 108 127 109 const labels = post.labels
-2
utils/story-transformer.ts
··· 19 19 story.media, 20 20 story.authorDid, 21 21 ctx.cfg, 22 - null, 23 22 { 24 23 firstImageOnly: true, 25 24 }, ··· 69 68 story.media, 70 69 story.authorDid, 71 70 ctx.cfg, 72 - null, 73 71 { 74 72 firstImageOnly: true, 75 73 },
+6 -27
views/index.ts
··· 220 220 cid: recordInfo.cid, 221 221 author, 222 222 record: recordInfo.record, 223 - media: mediaRecord 224 - ? this.media(uri, mediaRecord as Media, state) 225 - : undefined, 223 + media: mediaRecord ? this.media(uri, mediaRecord as Media) : undefined, 226 224 replyCount: repliesCount, 227 225 repostCount, 228 226 likeCount, ··· 292 290 cid: storyInfo.cid, 293 291 author, 294 292 record: storyInfo.record, 295 - media: mediaRecord ? this.storyMedia(uri, mediaRecord, state) : undefined, 293 + media: mediaRecord ? this.storyMedia(uri, mediaRecord) : undefined, 296 294 indexedAt: this.indexedAt(storyInfo)?.toISOString() ?? 297 295 new Date().toISOString(), 298 296 }; ··· 352 350 storyMedia( 353 351 storyUri: string, 354 352 media: $Typed<ImageMedia> | $Typed<VideoMedia> | { $type: string }, 355 - state?: HydrationState, 356 353 ): (ImageView | VideoMediaView) & { $type: string } | undefined { 357 354 const authorDid = uriToDid(storyUri); 358 355 ··· 364 361 // Check if it's a video media 365 362 if (isVideoMediaMain(media)) { 366 363 const videoMedia = media as VideoMediaMainType; 367 - const videoCid = videoMedia.video 368 - ? cidFromBlobJson(videoMedia.video) 369 - : ""; 370 - const videoMappingKey = `${authorDid}-${videoCid}`; 371 - const videoMapping = state?.videoMappings?.get(videoMappingKey) || null; 372 - return this.videoMedia(authorDid, videoMedia, videoMapping); 364 + return this.videoMedia(authorDid, videoMedia); 373 365 } 374 366 375 367 return undefined; ··· 666 658 media( 667 659 postUri: string, 668 660 media: Media | { $type: string }, 669 - state?: HydrationState, 670 661 ): (MediaView & { $type: string }) | undefined { 671 662 if (isImagesMedia(media)) { 672 663 return this.imagesMedia(uriToDid(postUri), media); 673 664 } else if (isVideoMedia(media)) { 674 665 const authorDid = uriToDid(postUri); 675 - const videoCid = media.video ? cidFromBlobJson(media.video) : ""; 676 - const videoMappingKey = `${authorDid}-${videoCid}`; 677 - const videoMapping = state?.videoMappings?.get(videoMappingKey) || null; 678 - return this.videoMedia(authorDid, media, videoMapping); 666 + return this.videoMedia(authorDid, media); 679 667 } else { 680 668 return undefined; 681 669 } ··· 718 706 videoMedia( 719 707 did: string, 720 708 media: VideoMedia, 721 - videoMapping?: { bunnyGuid: string } | null, 722 709 ): VideoMediaView & { $type: string } { 723 710 const cid = cidFromBlobJson(media.video); 724 711 725 - let playlist: string; 726 - let thumbnail: string; 727 - 728 - if (videoMapping) { 729 - playlist = `${this.videoCdn}/${videoMapping.bunnyGuid}/playlist.m3u8`; 730 - thumbnail = `${this.videoCdn}/${videoMapping.bunnyGuid}/thumbnail.jpg`; 731 - } else { 732 - playlist = `${this.videoCdn}/watch/${did}/${cid}/playlist.m3u8`; 733 - thumbnail = `${this.videoCdn}/${did}/${cid}/thumbnail`; 734 - } 712 + const playlist = `${this.videoCdn}/watch/${did}/${cid}/playlist.m3u8`; 713 + const thumbnail = `${this.thumbCdn}/${did}/${cid}/thumbnail`; 735 714 736 715 return { 737 716 $type: "so.sprk.media.video#view",