diff --git a/cirno-serializable/src/main/kotlin/net/cakeyfox/serializable/data/ActivityUpdateRequest.kt b/cirno-serializable/src/main/kotlin/net/cakeyfox/serializable/data/ActivityUpdateRequest.kt index 8cee03fd..82d17343 100644 --- a/cirno-serializable/src/main/kotlin/net/cakeyfox/serializable/data/ActivityUpdateRequest.kt +++ b/cirno-serializable/src/main/kotlin/net/cakeyfox/serializable/data/ActivityUpdateRequest.kt @@ -4,8 +4,8 @@ import kotlinx.serialization.Serializable @Serializable data class ActivityUpdateRequest( - val name: String, - val type: Int, + val name: String = "uwu", + val type: Int = 0, val url: String? = null, val status: String? = "online", ) diff --git a/common/src/main/kotlin/net/cakeyfox/common/Constants.kt b/common/src/main/kotlin/net/cakeyfox/common/Constants.kt index ad204a38..0ec10038 100644 --- a/common/src/main/kotlin/net/cakeyfox/common/Constants.kt +++ b/common/src/main/kotlin/net/cakeyfox/common/Constants.kt @@ -19,18 +19,18 @@ object Constants { const val SUPPORT_SERVER_ID = "768267522670723094" - fun getDefaultActivity(environment: String, clusterName: String?): String { + fun getDefaultActivity(activity: String, environment: String, clusterName: String?): String { if (clusterName != null) { return when(environment) { "development" -> "https://youtu.be/0OIqlp2U9EQ | Cluster: $clusterName" - "production" -> "foxybot.win · /help | Cluster: $clusterName" - else -> "foxybot.win · /help | Cluster: $clusterName" + "production" -> "$activity | Cluster: $clusterName" + else -> "$activity | Cluster: $clusterName" } } else { return when(environment) { "development" -> "https://youtu.be/0OIqlp2U9EQ" - "production" -> "foxybot.win · /help" - else -> "foxybot.win · /help" + "production" -> activity + else -> activity } } } diff --git a/foxy/build.gradle.kts b/foxy/build.gradle.kts index 7d898d64..15a10e6c 100644 --- a/foxy/build.gradle.kts +++ b/foxy/build.gradle.kts @@ -25,8 +25,9 @@ dependencies { implementation(libs.deviousjda) implementation("club.minnced:jda-ktx:${Versions.JDA_KTX}") - // DB - implementation("org.mongodb:mongodb-driver-sync:${Versions.MONGODB}") + // MongoDB + implementation("org.mongodb:bson-kotlinx:5.3.0") + implementation("org.mongodb:mongodb-driver-kotlin-coroutine:5.3.0") // Ktor implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") @@ -39,7 +40,7 @@ dependencies { implementation("io.ktor:ktor-server-content-negotiation:${Versions.KTOR}") implementation("io.ktor:ktor-server-auth:${Versions.KTOR}") - + // ThreadFactoryBuilder implementation("com.google.guava:guava:32.1.3-jre") // Caching diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/FoxyInstance.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/FoxyInstance.kt index 0ee3e535..5b97d469 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/FoxyInstance.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/FoxyInstance.kt @@ -14,7 +14,6 @@ import net.cakeyfox.foxy.command.component.FoxyComponentManager import net.cakeyfox.foxy.listeners.GuildListener import net.cakeyfox.foxy.listeners.InteractionsListener import net.cakeyfox.foxy.listeners.MessageListener -import net.cakeyfox.foxy.utils.ActivityUpdater import net.cakeyfox.foxy.utils.config.FoxyConfig import net.cakeyfox.foxy.utils.FoxyUtils import net.cakeyfox.foxy.utils.analytics.TopggStatsSender @@ -46,18 +45,20 @@ class FoxyInstance( lateinit var interactionManager: FoxyComponentManager lateinit var httpClient: HttpClient lateinit var selfUser: User + + private lateinit var foxyInternalAPI: FoxyInternalAPI private lateinit var topggStatsSender: TopggStatsSender private lateinit var environment: String + private val activeJobs = ThreadUtils.activeJobs private val currentClusterName = if (config.discord.clusters.size < 2) null else currentCluster.name private val coroutineExecutor = ThreadUtils.createThreadPool("CoroutineExecutor [%d]") + val threadPoolManager = ThreadPoolManager() val coroutineDispatcher = coroutineExecutor.asCoroutineDispatcher() suspend fun start() { val logger = KotlinLogging.logger(this::class.jvmName) - val activityUpdater = ActivityUpdater(this) - FoxyInternalAPI(this) environment = config.environment mongoClient = MongoDBClient() @@ -88,10 +89,13 @@ class FoxyInstance( MessageListener(this) ) .setAutoReconnect(true) - .setStatus(OnlineStatus.ONLINE) + .setStatus( + OnlineStatus.fromKey(mongoClient.utils.bot.getBotSettings().status) + ) .setActivity( Activity.customStatus( Constants.getDefaultActivity( + mongoClient.utils.bot.getActivity(), config.environment, currentClusterName ) @@ -115,6 +119,7 @@ class FoxyInstance( selfUser = shardManager.shards.first().selfUser topggStatsSender = TopggStatsSender(this) + foxyInternalAPI = FoxyInternalAPI(this) Runtime.getRuntime().addShutdownHook(thread(false) { try { @@ -126,7 +131,7 @@ class FoxyInstance( } httpClient.close() mongoClient.close() - activityUpdater.shutdown() + foxyInternalAPI.stop() activeJobs.forEach { logger.info { "Cancelling job $it" } diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/FoxyLauncher.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/FoxyLauncher.kt index 1969560c..09904008 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/FoxyLauncher.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/FoxyLauncher.kt @@ -44,9 +44,25 @@ object FoxyLauncher { val config = readConfigFile(configFile) val hostname = HostnameUtils.getHostname() - val currentCluster = config.discord.clusters.find { it.id == hostname } + val clusterId = if (config.discord.getClusterIdFromHostname) { + try { + // If the hostname is in the expected format, extract the ID after the "-" + hostname.split("-")[1].toInt() + } catch (e: IndexOutOfBoundsException) { + logger.error { "Invalid hostname ($hostname)! The hostname must contain '-' followed by a numeric ID (e.g., foxy-1)." } + exitProcess(1) + } catch (e: NumberFormatException) { + logger.error { "Invalid ID in hostname ($hostname)! The value after '-' must be a number (e.g., foxy-1)." } + exitProcess(1) + } + + } else { + config.discord.replicaId + } + + val currentCluster = config.discord.clusters.find { it.id == clusterId } ?: run { - logger.error { "Cluster $hostname not found in config file" } + logger.error { "Cluster $hostname (${clusterId}) not found in config file" } exitProcess(1) } diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/command/component/FoxyComponentManager.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/command/component/FoxyComponentManager.kt index c0c188a4..637864b7 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/command/component/FoxyComponentManager.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/command/component/FoxyComponentManager.kt @@ -45,7 +45,7 @@ class FoxyComponentManager( ) = createButton(targetUser.idLong, style, emoji, label, builder, callback) fun createLinkButton( - emoji: Emoji? = null, + emoji: String? = null, label: String = "", url: String, builder: (ButtonBuilder).() -> (Unit) = {}, @@ -56,7 +56,7 @@ class FoxyComponentManager( builder ) - fun createButton( + private fun createButton( targetUserId: Long, style: ButtonStyle, emoji: String? = null, @@ -83,8 +83,8 @@ class FoxyComponentManager( callback.invoke(it) } - fun linkButton( - emoji: Emoji? = null, + private fun linkButton( + emoji: String? = null, label: String = "", url: String, builder: (ButtonBuilder).() -> (Unit) = {}, @@ -93,7 +93,7 @@ class FoxyComponentManager( ButtonStyle.LINK, url, label, - emoji + Emoji.fromFormatted("<:emoji:$emoji") ).let { ButtonBuilder(it).apply(builder).button } @@ -112,7 +112,7 @@ class FoxyComponentManager( style, ComponentId(buttonId).toString(), label, - emoji?.let { foxy.shardManager.getEmojiById(it) } + emoji?.let { Emoji.fromFormatted("<:emoji:$emoji") } ).let { ButtonBuilder(it).apply(builder).button } @@ -149,7 +149,7 @@ class FoxyComponentManager( callback.invoke(context, strings) } - fun stringSelectMenu( + private fun stringSelectMenu( builder: (StringSelectMenu.Builder).() -> (Unit) = {}, callback: suspend (FoxyInteractionContext, List) -> (Unit) ): StringSelectMenu { @@ -160,7 +160,7 @@ class FoxyComponentManager( .build() } - fun modal( + private fun modal( title: String, builder: (ModalBuilder).() -> (Unit) = {}, callback: suspend (FoxyInteractionContext) -> (Unit) diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/command/vanilla/economy/DailyExecutor.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/command/vanilla/economy/DailyExecutor.kt index 516cab9c..a1fb363e 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/command/vanilla/economy/DailyExecutor.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/command/vanilla/economy/DailyExecutor.kt @@ -20,13 +20,13 @@ class DailyExecutor: FoxyCommandExecutor() { actionRow( context.foxy.interactionManager.createLinkButton( - context.foxy.shardManager.getEmojiById(FoxyEmotes.FoxyPetPet), + FoxyEmotes.FoxyPetPet, context.locale["daily.embed.redeemDaily"], Constants.DAILY ), context.foxy.interactionManager.createLinkButton( - context.foxy.shardManager.getEmojiById(FoxyEmotes.FoxyDaily), + FoxyEmotes.FoxyDaily, context.locale["daily.embed.buyMore"], Constants.PREMIUM ) diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/command/vanilla/utils/PingExecutor.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/command/vanilla/utils/PingExecutor.kt index f920e77c..07a1623d 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/command/vanilla/utils/PingExecutor.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/command/vanilla/utils/PingExecutor.kt @@ -13,6 +13,8 @@ class PingExecutor : FoxyCommandExecutor() { val totalShards = context.jda.shardInfo.shardTotal val minClusterShards = context.foxy.currentCluster.minShard val maxClusterShards = context.foxy.currentCluster.maxShard + val currentClusterId = context.foxy.currentCluster.id + val currentClusterName = context.foxy.currentCluster.name context.reply { embed { @@ -34,7 +36,8 @@ class PingExecutor : FoxyCommandExecutor() { field { name = pretty(FoxyEmotes.FoxyDrinkingCoffee, "Cluster:") - value = "`${context.foxy.currentCluster.name}` **(Shard ${minClusterShards}/${maxClusterShards})**" + value = + "Cluster $currentClusterId - `${currentClusterName}` **[$minClusterShards-$maxClusterShards]**" inline = false } } diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/modules/antiraid/AntiRaidModule.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/modules/antiraid/AntiRaidModule.kt index bb284729..1556bfe4 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/modules/antiraid/AntiRaidModule.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/modules/antiraid/AntiRaidModule.kt @@ -65,7 +65,6 @@ class AntiRaidModule( val userId = event.user.id val antiRaidSettings = guildInfo.antiRaidModule - timestamps.add(currentTimestamp) if ((timestamps?.size ?: 0) > antiRaidSettings.newUsersThreshold) { diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/ActivityUpdater.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/ActivityUpdater.kt deleted file mode 100644 index e061dacb..00000000 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/ActivityUpdater.kt +++ /dev/null @@ -1,71 +0,0 @@ -package net.cakeyfox.foxy.utils - -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import io.ktor.server.application.* -import io.ktor.server.engine.* -import io.ktor.server.netty.* -import io.ktor.server.plugins.contentnegotiation.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import kotlinx.serialization.json.Json -import mu.KotlinLogging -import net.cakeyfox.foxy.FoxyInstance -import net.cakeyfox.serializable.data.ActivityUpdateRequest -import net.dv8tion.jda.api.OnlineStatus -import net.dv8tion.jda.api.entities.Activity -import net.dv8tion.jda.api.entities.Activity.ActivityType -import kotlin.reflect.jvm.jvmName - -class ActivityUpdater( - val foxy: FoxyInstance -) { - private val logger = KotlinLogging.logger(this::class.jvmName) - private val server = embeddedServer(Netty, port = foxy.config.others.activityUpdater.port) { - install(ContentNegotiation) { - json( - Json { - prettyPrint = true - isLenient = true - coerceInputValues = true - ignoreUnknownKeys = true - } - ) - } - - routing { - post("/status/update") { - val request = call.receive() - logger.info { "Received activity update request: $request" } - - if (request.type !in 0..5) { - logger.error { "Invalid activity type: ${request.type}" } - call.respond(HttpStatusCode.BadRequest, "Invalid activity type: ${request.type}") - return@post - } - - foxy.shardManager.shards.forEach { - request.status?.let { OnlineStatus.fromKey(it) } ?: OnlineStatus.ONLINE - Activity.of( - ActivityType.fromKey(request.type), - request.name, - request.url - ) - - logger.info { "Updating status for shard: #${it.shardInfo.shardId}" } - } - call.respond(HttpStatusCode.OK, "Activity updated") - } - } - } - - init { - server.start(wait = false) - } - - fun shutdown() { - logger.info { "Shutting down ActivityUpdater server" } - server.stop() - } -} \ No newline at end of file diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/FoxyUtils.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/FoxyUtils.kt index 2dc53c9e..c0fbf67a 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/FoxyUtils.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/FoxyUtils.kt @@ -82,7 +82,7 @@ class FoxyUtils( actionRow( foxy.interactionManager.createLinkButton( - context.jda.getEmojiById(FoxyEmotes.FoxyCupcake), + FoxyEmotes.FoxyCupcake, context.locale["ban.appealButton"], Constants.UNBAN_FORM_URL ) diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/api/FoxyInternalAPI.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/api/FoxyInternalAPI.kt index ef043ab1..b48af98b 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/api/FoxyInternalAPI.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/api/FoxyInternalAPI.kt @@ -9,46 +9,52 @@ import io.ktor.server.netty.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.response.* import io.ktor.server.routing.* +import mu.KotlinLogging import net.cakeyfox.common.Constants import net.cakeyfox.foxy.FoxyInstance -import net.cakeyfox.foxy.utils.api.routes.GetGuildInfo -import net.cakeyfox.foxy.utils.api.routes.GetGuildsFromCluster -import net.cakeyfox.foxy.utils.api.routes.GetUserRolesFromAGuild +import net.cakeyfox.foxy.utils.api.routes.* import java.io.File +import kotlin.reflect.jvm.jvmName class FoxyInternalAPI( val foxy: FoxyInstance ) { - init { - embeddedServer(Netty, port = foxy.config.others.statsSenderPort) { - install(ContentNegotiation) { - json() - } + private val logger = KotlinLogging.logger(this::class.jvmName) + private val server = embeddedServer(Netty, port = foxy.config.others.internalApi.port) { + install(ContentNegotiation) { + json() + } - install(Authentication) { - bearer("auth-bearer") { - authenticate { tokenCredential -> - if (tokenCredential.token == foxy.config.others.internalApi.key) { - UserIdPrincipal("authenticatedUser") - } else { - null - } + install(Authentication) { + bearer("auth-bearer") { + authenticate { tokenCredential -> + if (tokenCredential.token == foxy.config.others.internalApi.key) { + UserIdPrincipal("authenticatedUser") + } else { + null } } } + } - routing { - get("/") { - call.respondRedirect(Constants.FOXY_WEBSITE) - } - authenticate("auth-bearer") { - GetGuildsFromCluster().apply { getGuildsFromCluster(foxy) } - GetGuildInfo().apply { getGuildInfo(foxy) } - GetUserRolesFromAGuild().apply { getUserRolesFromAGuild(foxy) } - } - - staticFiles("/assets", File(this::class.java.classLoader.getResource("profile")!!.file)) + routing { + get("/") { + call.respondRedirect(Constants.FOXY_WEBSITE) } - }.start(wait = false) + authenticate("auth-bearer") { + GetGuildsFromCluster().apply { getGuildsFromCluster(foxy) } + GetGuildInfo().apply { getGuildInfo(foxy) } + GetUserRolesFromAGuild().apply { getUserRolesFromAGuild(foxy) } + GetClusterInfo().apply { getClusterInfo(foxy) } + UpdateActivityRoute().apply { updateActivity(foxy) } + } + + staticFiles("/assets", File(this::class.java.classLoader.getResource("profile")!!.file)) + } + }.start(wait = false) + + fun stop() { + server.stop() + logger.info { "FoxyInternalAPI server stopped" } } } \ No newline at end of file diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/api/routes/GetClusterInfo.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/api/routes/GetClusterInfo.kt new file mode 100644 index 00000000..62b20a86 --- /dev/null +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/api/routes/GetClusterInfo.kt @@ -0,0 +1,58 @@ +package net.cakeyfox.foxy.utils.api.routes + +import io.ktor.http.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import kotlinx.serialization.json.* +import net.cakeyfox.foxy.FoxyInstance +import java.lang.management.ManagementFactory + +class GetClusterInfo { + fun Route.getClusterInfo(foxy: FoxyInstance) { + get("/api/v1/info") { + val currentCluster = foxy.currentCluster + val runtime = Runtime.getRuntime() + val usedMemory = (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024 + val maxMemory = runtime.maxMemory() / 1024 / 1024 + val freeMemory = runtime.freeMemory() / 1024 / 1024 + val totalMemory = runtime.totalMemory() / 1024 / 1024 + + val clusterInfo = buildJsonObject { + put("id", currentCluster.id) + put("name", currentCluster.name) + putJsonObject("versions") { + put("kotlin", KotlinVersion.CURRENT.toString()) + put("java", System.getProperty("java.version")) + } + put("threadCount", Thread.activeCount()) + put("shardCount", foxy.shardManager.shards.size) + put("guildCount", foxy.shardManager.shards.sumOf { it.guilds.size }) + put("ping", foxy.shardManager.averageGatewayPing) + put("minShard", currentCluster.minShard) + put("maxShard", currentCluster.maxShard) + put("uptime", ManagementFactory.getRuntimeMXBean().uptime) + putJsonObject("memory") { + put("used", usedMemory) + put("max", maxMemory) + put("free", freeMemory) + put("total", totalMemory) + } + putJsonArray("shards") { + for (shard in foxy.shardManager.shards.sortedBy { it.shardInfo.shardId }) { + addJsonObject { + put("id", shard.shardInfo.shardId) + put("ping", shard.gatewayPing) + put("status", shard.status.toString()) + put("guildCount", shard.guildCache.size()) + } + } + } + } + + call.respondText( + text = clusterInfo.toString(), + contentType = ContentType.Application.Json + ) + } + } +} \ No newline at end of file diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/api/routes/UpdateActivityRoute.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/api/routes/UpdateActivityRoute.kt new file mode 100644 index 00000000..14283456 --- /dev/null +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/api/routes/UpdateActivityRoute.kt @@ -0,0 +1,42 @@ +package net.cakeyfox.foxy.utils.api.routes + +import io.ktor.http.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import mu.KotlinLogging +import net.cakeyfox.foxy.FoxyInstance +import net.cakeyfox.serializable.data.ActivityUpdateRequest +import net.dv8tion.jda.api.OnlineStatus +import net.dv8tion.jda.api.entities.Activity +import net.dv8tion.jda.api.entities.Activity.ActivityType + +class UpdateActivityRoute { + private val logger = KotlinLogging.logger { } + + fun Route.updateActivity(foxy: FoxyInstance) { + post("/api/v1/status/update") { + val request = call.receive() + logger.info { "Received activity update request: $request" } + + if (request.type !in 0..5) { + logger.error { "Invalid activity type: ${request.type}" } + call.respond(HttpStatusCode.BadRequest, "Invalid activity type: ${request.type}") + return@post + } + + if (request.status != null) { + foxy.shardManager.setStatus(OnlineStatus.fromKey(request.status)) + } + + foxy.shardManager.setActivity( + Activity.of( + ActivityType.fromKey(request.type), + request.name, + request.url + ) + ) + call.respond(HttpStatusCode.OK, "Activity updated") + } + } +} \ No newline at end of file diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/config/FoxyConfig.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/config/FoxyConfig.kt index a9d3d7f1..4a37755d 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/config/FoxyConfig.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/config/FoxyConfig.kt @@ -16,12 +16,14 @@ data class FoxyConfig( val applicationId: String, val token: String, val totalShards: Int, + val getClusterIdFromHostname: Boolean, + val replicaId: Int, val clusters: List ) @Serializable data class Cluster( - val id: String, + val id: Int, val name: String, val minShard: Int, val maxShard: Int, @@ -41,13 +43,12 @@ data class FoxyConfig( val foxyApi: FoxyAPISettings, val internalApi: InternalApi, val artistry: ArtistrySettings, - val activityUpdater: ActivityUpdaterSettings, - val statsSenderPort: Int, val topggToken: String, ) { @Serializable data class InternalApi( - val key: String + val key: String, + val port: Int ) @Serializable @@ -59,10 +60,5 @@ data class FoxyConfig( data class ArtistrySettings( val key: String ) - - @Serializable - data class ActivityUpdaterSettings( - val port: Int - ) } } \ No newline at end of file diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/MongoDBClient.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/MongoDBClient.kt index f0499dd5..23bf94fd 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/MongoDBClient.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/MongoDBClient.kt @@ -1,14 +1,14 @@ package net.cakeyfox.foxy.utils.database -import com.mongodb.client.MongoClient -import com.mongodb.client.MongoClients -import com.mongodb.client.MongoCollection -import com.mongodb.client.MongoDatabase +import com.mongodb.kotlin.client.coroutine.MongoClient +import com.mongodb.kotlin.client.coroutine.MongoCollection +import com.mongodb.kotlin.client.coroutine.MongoDatabase import kotlinx.serialization.json.Json import mu.KotlinLogging import net.cakeyfox.foxy.utils.database.utils.DatabaseUtils import net.cakeyfox.foxy.FoxyInstance -import org.bson.Document +import net.cakeyfox.serializable.database.data.FoxyUser +import net.cakeyfox.serializable.database.data.Guild import kotlin.reflect.jvm.jvmName class MongoDBClient { @@ -17,8 +17,8 @@ class MongoDBClient { } private lateinit var mongoClient: MongoClient - private lateinit var guilds: MongoCollection - lateinit var users: MongoCollection + private lateinit var guilds: MongoCollection + lateinit var users: MongoCollection lateinit var database: MongoDatabase val json = Json { @@ -29,7 +29,7 @@ class MongoDBClient { val utils = DatabaseUtils(this) fun start(foxy: FoxyInstance) { - mongoClient = MongoClients.create(foxy.config.database.uri) + mongoClient = MongoClient.create(foxy.config.database.uri) database = mongoClient.getDatabase(foxy.config.database.databaseName) users = database.getCollection("users") guilds = database.getCollection("guilds") diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/BotUtils.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/BotUtils.kt new file mode 100644 index 00000000..ea96cf0e --- /dev/null +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/BotUtils.kt @@ -0,0 +1,69 @@ +package net.cakeyfox.foxy.utils.database.utils + +import com.mongodb.kotlin.client.coroutine.MongoCollection +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import mu.KotlinLogging +import net.cakeyfox.foxy.utils.database.MongoDBClient +import org.bson.Document + +class BotUtils( + val client: MongoDBClient +) { + companion object { + private val logger = KotlinLogging.logger { } + } + + suspend fun getBotSettings(): BotSettings { + val botSettings = client.database.getCollection("botSettings") + val botSettingsDocument = botSettings.find().firstOrNull() + ?: run { + createBotSettings(botSettings) + return BotSettings( + activity = "foxybot.win · /help", + status = "online", + avatarUrl = null + ) + } + + val documentToJSON = botSettingsDocument.toJson() + return client.json.decodeFromString(documentToJSON) + } + + suspend fun getActivity(): String { + val botSettings = client.database.getCollection("botSettings") + val botSettingsDocument = botSettings.find().firstOrNull() + ?: run { + createBotSettings(botSettings) + return "foxybot.win · /help" + } + + val documentToJSON = botSettingsDocument.toJson() + val botSettingsData = client.json.decodeFromString(documentToJSON) + + return botSettingsData.activity + } + + @Serializable + data class BotSettings( + val activity: String, + val status: String, + val avatarUrl: String? + ) + + private suspend fun createBotSettings(botSettingsCollection: MongoCollection) { + val botSettings = BotSettings( + activity = "foxybot.win · /help", + status = "online", + avatarUrl = null + ) + + logger.info { "Creating new bot settings document" } + + val documentToJSON = client.json.encodeToString(botSettings) + val document = Document.parse(documentToJSON) + + botSettingsCollection.insertOne(document) + } +} \ No newline at end of file diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/DatabaseUtils.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/DatabaseUtils.kt index 91129024..6c602238 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/DatabaseUtils.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/DatabaseUtils.kt @@ -9,4 +9,5 @@ class DatabaseUtils( val economy = EconomyUtils(client) val guild = GuildUtils(client) val user = UserUtils(client) + val bot = BotUtils(client) } \ No newline at end of file diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/EconomyUtils.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/EconomyUtils.kt index 6cfade8a..d1ceed70 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/EconomyUtils.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/EconomyUtils.kt @@ -12,7 +12,9 @@ import java.util.Date class EconomyUtils( private val client: MongoDBClient ) { - fun createTransaction(userId: String, transaction: Transaction) { + suspend fun createTransaction(userId: String, transaction: Transaction) { + val collection = client.database.getCollection("users") + val update = combine( push( "userTransactions", @@ -28,6 +30,6 @@ class EconomyUtils( val updateOptions = UpdateOptions().upsert(true) - client.users.updateOne(eq("_id", userId), update, updateOptions) + collection.updateOne(eq("_id", userId), update, updateOptions) } } \ No newline at end of file diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/GuildUtils.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/GuildUtils.kt index 1ca337b9..29a5799a 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/GuildUtils.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/GuildUtils.kt @@ -11,8 +11,10 @@ import kotlin.reflect.KClass import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.node.ObjectNode import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext import mu.KotlinLogging +import com.mongodb.client.model.Filters.eq import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.jvmName @@ -32,14 +34,13 @@ class GuildUtils( suspend fun deleteGuild(guildId: String) { withContext(Dispatchers.IO) { - val guilds = client.database.getCollection("guilds") - val query = Document("_id", guildId) - guilds.deleteOne(query) + val guilds = client.database.getCollection("guilds") + guilds.deleteOne(eq("_id", guildId)) } } - private fun createGuild(guildId: String): Guild { - val guilds = client.database.getCollection("guilds") + private suspend fun createGuild(guildId: String): Guild { + val guilds = client.database.getCollection("guilds") val newGuild = Guild( _id = guildId, @@ -61,10 +62,11 @@ class GuildUtils( private suspend fun updateGuildWithNewFields(guildId: String): Guild { return withContext(Dispatchers.IO) { - val guilds = client.database.getCollection("guilds") + val guilds = client.database.getCollection("guilds") - val query = Document("_id", guildId) - val existingDocument = guilds.find(query).firstOrNull() ?: return@withContext createGuild(guildId) + val existingDocument = + guilds.find(eq("_id", guildId)) + .firstOrNull() ?: return@withContext createGuild(guildId) val documentToJSON = existingDocument.toJson() @@ -78,7 +80,7 @@ class GuildUtils( val updatedDocument = Document.parse(client.json.encodeToString(updatedGuild)) val update = Document("\$set", updatedDocument) - guilds.updateOne(query, update) + guilds.updateOne(eq("_id", guildId), update) updatedGuild } diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/ProfileUtils.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/ProfileUtils.kt index a7640b5b..beb7a9cc 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/ProfileUtils.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/ProfileUtils.kt @@ -1,6 +1,6 @@ package net.cakeyfox.foxy.utils.database.utils -import com.mongodb.client.MongoCollection +import kotlinx.coroutines.flow.firstOrNull import mu.KotlinLogging import net.cakeyfox.foxy.utils.database.MongoDBClient import net.cakeyfox.serializable.database.data.Background @@ -15,8 +15,8 @@ class ProfileUtils( ) { private val logger = KotlinLogging.logger(this::class.jvmName) - fun getBackground(backgroundId: String): Background { - val collection: MongoCollection = client.database.getCollection("backgrounds") + suspend fun getBackground(backgroundId: String): Background { + val collection = client.database.getCollection("backgrounds") val query = Document("id", backgroundId) val existingDocument = collection.find(query).firstOrNull() @@ -30,8 +30,8 @@ class ProfileUtils( return client.json.decodeFromString(documentToJSON!!) } - fun getLayout(layoutId: String): Layout { - val collection: MongoCollection = client.database.getCollection("layouts") + suspend fun getLayout(layoutId: String): Layout { + val collection = client.database.getCollection("layouts") val query = Document("id", layoutId) val existingDocument = collection.find(query).firstOrNull() @@ -45,8 +45,8 @@ class ProfileUtils( return client.json.decodeFromString(documentToJSON!!) } - fun getDecoration(decorationId: String): Decoration { - val collection: MongoCollection = client.database.getCollection("decorations") + suspend fun getDecoration(decorationId: String): Decoration { + val collection = client.database.getCollection("decorations") val query = Document("id", decorationId) val existingDocument = collection.find(query).firstOrNull() @@ -60,11 +60,11 @@ class ProfileUtils( return client.json.decodeFromString(documentToJSON!!) } - fun getBadges(): List { - val collection: MongoCollection = client.database.getCollection("badges") + suspend fun getBadges(): List { + val collection= client.database.getCollection("badges") val badges = mutableListOf() - collection.find().forEach { + collection.find().collect { val documentToJSON = it.toJson() badges.add(client.json.decodeFromString(documentToJSON!!)) } diff --git a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/UserUtils.kt b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/UserUtils.kt index 36299872..83559a4c 100644 --- a/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/UserUtils.kt +++ b/foxy/src/main/kotlin/net/cakeyfox/foxy/utils/database/utils/UserUtils.kt @@ -1,13 +1,14 @@ package net.cakeyfox.foxy.utils.database.utils -import com.mongodb.client.MongoCollection import kotlinx.datetime.toJavaInstant import kotlinx.serialization.encodeToString import net.cakeyfox.foxy.utils.database.MongoDBClient import net.cakeyfox.serializable.database.data.* import org.bson.Document import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext +import com.mongodb.client.model.Filters.eq class UserUtils( private val client: MongoDBClient @@ -15,10 +16,9 @@ class UserUtils( suspend fun getDiscordUser(userId: String): FoxyUser { return withContext(Dispatchers.IO) { - val collection: MongoCollection = client.database.getCollection("users") + val collection = client.database.getCollection("users") - val query = Document("_id", userId) - val existingUserDocument = collection.find(query).firstOrNull() + val existingUserDocument = collection.find(eq("_id", userId)).firstOrNull() ?: return@withContext createUser(userId) val documentToJSON = existingUserDocument.toJson() @@ -47,11 +47,11 @@ class UserUtils( suspend fun getAllUsers(): List { return withContext(Dispatchers.IO) { - val collection: MongoCollection = client.database.getCollection("users") + val collection = client.database.getCollection("users") val users = mutableListOf() - collection.find().forEach { + collection.find().collect { val documentToJSON = it.toJson() users.add(client.json.decodeFromString(documentToJSON)) } @@ -60,7 +60,9 @@ class UserUtils( } } - private fun createUser(userId: String): FoxyUser { + private suspend fun createUser(userId: String): FoxyUser { + val collection = client.database.getCollection("users") + val newUser = FoxyUser( _id = userId, userCakes = UserCakes(balance = 0.0), @@ -78,7 +80,7 @@ class UserUtils( val document = Document.parse(documentToJSON) document["userCreationTimestamp"] = java.util.Date.from(newUser.userCreationTimestamp.toJavaInstant()) - client.users.insertOne(document) + collection.insertOne(document) return newUser } diff --git a/foxy/src/main/resources/foxy.conf b/foxy/src/main/resources/foxy.conf index 99c41196..5ee7a918 100644 --- a/foxy/src/main/resources/foxy.conf +++ b/foxy/src/main/resources/foxy.conf @@ -11,10 +11,14 @@ discord = { # the sum of all cluster shards totalShards = 1 + # If true, Foxy will get the cluster ID from the hostname substring (e.g. foxy-1, foxy-2, foxy-3) + getClusterIdFromHostname = false + + # If getClusterIdFromHostname is false, this will be used as the current cluster ID + replicaId = 1 + clusters = [{ - # ID must be your machine name (e.g. DESKTOP-XXXXX) - id = "foxy" - # Put what you want, which will be shown on /ping command + id = 1 name = "Foxy" # Shards that will be launched on this cluster # (e.g. if minShard is 0 and maxShard is 3, this cluster will handle the shards 0,1,2,3) @@ -44,17 +48,13 @@ others = { internalApi = { key = "" + port = 3000 } topggToken = "" - statsSenderPort = 3000 # Artistry (https://github.com/FoxyTheBot/Artistry) artistry = { key = } - - activityUpdater = { - port = 8080 - } } \ No newline at end of file