diff --git a/src/main/kotlin/org/quiltmc/community/_Utils.kt b/src/main/kotlin/org/quiltmc/community/_Utils.kt index 0092aad5..56dcf0d5 100644 --- a/src/main/kotlin/org/quiltmc/community/_Utils.kt +++ b/src/main/kotlin/org/quiltmc/community/_Utils.kt @@ -108,6 +108,7 @@ suspend fun ExtensibleBotBuilder.database(migrate: Boolean = false) { single { FilterCollection() } bind FilterCollection::class single { FilterEventCollection() } bind FilterEventCollection::class single { GlobalSettingsCollection() } bind GlobalSettingsCollection::class + single { LinkedMessagesCollection() } bind LinkedMessagesCollection::class single { MetaCollection() } bind MetaCollection::class single { OwnedThreadCollection() } bind OwnedThreadCollection::class single { ServerSettingsCollection() } bind ServerSettingsCollection::class diff --git a/src/main/kotlin/org/quiltmc/community/database/Migrations.kt b/src/main/kotlin/org/quiltmc/community/database/Migrations.kt index 917be1b4..3af46725 100644 --- a/src/main/kotlin/org/quiltmc/community/database/Migrations.kt +++ b/src/main/kotlin/org/quiltmc/community/database/Migrations.kt @@ -40,7 +40,7 @@ object Migrations : KordExKoinComponent { @Suppress("TooGenericExceptionCaught") try { @Suppress("MagicNumber") - when (nextVersion) { // TODO: This should **REEEEEEEEEAAALLLLLLLLLLLLLLLLLLLLLLLY** be annotation-based + when (nextVersion) { // TODO: Someone please make this use annotations I don't have the spoons pls 1 -> ::v1 2 -> ::v2 3 -> ::v3 @@ -62,6 +62,7 @@ object Migrations : KordExKoinComponent { 19 -> ::v19 20 -> ::v20 21 -> ::v21 + 22 -> ::v22 else -> break }(db.mongo) diff --git a/src/main/kotlin/org/quiltmc/community/database/collections/LinkedMessagesCollection.kt b/src/main/kotlin/org/quiltmc/community/database/collections/LinkedMessagesCollection.kt new file mode 100644 index 00000000..eb12b660 --- /dev/null +++ b/src/main/kotlin/org/quiltmc/community/database/collections/LinkedMessagesCollection.kt @@ -0,0 +1,32 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package org.quiltmc.community.database.collections + +import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent +import dev.kord.common.entity.Snowflake +import org.koin.core.component.inject +import org.litote.kmongo.contains +import org.litote.kmongo.eq +import org.quiltmc.community.database.Collection +import org.quiltmc.community.database.Database +import org.quiltmc.community.database.entities.LinkedMessages + +class LinkedMessagesCollection : KordExKoinComponent { + private val database: Database by inject() + private val col = database.mongo.getCollection(name) + + suspend fun getBySource(id: Snowflake) = + col.findOne(LinkedMessages::_id eq id) + + fun getByTarget(id: Snowflake) = + col.find(LinkedMessages::targets contains id).toFlow() + + suspend fun set(linkedMessages: LinkedMessages) = + col.save(linkedMessages) + + companion object : Collection("linked-messages") +} diff --git a/src/main/kotlin/org/quiltmc/community/database/entities/LinkedMessages.kt b/src/main/kotlin/org/quiltmc/community/database/entities/LinkedMessages.kt new file mode 100644 index 00000000..38690b29 --- /dev/null +++ b/src/main/kotlin/org/quiltmc/community/database/entities/LinkedMessages.kt @@ -0,0 +1,28 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +@file:Suppress("DataClassShouldBeImmutable", "DataClassContainsFunctions") // Well, yes, but actually no. + +package org.quiltmc.community.database.entities + +import com.kotlindiscord.kord.extensions.utils.getKoin +import dev.kord.common.entity.Snowflake +import kotlinx.serialization.Serializable +import org.quiltmc.community.database.Entity +import org.quiltmc.community.database.collections.LinkedMessagesCollection + +@Serializable +data class LinkedMessages( + override val _id: Snowflake, + + val targets: MutableList = mutableListOf() +) : Entity { + suspend fun save() { + val collection = getKoin().get() + + collection.set(this) + } +} diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v22.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v22.kt new file mode 100644 index 00000000..7eb8e7a0 --- /dev/null +++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v22.kt @@ -0,0 +1,14 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package org.quiltmc.community.database.migrations + +import org.litote.kmongo.coroutine.CoroutineDatabase +import org.quiltmc.community.database.collections.LinkedMessagesCollection + +suspend fun v22(db: CoroutineDatabase) { + db.createCollection(LinkedMessagesCollection.name) +} diff --git a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/ForumExtension.kt b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/ForumExtension.kt index b68f4a85..fee184d4 100644 --- a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/ForumExtension.kt +++ b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/ForumExtension.kt @@ -22,12 +22,15 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralMessageCommand import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.extensions.event +import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.PKMessageCreateEvent +import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.PKMessageUpdateEvent import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI import com.kotlindiscord.kord.extensions.modules.unsafe.extensions.unsafeSubCommand import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialSlashCommandResponse import com.kotlindiscord.kord.extensions.modules.unsafe.types.ackEphemeral import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.addReaction +import com.kotlindiscord.kord.extensions.utils.deleteOwnReaction import com.kotlindiscord.kord.extensions.utils.ensureWebhook import com.kotlindiscord.kord.extensions.utils.extraData import dev.kord.common.annotation.KordUnsafe @@ -47,14 +50,15 @@ import dev.kord.core.entity.channel.NewsChannel import dev.kord.core.entity.channel.TopGuildMessageChannel import dev.kord.core.entity.channel.thread.TextChannelThread import dev.kord.core.entity.channel.thread.ThreadChannel -import dev.kord.core.event.message.MessageCreateEvent import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.request.* import kotlinx.coroutines.delay import org.koin.core.component.inject import org.quiltmc.community.* +import org.quiltmc.community.database.collections.LinkedMessagesCollection import org.quiltmc.community.database.collections.UserFlagsCollection +import org.quiltmc.community.database.entities.LinkedMessages import org.quiltmc.community.database.entities.UserFlags import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes @@ -69,6 +73,7 @@ class ForumExtension : Extension() { override val name: String = "forum" private val userFlags: UserFlagsCollection by inject() + private val linkedMessages: LinkedMessagesCollection by inject() override suspend fun setup() { ephemeralSlashCommand { @@ -396,15 +401,17 @@ class ForumExtension : Extension() { } } - event { + event { check { val parent = topChannelFor(event) val message = messageFor(event) val user = userFor(event) if ( + message == null || // No message to be found parent?.id != DEVLOG_FORUM || // Wrong forum channel - message?.asMessageOrNull()?.author?.id == kord.selfId || // Cozy sent the message + message.asMessageOrNull()?.author?.id == kord.selfId || // Cozy sent the message + linkedMessages.getBySource(message.id) != null || // Already published user == null // No user for the event ) { fail() @@ -427,12 +434,44 @@ class ForumExtension : Extension() { event.message.publishDevlog() } } + + event { + check { + val parent = topChannelFor(event) + val message = messageFor(event) + val user = userFor(event) + + if ( + message == null || // No message to be found + parent?.id != DEVLOG_FORUM || // Wrong forum channel + message.asMessageOrNull()?.author?.id == kord.selfId || // Cozy sent the message + linkedMessages.getBySource(message.id) == null || // Not published + user == null // No user for the event + ) { + fail() + + return@check + } + + pass() + } + + action { + event.message.asMessage().editDevlog() + } + } } private suspend fun Message.publishDevlog() { val publishingChannel = kord.getChannelOf(DEVLOG_CHANNEL) ?: throw DiscordRelayedException("Unable to get the publishing channel") + val existingLinkedMessage = linkedMessages.getBySource(id) + + if (existingLinkedMessage != null) { + throw DiscordRelayedException("This message was already published.") + } + val thread = channel.asChannelOf() val firstMessage = thread.getFirstMessage()!! @@ -454,6 +493,8 @@ class ForumExtension : Extension() { this.content = this@publishDevlog.content } + LinkedMessages(id, mutableListOf(message.id)).save() + if (publishingChannel is NewsChannel) { message.publish() } @@ -461,6 +502,32 @@ class ForumExtension : Extension() { addReaction("🚀") } + private suspend fun Message.editDevlog() { + val publishingChannel = kord.getChannelOf(DEVLOG_CHANNEL) + ?: throw DiscordRelayedException("Unable to get the publishing channel") + + val linkedMessage = linkedMessages.getBySource(id) + ?: return // Hasn't been published yet + + val webhook = ensureWebhook(publishingChannel, "Quilt Devlogs") { + HttpClient().get(TOOLCHAIN_LOGO).body() + } + + linkedMessage.targets.forEach { + val message = webhook.getMessage(webhook.token!!, it) + + message.edit(webhook.id, webhook.token!!) { + this.content = this@editDevlog.content + } + } + + addReaction("✅") + + delay(3.seconds) + + deleteOwnReaction("✅") + } + inner class PostTagArgs : Arguments() { override val parseForAutocomplete: Boolean = true