Bluesky feed server - NSFW Likes

wip: Add cursor to continually load feed

Changed files
+37 -17
app
src
main
kotlin
resources
gradle
+1
app/build.gradle.kts
··· 19 19 implementation(libs.ktor.server.netty) 20 20 implementation(libs.ktor.server.content.negotiation) 21 21 implementation(libs.ktor.serialization.kotlinx.json) 22 + implementation(libs.java.jwt) 22 23 } 23 24 24 25 java {
+32 -15
app/src/main/kotlin/DarkFeedApi.kt
··· 1 1 package gay.averyrivers 2 2 3 + import com.auth0.jwt.JWT 3 4 import gay.averyrivers.lexicon.app.bsky.feed.FeedSkeleton 4 5 import gay.averyrivers.lexicon.app.bsky.feed.defs.PostView 5 6 import gay.averyrivers.lexicon.app.bsky.feed.defs.SkeletonFeedPost ··· 70 71 } 71 72 72 73 private suspend fun handleGetFeedSkeleton(call: RoutingCall) { 73 - // TODO: Get requestor's DID from Authorization header. 74 - call.respond(buildFeedSkeleton("did:plc:zhxv5pxpmojhnvaqy4mwailv")) 74 + val requestor = JWT.decode(call.request.headers["Authorization"]?.removePrefix("Bearer ")).issuer 75 + val limit = call.queryParameters["limit"]?.toIntOrNull() 76 + val cursor = call.queryParameters["cursor"] 77 + 78 + println("handleGetFeedSkeleton: requestor: $requestor, limit: $limit, cursor: $cursor") 79 + 80 + call.respond(buildFeedSkeleton(requestor, limit, cursor)) 75 81 } 76 82 77 - private suspend fun buildFeedSkeleton(requestor: String): FeedSkeleton { 78 - val actorLikes = bskyApi.getLikesByActor(requestor) 79 - .first 80 - .map { likeRef -> likeRef.value.subject.uri } 83 + private suspend fun buildFeedSkeleton(requestor: String, limit: Int? = null, cursor: String? = null): FeedSkeleton { 84 + val labeledPosts: MutableSet<PostView> = mutableSetOf() 85 + var apiCallsCount = 0 86 + var getLikesByActorCursor: String? = cursor?.split(':')?.last() 81 87 82 - val labeledPosts = actorLikes 83 - .chunked(25) 84 - .map { chunkedActorLikes -> 85 - bskyApi.getPostLabels(chunkedActorLikes) 86 - .filter { post -> 87 - post.labels?.any { label -> listOf("porn", "sexual").contains(label.value) } ?: false 88 - } 89 - } 90 - .flatten() 88 + while (labeledPosts.count() < (limit ?: 10) && apiCallsCount < 10) { 89 + bskyApi.getLikesByActor(requestor, getLikesByActorCursor) 90 + .also { getLikesByActorCursor = it.second } 91 + .first 92 + .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) } 103 + 104 + apiCallsCount++ 91 105 106 + println("\u001b[31mgetLikesByActor Call Count: $apiCallsCount\nPosts Found: ${labeledPosts.count()}\nCursor: $getLikesByActorCursor\u001b[0m") 107 + } 92 108 93 109 return FeedSkeleton( 110 + cursor = "$requestor:$getLikesByActorCursor", 94 111 feed = labeledPosts.map { post -> SkeletonFeedPost(post = post.uri) } 95 112 ) 96 113 }
+2 -2
app/src/main/resources/logback.xml
··· 8 8 <appender-ref ref="STDOUT"/> 9 9 </root> 10 10 <logger name="io.netty" level="INFO"/> 11 - <logger name="io.ktor.client" level="INFO"/> 12 - <logger name="io.ktor.server" level="INFO"/> 11 + <logger name="io.ktor.client" level="WARN"/> 12 + <logger name="io.ktor.server" level="WARN"/> 13 13 </configuration>
+2
gradle/libs.versions.toml
··· 2 2 kotlin = "2.0.21" 3 3 logback = "1.5.12" 4 4 ktor = "3.0.1" 5 + auth0 = "4.4.0" 5 6 6 7 [libraries] 7 8 logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } ··· 14 15 ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" } 15 16 ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation", version.ref = "ktor" } 16 17 ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } 18 + java-jwt = { group = "com.auth0", name = "java-jwt", version.ref = "auth0" } 17 19 18 20 [plugins] 19 21 kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }