From 69220fba32dedfefaf4c8c0894d46e4c7eb3c015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wieland=20Sch=C3=B6bl?= Date: Fri, 8 Jan 2021 17:40:46 +0100 Subject: [PATCH] Change CLI to use kordx.commands --- .gitignore | 2 +- README.md | 6 +- src/main/kotlin/de/wulkanat/Admin.kt | 163 ++++++------------ src/main/kotlin/de/wulkanat/AdminCli.kt | 13 +- src/main/kotlin/de/wulkanat/DataIO.kt | 2 +- src/main/kotlin/de/wulkanat/Main.kt | 5 +- src/main/kotlin/de/wulkanat/OwnerCli.kt | 41 ++--- src/main/kotlin/de/wulkanat/cli/AdminCli.kt | 109 ++++++++++++ src/main/kotlin/de/wulkanat/cli/OwnerCli.kt | 159 +++++++++++++++++ .../kotlin/de/wulkanat/extensions/Generic.kt | 8 + .../kotlin/de/wulkanat/extensions/User.kt | 7 + src/main/kotlin/de/wulkanat/files/Config.kt | 26 +++ src/main/kotlin/de/wulkanat/files/Servers.kt | 4 + .../{Channels.kt => files/ServiceChannels.kt} | 41 +++-- .../files/concept/SerializableObject.kt | 24 +++ 15 files changed, 448 insertions(+), 162 deletions(-) create mode 100644 src/main/kotlin/de/wulkanat/cli/AdminCli.kt create mode 100644 src/main/kotlin/de/wulkanat/cli/OwnerCli.kt create mode 100644 src/main/kotlin/de/wulkanat/extensions/Generic.kt create mode 100644 src/main/kotlin/de/wulkanat/extensions/User.kt create mode 100644 src/main/kotlin/de/wulkanat/files/Config.kt create mode 100644 src/main/kotlin/de/wulkanat/files/Servers.kt rename src/main/kotlin/de/wulkanat/{Channels.kt => files/ServiceChannels.kt} (82%) create mode 100644 src/main/kotlin/de/wulkanat/files/concept/SerializableObject.kt diff --git a/.gitignore b/.gitignore index a899ed8..fbc4ac1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ servers.json -admin.json +config.json service_channels.json *.hprof /build diff --git a/README.md b/README.md index 9de879f..b49bd7f 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ the bot should message you on Discord. *Note:* You need to invite the bot into a server before it can message you. -Run it once (it should crash or print an error), so `admin.json`, `servers.json` and `service_channels.json` +Run it once (it should crash or print an error), so `config.json`, `servers.json` and `service_channels.json` are being created. -Add your Discord ID `adminId` (not name), Bot token `token`, and update frequency `updateMs` to the `admin.json`, +Add your Discord ID `adminId` (not name), Bot token `token`, and update frequency `updateMs` to the `config.json`, optionally you can add your own messages for when the bot is looking and when it can't reach Hytale Servers. If you verified that everything works correctly, you can start the server in the background, on Linux that is @@ -59,7 +59,7 @@ I developed it under Windows, and had some trouble compiling it on Linux. You mi | !help | | Show a help dialog with all these commands | These commands will only work by private messaging the bot (and will be ignored if they don't -come from the admin registered in the `admin.json`. +come from the admin registered in the `config.json`. ## TODO diff --git a/src/main/kotlin/de/wulkanat/Admin.kt b/src/main/kotlin/de/wulkanat/Admin.kt index c442d41..034c14e 100644 --- a/src/main/kotlin/de/wulkanat/Admin.kt +++ b/src/main/kotlin/de/wulkanat/Admin.kt @@ -1,141 +1,82 @@ package de.wulkanat -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonConfiguration -import net.dv8tion.jda.api.EmbedBuilder -import net.dv8tion.jda.api.JDA -import net.dv8tion.jda.api.entities.Activity -import net.dv8tion.jda.api.entities.MessageEmbed -import net.dv8tion.jda.api.entities.User +import com.gitlab.kordlib.common.entity.Snowflake +import com.gitlab.kordlib.core.Kord +import com.gitlab.kordlib.core.behavior.channel.createEmbed +import com.gitlab.kordlib.core.entity.User +import com.gitlab.kordlib.rest.builder.message.EmbedBuilder +import de.wulkanat.files.Config +import de.wulkanat.files.ServiceChannels +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import java.awt.Color object Admin { - val userId: Long - val token: String - val updateMs: Long - val message: String - val offlineMessage: String - - init { - val admin = Json(JsonConfiguration.Stable).parse(AdminFile.serializer(), ADMIN_FILE.readText()) - userId = admin.adminId - token = admin.token - updateMs = admin.updateMs - message = admin.watchingMessage - offlineMessage = admin.offlineMessage - } - - var jda: JDA? = null + var jda: Kord? = null set(value) { field = value - admin = value?.retrieveUserById(userId)?.complete() - if (admin == null) { - kotlin.io.println("Connection to de.wulkanat.Admin failed!") - } else { - kotlin.io.println("Connected to ${admin!!.name}. No further errors will be printed here.") + GlobalScope.launch { + admin = value?.getUser(Snowflake(Config.adminId)) + if (admin == null) { + kotlin.io.println("Connection to de.wulkanat.Admin failed!") + } else { + kotlin.io.println("Connected to ${admin!!.username}. No further errors will be printed here.") + } } } var admin: User? = null - fun println(msg: String) { - sendDevMessage( - EmbedBuilder() - .setTitle(msg) - .setColor(Color.WHITE) - .build(), - msg - ) + suspend fun println(msg: String) { + sendDevMessage(msg) { + title = msg + color = Color.WHITE + } } - fun printlnBlocking(msg: String) { - senDevMessageBlocking( - EmbedBuilder() - .setTitle(msg) - .setColor(Color.WHITE) - .build(), - msg - ) + suspend fun error(msg: String, error: String, author: User? = null) { + sendDevMessage("$msg\n\n$error") { + title = msg + description = error + color = Color.RED + author?.let { author { + name = it.tag + icon = it.avatar.url + url = it.avatar.url + }} + } } - fun error(msg: String, error: String, author: User? = null) { - sendDevMessage( - EmbedBuilder() - .setTitle(msg) - .setDescription(error) - .setColor(Color.RED) - .run { - if (author == null) { - this - } else { - this.setAuthor(author.asTag, author.avatarUrl, author.avatarUrl) - } - } - .build() - , "$msg\n\n${error}" - ) + suspend fun warning(msg: String) { + sendDevMessage(msg) { + title = msg + color = Color.YELLOW + } } - fun errorBlocking(msg: String, error: Exception) { - senDevMessageBlocking( - EmbedBuilder() - .setTitle(msg) - .setDescription(error.message) - .setColor(Color.RED) - .build() - , "$msg\n\n${error.message}" - ) - } - - fun warning(msg: String) { - sendDevMessage( - EmbedBuilder() - .setTitle(msg) - .setColor(Color.YELLOW) - .build(), - msg - ) - } - - fun info() { - sendDevMessage( - EmbedBuilder() - .setTitle("Now watching for new Hytale Blogposts every ${updateMs / 1000}s") - .setDescription(""" - ${Channels.getServerNames().joinToString("\n")} - - **_Service Channels_** - ${Channels.getServiceChannelServers().joinToString("\n")} - """.trimIndent()) - .setColor(Color.GREEN) - .build(), - "Now watching for new Hytale BlogPosts" - ) + suspend fun info() { + sendDevMessage("Now watching for new Hytale BlogPosts") { + title = "Now watching for new Hytale Blogposts every ${Config.updateMs / 1000}s" + description = """ + ${ServiceChannels.getServerNames().joinToString("\n")} + + **_Service Channels_** + ${ServiceChannels.getServiceChannelServers().joinToString("\n")} + """.trimIndent() + color = Color.GREEN + } } fun silent(msg: String) { kotlin.io.println(msg) } - private fun senDevMessageBlocking(messageEmbed: MessageEmbed, fallback: String) { - admin = jda!!.retrieveUserById(userId).complete() - val devChannel = admin?.openPrivateChannel() ?: kotlin.run { + private suspend inline fun sendDevMessage(fallback: String, crossinline embed: EmbedBuilder.() -> Unit) { + val devChannel = admin?.getDmChannel() ?: kotlin.run { kotlin.io.println(fallback) return } - devChannel.complete() - .sendMessage(messageEmbed).complete() - } - - fun sendDevMessage(messageEmbed: MessageEmbed, fallback: String) { - val devChannel = admin?.openPrivateChannel() ?: kotlin.run { - kotlin.io.println(fallback) - return - } - - devChannel.queue { - it.sendMessage(messageEmbed).queue() - } + devChannel.createEmbed(embed) } } \ No newline at end of file diff --git a/src/main/kotlin/de/wulkanat/AdminCli.kt b/src/main/kotlin/de/wulkanat/AdminCli.kt index 945c0b7..dab1321 100644 --- a/src/main/kotlin/de/wulkanat/AdminCli.kt +++ b/src/main/kotlin/de/wulkanat/AdminCli.kt @@ -1,5 +1,6 @@ package de.wulkanat +import de.wulkanat.files.ServiceChannels import de.wulkanat.model.BlogPostPreview import net.dv8tion.jda.api.hooks.ListenerAdapter import de.wulkanat.web.SiteWatcher @@ -41,23 +42,23 @@ class AdminCli : ListenerAdapter() { if (command.size != 3) { Admin.println("Enclose message and title in backticks (`)") } else { - Channels.sendServiceMessage(command[1].value.trim('`'), command[2].value.trim('`')) + ServiceChannels.sendServiceMessage(command[1].value.trim('`'), command[2].value.trim('`')) } } "refreshList" -> { - Channels.channels = Channels.refreshChannelsFromDisk() - Channels.serviceChannels = Channels.refreshServiceChannelsFromDisk() + ServiceChannels.channels = ServiceChannels.refreshChannelsFromDisk() + ServiceChannels.serviceChannels = ServiceChannels.refreshServiceChannelsFromDisk() Admin.info() } "removeInactive" -> { - Channels.channels.removeAll { channel -> - Channels.testServerId(channel.id) ?: run { + ServiceChannels.channels.removeAll { channel -> + ServiceChannels.testServerId(channel.id) ?: run { Admin.println("Removed ${channel.id}") null } == null } Admin.info() - Channels.saveChannels() + ServiceChannels.saveChannels() } "help" -> { event.message.channel.sendMessage( diff --git a/src/main/kotlin/de/wulkanat/DataIO.kt b/src/main/kotlin/de/wulkanat/DataIO.kt index d84571a..6fc1346 100644 --- a/src/main/kotlin/de/wulkanat/DataIO.kt +++ b/src/main/kotlin/de/wulkanat/DataIO.kt @@ -10,7 +10,7 @@ import java.io.File @Serializable data class DiscordChannel( val id: Long, - var mentionedRole: String? = null, + var mentionedRole: Long? = null, var autoPublish: Boolean = false, var message: CustomMessage? = null ) diff --git a/src/main/kotlin/de/wulkanat/Main.kt b/src/main/kotlin/de/wulkanat/Main.kt index f409e59..ac56251 100644 --- a/src/main/kotlin/de/wulkanat/Main.kt +++ b/src/main/kotlin/de/wulkanat/Main.kt @@ -1,5 +1,6 @@ package de.wulkanat +import de.wulkanat.files.ServiceChannels import net.dv8tion.jda.api.JDABuilder import net.dv8tion.jda.api.entities.Activity import net.dv8tion.jda.api.requests.GatewayIntent @@ -18,7 +19,7 @@ fun main() { builder.addEventListener(OwnerCli()) builder.awaitReady() - Channels.jda = builder + ServiceChannels.client = builder Admin.jda = builder DiscordRpc.jda = builder Admin.info() @@ -34,7 +35,7 @@ fun main() { timer("Updater", daemon = true, initialDelay = 0L, period = Admin.updateMs) { if (SiteWatcher.hasNewBlogPost()) { - Channels.sentToAll(SiteWatcher.newestBlog!!.toMessageEmbed()) + ServiceChannels.sentToAll(SiteWatcher.newestBlog!!.toMessageEmbed()) } } } \ No newline at end of file diff --git a/src/main/kotlin/de/wulkanat/OwnerCli.kt b/src/main/kotlin/de/wulkanat/OwnerCli.kt index 3c7cfec..eb9089b 100644 --- a/src/main/kotlin/de/wulkanat/OwnerCli.kt +++ b/src/main/kotlin/de/wulkanat/OwnerCli.kt @@ -1,5 +1,6 @@ package de.wulkanat +import de.wulkanat.files.ServiceChannels import net.dv8tion.jda.api.EmbedBuilder import net.dv8tion.jda.api.Permission import net.dv8tion.jda.api.events.message.MessageReceivedEvent @@ -21,7 +22,7 @@ class OwnerCli : ListenerAdapter() { when (command.first()) { "add" -> { - val result = Channels.addChannel(channelId, null) + val result = ServiceChannels.addChannel(channelId, null) if (result == null) { event.message.channel.sendMessage("Already added.").queue() } else { @@ -30,8 +31,8 @@ class OwnerCli : ListenerAdapter() { } } "remove" -> { - val result = Channels.channels.removeAll { it.id == channelId } - Channels.saveChannels() + val result = ServiceChannels.channels.removeAll { it.id == channelId } + ServiceChannels.saveChannels() if (result) { event.message.channel.sendMessage("Removed.").queue() } else { @@ -39,11 +40,11 @@ class OwnerCli : ListenerAdapter() { } } "publish" -> { - val result = Channels.channels.find { it.id == channelId } + val result = ServiceChannels.channels.find { it.id == channelId } if (result != null) { if (command.size > 1 && listOf("on", "off").contains(command[1])) { result.autoPublish = command[1] == "on" - Channels.saveChannels() + ServiceChannels.saveChannels() event.message.channel.sendMessage("Auto publish is now ${command[1]}").queue() } else { @@ -54,7 +55,7 @@ class OwnerCli : ListenerAdapter() { } } "ping" -> { - val result = Channels.channels.find { it.id == channelId } + val result = ServiceChannels.channels.find { it.id == channelId } if (result != null) { if (command.size > 1) { val roles = event.message.guild.getRolesByName(command[1], false) @@ -76,7 +77,7 @@ class OwnerCli : ListenerAdapter() { result.mentionedRole } } - Channels.saveChannels() + ServiceChannels.saveChannels() } else { event.message.channel.sendMessage("Usage: `${prefix}ping [everyone|none|roleName]`") } @@ -85,12 +86,12 @@ class OwnerCli : ListenerAdapter() { } } "setMessage" -> { - val result = Channels.channels.find { it.id == channelId } + val result = ServiceChannels.channels.find { it.id == channelId } if (result != null) { if (command.size > 1) { val message = event.message.contentRaw.removePrefix("${prefix}setMessage").trim() result.message = CustomMessage(message) - Channels.saveChannels() + ServiceChannels.saveChannels() event.message.channel.sendMessage("Set `$message` as message.").queue() } else { event.message.channel.sendMessage("Usage: `${prefix}setMessage [message]`") @@ -100,10 +101,10 @@ class OwnerCli : ListenerAdapter() { } } "resetMessage" -> { - val result = Channels.channels.find { it.id == channelId } + val result = ServiceChannels.channels.find { it.id == channelId } if (result != null) { result.message = null - Channels.saveChannels() + ServiceChannels.saveChannels() event.message.channel.sendMessage("Reset to no message.").queue() } else { event.message.channel.sendMessage("Channel is not registered.").queue() @@ -112,32 +113,32 @@ class OwnerCli : ListenerAdapter() { "serviceChannel" -> { if (command.size > 1 && listOf("add", "remove").contains(command[1])) { if (command[1] == "add") { - if (Channels.serviceChannels.find { it.id == channelId } != null) { + if (ServiceChannels.serviceChannels.find { it.id == channelId } != null) { event.message.channel.sendMessage("Already a service channel.").queue() } else { - Channels.serviceChannels.add(ServiceChannel(channelId)) - Channels.saveChannels() + ServiceChannels.serviceChannels.add(ServiceChannel(channelId)) + ServiceChannels.saveChannels() event.message.channel.sendMessage("Added as service channel.").queue() } } else { event.message.channel.sendMessage( - if (Channels.serviceChannels.removeAll { it.id == channelId }) "Channel removed." + if (ServiceChannels.serviceChannels.removeAll { it.id == channelId }) "Channel removed." else "Not a service channel." ).queue() } - Channels.saveChannels() + ServiceChannels.saveChannels() } else { event.message.channel.sendMessage("Usage: `${prefix}serviceChannel [add|remove]`") } } "publishMessage" -> { - val result = Channels.channels.find { it.id == channelId } + val result = ServiceChannels.channels.find { it.id == channelId } if (result != null) { if (result.message != null) { if (command.size > 1 && listOf("on", "off").contains(command[1])) { result.message?.pushAnnouncement = command[1] == "on" - Channels.saveChannels() + ServiceChannels.saveChannels() event.message.channel.sendMessage("Auto publish (message) is now ${command[1]}").queue() } else { @@ -156,10 +157,10 @@ class OwnerCli : ListenerAdapter() { .setTitle("Server overview") .setColor(Color.GREEN) .setDescription(""" - ${Channels.getServerNames(event.message.guild.idLong).joinToString("\n")} + ${ServiceChannels.getServerNames(event.message.guild.idLong).joinToString("\n")} **_Service Channels_** - ${Channels.getServiceChannelServers(event.message.guild.idLong).joinToString("\n")} + ${ServiceChannels.getServiceChannelServers(event.message.guild.idLong).joinToString("\n")} """.trimIndent()) .setAuthor(Admin.admin?.name, Admin.admin?.avatarUrl, Admin.admin?.avatarUrl) .build() diff --git a/src/main/kotlin/de/wulkanat/cli/AdminCli.kt b/src/main/kotlin/de/wulkanat/cli/AdminCli.kt new file mode 100644 index 0000000..67c4dbf --- /dev/null +++ b/src/main/kotlin/de/wulkanat/cli/AdminCli.kt @@ -0,0 +1,109 @@ +@file:AutoWired + +package de.wulkanat.cli + +import com.gitlab.kordlib.core.entity.channel.DmChannel +import com.gitlab.kordlib.kordx.commands.annotation.AutoWired +import com.gitlab.kordlib.kordx.commands.argument.primitive.BooleanArgument +import com.gitlab.kordlib.kordx.commands.argument.text.StringArgument +import com.gitlab.kordlib.kordx.commands.kord.model.precondition.precondition +import com.gitlab.kordlib.kordx.commands.kord.model.prefix.kord +import com.gitlab.kordlib.kordx.commands.kord.model.prefix.mention +import com.gitlab.kordlib.kordx.commands.kord.model.respondEmbed +import com.gitlab.kordlib.kordx.commands.kord.module.module +import com.gitlab.kordlib.kordx.commands.model.command.invoke +import com.gitlab.kordlib.kordx.commands.model.prefix.literal +import com.gitlab.kordlib.kordx.commands.model.prefix.or +import com.gitlab.kordlib.kordx.commands.model.prefix.prefix +import de.wulkanat.Admin +import de.wulkanat.extensions.alsoIf +import de.wulkanat.extensions.isBotAdmin +import de.wulkanat.files.ServiceChannels +import de.wulkanat.model.BlogPostPreview +import de.wulkanat.web.SiteWatcher + +val prefixes = prefix { + kord { mention() or literal("%!") } +} + +fun adminCommands() = module("admin-commands") { + precondition { author.isBotAdmin && channel.asChannelOrNull() is DmChannel } + + command("stop") { + invoke { + respond("Shutting down...") + kord.shutdown() + } + } + + command("info") { + invoke { + Admin.info() + } + } + + command("fakeUpdate") { + invoke { + respond("THIS WILL CAUSE A MESSAGE ON **ALL** SERVERS.\nContinue? [y/n]") + if (read(BooleanArgument(trueValue = "y", falseValue = "n"))) { + respond("Sending fake update on next cycle") + + SiteWatcher.newestBlog = BlogPostPreview( + title = "FakePost", + imgUrl = "", + fullPostUrl = "", + author = "wulkanat", + date = "now", + description = "Lorem Ipsum" + ) + } else { + respond("Aborting") + } + } + } + + command("serviceMessage") { + invoke { + respond("What's the title?") + val title = read(StringArgument) + respond("What's the message?") + val message = read(StringArgument) + respondEmbed { + this.title = title + description = message + footer { + text = "Is that correct? [y/n]" + } + } + if (read(BooleanArgument(trueValue = "y", falseValue = "n"))) { + respond("Sending") + ServiceChannels.sendServiceMessage(title, message) + } else { + respond("Aborting") + } + } + } + + command("refreshList") { + invoke { + ServiceChannels.channels = ServiceChannels.refreshChannelsFromDisk() + ServiceChannels.serviceChannels = ServiceChannels.refreshServiceChannelsFromDisk() + Admin.info() + } + } + + command("removeInactive") { + invoke { + respondEmbed { + title = "Channels removed" + + ServiceChannels.channels.removeAll { channel -> + (ServiceChannels.testServerId(channel.id) == null).alsoIf(true) { + field { name = channel.id.toString() } + } + } + } + ServiceChannels.saveChannels() + } + } +} diff --git a/src/main/kotlin/de/wulkanat/cli/OwnerCli.kt b/src/main/kotlin/de/wulkanat/cli/OwnerCli.kt new file mode 100644 index 0000000..7efdb47 --- /dev/null +++ b/src/main/kotlin/de/wulkanat/cli/OwnerCli.kt @@ -0,0 +1,159 @@ +@file:AutoWired + +package de.wulkanat.cli + +import com.gitlab.kordlib.common.entity.Permission +import com.gitlab.kordlib.kordx.commands.annotation.AutoWired +import com.gitlab.kordlib.kordx.commands.argument.primitive.BooleanArgument +import com.gitlab.kordlib.kordx.commands.argument.text.StringArgument +import com.gitlab.kordlib.kordx.commands.kord.argument.RoleArgument +import com.gitlab.kordlib.kordx.commands.kord.model.precondition.precondition +import com.gitlab.kordlib.kordx.commands.kord.model.respondEmbed +import com.gitlab.kordlib.kordx.commands.kord.module.module +import com.gitlab.kordlib.kordx.commands.model.command.invoke +import de.wulkanat.Admin +import de.wulkanat.CustomMessage +import de.wulkanat.ServiceChannel +import de.wulkanat.files.ServiceChannels +import java.awt.Color + +// TODO: channel argument? +fun ownerCommands() = module("owner-commands") { + precondition { + message.getAuthorAsMember()?.getPermissions()?.contains(Permission.Administrator) ?: false + } + + command("add") { + invoke { + if (ServiceChannels.addChannel(channel.id.longValue, null) == null) { + respond("Already added.") + } else { + respond("Added.") + Admin.info() + } + } + } + + command("remove") { + invoke { + val result = ServiceChannels.channels.removeAll { it.id == channel.id.longValue } + ServiceChannels.saveChannels() + if (result) { + respond("Removed.") + } else { + respond("This channel is not registered.") + } + } + } + + command("publish") { + invoke(BooleanArgument(trueValue = "on", falseValue = "off")) { doAutoPublish -> + ServiceChannels.channels.find { it.id == channel.id.longValue }?.also { + it.autoPublish = doAutoPublish + ServiceChannels.saveChannels() + } ?: respond("Channel not registered") + } + } + + command("ping") { + invoke(RoleArgument) { role -> + ServiceChannels.channels.find { it.id == channel.id.longValue }?.also { + // TODO: @everyone + it.mentionedRole = role.id.longValue + ServiceChannels.saveChannels() + } ?: respond("Channel not registered") + } + } + + command("setMessage") { + invoke(StringArgument) { message -> + ServiceChannels.channels.find { it.id == channel.id.longValue}?.also { + it.message = CustomMessage(message) + respond("Set `$message` as a message.") + } ?: respond("Channel not registered!") + } + } + + command("resetMessage") { + invoke { + ServiceChannels.channels.find { it.id == channel.id.longValue }?.also { + it.message = null + respond("Reset to no message") + } ?: respond("Channel not registered!") + } + } + + command("serviceChannel") { + invoke(BooleanArgument(trueValue = "add", falseValue = "remove")) { addChannel -> + if (addChannel) { + ServiceChannels.serviceChannels.find { it.id == channel.id.longValue }?.also { + respond("Already a service channel") + } ?: run { + ServiceChannels.serviceChannels.add(ServiceChannel(channel.id.longValue)) + respond("Added as a service channel") + } + } else { + respond(if (ServiceChannels.serviceChannels.removeAll { it.id == channel.id.longValue }) + "Channel removed" else "Not a service channel") + } + } + } + + command("publishMessage") { + invoke(BooleanArgument(trueValue = "on", falseValue = "off")) { doAutoPublish -> + ServiceChannels.channels.find { it.id == channel.id.longValue }?.also { + it.message?.pushAnnouncement = doAutoPublish + ServiceChannels.saveChannels() + respond("Auto publish is now ${if (doAutoPublish) "on" else "off"}") + } ?: respond("Channel not registered!") + } + } + + command("info") { + invoke { + respondEmbed { + title = "Server Overview" + color = Color.GREEN + description = """ + ${ServiceChannels.getServerNames(guild?.id?.longValue).joinToString("\n")} + + **_Service Channels_** + ${ServiceChannels.getServiceChannelServers(guild?.id?.longValue).joinToString("\n")} + """.trimIndent() + Admin.admin?.let { + author { + name = it.username + icon = it.avatar.url + url = "https://github.com/wulkanat/BlogShot" + } + } + } + } + } + + command("report") { + invoke { + respond("What is the error you encountered?") + val errorReport = read(StringArgument) + respondEmbed { + title = "Error Report Preview" + color = Color.RED + description = errorReport + message.author?.let { + author { + name = it.username + icon = it.avatar.url + } + } + + } + respond("Send? [y/n]") + if (read(BooleanArgument(trueValue = "y", falseValue = "n"))) { + respond("Sent") + Admin.error(guild?.asGuildOrNull()?.name ?: "Unknown Guild", errorReport, author) + } else { + respond("Aborting") + } + } + } +} diff --git a/src/main/kotlin/de/wulkanat/extensions/Generic.kt b/src/main/kotlin/de/wulkanat/extensions/Generic.kt new file mode 100644 index 0000000..2d9bfad --- /dev/null +++ b/src/main/kotlin/de/wulkanat/extensions/Generic.kt @@ -0,0 +1,8 @@ +package de.wulkanat.extensions + +inline fun Boolean.alsoIf(other: T, body: () -> Unit): Boolean { + if (this == other) { + body() + } + return this +} diff --git a/src/main/kotlin/de/wulkanat/extensions/User.kt b/src/main/kotlin/de/wulkanat/extensions/User.kt new file mode 100644 index 0000000..e086bc7 --- /dev/null +++ b/src/main/kotlin/de/wulkanat/extensions/User.kt @@ -0,0 +1,7 @@ +package de.wulkanat.extensions + +import com.gitlab.kordlib.core.entity.User +import de.wulkanat.files.Config + +val User.isBotAdmin: Boolean + get() = id.longValue == Config.adminId diff --git a/src/main/kotlin/de/wulkanat/files/Config.kt b/src/main/kotlin/de/wulkanat/files/Config.kt new file mode 100644 index 0000000..e8e920c --- /dev/null +++ b/src/main/kotlin/de/wulkanat/files/Config.kt @@ -0,0 +1,26 @@ +package de.wulkanat.files + +import de.wulkanat.files.concept.SerializableObject +import kotlinx.serialization.Serializable + +object Config : SerializableObject("config.json", Data(), Data.serializer()) { + val adminId: Long + get() = instance.adminId + val token: String + get() = instance.token + val updateMs: Long + get() = instance.updateMs + val watchingMessage: String + get() = instance.watchingMessage + val offlineMessage: String + get() = instance.offlineMessage + + @Serializable + data class Data( + val adminId: Long = 12345, + val token: String = "12345", + val updateMs: Long = 30000, + val watchingMessage: String = "for new Blogposts", + val offlineMessage: String = "CONNECTION FAILED" + ) +} diff --git a/src/main/kotlin/de/wulkanat/files/Servers.kt b/src/main/kotlin/de/wulkanat/files/Servers.kt new file mode 100644 index 0000000..dfd20c0 --- /dev/null +++ b/src/main/kotlin/de/wulkanat/files/Servers.kt @@ -0,0 +1,4 @@ +package de.wulkanat.files + +object Servers { +} \ No newline at end of file diff --git a/src/main/kotlin/de/wulkanat/Channels.kt b/src/main/kotlin/de/wulkanat/files/ServiceChannels.kt similarity index 82% rename from src/main/kotlin/de/wulkanat/Channels.kt rename to src/main/kotlin/de/wulkanat/files/ServiceChannels.kt index aaef499..ab567c0 100644 --- a/src/main/kotlin/de/wulkanat/Channels.kt +++ b/src/main/kotlin/de/wulkanat/files/ServiceChannels.kt @@ -1,6 +1,9 @@ -package de.wulkanat +package de.wulkanat.files -import de.wulkanat.extensions.crosspost +import com.gitlab.kordlib.core.Kord +import com.gitlab.kordlib.core.entity.Embed +import com.gitlab.kordlib.rest.builder.message.EmbedBuilder +import de.wulkanat.* import kotlinx.serialization.list import net.dv8tion.jda.api.EmbedBuilder import net.dv8tion.jda.api.JDA @@ -9,8 +12,8 @@ import net.dv8tion.jda.api.entities.MessageEmbed import net.dv8tion.jda.api.entities.TextChannel import java.awt.Color -object Channels { - var jda: JDA? = null +object ServiceChannels { + var client: Kord? = null /** * List of (ServerID, ChannelID) @@ -18,13 +21,13 @@ object Channels { var channels: MutableList = refreshChannelsFromDisk() var serviceChannels: MutableList = refreshServiceChannelsFromDisk() - fun sentToAll(messageEmbed: MessageEmbed) { - if (jda == null) + fun sentToAll(messageEmbed: Embed) { + if (client == null) return for (channel_pair in channels) { try { - val channel = jda!!.getTextChannelById(channel_pair.id) ?: continue + val channel = client!!.getTextChannelById(channel_pair.id) ?: continue val customMessage = channel_pair.message?.message ?: "" if (channel_pair.mentionedRole != null) { @@ -66,13 +69,14 @@ object Channels { .build() for (channelInfo in serviceChannels) { - val channel = jda!!.getTextChannelById(channelInfo.id) + val channel = client!!.getTextChannelById(channelInfo.id) channel?.sendMessage(serviceMessage)?.queue() } Admin.println("Service message distributed to ${serviceChannels.size} channels.") - Admin.sendDevMessage(serviceMessage, """ + Admin.sendDevMessage( + serviceMessage, """ *************** SERVICE MESSAGE @@ -80,12 +84,13 @@ object Channels { ------- $message *************** - """.trimIndent()) + """.trimIndent() + ) } fun checkEveryonePermission() { for (channel_pair in channels) { - val channel = jda!!.getTextChannelById(channel_pair.id) ?: continue + val channel = client!!.getTextChannelById(channel_pair.id) ?: continue if (channel_pair.mentionedRole == "everyone" && channel.guild.selfMember.hasPermission(Permission.MESSAGE_MENTION_EVERYONE) @@ -111,11 +116,11 @@ object Channels { } fun getServerNames(server: Long? = null): List { - if (jda == null) + if (client == null) return listOf() - return channels.filter { server == null || (jda!!.getTextChannelById(it.id)?.guild?.idLong == server) }.map { - val channel = jda!!.getTextChannelById(it.id) + return channels.filter { server == null || (client!!.getTextChannelById(it.id)?.guild?.idLong == server) }.map { + val channel = client!!.getTextChannelById(it.id) if (channel == null) { Admin.warning("Channel ${it.id} is no longer active!") return@map "**${it.id}** *(inactive)*" @@ -137,17 +142,17 @@ object Channels { } fun getServiceChannelServers(server: Long? = null): List { - if (jda == null) + if (client == null) return listOf() - return serviceChannels.filter { server == null || (jda!!.getTextChannelById(it.id)?.guild?.idLong == server) }.map { - val channel = jda!!.getTextChannelById(it.id) + return serviceChannels.filter { server == null || (client!!.getTextChannelById(it.id)?.guild?.idLong == server) }.map { + val channel = client!!.getTextChannelById(it.id) "**${channel?.guild?.name ?: it.id}** #${channel?.name ?: "(inactive)"}" } } fun testServerId(id: Long): TextChannel? { - return jda?.getTextChannelById(id) + return client?.getTextChannelById(id) } fun addChannel(id: Long, role: String?): DiscordChannel? { diff --git a/src/main/kotlin/de/wulkanat/files/concept/SerializableObject.kt b/src/main/kotlin/de/wulkanat/files/concept/SerializableObject.kt new file mode 100644 index 0000000..26d0b30 --- /dev/null +++ b/src/main/kotlin/de/wulkanat/files/concept/SerializableObject.kt @@ -0,0 +1,24 @@ +package de.wulkanat.files.concept + +import de.wulkanat.extensions.ensureExists +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json +import java.io.File + +abstract class SerializableObject( + fileName: String, + defaultText: T? = null, + private val childSerializer: KSerializer +) { + private val json = Json { allowStructuredMapKeys = true } + private val file = File(fileName).ensureExists(defaultText?.let { json.encodeToString(childSerializer, it) }) + var instance: T = json.decodeFromString(childSerializer, file.readText()) + + fun refresh() { + instance = json.decodeFromString(childSerializer, file.readText()) + } + + fun save() { + file.writeText(json.encodeToString(childSerializer, instance)) + } +}