Skip to content

Commit

Permalink
EXPERIMENTAL: Implements sharding manager
Browse files Browse the repository at this point in the history
  • Loading branch information
WinG4merBR committed Jan 8, 2025
1 parent 7a60fa3 commit 62fd9fd
Show file tree
Hide file tree
Showing 15 changed files with 151 additions and 59 deletions.
76 changes: 51 additions & 25 deletions foxy/src/main/kotlin/net/cakeyfox/foxy/FoxyInstance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.Job
import mu.KotlinLogging
import net.cakeyfox.artistry.ArtistryClient
import net.cakeyfox.foxy.command.FoxyCommandManager
Expand All @@ -13,28 +14,32 @@ import net.cakeyfox.foxy.listeners.GuildEventListener
import net.cakeyfox.foxy.listeners.InteractionEventListener
import net.cakeyfox.foxy.listeners.MajorEventListener
import net.cakeyfox.foxy.utils.ActivityUpdater
import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.JDABuilder
import net.cakeyfox.foxy.utils.config.FoxyConfig
import net.cakeyfox.foxy.utils.FoxyUtils
import net.cakeyfox.foxy.utils.database.MongoDBClient
import net.dv8tion.jda.api.entities.User
import net.dv8tion.jda.api.requests.GatewayIntent
import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder
import net.dv8tion.jda.api.sharding.ShardManager
import net.dv8tion.jda.api.utils.cache.CacheFlag
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.concurrent.thread
import kotlin.reflect.jvm.jvmName

