diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiDiscordRoutes.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiDiscordRoutes.kt index d84c717..c3f11d3 100644 --- a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiDiscordRoutes.kt +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiDiscordRoutes.kt @@ -15,6 +15,7 @@ import space.mori.chzzk_bot.common.events.DiscordRegisterEvent import space.mori.chzzk_bot.common.services.UserService import space.mori.chzzk_bot.common.utils.getRandomString import space.mori.chzzk_bot.webserver.UserSession +import space.mori.chzzk_bot.webserver.utils.DiscordGuildCache fun Route.apiDiscordRoutes() { val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java) @@ -63,7 +64,8 @@ fun Route.apiDiscordRoutes() { call.respond(HttpStatusCode.BadRequest, "User does not exist") return@get } - call.respond(HttpStatusCode.OK, session.discordGuildList) + + call.respond(HttpStatusCode.OK, DiscordGuildCache.getCachedGuilds(session.discordGuildList)) return@get } } diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/utils/DiscordGuildCache.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/utils/DiscordGuildCache.kt new file mode 100644 index 0000000..b55db0e --- /dev/null +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/utils/DiscordGuildCache.kt @@ -0,0 +1,75 @@ +package space.mori.chzzk_bot.webserver.utils + +import applicationHttpClient +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.http.* +import kotlinx.serialization.Serializable +import space.mori.chzzk_bot.webserver.DiscordGuildListAPI +import space.mori.chzzk_bot.webserver.dotenv +import java.time.Instant +import java.util.concurrent.ConcurrentHashMap + +object DiscordGuildCache { + private val cache = ConcurrentHashMap() + private const val EXP_SECONDS = 600L + + suspend fun getCachedGuilds(guildId: String): Guild? { + val now = Instant.now() + + return if(cache.isNotEmpty() && cache[guildId]?.timestamp?.plusSeconds(EXP_SECONDS)?.isAfter(now) == true) { + cache[guildId]?.guild + } else { + fetchAllGuilds() + cache[guildId]?.guild + } + } + + suspend fun getCachedGuilds(guildId: List): List { + return guildId.mapNotNull { getCachedGuilds(it) } + } + + private suspend fun fetchGuilds(beforeGuildId: String? = null, limit: Int = 100): List { + val result = applicationHttpClient.get("https://discord.com/api/users/@me/guilds") { + headers { + append(HttpHeaders.Authorization, "Bot ${dotenv["DISCORD_TOKEN"]}") + } + parameter("limit", limit) + if (beforeGuildId != null) { + parameter("before", beforeGuildId) + } + } + + return result.body>() + } + + private suspend fun fetchAllGuilds() { + var lastGuildId: String? = null + while (true) { + val guilds = fetchGuilds(lastGuildId) + if (guilds.isEmpty()) { + break + } + + guilds.forEach { + cache[it.id] = CachedGuilds( + Guild(it.id, it.name, it.icon, it.banner) + ) + } + lastGuildId = guilds.last().id + } + } +} + +data class CachedGuilds( + val guild: Guild, + val timestamp: Instant = Instant.now() +) + +@Serializable +data class Guild( + val id: String, + val name: String, + val icon: String?, + val banner: String?, +) \ No newline at end of file