1+ package dev.slne.surf.database.user
2+
3+ import com.github.benmanes.caffeine.cache.Caffeine
4+ import com.sksamuel.aedile.core.expireAfterWrite
5+ import dev.slne.surf.database.user.minecraft.MinecraftApiClient
6+ import dev.slne.surf.database.user.minetools.MinetoolsApiClient
7+ import io.ktor.client.*
8+ import io.ktor.client.call.*
9+ import io.ktor.client.request.*
10+ import io.ktor.client.statement.*
11+ import kotlinx.coroutines.DelicateCoroutinesApi
12+ import kotlinx.coroutines.Dispatchers
13+ import kotlinx.coroutines.GlobalScope
14+ import kotlinx.coroutines.future.await
15+ import kotlinx.coroutines.future.future
16+ import kotlinx.coroutines.withContext
17+ import kotlinx.serialization.json.Json
18+ import java.util.*
19+ import kotlin.coroutines.CoroutineContext
20+ import kotlin.time.Duration.Companion.hours
21+
22+ @OptIn(DelicateCoroutinesApi ::class )
23+ object UserLookupService {
24+
25+ private val nameToUuidCache = Caffeine .newBuilder()
26+ .expireAfterWrite(1 .hours)
27+ .buildAsync<String , UUID > { key, _ ->
28+ GlobalScope .future {
29+ try {
30+ MinecraftApiClient .getUuid(key)?.uuid
31+ } catch (_: Exception ) {
32+ try {
33+ MinetoolsApiClient .getUuid(key)?.uuid
34+ } catch (_: Exception ) {
35+ null
36+ }
37+ }
38+ }
39+ }
40+
41+ private val uuidToNameCache = Caffeine .newBuilder()
42+ .expireAfterWrite(1 .hours)
43+ .buildAsync<UUID , String > { key, _ ->
44+ GlobalScope .future {
45+ try {
46+ MinecraftApiClient .getUsername(key)?.name
47+ } catch (_: Exception ) {
48+ try {
49+ MinetoolsApiClient .getUsername(key)?.name
50+ } catch (_: Exception ) {
51+ null
52+ }
53+ }
54+ }
55+ }
56+
57+ val client = HttpClient ()
58+ val json = Json { ignoreUnknownKeys = true }
59+
60+ /* *
61+ * Fetches data from an API and deserializes it into an object of type [T].
62+ *
63+ * @param url The URL of the API endpoint.
64+ *
65+ * @return The deserialized object, or null if the request failed.
66+ */
67+ suspend inline fun <reified T > fetchFromApi (url : String ): T ? {
68+ val response: HttpResponse = client.get(url)
69+
70+ return if (response.status.value == 200 ) {
71+ json.decodeFromString<T >(response.body())
72+ } else {
73+ null
74+ }
75+ }
76+
77+ /* *
78+ * Returns the UUID of a player by their username.
79+ *
80+ * @param username The username of the player.
81+ *
82+ * @return The UUID of the player, or null if the player does not exist.
83+ */
84+ suspend fun getUuidByUsername (
85+ username : String ,
86+ context : CoroutineContext = Dispatchers .IO
87+ ): UUID ? = withContext(context) { nameToUuidCache.get(username).await() }
88+
89+ /* *
90+ * Returns the username of a player by their UUID.
91+ *
92+ * @param uuid The UUID of the player.
93+ *
94+ * @return The username of the player, or null if the player does not exist.
95+ */
96+ suspend fun getUsernameByUuid (
97+ uuid : UUID ,
98+ context : CoroutineContext = Dispatchers .IO
99+ ): String? = withContext(context) { uuidToNameCache.get(uuid).await() }
100+ }
0 commit comments