+1
app/build.gradle.kts
+1
app/build.gradle.kts
+32
-15
app/src/main/kotlin/DarkFeedApi.kt
+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
+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
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" }