class FoxyInstance(
val config: FoxyConfig
) {
lateinit var jda: JDA
lateinit var jda: ShardManager
lateinit var mongoClient: MongoDBClient
lateinit var commandHandler: FoxyCommandManager
lateinit var artistryClient: ArtistryClient
lateinit var utils: FoxyUtils
lateinit var interactionManager: FoxyComponentManager
lateinit var environment: String
lateinit var httpClient: HttpClient
lateinit var selfUser: User

fun start() {
suspend fun start() {
val logger = KotlinLogging.logger(this::class.jvmName)
val activityUpdater = ActivityUpdater(this)
mongoClient = MongoDBClient()
Expand All @@ -54,33 +59,54 @@ class FoxyInstance(
}

mongoClient.start(this)
jda = JDABuilder.createDefault(config.discordToken)
.setEnabledIntents(
GatewayIntent.GUILD_MEMBERS,
GatewayIntent.MESSAGE_CONTENT,
GatewayIntent.GUILD_MESSAGES,
GatewayIntent.GUILD_EMOJIS_AND_STICKERS,
GatewayIntent.SCHEDULED_EVENTS
)
.enableCache(CacheFlag.SCHEDULED_EVENTS)
.enableCache(CacheFlag.MEMBER_OVERRIDES)
.disableCache(CacheFlag.VOICE_STATE)
.build()

jda.addEventListener(
jda = DefaultShardManagerBuilder.create(
GatewayIntent.GUILD_MEMBERS,
GatewayIntent.MESSAGE_CONTENT,
GatewayIntent.GUILD_MESSAGES,
GatewayIntent.GUILD_EMOJIS_AND_STICKERS,
GatewayIntent.SCHEDULED_EVENTS
).addEventListeners(
MajorEventListener(this),
GuildEventListener(this),
InteractionEventListener(this)
)
.setShardsTotal(config.totalShards)
.setShards(config.minClusterShard, config.maxClusterShard)
.disableCache(CacheFlag.entries)
.enableCache(
CacheFlag.EMOJI,
CacheFlag.STICKER,
CacheFlag.MEMBER_OVERRIDES
)
.setToken(config.discordToken)
.setEnableShutdownHook(false)
.build()

jda.awaitReady()
this.commandHandler.handle()

Runtime.getRuntime().addShutdownHook(Thread {
logger.info { "Shutting down..." }
jda.shutdown()
httpClient.close()
mongoClient.close()
activityUpdater.shutdown()
selfUser = jda.getShardById(0)?.selfUser!!

Runtime.getRuntime().addShutdownHook(thread(false) {
try {
logger.info { "Foxy is shutting down..." }
jda.shards.forEach { shard ->
shard.removeEventListener(*shard.registeredListeners.toTypedArray())
logger.info { "Shutting down shard #${shard.shardInfo.shardId}..."}
shard.shutdown()
}
httpClient.close()
mongoClient.close()
activityUpdater.shutdown()

activeJobs.forEach {
logger.info { "Cancelling job $it" }
it.cancel()
}
} catch (e: Exception) {
logger.error(e) { "Error during shutdown process" }
}
})
}

private val activeJobs = ConcurrentLinkedQueue<Job>()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.cakeyfox.foxy.command

import dev.minn.jda.ktx.coroutines.await
import mu.KotlinLogging
import net.cakeyfox.foxy.FoxyInstance
import net.cakeyfox.foxy.command.structure.FoxyCommandDeclarationWrapper
import net.cakeyfox.foxy.command.vanilla.actions.declarations.ActionsCommand
Expand All @@ -13,11 +14,14 @@ import net.cakeyfox.foxy.command.vanilla.social.declarations.MarryCommand
import net.cakeyfox.foxy.command.vanilla.social.declarations.ProfileCommand
import net.cakeyfox.foxy.command.vanilla.utils.declarations.DblCommand
import net.cakeyfox.foxy.command.vanilla.utils.declarations.HelpCommand
import net.cakeyfox.foxy.command.vanilla.utils.declarations.PingCommand
import net.cakeyfox.foxy.command.vanilla.utils.declarations.TopCommand
import net.dv8tion.jda.api.interactions.commands.Command
import kotlin.reflect.jvm.jvmName

class FoxyCommandManager(private val foxy: FoxyInstance) {
private val commands = mutableListOf<FoxyCommandDeclarationWrapper>()
private val logger = KotlinLogging.logger(this::class.jvmName)

operator fun get(name: String): FoxyCommandDeclarationWrapper? {
return commands.find { it.create().name == name }
Expand All @@ -27,25 +31,25 @@ class FoxyCommandManager(private val foxy: FoxyInstance) {
commands.add(command)
}

suspend fun handle(): MutableList<Command>? {
val action = foxy.jda.updateCommands()
val privateGuild = foxy.jda.getGuildById(foxy.config.guildId)!!
suspend fun handle(): MutableList<Command> {
val allCommands = mutableListOf<Command>()
logger.info { "Starting command handling for ${foxy.jda.shards.size + 1} shards" }
foxy.jda.shards.forEach { shard ->
val action = shard.updateCommands()

commands.forEach { command ->
if (command.create().isPrivate) {
privateGuild.updateCommands().addCommands(
command.create().build()
).await()
} else {
action.addCommands(
command.create().build()
)
commands.forEach { command ->
action.addCommands(command.create().build())
}

val registeredCommands = action.await()
allCommands.addAll(registeredCommands)
logger.info { "${commands.size} commands registered on shard ${shard.shardInfo.shardId}" }
}

return action.await()
return allCommands
}


init {
/* ---- [Roleplay] ---- */
register(ActionsCommand())
Expand All @@ -71,5 +75,6 @@ class FoxyCommandManager(private val foxy: FoxyInstance) {
/* ---- [Utils] ---- */
register(HelpCommand())
register(DblCommand())
register(PingCommand())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class MarryExecutor : FoxyCommandExecutor() {
return
}

if (user.id == context.foxy.jda.selfUser.id) {
if (user.id == context.foxy.selfUser.id) {
context.reply(true) {
content = pretty(
FoxyEmotes.FoxyCry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class HelpExecutor : FoxyCommandExecutor() {
)

color = Colors.FOXY_DEFAULT
thumbnail = context.foxy.jda.selfUser.effectiveAvatarUrl
thumbnail = context.foxy.selfUser.effectiveAvatarUrl

// Yes, using "emoji" instead of emoji name will work
field {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package net.cakeyfox.foxy.command.vanilla.utils

import net.cakeyfox.common.FoxyEmotes
import net.cakeyfox.foxy.command.FoxyInteractionContext
import net.cakeyfox.foxy.command.structure.FoxyCommandExecutor
import net.cakeyfox.foxy.utils.pretty
import java.net.InetAddress

class PingExecutor : FoxyCommandExecutor() {
override suspend fun execute(context: FoxyInteractionContext) {
val gatewayPing = context.jda.gatewayPing
val currentShardId = context.jda.shardInfo.shardId + 1
val totalShards = context.jda.shardInfo.shardTotal

val response = pretty(FoxyEmotes.FoxyHowdy, "Ping\n") +
pretty(FoxyEmotes.FoxyWow, "Gateway Ping: ${gatewayPing}ms\n") +
pretty(FoxyEmotes.FoxyThink, "Shard ID: ${currentShardId}/${totalShards}\n") +
pretty(FoxyEmotes.FoxyCupcake, "Cluster: `${InetAddress.getLocalHost().hostName}`")

context.reply {
content = response
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package net.cakeyfox.foxy.command.vanilla.utils

import com.github.benmanes.caffeine.cache.Caffeine
import dev.minn.jda.ktx.coroutines.await
import net.cakeyfox.common.Colors
import net.cakeyfox.common.FoxyEmotes
import net.cakeyfox.foxy.command.FoxyInteractionContext
import net.cakeyfox.foxy.command.structure.FoxyCommandExecutor
import net.cakeyfox.serializable.database.data.FoxyUser
import okhttp3.Cache

class TopCakesExecutor : FoxyCommandExecutor() {
override suspend fun execute(context: FoxyInteractionContext) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package net.cakeyfox.foxy.command.vanilla.utils.declarations

import net.cakeyfox.foxy.command.structure.FoxyCommandDeclarationBuilder
import net.cakeyfox.foxy.command.structure.FoxyCommandDeclarationWrapper
import net.cakeyfox.foxy.command.vanilla.utils.PingExecutor

class PingCommand : FoxyCommandDeclarationWrapper {
override fun create(): FoxyCommandDeclarationBuilder = command(
"ping",
"ping.description",

block = {
executor = PingExecutor()
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ class MajorEventListener(private val foxy: FoxyInstance): ListenerAdapter() {
OnlineStatus.ONLINE,
Activity.customStatus(Constants.DEFAULT_ACTIVITY(foxy.environment)))

val commands = foxy.commandHandler.handle()
logger.info { "Registered ${commands?.size} commands" }

if (foxy.environment == "production") {
topggStats.send(event.jda.guildCache.size())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,16 @@ class ActivityUpdater(
return@post
}

foxy.jda.presence.setPresence(
request.status?.let { OnlineStatus.fromKey(it) } ?: OnlineStatus.ONLINE,
foxy.jda.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")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class TopggStatsSender(
): StatsSender {
private val logger = KotlinLogging.logger(this::class.jvmName)
private val token = foxy.config.dblToken
private val clientId = foxy.jda.selfUser.id
private val clientId = foxy.config.applicationId

override suspend fun send(guildCount: Long): Boolean {
return withContext(Dispatchers.IO) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import kotlinx.serialization.Serializable

@Serializable
data class FoxyConfig(
val applicationId: String,
val ownerId: String,
val guildId: String,
val environment: String,
val discordToken: String,
val minShards: Int,
val maxShards: Int,
val totalShards: Int,
val mongoUri: String,
val dbName: String,
val mongoTimeout: Long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import net.dv8tion.jda.api.entities.Member
import java.time.Instant

object BadgeUtils {
private val twelveHoursAgo = System.currentTimeMillis() - 12 * 60 * 60 * 1000

fun getBadges(member: Member, defaultBadges: List<Badge>, data: FoxyUser): List<Badge> {
val userBadges = mutableListOf<Badge>()

Expand All @@ -17,7 +19,6 @@ object BadgeUtils {
}
userBadges.addAll(roleBadges)

val twelveHoursAgo = System.currentTimeMillis() - 12 * 60 * 60 * 1000
val additionalBadges = listOf(
BadgeCondition("married", data.marryStatus.marriedWith != null),
BadgeCondition("upvoter", data.lastVote?.let {
Expand All @@ -41,13 +42,20 @@ object BadgeUtils {
}
}

defaultBadges.filter { it.isFromGuild != null }.forEach { badge ->
if (userBadges.none {
it.id == badge.id || it.isFromGuild == badge.isFromGuild
}) {
userBadges.add(badge)
}
}

return userBadges.distinctBy { it.id }.sortedByDescending { it.priority }
}

fun getFallbackBadges(defaultBadges: List<Badge>, userData: FoxyUser): List<Badge> {
val userBadges = mutableListOf<Badge>()

val twelveHoursAgo = System.currentTimeMillis() - 12 * 60 * 60 * 1000
val additionalBadges = listOf(
BadgeCondition("married", userData.marryStatus.marriedWith != null),
BadgeCondition("upvoter", userData.lastVote?.let {
Expand Down
20 changes: 10 additions & 10 deletions foxy/src/main/resources/foxy.conf
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Basic settings
ownerId=
guildId=
applicationId=

# Environment type
environment=development

# Basic Authentication
discordToken=

# Database Settings
mongoUri="YOUR-MONGODB-URI"
dbName=Foxy
minShards = 0
maxShards = 0
totalShards = 1

mongoUri=
dbName=foxy
mongoTimeout=5000

# Others
foxyApiKey=key
dblToken=key
artistryKey=key
foxyApiKey=
dblToken=
artistryKey=
activityPort=8080
Loading

0 comments on commit 62fd9fd

Please sign in to comment.