Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/main/kotlin/commands/CommandName.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ sealed class CommandName(val commandName: String) {
data object Next : CommandName("next")
data object Disconnect : CommandName("disconnect")
data object Locale : CommandName("locale")
data object Config : CommandName("config") {
data object Reload : CommandName("reload")
data object Set : CommandName("set")
data object Get : CommandName("get")
}
data object Radio : CommandName("radio") {
data object Play : CommandName("play")
data object List : CommandName("list")
Expand Down
59 changes: 59 additions & 0 deletions src/main/kotlin/commands/config/ConfigGroupCommand.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package es.wokis.commands.config

import dev.kord.core.Kord
import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
import dev.kord.core.behavior.interaction.response.respond
import dev.kord.core.entity.interaction.AutoCompleteInteraction
import dev.kord.core.entity.interaction.ChatInputCommandInteraction
import dev.kord.core.entity.interaction.SubCommand as KordSubCommand
import es.wokis.commands.Autocomplete
import es.wokis.commands.CommandName
import es.wokis.commands.Component
import es.wokis.commands.GroupCommand
import es.wokis.localization.LocalizationKeys
import es.wokis.services.localization.LocalizationService
import es.wokis.utils.orDefaultLocale

class ConfigGroupCommand(
private val configReloadCommand: ConfigReloadCommand,
private val configSetCommand: ConfigSetCommand,
private val configGetCommand: ConfigGetCommand,
private val localizationService: LocalizationService
) : GroupCommand, Component, Autocomplete {

override suspend fun onRegisterCommand(kord: Kord) {
kord.createGlobalChatInputCommand(CommandName.Config.commandName, localizationService.getString(LocalizationKeys.CONFIG_COMMAND_DESCRIPTION)) {
descriptionLocalizations = localizationService.getLocalizations(LocalizationKeys.CONFIG_COMMAND_DESCRIPTION)
configReloadCommand.onRegisterCommand(this)
configSetCommand.onRegisterCommand(this)
configGetCommand.onRegisterCommand(this)
}
}

override suspend fun onExecute(
interaction: ChatInputCommandInteraction,
response: DeferredPublicMessageInteractionResponseBehavior
) {
val commandName = (interaction.command as? KordSubCommand)?.name
commandName?.let {
when (commandName) {
CommandName.Config.Reload.commandName -> configReloadCommand.onExecute(interaction, response)
CommandName.Config.Set.commandName -> configSetCommand.onExecute(interaction, response)
CommandName.Config.Get.commandName -> configGetCommand.onExecute(interaction, response)
}
} ?: response.respond {
val locale = interaction.guildLocale.orDefaultLocale()
content = localizationService.getString(LocalizationKeys.ERROR_UNEXPECTED, locale)
}
}

override suspend fun onInteract(interaction: dev.kord.core.entity.interaction.ComponentInteraction) {
}

override suspend fun onAutoComplete(interaction: AutoCompleteInteraction) {
val subCommandName = (interaction.command as? KordSubCommand)?.name
when (subCommandName) {
CommandName.Config.Get.commandName -> configGetCommand.onAutoComplete(interaction)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package es.wokis.commands.config

import dev.kord.common.entity.Choice
import dev.kord.common.entity.optional.Optional
import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
import dev.kord.core.behavior.interaction.response.respond
import dev.kord.core.behavior.interaction.suggest
import dev.kord.core.entity.interaction.AutoCompleteInteraction
import dev.kord.core.entity.interaction.ChatInputCommandInteraction
import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
import dev.kord.rest.builder.interaction.string
import dev.kord.rest.builder.interaction.subCommand
import es.wokis.commands.Autocomplete
import es.wokis.commands.CommandName
import es.wokis.commands.SubCommand
import es.wokis.localization.LocalizationKeys
import es.wokis.services.config.ConfigService
import es.wokis.services.localization.LocalizationService
import es.wokis.utils.orDefaultLocale

private const val ARGUMENT_SECTION = "section"

class ConfigGetCommand(
private val configService: ConfigService,
private val localizationService: LocalizationService
) : SubCommand, Autocomplete {

private val validSections = listOf("database", "youtube", "deezer", "spotify", "tidal", "kokoro")

override suspend fun onRegisterCommand(builder: GlobalChatInputCreateBuilder) {
builder.apply {
subCommand(CommandName.Config.Get.commandName, localizationService.getString(LocalizationKeys.CONFIG_GET_COMMAND_DESCRIPTION)) {
descriptionLocalizations = localizationService.getLocalizations(LocalizationKeys.CONFIG_GET_SECTION_DESCRIPTION)
string(ARGUMENT_SECTION, localizationService.getString(LocalizationKeys.CONFIG_GET_SECTION_DESCRIPTION)) {
descriptionLocalizations = localizationService.getLocalizations(LocalizationKeys.CONFIG_GET_SECTION_DESCRIPTION)
required = true
autocomplete = true
}
}
}
}

override suspend fun onExecute(
interaction: ChatInputCommandInteraction,
response: DeferredPublicMessageInteractionResponseBehavior
) {
val locale = interaction.guildLocale.orDefaultLocale()
val section = interaction.command.strings[ARGUMENT_SECTION]

if (section == null || section !in validSections) {
response.respond {
content = localizationService.getString(LocalizationKeys.CONFIG_INVALID_SECTION, locale)
}
return
}

val config = configService.config
val sectionData = when (section) {
"database" -> config.database
"youtube" -> config.youtube
"deezer" -> config.deezer
"spotify" -> config.spotify
"tidal" -> config.tidal
"kokoro" -> config.kokoro
else -> null
}

response.respond {
content = localizationService.getStringFormat(
LocalizationKeys.CONFIG_GET_DISPLAY,
locale,
arrayOf(section, sectionData.toString())
)
}
}

override suspend fun onAutoComplete(interaction: AutoCompleteInteraction) {
val input = interaction.command.strings[ARGUMENT_SECTION].orEmpty().lowercase()

val suggestions = validSections
.filter { it.lowercase().contains(input) }
.take(25)
.map { section ->
Choice.StringChoice(
name = section,
nameLocalizations = Optional.Missing(),
value = section
)
}

interaction.suggest(suggestions)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package es.wokis.commands.config

import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
import dev.kord.core.behavior.interaction.response.respond
import dev.kord.core.entity.interaction.ChatInputCommandInteraction
import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
import dev.kord.rest.builder.interaction.subCommand
import es.wokis.commands.CommandName
import es.wokis.commands.SubCommand
import es.wokis.localization.LocalizationKeys
import es.wokis.services.config.ConfigService
import es.wokis.services.localization.LocalizationService
import es.wokis.utils.Log
import es.wokis.utils.orDefaultLocale

class ConfigReloadCommand(
private val configService: ConfigService,
private val localizationService: LocalizationService
) : SubCommand {

override suspend fun onRegisterCommand(builder: GlobalChatInputCreateBuilder) {
builder.apply {
subCommand(CommandName.Config.Reload.commandName, localizationService.getString(LocalizationKeys.CONFIG_RELOAD_COMMAND_DESCRIPTION)) {
descriptionLocalizations = localizationService.getLocalizations(LocalizationKeys.CONFIG_RELOAD_COMMAND_DESCRIPTION)
}
}
}

override suspend fun onExecute(
interaction: ChatInputCommandInteraction,
response: DeferredPublicMessageInteractionResponseBehavior
) {
val locale = interaction.guildLocale.orDefaultLocale()
try {
configService.reload()
Log.info("Configuration reloaded via command by user ${interaction.user.id}")
response.respond {
content = localizationService.getString(LocalizationKeys.CONFIG_RELOAD_SUCCESS, locale)
}
} catch (e: Exception) {
Log.error("Failed to reload config via command", e)
response.respond {
content = localizationService.getString(LocalizationKeys.ERROR_UNEXPECTED, locale)
}
}
}
}
147 changes: 147 additions & 0 deletions src/main/kotlin/commands/config/subcommands/set/ConfigSetCommand.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package es.wokis.commands.config

import dev.kord.common.entity.Permissions
import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
Comment thread
Wikijito7 marked this conversation as resolved.
import dev.kord.core.behavior.interaction.response.respond
import dev.kord.core.entity.interaction.ChatInputCommandInteraction
import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
import dev.kord.rest.builder.interaction.string
import dev.kord.rest.builder.interaction.subCommand
import es.wokis.commands.CommandName
import es.wokis.commands.SubCommand
import es.wokis.localization.LocalizationKeys
import es.wokis.services.config.ConfigService
import es.wokis.services.localization.LocalizationService
import es.wokis.utils.Log
import es.wokis.utils.orDefaultLocale
import java.io.File
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive

private const val ARGUMENT_SECTION = "section"
private const val ARGUMENT_KEY = "key"
private const val ARGUMENT_VALUE = "value"
private const val CONFIG_PATH = "./data/config.json"

class ConfigSetCommand(
private val configService: ConfigService,
private val localizationService: LocalizationService
) : SubCommand {

private val validSections = mapOf(
"database" to listOf("enabled", "username", "password", "database"),
"youtube" to listOf("enabled", "oauth2_token", "po_token", "visitor_data", "remote_cipher_url", "remote_cipher_password"),
"deezer" to listOf("enabled", "master_decryption_key", "arl_token"),
"spotify" to listOf("enabled", "client_id", "client_secret", "custom_endpoint"),
"tidal" to listOf("enabled", "country_code", "token"),
"kokoro" to listOf("enabled", "base_url", "default_voice", "default_speed", "default_lang_code")
)

override suspend fun onRegisterCommand(builder: GlobalChatInputCreateBuilder) {
builder.apply {
subCommand(CommandName.Config.Set.commandName, localizationService.getString(LocalizationKeys.CONFIG_SET_COMMAND_DESCRIPTION)) {
descriptionLocalizations = localizationService.getLocalizations(LocalizationKeys.CONFIG_SET_COMMAND_DESCRIPTION)
string(ARGUMENT_SECTION, localizationService.getString(LocalizationKeys.CONFIG_SET_SECTION_DESCRIPTION)) {
descriptionLocalizations = localizationService.getLocalizations(LocalizationKeys.CONFIG_SET_SECTION_DESCRIPTION)
required = true
}
string(ARGUMENT_KEY, localizationService.getString(LocalizationKeys.CONFIG_SET_KEY_DESCRIPTION)) {
descriptionLocalizations = localizationService.getLocalizations(LocalizationKeys.CONFIG_SET_KEY_DESCRIPTION)
required = true
}
string(ARGUMENT_VALUE, localizationService.getString(LocalizationKeys.CONFIG_SET_VALUE_DESCRIPTION)) {
descriptionLocalizations = localizationService.getLocalizations(LocalizationKeys.CONFIG_SET_VALUE_DESCRIPTION)
required = true
}
}
}
}

override suspend fun onExecute(
interaction: ChatInputCommandInteraction,
response: DeferredPublicMessageInteractionResponseBehavior
) {
val locale = interaction.guildLocale.orDefaultLocale()
val section = interaction.command.strings[ARGUMENT_SECTION]
val key = interaction.command.strings[ARGUMENT_KEY]
val value = interaction.command.strings[ARGUMENT_VALUE]

if (section == null || section !in validSections) {
response.respond {
content = localizationService.getString(LocalizationKeys.CONFIG_INVALID_SECTION, locale)
}
return
}

if (key == null || key !in validSections[section]!!) {
response.respond {
content = localizationService.getString(LocalizationKeys.CONFIG_INVALID_KEY, locale)
}
return
}

if (value.isNullOrEmpty()) {
response.respond {
content = localizationService.getString(LocalizationKeys.ERROR_NO_CONTENT_PROVIDED, locale)
}
return
}

if (section == "discord_bot_token" || (section == "database" && key == "password")) {
response.respond {
content = localizationService.getString(LocalizationKeys.CONFIG_CANNOT_MODIFY_TOKEN, locale)
}
return
}

try {
updateConfigValue(section, key, value!!)
configService.reload()
Log.info("Config updated via command: $section.$key = $value by user ${interaction.user.id}")
response.respond {
content = localizationService.getStringFormat(
LocalizationKeys.CONFIG_SET_SUCCESS,
locale,
arrayOf("$section.$key", value)
)
}
} catch (e: Exception) {
Log.error("Failed to update config via command", e)
response.respond {
content = localizationService.getString(LocalizationKeys.ERROR_UNEXPECTED, locale)
}
}
}

private fun updateConfigValue(section: String, key: String, value: String) {
val configFile = File(CONFIG_PATH)
val json = Json { prettyPrint = true }
val configJson = json.parseToJsonElement(configFile.readText()) as JsonObject
val sectionJson = configJson[section] as? JsonObject
?: throw IllegalStateException("Section $section not found in config")

val updatedSectionJson = JsonObject(
sectionJson.toMutableMap().apply {
val newValue = when {
value.lowercase() == "true" -> JsonPrimitive(true)
value.lowercase() == "false" -> JsonPrimitive(false)
value.toDoubleOrNull() != null -> JsonPrimitive(value.toDouble())
else -> JsonPrimitive(value)
}
put(key, newValue)
}
)

val updatedConfigJson = JsonObject(
configJson.toMutableMap().apply {
put(section, updatedSectionJson)
}
)

configFile.writeText(json.encodeToString(
JsonObject.serializer(),
updatedConfigJson
))
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/di/CommandModule.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package es.wokis.di

import es.wokis.commands.config.ConfigGroupCommand
import es.wokis.commands.config.ConfigGetCommand
import es.wokis.commands.config.ConfigReloadCommand
import es.wokis.commands.config.ConfigSetCommand
import es.wokis.commands.queue.QueueCommand
import commands.play.PlayCommand
import es.wokis.commands.player.PlayerCommand
Expand Down Expand Up @@ -43,5 +47,9 @@ val commandModule = module {
factoryOf(::RadioSearchCountryCodeCommand)
factoryOf(::RadioRandomCommand)
factoryOf(::RadioCountryCodesCommand)
factoryOf(::ConfigGroupCommand)
factoryOf(::ConfigReloadCommand)
factoryOf(::ConfigSetCommand)
factoryOf(::ConfigGetCommand)
factoryOf(::LocaleCommand)
}
2 changes: 2 additions & 0 deletions src/main/kotlin/di/ServicesModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import es.wokis.dispatchers.AppDispatchers
import es.wokis.dispatchers.AppDispatchersImpl
import es.wokis.services.commands.CommandHandlerService
import es.wokis.services.commands.CommandHandlerServiceImpl
import es.wokis.services.config.ConfigMigrationService
import es.wokis.services.config.ConfigService
import es.wokis.services.error.ErrorHandlerService
import es.wokis.services.localization.LocalizationService
Expand All @@ -19,6 +20,7 @@ import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module

val servicesModule = module {
singleOf(::ConfigMigrationService)
singleOf(::ConfigService)
factoryOf(::MessageProcessorService)
factoryOf(::AudioPlayerManagerProvider)
Expand Down
Loading
Loading