diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/BotConfig.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/BotConfig.kt index 63b0650..750f7ae 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/BotConfig.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/BotConfig.kt @@ -11,6 +11,8 @@ data class BotConfig( val githubTokenOwn: String, val githubTokenPullRequests: String, val editPermissionRoleIds: Map, + val githubWebhookUserId: String, + val openPrTagId: String ) object ConfigLoader { @@ -24,7 +26,9 @@ object ConfigLoader { "TODO: github token with sh mod repo access", mapOf( "user friendly (non important) name" to "TODO: role id" - ) + ), + "TODO: github webhook 'user' id", + "TODO: discord forum open pull request tag id" ) fun load(filePath: String): BotConfig { try { diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/CommandListener.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/CommandListener.kt index 3220900..55a3acf 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/CommandListener.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/CommandListener.kt @@ -7,6 +7,7 @@ import at.hannibal2.skyhanni.discord.Utils.logAction import at.hannibal2.skyhanni.discord.Utils.reply import at.hannibal2.skyhanni.discord.command.* import at.hannibal2.skyhanni.discord.utils.ErrorManager.handleError +import net.dv8tion.jda.api.entities.channel.ChannelType import net.dv8tion.jda.api.events.message.MessageReceivedEvent import org.reflections.Reflections import java.lang.reflect.Modifier @@ -38,6 +39,9 @@ object CommandListener { if (this.author.id == BOT_ID) { BotMessageHandler.handle(this) } + if (this.author.id == BOT.config.githubWebhookUserId) { + LinkListener.onMessage(bot, this) + } return } @@ -66,7 +70,7 @@ object CommandListener { return } - if (!command.userCommand) { + if (!command.userCommand && channelType != ChannelType.GUILD_PUBLIC_THREAD) { if (!hasAdminPermissions()) { reply("No permissions $PLEADING_FACE") return @@ -78,7 +82,7 @@ object CommandListener { } } - // allows to use `! -help` instaed of `!help -` + // allows to use `! -help` instead of `!help -` if (args.size == 1 && args.first() == "-help") { with(HelpCommand) { sendUsageReply(literal) diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/Database.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/Database.kt index d3605b9..d69b668 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/Database.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/Database.kt @@ -8,6 +8,7 @@ data class Tag(val keyword: String, var response: String, var uses: Int) object Database { private val connection: Connection = DriverManager.getConnection("jdbc:sqlite:bot.db") private val tags = mutableMapOf() + private val linkedForumPosts = mutableMapOf() // key = channel id, value = pr number init { val statement = connection.createStatement() @@ -19,7 +20,16 @@ object Database { append("response TEXT, ") append("count INTEGER DEFAULT 0)") }) + + statement.execute(buildString { + append("CREATE TABLE IF NOT EXISTS linked_posts (") + append("id INTEGER PRIMARY KEY AUTOINCREMENT, ") + append("channel_id STRING UNIQUE, ") + append("pull_request_id INTEGER UNIQUE)") + }) + loadTagCache() + loadLinkCache() } private fun loadTagCache() { @@ -34,6 +44,17 @@ object Database { resultSet.close() } + private fun loadLinkCache() { + val statement = connection.prepareStatement("SELECT channel_id, pull_request_id FROM linked_posts") + val resultSet = statement.executeQuery() + while (resultSet.next()) { + val channelId = resultSet.getString("channel_id") + val pr = resultSet.getInt("pull_request_id") + linkedForumPosts[channelId] = pr + } + resultSet.close() + } + private fun ensureCountColumnExists() { val statement = connection.prepareStatement("PRAGMA table_info(keywords)") val resultSet = statement.executeQuery() @@ -66,6 +87,19 @@ object Database { return updated } + fun addLink(channelId: String, pr: Int): Boolean { + val statement = connection.prepareStatement( + "INSERT OR REPLACE INTO linked_posts (channel_id, pull_request_id) VALUES (?, ?)" + ) + statement.setString(1, channelId) + statement.setInt(2, pr) + val updated = statement.executeUpdate() > 0 + if (updated) { + linkedForumPosts[channelId] = pr + } + return updated + } + fun getResponse(keyword: String, increment: Boolean = false): String? { val key = keyword.lowercase() val kObj = tags[key] ?: return null @@ -81,6 +115,10 @@ object Database { return kObj.response } + fun getChannelId(prNumber: Int): String? = linkedForumPosts.entries.find { it.value == prNumber }?.key + + fun getPullrequest(channelId: String): Int? = linkedForumPosts[channelId] + fun deleteTag(keyword: String): Boolean { val key = keyword.lowercase() val statement = connection.prepareStatement("DELETE FROM keywords WHERE keyword = ?") @@ -90,8 +128,18 @@ object Database { return updated } + fun deleteLink(channelId: String): Boolean { + val statement = connection.prepareStatement("DELETE FROM linked_posts WHERE channel_id = ?") + statement.setString(1, channelId) + val updated = statement.executeUpdate() > 0 + if (updated) linkedForumPosts.remove(channelId) + return updated + } + fun listTags(): List = tags.values.toList() + fun listLinks(): Map = linkedForumPosts + fun getTagCount(keyword: String): Int? { return tags[keyword.lowercase()]?.uses } diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/LinkListener.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/LinkListener.kt new file mode 100644 index 0000000..ba5ffe5 --- /dev/null +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/LinkListener.kt @@ -0,0 +1,33 @@ +package at.hannibal2.skyhanni.discord + +import at.hannibal2.skyhanni.discord.Utils.messageSend +import net.dv8tion.jda.api.events.message.MessageReceivedEvent + +object LinkListener { + + private val githubPattern = + "\\[SkyHanniStudios/DiscordBot] (New comment on pull request|Pull request review submitted:) #(?\\d+):? .+".toPattern() + + fun onMessage(bot: DiscordBot, event: MessageReceivedEvent) { + event.onMessage(bot) + } + + private fun MessageReceivedEvent.onMessage(bot: DiscordBot) { + val embed = this.message.embeds[0] + val title = embed.title ?: return + + val prNumber = getPr(title)?.toInt() ?: return + val channelId = Database.getChannelId(prNumber) ?: return + + val guild = bot.jda.getGuildById(BOT.config.allowedServerId) ?: return + val channel = guild.getThreadChannelById(channelId) ?: return + + channel.messageSend(this.message.embeds[0]) + } + + private fun getPr(title: String): String? { + val matcher = githubPattern.matcher(title) + if (!matcher.matches()) return null + return matcher.group("pr") + } +} \ 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 354f3b3..48abd6b 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/Utils.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/Utils.kt @@ -46,6 +46,10 @@ object Utils { logAction("Error: $text") } + fun MessageReceivedEvent.userSuccess(text: String) { + message.messageReply("✅ $text") + } + fun MessageReceivedEvent.reply(embed: MessageEmbed) { message.messageReply(embed) } @@ -81,6 +85,14 @@ object Utils { } } + fun MessageChannel.messageSend(embed: MessageEmbed, instantly: Boolean = false) { + if (instantly) { + sendMessageEmbeds(embed).complete() + } else { + sendMessageEmbeds(embed).queue() + } + } + fun Message.replyWithConsumer(text: String, consumer: (MessageReceivedEvent) -> Unit) { BotMessageHandler.log(text, consumer) messageReply(text) diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/LinkCommands.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/LinkCommands.kt new file mode 100644 index 0000000..69aa825 --- /dev/null +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/LinkCommands.kt @@ -0,0 +1,129 @@ +package at.hannibal2.skyhanni.discord.command + +import at.hannibal2.skyhanni.discord.BOT +import at.hannibal2.skyhanni.discord.Database +import at.hannibal2.skyhanni.discord.Option +import at.hannibal2.skyhanni.discord.PLEADING_FACE +import at.hannibal2.skyhanni.discord.Utils.logAction +import at.hannibal2.skyhanni.discord.Utils.reply +import at.hannibal2.skyhanni.discord.Utils.userError +import at.hannibal2.skyhanni.discord.Utils.userSuccess +import at.hannibal2.skyhanni.discord.command.LinkCommand.setTags +import at.hannibal2.skyhanni.discord.command.LinkCommand.setTitle +import at.hannibal2.skyhanni.discord.command.PullRequestCommand.parseValidPrNumber +import at.hannibal2.skyhanni.discord.github.GitHubClient +import net.dv8tion.jda.api.entities.channel.ChannelType +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel +import net.dv8tion.jda.api.entities.channel.forums.ForumTag +import net.dv8tion.jda.api.events.message.MessageReceivedEvent +import net.dv8tion.jda.api.managers.channel.concrete.ThreadChannelManager + +object LinkCommand : BaseCommand() { + override val name = "link" + + override val description = "Link a forum post to a pull request." + override val options: List