From 7c0ef9a6e9a8819b36e8c5abcfca77f0b0aa2e71 Mon Sep 17 00:00:00 2001 From: Brett Bender Date: Thu, 3 Nov 2022 14:30:57 -0500 Subject: [PATCH] feat: config files, better docs, move towards DB --- build.gradle.kts | 4 +- gradle.properties | 2 +- .../xyz/brettb/discord/ieeevents/Constants.kt | 2 +- .../xyz/brettb/discord/ieeevents/IEEEvents.kt | 63 ++++++------------- .../xyz/brettb/discord/ieeevents/Utils.kt | 5 +- .../ieeevents/commands/CommandCategories.kt | 7 +++ .../ieeevents/commands/info/HelpCommand.kt | 5 +- .../commands/info/UpcomingEventsCommand.kt | 4 ++ .../commands/settings/ChangePrefixCommand.kt | 4 ++ .../ieeevents/data/settings/BotConfig.kt | 58 +++++++++++++++++ .../data/settings/IEEEventsGuildSettings.kt | 30 ++++++++- .../ieeevents/services/EventsService.kt | 9 ++- .../ieeevents/services/StatusService.kt | 7 ++- .../ieeevents/services/ToggleableService.kt | 20 +++++- 14 files changed, 161 insertions(+), 59 deletions(-) create mode 100644 src/main/kotlin/xyz/brettb/discord/ieeevents/data/settings/BotConfig.kt diff --git a/build.gradle.kts b/build.gradle.kts index d52ac94..89b8a47 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,8 +59,8 @@ dependencies { // Data implementation("org.yaml:snakeyaml:1.31") implementation("org.litote.kmongo:kmongo:4.7.2") - implementation("com.sksamuel.hoplite:hoplite-core:2.6.5") - implementation("com.sksamuel.hoplite:hoplite-yaml:2.6.5") + implementation("com.sksamuel.hoplite:hoplite-core:1.4.16") + implementation("com.sksamuel.hoplite:hoplite-yaml:1.4.16") implementation(kotlin("stdlib-jdk8")) diff --git a/gradle.properties b/gradle.properties index e67e464..f71ad26 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ major=0 -minor=2 +minor=3 patch=0 kotlin.code.style=official diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/Constants.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/Constants.kt index 33523a5..a975ac3 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/Constants.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/Constants.kt @@ -3,7 +3,7 @@ package xyz.brettb.discord.ieeevents object Constants { object Colors { - val BLUE = 0x0066A1 + const val BLUE = 0x0066A1 } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/IEEEvents.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/IEEEvents.kt index 7f4260c..11e0bff 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/IEEEvents.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/IEEEvents.kt @@ -2,6 +2,7 @@ package xyz.brettb.discord.ieeevents import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger +import com.sksamuel.hoplite.ConfigLoader import net.dv8tion.jda.api.JDA import net.dv8tion.jda.api.JDABuilder import net.dv8tion.jda.api.Permission @@ -20,25 +21,20 @@ import net.fortuna.ical4j.model.Period import net.fortuna.ical4j.model.component.VEvent import org.reflections.Reflections import org.slf4j.LoggerFactory -import org.yaml.snakeyaml.Yaml import tech.junodevs.discord.kriess.impl.managers.CommandManager import tech.junodevs.discord.kriess.menus.MenuListener -import tech.junodevs.discord.kriess.services.Service import xyz.brettb.discord.ieeevents.commands.CommandBase -import xyz.brettb.discord.ieeevents.commands.info.HelpCommand -import xyz.brettb.discord.ieeevents.commands.info.UpcomingEventsCommand -import xyz.brettb.discord.ieeevents.commands.settings.ChangePrefixCommand +import xyz.brettb.discord.ieeevents.data.settings.BotConfig import xyz.brettb.discord.ieeevents.data.settings.IEEEventsGuildSettings import xyz.brettb.discord.ieeevents.data.settings.IEEEventsGuildSettingsManager -import xyz.brettb.discord.ieeevents.services.StatusService import xyz.brettb.discord.ieeevents.services.ToggleableService import java.io.File -import java.io.FileInputStream import java.nio.file.Files import java.time.Duration import java.time.temporal.ChronoUnit import kotlin.system.exitProcess +@Suppress("HasPlatformType") val logger = LoggerFactory.getLogger(IEEEventsBot.javaClass) fun main() { @@ -47,7 +43,7 @@ fun main() { IEEEventsBot.load() IEEEventsBot.JDA = JDABuilder - .createDefault(IEEEventsBot.token) + .createDefault(IEEEventsBot.config.token) .enableIntents(GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MEMBERS) .addEventListeners(IEEEventsBot.commandManager, IEEEventsBot, MenuListener) .setChunkingFilter(ChunkingFilter.ALL) @@ -69,13 +65,16 @@ fun main() { // Ensure the StatusService stops correctly Runtime.getRuntime().addShutdownHook(Thread { - IEEEventsBot.services.forEach{ service -> + IEEEventsBot.services.forEach { service -> (service.getDeclaredField("INSTANCE")[null] as ToggleableService).shutdown() } }) } +/** + * The IEEE Event Mirroring Bot + */ object IEEEventsBot : EventListener { /** * The [JDA] instance used by the bot. @@ -88,32 +87,18 @@ object IEEEventsBot : EventListener { lateinit var commandManager: CommandManager /** - * The YAML instance used. + * The bot [config]uration */ - private val yaml = Yaml() + lateinit var config: BotConfig /** - * The bot's default prefix. - */ - lateinit var prefix: String - - /** - * The bot's login token. - */ - lateinit var token: String - - /** - * The calendar url that we're attempting to mirror between discord + the source. - */ - lateinit var calendarUrl: String - - /** - * The iCal [Calendar] object gotten from the [calendarUrl]. + * The iCal [Calendar] object gotten from the [BotConfig.calendarUrl]. */ + @Suppress("MemberVisibilityCanBePrivate") val calendar: Calendar? get() = try { - val inputStream = Utils.downloadCalendar(calendarUrl) + val inputStream = Utils.downloadCalendar(config.calendarUrl) CalendarBuilder().build(inputStream) } catch (t: Throwable) { null @@ -137,13 +122,9 @@ object IEEEventsBot : EventListener { } /** - * Is the bot going to update its status? - */ - var updateStatus: Boolean = true - - /** - * The commands the bot has. + * The [commands] the bot has. */ + @Suppress("MemberVisibilityCanBePrivate") val commands: List> = Reflections("xyz.brettb.discord.ieeevents.commands").getSubTypesOf(CommandBase::class.java) .filterNot { @@ -159,7 +140,7 @@ object IEEEventsBot : EventListener { .sortedBy { it.name } /** - * The services the bot is using. + * The [services] the bot is using. */ val services: List> = Reflections("xyz.brettb.discord.ieeevents.services").getSubTypesOf(ToggleableService::class.java) @@ -185,20 +166,14 @@ object IEEEventsBot : EventListener { exitProcess(1) } - val output = yaml.load(FileInputStream(configFile)) as Map - prefix = output["prefix"].toString() - token = output["token"].toString() - updateStatus = output["update-status"]?.toString()?.toBoolean() ?: true - calendarUrl = output["calendar-url"].toString() - - val logLevel = output["log-level"]?.toString() ?: "INFO" + config = ConfigLoader().loadConfigOrThrow(configFile) - val level = Level.toLevel(logLevel, Level.INFO) + val level = Level.toLevel(config.logLevel, Level.INFO) (LoggerFactory.getLogger("ROOT") as Logger).level = level // Managers IEEEventsGuildSettingsManager.start() - commandManager = CommandManager(IEEEventsGuildSettingsManager, prefix) { cEvent, t -> + commandManager = CommandManager(IEEEventsGuildSettingsManager, config.prefix) { cEvent, t -> cEvent.replyError("An unknown error occurred!", { }, { }) logger.error("An uncaught exception occurred in a command: $t") } diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/Utils.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/Utils.kt index b453ea8..c92abdf 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/Utils.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/Utils.kt @@ -23,8 +23,9 @@ object Utils { val body = response.body val resCode = response.code if (resCode >= HttpURLConnection.HTTP_OK && - resCode < HttpURLConnection.HTTP_MULT_CHOICE && - body != null) { + resCode < HttpURLConnection.HTTP_MULT_CHOICE && + body != null + ) { return body.byteStream() } else { throw error("failed to download file") diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/CommandCategories.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/CommandCategories.kt index 14dd2d4..abd61e7 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/CommandCategories.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/CommandCategories.kt @@ -3,6 +3,13 @@ package xyz.brettb.discord.ieeevents.commands import tech.junodevs.discord.kriess.command.CommandCategory object CommandCategories { + /** + * The Information Category + */ val INFO = CommandCategory("Information") + + /** + * The Settings Category + */ val SETTINGS = CommandCategory("Settings") } \ No newline at end of file diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/info/HelpCommand.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/info/HelpCommand.kt index 49470c2..7020cdd 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/info/HelpCommand.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/info/HelpCommand.kt @@ -6,12 +6,15 @@ import tech.junodevs.discord.kriess.command.CommandEvent import tech.junodevs.discord.kriess.menus.PaginationOptions import tech.junodevs.discord.kriess.menus.PaginatorMenu import xyz.brettb.discord.ieeevents.Constants -import xyz.brettb.discord.ieeevents.IEEEventsBot import xyz.brettb.discord.ieeevents.commands.CommandBase import xyz.brettb.discord.ieeevents.commands.CommandCategories import xyz.brettb.discord.ieeevents.data.settings.settings import java.util.concurrent.TimeUnit +/** + * The [HelpCommand] allows a user to check what commands exist + */ +@Suppress("unused") object HelpCommand : CommandBase( "help", "Get some help on the bot", diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/info/UpcomingEventsCommand.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/info/UpcomingEventsCommand.kt index 49b31a6..e763ddf 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/info/UpcomingEventsCommand.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/info/UpcomingEventsCommand.kt @@ -14,6 +14,10 @@ import java.time.ZoneId import java.time.format.DateTimeFormatter import java.util.concurrent.TimeUnit +/** + * The [UpcomingEventsCommand] allows a user to check for upcoming events on the calendar. + */ +@Suppress("unused") object UpcomingEventsCommand : CommandBase( "upcoming", "Get a list of upcoming events", diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/settings/ChangePrefixCommand.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/settings/ChangePrefixCommand.kt index 816f5d8..e0a321b 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/settings/ChangePrefixCommand.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/commands/settings/ChangePrefixCommand.kt @@ -6,6 +6,10 @@ import xyz.brettb.discord.ieeevents.commands.CommandBase import xyz.brettb.discord.ieeevents.commands.CommandCategories import xyz.brettb.discord.ieeevents.data.settings.IEEEventsGuildSettingsManager +/** + * The [ChangePrefixCommand] allows you to change the bots' prefix in a given server. + */ +@Suppress("unused") object ChangePrefixCommand : CommandBase( "changeprefix", "Change the bot prefix", diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/data/settings/BotConfig.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/data/settings/BotConfig.kt new file mode 100644 index 0000000..89a7c05 --- /dev/null +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/data/settings/BotConfig.kt @@ -0,0 +1,58 @@ +package xyz.brettb.discord.ieeevents.data.settings + +/** + * The required values for the bot to run. + */ +data class BotConfig( + /** + * The Discord Log-In Token + */ + val token: String, + + /** + * The calendar URL the bot should mirror to set channels + */ + val calendarUrl: String, + + /** + * The bots' default prefix + */ + val prefix: String = "e.", + + /** + * The minimum log level that should be logged + */ + val logLevel: String = "INFO", + + /** + * If the bot should update its status + */ + val updateStatus: Boolean = true, + + /** + * The list of user IDs that should ignore permissions + */ + val owners: List = emptyList(), + + /** + * The database the bot should connect to + */ + val database: DatabaseConfig = DatabaseConfig(), +) { + + /** + * The required information to connect to the database + */ + data class DatabaseConfig( + /** + * The connection URL for the database + */ + val url: String = "mongodb://mongo", + + /** + * The name of the database to use + */ + val name: String = "ieeebot" + ) + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/data/settings/IEEEventsGuildSettings.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/data/settings/IEEEventsGuildSettings.kt index 5924c39..591d370 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/data/settings/IEEEventsGuildSettings.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/data/settings/IEEEventsGuildSettings.kt @@ -6,11 +6,32 @@ import tech.junodevs.discord.kriess.providers.GuildSettingsProvider import xyz.brettb.discord.ieeevents.IEEEventsBot import java.util.concurrent.CompletableFuture -class IEEEventsGuildSettings(val guildid: Long, var prefix: String? = null, val eventChannelID: Long? = null, var mirrorEvents: Boolean = false) : GuildSettingsProvider { +class IEEEventsGuildSettings( + /** + * The ID of the guild this settings object is tied to + */ + @Suppress("unused") val guildid: Long, + /** + * The guild's specific prefix + */ + var prefix: String? = null, + /** + * The ID of the channel where the events should be sent. + */ + @Suppress("MemberVisibilityCanBePrivate") val eventChannelID: Long? = null, + /** + * Should we mirror events to this guild from the calendar? + */ + var mirrorEvents: Boolean = false +) : GuildSettingsProvider { override val realPrefix: String - get() = prefix ?: IEEEventsBot.prefix + get() = prefix ?: IEEEventsBot.config.prefix + /** + * The channel where the events should be sent + */ + @Suppress("unused") val eventChannel: TextChannel? get() = if (eventChannelID != null) IEEEventsBot.JDA.getTextChannelById(eventChannelID) else null @@ -24,5 +45,8 @@ class IEEEventsGuildSettings(val guildid: Long, var prefix: String? = null, val } +/** + * Get the settings for a given guild + */ val Guild.settings: CompletableFuture - get() = IEEEventsGuildSettingsManager.getSettingsFor(this) \ No newline at end of file + get() = IEEEventsGuildSettingsManager.getSettingsFor(this) \ No newline at end of file diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/services/EventsService.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/services/EventsService.kt index 8a79803..5d80c23 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/services/EventsService.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/services/EventsService.kt @@ -1,16 +1,21 @@ package xyz.brettb.discord.ieeevents.services -import tech.junodevs.discord.kriess.services.Service import xyz.brettb.discord.ieeevents.IEEEventsBot import xyz.brettb.discord.ieeevents.data.settings.settings +import xyz.brettb.discord.ieeevents.logger import java.util.concurrent.TimeUnit +/** + * Ensures Discord Events & Calendar events match every 30 minutes. + */ +@Suppress("unused") object EventsService : ToggleableService(TimeUnit.MINUTES.toSeconds(30), 15) { override fun execute() { - val events = IEEEventsBot.events + @Suppress("UNUSED_VARIABLE") val events = IEEEventsBot.events IEEEventsBot.JDA.guilds.forEach { guild -> if (guild.settings.get().mirrorEvents) { + logger.info("Mirroring events in guild ${guild.name}") // Go through events in events /// Ensure each event exists //// If not, create + send announcement message diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/services/StatusService.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/services/StatusService.kt index 274365c..601e316 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/services/StatusService.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/services/StatusService.kt @@ -2,13 +2,16 @@ package xyz.brettb.discord.ieeevents.services import net.dv8tion.jda.api.OnlineStatus import net.dv8tion.jda.api.entities.Activity -import tech.junodevs.discord.kriess.services.Service import xyz.brettb.discord.ieeevents.IEEEventsBot +/** + * Updates the bots' status every 60 seconds (if enabled) + */ +@Suppress("unused") object StatusService : ToggleableService(60, 0) { override fun execute() { - if (IEEEventsBot.updateStatus) { + if (IEEEventsBot.config.updateStatus) { val events = IEEEventsBot.events IEEEventsBot.JDA.presence.setPresence( OnlineStatus.DO_NOT_DISTURB, Activity.watching( diff --git a/src/main/kotlin/xyz/brettb/discord/ieeevents/services/ToggleableService.kt b/src/main/kotlin/xyz/brettb/discord/ieeevents/services/ToggleableService.kt index 7605095..b14cd12 100644 --- a/src/main/kotlin/xyz/brettb/discord/ieeevents/services/ToggleableService.kt +++ b/src/main/kotlin/xyz/brettb/discord/ieeevents/services/ToggleableService.kt @@ -2,6 +2,24 @@ package xyz.brettb.discord.ieeevents.services import tech.junodevs.discord.kriess.services.Service -abstract class ToggleableService(period: Long, initial: Long = period, val enabled: Boolean = true) : +/** + * A service that can be prevented from being enabled + */ +abstract class ToggleableService( + /** + * The [period] (number of seconds) between each execution + */ + period: Long, + + /** + * The [initial] delay before the service is executed for the first time + */ + initial: Long = period, + + /** + * If the service should be [enabled] when it is initialized. + */ + val enabled: Boolean = true +) : Service(initial, period) { } \ No newline at end of file