From 4f0fec0d17a66c02dfec8b1cc17e3fb5ff4072f2 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Tue, 5 Dec 2023 22:48:35 +0200 Subject: [PATCH 01/26] Improved some things. --- .idea/.name | 1 + .../kotlin/quest/laxla/supertrouper/App.kt | 5 +++-- ...outExtension.kt => MaintenanceExtension.kt} | 18 +----------------- .../laxla/supertrouper/TrouperExtension.kt | 8 ++++++++ 4 files changed, 13 insertions(+), 19 deletions(-) create mode 100644 .idea/.name rename src/main/kotlin/quest/laxla/supertrouper/{AboutExtension.kt => MaintenanceExtension.kt} (56%) create mode 100644 src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..24bc394 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Super Trouper diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/supertrouper/App.kt index 7afa490..a36c02b 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/App.kt @@ -2,6 +2,7 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.utils.env +import dev.kord.common.entity.Snowflake import kotlinx.coroutines.runBlocking private val token = env("token") @@ -10,11 +11,11 @@ val officialServer = env("official_server") fun main() = runBlocking { ExtensibleBot(token) { extensions { - add(::AboutExtension) + add(::MaintenanceExtension) } applicationCommands { - defaultGuild(officialServer) + defaultGuild(Snowflake(officialServer)) } }.start() } diff --git a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt similarity index 56% rename from src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt rename to src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt index 066c89a..927fc67 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt @@ -1,27 +1,11 @@ package quest.laxla.supertrouper -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake -class AboutExtension : Extension() { - override val name: String - get() = "about" - +class MaintenanceExtension : TrouperExtension() { override suspend fun setup() { - ephemeralSlashCommand { - name = "about" - - action { - respond { - //language=Markdown - content = "Hey! This is a test command. It's powered by *magic*:sparkles:" - } - } - } - publicSlashCommand { name = "stop" description = "WARNING: Stops the bot completely." diff --git a/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt new file mode 100644 index 0000000..17183e6 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt @@ -0,0 +1,8 @@ +package quest.laxla.supertrouper + +import com.kotlindiscord.kord.extensions.extensions.Extension + +abstract class TrouperExtension : Extension() { + final override val name: String = this::class.simpleName!!.substringBeforeLast("Extension") + .replace("([A-Z])".toRegex()) { '-' + it.groups.single()!!.value.lowercase() }.removePrefix("-") +} From de0dcf14de92b59f2710dee5e839c32ac5af2d9a Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 6 Dec 2023 15:08:08 +0200 Subject: [PATCH 02/26] Bot works --- .idea/misc.xml | 1 - build.gradle.kts | 2 + .../kotlin/quest/laxla/supertrouper/App.kt | 15 +- .../supertrouper/MaintenanceExtension.kt | 10 +- .../quest/laxla/supertrouper/Overwrites.kt | 53 ++++++ .../supertrouper/PrivateMassagingExtension.kt | 154 ++++++++++++++++++ .../laxla/supertrouper/TargetedArguments.kt | 19 +++ .../laxla/supertrouper/TrouperExtension.kt | 4 +- 8 files changed, 245 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt create mode 100644 src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt create mode 100644 src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 3657fb2..b838806 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/build.gradle.kts b/build.gradle.kts index 98955df..ab1749f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,7 +35,9 @@ val klogging: String by project dependencies { implementation(kotlin("stdlib")) + implementation(kotlin("reflect")) implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serialization") + implementation("com.kotlindiscord.kord.extensions:kord-extensions:$kordex") implementation("io.github.oshai:kotlin-logging:$klogging") diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/supertrouper/App.kt index a36c02b..ef05775 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/App.kt @@ -2,20 +2,21 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.utils.env -import dev.kord.common.entity.Snowflake +import com.kotlindiscord.kord.extensions.utils.envOrNull import kotlinx.coroutines.runBlocking -private val token = env("token") -val officialServer = env("official_server") +private val token = env("TOKEN") +private val testingServer = envOrNull("TESTING_SERVER") fun main() = runBlocking { ExtensibleBot(token) { - extensions { - add(::MaintenanceExtension) + applicationCommands { + testingServer?.let { defaultGuild(it) } } - applicationCommands { - defaultGuild(Snowflake(officialServer)) + extensions { + add(::MaintenanceExtension) + add(::PrivateMassagingExtension) } }.start() } diff --git a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt index 927fc67..322e091 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt @@ -1,16 +1,18 @@ package quest.laxla.supertrouper +import com.kotlindiscord.kord.extensions.checks.isBotAdmin import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand -import dev.kord.common.entity.Permission -import dev.kord.common.entity.Snowflake +import com.kotlindiscord.kord.extensions.extensions.slashCommandCheck class MaintenanceExtension : TrouperExtension() { override suspend fun setup() { + slashCommandCheck { + isBotAdmin() + } + publicSlashCommand { name = "stop" description = "WARNING: Stops the bot completely." - guildId = Snowflake(officialServer) - requirePermission(Permission.Administrator) action { //language=Markdown diff --git a/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt b/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt new file mode 100644 index 0000000..d299df1 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt @@ -0,0 +1,53 @@ +package quest.laxla.supertrouper + +import dev.kord.common.entity.Overwrite +import dev.kord.common.entity.OverwriteType +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.Snowflake +import dev.kord.core.entity.PermissionOverwriteEntity +import dev.kord.rest.builder.channel.PermissionOverwritesBuilder + +fun overwrite( + target: Snowflake, + type: OverwriteType, + allowed: Permissions = Permissions(), + denied: Permissions = Permissions() +) = Overwrite(target, type, allowed, denied) + +fun PermissionOverwritesBuilder.addOverwrite( + target: Snowflake, + type: OverwriteType, + allowed: Permissions = Permissions(), + denied: Permissions = Permissions() +) = addOverwrite(overwrite(target, type, allowed, denied)) + +fun PermissionOverwritesBuilder.sync( + vararg overrides: Overwrite, + defaults: Iterable +) = sync(overrides.asIterable(), defaults) + +fun PermissionOverwritesBuilder.sync( + overrides: Iterable, + defaults: Iterable +) { + val permissions = mutableMapOf() + + defaults.forEach { default -> + val override = overrides.find { it.id == default.target && it.type == default.type } + + if (override == null) addOverwrite(default.target, default.type, default.allowed, default.denied) + else permissions[override] = default + } + + overrides.forEach { override -> + val default = permissions[override] + + if (default == null) addOverwrite(override) + else addOverwrite( + default.target, + default.type, + default.allowed - default.denied - override.deny + override.allow, + default.denied - default.allowed - override.allow + override.deny + ) + } +} diff --git a/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt new file mode 100644 index 0000000..00b53e3 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt @@ -0,0 +1,154 @@ +package quest.laxla.supertrouper + +import com.kotlindiscord.kord.extensions.checks.anyGuild +import com.kotlindiscord.kord.extensions.checks.isNotBot +import com.kotlindiscord.kord.extensions.extensions.* +import com.kotlindiscord.kord.extensions.utils.any +import com.kotlindiscord.kord.extensions.utils.envOrNull +import dev.kord.common.entity.OverwriteType +import dev.kord.common.entity.Permission +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.channel.createTextChannel +import dev.kord.core.behavior.channel.edit +import dev.kord.core.behavior.createCategory +import dev.kord.core.entity.User +import dev.kord.core.entity.channel.Category +import dev.kord.core.entity.channel.TextChannel +import dev.kord.core.event.guild.MemberJoinEvent +import dev.kord.gateway.Intent +import dev.kord.gateway.PrivilegedIntent +import dev.kord.rest.builder.channel.addMemberOverwrite +import dev.kord.rest.builder.channel.addRoleOverwrite +import kotlinx.coroutines.flow.* + +private const val PrivateMessagesCategoryName = "Private Messages" +private val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30 +private val privateMessageOwnerPermissions = Permission.ViewChannel + Permission.ReadMessageHistory +private val privateMessageBotPermissions = + privateMessageOwnerPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages + +class PrivateMassagingExtension : TrouperExtension() { + @OptIn(PrivilegedIntent::class) + override suspend fun setup() { + intents += Intent.GuildMembers + + slashCommandCheck { + anyGuild() + isNotBot() + } + + userCommandCheck { + anyGuild() + isNotBot() + } + + event { + action { + if (event.guild.members.count() < memberLimit) getOrCreateChannel(event.guild, event.member) + } + } + + ephemeralSlashCommand(::TargetedArguments) { + name = "pm" + description = "Get a link to a user's private messages channel" + + action { + respond { + content = getOrCreateChannelMention(guild!!, target.asUser()) + } + } + } + + ephemeralUserCommand { + name = "Private Message" + + action { + respond { + content = getOrCreateChannelMention(guild!!, targetUsers.single()) + } + } + } + + ephemeralSlashCommand(::TargetedArguments) slash@{ + name = "sync" + description = "Syncs a private message channel's permissions with the category." + + requirePermission(Permission.ManageRoles) + + action { + val category = getOrCreateCategory(guild!!) + val targetUser = target.asUser() + val targetChannel = getChannel(category, targetUser) + if (targetChannel == null) { + respond { content = "${target.mention} does not have a private message channel in this server." } + return@action + } + + targetChannel.edit { + sync( + overwrite(this@slash.kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), + overwrite(targetUser.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), + defaults = category.permissionOverwrites + ) + } + + respond { + content = "Synced ${targetChannel.mention} for ${target.mention} successfully." + } + } + } + } + + private suspend fun getOrCreateChannelMention(guild: GuildBehavior, user: User): String = + user.mention + ": " + (getOrCreateChannel(guild, user)?.mention ?: "Ineligible") + + private suspend fun getOrCreateChannel(guild: GuildBehavior, user: User) = + getOrCreateChannel(getOrCreateCategory(guild), user) + + private suspend fun getOrCreateCategory(guild: GuildBehavior) = getCategory(guild) ?: createCategory(guild) + + private suspend fun getCategory(guild: GuildBehavior) = guild.channels.filterIsInstance().filter { + it.name.equals(PrivateMessagesCategoryName, ignoreCase = true) + }.singleOrNull() + + private suspend fun createCategory(guild: GuildBehavior) = guild.createCategory(PrivateMessagesCategoryName) { + reason = "Private messaging category was missing." + nsfw = false + + addMemberOverwrite(kord.selfId) { + allowed += privateMessageBotPermissions + } + + addRoleOverwrite(guild.id) { + denied += Permission.ViewChannel + } + } + + private suspend fun getOrCreateChannel(category: Category, user: User) = + if (user.isBot) null else getChannel(category, user) ?: createChannel(category, user) + + private suspend fun getChannel(category: Category, user: User) = + category.channels.filterIsInstance().firstOrNull { channel -> + channel.categoryId == category.id && (channel.topic?.contains(user.mention) == true || channel.pinnedMessages.any { + it.author?.id == kord.selfId && it.mentionedUserIds.singleOrNull() == user.id + }) + } + + private suspend fun createChannel(category: Category, user: User): TextChannel { + val mention = user.mention + + val channel = category.createTextChannel(user.username) { + reason = "Created a PM with $mention." + nsfw = category.data.nsfw.discordBoolean + topic = mention + + sync( + overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), + overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), + defaults = category.permissionOverwrites + ) + } + + return channel + } +} diff --git a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt new file mode 100644 index 0000000..1f2ef39 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt @@ -0,0 +1,19 @@ +package quest.laxla.supertrouper + +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext +import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalUser +import com.kotlindiscord.kord.extensions.components.forms.ModalForm + +private const val TargetArgumentName = "target" +private const val TargetArgumentDescription = "Target of this command. Defaults to you." + +open class TargetedArguments : Arguments() { + val targetOrNull by optionalUser { + name = TargetArgumentName + description = TargetArgumentDescription + } +} + +val C.target where C : SlashCommandContext<*, A, M>, A : TargetedArguments, M : ModalForm + get() = arguments.targetOrNull ?: user diff --git a/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt index 17183e6..7c06e3e 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt @@ -2,7 +2,9 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.extensions.Extension +private const val NameRegexGroup = "name" + abstract class TrouperExtension : Extension() { final override val name: String = this::class.simpleName!!.substringBeforeLast("Extension") - .replace("([A-Z])".toRegex()) { '-' + it.groups.single()!!.value.lowercase() }.removePrefix("-") + .replace("(?<$NameRegexGroup>[A-Z])".toRegex()) { '-' + it.groups[NameRegexGroup]!!.value.lowercase() }.removePrefix("-") } From 9f2abd6c95a71eda15aee3412c9f95331bcfb212 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 6 Dec 2023 16:02:41 +0200 Subject: [PATCH 03/26] Re-sorted some things --- .../kotlin/quest/laxla/supertrouper/App.kt | 3 + .../supertrouper/PrivateMassagingExtension.kt | 154 ------------------ .../messaging/PrivateMassagingExtension.kt | 149 +++++++++++++++++ .../messaging/PrivateMessaging.kt | 32 ++++ 4 files changed, 184 insertions(+), 154 deletions(-) delete mode 100644 src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt create mode 100644 src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt create mode 100644 src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/supertrouper/App.kt index ef05775..53e487c 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/App.kt @@ -3,7 +3,9 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.utils.env import com.kotlindiscord.kord.extensions.utils.envOrNull +import dev.kord.gateway.PrivilegedIntent import kotlinx.coroutines.runBlocking +import quest.laxla.supertrouper.messaging.PrivateMassagingExtension private val token = env("TOKEN") private val testingServer = envOrNull("TESTING_SERVER") @@ -16,6 +18,7 @@ fun main() = runBlocking { extensions { add(::MaintenanceExtension) + @OptIn(PrivilegedIntent::class) add(::PrivateMassagingExtension) } }.start() diff --git a/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt deleted file mode 100644 index 00b53e3..0000000 --- a/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt +++ /dev/null @@ -1,154 +0,0 @@ -package quest.laxla.supertrouper - -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.isNotBot -import com.kotlindiscord.kord.extensions.extensions.* -import com.kotlindiscord.kord.extensions.utils.any -import com.kotlindiscord.kord.extensions.utils.envOrNull -import dev.kord.common.entity.OverwriteType -import dev.kord.common.entity.Permission -import dev.kord.core.behavior.GuildBehavior -import dev.kord.core.behavior.channel.createTextChannel -import dev.kord.core.behavior.channel.edit -import dev.kord.core.behavior.createCategory -import dev.kord.core.entity.User -import dev.kord.core.entity.channel.Category -import dev.kord.core.entity.channel.TextChannel -import dev.kord.core.event.guild.MemberJoinEvent -import dev.kord.gateway.Intent -import dev.kord.gateway.PrivilegedIntent -import dev.kord.rest.builder.channel.addMemberOverwrite -import dev.kord.rest.builder.channel.addRoleOverwrite -import kotlinx.coroutines.flow.* - -private const val PrivateMessagesCategoryName = "Private Messages" -private val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30 -private val privateMessageOwnerPermissions = Permission.ViewChannel + Permission.ReadMessageHistory -private val privateMessageBotPermissions = - privateMessageOwnerPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages - -class PrivateMassagingExtension : TrouperExtension() { - @OptIn(PrivilegedIntent::class) - override suspend fun setup() { - intents += Intent.GuildMembers - - slashCommandCheck { - anyGuild() - isNotBot() - } - - userCommandCheck { - anyGuild() - isNotBot() - } - - event { - action { - if (event.guild.members.count() < memberLimit) getOrCreateChannel(event.guild, event.member) - } - } - - ephemeralSlashCommand(::TargetedArguments) { - name = "pm" - description = "Get a link to a user's private messages channel" - - action { - respond { - content = getOrCreateChannelMention(guild!!, target.asUser()) - } - } - } - - ephemeralUserCommand { - name = "Private Message" - - action { - respond { - content = getOrCreateChannelMention(guild!!, targetUsers.single()) - } - } - } - - ephemeralSlashCommand(::TargetedArguments) slash@{ - name = "sync" - description = "Syncs a private message channel's permissions with the category." - - requirePermission(Permission.ManageRoles) - - action { - val category = getOrCreateCategory(guild!!) - val targetUser = target.asUser() - val targetChannel = getChannel(category, targetUser) - if (targetChannel == null) { - respond { content = "${target.mention} does not have a private message channel in this server." } - return@action - } - - targetChannel.edit { - sync( - overwrite(this@slash.kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), - overwrite(targetUser.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), - defaults = category.permissionOverwrites - ) - } - - respond { - content = "Synced ${targetChannel.mention} for ${target.mention} successfully." - } - } - } - } - - private suspend fun getOrCreateChannelMention(guild: GuildBehavior, user: User): String = - user.mention + ": " + (getOrCreateChannel(guild, user)?.mention ?: "Ineligible") - - private suspend fun getOrCreateChannel(guild: GuildBehavior, user: User) = - getOrCreateChannel(getOrCreateCategory(guild), user) - - private suspend fun getOrCreateCategory(guild: GuildBehavior) = getCategory(guild) ?: createCategory(guild) - - private suspend fun getCategory(guild: GuildBehavior) = guild.channels.filterIsInstance().filter { - it.name.equals(PrivateMessagesCategoryName, ignoreCase = true) - }.singleOrNull() - - private suspend fun createCategory(guild: GuildBehavior) = guild.createCategory(PrivateMessagesCategoryName) { - reason = "Private messaging category was missing." - nsfw = false - - addMemberOverwrite(kord.selfId) { - allowed += privateMessageBotPermissions - } - - addRoleOverwrite(guild.id) { - denied += Permission.ViewChannel - } - } - - private suspend fun getOrCreateChannel(category: Category, user: User) = - if (user.isBot) null else getChannel(category, user) ?: createChannel(category, user) - - private suspend fun getChannel(category: Category, user: User) = - category.channels.filterIsInstance().firstOrNull { channel -> - channel.categoryId == category.id && (channel.topic?.contains(user.mention) == true || channel.pinnedMessages.any { - it.author?.id == kord.selfId && it.mentionedUserIds.singleOrNull() == user.id - }) - } - - private suspend fun createChannel(category: Category, user: User): TextChannel { - val mention = user.mention - - val channel = category.createTextChannel(user.username) { - reason = "Created a PM with $mention." - nsfw = category.data.nsfw.discordBoolean - topic = mention - - sync( - overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), - overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), - defaults = category.permissionOverwrites - ) - } - - return channel - } -} diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt new file mode 100644 index 0000000..2a933ef --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt @@ -0,0 +1,149 @@ +package quest.laxla.supertrouper.messaging + +import com.kotlindiscord.kord.extensions.checks.anyGuild +import com.kotlindiscord.kord.extensions.checks.isNotBot +import com.kotlindiscord.kord.extensions.extensions.* +import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext +import dev.kord.common.entity.OverwriteType +import dev.kord.common.entity.Permission +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.channel.createTextChannel +import dev.kord.core.behavior.channel.edit +import dev.kord.core.behavior.createCategory +import dev.kord.core.entity.User +import dev.kord.core.entity.channel.Category +import dev.kord.core.entity.channel.TextChannel +import dev.kord.core.event.guild.MemberJoinEvent +import dev.kord.gateway.Intent +import dev.kord.gateway.PrivilegedIntent +import dev.kord.rest.builder.channel.addMemberOverwrite +import dev.kord.rest.builder.channel.addRoleOverwrite +import kotlinx.coroutines.flow.count +import quest.laxla.supertrouper.* + +@PrivilegedIntent +class PrivateMassagingExtension : TrouperExtension() { + override suspend fun setup() { + intents += Intent.GuildMembers + + slashCommandCheck { anyGuild(); isNotBot() } + userCommandCheck { anyGuild(); isNotBot() } + + event { + action { + if (event.member.isEligible && event.guild.members.count() < memberLimit) getOrCreateChannel( + getOrCreateCategory(event.guild), + event.member + ) + } + } + + ephemeralSlashCommand(::TargetedArguments) { + name = "pm" + description = "Get a link to a user's private messages channel" + + action { + executeFindCommand(getOrCreateCategory(guild!!), target.asUser()) + } + } + + ephemeralUserCommand { + name = "Private Message" + + action { + executeFindCommand(getOrCreateCategory(guild!!), targetUsers.single()) + } + } + + ephemeralSlashCommand(::TargetedArguments) slash@{ + name = "sync" + description = "Syncs a private message channel's permissions with the category." + + requirePermission(Permission.ManageRoles) + + action { + executeSyncCommand(getOrCreateCategory(guild!!), target.asUser()) + } + } + + ephemeralUserCommand { + name = "Sync PM Channel" + + requirePermission(Permission.ManageRoles) + + action { + executeSyncCommand(getOrCreateCategory(guild!!), user.asUser()) + } + } + } + + private suspend fun EphemeralInteractionContext.executeSyncCommand(category: Category, user: User) { + val channel = getChannel(category, user) + if (channel == null) { + respond { content = "${user.mention} does not have a private messaging channel in this server." } + return + } + + val userMention = user.mention + val channelMention = channel.mention + + channel.edit { + reason = "Syncing $userMention" + + sync( + overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), + overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), + defaults = category.permissionOverwrites + ) + } + + respond { + content = "Synced $channelMention for $userMention successfully." + } + } + + private suspend fun EphemeralInteractionContext.executeFindCommand(category: Category, user: User) { + if (user.isEligible) { + val channel = getOrCreateChannel(category, user) + respond { content = channel.mention } + } else respond { + content = user.mention + " is not eligible for private messaging." + } + } + + private suspend fun getOrCreateCategory(guild: GuildBehavior) = getCategory(guild) ?: createCategory(guild) + + private suspend fun createCategory(guild: GuildBehavior) = guild.createCategory(PrivateMessagesCategoryName) { + reason = "Private messaging category was missing." + nsfw = false + + addMemberOverwrite(kord.selfId) { + allowed += privateMessageBotPermissions + } + + addRoleOverwrite(guild.id) { + denied += Permission.ViewChannel + } + } + + private suspend fun getOrCreateChannel(category: Category, user: User) = + getChannel(category, user) ?: createChannel(category, user) + + private suspend fun createChannel(category: Category, user: User): TextChannel { + val mention = user.mention + + val channel = category.createTextChannel(user.username) { + reason = "Created a PM with $mention." + nsfw = category.data.nsfw.discordBoolean + topic = "This channel is $mention's private message." + + sync( + overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), + overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), + defaults = category.permissionOverwrites + ) + } + + return channel + } +} diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt new file mode 100644 index 0000000..2afb6b4 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt @@ -0,0 +1,32 @@ +package quest.laxla.supertrouper.messaging + +import com.kotlindiscord.kord.extensions.utils.any +import com.kotlindiscord.kord.extensions.utils.envOrNull +import dev.kord.common.entity.Permission +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.entity.User +import dev.kord.core.entity.channel.Category +import dev.kord.core.entity.channel.TextChannel +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.firstOrNull + +const val PrivateMessagesCategoryName = "Private Messages" +val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30 +val privateMessageOwnerPermissions = Permission.ViewChannel + Permission.ReadMessageHistory +val privateMessageBotPermissions = + privateMessageOwnerPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages + +suspend infix fun TextChannel.isOf(user: UserBehavior) = topic?.contains(user.mention) == true || pinnedMessages.any { + it.author?.id == kord.selfId && it.mentionedUserIds.singleOrNull() == user.id +} + +suspend fun getChannel(category: Category, user: User) = category.channels.filterIsInstance().firstOrNull { + it.categoryId == category.id && it isOf user +} + +suspend fun getCategory(guild: GuildBehavior) = + guild.channels.filterIsInstance().firstOrNull { it.isUsableForPrivateMessaging } + +val User.isEligible get() = !isBot +val Category.isUsableForPrivateMessaging get() = name.equals(PrivateMessagesCategoryName, ignoreCase = true) From 17dcdf7059174af4617b760c733ceefe11f76b42 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 6 Dec 2023 16:12:20 +0200 Subject: [PATCH 04/26] Updated some descriptions --- .../laxla/supertrouper/MaintenanceExtension.kt | 18 ++++++++++++------ .../messaging/PrivateMassagingExtension.kt | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt index 322e091..6222636 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt @@ -1,6 +1,7 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.checks.isBotAdmin +import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand import com.kotlindiscord.kord.extensions.extensions.slashCommandCheck @@ -11,13 +12,18 @@ class MaintenanceExtension : TrouperExtension() { } publicSlashCommand { - name = "stop" - description = "WARNING: Stops the bot completely." + name = "maintenance" + description = "Maintenance commands for maintainers of the bot" - action { - //language=Markdown - respond { content = "# Invoking Protocol: Emergency Stop" } - bot.stop() + publicSubCommand { + name = "stop" + description = "Stops the bot completely" + + action { + //language=Markdown + respond { content = "# Invoking Protocol: Emergency Stop" } + bot.stop() + } } } } diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt index 2a933ef..15d15dc 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt @@ -57,7 +57,7 @@ class PrivateMassagingExtension : TrouperExtension() { ephemeralSlashCommand(::TargetedArguments) slash@{ name = "sync" - description = "Syncs a private message channel's permissions with the category." + description = "Syncs a private message channel's permissions with the category" requirePermission(Permission.ManageRoles) From 991a0ca8eb3a7a839b76a46562886c5928ee13a6 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 13 Dec 2023 17:07:55 +0200 Subject: [PATCH 05/26] Almost final bot version --- build.gradle.kts | 2 +- .../laxla/supertrouper/AboutExtension.kt | 8 ++ .../kotlin/quest/laxla/supertrouper/App.kt | 5 +- .../supertrouper/MaintenanceExtension.kt | 20 ++--- .../quest/laxla/supertrouper/Overwrites.kt | 14 ++-- .../laxla/supertrouper/TargetedArguments.kt | 5 +- .../messaging/PrivateMassagingExtension.kt | 82 ++++++++++++++----- .../messaging/PrivateMessaging.kt | 58 +++++++++++-- 8 files changed, 145 insertions(+), 49 deletions(-) create mode 100644 src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt diff --git a/build.gradle.kts b/build.gradle.kts index ab1749f..59cfa0d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -36,7 +36,7 @@ val klogging: String by project dependencies { implementation(kotlin("stdlib")) implementation(kotlin("reflect")) - implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serialization") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$serialization") implementation("com.kotlindiscord.kord.extensions:kord-extensions:$kordex") diff --git a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt new file mode 100644 index 0000000..1761e11 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt @@ -0,0 +1,8 @@ +package quest.laxla.supertrouper + +class AboutExtension : TrouperExtension() { + override suspend fun setup() { + TODO("Not yet implemented") + } + +} diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/supertrouper/App.kt index 53e487c..8754092 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/App.kt @@ -8,12 +8,13 @@ import kotlinx.coroutines.runBlocking import quest.laxla.supertrouper.messaging.PrivateMassagingExtension private val token = env("TOKEN") -private val testingServer = envOrNull("TESTING_SERVER") +val officialServer = env("OFFICIAL_SERVER") +val isDevelopmentEnvironment = envOrNull("IS_DEV_ENV").toBoolean() fun main() = runBlocking { ExtensibleBot(token) { applicationCommands { - testingServer?.let { defaultGuild(it) } + if (isDevelopmentEnvironment) defaultGuild(officialServer) } extensions { diff --git a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt index 6222636..5290458 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt @@ -1,9 +1,9 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.checks.isBotAdmin -import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand import com.kotlindiscord.kord.extensions.extensions.slashCommandCheck +import dev.kord.common.entity.Snowflake class MaintenanceExtension : TrouperExtension() { override suspend fun setup() { @@ -12,18 +12,14 @@ class MaintenanceExtension : TrouperExtension() { } publicSlashCommand { - name = "maintenance" - description = "Maintenance commands for maintainers of the bot" + name = "stop" + description = "Stops the bot completely" + guildId = Snowflake(officialServer) - publicSubCommand { - name = "stop" - description = "Stops the bot completely" - - action { - //language=Markdown - respond { content = "# Invoking Protocol: Emergency Stop" } - bot.stop() - } + action { + //language=Markdown + respond { content = "# Invoking Protocol: Emergency Stop" } + bot.stop() } } } diff --git a/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt b/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt index d299df1..9ffff57 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt @@ -23,30 +23,32 @@ fun PermissionOverwritesBuilder.addOverwrite( fun PermissionOverwritesBuilder.sync( vararg overrides: Overwrite, - defaults: Iterable -) = sync(overrides.asIterable(), defaults) + defaults: Iterable, + neverAllow: Permissions = Permissions() +) = sync(overrides.asIterable(), defaults, neverAllow) fun PermissionOverwritesBuilder.sync( overrides: Iterable, - defaults: Iterable + defaults: Iterable, + neverAllow: Permissions = Permissions() ) { val permissions = mutableMapOf() defaults.forEach { default -> val override = overrides.find { it.id == default.target && it.type == default.type } - if (override == null) addOverwrite(default.target, default.type, default.allowed, default.denied) + if (override == null) addOverwrite(default.target, default.type, default.allowed - neverAllow, default.denied) else permissions[override] = default } overrides.forEach { override -> val default = permissions[override] - if (default == null) addOverwrite(override) + if (default == null) addOverwrite(override.copy(allow = override.allow - neverAllow)) else addOverwrite( default.target, default.type, - default.allowed - default.denied - override.deny + override.allow, + default.allowed - default.denied + override.allow - override.deny - neverAllow, default.denied - default.allowed - override.allow + override.deny ) } diff --git a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt index 1f2ef39..52a8fe5 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt @@ -2,6 +2,7 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext +import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalMember import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalUser import com.kotlindiscord.kord.extensions.components.forms.ModalForm @@ -9,11 +10,11 @@ private const val TargetArgumentName = "target" private const val TargetArgumentDescription = "Target of this command. Defaults to you." open class TargetedArguments : Arguments() { - val targetOrNull by optionalUser { + val targetOrNull by optionalMember { name = TargetArgumentName description = TargetArgumentDescription } } val C.target where C : SlashCommandContext<*, A, M>, A : TargetedArguments, M : ModalForm - get() = arguments.targetOrNull ?: user + get() = arguments.targetOrNull ?: member!! diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt index 15d15dc..365ebef 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt @@ -4,20 +4,32 @@ import com.kotlindiscord.kord.extensions.checks.anyGuild import com.kotlindiscord.kord.extensions.checks.isNotBot import com.kotlindiscord.kord.extensions.extensions.* import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext -import dev.kord.common.entity.OverwriteType -import dev.kord.common.entity.Permission +import com.kotlindiscord.kord.extensions.utils.any +import dev.kord.common.entity.* import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.behavior.channel.asChannelOfOrNull +import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.channel.createTextChannel import dev.kord.core.behavior.channel.edit import dev.kord.core.behavior.createCategory +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.entity.Member +import dev.kord.core.entity.Role import dev.kord.core.entity.User import dev.kord.core.entity.channel.Category import dev.kord.core.entity.channel.TextChannel import dev.kord.core.event.guild.MemberJoinEvent +import dev.kord.core.event.interaction.GuildButtonInteractionCreateEvent +import dev.kord.core.event.interaction.GuildSelectMenuInteractionCreateEvent import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent import dev.kord.rest.builder.channel.addMemberOverwrite import dev.kord.rest.builder.channel.addRoleOverwrite +import dev.kord.rest.builder.message.actionRow +import dev.kord.rest.builder.message.create.AbstractMessageCreateBuilder +import dev.kord.rest.builder.message.embed import kotlinx.coroutines.flow.count import quest.laxla.supertrouper.* @@ -31,10 +43,8 @@ class PrivateMassagingExtension : TrouperExtension() { event { action { - if (event.member.isEligible && event.guild.members.count() < memberLimit) getOrCreateChannel( - getOrCreateCategory(event.guild), - event.member - ) + if (event.member.isEligible && event.guild.members.count() < memberLimit) + getOrCreateChannel(getOrCreateCategory(event.guild), event.member) } } @@ -43,7 +53,7 @@ class PrivateMassagingExtension : TrouperExtension() { description = "Get a link to a user's private messages channel" action { - executeFindCommand(getOrCreateCategory(guild!!), target.asUser()) + executeFindCommand(getOrCreateCategory(guild!!), target.asUser(), user) } } @@ -51,11 +61,11 @@ class PrivateMassagingExtension : TrouperExtension() { name = "Private Message" action { - executeFindCommand(getOrCreateCategory(guild!!), targetUsers.single()) + executeFindCommand(getOrCreateCategory(guild!!), targetUsers.single(), user) } } - ephemeralSlashCommand(::TargetedArguments) slash@{ + ephemeralSlashCommand(::TargetedArguments) { name = "sync" description = "Syncs a private message channel's permissions with the category" @@ -75,6 +85,16 @@ class PrivateMassagingExtension : TrouperExtension() { executeSyncCommand(getOrCreateCategory(guild!!), user.asUser()) } } + + event { + action { + when (event.interaction.componentId) { + PingButton -> event.interaction.respondPublic { + executePingCommand(event.interaction.channel.asChannel(), event.interaction.user) + } + } + } + } } private suspend fun EphemeralInteractionContext.executeSyncCommand(category: Category, user: User) { @@ -88,12 +108,13 @@ class PrivateMassagingExtension : TrouperExtension() { val channelMention = channel.mention channel.edit { - reason = "Syncing $userMention" + reason = "Sync $channelMention with category for $userMention" sync( - overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), - overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), - defaults = category.permissionOverwrites + overwrite(kord.selfId, OverwriteType.Member, allowed = pmBotPermissions), + overwrite(user.id, OverwriteType.Member, allowed = pmMemberPermissions), + defaults = category.permissionOverwrites, + neverAllow = kord.getSelf().asMember(category.guildId).getDeniedPermissions() ) } @@ -102,10 +123,14 @@ class PrivateMassagingExtension : TrouperExtension() { } } - private suspend fun EphemeralInteractionContext.executeFindCommand(category: Category, user: User) { + private suspend fun EphemeralInteractionContext.executeFindCommand( + category: Category, user: User, searcher: UserBehavior = user + ) { if (user.isEligible) { val channel = getOrCreateChannel(category, user) respond { content = channel.mention } + + channel.ping(searcher) } else respond { content = user.mention + " is not eligible for private messaging." } @@ -118,7 +143,7 @@ class PrivateMassagingExtension : TrouperExtension() { nsfw = false addMemberOverwrite(kord.selfId) { - allowed += privateMessageBotPermissions + allowed += pmBotPermissions } addRoleOverwrite(guild.id) { @@ -135,15 +160,34 @@ class PrivateMassagingExtension : TrouperExtension() { val channel = category.createTextChannel(user.username) { reason = "Created a PM with $mention." nsfw = category.data.nsfw.discordBoolean - topic = "This channel is $mention's private message." + topic = "$mention's private messaging channel." sync( - overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), - overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), - defaults = category.permissionOverwrites + overwrite(kord.selfId, OverwriteType.Member, allowed = pmBotPermissions), + overwrite(user.id, OverwriteType.Member, allowed = pmMemberPermissions), + defaults = category.permissionOverwrites, + neverAllow = kord.getSelf().asMember(category.guildId).getDeniedPermissions() ) } + val avatar = (user.avatar ?: user.defaultAvatar).cdnUrl.toUrl() + + channel.createMessage { + embed { + description = "# $mention" + thumbnail { url = avatar } + } + + actionRow { + interactionButton(ButtonStyle.Primary, customId = PingButton) { + label = "Ping" + emoji = DiscordPartialEmoji(name = "\uD83D\uDD14") + } + } + } + + channel.ping(user) + return channel } } diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt index 2afb6b4..25f858c 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt @@ -1,25 +1,36 @@ package quest.laxla.supertrouper.messaging -import com.kotlindiscord.kord.extensions.utils.any import com.kotlindiscord.kord.extensions.utils.envOrNull +import dev.kord.common.entity.ALL import dev.kord.common.entity.Permission +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.UserBehavior +import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.entity.Member import dev.kord.core.entity.User import dev.kord.core.entity.channel.Category +import dev.kord.core.entity.channel.MessageChannel import dev.kord.core.entity.channel.TextChannel +import dev.kord.rest.builder.message.allowedMentions +import dev.kord.rest.builder.message.create.AbstractMessageCreateBuilder import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.firstOrNull const val PrivateMessagesCategoryName = "Private Messages" +const val InviteSelect = "PM.invite" +const val PingButton = "PM.ping" +const val ManagePmButton = "PM.manage" +const val UserIdCapturingGroup = "userID" val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30 -val privateMessageOwnerPermissions = Permission.ViewChannel + Permission.ReadMessageHistory -val privateMessageBotPermissions = - privateMessageOwnerPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages +val pmMemberPermissions = Permission.ViewChannel + Permission.ReadMessageHistory +val pmBotPermissions = + pmMemberPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages +val userMentionRegex = "<@(?<$UserIdCapturingGroup>[1-9][0-9]+)>".toRegex() -suspend infix fun TextChannel.isOf(user: UserBehavior) = topic?.contains(user.mention) == true || pinnedMessages.any { - it.author?.id == kord.selfId && it.mentionedUserIds.singleOrNull() == user.id -} +infix fun TextChannel.isOf(user: UserBehavior) = topic?.contains(user.mention) == true suspend fun getChannel(category: Category, user: User) = category.channels.filterIsInstance().firstOrNull { it.categoryId == category.id && it isOf user @@ -30,3 +41,36 @@ suspend fun getCategory(guild: GuildBehavior) = val User.isEligible get() = !isBot val Category.isUsableForPrivateMessaging get() = name.equals(PrivateMessagesCategoryName, ignoreCase = true) + +fun AbstractMessageCreateBuilder.executePingCommand( + channel: MessageChannel, + pinger: UserBehavior +) { + val owners = channel.owners?.toList() + + if (owners == null) { + allowedMentions() + + content = "The owner of this channel is unknown. They need to be mentioned in the channel's topic, " + + "Like this: `<@userID>`." + } else { + allowedMentions { + users.addAll(owners.asSequence().map { + Snowflake(it.groups[UserIdCapturingGroup]!!.value) + }) + } + + content = "Hey, " + owners.joinToString(separator = " ") { + it.value + } + ", y'all were pinged by " + pinger.mention + '!' + } +} + +suspend fun MessageChannelBehavior.ping(user: UserBehavior) = createMessage { + allowedMentions { users.add(user.id) } + content = user.mention +}.delete(reason = "Ghost pinged " + user.mention) + +val MessageChannel.owners get() = data.topic.value?.let { userMentionRegex.findAll(it) } + +suspend fun Member.getDeniedPermissions() = Permissions.ALL - getPermissions() From 3a7eea713b0239995881142d0e561fa7c8c6c985 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 13 Dec 2023 18:46:58 +0200 Subject: [PATCH 06/26] Added `/about` --- .idea/inspectionProfiles/Project_Default.xml | 6 + LICENSE.md | 356 ++++++++++++++++++ .../laxla/supertrouper/AboutExtension.kt | 117 +++++- .../kotlin/quest/laxla/supertrouper/App.kt | 6 + .../laxla/supertrouper/TargetedArguments.kt | 1 - .../kotlin/quest/laxla/supertrouper/Utils.kt | 27 ++ .../messaging/PrivateMassagingExtension.kt | 12 +- .../messaging/PrivateMessaging.kt | 2 - 8 files changed, 515 insertions(+), 12 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 LICENSE.md create mode 100644 src/main/kotlin/quest/laxla/supertrouper/Utils.kt diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..10b773b --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..bfa8437 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,356 @@ +Mozilla Public License Version 2.0 +================================== + +### 1. Definitions + +**1.1. “Contributor”** +means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software. + +**1.2. “Contributor Version”** +means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor's Contribution. + +**1.3. “Contribution”** +means Covered Software of a particular Contributor. + +**1.4. “Covered Software”** +means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof. + +**1.5. “Incompatible With Secondary Licenses”** +means + +* **(a)** that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or +* **(b)** that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +**1.6. “Executable Form”** +means any form of the work other than Source Code Form. + +**1.7. “Larger Work”** +means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software. + +**1.8. “License”** +means this document. + +**1.9. “Licensable”** +means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + +**1.10. “Modifications”** +means any of the following: + +* **(a)** any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or +* **(b)** any new file in Source Code Form that contains any Covered + Software. + +**1.11. “Patent Claims” of a Contributor** +means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version. + +**1.12. “Secondary License”** +means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses. + +**1.13. “Source Code Form”** +means the form of the work preferred for making modifications. + +**1.14. “You” (or “Your”)** +means an individual or a legal entity exercising rights under this +License. For legal entities, “You” includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, “control” means **(a)** the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or **(b)** ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + + +### 2. License Grants and Conditions + +#### 2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +* **(a)** under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and +* **(b)** under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +#### 2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +#### 2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +* **(a)** for any code that a Contributor has removed from Covered Software; + or +* **(b)** for infringements caused by: **(i)** Your and any other third party's + modifications of Covered Software, or **(ii)** the combination of its + Contributions with other software (except as part of its Contributor + Version); or +* **(c)** under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +#### 2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +#### 2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +#### 2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +#### 2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + + +### 3. Responsibilities + +#### 3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +#### 3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +* **(a)** such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +* **(b)** You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +#### 3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +#### 3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +#### 3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + + +### 4. Inability to Comply Due to Statute or Regulation + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: **(a)** comply with +the terms of this License to the maximum extent possible; and **(b)** +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + + +### 5. Termination + +**5.1.** The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated **(a)** provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and **(b)** on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +**5.2.** If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + + +### 6. Disclaimer of Warranty + +> Covered Software is provided under this License on an “as is” +> basis, without warranty of any kind, either expressed, implied, or +> statutory, including, without limitation, warranties that the +> Covered Software is free of defects, merchantable, fit for a +> particular purpose or non-infringing. The entire risk as to the +> quality and performance of the Covered Software is with You. +> Should any Covered Software prove defective in any respect, You +> (not any Contributor) assume the cost of any necessary servicing, +> repair, or correction. This disclaimer of warranty constitutes an +> essential part of this License. No use of any Covered Software is +> authorized under this License except under this disclaimer. + +### 7. Limitation of Liability + +> Under no circumstances and under no legal theory, whether tort +> (including negligence), contract, or otherwise, shall any +> Contributor, or anyone who distributes Covered Software as +> permitted above, be liable to You for any direct, indirect, +> special, incidental, or consequential damages of any character +> including, without limitation, damages for lost profits, loss of +> goodwill, work stoppage, computer failure or malfunction, or any +> and all other commercial damages or losses, even if such party +> shall have been informed of the possibility of such damages. This +> limitation of liability shall not apply to liability for death or +> personal injury resulting from such party's negligence to the +> extent applicable law prohibits such limitation. Some +> jurisdictions do not allow the exclusion or limitation of +> incidental or consequential damages, so this exclusion and +> limitation may not apply to You. + + +### 8. Litigation + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + + +### 9. Miscellaneous + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + + +### 10. Versions of the License + +#### 10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +#### 10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +#### 10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +#### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +## Exhibit A - Source Code Form License Notice + + 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 http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +## Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + diff --git a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt index 1761e11..d44b39d 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt @@ -1,8 +1,123 @@ package quest.laxla.supertrouper +import com.kotlindiscord.kord.extensions.components.components +import com.kotlindiscord.kord.extensions.components.disabledButton +import com.kotlindiscord.kord.extensions.components.linkButton +import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand +import dev.kord.common.entity.ButtonStyle +import dev.kord.core.entity.effectiveName +import dev.kord.rest.builder.message.allowedMentions +import dev.kord.rest.builder.message.create.AbstractMessageCreateBuilder +import dev.kord.rest.builder.message.embed +import quest.laxla.supertrouper.messaging.PrivateMessagesCategoryName + class AboutExtension : TrouperExtension() { override suspend fun setup() { - TODO("Not yet implemented") + ephemeralSlashCommand { + name = "about" + description = "About Super Trouper" + + action { + respond { + about() + } + } + } } + + suspend fun AbstractMessageCreateBuilder.about() { + val self = kord.getSelf() + val avatar = (self.avatar ?: self.defaultAvatar).cdnUrl.toUrl() + val mention = self.mention + allowedMentions() + + embed { + title = "About ${self.effectiveName}" + thumbnail { url = avatar } + + if (isDevelopmentEnvironment) field { + name = "Development Environment" + //language=Markdown + value = "> This instance is hosted on someone's personal computer, " + + "and *may* contain **malicious code** and/or **steal your data**. " + + "This is not considered to be an official version of Super Trouper. " + + "Do *not* rely on the lack of this message to determine if an instance is official." + } + + //language=Markdown + description = "$mention is an *open-source* bot made by the plural community, for the plural community.\n\n" + + "$mention creates a private channel for members, " + + "allowing them to talk to the server's owners or moderators. " + + "Use `/pm` to get a link to your PM channel. " + + "PM channels inherit their permissions from the `$PrivateMessagesCategoryName` category, " + + "and can be synced by moderators (`Manage Permissions` is required by default).\n\n" + + "$mention is free, open source software. You can host the bot on your own server, " + + "without paying us a penny. We know not everyone can afford that, so we host it for you, " + + "using our own money. Please, help us making $mention available for everyone, everywhere, for free." + } + + components { + val app = kord.getApplicationInfo() + + disabledButton { + style = ButtonStyle.Primary + label = "About" + } + + app.inviteUrl?.let { + linkButton { + url = it + label = "Invite" + } + } + + officialServerUrl?.let { + linkButton { + url = it + label = "Join" + } + } + + donateUrl?.let { + linkButton { + url = it + label = "Donate" + } + } + + repoUrl?.let { + linkButton { + url = it + label = "Contribute" + } + } + + disabledButton(row = 1) { + style = ButtonStyle.Primary + label = "Legal" + } + + licenseUrl?.let { + linkButton(row = 1) { + url = it + label = license ?: "License" + } + } + + app.privacyPolicyUrl?.let { + linkButton(row = 1) { + url = it + label = "Privacy Policy" + } + } + + app.termsOfServiceUrl?.let { + linkButton(row = 1) { + url = it + label = "Terms of Service" + } + } + } + } } diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/supertrouper/App.kt index 8754092..fa21331 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/App.kt @@ -10,6 +10,11 @@ import quest.laxla.supertrouper.messaging.PrivateMassagingExtension private val token = env("TOKEN") val officialServer = env("OFFICIAL_SERVER") val isDevelopmentEnvironment = envOrNull("IS_DEV_ENV").toBoolean() +val officialServerUrl = envOrNull("OFFICIAL_SERVER_URL") +val license = envOrNull("LICENSE") +val licenseUrl = envOrNull("LICENSE_URL") +val donateUrl = envOrNull("DONATE_URL") +val repoUrl = envOrNull("REPO_URL") fun main() = runBlocking { ExtensibleBot(token) { @@ -21,6 +26,7 @@ fun main() = runBlocking { add(::MaintenanceExtension) @OptIn(PrivilegedIntent::class) add(::PrivateMassagingExtension) + add(::AboutExtension) } }.start() } diff --git a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt index 52a8fe5..55325df 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt @@ -3,7 +3,6 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalMember -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalUser import com.kotlindiscord.kord.extensions.components.forms.ModalForm private const val TargetArgumentName = "target" diff --git a/src/main/kotlin/quest/laxla/supertrouper/Utils.kt b/src/main/kotlin/quest/laxla/supertrouper/Utils.kt new file mode 100644 index 0000000..a898daa --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/Utils.kt @@ -0,0 +1,27 @@ +package quest.laxla.supertrouper + +import dev.kord.core.entity.Application +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.text.buildString as buildStringKt + +@OptIn(ExperimentalContracts::class) +inline fun T.buildString(capacity: Int? = null, builderAction: StringBuilder.(T) -> Unit): String { + contract { + callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) + } + + return if (capacity == null) buildStringKt { builderAction(this@buildString) } + else buildStringKt(capacity) { builderAction(this@buildString) } +} + + +val Application.inviteUrl get() = customInstallUrl ?: installParams?.buildString { + append("https://discord.com/api/oauth2/authorize?client_id=") + append(id) + append("&permissions=") + append(it.permissions.code.value) + append("&scope=") + append(it.scopes.joinToString(separator = "+")) +} diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt index 365ebef..d920067 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt @@ -4,31 +4,27 @@ import com.kotlindiscord.kord.extensions.checks.anyGuild import com.kotlindiscord.kord.extensions.checks.isNotBot import com.kotlindiscord.kord.extensions.extensions.* import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext -import com.kotlindiscord.kord.extensions.utils.any -import dev.kord.common.entity.* +import dev.kord.common.entity.ButtonStyle +import dev.kord.common.entity.DiscordPartialEmoji +import dev.kord.common.entity.OverwriteType +import dev.kord.common.entity.Permission import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.UserBehavior -import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.channel.createTextChannel import dev.kord.core.behavior.channel.edit import dev.kord.core.behavior.createCategory -import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic -import dev.kord.core.entity.Member -import dev.kord.core.entity.Role import dev.kord.core.entity.User import dev.kord.core.entity.channel.Category import dev.kord.core.entity.channel.TextChannel import dev.kord.core.event.guild.MemberJoinEvent import dev.kord.core.event.interaction.GuildButtonInteractionCreateEvent -import dev.kord.core.event.interaction.GuildSelectMenuInteractionCreateEvent import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent import dev.kord.rest.builder.channel.addMemberOverwrite import dev.kord.rest.builder.channel.addRoleOverwrite import dev.kord.rest.builder.message.actionRow -import dev.kord.rest.builder.message.create.AbstractMessageCreateBuilder import dev.kord.rest.builder.message.embed import kotlinx.coroutines.flow.count import quest.laxla.supertrouper.* diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt index 25f858c..65438a5 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt @@ -20,9 +20,7 @@ import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.firstOrNull const val PrivateMessagesCategoryName = "Private Messages" -const val InviteSelect = "PM.invite" const val PingButton = "PM.ping" -const val ManagePmButton = "PM.manage" const val UserIdCapturingGroup = "userID" val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30 val pmMemberPermissions = Permission.ViewChannel + Permission.ReadMessageHistory From a348529ef2123e614124efd300fe54d3beb14c04 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 13 Dec 2023 18:59:36 +0200 Subject: [PATCH 07/26] Created README.md, removed useless gradle plugins. --- README.md | 69 +--- build.gradle.kts | 10 - detekt.yml | 679 -------------------------------------- gradle.properties | 2 - gradle/libs.versions.toml | 22 -- settings.gradle.kts | 7 - 6 files changed, 16 insertions(+), 773 deletions(-) delete mode 100644 detekt.yml delete mode 100644 gradle/libs.versions.toml diff --git a/README.md b/README.md index 107d1ca..2667a3b 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,17 @@ -# KordEx Bot Template - -This repository contains a basic KordEx example bot for you to use as a template for your own KordEx bots. This -includes the following: - -* A basic extension that allows you to slap other people, using both message commands and slash commands. -* A basic bot configuration that enables slash commands and shows you how to conditionally provide a different - message command prefix for different guilds. -* A Gradle Kotlin build script that uses the Kotlin Discord public maven repo, Detekt for linting (with a - fairly strict configuration) and a Git commit hook plugin that runs Detekt when you make a commit - this uses Gradle - 7's new version catalog feature, for easy configuration of dependencies. -* GitHub CI scripts that build the bot and publish its artefacts. -* A reasonable `.gitignore` file, including one in the `.idea` folder that ignores files that you shouldn't commit - - if you're using IDEA yourself, you should install the Ignore plugin to handle changes to this for you. -* A Groovy-based Logback config, so you've reasonable logging out of the box. - -**Note:** This template includes a `.editorconfig` file that defaults to using tabs for indentation in almost all file -types. This is because tabs are more accessible for the blind, or those with impaired vision. We won't accept -feedback or PRs targeting this approach. - -## Potential Changes - -* The `.yml` files in `.github/` are used to configure GitHub apps. If you're not using them, you can remove them. -* The provided `LICENSE` file contains The Unlicense, which makes this repository public domain. You will probably want - to change this - we suggest looking at [Choose a License](https://choosealicense.com/) if you're not sure where to start. -* In the `build.gradle.kts`: - * Set the `group` and `version` properties as appropriate. - * If you're not using this to test KordEx builds, you can remove the `mavenLocal()` from the `repositories` block. - * In the `application` and `tasks.jar` blocks, update the main class path/name as appropriate. - * To target a newer/older Java version, change the options in the `KotlinCompile` configuration and `java` blocks -* In the `settings.gradle.kts`, update the name of the root project as appropriate. -* The bundled Detekt config is pretty strict - you can check over `detekt.yml` if you want to change it, but you need to - follow the TODOs in that file regardless. -* The Logback configuration is in `src/main/resources/logback.groovy`. If the logging setup doesn't suit, you can change - it there. - -## Bundled Bot - -* `App.kt` includes a basic bot, which uses environment variables (or variables in a `.env` file) for the testing guild - ID (`TEST_SERVER`) and the bot's token (`TOKEN`). You can specify these either directly as environment variables, or - as `KEY=value` pairs in a file named `.env`. Some example code is also included that shows one potential way of - providing different command prefixes for different servers. -* `TestExtension.kt` includes an example extension that creates a `slap` command - this command works as both a - message command and slash command, and allows you to slap other users with whatever you wish, defaulting to a - `large, smelly trout`. - -To test the bot, we recommend using a `.env` file that looks like the following: - -```dotenv -TOKEN=abc... -TEST_SERVER=123... +# Super Trouper + +## Running + +Create a `.env` file before running the bot. + +```properties +TOKEN= +AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT=30 +IS_DEV_ENV=true +DONATE_URL= +LICENSE=Mozilla Public License 2.0 +LICENSE_URL=https://www.mozilla.org/en-US/MPL/2.0/ +OFFICIAL_SERVER=1130171551636004864 +OFFICIAL_SERVER_URL=https://discord.gg/xJu6MH2KUc +REPO_URL=https://github.com/LaylaMeower/SuperTrouper ``` - -Create this file, fill it out, and run the `run` gradle task for testing in development. diff --git a/build.gradle.kts b/build.gradle.kts index 59cfa0d..ff6f6d0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,6 @@ plugins { kotlin("plugin.serialization") id("com.github.johnrengelman.shadow") - id("io.gitlab.arturbosch.detekt") } group = "quest.laxla" @@ -26,7 +25,6 @@ repositories { } } -val detekt: String by project val kordex: String by project val serialization: String by project val logback: String by project @@ -43,8 +41,6 @@ dependencies { implementation("io.github.oshai:kotlin-logging:$klogging") runtimeOnly("org.slf4j:slf4j-api:$slf4j") runtimeOnly("ch.qos.logback:logback-classic:$logback") - - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detekt") } application { @@ -68,9 +64,3 @@ java { sourceCompatibility = java targetCompatibility = java } - -detekt { - buildUponDefaultConfig = true - - config.from(rootProject.files("detekt.yml")) -} diff --git a/detekt.yml b/detekt.yml deleted file mode 100644 index e3ddb48..0000000 --- a/detekt.yml +++ /dev/null @@ -1,679 +0,0 @@ -# TODO: Update `rootPackage` in naming -> InvalidPackageDeclaration - -build: - maxIssues: 0 - excludeCorrectable: false - weights: - # complexity: 2 - # LongParameterList: 1 - # style: 1 - # comments: 1 - -config: - validation: true - # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' - excludes: '' - -processors: - active: true - exclude: - - 'DetektProgressListener' - - 'FunctionCountProcessor' - - 'PropertyCountProcessor' - - 'ClassCountProcessor' - - 'PackageCountProcessor' - - 'KtFileCountProcessor' - -console-reports: - active: true - exclude: - - 'ProjectStatisticsReport' - - 'NotificationReport' - - 'FileBasedFindingsReport' - -output-reports: - active: true - exclude: - # - 'HtmlOutputReport' - - 'TxtOutputReport' - # - 'XmlOutputReport' - -comments: - active: true - - AbsentOrWrongFileLicense: - active: false - licenseTemplateFile: 'license.template' - CommentOverPrivateFunction: - active: false - CommentOverPrivateProperty: - active: false - EndOfSentenceFormat: - active: true - endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - UndocumentedPublicClass: - active: false - searchInNestedClass: true - searchInInnerClass: true - searchInInnerObject: true - searchInInnerInterface: true - UndocumentedPublicFunction: - active: false - UndocumentedPublicProperty: - active: false - -complexity: - active: true - ComplexCondition: - active: true - threshold: 10 - ComplexInterface: - active: false - threshold: 10 - includeStaticDeclarations: false - includePrivateDeclarations: false - ComplexMethod: - active: false - threshold: 15 - ignoreSingleWhenExpression: false - ignoreSimpleWhenEntries: false - ignoreNestingFunctions: false - nestingFunctions: [ run, let, apply, with, also, use, forEach, isNotNull, ifNull ] - LabeledExpression: - active: false - ignoredLabels: [ ] - LargeClass: - active: false - threshold: 600 - LongMethod: - active: false - threshold: 60 - LongParameterList: - active: false - functionThreshold: 6 - constructorThreshold: 7 - ignoreDefaultParameters: false - ignoreDataClasses: true - ignoreAnnotated: [ ] - MethodOverloading: - active: false - threshold: 6 - NestedBlockDepth: - active: false - threshold: 4 - ReplaceSafeCallChainWithRun: - active: true - StringLiteralDuplication: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - threshold: 3 - ignoreAnnotation: true - excludeStringsWithLessThan5Characters: true - ignoreStringsRegex: '$^' - TooManyFunctions: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - thresholdInFiles: 11 - thresholdInClasses: 11 - thresholdInInterfaces: 11 - thresholdInObjects: 11 - thresholdInEnums: 11 - ignoreDeprecated: false - ignorePrivate: false - ignoreOverridden: false - -coroutines: - active: true - GlobalCoroutineUsage: - active: true - RedundantSuspendModifier: - active: true - SuspendFunWithFlowReturnType: - active: true - -empty-blocks: - active: true - EmptyCatchBlock: - active: true - allowedExceptionNameRegex: '^(_|(ignore|expected).*)' - EmptyClassBlock: - active: true - EmptyDefaultConstructor: - active: true - EmptyDoWhileBlock: - active: true - EmptyElseBlock: - active: true - EmptyFinallyBlock: - active: true - EmptyForBlock: - active: true - EmptyFunctionBlock: - active: true - ignoreOverridden: false - EmptyIfBlock: - active: true - EmptyInitBlock: - active: true - EmptyKtFile: - active: true - EmptySecondaryConstructor: - active: true - EmptyTryBlock: - active: true - EmptyWhenBlock: - active: true - EmptyWhileBlock: - active: true - -exceptions: - active: true - ExceptionRaisedInUnexpectedLocation: - active: true - methodNames: [ toString, hashCode, equals, finalize ] - InstanceOfCheckForException: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - NotImplementedDeclaration: - active: false - PrintStackTrace: - active: true - RethrowCaughtException: - active: true - ReturnFromFinally: - active: true - ignoreLabeled: false - SwallowedException: - active: false - ignoredExceptionTypes: - - InterruptedException - - NumberFormatException - - ParseException - - MalformedURLException - allowedExceptionNameRegex: '^(_|(ignore|expected).*)' - ThrowingExceptionFromFinally: - active: true - ThrowingExceptionInMain: - active: true - ThrowingExceptionsWithoutMessageOrCause: - active: true - exceptions: - - IllegalArgumentException - - IllegalStateException - - IOException - ThrowingNewInstanceOfSameException: - active: true - TooGenericExceptionCaught: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - exceptionNames: - - ArrayIndexOutOfBoundsException - - Error - - Exception - - IllegalMonitorStateException - - NullPointerException - - IndexOutOfBoundsException - - RuntimeException - - Throwable - allowedExceptionNameRegex: '^(_|(ignore|expected).*)' - TooGenericExceptionThrown: - active: true - exceptionNames: - - Error - - Exception - - Throwable - - RuntimeException - -formatting: - active: true - android: false - autoCorrect: true - AnnotationOnSeparateLine: - active: true - autoCorrect: true - AnnotationSpacing: - active: true - autoCorrect: true - ArgumentListWrapping: - active: false # It's wrong! - autoCorrect: true - ChainWrapping: - active: true - autoCorrect: true - CommentSpacing: - active: true - autoCorrect: true - EnumEntryNameCase: - active: true - autoCorrect: true - Filename: - active: true - FinalNewline: - active: true - autoCorrect: true - insertFinalNewLine: true - ImportOrdering: - active: true - autoCorrect: true - layout: "*,java.**,javax.**,kotlin.**,^" - Indentation: - active: false - autoCorrect: false - indentSize: 4 - continuationIndentSize: 4 - MaximumLineLength: - active: true - maxLineLength: 120 - ModifierOrdering: - active: true - autoCorrect: true - MultiLineIfElse: - active: true - autoCorrect: true - NoBlankLineBeforeRbrace: - active: true - autoCorrect: true - NoConsecutiveBlankLines: - active: true - autoCorrect: true - NoEmptyClassBody: - active: true - autoCorrect: true - NoEmptyFirstLineInMethodBlock: - active: true - autoCorrect: true - NoLineBreakAfterElse: - active: true - autoCorrect: true - NoLineBreakBeforeAssignment: - active: true - autoCorrect: true - NoMultipleSpaces: - active: false - autoCorrect: false - NoSemicolons: - active: true - autoCorrect: true - NoTrailingSpaces: - active: true - autoCorrect: true - NoUnitReturn: - active: true - autoCorrect: true - NoUnusedImports: - active: true - autoCorrect: true - NoWildcardImports: - active: false - PackageName: - active: true - autoCorrect: true - ParameterListWrapping: - active: true - autoCorrect: true - indentSize: 4 - SpacingAroundColon: - active: true - autoCorrect: true - SpacingAroundComma: - active: true - autoCorrect: true - SpacingAroundCurly: - active: true - autoCorrect: true - SpacingAroundDot: - active: true - autoCorrect: true - SpacingAroundDoubleColon: - active: true - autoCorrect: true - SpacingAroundKeyword: - active: true - autoCorrect: true - SpacingAroundOperators: - active: true - autoCorrect: true - SpacingAroundParens: - active: true - autoCorrect: true - SpacingAroundRangeOperator: - active: true - autoCorrect: true - SpacingBetweenDeclarationsWithAnnotations: - active: true - autoCorrect: true - SpacingBetweenDeclarationsWithComments: - active: true - autoCorrect: true - StringTemplate: - active: true - autoCorrect: true - -naming: - active: true - ClassNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - classPattern: '[A-Z$][a-zA-Z0-9$]*' - ConstructorParameterNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - parameterPattern: '[a-z][A-Za-z0-9]*' - privateParameterPattern: '[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - ignoreOverridden: true - EnumNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' - ForbiddenClassName: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - forbiddenName: [ ] - FunctionMaxLength: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - maximumFunctionNameLength: 30 - FunctionMinLength: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - minimumFunctionNameLength: 3 - FunctionNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' - excludeClassPattern: '$^' - ignoreOverridden: true - FunctionParameterNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - parameterPattern: '[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - ignoreOverridden: true - - InvalidPackageDeclaration: - active: true - # TODO: Update this with your project's base package - rootPackage: 'template' - - MatchingDeclarationName: - active: true - mustBeFirst: true - MemberNameEqualsClassName: - active: true - ignoreOverridden: true - NonBooleanPropertyPrefixedWithIs: - active: true - ObjectPropertyNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - constantPattern: '[A-Za-z][_A-Za-z0-9]*' - propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' - PackageNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$' - TopLevelPropertyNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - constantPattern: '[A-Z][_A-Z0-9]*' - propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' - VariableMaxLength: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - maximumVariableNameLength: 64 - VariableMinLength: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - minimumVariableNameLength: 1 - VariableNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - variablePattern: '[a-z][A-Za-z0-9]*' - privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - ignoreOverridden: true - -performance: - active: true - ArrayPrimitive: - active: true - ForEachOnRange: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - SpreadOperator: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - UnnecessaryTemporaryInstantiation: - active: true - -potential-bugs: - active: true - Deprecation: - active: true - DuplicateCaseInWhenExpression: - active: true - EqualsAlwaysReturnsTrueOrFalse: - active: true - EqualsWithHashCodeExist: - active: true - ExplicitGarbageCollectionCall: - active: true - HasPlatformType: - active: true - IgnoredReturnValue: - active: true - ImplicitDefaultLocale: - active: false - ImplicitUnitReturnType: - active: true - allowExplicitReturnType: true - InvalidRange: - active: true - IteratorHasNextCallsNextMethod: - active: true - IteratorNotThrowingNoSuchElementException: - active: true - LateinitUsage: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - excludeAnnotatedProperties: [ ] - ignoreOnClassesPattern: '' - MapGetWithNotNullAssertionOperator: - active: true - MissingWhenCase: - active: true - NullableToStringCall: - active: true - RedundantElseInWhen: - active: true - UnconditionalJumpStatementInLoop: - active: true - UnnecessaryNotNullOperator: - active: true - UnnecessarySafeCall: - active: true - UnreachableCode: - active: true - UnsafeCallOnNullableType: - active: true - UnsafeCast: - active: true - UselessPostfixExpression: - active: true - WrongEqualsTypeParameter: - active: true - -style: - active: true - ClassOrdering: - active: true - CollapsibleIfStatements: - active: true - DataClassContainsFunctions: - active: true - conversionFunctionPrefix: 'to' - DataClassShouldBeImmutable: - active: true - EqualsNullCall: - active: true - EqualsOnSignatureLine: - active: true - ExplicitCollectionElementAccessMethod: - active: true - ExplicitItLambdaParameter: - active: true - ExpressionBodySyntax: - active: true - includeLineWrapping: false - ForbiddenComment: - active: false - values: [ 'TODO:', 'FIXME:', 'STOPSHIP:' ] - allowedPatterns: '' - ForbiddenImport: - active: false - imports: [ ] - forbiddenPatterns: '' - ForbiddenMethodCall: - active: false - methods: [ ] - ForbiddenPublicDataClass: - active: false - ignorePackages: [ '*.internal', '*.internal.*' ] - ForbiddenVoid: - active: true - ignoreOverridden: true - ignoreUsageInGenerics: false - FunctionOnlyReturningConstant: - active: true - ignoreOverridableFunction: true - excludedFunctions: 'describeContents' - excludeAnnotatedFunction: [ 'dagger.Provides' ] - LibraryCodeMustSpecifyReturnType: - active: true - LibraryEntitiesShouldNotBePublic: - active: true - LoopWithTooManyJumpStatements: - active: true - maxJumpCount: 3 - MagicNumber: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - ignoreNumbers: [ '-1', '0', '1', '2' ] - ignoreHashCodeFunction: true - ignorePropertyDeclaration: false - ignoreLocalVariableDeclaration: false - ignoreConstantDeclaration: true - ignoreCompanionObjectPropertyDeclaration: true - ignoreAnnotation: true - ignoreNamedArgument: true - ignoreEnums: true - ignoreRanges: false - MandatoryBracesIfStatements: - active: true - MandatoryBracesLoops: - active: true - MaxLineLength: - active: true - maxLineLength: 120 - excludePackageStatements: true - excludeImportStatements: true - excludeCommentStatements: false - MayBeConst: - active: true - ModifierOrder: - active: true - NestedClassesVisibility: - active: true - NewLineAtEndOfFile: - active: true - NoTabs: - active: false - OptionalAbstractKeyword: - active: true - OptionalUnit: - active: false - OptionalWhenBraces: - active: true - PreferToOverPairSyntax: - active: true - ProtectedMemberInFinalClass: - active: true - RedundantExplicitType: - active: true - RedundantVisibilityModifierRule: - active: false - ReturnCount: - active: false - max: 2 - excludedFunctions: 'equals' - excludeLabeled: false - excludeReturnFromLambda: true - excludeGuardClauses: false - SafeCast: - active: true - SerialVersionUIDInSerializableClass: - active: true - SpacingBetweenPackageAndImports: - active: true - ThrowsCount: - active: false - max: 2 - TrailingWhitespace: - active: true - UnderscoresInNumericLiterals: - active: true - acceptableDecimalLength: 5 - UnnecessaryAbstractClass: - active: true - excludeAnnotatedClasses: [ 'dagger.Module' ] - UnnecessaryAnnotationUseSiteTarget: - active: true - UnnecessaryApply: - active: true - UnnecessaryInheritance: - active: true - UnnecessaryLet: - active: true - UnnecessaryParentheses: - active: true - UntilInsteadOfRangeTo: - active: true - UnusedImports: - active: true - UnusedPrivateClass: - active: true - UnusedPrivateMember: - active: true - allowedNames: '(_|ignored|expected|serialVersionUID)' - UseArrayLiteralsInAnnotations: - active: true - UseCheckNotNull: - active: true - UseCheckOrError: - active: true - UseDataClass: - active: true - excludeAnnotatedClasses: [ ] - allowVars: false - UseEmptyCounterpart: - active: true - UseIfInsteadOfWhen: - active: true - UseRequire: - active: true - UseRequireNotNull: - active: true - UselessCallOnNotNull: - active: true - UtilityClassWithPublicConstructor: - active: true - VarCouldBeVal: - active: true - WildcardImport: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - excludeImports: [ 'java.util.*', 'kotlinx.android.synthetic.*' ] diff --git a/gradle.properties b/gradle.properties index e3d93cd..06fa7f9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,9 +5,7 @@ kotlin.incremental=true org.gradle.kotlin.dsl.skipMetadataVersionCheck=false kotlin=1.9.21 -detekt=1.23.4 shadow=8.1.1 -hooks=0.0.2 jvm=17 kordex=1.6.0 serialization=1.6.2 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index e565e09..0000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,22 +0,0 @@ -[versions] -detekt = "1.23.1" # Note: Plugin versions must be updated in the settings.gradle.kts too -kotlin = "1.9.0" # Note: Plugin versions must be updated in the settings.gradle.kts too - -groovy = "3.0.14" -jansi = "2.4.0" -kord-extensions = "1.5.9-SNAPSHOT" -kx-ser = "1.5.1" -logging = "3.0.5" -logback = "1.4.5" -logback-groovy = "1.14.4" - -[libraries] -detekt = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } -groovy = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" } -jansi = { module = "org.fusesource.jansi:jansi", version.ref = "jansi" } -kord-extensions = { module = "com.kotlindiscord.kord.extensions:kord-extensions", version.ref = "kord-extensions" } -kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } -kx-ser = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kx-ser" } -logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } -logback-groovy = { module = "io.github.virtualdogbert:logback-groovy-config", version.ref = "logback-groovy" } -logging = { module = "io.github.microutils:kotlin-logging", version.ref = "logging" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 36d6c28..929c86d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,18 +1,11 @@ pluginManagement { val kotlin: String by settings - val detekt: String by settings val shadow: String by settings - val hooks: String by settings plugins { - // Update this in libs.version.toml when you change it here. kotlin("jvm") version kotlin kotlin("plugin.serialization") version kotlin - // Update this in libs.version.toml when you change it here. - id("io.gitlab.arturbosch.detekt") version detekt - - id("com.github.jakemarsden.git-hooks") version hooks id("com.github.johnrengelman.shadow") version shadow } } From 1ec2da8907720fcae3dd2301e1179f773aaa9330 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Thu, 14 Dec 2023 16:29:15 +0200 Subject: [PATCH 08/26] Renamed bot to Trouper --- .github/workflows/ci.yml | 13 ++++++++----- .github/workflows/develop.yml | 13 ++++++++----- .github/workflows/root.yml | 13 ++++++++----- .gitignore | 1 + .idea/.name | 1 - build.gradle.kts | 2 +- settings.gradle.kts | 2 +- .../{supertrouper => trouper}/AboutExtension.kt | 7 +++---- .../quest/laxla/{supertrouper => trouper}/App.kt | 4 ++-- .../MaintenanceExtension.kt | 2 +- .../laxla/{supertrouper => trouper}/Overwrites.kt | 2 +- .../{supertrouper => trouper}/TargetedArguments.kt | 2 +- .../{supertrouper => trouper}/TrouperExtension.kt | 2 +- .../quest/laxla/{supertrouper => trouper}/Utils.kt | 2 +- .../messaging/PrivateMassagingExtension.kt | 4 ++-- .../messaging/PrivateMessaging.kt | 2 +- 16 files changed, 40 insertions(+), 32 deletions(-) delete mode 100644 .idea/.name rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/AboutExtension.kt (94%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/App.kt (89%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/MaintenanceExtension.kt (94%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/Overwrites.kt (98%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/TargetedArguments.kt (95%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/TrouperExtension.kt (91%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/Utils.kt (96%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/messaging/PrivateMassagingExtension.kt (98%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/messaging/PrivateMessaging.kt (98%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d521601..d13c2bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,13 +13,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: - java-version: 1.17 + java-version: 17 + distribution: temurin - name: Gradle (Build) uses: gradle/gradle-build-action@v2 @@ -28,15 +29,17 @@ jobs: arguments: build - name: Upload artifacts (Main JAR) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Main JAR path: build/libs/*-all.jar + if-no-files-found: error - name: Upload artifacts (JARs) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: JARs path: build/libs/*.jar + if-no-files-found: warn diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 409f750..33828cf 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -10,13 +10,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: - java-version: 1.17 + java-version: 17 + distribution: temurin - name: Gradle (Build) uses: gradle/gradle-build-action@v2 @@ -25,15 +26,17 @@ jobs: arguments: build - name: Upload artifacts (Main JAR) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Main JAR path: build/libs/*-all.jar + if-no-files-found: error - name: Upload artifacts (JARs) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: JARs path: build/libs/*.jar + if-no-files-found: warn diff --git a/.github/workflows/root.yml b/.github/workflows/root.yml index da08301..2a005ff 100644 --- a/.github/workflows/root.yml +++ b/.github/workflows/root.yml @@ -10,13 +10,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: - java-version: 1.17 + java-version: 17 + distribution: temurin - name: Gradle (Build) uses: gradle/gradle-build-action@v2 @@ -26,15 +27,17 @@ jobs: dependency-graph: generate-and-submit - name: Upload artifacts (Main JAR) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Main JAR path: build/libs/*-all.jar + if-no-files-found: error - name: Upload artifacts (JARs) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: JARs path: build/libs/*.jar + if-no-files-found: warn diff --git a/.gitignore b/.gitignore index e798797..cfd0ada 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ hs_err_pid* # Generated files .idea/**/contentModel.xml +.idea/.name # Sensitive or high-churn files .idea/**/dataSources/ diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 24bc394..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -Super Trouper diff --git a/build.gradle.kts b/build.gradle.kts index ff6f6d0..5afb90e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,7 +44,7 @@ dependencies { } application { - mainClass = "quest.laxla.supertrouper.AppKt" + mainClass = "quest.laxla.trouper.AppKt" } val jvm: String by project diff --git a/settings.gradle.kts b/settings.gradle.kts index 929c86d..522a0be 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,4 +10,4 @@ pluginManagement { } } -rootProject.name = "supertrouper" +rootProject.name = "Trouper" diff --git a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt b/src/main/kotlin/quest/laxla/trouper/AboutExtension.kt similarity index 94% rename from src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt rename to src/main/kotlin/quest/laxla/trouper/AboutExtension.kt index d44b39d..9b85282 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt +++ b/src/main/kotlin/quest/laxla/trouper/AboutExtension.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import com.kotlindiscord.kord.extensions.components.components import com.kotlindiscord.kord.extensions.components.disabledButton @@ -9,7 +9,7 @@ import dev.kord.core.entity.effectiveName import dev.kord.rest.builder.message.allowedMentions import dev.kord.rest.builder.message.create.AbstractMessageCreateBuilder import dev.kord.rest.builder.message.embed -import quest.laxla.supertrouper.messaging.PrivateMessagesCategoryName +import quest.laxla.trouper.messaging.PrivateMessagesCategoryName class AboutExtension : TrouperExtension() { override suspend fun setup() { @@ -25,7 +25,6 @@ class AboutExtension : TrouperExtension() { } } - suspend fun AbstractMessageCreateBuilder.about() { val self = kord.getSelf() val avatar = (self.avatar ?: self.defaultAvatar).cdnUrl.toUrl() @@ -41,7 +40,7 @@ class AboutExtension : TrouperExtension() { //language=Markdown value = "> This instance is hosted on someone's personal computer, " + "and *may* contain **malicious code** and/or **steal your data**. " + - "This is not considered to be an official version of Super Trouper. " + + "This is not considered to be an official version of Trouper. " + "Do *not* rely on the lack of this message to determine if an instance is official." } diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/trouper/App.kt similarity index 89% rename from src/main/kotlin/quest/laxla/supertrouper/App.kt rename to src/main/kotlin/quest/laxla/trouper/App.kt index fa21331..6a31b4b 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/trouper/App.kt @@ -1,11 +1,11 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.utils.env import com.kotlindiscord.kord.extensions.utils.envOrNull import dev.kord.gateway.PrivilegedIntent import kotlinx.coroutines.runBlocking -import quest.laxla.supertrouper.messaging.PrivateMassagingExtension +import quest.laxla.trouper.messaging.PrivateMassagingExtension private val token = env("TOKEN") val officialServer = env("OFFICIAL_SERVER") diff --git a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt b/src/main/kotlin/quest/laxla/trouper/MaintenanceExtension.kt similarity index 94% rename from src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt rename to src/main/kotlin/quest/laxla/trouper/MaintenanceExtension.kt index 5290458..017b72b 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt +++ b/src/main/kotlin/quest/laxla/trouper/MaintenanceExtension.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import com.kotlindiscord.kord.extensions.checks.isBotAdmin import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand diff --git a/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt b/src/main/kotlin/quest/laxla/trouper/Overwrites.kt similarity index 98% rename from src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt rename to src/main/kotlin/quest/laxla/trouper/Overwrites.kt index 9ffff57..c5aff62 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt +++ b/src/main/kotlin/quest/laxla/trouper/Overwrites.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import dev.kord.common.entity.Overwrite import dev.kord.common.entity.OverwriteType diff --git a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt b/src/main/kotlin/quest/laxla/trouper/TargetedArguments.kt similarity index 95% rename from src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt rename to src/main/kotlin/quest/laxla/trouper/TargetedArguments.kt index 55325df..09a1a99 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt +++ b/src/main/kotlin/quest/laxla/trouper/TargetedArguments.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext diff --git a/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt b/src/main/kotlin/quest/laxla/trouper/TrouperExtension.kt similarity index 91% rename from src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt rename to src/main/kotlin/quest/laxla/trouper/TrouperExtension.kt index 7c06e3e..1435c21 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt +++ b/src/main/kotlin/quest/laxla/trouper/TrouperExtension.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import com.kotlindiscord.kord.extensions.extensions.Extension diff --git a/src/main/kotlin/quest/laxla/supertrouper/Utils.kt b/src/main/kotlin/quest/laxla/trouper/Utils.kt similarity index 96% rename from src/main/kotlin/quest/laxla/supertrouper/Utils.kt rename to src/main/kotlin/quest/laxla/trouper/Utils.kt index a898daa..ccbca93 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/Utils.kt +++ b/src/main/kotlin/quest/laxla/trouper/Utils.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import dev.kord.core.entity.Application import kotlin.contracts.ExperimentalContracts diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/trouper/messaging/PrivateMassagingExtension.kt similarity index 98% rename from src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt rename to src/main/kotlin/quest/laxla/trouper/messaging/PrivateMassagingExtension.kt index d920067..dac888b 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt +++ b/src/main/kotlin/quest/laxla/trouper/messaging/PrivateMassagingExtension.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper.messaging +package quest.laxla.trouper.messaging import com.kotlindiscord.kord.extensions.checks.anyGuild import com.kotlindiscord.kord.extensions.checks.isNotBot @@ -27,7 +27,7 @@ import dev.kord.rest.builder.channel.addRoleOverwrite import dev.kord.rest.builder.message.actionRow import dev.kord.rest.builder.message.embed import kotlinx.coroutines.flow.count -import quest.laxla.supertrouper.* +import quest.laxla.trouper.* @PrivilegedIntent class PrivateMassagingExtension : TrouperExtension() { diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt b/src/main/kotlin/quest/laxla/trouper/messaging/PrivateMessaging.kt similarity index 98% rename from src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt rename to src/main/kotlin/quest/laxla/trouper/messaging/PrivateMessaging.kt index 65438a5..48d659d 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt +++ b/src/main/kotlin/quest/laxla/trouper/messaging/PrivateMessaging.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper.messaging +package quest.laxla.trouper.messaging import com.kotlindiscord.kord.extensions.utils.envOrNull import dev.kord.common.entity.ALL From 46f6cbfaa3bd7dc9617374dd62de7d2471579554 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Thu, 14 Dec 2023 18:15:33 +0200 Subject: [PATCH 09/26] Assets! --- assets/SuperTrouper.png | Bin 0 -> 259 bytes assets/SuperTrouper.pxo | Bin 0 -> 521 bytes assets/TrouperDev.png | Bin 0 -> 260 bytes assets/TrouperDev.pxo | Bin 0 -> 517 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/SuperTrouper.png create mode 100644 assets/SuperTrouper.pxo create mode 100644 assets/TrouperDev.png create mode 100644 assets/TrouperDev.pxo diff --git a/assets/SuperTrouper.png b/assets/SuperTrouper.png new file mode 100644 index 0000000000000000000000000000000000000000..2785dcbd8c993d411087dd0f21d33917f48c3428 GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|_IkQFhFJ8T zouDt&e+dg#T&Z$4t`uZ z*ZlXt8veS*up-7A%gkryCKyN7ByTADc5Y3IYtouOw=#n6ca%#uocaAXG}q*KMmv=Z;=(hr*B5b|<_ZOXjyIP1NG; ze6;KNmgS6Vq;=X|M6Qd7&sWe^Z(Yn5`_;d5@&?_%_bZrQzX=v^az8etDnm{ Hr-UW|j3{SG literal 0 HcmV?d00001 diff --git a/assets/SuperTrouper.pxo b/assets/SuperTrouper.pxo new file mode 100644 index 0000000000000000000000000000000000000000..ede04f922e006d27a49e2e67e13ac7327e699e5f GIT binary patch literal 521 zcmV+k0`~n!Lr_Kn000005C8zd1^@u@0RR9fwJ-f(!38}J09KYqE}$prKi5}PV_pB? zYP+q-zjsPhN-6Tc+_H*G-Di;y4ruk1BnTdGoW@ZNtWrMTG)N6;_bI zriq*$&uy9%%BVoF`x>wfG)ITQ@J~1a>;i`4_bIm->|kT4 z3JSUHMH8ZMB!%N2gK^D%c5XT=%waSk%)Tb1p#f65zsf$l$J+A7$y&gV<9tm=Lg_#5 zjhfioE&&Z7Ac*9FA$kA>h#W)_4geOEdR%^8!(~zU2{%*LH;j@sA36L?OG2w(0 zQ9!}mVlkD@@sqXs3HC9#{}=bG7htE_IsXsti|p%wd0`mG(EQ6flEd((=8Z+i_;vn} zOJ4>KV{PB3i%Ak;9CcRX@CSwsB`7xzkk+UW7`BBOoeN^g?&Q59DiS3k5`2YqI$F#U z-P8bqW0o;4cOjf0i_DhfL7M((k5dyA^M0cLucp;cXu^@20xRj6A2qQAV<%C}sR(~n LPIN>SM?+9XhY0Ew literal 0 HcmV?d00001 diff --git a/assets/TrouperDev.png b/assets/TrouperDev.png new file mode 100644 index 0000000000000000000000000000000000000000..fd9279fa1258121901b25831fc0966bc2cb59fe6 GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|_IbKEhFJ8T z4Y1~GauAtgS{5A?<;iV2=kO730j~228H%?nnIEx!lM)MXPSV-Y*m?Ha`L5n~{?3VW zvW##3+{0Mc$id2ZVC%h$NzC(4zLIXXK9>`?N#)_1KeseA`T{0oCUMQZs={ftLE7&& zFGGXBOm$hwzpr@(E9HN!O?qm3+-SyS-zC29O*UqE3N1-uWjJv<@U!z&%kve=Dx6-k z3?Z|&96bnriduI1AW5a>FVdQ I&MBb@0K7G2qyPW_ literal 0 HcmV?d00001 diff --git a/assets/TrouperDev.pxo b/assets/TrouperDev.pxo new file mode 100644 index 0000000000000000000000000000000000000000..39546968c5bd3a3dbf615b0e1203b813f26fecf3 GIT binary patch literal 517 zcmV+g0{Z<&Lr_Kn000005C8zb1^@u<0RR9fwJ-f(zXcr+0Cv_#FOVte|9{p0uY&rq z^)}l`B-%TrE2R`cNbbGU-ih49Uu$&)SN>>|P0%z_4Wt$cIRH8UH~`xG#>LONoC>AW zneSrB#R_zb8~e#{D3yyJFxNx?%BV@XE%qHzAaNWC#iL3eWZwL&b=$D9WKp4uQiU5N zuxVnaUomHoCOrfgP^IklDYQVTH_vUF6v~)j_cdTEXpT+-!#`mLun!oH->2MWu!D`E zDo`P}y=X!fjZjQyK$!%zwIc`l8pgYs6iGHq6uWuXkjXa18{;Fx}ygYfRqeKHAn!3$_bDW93-BJ zD42k5VH=gs|CepK3-%whv(=8Au$T5Cf028ay*Pp|Od}eSe@6#9C~r7n0Uf;8#sA;8 ze(5>(dOvF4#4*KOwF%?61I&hu6DRLO$<(TVu@>O4Oz-4SQO-$W9 Date: Thu, 14 Dec 2023 18:24:34 +0200 Subject: [PATCH 10/26] Added scaled versions of files --- assets/SuperTrouper_scaled.png | Bin 0 -> 5871 bytes assets/TrouperDev_scaled.png | Bin 0 -> 5878 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/SuperTrouper_scaled.png create mode 100644 assets/TrouperDev_scaled.png diff --git a/assets/SuperTrouper_scaled.png b/assets/SuperTrouper_scaled.png new file mode 100644 index 0000000000000000000000000000000000000000..d50560346e3949e73f7d67dfcc7aa44c71cb200f GIT binary patch literal 5871 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGXsOzHcuDFkP5~(2Nfqx3uj`z z@cZteqdf_%97+!ChPyr9Gq}`*gd0vKD-uv;K2(F8iVzWK=PR>kT|dh6zVgs z0||!DQa~ahju}Xv$p#V)m7|Ku2#wuL3|1(3oLNX92RY0{=1`g zUu&}M{(Etj5u>FEt;^PR3x(xXw~1+V2F1eDg!AjShd&4y z%>uN~0tG7&vGBgWp8t7O)yFB5ZJ#~lVmO{TTAk9kadA(y!TIjff76=({`@z6&YQEl z^BaS0_g0)bDfhX6^?}1^2B3Wgka?~59oW!${5I9^+x!22wAHIWzl(kl5^uNn)XBK| zAG5%DpuJ-%(?C$qDZ%!`{Zw%i1?}Mp2qsj6 zOdPr3!T&d031Y&42&?n;y;g?eY5h zm`3N(aHe&tdmqWb!1vi!e!a{aU>q%}6w9eP%tPFf#N~-rfP56HOuxJCb?fKWy z7T3Pl>?ztXTB6Xpe2ry%;NidhIIx8I_G{MUzt6wDpY!Hl)effis(IJ9%e<+!iyJL< zXkXX8=Q^-(US2;iz~6o~Rsa3BUHoB8`TciAn|{B3$i;9x6Ih#$*0;25P}~!3aJIcG zmv4E)fB)a9lWTu{Fe+HJJHOoW=HIUctPdPU+d8yv%=&J1UW3|23^Gecd%U#n|A$-Ly8QOzwC2B`*Vh+q0#?HhV?ZSj14G|M zVBtSHxswHg#tZ&kmy;0!so?na|CXc}UbUl= cLplUzuwHnVcwfwq`y|L4p00i_>zopr03EHAF8}}l literal 0 HcmV?d00001 From bbb9ef3a591e4747df0b695cc534022273394af1 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Thu, 14 Dec 2023 18:27:20 +0200 Subject: [PATCH 11/26] Renamed files --- ...perTrouper_scaled.png => SuperTrouper_1024x.png} | Bin assets/{SuperTrouper.png => SuperTrouper_16x.png} | Bin .../{TrouperDev_scaled.png => TrouperDev_1024x.png} | Bin assets/{TrouperDev.png => TrouperDev_16x.png} | Bin 4 files changed, 0 insertions(+), 0 deletions(-) rename assets/{SuperTrouper_scaled.png => SuperTrouper_1024x.png} (100%) rename assets/{SuperTrouper.png => SuperTrouper_16x.png} (100%) rename assets/{TrouperDev_scaled.png => TrouperDev_1024x.png} (100%) rename assets/{TrouperDev.png => TrouperDev_16x.png} (100%) diff --git a/assets/SuperTrouper_scaled.png b/assets/SuperTrouper_1024x.png similarity index 100% rename from assets/SuperTrouper_scaled.png rename to assets/SuperTrouper_1024x.png diff --git a/assets/SuperTrouper.png b/assets/SuperTrouper_16x.png similarity index 100% rename from assets/SuperTrouper.png rename to assets/SuperTrouper_16x.png diff --git a/assets/TrouperDev_scaled.png b/assets/TrouperDev_1024x.png similarity index 100% rename from assets/TrouperDev_scaled.png rename to assets/TrouperDev_1024x.png diff --git a/assets/TrouperDev.png b/assets/TrouperDev_16x.png similarity index 100% rename from assets/TrouperDev.png rename to assets/TrouperDev_16x.png From e8e42802ea3b1258fd50e4b10140d39308f2efb2 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Thu, 14 Dec 2023 18:29:29 +0200 Subject: [PATCH 12/26] Added 128x versions --- assets/SuperTrouper_128x.png | Bin 0 -> 635 bytes assets/TrouperDev_128x.png | Bin 0 -> 634 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/SuperTrouper_128x.png create mode 100644 assets/TrouperDev_128x.png diff --git a/assets/SuperTrouper_128x.png b/assets/SuperTrouper_128x.png new file mode 100644 index 0000000000000000000000000000000000000000..3be9d7e0a2b764c2d25d35fae90bd33a60287abe GIT binary patch literal 635 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>U^4Y|aSW-L z^Y)Hno=Bj~frqh8s~roPxC9oge4+b-_XTf&%G$dv+6oWY3ivN57%V8b@#>M_XQyRP z&VKt|TXyMP%J0+O|K6W1+0G0!SnJ#7{Djwa_v?1V?)q2pKkK7)P1V=TkI(nZ$NBx0 zwwdws;TiP?3&tay3VsZQOdY}wcMjh)FL-n8H_Pebe{X7;v>oO!JYo?*sQJdo=Jx#W z;@XC_o0$;0z>3P>-{;<-@gZ@*!hD9iy+`*!%wZ+&0(`uU&h_VsIj{h57v_R-hT_45*ra3MJc?BI*``~`J? zGd}*h`E*xp!(@5&?(6z~{lE7zW4QhDcgAb;zR!=p|MKmh&FjPU#n#8mY}@OfzzXy? zvN!X7MSjhYPbiVI|Mc_k=1_b4Pk%n7e*E>uvL4g+`uTzG#s6N|{C)H3_S}2t@4LPK z-=9-^PJSc0x6S|VZMZnc-s-&k+~fT+zh(AgdikpHhg;8oy?*|2b0i~5pfh#M{ukF^ jS*M5YC14DL!S^%r_wL37{l7hVB8czl>gTe~DWM4fjeQkv literal 0 HcmV?d00001 diff --git a/assets/TrouperDev_128x.png b/assets/TrouperDev_128x.png new file mode 100644 index 0000000000000000000000000000000000000000..cd7f5c49fb1f5e39a00e031e2a4858878e7c59e4 GIT binary patch literal 634 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>U^4M^aSW-L z^Y)HmmPnw?frq^}mTuW{$%$p{SD7Yp0q*k(QR{BpjAqeScp{>iv{pgvp2G%D+3N1f zQz?~`|Lj{|t7Dy0XY*tK%#(TSK%+xs&hBeiyL)qW;m$4pvcG43H2<@&e%p_q?fL%8 z|MBLX{ZsTz*kKOCBNhSm1`Ea`oC@bG4;*+QlaTWIIZGKt#E+qnsROJi?>x{f>9ZfR z&vT}2W(F!oNU;c%Kc3J2qvmH_pm-2ZvM_P)9}$7Ywj{iiQaw};M`|M~0L$w$B5Sk@omgM|^)+jkD8U%5EPe%JiE zx=$~>)<4f*{QbZCHoNEj7;c>XFYZ9bIr+Qir9 rG3@#Bo$1WS;+tqb2gWWK>`45_@N{v4;x!&G7Lb6atDnm{r-UW|dk7fP literal 0 HcmV?d00001 From f5febe0b0c6a9167bfcbe5a9828c6afe3500f585 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Tue, 5 Dec 2023 22:48:35 +0200 Subject: [PATCH 13/26] Improved some things. --- .idea/.name | 1 + .../kotlin/quest/laxla/supertrouper/App.kt | 5 +++-- ...outExtension.kt => MaintenanceExtension.kt} | 18 +----------------- .../laxla/supertrouper/TrouperExtension.kt | 8 ++++++++ 4 files changed, 13 insertions(+), 19 deletions(-) create mode 100644 .idea/.name rename src/main/kotlin/quest/laxla/supertrouper/{AboutExtension.kt => MaintenanceExtension.kt} (56%) create mode 100644 src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..24bc394 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Super Trouper diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/supertrouper/App.kt index 7afa490..a36c02b 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/App.kt @@ -2,6 +2,7 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.utils.env +import dev.kord.common.entity.Snowflake import kotlinx.coroutines.runBlocking private val token = env("token") @@ -10,11 +11,11 @@ val officialServer = env("official_server") fun main() = runBlocking { ExtensibleBot(token) { extensions { - add(::AboutExtension) + add(::MaintenanceExtension) } applicationCommands { - defaultGuild(officialServer) + defaultGuild(Snowflake(officialServer)) } }.start() } diff --git a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt similarity index 56% rename from src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt rename to src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt index 066c89a..927fc67 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt @@ -1,27 +1,11 @@ package quest.laxla.supertrouper -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake -class AboutExtension : Extension() { - override val name: String - get() = "about" - +class MaintenanceExtension : TrouperExtension() { override suspend fun setup() { - ephemeralSlashCommand { - name = "about" - - action { - respond { - //language=Markdown - content = "Hey! This is a test command. It's powered by *magic*:sparkles:" - } - } - } - publicSlashCommand { name = "stop" description = "WARNING: Stops the bot completely." diff --git a/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt new file mode 100644 index 0000000..17183e6 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt @@ -0,0 +1,8 @@ +package quest.laxla.supertrouper + +import com.kotlindiscord.kord.extensions.extensions.Extension + +abstract class TrouperExtension : Extension() { + final override val name: String = this::class.simpleName!!.substringBeforeLast("Extension") + .replace("([A-Z])".toRegex()) { '-' + it.groups.single()!!.value.lowercase() }.removePrefix("-") +} From 8d1e391bcf5d2abe51419c55a4304dfa472f4e1c Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 6 Dec 2023 15:08:08 +0200 Subject: [PATCH 14/26] Bot works --- .idea/misc.xml | 1 - build.gradle.kts | 2 + .../kotlin/quest/laxla/supertrouper/App.kt | 15 +- .../supertrouper/MaintenanceExtension.kt | 10 +- .../quest/laxla/supertrouper/Overwrites.kt | 53 ++++++ .../supertrouper/PrivateMassagingExtension.kt | 154 ++++++++++++++++++ .../laxla/supertrouper/TargetedArguments.kt | 19 +++ .../laxla/supertrouper/TrouperExtension.kt | 4 +- 8 files changed, 245 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt create mode 100644 src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt create mode 100644 src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 3657fb2..b838806 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/build.gradle.kts b/build.gradle.kts index 98955df..ab1749f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,7 +35,9 @@ val klogging: String by project dependencies { implementation(kotlin("stdlib")) + implementation(kotlin("reflect")) implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serialization") + implementation("com.kotlindiscord.kord.extensions:kord-extensions:$kordex") implementation("io.github.oshai:kotlin-logging:$klogging") diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/supertrouper/App.kt index a36c02b..ef05775 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/App.kt @@ -2,20 +2,21 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.utils.env -import dev.kord.common.entity.Snowflake +import com.kotlindiscord.kord.extensions.utils.envOrNull import kotlinx.coroutines.runBlocking -private val token = env("token") -val officialServer = env("official_server") +private val token = env("TOKEN") +private val testingServer = envOrNull("TESTING_SERVER") fun main() = runBlocking { ExtensibleBot(token) { - extensions { - add(::MaintenanceExtension) + applicationCommands { + testingServer?.let { defaultGuild(it) } } - applicationCommands { - defaultGuild(Snowflake(officialServer)) + extensions { + add(::MaintenanceExtension) + add(::PrivateMassagingExtension) } }.start() } diff --git a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt index 927fc67..322e091 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt @@ -1,16 +1,18 @@ package quest.laxla.supertrouper +import com.kotlindiscord.kord.extensions.checks.isBotAdmin import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand -import dev.kord.common.entity.Permission -import dev.kord.common.entity.Snowflake +import com.kotlindiscord.kord.extensions.extensions.slashCommandCheck class MaintenanceExtension : TrouperExtension() { override suspend fun setup() { + slashCommandCheck { + isBotAdmin() + } + publicSlashCommand { name = "stop" description = "WARNING: Stops the bot completely." - guildId = Snowflake(officialServer) - requirePermission(Permission.Administrator) action { //language=Markdown diff --git a/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt b/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt new file mode 100644 index 0000000..d299df1 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt @@ -0,0 +1,53 @@ +package quest.laxla.supertrouper + +import dev.kord.common.entity.Overwrite +import dev.kord.common.entity.OverwriteType +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.Snowflake +import dev.kord.core.entity.PermissionOverwriteEntity +import dev.kord.rest.builder.channel.PermissionOverwritesBuilder + +fun overwrite( + target: Snowflake, + type: OverwriteType, + allowed: Permissions = Permissions(), + denied: Permissions = Permissions() +) = Overwrite(target, type, allowed, denied) + +fun PermissionOverwritesBuilder.addOverwrite( + target: Snowflake, + type: OverwriteType, + allowed: Permissions = Permissions(), + denied: Permissions = Permissions() +) = addOverwrite(overwrite(target, type, allowed, denied)) + +fun PermissionOverwritesBuilder.sync( + vararg overrides: Overwrite, + defaults: Iterable +) = sync(overrides.asIterable(), defaults) + +fun PermissionOverwritesBuilder.sync( + overrides: Iterable, + defaults: Iterable +) { + val permissions = mutableMapOf() + + defaults.forEach { default -> + val override = overrides.find { it.id == default.target && it.type == default.type } + + if (override == null) addOverwrite(default.target, default.type, default.allowed, default.denied) + else permissions[override] = default + } + + overrides.forEach { override -> + val default = permissions[override] + + if (default == null) addOverwrite(override) + else addOverwrite( + default.target, + default.type, + default.allowed - default.denied - override.deny + override.allow, + default.denied - default.allowed - override.allow + override.deny + ) + } +} diff --git a/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt new file mode 100644 index 0000000..00b53e3 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt @@ -0,0 +1,154 @@ +package quest.laxla.supertrouper + +import com.kotlindiscord.kord.extensions.checks.anyGuild +import com.kotlindiscord.kord.extensions.checks.isNotBot +import com.kotlindiscord.kord.extensions.extensions.* +import com.kotlindiscord.kord.extensions.utils.any +import com.kotlindiscord.kord.extensions.utils.envOrNull +import dev.kord.common.entity.OverwriteType +import dev.kord.common.entity.Permission +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.channel.createTextChannel +import dev.kord.core.behavior.channel.edit +import dev.kord.core.behavior.createCategory +import dev.kord.core.entity.User +import dev.kord.core.entity.channel.Category +import dev.kord.core.entity.channel.TextChannel +import dev.kord.core.event.guild.MemberJoinEvent +import dev.kord.gateway.Intent +import dev.kord.gateway.PrivilegedIntent +import dev.kord.rest.builder.channel.addMemberOverwrite +import dev.kord.rest.builder.channel.addRoleOverwrite +import kotlinx.coroutines.flow.* + +private const val PrivateMessagesCategoryName = "Private Messages" +private val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30 +private val privateMessageOwnerPermissions = Permission.ViewChannel + Permission.ReadMessageHistory +private val privateMessageBotPermissions = + privateMessageOwnerPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages + +class PrivateMassagingExtension : TrouperExtension() { + @OptIn(PrivilegedIntent::class) + override suspend fun setup() { + intents += Intent.GuildMembers + + slashCommandCheck { + anyGuild() + isNotBot() + } + + userCommandCheck { + anyGuild() + isNotBot() + } + + event { + action { + if (event.guild.members.count() < memberLimit) getOrCreateChannel(event.guild, event.member) + } + } + + ephemeralSlashCommand(::TargetedArguments) { + name = "pm" + description = "Get a link to a user's private messages channel" + + action { + respond { + content = getOrCreateChannelMention(guild!!, target.asUser()) + } + } + } + + ephemeralUserCommand { + name = "Private Message" + + action { + respond { + content = getOrCreateChannelMention(guild!!, targetUsers.single()) + } + } + } + + ephemeralSlashCommand(::TargetedArguments) slash@{ + name = "sync" + description = "Syncs a private message channel's permissions with the category." + + requirePermission(Permission.ManageRoles) + + action { + val category = getOrCreateCategory(guild!!) + val targetUser = target.asUser() + val targetChannel = getChannel(category, targetUser) + if (targetChannel == null) { + respond { content = "${target.mention} does not have a private message channel in this server." } + return@action + } + + targetChannel.edit { + sync( + overwrite(this@slash.kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), + overwrite(targetUser.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), + defaults = category.permissionOverwrites + ) + } + + respond { + content = "Synced ${targetChannel.mention} for ${target.mention} successfully." + } + } + } + } + + private suspend fun getOrCreateChannelMention(guild: GuildBehavior, user: User): String = + user.mention + ": " + (getOrCreateChannel(guild, user)?.mention ?: "Ineligible") + + private suspend fun getOrCreateChannel(guild: GuildBehavior, user: User) = + getOrCreateChannel(getOrCreateCategory(guild), user) + + private suspend fun getOrCreateCategory(guild: GuildBehavior) = getCategory(guild) ?: createCategory(guild) + + private suspend fun getCategory(guild: GuildBehavior) = guild.channels.filterIsInstance().filter { + it.name.equals(PrivateMessagesCategoryName, ignoreCase = true) + }.singleOrNull() + + private suspend fun createCategory(guild: GuildBehavior) = guild.createCategory(PrivateMessagesCategoryName) { + reason = "Private messaging category was missing." + nsfw = false + + addMemberOverwrite(kord.selfId) { + allowed += privateMessageBotPermissions + } + + addRoleOverwrite(guild.id) { + denied += Permission.ViewChannel + } + } + + private suspend fun getOrCreateChannel(category: Category, user: User) = + if (user.isBot) null else getChannel(category, user) ?: createChannel(category, user) + + private suspend fun getChannel(category: Category, user: User) = + category.channels.filterIsInstance().firstOrNull { channel -> + channel.categoryId == category.id && (channel.topic?.contains(user.mention) == true || channel.pinnedMessages.any { + it.author?.id == kord.selfId && it.mentionedUserIds.singleOrNull() == user.id + }) + } + + private suspend fun createChannel(category: Category, user: User): TextChannel { + val mention = user.mention + + val channel = category.createTextChannel(user.username) { + reason = "Created a PM with $mention." + nsfw = category.data.nsfw.discordBoolean + topic = mention + + sync( + overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), + overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), + defaults = category.permissionOverwrites + ) + } + + return channel + } +} diff --git a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt new file mode 100644 index 0000000..1f2ef39 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt @@ -0,0 +1,19 @@ +package quest.laxla.supertrouper + +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext +import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalUser +import com.kotlindiscord.kord.extensions.components.forms.ModalForm + +private const val TargetArgumentName = "target" +private const val TargetArgumentDescription = "Target of this command. Defaults to you." + +open class TargetedArguments : Arguments() { + val targetOrNull by optionalUser { + name = TargetArgumentName + description = TargetArgumentDescription + } +} + +val C.target where C : SlashCommandContext<*, A, M>, A : TargetedArguments, M : ModalForm + get() = arguments.targetOrNull ?: user diff --git a/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt index 17183e6..7c06e3e 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt @@ -2,7 +2,9 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.extensions.Extension +private const val NameRegexGroup = "name" + abstract class TrouperExtension : Extension() { final override val name: String = this::class.simpleName!!.substringBeforeLast("Extension") - .replace("([A-Z])".toRegex()) { '-' + it.groups.single()!!.value.lowercase() }.removePrefix("-") + .replace("(?<$NameRegexGroup>[A-Z])".toRegex()) { '-' + it.groups[NameRegexGroup]!!.value.lowercase() }.removePrefix("-") } From 5d956bff71289220decb6a5580fb294f10073029 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 6 Dec 2023 16:02:41 +0200 Subject: [PATCH 15/26] Re-sorted some things --- .../kotlin/quest/laxla/supertrouper/App.kt | 3 + .../supertrouper/PrivateMassagingExtension.kt | 154 ------------------ .../messaging/PrivateMassagingExtension.kt | 149 +++++++++++++++++ .../messaging/PrivateMessaging.kt | 32 ++++ 4 files changed, 184 insertions(+), 154 deletions(-) delete mode 100644 src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt create mode 100644 src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt create mode 100644 src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/supertrouper/App.kt index ef05775..53e487c 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/App.kt @@ -3,7 +3,9 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.utils.env import com.kotlindiscord.kord.extensions.utils.envOrNull +import dev.kord.gateway.PrivilegedIntent import kotlinx.coroutines.runBlocking +import quest.laxla.supertrouper.messaging.PrivateMassagingExtension private val token = env("TOKEN") private val testingServer = envOrNull("TESTING_SERVER") @@ -16,6 +18,7 @@ fun main() = runBlocking { extensions { add(::MaintenanceExtension) + @OptIn(PrivilegedIntent::class) add(::PrivateMassagingExtension) } }.start() diff --git a/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt deleted file mode 100644 index 00b53e3..0000000 --- a/src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt +++ /dev/null @@ -1,154 +0,0 @@ -package quest.laxla.supertrouper - -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.isNotBot -import com.kotlindiscord.kord.extensions.extensions.* -import com.kotlindiscord.kord.extensions.utils.any -import com.kotlindiscord.kord.extensions.utils.envOrNull -import dev.kord.common.entity.OverwriteType -import dev.kord.common.entity.Permission -import dev.kord.core.behavior.GuildBehavior -import dev.kord.core.behavior.channel.createTextChannel -import dev.kord.core.behavior.channel.edit -import dev.kord.core.behavior.createCategory -import dev.kord.core.entity.User -import dev.kord.core.entity.channel.Category -import dev.kord.core.entity.channel.TextChannel -import dev.kord.core.event.guild.MemberJoinEvent -import dev.kord.gateway.Intent -import dev.kord.gateway.PrivilegedIntent -import dev.kord.rest.builder.channel.addMemberOverwrite -import dev.kord.rest.builder.channel.addRoleOverwrite -import kotlinx.coroutines.flow.* - -private const val PrivateMessagesCategoryName = "Private Messages" -private val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30 -private val privateMessageOwnerPermissions = Permission.ViewChannel + Permission.ReadMessageHistory -private val privateMessageBotPermissions = - privateMessageOwnerPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages - -class PrivateMassagingExtension : TrouperExtension() { - @OptIn(PrivilegedIntent::class) - override suspend fun setup() { - intents += Intent.GuildMembers - - slashCommandCheck { - anyGuild() - isNotBot() - } - - userCommandCheck { - anyGuild() - isNotBot() - } - - event { - action { - if (event.guild.members.count() < memberLimit) getOrCreateChannel(event.guild, event.member) - } - } - - ephemeralSlashCommand(::TargetedArguments) { - name = "pm" - description = "Get a link to a user's private messages channel" - - action { - respond { - content = getOrCreateChannelMention(guild!!, target.asUser()) - } - } - } - - ephemeralUserCommand { - name = "Private Message" - - action { - respond { - content = getOrCreateChannelMention(guild!!, targetUsers.single()) - } - } - } - - ephemeralSlashCommand(::TargetedArguments) slash@{ - name = "sync" - description = "Syncs a private message channel's permissions with the category." - - requirePermission(Permission.ManageRoles) - - action { - val category = getOrCreateCategory(guild!!) - val targetUser = target.asUser() - val targetChannel = getChannel(category, targetUser) - if (targetChannel == null) { - respond { content = "${target.mention} does not have a private message channel in this server." } - return@action - } - - targetChannel.edit { - sync( - overwrite(this@slash.kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), - overwrite(targetUser.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), - defaults = category.permissionOverwrites - ) - } - - respond { - content = "Synced ${targetChannel.mention} for ${target.mention} successfully." - } - } - } - } - - private suspend fun getOrCreateChannelMention(guild: GuildBehavior, user: User): String = - user.mention + ": " + (getOrCreateChannel(guild, user)?.mention ?: "Ineligible") - - private suspend fun getOrCreateChannel(guild: GuildBehavior, user: User) = - getOrCreateChannel(getOrCreateCategory(guild), user) - - private suspend fun getOrCreateCategory(guild: GuildBehavior) = getCategory(guild) ?: createCategory(guild) - - private suspend fun getCategory(guild: GuildBehavior) = guild.channels.filterIsInstance().filter { - it.name.equals(PrivateMessagesCategoryName, ignoreCase = true) - }.singleOrNull() - - private suspend fun createCategory(guild: GuildBehavior) = guild.createCategory(PrivateMessagesCategoryName) { - reason = "Private messaging category was missing." - nsfw = false - - addMemberOverwrite(kord.selfId) { - allowed += privateMessageBotPermissions - } - - addRoleOverwrite(guild.id) { - denied += Permission.ViewChannel - } - } - - private suspend fun getOrCreateChannel(category: Category, user: User) = - if (user.isBot) null else getChannel(category, user) ?: createChannel(category, user) - - private suspend fun getChannel(category: Category, user: User) = - category.channels.filterIsInstance().firstOrNull { channel -> - channel.categoryId == category.id && (channel.topic?.contains(user.mention) == true || channel.pinnedMessages.any { - it.author?.id == kord.selfId && it.mentionedUserIds.singleOrNull() == user.id - }) - } - - private suspend fun createChannel(category: Category, user: User): TextChannel { - val mention = user.mention - - val channel = category.createTextChannel(user.username) { - reason = "Created a PM with $mention." - nsfw = category.data.nsfw.discordBoolean - topic = mention - - sync( - overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), - overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), - defaults = category.permissionOverwrites - ) - } - - return channel - } -} diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt new file mode 100644 index 0000000..2a933ef --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt @@ -0,0 +1,149 @@ +package quest.laxla.supertrouper.messaging + +import com.kotlindiscord.kord.extensions.checks.anyGuild +import com.kotlindiscord.kord.extensions.checks.isNotBot +import com.kotlindiscord.kord.extensions.extensions.* +import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext +import dev.kord.common.entity.OverwriteType +import dev.kord.common.entity.Permission +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.channel.createTextChannel +import dev.kord.core.behavior.channel.edit +import dev.kord.core.behavior.createCategory +import dev.kord.core.entity.User +import dev.kord.core.entity.channel.Category +import dev.kord.core.entity.channel.TextChannel +import dev.kord.core.event.guild.MemberJoinEvent +import dev.kord.gateway.Intent +import dev.kord.gateway.PrivilegedIntent +import dev.kord.rest.builder.channel.addMemberOverwrite +import dev.kord.rest.builder.channel.addRoleOverwrite +import kotlinx.coroutines.flow.count +import quest.laxla.supertrouper.* + +@PrivilegedIntent +class PrivateMassagingExtension : TrouperExtension() { + override suspend fun setup() { + intents += Intent.GuildMembers + + slashCommandCheck { anyGuild(); isNotBot() } + userCommandCheck { anyGuild(); isNotBot() } + + event { + action { + if (event.member.isEligible && event.guild.members.count() < memberLimit) getOrCreateChannel( + getOrCreateCategory(event.guild), + event.member + ) + } + } + + ephemeralSlashCommand(::TargetedArguments) { + name = "pm" + description = "Get a link to a user's private messages channel" + + action { + executeFindCommand(getOrCreateCategory(guild!!), target.asUser()) + } + } + + ephemeralUserCommand { + name = "Private Message" + + action { + executeFindCommand(getOrCreateCategory(guild!!), targetUsers.single()) + } + } + + ephemeralSlashCommand(::TargetedArguments) slash@{ + name = "sync" + description = "Syncs a private message channel's permissions with the category." + + requirePermission(Permission.ManageRoles) + + action { + executeSyncCommand(getOrCreateCategory(guild!!), target.asUser()) + } + } + + ephemeralUserCommand { + name = "Sync PM Channel" + + requirePermission(Permission.ManageRoles) + + action { + executeSyncCommand(getOrCreateCategory(guild!!), user.asUser()) + } + } + } + + private suspend fun EphemeralInteractionContext.executeSyncCommand(category: Category, user: User) { + val channel = getChannel(category, user) + if (channel == null) { + respond { content = "${user.mention} does not have a private messaging channel in this server." } + return + } + + val userMention = user.mention + val channelMention = channel.mention + + channel.edit { + reason = "Syncing $userMention" + + sync( + overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), + overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), + defaults = category.permissionOverwrites + ) + } + + respond { + content = "Synced $channelMention for $userMention successfully." + } + } + + private suspend fun EphemeralInteractionContext.executeFindCommand(category: Category, user: User) { + if (user.isEligible) { + val channel = getOrCreateChannel(category, user) + respond { content = channel.mention } + } else respond { + content = user.mention + " is not eligible for private messaging." + } + } + + private suspend fun getOrCreateCategory(guild: GuildBehavior) = getCategory(guild) ?: createCategory(guild) + + private suspend fun createCategory(guild: GuildBehavior) = guild.createCategory(PrivateMessagesCategoryName) { + reason = "Private messaging category was missing." + nsfw = false + + addMemberOverwrite(kord.selfId) { + allowed += privateMessageBotPermissions + } + + addRoleOverwrite(guild.id) { + denied += Permission.ViewChannel + } + } + + private suspend fun getOrCreateChannel(category: Category, user: User) = + getChannel(category, user) ?: createChannel(category, user) + + private suspend fun createChannel(category: Category, user: User): TextChannel { + val mention = user.mention + + val channel = category.createTextChannel(user.username) { + reason = "Created a PM with $mention." + nsfw = category.data.nsfw.discordBoolean + topic = "This channel is $mention's private message." + + sync( + overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), + overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), + defaults = category.permissionOverwrites + ) + } + + return channel + } +} diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt new file mode 100644 index 0000000..2afb6b4 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt @@ -0,0 +1,32 @@ +package quest.laxla.supertrouper.messaging + +import com.kotlindiscord.kord.extensions.utils.any +import com.kotlindiscord.kord.extensions.utils.envOrNull +import dev.kord.common.entity.Permission +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.entity.User +import dev.kord.core.entity.channel.Category +import dev.kord.core.entity.channel.TextChannel +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.firstOrNull + +const val PrivateMessagesCategoryName = "Private Messages" +val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30 +val privateMessageOwnerPermissions = Permission.ViewChannel + Permission.ReadMessageHistory +val privateMessageBotPermissions = + privateMessageOwnerPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages + +suspend infix fun TextChannel.isOf(user: UserBehavior) = topic?.contains(user.mention) == true || pinnedMessages.any { + it.author?.id == kord.selfId && it.mentionedUserIds.singleOrNull() == user.id +} + +suspend fun getChannel(category: Category, user: User) = category.channels.filterIsInstance().firstOrNull { + it.categoryId == category.id && it isOf user +} + +suspend fun getCategory(guild: GuildBehavior) = + guild.channels.filterIsInstance().firstOrNull { it.isUsableForPrivateMessaging } + +val User.isEligible get() = !isBot +val Category.isUsableForPrivateMessaging get() = name.equals(PrivateMessagesCategoryName, ignoreCase = true) From 5f3e1827a3e5da9a50a0073c7779edd543f9d4df Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 6 Dec 2023 16:12:20 +0200 Subject: [PATCH 16/26] Updated some descriptions --- .../laxla/supertrouper/MaintenanceExtension.kt | 18 ++++++++++++------ .../messaging/PrivateMassagingExtension.kt | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt index 322e091..6222636 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt @@ -1,6 +1,7 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.checks.isBotAdmin +import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand import com.kotlindiscord.kord.extensions.extensions.slashCommandCheck @@ -11,13 +12,18 @@ class MaintenanceExtension : TrouperExtension() { } publicSlashCommand { - name = "stop" - description = "WARNING: Stops the bot completely." + name = "maintenance" + description = "Maintenance commands for maintainers of the bot" - action { - //language=Markdown - respond { content = "# Invoking Protocol: Emergency Stop" } - bot.stop() + publicSubCommand { + name = "stop" + description = "Stops the bot completely" + + action { + //language=Markdown + respond { content = "# Invoking Protocol: Emergency Stop" } + bot.stop() + } } } } diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt index 2a933ef..15d15dc 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt @@ -57,7 +57,7 @@ class PrivateMassagingExtension : TrouperExtension() { ephemeralSlashCommand(::TargetedArguments) slash@{ name = "sync" - description = "Syncs a private message channel's permissions with the category." + description = "Syncs a private message channel's permissions with the category" requirePermission(Permission.ManageRoles) From 66a12ef764834c469cd637c89fc38eb0576bd22e Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 13 Dec 2023 17:07:55 +0200 Subject: [PATCH 17/26] Almost final bot version --- build.gradle.kts | 2 +- .../laxla/supertrouper/AboutExtension.kt | 8 ++ .../kotlin/quest/laxla/supertrouper/App.kt | 5 +- .../supertrouper/MaintenanceExtension.kt | 20 ++--- .../quest/laxla/supertrouper/Overwrites.kt | 14 ++-- .../laxla/supertrouper/TargetedArguments.kt | 5 +- .../messaging/PrivateMassagingExtension.kt | 82 ++++++++++++++----- .../messaging/PrivateMessaging.kt | 58 +++++++++++-- 8 files changed, 145 insertions(+), 49 deletions(-) create mode 100644 src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt diff --git a/build.gradle.kts b/build.gradle.kts index ab1749f..59cfa0d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -36,7 +36,7 @@ val klogging: String by project dependencies { implementation(kotlin("stdlib")) implementation(kotlin("reflect")) - implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serialization") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$serialization") implementation("com.kotlindiscord.kord.extensions:kord-extensions:$kordex") diff --git a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt new file mode 100644 index 0000000..1761e11 --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt @@ -0,0 +1,8 @@ +package quest.laxla.supertrouper + +class AboutExtension : TrouperExtension() { + override suspend fun setup() { + TODO("Not yet implemented") + } + +} diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/supertrouper/App.kt index 53e487c..8754092 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/App.kt @@ -8,12 +8,13 @@ import kotlinx.coroutines.runBlocking import quest.laxla.supertrouper.messaging.PrivateMassagingExtension private val token = env("TOKEN") -private val testingServer = envOrNull("TESTING_SERVER") +val officialServer = env("OFFICIAL_SERVER") +val isDevelopmentEnvironment = envOrNull("IS_DEV_ENV").toBoolean() fun main() = runBlocking { ExtensibleBot(token) { applicationCommands { - testingServer?.let { defaultGuild(it) } + if (isDevelopmentEnvironment) defaultGuild(officialServer) } extensions { diff --git a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt index 6222636..5290458 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt @@ -1,9 +1,9 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.checks.isBotAdmin -import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand import com.kotlindiscord.kord.extensions.extensions.slashCommandCheck +import dev.kord.common.entity.Snowflake class MaintenanceExtension : TrouperExtension() { override suspend fun setup() { @@ -12,18 +12,14 @@ class MaintenanceExtension : TrouperExtension() { } publicSlashCommand { - name = "maintenance" - description = "Maintenance commands for maintainers of the bot" + name = "stop" + description = "Stops the bot completely" + guildId = Snowflake(officialServer) - publicSubCommand { - name = "stop" - description = "Stops the bot completely" - - action { - //language=Markdown - respond { content = "# Invoking Protocol: Emergency Stop" } - bot.stop() - } + action { + //language=Markdown + respond { content = "# Invoking Protocol: Emergency Stop" } + bot.stop() } } } diff --git a/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt b/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt index d299df1..9ffff57 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt @@ -23,30 +23,32 @@ fun PermissionOverwritesBuilder.addOverwrite( fun PermissionOverwritesBuilder.sync( vararg overrides: Overwrite, - defaults: Iterable -) = sync(overrides.asIterable(), defaults) + defaults: Iterable, + neverAllow: Permissions = Permissions() +) = sync(overrides.asIterable(), defaults, neverAllow) fun PermissionOverwritesBuilder.sync( overrides: Iterable, - defaults: Iterable + defaults: Iterable, + neverAllow: Permissions = Permissions() ) { val permissions = mutableMapOf() defaults.forEach { default -> val override = overrides.find { it.id == default.target && it.type == default.type } - if (override == null) addOverwrite(default.target, default.type, default.allowed, default.denied) + if (override == null) addOverwrite(default.target, default.type, default.allowed - neverAllow, default.denied) else permissions[override] = default } overrides.forEach { override -> val default = permissions[override] - if (default == null) addOverwrite(override) + if (default == null) addOverwrite(override.copy(allow = override.allow - neverAllow)) else addOverwrite( default.target, default.type, - default.allowed - default.denied - override.deny + override.allow, + default.allowed - default.denied + override.allow - override.deny - neverAllow, default.denied - default.allowed - override.allow + override.deny ) } diff --git a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt index 1f2ef39..52a8fe5 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt @@ -2,6 +2,7 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext +import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalMember import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalUser import com.kotlindiscord.kord.extensions.components.forms.ModalForm @@ -9,11 +10,11 @@ private const val TargetArgumentName = "target" private const val TargetArgumentDescription = "Target of this command. Defaults to you." open class TargetedArguments : Arguments() { - val targetOrNull by optionalUser { + val targetOrNull by optionalMember { name = TargetArgumentName description = TargetArgumentDescription } } val C.target where C : SlashCommandContext<*, A, M>, A : TargetedArguments, M : ModalForm - get() = arguments.targetOrNull ?: user + get() = arguments.targetOrNull ?: member!! diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt index 15d15dc..365ebef 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt @@ -4,20 +4,32 @@ import com.kotlindiscord.kord.extensions.checks.anyGuild import com.kotlindiscord.kord.extensions.checks.isNotBot import com.kotlindiscord.kord.extensions.extensions.* import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext -import dev.kord.common.entity.OverwriteType -import dev.kord.common.entity.Permission +import com.kotlindiscord.kord.extensions.utils.any +import dev.kord.common.entity.* import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.behavior.channel.asChannelOfOrNull +import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.channel.createTextChannel import dev.kord.core.behavior.channel.edit import dev.kord.core.behavior.createCategory +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.entity.Member +import dev.kord.core.entity.Role import dev.kord.core.entity.User import dev.kord.core.entity.channel.Category import dev.kord.core.entity.channel.TextChannel import dev.kord.core.event.guild.MemberJoinEvent +import dev.kord.core.event.interaction.GuildButtonInteractionCreateEvent +import dev.kord.core.event.interaction.GuildSelectMenuInteractionCreateEvent import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent import dev.kord.rest.builder.channel.addMemberOverwrite import dev.kord.rest.builder.channel.addRoleOverwrite +import dev.kord.rest.builder.message.actionRow +import dev.kord.rest.builder.message.create.AbstractMessageCreateBuilder +import dev.kord.rest.builder.message.embed import kotlinx.coroutines.flow.count import quest.laxla.supertrouper.* @@ -31,10 +43,8 @@ class PrivateMassagingExtension : TrouperExtension() { event { action { - if (event.member.isEligible && event.guild.members.count() < memberLimit) getOrCreateChannel( - getOrCreateCategory(event.guild), - event.member - ) + if (event.member.isEligible && event.guild.members.count() < memberLimit) + getOrCreateChannel(getOrCreateCategory(event.guild), event.member) } } @@ -43,7 +53,7 @@ class PrivateMassagingExtension : TrouperExtension() { description = "Get a link to a user's private messages channel" action { - executeFindCommand(getOrCreateCategory(guild!!), target.asUser()) + executeFindCommand(getOrCreateCategory(guild!!), target.asUser(), user) } } @@ -51,11 +61,11 @@ class PrivateMassagingExtension : TrouperExtension() { name = "Private Message" action { - executeFindCommand(getOrCreateCategory(guild!!), targetUsers.single()) + executeFindCommand(getOrCreateCategory(guild!!), targetUsers.single(), user) } } - ephemeralSlashCommand(::TargetedArguments) slash@{ + ephemeralSlashCommand(::TargetedArguments) { name = "sync" description = "Syncs a private message channel's permissions with the category" @@ -75,6 +85,16 @@ class PrivateMassagingExtension : TrouperExtension() { executeSyncCommand(getOrCreateCategory(guild!!), user.asUser()) } } + + event { + action { + when (event.interaction.componentId) { + PingButton -> event.interaction.respondPublic { + executePingCommand(event.interaction.channel.asChannel(), event.interaction.user) + } + } + } + } } private suspend fun EphemeralInteractionContext.executeSyncCommand(category: Category, user: User) { @@ -88,12 +108,13 @@ class PrivateMassagingExtension : TrouperExtension() { val channelMention = channel.mention channel.edit { - reason = "Syncing $userMention" + reason = "Sync $channelMention with category for $userMention" sync( - overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), - overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), - defaults = category.permissionOverwrites + overwrite(kord.selfId, OverwriteType.Member, allowed = pmBotPermissions), + overwrite(user.id, OverwriteType.Member, allowed = pmMemberPermissions), + defaults = category.permissionOverwrites, + neverAllow = kord.getSelf().asMember(category.guildId).getDeniedPermissions() ) } @@ -102,10 +123,14 @@ class PrivateMassagingExtension : TrouperExtension() { } } - private suspend fun EphemeralInteractionContext.executeFindCommand(category: Category, user: User) { + private suspend fun EphemeralInteractionContext.executeFindCommand( + category: Category, user: User, searcher: UserBehavior = user + ) { if (user.isEligible) { val channel = getOrCreateChannel(category, user) respond { content = channel.mention } + + channel.ping(searcher) } else respond { content = user.mention + " is not eligible for private messaging." } @@ -118,7 +143,7 @@ class PrivateMassagingExtension : TrouperExtension() { nsfw = false addMemberOverwrite(kord.selfId) { - allowed += privateMessageBotPermissions + allowed += pmBotPermissions } addRoleOverwrite(guild.id) { @@ -135,15 +160,34 @@ class PrivateMassagingExtension : TrouperExtension() { val channel = category.createTextChannel(user.username) { reason = "Created a PM with $mention." nsfw = category.data.nsfw.discordBoolean - topic = "This channel is $mention's private message." + topic = "$mention's private messaging channel." sync( - overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions), - overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions), - defaults = category.permissionOverwrites + overwrite(kord.selfId, OverwriteType.Member, allowed = pmBotPermissions), + overwrite(user.id, OverwriteType.Member, allowed = pmMemberPermissions), + defaults = category.permissionOverwrites, + neverAllow = kord.getSelf().asMember(category.guildId).getDeniedPermissions() ) } + val avatar = (user.avatar ?: user.defaultAvatar).cdnUrl.toUrl() + + channel.createMessage { + embed { + description = "# $mention" + thumbnail { url = avatar } + } + + actionRow { + interactionButton(ButtonStyle.Primary, customId = PingButton) { + label = "Ping" + emoji = DiscordPartialEmoji(name = "\uD83D\uDD14") + } + } + } + + channel.ping(user) + return channel } } diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt index 2afb6b4..25f858c 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt @@ -1,25 +1,36 @@ package quest.laxla.supertrouper.messaging -import com.kotlindiscord.kord.extensions.utils.any import com.kotlindiscord.kord.extensions.utils.envOrNull +import dev.kord.common.entity.ALL import dev.kord.common.entity.Permission +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.UserBehavior +import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.entity.Member import dev.kord.core.entity.User import dev.kord.core.entity.channel.Category +import dev.kord.core.entity.channel.MessageChannel import dev.kord.core.entity.channel.TextChannel +import dev.kord.rest.builder.message.allowedMentions +import dev.kord.rest.builder.message.create.AbstractMessageCreateBuilder import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.firstOrNull const val PrivateMessagesCategoryName = "Private Messages" +const val InviteSelect = "PM.invite" +const val PingButton = "PM.ping" +const val ManagePmButton = "PM.manage" +const val UserIdCapturingGroup = "userID" val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30 -val privateMessageOwnerPermissions = Permission.ViewChannel + Permission.ReadMessageHistory -val privateMessageBotPermissions = - privateMessageOwnerPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages +val pmMemberPermissions = Permission.ViewChannel + Permission.ReadMessageHistory +val pmBotPermissions = + pmMemberPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages +val userMentionRegex = "<@(?<$UserIdCapturingGroup>[1-9][0-9]+)>".toRegex() -suspend infix fun TextChannel.isOf(user: UserBehavior) = topic?.contains(user.mention) == true || pinnedMessages.any { - it.author?.id == kord.selfId && it.mentionedUserIds.singleOrNull() == user.id -} +infix fun TextChannel.isOf(user: UserBehavior) = topic?.contains(user.mention) == true suspend fun getChannel(category: Category, user: User) = category.channels.filterIsInstance().firstOrNull { it.categoryId == category.id && it isOf user @@ -30,3 +41,36 @@ suspend fun getCategory(guild: GuildBehavior) = val User.isEligible get() = !isBot val Category.isUsableForPrivateMessaging get() = name.equals(PrivateMessagesCategoryName, ignoreCase = true) + +fun AbstractMessageCreateBuilder.executePingCommand( + channel: MessageChannel, + pinger: UserBehavior +) { + val owners = channel.owners?.toList() + + if (owners == null) { + allowedMentions() + + content = "The owner of this channel is unknown. They need to be mentioned in the channel's topic, " + + "Like this: `<@userID>`." + } else { + allowedMentions { + users.addAll(owners.asSequence().map { + Snowflake(it.groups[UserIdCapturingGroup]!!.value) + }) + } + + content = "Hey, " + owners.joinToString(separator = " ") { + it.value + } + ", y'all were pinged by " + pinger.mention + '!' + } +} + +suspend fun MessageChannelBehavior.ping(user: UserBehavior) = createMessage { + allowedMentions { users.add(user.id) } + content = user.mention +}.delete(reason = "Ghost pinged " + user.mention) + +val MessageChannel.owners get() = data.topic.value?.let { userMentionRegex.findAll(it) } + +suspend fun Member.getDeniedPermissions() = Permissions.ALL - getPermissions() From 32f90dbca21bd118d1ac91be7a23ee3252768a88 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 13 Dec 2023 18:46:58 +0200 Subject: [PATCH 18/26] Added `/about` --- .idea/inspectionProfiles/Project_Default.xml | 6 + LICENSE.md | 356 ++++++++++++++++++ .../laxla/supertrouper/AboutExtension.kt | 117 +++++- .../kotlin/quest/laxla/supertrouper/App.kt | 6 + .../laxla/supertrouper/TargetedArguments.kt | 1 - .../kotlin/quest/laxla/supertrouper/Utils.kt | 27 ++ .../messaging/PrivateMassagingExtension.kt | 12 +- .../messaging/PrivateMessaging.kt | 2 - 8 files changed, 515 insertions(+), 12 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 LICENSE.md create mode 100644 src/main/kotlin/quest/laxla/supertrouper/Utils.kt diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..10b773b --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..bfa8437 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,356 @@ +Mozilla Public License Version 2.0 +================================== + +### 1. Definitions + +**1.1. “Contributor”** +means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software. + +**1.2. “Contributor Version”** +means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor's Contribution. + +**1.3. “Contribution”** +means Covered Software of a particular Contributor. + +**1.4. “Covered Software”** +means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof. + +**1.5. “Incompatible With Secondary Licenses”** +means + +* **(a)** that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or +* **(b)** that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +**1.6. “Executable Form”** +means any form of the work other than Source Code Form. + +**1.7. “Larger Work”** +means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software. + +**1.8. “License”** +means this document. + +**1.9. “Licensable”** +means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + +**1.10. “Modifications”** +means any of the following: + +* **(a)** any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or +* **(b)** any new file in Source Code Form that contains any Covered + Software. + +**1.11. “Patent Claims” of a Contributor** +means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version. + +**1.12. “Secondary License”** +means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses. + +**1.13. “Source Code Form”** +means the form of the work preferred for making modifications. + +**1.14. “You” (or “Your”)** +means an individual or a legal entity exercising rights under this +License. For legal entities, “You” includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, “control” means **(a)** the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or **(b)** ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + + +### 2. License Grants and Conditions + +#### 2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +* **(a)** under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and +* **(b)** under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +#### 2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +#### 2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +* **(a)** for any code that a Contributor has removed from Covered Software; + or +* **(b)** for infringements caused by: **(i)** Your and any other third party's + modifications of Covered Software, or **(ii)** the combination of its + Contributions with other software (except as part of its Contributor + Version); or +* **(c)** under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +#### 2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +#### 2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +#### 2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +#### 2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + + +### 3. Responsibilities + +#### 3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +#### 3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +* **(a)** such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +* **(b)** You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +#### 3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +#### 3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +#### 3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + + +### 4. Inability to Comply Due to Statute or Regulation + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: **(a)** comply with +the terms of this License to the maximum extent possible; and **(b)** +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + + +### 5. Termination + +**5.1.** The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated **(a)** provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and **(b)** on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +**5.2.** If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + + +### 6. Disclaimer of Warranty + +> Covered Software is provided under this License on an “as is” +> basis, without warranty of any kind, either expressed, implied, or +> statutory, including, without limitation, warranties that the +> Covered Software is free of defects, merchantable, fit for a +> particular purpose or non-infringing. The entire risk as to the +> quality and performance of the Covered Software is with You. +> Should any Covered Software prove defective in any respect, You +> (not any Contributor) assume the cost of any necessary servicing, +> repair, or correction. This disclaimer of warranty constitutes an +> essential part of this License. No use of any Covered Software is +> authorized under this License except under this disclaimer. + +### 7. Limitation of Liability + +> Under no circumstances and under no legal theory, whether tort +> (including negligence), contract, or otherwise, shall any +> Contributor, or anyone who distributes Covered Software as +> permitted above, be liable to You for any direct, indirect, +> special, incidental, or consequential damages of any character +> including, without limitation, damages for lost profits, loss of +> goodwill, work stoppage, computer failure or malfunction, or any +> and all other commercial damages or losses, even if such party +> shall have been informed of the possibility of such damages. This +> limitation of liability shall not apply to liability for death or +> personal injury resulting from such party's negligence to the +> extent applicable law prohibits such limitation. Some +> jurisdictions do not allow the exclusion or limitation of +> incidental or consequential damages, so this exclusion and +> limitation may not apply to You. + + +### 8. Litigation + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + + +### 9. Miscellaneous + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + + +### 10. Versions of the License + +#### 10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +#### 10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +#### 10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +#### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +## Exhibit A - Source Code Form License Notice + + 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 http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +## Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + diff --git a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt index 1761e11..d44b39d 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt @@ -1,8 +1,123 @@ package quest.laxla.supertrouper +import com.kotlindiscord.kord.extensions.components.components +import com.kotlindiscord.kord.extensions.components.disabledButton +import com.kotlindiscord.kord.extensions.components.linkButton +import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand +import dev.kord.common.entity.ButtonStyle +import dev.kord.core.entity.effectiveName +import dev.kord.rest.builder.message.allowedMentions +import dev.kord.rest.builder.message.create.AbstractMessageCreateBuilder +import dev.kord.rest.builder.message.embed +import quest.laxla.supertrouper.messaging.PrivateMessagesCategoryName + class AboutExtension : TrouperExtension() { override suspend fun setup() { - TODO("Not yet implemented") + ephemeralSlashCommand { + name = "about" + description = "About Super Trouper" + + action { + respond { + about() + } + } + } } + + suspend fun AbstractMessageCreateBuilder.about() { + val self = kord.getSelf() + val avatar = (self.avatar ?: self.defaultAvatar).cdnUrl.toUrl() + val mention = self.mention + allowedMentions() + + embed { + title = "About ${self.effectiveName}" + thumbnail { url = avatar } + + if (isDevelopmentEnvironment) field { + name = "Development Environment" + //language=Markdown + value = "> This instance is hosted on someone's personal computer, " + + "and *may* contain **malicious code** and/or **steal your data**. " + + "This is not considered to be an official version of Super Trouper. " + + "Do *not* rely on the lack of this message to determine if an instance is official." + } + + //language=Markdown + description = "$mention is an *open-source* bot made by the plural community, for the plural community.\n\n" + + "$mention creates a private channel for members, " + + "allowing them to talk to the server's owners or moderators. " + + "Use `/pm` to get a link to your PM channel. " + + "PM channels inherit their permissions from the `$PrivateMessagesCategoryName` category, " + + "and can be synced by moderators (`Manage Permissions` is required by default).\n\n" + + "$mention is free, open source software. You can host the bot on your own server, " + + "without paying us a penny. We know not everyone can afford that, so we host it for you, " + + "using our own money. Please, help us making $mention available for everyone, everywhere, for free." + } + + components { + val app = kord.getApplicationInfo() + + disabledButton { + style = ButtonStyle.Primary + label = "About" + } + + app.inviteUrl?.let { + linkButton { + url = it + label = "Invite" + } + } + + officialServerUrl?.let { + linkButton { + url = it + label = "Join" + } + } + + donateUrl?.let { + linkButton { + url = it + label = "Donate" + } + } + + repoUrl?.let { + linkButton { + url = it + label = "Contribute" + } + } + + disabledButton(row = 1) { + style = ButtonStyle.Primary + label = "Legal" + } + + licenseUrl?.let { + linkButton(row = 1) { + url = it + label = license ?: "License" + } + } + + app.privacyPolicyUrl?.let { + linkButton(row = 1) { + url = it + label = "Privacy Policy" + } + } + + app.termsOfServiceUrl?.let { + linkButton(row = 1) { + url = it + label = "Terms of Service" + } + } + } + } } diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/supertrouper/App.kt index 8754092..fa21331 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/App.kt @@ -10,6 +10,11 @@ import quest.laxla.supertrouper.messaging.PrivateMassagingExtension private val token = env("TOKEN") val officialServer = env("OFFICIAL_SERVER") val isDevelopmentEnvironment = envOrNull("IS_DEV_ENV").toBoolean() +val officialServerUrl = envOrNull("OFFICIAL_SERVER_URL") +val license = envOrNull("LICENSE") +val licenseUrl = envOrNull("LICENSE_URL") +val donateUrl = envOrNull("DONATE_URL") +val repoUrl = envOrNull("REPO_URL") fun main() = runBlocking { ExtensibleBot(token) { @@ -21,6 +26,7 @@ fun main() = runBlocking { add(::MaintenanceExtension) @OptIn(PrivilegedIntent::class) add(::PrivateMassagingExtension) + add(::AboutExtension) } }.start() } diff --git a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt index 52a8fe5..55325df 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt @@ -3,7 +3,6 @@ package quest.laxla.supertrouper import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalMember -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalUser import com.kotlindiscord.kord.extensions.components.forms.ModalForm private const val TargetArgumentName = "target" diff --git a/src/main/kotlin/quest/laxla/supertrouper/Utils.kt b/src/main/kotlin/quest/laxla/supertrouper/Utils.kt new file mode 100644 index 0000000..a898daa --- /dev/null +++ b/src/main/kotlin/quest/laxla/supertrouper/Utils.kt @@ -0,0 +1,27 @@ +package quest.laxla.supertrouper + +import dev.kord.core.entity.Application +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.text.buildString as buildStringKt + +@OptIn(ExperimentalContracts::class) +inline fun T.buildString(capacity: Int? = null, builderAction: StringBuilder.(T) -> Unit): String { + contract { + callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) + } + + return if (capacity == null) buildStringKt { builderAction(this@buildString) } + else buildStringKt(capacity) { builderAction(this@buildString) } +} + + +val Application.inviteUrl get() = customInstallUrl ?: installParams?.buildString { + append("https://discord.com/api/oauth2/authorize?client_id=") + append(id) + append("&permissions=") + append(it.permissions.code.value) + append("&scope=") + append(it.scopes.joinToString(separator = "+")) +} diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt index 365ebef..d920067 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt @@ -4,31 +4,27 @@ import com.kotlindiscord.kord.extensions.checks.anyGuild import com.kotlindiscord.kord.extensions.checks.isNotBot import com.kotlindiscord.kord.extensions.extensions.* import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext -import com.kotlindiscord.kord.extensions.utils.any -import dev.kord.common.entity.* +import dev.kord.common.entity.ButtonStyle +import dev.kord.common.entity.DiscordPartialEmoji +import dev.kord.common.entity.OverwriteType +import dev.kord.common.entity.Permission import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.UserBehavior -import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.channel.createTextChannel import dev.kord.core.behavior.channel.edit import dev.kord.core.behavior.createCategory -import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic -import dev.kord.core.entity.Member -import dev.kord.core.entity.Role import dev.kord.core.entity.User import dev.kord.core.entity.channel.Category import dev.kord.core.entity.channel.TextChannel import dev.kord.core.event.guild.MemberJoinEvent import dev.kord.core.event.interaction.GuildButtonInteractionCreateEvent -import dev.kord.core.event.interaction.GuildSelectMenuInteractionCreateEvent import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent import dev.kord.rest.builder.channel.addMemberOverwrite import dev.kord.rest.builder.channel.addRoleOverwrite import dev.kord.rest.builder.message.actionRow -import dev.kord.rest.builder.message.create.AbstractMessageCreateBuilder import dev.kord.rest.builder.message.embed import kotlinx.coroutines.flow.count import quest.laxla.supertrouper.* diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt index 25f858c..65438a5 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt +++ b/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt @@ -20,9 +20,7 @@ import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.firstOrNull const val PrivateMessagesCategoryName = "Private Messages" -const val InviteSelect = "PM.invite" const val PingButton = "PM.ping" -const val ManagePmButton = "PM.manage" const val UserIdCapturingGroup = "userID" val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30 val pmMemberPermissions = Permission.ViewChannel + Permission.ReadMessageHistory From 63f103539edbdb6d27691defbce8266540a45881 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Wed, 13 Dec 2023 18:59:36 +0200 Subject: [PATCH 19/26] Created README.md, removed useless gradle plugins. --- README.md | 69 +--- build.gradle.kts | 10 - detekt.yml | 679 -------------------------------------- gradle.properties | 2 - gradle/libs.versions.toml | 22 -- settings.gradle.kts | 7 - 6 files changed, 16 insertions(+), 773 deletions(-) delete mode 100644 detekt.yml delete mode 100644 gradle/libs.versions.toml diff --git a/README.md b/README.md index 107d1ca..2667a3b 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,17 @@ -# KordEx Bot Template - -This repository contains a basic KordEx example bot for you to use as a template for your own KordEx bots. This -includes the following: - -* A basic extension that allows you to slap other people, using both message commands and slash commands. -* A basic bot configuration that enables slash commands and shows you how to conditionally provide a different - message command prefix for different guilds. -* A Gradle Kotlin build script that uses the Kotlin Discord public maven repo, Detekt for linting (with a - fairly strict configuration) and a Git commit hook plugin that runs Detekt when you make a commit - this uses Gradle - 7's new version catalog feature, for easy configuration of dependencies. -* GitHub CI scripts that build the bot and publish its artefacts. -* A reasonable `.gitignore` file, including one in the `.idea` folder that ignores files that you shouldn't commit - - if you're using IDEA yourself, you should install the Ignore plugin to handle changes to this for you. -* A Groovy-based Logback config, so you've reasonable logging out of the box. - -**Note:** This template includes a `.editorconfig` file that defaults to using tabs for indentation in almost all file -types. This is because tabs are more accessible for the blind, or those with impaired vision. We won't accept -feedback or PRs targeting this approach. - -## Potential Changes - -* The `.yml` files in `.github/` are used to configure GitHub apps. If you're not using them, you can remove them. -* The provided `LICENSE` file contains The Unlicense, which makes this repository public domain. You will probably want - to change this - we suggest looking at [Choose a License](https://choosealicense.com/) if you're not sure where to start. -* In the `build.gradle.kts`: - * Set the `group` and `version` properties as appropriate. - * If you're not using this to test KordEx builds, you can remove the `mavenLocal()` from the `repositories` block. - * In the `application` and `tasks.jar` blocks, update the main class path/name as appropriate. - * To target a newer/older Java version, change the options in the `KotlinCompile` configuration and `java` blocks -* In the `settings.gradle.kts`, update the name of the root project as appropriate. -* The bundled Detekt config is pretty strict - you can check over `detekt.yml` if you want to change it, but you need to - follow the TODOs in that file regardless. -* The Logback configuration is in `src/main/resources/logback.groovy`. If the logging setup doesn't suit, you can change - it there. - -## Bundled Bot - -* `App.kt` includes a basic bot, which uses environment variables (or variables in a `.env` file) for the testing guild - ID (`TEST_SERVER`) and the bot's token (`TOKEN`). You can specify these either directly as environment variables, or - as `KEY=value` pairs in a file named `.env`. Some example code is also included that shows one potential way of - providing different command prefixes for different servers. -* `TestExtension.kt` includes an example extension that creates a `slap` command - this command works as both a - message command and slash command, and allows you to slap other users with whatever you wish, defaulting to a - `large, smelly trout`. - -To test the bot, we recommend using a `.env` file that looks like the following: - -```dotenv -TOKEN=abc... -TEST_SERVER=123... +# Super Trouper + +## Running + +Create a `.env` file before running the bot. + +```properties +TOKEN= +AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT=30 +IS_DEV_ENV=true +DONATE_URL= +LICENSE=Mozilla Public License 2.0 +LICENSE_URL=https://www.mozilla.org/en-US/MPL/2.0/ +OFFICIAL_SERVER=1130171551636004864 +OFFICIAL_SERVER_URL=https://discord.gg/xJu6MH2KUc +REPO_URL=https://github.com/LaylaMeower/SuperTrouper ``` - -Create this file, fill it out, and run the `run` gradle task for testing in development. diff --git a/build.gradle.kts b/build.gradle.kts index 59cfa0d..ff6f6d0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,6 @@ plugins { kotlin("plugin.serialization") id("com.github.johnrengelman.shadow") - id("io.gitlab.arturbosch.detekt") } group = "quest.laxla" @@ -26,7 +25,6 @@ repositories { } } -val detekt: String by project val kordex: String by project val serialization: String by project val logback: String by project @@ -43,8 +41,6 @@ dependencies { implementation("io.github.oshai:kotlin-logging:$klogging") runtimeOnly("org.slf4j:slf4j-api:$slf4j") runtimeOnly("ch.qos.logback:logback-classic:$logback") - - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detekt") } application { @@ -68,9 +64,3 @@ java { sourceCompatibility = java targetCompatibility = java } - -detekt { - buildUponDefaultConfig = true - - config.from(rootProject.files("detekt.yml")) -} diff --git a/detekt.yml b/detekt.yml deleted file mode 100644 index e3ddb48..0000000 --- a/detekt.yml +++ /dev/null @@ -1,679 +0,0 @@ -# TODO: Update `rootPackage` in naming -> InvalidPackageDeclaration - -build: - maxIssues: 0 - excludeCorrectable: false - weights: - # complexity: 2 - # LongParameterList: 1 - # style: 1 - # comments: 1 - -config: - validation: true - # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' - excludes: '' - -processors: - active: true - exclude: - - 'DetektProgressListener' - - 'FunctionCountProcessor' - - 'PropertyCountProcessor' - - 'ClassCountProcessor' - - 'PackageCountProcessor' - - 'KtFileCountProcessor' - -console-reports: - active: true - exclude: - - 'ProjectStatisticsReport' - - 'NotificationReport' - - 'FileBasedFindingsReport' - -output-reports: - active: true - exclude: - # - 'HtmlOutputReport' - - 'TxtOutputReport' - # - 'XmlOutputReport' - -comments: - active: true - - AbsentOrWrongFileLicense: - active: false - licenseTemplateFile: 'license.template' - CommentOverPrivateFunction: - active: false - CommentOverPrivateProperty: - active: false - EndOfSentenceFormat: - active: true - endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - UndocumentedPublicClass: - active: false - searchInNestedClass: true - searchInInnerClass: true - searchInInnerObject: true - searchInInnerInterface: true - UndocumentedPublicFunction: - active: false - UndocumentedPublicProperty: - active: false - -complexity: - active: true - ComplexCondition: - active: true - threshold: 10 - ComplexInterface: - active: false - threshold: 10 - includeStaticDeclarations: false - includePrivateDeclarations: false - ComplexMethod: - active: false - threshold: 15 - ignoreSingleWhenExpression: false - ignoreSimpleWhenEntries: false - ignoreNestingFunctions: false - nestingFunctions: [ run, let, apply, with, also, use, forEach, isNotNull, ifNull ] - LabeledExpression: - active: false - ignoredLabels: [ ] - LargeClass: - active: false - threshold: 600 - LongMethod: - active: false - threshold: 60 - LongParameterList: - active: false - functionThreshold: 6 - constructorThreshold: 7 - ignoreDefaultParameters: false - ignoreDataClasses: true - ignoreAnnotated: [ ] - MethodOverloading: - active: false - threshold: 6 - NestedBlockDepth: - active: false - threshold: 4 - ReplaceSafeCallChainWithRun: - active: true - StringLiteralDuplication: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - threshold: 3 - ignoreAnnotation: true - excludeStringsWithLessThan5Characters: true - ignoreStringsRegex: '$^' - TooManyFunctions: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - thresholdInFiles: 11 - thresholdInClasses: 11 - thresholdInInterfaces: 11 - thresholdInObjects: 11 - thresholdInEnums: 11 - ignoreDeprecated: false - ignorePrivate: false - ignoreOverridden: false - -coroutines: - active: true - GlobalCoroutineUsage: - active: true - RedundantSuspendModifier: - active: true - SuspendFunWithFlowReturnType: - active: true - -empty-blocks: - active: true - EmptyCatchBlock: - active: true - allowedExceptionNameRegex: '^(_|(ignore|expected).*)' - EmptyClassBlock: - active: true - EmptyDefaultConstructor: - active: true - EmptyDoWhileBlock: - active: true - EmptyElseBlock: - active: true - EmptyFinallyBlock: - active: true - EmptyForBlock: - active: true - EmptyFunctionBlock: - active: true - ignoreOverridden: false - EmptyIfBlock: - active: true - EmptyInitBlock: - active: true - EmptyKtFile: - active: true - EmptySecondaryConstructor: - active: true - EmptyTryBlock: - active: true - EmptyWhenBlock: - active: true - EmptyWhileBlock: - active: true - -exceptions: - active: true - ExceptionRaisedInUnexpectedLocation: - active: true - methodNames: [ toString, hashCode, equals, finalize ] - InstanceOfCheckForException: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - NotImplementedDeclaration: - active: false - PrintStackTrace: - active: true - RethrowCaughtException: - active: true - ReturnFromFinally: - active: true - ignoreLabeled: false - SwallowedException: - active: false - ignoredExceptionTypes: - - InterruptedException - - NumberFormatException - - ParseException - - MalformedURLException - allowedExceptionNameRegex: '^(_|(ignore|expected).*)' - ThrowingExceptionFromFinally: - active: true - ThrowingExceptionInMain: - active: true - ThrowingExceptionsWithoutMessageOrCause: - active: true - exceptions: - - IllegalArgumentException - - IllegalStateException - - IOException - ThrowingNewInstanceOfSameException: - active: true - TooGenericExceptionCaught: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - exceptionNames: - - ArrayIndexOutOfBoundsException - - Error - - Exception - - IllegalMonitorStateException - - NullPointerException - - IndexOutOfBoundsException - - RuntimeException - - Throwable - allowedExceptionNameRegex: '^(_|(ignore|expected).*)' - TooGenericExceptionThrown: - active: true - exceptionNames: - - Error - - Exception - - Throwable - - RuntimeException - -formatting: - active: true - android: false - autoCorrect: true - AnnotationOnSeparateLine: - active: true - autoCorrect: true - AnnotationSpacing: - active: true - autoCorrect: true - ArgumentListWrapping: - active: false # It's wrong! - autoCorrect: true - ChainWrapping: - active: true - autoCorrect: true - CommentSpacing: - active: true - autoCorrect: true - EnumEntryNameCase: - active: true - autoCorrect: true - Filename: - active: true - FinalNewline: - active: true - autoCorrect: true - insertFinalNewLine: true - ImportOrdering: - active: true - autoCorrect: true - layout: "*,java.**,javax.**,kotlin.**,^" - Indentation: - active: false - autoCorrect: false - indentSize: 4 - continuationIndentSize: 4 - MaximumLineLength: - active: true - maxLineLength: 120 - ModifierOrdering: - active: true - autoCorrect: true - MultiLineIfElse: - active: true - autoCorrect: true - NoBlankLineBeforeRbrace: - active: true - autoCorrect: true - NoConsecutiveBlankLines: - active: true - autoCorrect: true - NoEmptyClassBody: - active: true - autoCorrect: true - NoEmptyFirstLineInMethodBlock: - active: true - autoCorrect: true - NoLineBreakAfterElse: - active: true - autoCorrect: true - NoLineBreakBeforeAssignment: - active: true - autoCorrect: true - NoMultipleSpaces: - active: false - autoCorrect: false - NoSemicolons: - active: true - autoCorrect: true - NoTrailingSpaces: - active: true - autoCorrect: true - NoUnitReturn: - active: true - autoCorrect: true - NoUnusedImports: - active: true - autoCorrect: true - NoWildcardImports: - active: false - PackageName: - active: true - autoCorrect: true - ParameterListWrapping: - active: true - autoCorrect: true - indentSize: 4 - SpacingAroundColon: - active: true - autoCorrect: true - SpacingAroundComma: - active: true - autoCorrect: true - SpacingAroundCurly: - active: true - autoCorrect: true - SpacingAroundDot: - active: true - autoCorrect: true - SpacingAroundDoubleColon: - active: true - autoCorrect: true - SpacingAroundKeyword: - active: true - autoCorrect: true - SpacingAroundOperators: - active: true - autoCorrect: true - SpacingAroundParens: - active: true - autoCorrect: true - SpacingAroundRangeOperator: - active: true - autoCorrect: true - SpacingBetweenDeclarationsWithAnnotations: - active: true - autoCorrect: true - SpacingBetweenDeclarationsWithComments: - active: true - autoCorrect: true - StringTemplate: - active: true - autoCorrect: true - -naming: - active: true - ClassNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - classPattern: '[A-Z$][a-zA-Z0-9$]*' - ConstructorParameterNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - parameterPattern: '[a-z][A-Za-z0-9]*' - privateParameterPattern: '[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - ignoreOverridden: true - EnumNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' - ForbiddenClassName: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - forbiddenName: [ ] - FunctionMaxLength: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - maximumFunctionNameLength: 30 - FunctionMinLength: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - minimumFunctionNameLength: 3 - FunctionNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' - excludeClassPattern: '$^' - ignoreOverridden: true - FunctionParameterNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - parameterPattern: '[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - ignoreOverridden: true - - InvalidPackageDeclaration: - active: true - # TODO: Update this with your project's base package - rootPackage: 'template' - - MatchingDeclarationName: - active: true - mustBeFirst: true - MemberNameEqualsClassName: - active: true - ignoreOverridden: true - NonBooleanPropertyPrefixedWithIs: - active: true - ObjectPropertyNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - constantPattern: '[A-Za-z][_A-Za-z0-9]*' - propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' - PackageNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$' - TopLevelPropertyNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - constantPattern: '[A-Z][_A-Z0-9]*' - propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' - VariableMaxLength: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - maximumVariableNameLength: 64 - VariableMinLength: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - minimumVariableNameLength: 1 - VariableNaming: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - variablePattern: '[a-z][A-Za-z0-9]*' - privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - ignoreOverridden: true - -performance: - active: true - ArrayPrimitive: - active: true - ForEachOnRange: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - SpreadOperator: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - UnnecessaryTemporaryInstantiation: - active: true - -potential-bugs: - active: true - Deprecation: - active: true - DuplicateCaseInWhenExpression: - active: true - EqualsAlwaysReturnsTrueOrFalse: - active: true - EqualsWithHashCodeExist: - active: true - ExplicitGarbageCollectionCall: - active: true - HasPlatformType: - active: true - IgnoredReturnValue: - active: true - ImplicitDefaultLocale: - active: false - ImplicitUnitReturnType: - active: true - allowExplicitReturnType: true - InvalidRange: - active: true - IteratorHasNextCallsNextMethod: - active: true - IteratorNotThrowingNoSuchElementException: - active: true - LateinitUsage: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - excludeAnnotatedProperties: [ ] - ignoreOnClassesPattern: '' - MapGetWithNotNullAssertionOperator: - active: true - MissingWhenCase: - active: true - NullableToStringCall: - active: true - RedundantElseInWhen: - active: true - UnconditionalJumpStatementInLoop: - active: true - UnnecessaryNotNullOperator: - active: true - UnnecessarySafeCall: - active: true - UnreachableCode: - active: true - UnsafeCallOnNullableType: - active: true - UnsafeCast: - active: true - UselessPostfixExpression: - active: true - WrongEqualsTypeParameter: - active: true - -style: - active: true - ClassOrdering: - active: true - CollapsibleIfStatements: - active: true - DataClassContainsFunctions: - active: true - conversionFunctionPrefix: 'to' - DataClassShouldBeImmutable: - active: true - EqualsNullCall: - active: true - EqualsOnSignatureLine: - active: true - ExplicitCollectionElementAccessMethod: - active: true - ExplicitItLambdaParameter: - active: true - ExpressionBodySyntax: - active: true - includeLineWrapping: false - ForbiddenComment: - active: false - values: [ 'TODO:', 'FIXME:', 'STOPSHIP:' ] - allowedPatterns: '' - ForbiddenImport: - active: false - imports: [ ] - forbiddenPatterns: '' - ForbiddenMethodCall: - active: false - methods: [ ] - ForbiddenPublicDataClass: - active: false - ignorePackages: [ '*.internal', '*.internal.*' ] - ForbiddenVoid: - active: true - ignoreOverridden: true - ignoreUsageInGenerics: false - FunctionOnlyReturningConstant: - active: true - ignoreOverridableFunction: true - excludedFunctions: 'describeContents' - excludeAnnotatedFunction: [ 'dagger.Provides' ] - LibraryCodeMustSpecifyReturnType: - active: true - LibraryEntitiesShouldNotBePublic: - active: true - LoopWithTooManyJumpStatements: - active: true - maxJumpCount: 3 - MagicNumber: - active: true - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - ignoreNumbers: [ '-1', '0', '1', '2' ] - ignoreHashCodeFunction: true - ignorePropertyDeclaration: false - ignoreLocalVariableDeclaration: false - ignoreConstantDeclaration: true - ignoreCompanionObjectPropertyDeclaration: true - ignoreAnnotation: true - ignoreNamedArgument: true - ignoreEnums: true - ignoreRanges: false - MandatoryBracesIfStatements: - active: true - MandatoryBracesLoops: - active: true - MaxLineLength: - active: true - maxLineLength: 120 - excludePackageStatements: true - excludeImportStatements: true - excludeCommentStatements: false - MayBeConst: - active: true - ModifierOrder: - active: true - NestedClassesVisibility: - active: true - NewLineAtEndOfFile: - active: true - NoTabs: - active: false - OptionalAbstractKeyword: - active: true - OptionalUnit: - active: false - OptionalWhenBraces: - active: true - PreferToOverPairSyntax: - active: true - ProtectedMemberInFinalClass: - active: true - RedundantExplicitType: - active: true - RedundantVisibilityModifierRule: - active: false - ReturnCount: - active: false - max: 2 - excludedFunctions: 'equals' - excludeLabeled: false - excludeReturnFromLambda: true - excludeGuardClauses: false - SafeCast: - active: true - SerialVersionUIDInSerializableClass: - active: true - SpacingBetweenPackageAndImports: - active: true - ThrowsCount: - active: false - max: 2 - TrailingWhitespace: - active: true - UnderscoresInNumericLiterals: - active: true - acceptableDecimalLength: 5 - UnnecessaryAbstractClass: - active: true - excludeAnnotatedClasses: [ 'dagger.Module' ] - UnnecessaryAnnotationUseSiteTarget: - active: true - UnnecessaryApply: - active: true - UnnecessaryInheritance: - active: true - UnnecessaryLet: - active: true - UnnecessaryParentheses: - active: true - UntilInsteadOfRangeTo: - active: true - UnusedImports: - active: true - UnusedPrivateClass: - active: true - UnusedPrivateMember: - active: true - allowedNames: '(_|ignored|expected|serialVersionUID)' - UseArrayLiteralsInAnnotations: - active: true - UseCheckNotNull: - active: true - UseCheckOrError: - active: true - UseDataClass: - active: true - excludeAnnotatedClasses: [ ] - allowVars: false - UseEmptyCounterpart: - active: true - UseIfInsteadOfWhen: - active: true - UseRequire: - active: true - UseRequireNotNull: - active: true - UselessCallOnNotNull: - active: true - UtilityClassWithPublicConstructor: - active: true - VarCouldBeVal: - active: true - WildcardImport: - active: false - excludes: [ '**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt' ] - excludeImports: [ 'java.util.*', 'kotlinx.android.synthetic.*' ] diff --git a/gradle.properties b/gradle.properties index e3d93cd..06fa7f9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,9 +5,7 @@ kotlin.incremental=true org.gradle.kotlin.dsl.skipMetadataVersionCheck=false kotlin=1.9.21 -detekt=1.23.4 shadow=8.1.1 -hooks=0.0.2 jvm=17 kordex=1.6.0 serialization=1.6.2 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index e565e09..0000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,22 +0,0 @@ -[versions] -detekt = "1.23.1" # Note: Plugin versions must be updated in the settings.gradle.kts too -kotlin = "1.9.0" # Note: Plugin versions must be updated in the settings.gradle.kts too - -groovy = "3.0.14" -jansi = "2.4.0" -kord-extensions = "1.5.9-SNAPSHOT" -kx-ser = "1.5.1" -logging = "3.0.5" -logback = "1.4.5" -logback-groovy = "1.14.4" - -[libraries] -detekt = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } -groovy = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" } -jansi = { module = "org.fusesource.jansi:jansi", version.ref = "jansi" } -kord-extensions = { module = "com.kotlindiscord.kord.extensions:kord-extensions", version.ref = "kord-extensions" } -kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } -kx-ser = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kx-ser" } -logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } -logback-groovy = { module = "io.github.virtualdogbert:logback-groovy-config", version.ref = "logback-groovy" } -logging = { module = "io.github.microutils:kotlin-logging", version.ref = "logging" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 36d6c28..929c86d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,18 +1,11 @@ pluginManagement { val kotlin: String by settings - val detekt: String by settings val shadow: String by settings - val hooks: String by settings plugins { - // Update this in libs.version.toml when you change it here. kotlin("jvm") version kotlin kotlin("plugin.serialization") version kotlin - // Update this in libs.version.toml when you change it here. - id("io.gitlab.arturbosch.detekt") version detekt - - id("com.github.jakemarsden.git-hooks") version hooks id("com.github.johnrengelman.shadow") version shadow } } From 088dcecf12623cfb3e1f48ec8536c231d74b7b82 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Thu, 14 Dec 2023 16:29:15 +0200 Subject: [PATCH 20/26] Renamed bot to Trouper --- .github/workflows/ci.yml | 13 ++++++++----- .github/workflows/develop.yml | 13 ++++++++----- .github/workflows/root.yml | 13 ++++++++----- .gitignore | 1 + .idea/.name | 1 - build.gradle.kts | 2 +- settings.gradle.kts | 2 +- .../{supertrouper => trouper}/AboutExtension.kt | 7 +++---- .../quest/laxla/{supertrouper => trouper}/App.kt | 4 ++-- .../MaintenanceExtension.kt | 2 +- .../laxla/{supertrouper => trouper}/Overwrites.kt | 2 +- .../{supertrouper => trouper}/TargetedArguments.kt | 2 +- .../{supertrouper => trouper}/TrouperExtension.kt | 2 +- .../quest/laxla/{supertrouper => trouper}/Utils.kt | 2 +- .../messaging/PrivateMassagingExtension.kt | 4 ++-- .../messaging/PrivateMessaging.kt | 2 +- 16 files changed, 40 insertions(+), 32 deletions(-) delete mode 100644 .idea/.name rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/AboutExtension.kt (94%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/App.kt (89%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/MaintenanceExtension.kt (94%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/Overwrites.kt (98%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/TargetedArguments.kt (95%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/TrouperExtension.kt (91%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/Utils.kt (96%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/messaging/PrivateMassagingExtension.kt (98%) rename src/main/kotlin/quest/laxla/{supertrouper => trouper}/messaging/PrivateMessaging.kt (98%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d521601..d13c2bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,13 +13,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: - java-version: 1.17 + java-version: 17 + distribution: temurin - name: Gradle (Build) uses: gradle/gradle-build-action@v2 @@ -28,15 +29,17 @@ jobs: arguments: build - name: Upload artifacts (Main JAR) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Main JAR path: build/libs/*-all.jar + if-no-files-found: error - name: Upload artifacts (JARs) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: JARs path: build/libs/*.jar + if-no-files-found: warn diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 409f750..33828cf 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -10,13 +10,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: - java-version: 1.17 + java-version: 17 + distribution: temurin - name: Gradle (Build) uses: gradle/gradle-build-action@v2 @@ -25,15 +26,17 @@ jobs: arguments: build - name: Upload artifacts (Main JAR) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Main JAR path: build/libs/*-all.jar + if-no-files-found: error - name: Upload artifacts (JARs) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: JARs path: build/libs/*.jar + if-no-files-found: warn diff --git a/.github/workflows/root.yml b/.github/workflows/root.yml index da08301..2a005ff 100644 --- a/.github/workflows/root.yml +++ b/.github/workflows/root.yml @@ -10,13 +10,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: - java-version: 1.17 + java-version: 17 + distribution: temurin - name: Gradle (Build) uses: gradle/gradle-build-action@v2 @@ -26,15 +27,17 @@ jobs: dependency-graph: generate-and-submit - name: Upload artifacts (Main JAR) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Main JAR path: build/libs/*-all.jar + if-no-files-found: error - name: Upload artifacts (JARs) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: JARs path: build/libs/*.jar + if-no-files-found: warn diff --git a/.gitignore b/.gitignore index e798797..cfd0ada 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ hs_err_pid* # Generated files .idea/**/contentModel.xml +.idea/.name # Sensitive or high-churn files .idea/**/dataSources/ diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 24bc394..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -Super Trouper diff --git a/build.gradle.kts b/build.gradle.kts index ff6f6d0..5afb90e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,7 +44,7 @@ dependencies { } application { - mainClass = "quest.laxla.supertrouper.AppKt" + mainClass = "quest.laxla.trouper.AppKt" } val jvm: String by project diff --git a/settings.gradle.kts b/settings.gradle.kts index 929c86d..522a0be 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,4 +10,4 @@ pluginManagement { } } -rootProject.name = "supertrouper" +rootProject.name = "Trouper" diff --git a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt b/src/main/kotlin/quest/laxla/trouper/AboutExtension.kt similarity index 94% rename from src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt rename to src/main/kotlin/quest/laxla/trouper/AboutExtension.kt index d44b39d..9b85282 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/AboutExtension.kt +++ b/src/main/kotlin/quest/laxla/trouper/AboutExtension.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import com.kotlindiscord.kord.extensions.components.components import com.kotlindiscord.kord.extensions.components.disabledButton @@ -9,7 +9,7 @@ import dev.kord.core.entity.effectiveName import dev.kord.rest.builder.message.allowedMentions import dev.kord.rest.builder.message.create.AbstractMessageCreateBuilder import dev.kord.rest.builder.message.embed -import quest.laxla.supertrouper.messaging.PrivateMessagesCategoryName +import quest.laxla.trouper.messaging.PrivateMessagesCategoryName class AboutExtension : TrouperExtension() { override suspend fun setup() { @@ -25,7 +25,6 @@ class AboutExtension : TrouperExtension() { } } - suspend fun AbstractMessageCreateBuilder.about() { val self = kord.getSelf() val avatar = (self.avatar ?: self.defaultAvatar).cdnUrl.toUrl() @@ -41,7 +40,7 @@ class AboutExtension : TrouperExtension() { //language=Markdown value = "> This instance is hosted on someone's personal computer, " + "and *may* contain **malicious code** and/or **steal your data**. " + - "This is not considered to be an official version of Super Trouper. " + + "This is not considered to be an official version of Trouper. " + "Do *not* rely on the lack of this message to determine if an instance is official." } diff --git a/src/main/kotlin/quest/laxla/supertrouper/App.kt b/src/main/kotlin/quest/laxla/trouper/App.kt similarity index 89% rename from src/main/kotlin/quest/laxla/supertrouper/App.kt rename to src/main/kotlin/quest/laxla/trouper/App.kt index fa21331..6a31b4b 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/App.kt +++ b/src/main/kotlin/quest/laxla/trouper/App.kt @@ -1,11 +1,11 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.utils.env import com.kotlindiscord.kord.extensions.utils.envOrNull import dev.kord.gateway.PrivilegedIntent import kotlinx.coroutines.runBlocking -import quest.laxla.supertrouper.messaging.PrivateMassagingExtension +import quest.laxla.trouper.messaging.PrivateMassagingExtension private val token = env("TOKEN") val officialServer = env("OFFICIAL_SERVER") diff --git a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt b/src/main/kotlin/quest/laxla/trouper/MaintenanceExtension.kt similarity index 94% rename from src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt rename to src/main/kotlin/quest/laxla/trouper/MaintenanceExtension.kt index 5290458..017b72b 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt +++ b/src/main/kotlin/quest/laxla/trouper/MaintenanceExtension.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import com.kotlindiscord.kord.extensions.checks.isBotAdmin import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand diff --git a/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt b/src/main/kotlin/quest/laxla/trouper/Overwrites.kt similarity index 98% rename from src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt rename to src/main/kotlin/quest/laxla/trouper/Overwrites.kt index 9ffff57..c5aff62 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt +++ b/src/main/kotlin/quest/laxla/trouper/Overwrites.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import dev.kord.common.entity.Overwrite import dev.kord.common.entity.OverwriteType diff --git a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt b/src/main/kotlin/quest/laxla/trouper/TargetedArguments.kt similarity index 95% rename from src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt rename to src/main/kotlin/quest/laxla/trouper/TargetedArguments.kt index 55325df..09a1a99 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt +++ b/src/main/kotlin/quest/laxla/trouper/TargetedArguments.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext diff --git a/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt b/src/main/kotlin/quest/laxla/trouper/TrouperExtension.kt similarity index 91% rename from src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt rename to src/main/kotlin/quest/laxla/trouper/TrouperExtension.kt index 7c06e3e..1435c21 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt +++ b/src/main/kotlin/quest/laxla/trouper/TrouperExtension.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import com.kotlindiscord.kord.extensions.extensions.Extension diff --git a/src/main/kotlin/quest/laxla/supertrouper/Utils.kt b/src/main/kotlin/quest/laxla/trouper/Utils.kt similarity index 96% rename from src/main/kotlin/quest/laxla/supertrouper/Utils.kt rename to src/main/kotlin/quest/laxla/trouper/Utils.kt index a898daa..ccbca93 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/Utils.kt +++ b/src/main/kotlin/quest/laxla/trouper/Utils.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper +package quest.laxla.trouper import dev.kord.core.entity.Application import kotlin.contracts.ExperimentalContracts diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt b/src/main/kotlin/quest/laxla/trouper/messaging/PrivateMassagingExtension.kt similarity index 98% rename from src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt rename to src/main/kotlin/quest/laxla/trouper/messaging/PrivateMassagingExtension.kt index d920067..dac888b 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMassagingExtension.kt +++ b/src/main/kotlin/quest/laxla/trouper/messaging/PrivateMassagingExtension.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper.messaging +package quest.laxla.trouper.messaging import com.kotlindiscord.kord.extensions.checks.anyGuild import com.kotlindiscord.kord.extensions.checks.isNotBot @@ -27,7 +27,7 @@ import dev.kord.rest.builder.channel.addRoleOverwrite import dev.kord.rest.builder.message.actionRow import dev.kord.rest.builder.message.embed import kotlinx.coroutines.flow.count -import quest.laxla.supertrouper.* +import quest.laxla.trouper.* @PrivilegedIntent class PrivateMassagingExtension : TrouperExtension() { diff --git a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt b/src/main/kotlin/quest/laxla/trouper/messaging/PrivateMessaging.kt similarity index 98% rename from src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt rename to src/main/kotlin/quest/laxla/trouper/messaging/PrivateMessaging.kt index 65438a5..48d659d 100644 --- a/src/main/kotlin/quest/laxla/supertrouper/messaging/PrivateMessaging.kt +++ b/src/main/kotlin/quest/laxla/trouper/messaging/PrivateMessaging.kt @@ -1,4 +1,4 @@ -package quest.laxla.supertrouper.messaging +package quest.laxla.trouper.messaging import com.kotlindiscord.kord.extensions.utils.envOrNull import dev.kord.common.entity.ALL From 8ec39582d6e2b728531aa76cfe66662abd967b3c Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Thu, 14 Dec 2023 18:15:33 +0200 Subject: [PATCH 21/26] Assets! --- assets/SuperTrouper.png | Bin 0 -> 259 bytes assets/SuperTrouper.pxo | Bin 0 -> 521 bytes assets/TrouperDev.png | Bin 0 -> 260 bytes assets/TrouperDev.pxo | Bin 0 -> 517 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/SuperTrouper.png create mode 100644 assets/SuperTrouper.pxo create mode 100644 assets/TrouperDev.png create mode 100644 assets/TrouperDev.pxo diff --git a/assets/SuperTrouper.png b/assets/SuperTrouper.png new file mode 100644 index 0000000000000000000000000000000000000000..2785dcbd8c993d411087dd0f21d33917f48c3428 GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|_IkQFhFJ8T zouDt&e+dg#T&Z$4t`uZ z*ZlXt8veS*up-7A%gkryCKyN7ByTADc5Y3IYtouOw=#n6ca%#uocaAXG}q*KMmv=Z;=(hr*B5b|<_ZOXjyIP1NG; ze6;KNmgS6Vq;=X|M6Qd7&sWe^Z(Yn5`_;d5@&?_%_bZrQzX=v^az8etDnm{ Hr-UW|j3{SG literal 0 HcmV?d00001 diff --git a/assets/SuperTrouper.pxo b/assets/SuperTrouper.pxo new file mode 100644 index 0000000000000000000000000000000000000000..ede04f922e006d27a49e2e67e13ac7327e699e5f GIT binary patch literal 521 zcmV+k0`~n!Lr_Kn000005C8zd1^@u@0RR9fwJ-f(!38}J09KYqE}$prKi5}PV_pB? zYP+q-zjsPhN-6Tc+_H*G-Di;y4ruk1BnTdGoW@ZNtWrMTG)N6;_bI zriq*$&uy9%%BVoF`x>wfG)ITQ@J~1a>;i`4_bIm->|kT4 z3JSUHMH8ZMB!%N2gK^D%c5XT=%waSk%)Tb1p#f65zsf$l$J+A7$y&gV<9tm=Lg_#5 zjhfioE&&Z7Ac*9FA$kA>h#W)_4geOEdR%^8!(~zU2{%*LH;j@sA36L?OG2w(0 zQ9!}mVlkD@@sqXs3HC9#{}=bG7htE_IsXsti|p%wd0`mG(EQ6flEd((=8Z+i_;vn} zOJ4>KV{PB3i%Ak;9CcRX@CSwsB`7xzkk+UW7`BBOoeN^g?&Q59DiS3k5`2YqI$F#U z-P8bqW0o;4cOjf0i_DhfL7M((k5dyA^M0cLucp;cXu^@20xRj6A2qQAV<%C}sR(~n LPIN>SM?+9XhY0Ew literal 0 HcmV?d00001 diff --git a/assets/TrouperDev.png b/assets/TrouperDev.png new file mode 100644 index 0000000000000000000000000000000000000000..fd9279fa1258121901b25831fc0966bc2cb59fe6 GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|_IbKEhFJ8T z4Y1~GauAtgS{5A?<;iV2=kO730j~228H%?nnIEx!lM)MXPSV-Y*m?Ha`L5n~{?3VW zvW##3+{0Mc$id2ZVC%h$NzC(4zLIXXK9>`?N#)_1KeseA`T{0oCUMQZs={ftLE7&& zFGGXBOm$hwzpr@(E9HN!O?qm3+-SyS-zC29O*UqE3N1-uWjJv<@U!z&%kve=Dx6-k z3?Z|&96bnriduI1AW5a>FVdQ I&MBb@0K7G2qyPW_ literal 0 HcmV?d00001 diff --git a/assets/TrouperDev.pxo b/assets/TrouperDev.pxo new file mode 100644 index 0000000000000000000000000000000000000000..39546968c5bd3a3dbf615b0e1203b813f26fecf3 GIT binary patch literal 517 zcmV+g0{Z<&Lr_Kn000005C8zb1^@u<0RR9fwJ-f(zXcr+0Cv_#FOVte|9{p0uY&rq z^)}l`B-%TrE2R`cNbbGU-ih49Uu$&)SN>>|P0%z_4Wt$cIRH8UH~`xG#>LONoC>AW zneSrB#R_zb8~e#{D3yyJFxNx?%BV@XE%qHzAaNWC#iL3eWZwL&b=$D9WKp4uQiU5N zuxVnaUomHoCOrfgP^IklDYQVTH_vUF6v~)j_cdTEXpT+-!#`mLun!oH->2MWu!D`E zDo`P}y=X!fjZjQyK$!%zwIc`l8pgYs6iGHq6uWuXkjXa18{;Fx}ygYfRqeKHAn!3$_bDW93-BJ zD42k5VH=gs|CepK3-%whv(=8Au$T5Cf028ay*Pp|Od}eSe@6#9C~r7n0Uf;8#sA;8 ze(5>(dOvF4#4*KOwF%?61I&hu6DRLO$<(TVu@>O4Oz-4SQO-$W9 Date: Thu, 14 Dec 2023 18:24:34 +0200 Subject: [PATCH 22/26] Added scaled versions of files --- assets/SuperTrouper_scaled.png | Bin 0 -> 5871 bytes assets/TrouperDev_scaled.png | Bin 0 -> 5878 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/SuperTrouper_scaled.png create mode 100644 assets/TrouperDev_scaled.png diff --git a/assets/SuperTrouper_scaled.png b/assets/SuperTrouper_scaled.png new file mode 100644 index 0000000000000000000000000000000000000000..d50560346e3949e73f7d67dfcc7aa44c71cb200f GIT binary patch literal 5871 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGXsOzHcuDFkP5~(2Nfqx3uj`z z@cZteqdf_%97+!ChPyr9Gq}`*gd0vKD-uv;K2(F8iVzWK=PR>kT|dh6zVgs z0||!DQa~ahju}Xv$p#V)m7|Ku2#wuL3|1(3oLNX92RY0{=1`g zUu&}M{(Etj5u>FEt;^PR3x(xXw~1+V2F1eDg!AjShd&4y z%>uN~0tG7&vGBgWp8t7O)yFB5ZJ#~lVmO{TTAk9kadA(y!TIjff76=({`@z6&YQEl z^BaS0_g0)bDfhX6^?}1^2B3Wgka?~59oW!${5I9^+x!22wAHIWzl(kl5^uNn)XBK| zAG5%DpuJ-%(?C$qDZ%!`{Zw%i1?}Mp2qsj6 zOdPr3!T&d031Y&42&?n;y;g?eY5h zm`3N(aHe&tdmqWb!1vi!e!a{aU>q%}6w9eP%tPFf#N~-rfP56HOuxJCb?fKWy z7T3Pl>?ztXTB6Xpe2ry%;NidhIIx8I_G{MUzt6wDpY!Hl)effis(IJ9%e<+!iyJL< zXkXX8=Q^-(US2;iz~6o~Rsa3BUHoB8`TciAn|{B3$i;9x6Ih#$*0;25P}~!3aJIcG zmv4E)fB)a9lWTu{Fe+HJJHOoW=HIUctPdPU+d8yv%=&J1UW3|23^Gecd%U#n|A$-Ly8QOzwC2B`*Vh+q0#?HhV?ZSj14G|M zVBtSHxswHg#tZ&kmy;0!so?na|CXc}UbUl= cLplUzuwHnVcwfwq`y|L4p00i_>zopr03EHAF8}}l literal 0 HcmV?d00001 From c5bb7c13ac02514c3dc664b3051a8366a881dc72 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Thu, 14 Dec 2023 18:27:20 +0200 Subject: [PATCH 23/26] Renamed files --- ...perTrouper_scaled.png => SuperTrouper_1024x.png} | Bin assets/{SuperTrouper.png => SuperTrouper_16x.png} | Bin .../{TrouperDev_scaled.png => TrouperDev_1024x.png} | Bin assets/{TrouperDev.png => TrouperDev_16x.png} | Bin 4 files changed, 0 insertions(+), 0 deletions(-) rename assets/{SuperTrouper_scaled.png => SuperTrouper_1024x.png} (100%) rename assets/{SuperTrouper.png => SuperTrouper_16x.png} (100%) rename assets/{TrouperDev_scaled.png => TrouperDev_1024x.png} (100%) rename assets/{TrouperDev.png => TrouperDev_16x.png} (100%) diff --git a/assets/SuperTrouper_scaled.png b/assets/SuperTrouper_1024x.png similarity index 100% rename from assets/SuperTrouper_scaled.png rename to assets/SuperTrouper_1024x.png diff --git a/assets/SuperTrouper.png b/assets/SuperTrouper_16x.png similarity index 100% rename from assets/SuperTrouper.png rename to assets/SuperTrouper_16x.png diff --git a/assets/TrouperDev_scaled.png b/assets/TrouperDev_1024x.png similarity index 100% rename from assets/TrouperDev_scaled.png rename to assets/TrouperDev_1024x.png diff --git a/assets/TrouperDev.png b/assets/TrouperDev_16x.png similarity index 100% rename from assets/TrouperDev.png rename to assets/TrouperDev_16x.png From 124399517c86986c55caf53a6abe4be4ae721e49 Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Thu, 14 Dec 2023 18:29:29 +0200 Subject: [PATCH 24/26] Added 128x versions --- assets/SuperTrouper_128x.png | Bin 0 -> 635 bytes assets/TrouperDev_128x.png | Bin 0 -> 634 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/SuperTrouper_128x.png create mode 100644 assets/TrouperDev_128x.png diff --git a/assets/SuperTrouper_128x.png b/assets/SuperTrouper_128x.png new file mode 100644 index 0000000000000000000000000000000000000000..3be9d7e0a2b764c2d25d35fae90bd33a60287abe GIT binary patch literal 635 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>U^4Y|aSW-L z^Y)Hno=Bj~frqh8s~roPxC9oge4+b-_XTf&%G$dv+6oWY3ivN57%V8b@#>M_XQyRP z&VKt|TXyMP%J0+O|K6W1+0G0!SnJ#7{Djwa_v?1V?)q2pKkK7)P1V=TkI(nZ$NBx0 zwwdws;TiP?3&tay3VsZQOdY}wcMjh)FL-n8H_Pebe{X7;v>oO!JYo?*sQJdo=Jx#W z;@XC_o0$;0z>3P>-{;<-@gZ@*!hD9iy+`*!%wZ+&0(`uU&h_VsIj{h57v_R-hT_45*ra3MJc?BI*``~`J? zGd}*h`E*xp!(@5&?(6z~{lE7zW4QhDcgAb;zR!=p|MKmh&FjPU#n#8mY}@OfzzXy? zvN!X7MSjhYPbiVI|Mc_k=1_b4Pk%n7e*E>uvL4g+`uTzG#s6N|{C)H3_S}2t@4LPK z-=9-^PJSc0x6S|VZMZnc-s-&k+~fT+zh(AgdikpHhg;8oy?*|2b0i~5pfh#M{ukF^ jS*M5YC14DL!S^%r_wL37{l7hVB8czl>gTe~DWM4fjeQkv literal 0 HcmV?d00001 diff --git a/assets/TrouperDev_128x.png b/assets/TrouperDev_128x.png new file mode 100644 index 0000000000000000000000000000000000000000..cd7f5c49fb1f5e39a00e031e2a4858878e7c59e4 GIT binary patch literal 634 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>U^4M^aSW-L z^Y)HmmPnw?frq^}mTuW{$%$p{SD7Yp0q*k(QR{BpjAqeScp{>iv{pgvp2G%D+3N1f zQz?~`|Lj{|t7Dy0XY*tK%#(TSK%+xs&hBeiyL)qW;m$4pvcG43H2<@&e%p_q?fL%8 z|MBLX{ZsTz*kKOCBNhSm1`Ea`oC@bG4;*+QlaTWIIZGKt#E+qnsROJi?>x{f>9ZfR z&vT}2W(F!oNU;c%Kc3J2qvmH_pm-2ZvM_P)9}$7Ywj{iiQaw};M`|M~0L$w$B5Sk@omgM|^)+jkD8U%5EPe%JiE zx=$~>)<4f*{QbZCHoNEj7;c>XFYZ9bIr+Qir9 rG3@#Bo$1WS;+tqb2gWWK>`45_@N{v4;x!&G7Lb6atDnm{r-UW|dk7fP literal 0 HcmV?d00001 From 6e84c792041eae740bb0e5b54e0188c2c98a336a Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Thu, 14 Dec 2023 21:01:16 +0200 Subject: [PATCH 25/26] Added the current bot version to `/about`; Changed CI to create releases when pushing to `root` branch. --- .github/workflows/ci.yml | 12 ++----- .github/workflows/develop.yml | 10 +----- .github/workflows/root.yml | 35 +++++++++++++------ .version | 1 + build.gradle.kts | 23 +++++++++--- .../quest/laxla/trouper/AboutExtension.kt | 2 +- src/main/kotlin/quest/laxla/trouper/App.kt | 1 + 7 files changed, 50 insertions(+), 34 deletions(-) create mode 100644 .version diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d13c2bc..ae6fd1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,8 @@ on: - develop pull_request: + branches-ignore: + - root jobs: build: @@ -22,20 +24,12 @@ jobs: java-version: 17 distribution: temurin - - name: Gradle (Build) + - name: Build uses: gradle/gradle-build-action@v2 with: arguments: build - - name: Upload artifacts (Main JAR) - uses: actions/upload-artifact@v3 - - with: - name: Main JAR - path: build/libs/*-all.jar - if-no-files-found: error - - name: Upload artifacts (JARs) uses: actions/upload-artifact@v3 diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 33828cf..8b30e86 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -19,20 +19,12 @@ jobs: java-version: 17 distribution: temurin - - name: Gradle (Build) + - name: Build uses: gradle/gradle-build-action@v2 with: arguments: build - - name: Upload artifacts (Main JAR) - uses: actions/upload-artifact@v3 - - with: - name: Main JAR - path: build/libs/*-all.jar - if-no-files-found: error - - name: Upload artifacts (JARs) uses: actions/upload-artifact@v3 diff --git a/.github/workflows/root.yml b/.github/workflows/root.yml index 2a005ff..6ba0aae 100644 --- a/.github/workflows/root.yml +++ b/.github/workflows/root.yml @@ -11,6 +11,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-tags: true - name: Set up Java uses: actions/setup-java@v4 @@ -19,25 +21,38 @@ jobs: java-version: 17 distribution: temurin - - name: Gradle (Build) + - name: Build uses: gradle/gradle-build-action@v2 with: arguments: build dependency-graph: generate-and-submit - - name: Upload artifacts (Main JAR) - uses: actions/upload-artifact@v3 - - with: - name: Main JAR - path: build/libs/*-all.jar - if-no-files-found: error - - - name: Upload artifacts (JARs) + - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: JARs path: build/libs/*.jar if-no-files-found: warn + + - name: Validate version + id: version + run: | + VERSION=$(cat .version) + echo "version=${VERSION}" >> $GITHUB_OUTPUT + + if git show-ref --tags --verify --quiet "refs/tags/${VERSION}"; then + echo "Version ${VERSION} was already released" + exit 0 + fi + + - name: Release artifacts + uses: marvinpinto/action-automatic-releases@v1 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + prerelease: false + automatic_release_tag: ${{ steps.version.outputs.version }} + files: | + build/libs/*.jar + LICENSE.md diff --git a/.version b/.version new file mode 100644 index 0000000..388bb06 --- /dev/null +++ b/.version @@ -0,0 +1 @@ +0.1.0-alpha diff --git a/build.gradle.kts b/build.gradle.kts index 5afb90e..f4b7140 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { } group = "quest.laxla" -version = "0.0.1" +version = file(".version").readText() repositories { google() @@ -43,14 +43,23 @@ dependencies { runtimeOnly("ch.qos.logback:logback-classic:$logback") } +val generatedResources = layout.buildDirectory.dir("generated/resources") + +tasks.processResources { + from(generatedResources) + + doFirst { + generatedResources.orNull?.run { + asFile.mkdirs() + file(".version").asFile.writeText(version.toString()) + } + } +} + application { mainClass = "quest.laxla.trouper.AppKt" } -val jvm: String by project - -tasks.withType { kotlinOptions.jvmTarget = jvm } - tasks.jar { manifest { attributes( @@ -59,6 +68,10 @@ tasks.jar { } } +val jvm: String by project + +tasks.withType { kotlinOptions.jvmTarget = jvm } + java { val java = JavaVersion.toVersion(jvm) sourceCompatibility = java diff --git a/src/main/kotlin/quest/laxla/trouper/AboutExtension.kt b/src/main/kotlin/quest/laxla/trouper/AboutExtension.kt index 9b85282..fbdd479 100644 --- a/src/main/kotlin/quest/laxla/trouper/AboutExtension.kt +++ b/src/main/kotlin/quest/laxla/trouper/AboutExtension.kt @@ -32,7 +32,7 @@ class AboutExtension : TrouperExtension() { allowedMentions() embed { - title = "About ${self.effectiveName}" + title = "About ${self.effectiveName}" + if (version == null) "" else " `$version`" thumbnail { url = avatar } if (isDevelopmentEnvironment) field { diff --git a/src/main/kotlin/quest/laxla/trouper/App.kt b/src/main/kotlin/quest/laxla/trouper/App.kt index 6a31b4b..ae09eda 100644 --- a/src/main/kotlin/quest/laxla/trouper/App.kt +++ b/src/main/kotlin/quest/laxla/trouper/App.kt @@ -15,6 +15,7 @@ val license = envOrNull("LICENSE") val licenseUrl = envOrNull("LICENSE_URL") val donateUrl = envOrNull("DONATE_URL") val repoUrl = envOrNull("REPO_URL") +val version = AboutExtension::class.java.getResourceAsStream("/.version")?.bufferedReader()?.use { it.readText() } fun main() = runBlocking { ExtensibleBot(token) { From 51ccebab7705878133f7e0381de6cce3df94338a Mon Sep 17 00:00:00 2001 From: LaylaMeower Date: Thu, 14 Dec 2023 21:17:06 +0200 Subject: [PATCH 26/26] Fixed bug with version name. --- build.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index f4b7140..cc022c0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.util.removeSuffixIfPresent plugins { application @@ -10,7 +11,7 @@ plugins { } group = "quest.laxla" -version = file(".version").readText() +version = file(".version").readText().removeSuffixIfPresent("\n") repositories { google()