Bluesky feed server - NSFW Likes
1
fork

Configure Feed

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

wip: Get labels concurrently, stop feed at end

+46 -19
+1 -3
.fleet/run.json
··· 7 7 "tasks": [ 8 8 ":app:run" 9 9 ], 10 - "args": [ 11 - "" 12 - ], 10 + "environmentFile": "local.env", 13 11 "initScripts": { 14 12 "flmapper": "ext.mapPath = { path -> path }" 15 13 }
+44 -15
app/src/main/kotlin/DarkFeedApi.kt
··· 11 11 import io.ktor.server.plugins.contentnegotiation.* 12 12 import io.ktor.server.response.* 13 13 import io.ktor.server.routing.* 14 + import kotlinx.coroutines.Job 15 + import kotlinx.coroutines.joinAll 16 + import kotlinx.coroutines.launch 17 + import kotlinx.coroutines.runBlocking 14 18 import kotlinx.serialization.Serializable 15 19 import kotlinx.serialization.json.Json 16 20 ··· 56 60 } 57 61 58 62 private suspend fun handleWellKnownDidJson(call: RoutingCall) { 63 + println("handleWellKnownDid: responding with hostname $hostname") 64 + 59 65 call.respond( 60 66 DidJson( 61 67 id = "did:web:$hostname", ··· 84 90 val labeledPosts: MutableSet<PostView> = mutableSetOf() 85 91 var apiCallsCount = 0 86 92 var getLikesByActorCursor: String? = cursor?.split(':')?.last() 93 + var isFeedFinished: Boolean = false 87 94 88 - while (labeledPosts.count() < (limit ?: 10) && apiCallsCount < 10) { 89 - bskyApi.getLikesByActor(requestor, getLikesByActorCursor) 95 + while (labeledPosts.count() < (limit ?: 10) && apiCallsCount < 10 && !isFeedFinished) { 96 + val likes = bskyApi.getLikesByActor(requestor, getLikesByActorCursor) 97 + .also { 98 + if (it.second == null) { 99 + println("buildFeedSkeleton: $requestor: getLikes cursor is null, no more likes available") 100 + isFeedFinished = true 101 + } 102 + } 90 103 .also { getLikesByActorCursor = it.second } 91 104 .first 92 105 .map { likeRef -> likeRef.value.subject.uri } 93 - .chunked(25) 94 - .map { likeUris -> 95 - // TODO: Run these calls concurrently 96 - bskyApi.getPostLabels(likeUris) 97 - .filter { post -> 98 - post.labels?.any { label -> listOf("porn", "sexual").contains(label.value) } ?: false 99 - } 100 - } 101 - .flatten() 102 - .also { labeledPosts.addAll(it) } 106 + 107 + println("buildFeedSkeleton: $requestor: got ${likes.count()} likes, new cursor: $getLikesByActorCursor") 108 + 109 + val handles: MutableList<Job> = mutableListOf() 110 + 111 + runBlocking { 112 + likes 113 + .chunked(25) 114 + .forEach { likeUris -> 115 + launch { 116 + bskyApi.getPostLabels(likeUris) 117 + .filter { post -> 118 + post.labels?.any { label -> listOf("porn", "sexual").contains(label.value) } 119 + ?: false 120 + }.also { labeledPosts.addAll(it) } 121 + }.also { handles.add(it) } 122 + } 123 + } 124 + 125 + handles.joinAll() 126 + 127 + println("buildFeedSkeleton: $requestor: found ${labeledPosts.count()} labeled likes") 103 128 104 129 apiCallsCount++ 105 - 106 - println("\u001b[31mgetLikesByActor Call Count: $apiCallsCount\nPosts Found: ${labeledPosts.count()}\nCursor: $getLikesByActorCursor\u001b[0m") 107 130 } 108 131 132 + println("buildFeedSkeleton: $requestor: returning FeedSkeleton, required $apiCallsCount getLikes calls") 133 + 109 134 return FeedSkeleton( 110 - cursor = "$requestor:$getLikesByActorCursor", 135 + cursor = if (!isFeedFinished) { 136 + "$requestor:$getLikesByActorCursor" 137 + } else { 138 + null 139 + }, 111 140 feed = labeledPosts.map { post -> SkeletonFeedPost(post = post.uri) } 112 141 ) 113 142 }
+1 -1
fly.toml
··· 24 24 [env] 25 25 DF_OWNER_PDS = 'bsky.social' 26 26 DF_OWNER_DID = 'did:plc:zhxv5pxpmojhnvaqy4mwailv' 27 - DF_HOSTNAME = 'darkfeed.fly.dev' 27 + DF_HOSTNAME = 'darkfeed.fly.dev'