From dc19902a7650c4bcab39f8f722fa689b3442c422 Mon Sep 17 00:00:00 2001 From: ILike2WatchMemes Date: Fri, 18 Apr 2025 02:36:11 +0200 Subject: [PATCH 1/5] manage commands --- .../hannibal2/skyhanni/discord/BotConfig.kt | 43 +++-- .../at/hannibal2/skyhanni/discord/Utils.kt | 19 ++ .../discord/command/ManageCommands.kt | 181 ++++++++++++++++++ .../discord/command/PullRequestCommand.kt | 11 +- 4 files changed, 228 insertions(+), 26 deletions(-) create mode 100644 src/main/kotlin/at/hannibal2/skyhanni/discord/command/ManageCommands.kt diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/BotConfig.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/BotConfig.kt index 63b0650..348b4e9 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/BotConfig.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/BotConfig.kt @@ -1,4 +1,5 @@ package at.hannibal2.skyhanni.discord + import com.google.gson.GsonBuilder import org.slf4j.LoggerFactory import java.io.File @@ -7,6 +8,7 @@ import kotlin.system.exitProcess data class BotConfig( val token: String, val botCommandChannelId: String, + val moderationChannelId: String, val allowedServerId: String, val githubTokenOwn: String, val githubTokenPullRequests: String, @@ -15,24 +17,29 @@ data class BotConfig( object ConfigLoader { private val gson = GsonBuilder().setPrettyPrinting().create() - private val logger = LoggerFactory.getLogger(ConfigLoader::class.java) + private val logger = LoggerFactory.getLogger(ConfigLoader::class.java) private val exampleConfig = BotConfig( - "TODO: discord token", - "TODO: staff channel id", - "TODO: allowed server id", - "TODO: github token with sh bot repo access", - "TODO: github token with sh mod repo access", - mapOf( - "user friendly (non important) name" to "TODO: role id" - ) - ) - fun load(filePath: String): BotConfig { - try { - val json = File(filePath).readText() - return gson.fromJson(json, BotConfig::class.java) - } catch (ex: Exception) { - logger.error("Could not load config. Below is an example config:\n```json\n${gson.toJson(exampleConfig)}\n```", ex) - exitProcess(1) - } + "TODO: discord token", + "TODO: staff channel id", + "TODO: moderation channel id", + "TODO: allowed server id", + "TODO: github token with sh bot repo access", + "TODO: github token with sh mod repo access", + mapOf( + "user friendly (non important) name" to "TODO: role id" + ) + ) + + fun load(filePath: String): BotConfig { + try { + val json = File(filePath).readText() + return gson.fromJson(json, BotConfig::class.java) + } catch (ex: Exception) { + logger.error( + "Could not load config. Below is an example config:\n```json\n${gson.toJson(exampleConfig)}\n```", + ex + ) + exitProcess(1) + } } } \ No newline at end of file diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/Utils.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/Utils.kt index 1d7b0b7..03b417d 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/Utils.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/Utils.kt @@ -11,6 +11,8 @@ import net.dv8tion.jda.api.utils.FileUpload import java.awt.Color import java.awt.Toolkit.getDefaultToolkit import java.io.File +import java.time.Instant +import java.time.format.DateTimeFormatter import java.util.zip.ZipFile import kotlin.time.Duration import kotlin.time.Duration.Companion.nanoseconds @@ -68,6 +70,10 @@ object Utils { } } + fun MessageChannel.embedSend(embed: MessageEmbed) { + sendMessageEmbeds(embed).queue() + } + fun Message.replyWithConsumer(text: String, consumer: (MessageReceivedEvent) -> Unit) { BotMessageHandler.log(text, consumer) messageReply(text) @@ -165,6 +171,19 @@ object Utils { } + fun parseToUnixTime(isoTimestamp: String): Long = + Instant.from(DateTimeFormatter.ISO_INSTANT.parse(isoTimestamp)).epochSecond + + fun passedSince(stringTime: String): String = "" + + private val mentionRegex = Regex("\\d+)>?") + + fun String.getId(): String? { + return mentionRegex.matchEntire(this)?.let { result -> + result.groups["id"]?.value + } + } + fun String.linkTo(link: String): String = "[$this](<$link>)" // keep comments as docs diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/ManageCommands.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/ManageCommands.kt new file mode 100644 index 0000000..053513f --- /dev/null +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/ManageCommands.kt @@ -0,0 +1,181 @@ +package at.hannibal2.skyhanni.discord.command + + +import at.hannibal2.skyhanni.discord.BOT +import at.hannibal2.skyhanni.discord.BIG_X +import at.hannibal2.skyhanni.discord.Option +import at.hannibal2.skyhanni.discord.PLEADING_FACE +import at.hannibal2.skyhanni.discord.SimpleTimeMark +import at.hannibal2.skyhanni.discord.Utils.embed +import at.hannibal2.skyhanni.discord.Utils.embedSend +import at.hannibal2.skyhanni.discord.Utils.getId +import at.hannibal2.skyhanni.discord.Utils.messageSend +import at.hannibal2.skyhanni.discord.Utils.reply +import at.hannibal2.skyhanni.discord.Utils.userError +import at.hannibal2.skyhanni.discord.command.ManageCommands.createModerationEmbed +import at.hannibal2.skyhanni.discord.command.ManageCommands.handleError +import at.hannibal2.skyhanni.discord.command.ManageCommands.sendDM +import at.hannibal2.skyhanni.discord.command.PullRequestCommand.passedSince +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.Permission +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.entities.MessageEmbed +import net.dv8tion.jda.api.entities.User +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel +import net.dv8tion.jda.api.events.message.MessageReceivedEvent +import net.dv8tion.jda.api.exceptions.ErrorResponseException +import net.dv8tion.jda.api.requests.ErrorResponse +import java.awt.Color +import java.util.concurrent.TimeUnit + +object ManageCommands { + fun createModerationEmbed( + action: String, + target: User, + moderator: Member, + reason: String, + color: Color = Color.RED + ): MessageEmbed { + return EmbedBuilder() + .setAuthor("${target.name} was $action", null, target.avatarUrl) + .addField("Mention", target.asMention, false) + .addField("Moderator", moderator.asMention, false) + .addField("Reason", reason, false) + .addField("Time", passedSince(SimpleTimeMark.now().toString()), false) + .setColor(color) + .build() + } + + fun handleError(error: Throwable): String { + return when (error) { + is ErrorResponseException -> { + when (error.errorResponse) { + ErrorResponse.UNKNOWN_MEMBER -> + "User not in guild!" + + ErrorResponse.UNKNOWN_USER -> + "User doesn't exist!" + + ErrorResponse.CANNOT_SEND_TO_USER -> + "Couldn't DM user!" + + else -> "Discord API error: ${error.errorResponse}" + } + } + + else -> "${error.message}" + } + } + + fun User.sendDM(channel: MessageChannel, action: String, reason: String, color: Color = Color.RED) { + openPrivateChannel().queue { dm -> + val embed = embed("You were $action!", "**Reason:** $reason", color) + + dm.sendMessageEmbeds(embed).queue( + { channel.messageSend("Sent DM to ${name}.") }, + { error -> channel.messageSend(handleError(error)) } + ) + } + } +} + +@Suppress("unused") +class KickCommand : BaseCommand() { + override val name: String = "kick" + override val description: String = "Kicks a member from the server." + override val options: List