blob: 33b3a224c1ee7362ce0c9f2b3381befeb2756430 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
package coffee.liz.rookery.images
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
/** One image search result: a small [thumbnail] to show in the picker and the [full] image to keep. */
data class ImageHit(val title: String, val thumbnail: String, val full: String, val source: String)
/** Brave Search image API: request building and response parsing (the HTTP call itself is the host's job). */
object BraveImages {
const val KEY_HEADER = "X-Subscription-Token"
fun url(query: String, count: Int = 12): String =
"https://api.search.brave.com/res/v1/images/search?q=${encode(query)}&count=$count&safesearch=strict"
fun parse(body: String): List<ImageHit> {
val root = Json.parseToJsonElement(body).jsonObject
return (root["results"] as? JsonArray).orEmpty().mapNotNull { e ->
val o = e as? JsonObject ?: return@mapNotNull null
val thumb = (o["thumbnail"] as? JsonObject)?.str("src")
val full = (o["properties"] as? JsonObject)?.str("url")
val any = thumb ?: full ?: return@mapNotNull null
if (!any.startsWith("https://")) return@mapNotNull null
ImageHit(o.str("title").orEmpty(), thumb ?: any, full?.takeIf { it.startsWith("https://") } ?: any, o.str("source").orEmpty())
}
}
private fun JsonObject.str(k: String) = (this[k] as? JsonPrimitive)?.contentOrNull
private fun encode(s: String): String = buildString {
for (b in s.encodeToByteArray()) {
val c = b.toInt().toChar()
if (c.isLetterOrDigit() && b >= 0 || c in "-_.~") append(c)
else append('%').append((b.toInt() and 0xFF).toString(16).uppercase().padStart(2, '0'))
}
}
}
|