diff --git a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/minecraft/MinecraftExtension.kt b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/minecraft/MinecraftExtension.kt
index 585f232..f877b72 100644
--- a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/minecraft/MinecraftExtension.kt
+++ b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/minecraft/MinecraftExtension.kt
@@ -10,6 +10,7 @@ import dev.kord.common.annotation.KordPreview
import dev.kord.common.entity.Permission
import dev.kord.common.entity.Snowflake
import dev.kord.core.behavior.channel.createMessage
+import dev.kord.core.behavior.edit
import dev.kord.core.builder.components.emoji
import dev.kord.core.entity.ReactionEmoji
import dev.kord.core.entity.channel.NewsChannel
@@ -21,15 +22,19 @@ import dev.kord.rest.builder.message.embed
import dev.kordex.core.DISCORD_FUCHSIA
import dev.kordex.core.DISCORD_GREEN
import dev.kordex.core.checks.hasPermission
+import dev.kordex.core.checks.or
import dev.kordex.core.commands.Arguments
import dev.kordex.core.commands.application.slash.ephemeralSubCommand
+import dev.kordex.core.commands.converters.impl.message
import dev.kordex.core.commands.converters.impl.optionalString
+import dev.kordex.core.commands.converters.impl.string
import dev.kordex.core.extensions.Extension
import dev.kordex.core.extensions.ephemeralSlashCommand
import dev.kordex.core.pagination.pages.Page
import dev.kordex.core.utils.scheduling.Scheduler
import dev.kordex.core.utils.scheduling.Task
import dev.kordex.core.utils.toReaction
+import dev.kordex.parser.Cursor
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.client.*
import io.ktor.client.call.*
@@ -40,6 +45,7 @@ import kotlinx.datetime.Clock
import kotlinx.serialization.json.Json
import org.apache.commons.text.StringEscapeUtils
import org.quiltmc.community.*
+import kotlin.system.exitProcess
private const val PAGINATOR_TIMEOUT = 60_000L // One minute
private const val CHUNK_SIZE = 10
@@ -49,7 +55,7 @@ private const val JSON_URL = "$BASE_URL/javaPatchNotes.json"
private const val CHECK_DELAY = 60L
-private val LINK_REGEX = "[^\"]+)\"[^>]*>(?[^<]+)".toRegex()
+private val LINK_REGEX = "[^\"\\s]+)\"?[^>]*>(?[^<]+)".toRegex()
@Suppress("MagicNumber", "UnderscoresInNumericLiterals")
private val CHANNELS: List = listOf(
@@ -160,9 +166,13 @@ class MinecraftExtension : Extension() {
name = "forget"
description = "Forget a version (the last one by default), allowing it to be relayed again."
- check { hasBaseModeratorRole() }
+ check {
+ hasBaseModeratorRole()
- check { hasPermission(Permission.Administrator) }
+ or {
+ hasPermission(Permission.Administrator)
+ }
+ }
action {
if (!::currentEntries.isInitialized) {
@@ -189,11 +199,52 @@ class MinecraftExtension : Extension() {
}
}
+ ephemeralSubCommand(::UpdateArguments) {
+ name = "update"
+ description = "Edit the given message to replace its embed. Useful when formatting code changes."
+
+ check {
+ hasBaseModeratorRole()
+
+ or {
+ hasPermission(Permission.Administrator)
+ }
+ }
+
+ action {
+ if (!::currentEntries.isInitialized) {
+ respond { content = "Still setting up - try again a bit later!" }
+ return@action
+ }
+
+ val entry = currentEntries.entries.firstOrNull {
+ it.version.equals(arguments.version, true)
+ }
+
+ if (entry == null) {
+ respond { content = "Unknown version supplied: `${arguments.version}`" }
+ return@action
+ }
+
+ arguments.message.edit {
+ patchNotes(entry.get())
+ }
+
+ respond { content = "Message edit to match version: `${entry.version}`" }
+ }
+ }
+
ephemeralSubCommand {
name = "run"
description = "Run the check task now, without waiting for it."
- check { hasBaseModeratorRole() }
+ check {
+ hasBaseModeratorRole()
+
+ or {
+ hasPermission(Permission.Administrator)
+ }
+ }
action {
respond { content = "Checking now..." }
@@ -247,7 +298,7 @@ class MinecraftExtension : Extension() {
.forEach { it.relay(patchNote) }
fun String.formatHTML(): String {
- var result = this
+ var result = StringEscapeUtils.unescapeHtml4(trim('\n'))
result = result.replace("\u200B", "")
result = result.replace("", "")
@@ -261,6 +312,9 @@ class MinecraftExtension : Extension() {
result = result.replace("", "**")
result = result.replace("", "**")
+ result = result.replace("", "_")
+ result = result.replace("", "_")
+
result = result.replace("", "`")
result = result.replace("
", "`")
@@ -286,10 +340,55 @@ class MinecraftExtension : Extension() {
)
}
- return StringEscapeUtils.unescapeHtml4(result.trim('\n'))
+ val cursor = Cursor(result)
+ var isQuote = false
+
+ result = ""
+
+ @Suppress("LoopWithTooManyJumpStatements") // Nah.
+ while (cursor.hasNext) {
+ result = result + (
+ cursor.consumeWhile { it != '<' }?.prefixQuote(isQuote)
+ ?: break
+ )
+
+ val temp = cursor.consumeWhile { it != '>' }
+ ?.plus(cursor.nextOrNull() ?: "")
+ ?: break
+
+ if (temp == "") {
+ isQuote = true
+
+ if (cursor.peekNext() == '\n') {
+ cursor.next()
+ }
+
+ continue
+ } else if (temp == "
") {
+ isQuote = false
+
+ continue
+ }
+
+ result = result + temp.prefixQuote(isQuote)
+ }
+
+ result = result.replace("<", "<")
+
+ return result.trim()
}
- fun String.truncateMarkdown(maxLength: Int = 1000): Pair {
+ fun String.prefixQuote(prefix: Boolean) =
+ if (prefix) {
+ split("\n")
+ .joinToString("\n") {
+ "> $it"
+ }
+ } else {
+ this
+ }
+
+ fun String.truncateMarkdown(maxLength: Int = 3000): Pair {
var result = this
if (length > maxLength) {
@@ -304,7 +403,7 @@ class MinecraftExtension : Extension() {
return result to 0
}
- private fun MessageBuilder.patchNotes(patchNote: PatchNote, maxLength: Int = 4000) {
+ private fun MessageBuilder.patchNotes(patchNote: PatchNote, maxLength: Int = 3000) {
val (truncated, remaining) = patchNote.body.formatHTML().truncateMarkdown(maxLength)
actionRow {
@@ -335,14 +434,14 @@ class MinecraftExtension : Extension() {
}
}
- private suspend fun TopGuildMessageChannel.relay(patchNote: PatchNote, maxLength: Int = 1000) {
+ private suspend fun TopGuildMessageChannel.relay(patchNote: PatchNote) {
val message = createMessage {
// If we are in the community guild, ping the update role
if (guildId == COMMUNITY_GUILD) {
content = "<@&$MINECRAFT_UPDATE_PING_ROLE>"
}
- patchNotes(patchNote, maxLength)
+ patchNotes(patchNote)
}
val title = if (patchNote.title.startsWith("minecraft ", true)) {
@@ -368,7 +467,10 @@ class MinecraftExtension : Extension() {
}
}
- private suspend fun PatchNoteEntry.get() =
+ fun getLatest() =
+ currentEntries.entries.first()
+
+ suspend fun PatchNoteEntry.get() =
client.get("$BASE_URL/$contentPath").body()
@OptIn(KordPreview::class)
@@ -378,4 +480,32 @@ class MinecraftExtension : Extension() {
description = "Specific version to get patch notes for"
}
}
+
+ @OptIn(KordPreview::class)
+ class UpdateArguments : Arguments() {
+ val version by string {
+ name = "version"
+ description = "Specific version to get patch notes for"
+ }
+
+ val message by message {
+ name = "message"
+ description = "Message to edit with a new embed"
+ }
+ }
+}
+
+// In-dev testing function
+@Suppress("unused")
+private suspend fun main() {
+ val ext = MinecraftExtension()
+ ext.populateVersions()
+
+ val current = ext.getLatest()
+
+ with(ext) {
+ println(current.get().body.formatHTML())
+ }
+
+ exitProcess(0)
}