From 2ff1f07b3659fb1b16f8b77733ace1c1a926e853 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 08:13:53 +0000 Subject: [PATCH 01/42] Bump detekt from 1.23.1 to 1.23.3 (#362) Bumps `detekt` from 1.23.1 to 1.23.3. Updates `io.gitlab.arturbosch.detekt:detekt-formatting` from 1.23.1 to 1.23.3 - [Release notes](https://github.com/detekt/detekt/releases) - [Commits](https://github.com/detekt/detekt/compare/v1.23.1...v1.23.3) Updates `io.gitlab.arturbosch.detekt` from 1.23.1 to 1.23.3 - [Release notes](https://github.com/detekt/detekt/releases) - [Commits](https://github.com/detekt/detekt/compare/v1.23.1...v1.23.3) --- updated-dependencies: - dependency-name: io.gitlab.arturbosch.detekt:detekt-formatting dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.gitlab.arturbosch.detekt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7ebdc5db..a6105102 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # Plugins kotlin = "1.9.10" shadow = "8.1.1" -detekt = "1.23.1" +detekt = "1.23.3" git-hooks = "0.0.2" grgit = "5.2.1" blossom = "2.1.0" From 57937a0b037c1dfa6d1ec0fa08e0bb7a7ba5e859 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 08:14:12 +0000 Subject: [PATCH 02/42] Bump kotlin from 1.9.10 to 1.9.20 (#363) Bumps `kotlin` from 1.9.10 to 1.9.20. Updates `org.jetbrains.kotlin.jvm` from 1.9.10 to 1.9.20 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.10...v1.9.20) Updates `org.jetbrains.kotlin.plugin.serialization` from 1.9.10 to 1.9.20 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.10...v1.9.20) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin.jvm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jetbrains.kotlin.plugin.serialization dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a6105102..f4aeadf2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # Plugins -kotlin = "1.9.10" +kotlin = "1.9.20" shadow = "8.1.1" detekt = "1.23.3" git-hooks = "0.0.2" From bc25aff7de572601468aa23943ad8d39f9da7871 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 08:14:45 +0000 Subject: [PATCH 03/42] Bump org.litote.kmongo:kmongo-coroutine-serialization (#364) Bumps [org.litote.kmongo:kmongo-coroutine-serialization](https://github.com/Litote/kmongo) from 4.10.0 to 4.11.0. - [Release notes](https://github.com/Litote/kmongo/releases) - [Commits](https://github.com/Litote/kmongo/compare/kmongo-4.10.0...kmongo-4.11.0) --- updated-dependencies: - dependency-name: org.litote.kmongo:kmongo-coroutine-serialization dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f4aeadf2..d653d8eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ kord-extensions = "1.5.9-20230809.104126-1" logging = "5.1.0" logback = "1.4.11" github-api = "1.317" -kmongo = "4.10.0" +kmongo = "4.11.0" cozy-welcome = "1.0.1-SNAPSHOT" dma = "v0.2.1" docgenerator = "0.1.2-SNAPSHOT" From be51c22ee24c29e688bcbdf373260899887c98de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 07:02:43 +0000 Subject: [PATCH 04/42] Bump io.github.oshai:kotlin-logging from 5.1.0 to 5.1.1 (#366) Bumps [io.github.oshai:kotlin-logging](https://github.com/oshai/kotlin-logging) from 5.1.0 to 5.1.1. - [Release notes](https://github.com/oshai/kotlin-logging/releases) - [Changelog](https://github.com/oshai/kotlin-logging/blob/master/ChangeLog-old.md) - [Commits](https://github.com/oshai/kotlin-logging/compare/5.1.0...5.1.1) --- updated-dependencies: - dependency-name: io.github.oshai:kotlin-logging dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d653d8eb..1985d4d7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ blossom = "2.1.0" # Libraries kord-extensions = "1.5.9-20230809.104126-1" -logging = "5.1.0" +logging = "5.1.1" logback = "1.4.11" github-api = "1.317" kmongo = "4.11.0" From d036fd40438655247cc652c63c962b11d1fc7e11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 07:02:58 +0000 Subject: [PATCH 05/42] Bump AButler/upload-release-assets from 2.0 to 3.0 (#367) Bumps [AButler/upload-release-assets](https://github.com/abutler/upload-release-assets) from 2.0 to 3.0. - [Release notes](https://github.com/abutler/upload-release-assets/releases) - [Commits](https://github.com/abutler/upload-release-assets/compare/v2.0...v3.0) --- updated-dependencies: - dependency-name: AButler/upload-release-assets dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f5d2d52b..c517b5c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: gradle-home-cache-cleanup: true - name: Upload artifacts GitHub - uses: AButler/upload-release-assets@v2.0 + uses: AButler/upload-release-assets@v3.0 with: files: 'build/libs/*-all.jar' From 892cbf26fb2555a219f4912328702416f33ac622 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 07:03:11 +0000 Subject: [PATCH 06/42] Bump kotlin from 1.9.20 to 1.9.21 (#368) * Bump kotlin from 1.9.20 to 1.9.21 Bumps `kotlin` from 1.9.20 to 1.9.21. Updates `org.jetbrains.kotlin.jvm` from 1.9.20 to 1.9.21 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.20...v1.9.21) Updates `org.jetbrains.kotlin.plugin.serialization` from 1.9.20 to 1.9.21 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.20...v1.9.21) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin.jvm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jetbrains.kotlin.plugin.serialization dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update libs.versions.toml --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: NoComment <67918617+NoComment1105@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1985d4d7..39bf33c2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # Plugins -kotlin = "1.9.20" +kotlin = "1.9.22" shadow = "8.1.1" detekt = "1.23.3" git-hooks = "0.0.2" From 88aa9f15d75ae9397583c3802d1cd03e021c33e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 07:03:21 +0000 Subject: [PATCH 07/42] Bump detekt from 1.23.3 to 1.23.4 (#369) Bumps `detekt` from 1.23.3 to 1.23.4. Updates `io.gitlab.arturbosch.detekt:detekt-formatting` from 1.23.3 to 1.23.4 - [Release notes](https://github.com/detekt/detekt/releases) - [Commits](https://github.com/detekt/detekt/compare/v1.23.3...v1.23.4) Updates `io.gitlab.arturbosch.detekt` from 1.23.3 to 1.23.4 - [Release notes](https://github.com/detekt/detekt/releases) - [Commits](https://github.com/detekt/detekt/compare/v1.23.3...v1.23.4) --- updated-dependencies: - dependency-name: io.gitlab.arturbosch.detekt:detekt-formatting dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.gitlab.arturbosch.detekt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39bf33c2..7731a981 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # Plugins kotlin = "1.9.22" shadow = "8.1.1" -detekt = "1.23.3" +detekt = "1.23.4" git-hooks = "0.0.2" grgit = "5.2.1" blossom = "2.1.0" From 5416a216a3ebfbe964990efe218c0d852e3871c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 07:04:13 +0000 Subject: [PATCH 08/42] Bump ch.qos.logback:logback-classic from 1.4.11 to 1.4.14 (#371) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.11 to 1.4.14. - [Commits](https://github.com/qos-ch/logback/compare/v_1.4.11...v_1.4.14) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7731a981..5c08aecb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ blossom = "2.1.0" # Libraries kord-extensions = "1.5.9-20230809.104126-1" logging = "5.1.1" -logback = "1.4.11" +logback = "1.4.14" github-api = "1.317" kmongo = "4.11.0" cozy-welcome = "1.0.1-SNAPSHOT" From 55444134e98ad322438e15646f7be13bada832a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 07:04:32 +0000 Subject: [PATCH 09/42] Bump actions/setup-java from 3 to 4 (#372) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gradle.yml | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index d581255c..b0d26a77 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a18d057b..ff8e2510 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c517b5c4..428e64db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' From 7b439e68b38cc066dc705d259600363d29b6fd76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 07:04:42 +0000 Subject: [PATCH 10/42] Bump actions/upload-artifact from 3 to 4 (#374) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gradle.yml | 2 +- .github/workflows/main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index b0d26a77..ca4daf64 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -31,7 +31,7 @@ jobs: gradle-home-cache-cleanup: true - name: Upload build artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Build Only Artifacts path: build/libs/*[0-9].jar diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ff8e2510..03662b7d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: gradle-home-cache-cleanup: true - name: Upload build artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Build and Deploy Artifacts From 96a0f33ed5f994bc0969a645a48cee41583af220 Mon Sep 17 00:00:00 2001 From: NoComment Date: Fri, 22 Dec 2023 07:53:45 +0000 Subject: [PATCH 11/42] Update to Kordex 1.7.1-SNAPSHOT --- gradle/libs.versions.toml | 2 +- .../lilybot/extensions/config/Config.kt | 3 +- .../extensions/events/AutoThreading.kt | 3 +- .../extensions/events/MemberLogging.kt | 2 +- .../extensions/events/MessageDelete.kt | 2 +- .../lilybot/extensions/events/MessageEdit.kt | 2 +- .../extensions/moderation/LockingCommands.kt | 3 +- .../moderation/ModerationCommands.kt | 7 ++-- .../lilybot/extensions/moderation/Report.kt | 4 +- .../lilybot/extensions/util/GalleryChannel.kt | 3 +- .../lilybot/extensions/util/Github.kt | 40 +++++++++---------- .../extensions/util/GuildAnnouncements.kt | 1 - .../lilybot/extensions/util/InfoCommands.kt | 3 +- .../lilybot/extensions/util/ModUtilities.kt | 4 +- .../extensions/util/NewsChannelPublishing.kt | 1 - .../extensions/util/PublicUtilities.kt | 6 +-- .../lilybot/extensions/util/Reminders.kt | 3 +- .../lilybot/extensions/util/RoleMenu.kt | 5 ++- .../lilybot/extensions/util/Tags.kt | 3 +- .../lilybot/extensions/util/ThreadControl.kt | 6 +-- .../lilybot/utils/_PermissionUtils.kt | 1 - 21 files changed, 42 insertions(+), 62 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5c08aecb..6b2146a8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ grgit = "5.2.1" blossom = "2.1.0" # Libraries -kord-extensions = "1.5.9-20230809.104126-1" +kord-extensions = "1.7.1-20231217.132026-9" logging = "5.1.1" logback = "1.4.14" github-api = "1.317" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt index 20bef24f..d8e42dc7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt @@ -19,7 +19,6 @@ import com.kotlindiscord.kord.extensions.modules.unsafe.extensions.unsafeSubComm import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialSlashCommandResponse import com.kotlindiscord.kord.extensions.modules.unsafe.types.ackEphemeral import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.botHasPermissions import dev.kord.common.entity.Permission import dev.kord.core.behavior.channel.createMessage @@ -27,7 +26,7 @@ import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.behavior.interaction.modal import dev.kord.core.entity.channel.TextChannel import dev.kord.rest.builder.message.EmbedBuilder -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt index f64201dd..ccd0d2ae 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt @@ -21,7 +21,6 @@ import com.kotlindiscord.kord.extensions.modules.unsafe.extensions.unsafeSubComm import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialSlashCommandResponse import com.kotlindiscord.kord.extensions.modules.unsafe.types.ackEphemeral import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.delete import com.kotlindiscord.kord.extensions.utils.respond import dev.kord.common.entity.ArchiveDuration @@ -44,7 +43,7 @@ import dev.kord.core.entity.channel.thread.TextChannelThread import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.core.event.channel.thread.ThreadChannelCreateEvent import dev.kord.core.supplier.EntitySupplyStrategy -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.coroutines.delay import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.AutoThreadingCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt index bbdba92f..9d5bcdaa 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt @@ -14,7 +14,7 @@ import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.guild.MemberJoinEvent import dev.kord.core.event.guild.MemberLeaveEvent -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt index b53201a2..c0ec6ae8 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt @@ -14,7 +14,7 @@ import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.message.MessageBulkDeleteEvent import dev.kord.rest.builder.message.create.UserMessageCreateBuilder -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import io.ktor.client.request.forms.ChannelProvider import io.ktor.util.cio.toByteReadChannel import kotlinx.datetime.Clock diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt index acae881b..7b839516 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt @@ -14,7 +14,7 @@ import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.attachmentsAndProxiedMessageInfo diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt index 163aa0e0..abd3c9d8 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt @@ -11,7 +11,6 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalChanne import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext -import com.kotlindiscord.kord.extensions.types.respond import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.core.behavior.channel.asChannelOfOrNull @@ -342,8 +341,8 @@ class LockingCommands : Extension() { if (targetChannel == null) { respond { content = "I can't fetch the targeted channel properly." - return null } + return null } return targetChannel diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt index 6e69d773..84a2ba55 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt @@ -21,14 +21,13 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalUser import com.kotlindiscord.kord.extensions.commands.converters.impl.snowflake import com.kotlindiscord.kord.extensions.commands.converters.impl.user import com.kotlindiscord.kord.extensions.components.components -import com.kotlindiscord.kord.extensions.components.ephemeralSelectMenu +import com.kotlindiscord.kord.extensions.components.ephemeralStringSelectMenu import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralMessageCommand import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PluralKit import com.kotlindiscord.kord.extensions.time.TimestampType import com.kotlindiscord.kord.extensions.time.toDiscord -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.dm import com.kotlindiscord.kord.extensions.utils.isNullOrBot import com.kotlindiscord.kord.extensions.utils.timeout @@ -49,7 +48,7 @@ import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.interaction.followup.EphemeralFollowupMessage import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.rest.builder.message.EmbedBuilder -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import dev.kord.rest.request.KtorRequestException import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOf @@ -134,7 +133,7 @@ class ModerationCommands : Extension() { menuMessage = respond { content = "How would you like to moderate this message?" components { - ephemeralSelectMenu { + ephemeralStringSelectMenu { placeholder = "Select action..." maximumChoices = 1 // Prevent selecting multiple options at once diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt index c0f0e3bf..19dcfe21 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt @@ -11,8 +11,6 @@ import com.kotlindiscord.kord.extensions.components.linkButton import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralMessageCommand import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.types.edit -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.getJumpUrl import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Snowflake @@ -22,7 +20,7 @@ import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt index 6344fee3..b1b0b083 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt @@ -9,7 +9,6 @@ import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSub import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.delete import com.kotlindiscord.kord.extensions.utils.permissionsForMember import com.kotlindiscord.kord.extensions.utils.respond @@ -22,7 +21,7 @@ import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.exception.EntityNotFoundException -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.coroutines.delay import org.hyacinthbots.lilybot.database.collections.GalleryChannelCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt index 3438a992..db957cc4 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt @@ -12,10 +12,8 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.string import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType -import com.kotlindiscord.kord.extensions.types.respond -import com.kotlindiscord.kord.extensions.types.respondEphemeral import dev.kord.common.entity.Permission -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.GithubCollection import org.hyacinthbots.lilybot.github @@ -75,7 +73,7 @@ class Github : Extension() { category = "extensions.util.Github.issue.InputCheck" message = "Input missing /" } - respondEphemeral { + respond { embed { title = "Make sure your repository input is formatted like this:" description = "Format: `User/Repo` or `Org/Repo` \nFor example: `HyacinthBots/LilyBot`" @@ -96,7 +94,7 @@ class Github : Extension() { try { github.getRepository(repository)?.getIssue(arguments.issue) } catch (e: GHFileNotFoundException) { - respondEphemeral { + respond { embed { title = "Unable to find issue number! Make sure this issue exists" } @@ -115,7 +113,7 @@ class Github : Extension() { try { iterator!!.hasNext() } catch (e: GHException) { - respondEphemeral { + respond { embed { title = "Unable to access repository, make sure this repository exists!" } @@ -131,7 +129,7 @@ class Github : Extension() { message = "Unable to find issue" } - respondEphemeral { + respond { embed { title = "Invalid issue number. Make sure this issue exists!" } @@ -177,7 +175,7 @@ class Github : Extension() { title = "Error!" description = "Error occurred initializing Pull Request. How did this happen?" color = DISCORD_RED - return@action + return@respond } } else { title = issue.title @@ -303,7 +301,7 @@ class Github : Extension() { category = "extensions.util.Github.repository.InputCheck" message = "Input missing /" } - respondEphemeral { + respond { embed { title = "Make sure your input is formatted like this:" description = "Format: `User/Repo` or `Org/Repo`\nFor example: `HyacinthBots/LilyBot`" @@ -332,7 +330,7 @@ class Github : Extension() { category = "extensions.util.Github.repository.getRepository" message = "Repository not found" } - respondEphemeral { + respond { embed { title = "Invalid repository name. Make sure this repository exists" } @@ -413,7 +411,7 @@ class Github : Extension() { category = "extensions.util.Github.user.getUser" message = "Unable to find user" } - respondEphemeral { + respond { embed { title = "Invalid Username. Make sure this user exists!" } @@ -425,11 +423,11 @@ class Github : Extension() { val isOrg: Boolean = ghUser?.type.equals("Organization") if (!isOrg) { - sentry.breadcrumb(BreadcrumbType.Info) { - category = "extensions.util.Github.user.isOrg" - message = "User is not Organisation" - data["isNotOrg"] = ghUser?.login - } +// sentry.breadcrumb(BreadcrumbType.Info) { +// category = "extensions.util.Github.user.isOrg" +// message = "User is not Organisation" +// data["isNotOrg"] = ghUser?.login +// } respond { embed { title = "GitHub profile for " + ghUser?.login @@ -481,11 +479,11 @@ class Github : Extension() { } } } else { - sentry.breadcrumb(BreadcrumbType.Info) { - category = "extensions.util.Github.user.isOrg" - message = "User is Organisation" - data["isOrg"] = ghUser?.login - } +// sentry.breadcrumb(BreadcrumbType.Info) { +// category = "extensions.util.Github.user.isOrg" +// message = "User is Organisation" +// data["isOrg"] = ghUser?.login +// } val org: GHOrganization? = github.getOrganization(ghUser?.login) respond { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt index 7b1507bd..26ad758b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt @@ -8,7 +8,6 @@ import com.kotlindiscord.kord.extensions.components.ephemeralButton import com.kotlindiscord.kord.extensions.components.forms.ModalForm import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.types.respond import dev.kord.common.Color import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt index b01d2f2c..fad8ef8f 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt @@ -7,9 +7,8 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand import com.kotlindiscord.kord.extensions.time.TimestampType import com.kotlindiscord.kord.extensions.time.toDiscord -import com.kotlindiscord.kord.extensions.types.respond import dev.kord.rest.builder.message.create.MessageCreateBuilder -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime import org.hyacinthbots.lilybot.database.collections.UptimeCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt index ceff7dcc..de71d44a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt @@ -22,7 +22,6 @@ import com.kotlindiscord.kord.extensions.components.forms.ModalForm import com.kotlindiscord.kord.extensions.components.linkButton import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.getJumpUrl import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler import com.kotlindiscord.kord.extensions.utils.scheduling.Task @@ -39,8 +38,7 @@ import dev.kord.core.behavior.interaction.followup.edit import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.interaction.followup.EphemeralFollowupMessage -import dev.kord.rest.builder.message.create.embed -import dev.kord.rest.builder.message.modify.embed +import dev.kord.rest.builder.message.embed import dev.kord.rest.request.KtorRequestException import kotlinx.coroutines.flow.toList import kotlinx.datetime.Clock diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt index 0f2c79cd..936bf597 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt @@ -14,7 +14,6 @@ import com.kotlindiscord.kord.extensions.extensions.event import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages -import com.kotlindiscord.kord.extensions.types.respond import dev.kord.common.Locale import dev.kord.common.asJavaLocale import dev.kord.common.entity.ChannelType diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt index 4c947b9c..b9dd03e3 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt @@ -13,7 +13,6 @@ import com.kotlindiscord.kord.extensions.components.ephemeralButton import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.dm import com.kotlindiscord.kord.extensions.utils.getTopRole import com.kotlindiscord.kord.extensions.utils.hasPermission @@ -25,8 +24,7 @@ import dev.kord.core.behavior.edit import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel -import dev.kord.rest.builder.message.create.embed -import dev.kord.rest.builder.message.modify.embed +import dev.kord.rest.builder.message.embed import dev.kord.rest.request.KtorRequestException import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection @@ -118,8 +116,8 @@ class PublicUtilities : Extension() { requesterAsMember.edit { nickname = arguments.newNick } respond { content = "You have permission to change your own nickname, so I've just made the change." - return@action } + return@action } // Declare the embed outside the action to allow us to reference it inside the action diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt index e8841118..a212ee6d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt @@ -21,7 +21,6 @@ import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages import com.kotlindiscord.kord.extensions.time.TimestampType import com.kotlindiscord.kord.extensions.time.toDiscord -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.botHasPermissions import com.kotlindiscord.kord.extensions.utils.dm import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler @@ -37,7 +36,7 @@ import dev.kord.core.entity.Guild import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.MessageCreateBuilder -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import dev.kord.rest.request.KtorRequestException import kotlinx.datetime.Clock import kotlinx.datetime.DateTimePeriod diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt index 6e23b744..f33b6c51 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt @@ -19,7 +19,6 @@ import com.kotlindiscord.kord.extensions.components.linkButton import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.getJumpUrl import com.kotlindiscord.kord.extensions.utils.getTopRole import dev.kord.common.entity.ButtonStyle @@ -35,7 +34,7 @@ import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.entity.Message import dev.kord.core.entity.Role import dev.kord.core.event.interaction.GuildButtonInteractionCreateEvent -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.toList @@ -486,6 +485,7 @@ class RoleMenu : Extension() { event.interaction.respondEphemeral { content = "Use the menu below to select roles." components { + // TODO Update to ephemeralRoleSelectMenu ephemeralSelectMenu { placeholder = "Select roles..." maximumChoices = roles.size @@ -582,6 +582,7 @@ class RoleMenu : Extension() { respond { content = "Use the menu below to subscribe to roles." components { + // TODO Update to ephemeralRoleSelectMenu ephemeralSelectMenu { placeholder = "Select roles to subscribe to..." minimumChoices = 0 diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt index 9917e317..61893e06 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt @@ -18,7 +18,6 @@ import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.suggestStringMap import dev.kord.common.Locale import dev.kord.common.asJavaLocale @@ -30,7 +29,7 @@ import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.rest.builder.message.EmbedBuilder -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.TagsCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt index db51fa71..9c212578 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt @@ -19,8 +19,6 @@ import com.kotlindiscord.kord.extensions.components.ephemeralButton import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.types.edit -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.hasPermission import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission @@ -37,7 +35,7 @@ import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.core.entity.interaction.response.EphemeralMessageInteractionResponse import dev.kord.core.event.channel.thread.ThreadUpdateEvent import dev.kord.core.exception.EntityNotFoundException -import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection @@ -353,8 +351,8 @@ class ThreadControl : Extension() { if (threadChannel == null) { respond { content = "Are you sure this channel is a thread? If it is, I can't fetch it properly." - return null } + return null } return threadChannel diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt index ca2af49a..6dd0108a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt @@ -3,7 +3,6 @@ package org.hyacinthbots.lilybot.utils import com.kotlindiscord.kord.extensions.checks.channelFor import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext -import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.botHasPermissions import com.kotlindiscord.kord.extensions.utils.getTopRole import dev.kord.common.entity.Permission From dba1bb371f65cf7774e26fee5736b05a4179c85e Mon Sep 17 00:00:00 2001 From: NoComment Date: Wed, 31 Jan 2024 17:03:47 +0000 Subject: [PATCH 12/42] Remove redundant codec --- .../hyacinthbots/lilybot/database/Database.kt | 4 ---- .../lilybot/database/SnowflakeCodec.kt | 23 ------------------- 2 files changed, 27 deletions(-) delete mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/database/SnowflakeCodec.kt diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/Database.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/Database.kt index 92ddf92e..01e2d82c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/Database.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/Database.kt @@ -3,21 +3,17 @@ package org.hyacinthbots.lilybot.database import com.mongodb.ConnectionString import com.mongodb.MongoClientSettings import org.bson.UuidRepresentation -import org.bson.codecs.configuration.CodecRegistries import org.hyacinthbots.lilybot.database.migrations.Migrator import org.hyacinthbots.lilybot.utils.MONGO_URI import org.litote.kmongo.coroutine.coroutine import org.litote.kmongo.reactivestreams.KMongo class Database { - private val codecRegistry = CodecRegistries.fromCodecs(SnowflakeCodec) - // Connect to the database using the provided connection URL private val settings = MongoClientSettings .builder() .uuidRepresentation(UuidRepresentation.STANDARD) .applyConnectionString(ConnectionString(MONGO_URI)) - .codecRegistry(CodecRegistries.fromRegistries(codecRegistry, MongoClientSettings.getDefaultCodecRegistry())) .build() private val client = KMongo.createClient(settings).coroutine diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/SnowflakeCodec.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/SnowflakeCodec.kt deleted file mode 100644 index 4e5b78b5..00000000 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/SnowflakeCodec.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.hyacinthbots.lilybot.database - -import dev.kord.common.entity.Snowflake -import org.bson.BsonInvalidOperationException -import org.bson.BsonReader -import org.bson.BsonWriter -import org.bson.codecs.Codec -import org.bson.codecs.DecoderContext -import org.bson.codecs.EncoderContext - -object SnowflakeCodec : Codec { - override fun encode(writer: BsonWriter, value: Snowflake, encoderContext: EncoderContext) { - writer.writeInt64(value.value.toLong()) - } - - override fun decode(reader: BsonReader, decoderContext: DecoderContext): Snowflake = try { - Snowflake(reader.readString()) - } catch (_: BsonInvalidOperationException) { - Snowflake(reader.readInt64()) - } - - override fun getEncoderClass(): Class = Snowflake::class.java -} From 34ab851d9cb6304aae8082262bb92dcad377bebd Mon Sep 17 00:00:00 2001 From: NoComment Date: Wed, 31 Jan 2024 17:34:41 +0000 Subject: [PATCH 13/42] Update dependencies and upgradle (still hilarious) --- .github/workflows/gradle.yml | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/release.yml | 2 +- build.gradle.kts | 14 +++++++++++--- gradle/libs.versions.toml | 12 ++++++------ gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 14 +++++++------- .../org/hyacinthbots/lilybot/LilyBot.kt | 2 +- .../collections/WelcomeChannelCollection.kt | 4 ++-- 10 files changed, 31 insertions(+), 23 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index ca4daf64..9408667c 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -25,7 +25,7 @@ jobs: run: chmod +x gradlew - name: Build Artifacts - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v3 with: arguments: build --stacktrace gradle-home-cache-cleanup: true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 03662b7d..95758155 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: run: chmod +x gradlew - name: Build Artifacts - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v3 with: arguments: build --stacktrace gradle-home-cache-cleanup: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 428e64db..d30bf7af 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: run: chmod +x gradlew - name: Build Artifacts - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v3 with: arguments: build --stacktrace gradle-home-cache-cleanup: true diff --git a/build.gradle.kts b/build.gradle.kts index 805d7dc4..833d2f72 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,11 +35,21 @@ repositories { url = uri("https://maven.fabricmc.net/") } + maven { + name = "QuiltMC (Releases)" + url = uri("https://maven.quiltmc.org/repository/release/") + } + maven { name = "QuiltMC (Snapshots)" url = uri("https://maven.quiltmc.org/repository/snapshot/") } + maven { + name = "Shedaniel" + url = uri("https://maven.shedaniel.me") + } + maven { name = "JitPack" url = uri("https://jitpack.io") @@ -53,6 +63,7 @@ dependencies { implementation(libs.kord.extensions.phishing) implementation(libs.kord.extensions.pluralkit) implementation(libs.kord.extensions.unsafe) + implementation(libs.kord.extensions.welcome) implementation(libs.kotlin.stdlib) @@ -66,9 +77,6 @@ dependencies { // KMongo implementation(libs.kmongo) - // Cozy's welcome module - implementation(libs.cozy.welcome) - implementation(libs.dma) implementation(libs.docgenerator) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6b2146a8..73b50f31 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,27 +8,27 @@ grgit = "5.2.1" blossom = "2.1.0" # Libraries -kord-extensions = "1.7.1-20231217.132026-9" -logging = "5.1.1" +kord-extensions = "1.7.1-20240117.210840-21" +kord-welcome = "1.7.1-20240117.210840-10" +logging = "6.0.3" logback = "1.4.14" -github-api = "1.317" +github-api = "1.318" kmongo = "4.11.0" -cozy-welcome = "1.0.1-SNAPSHOT" dma = "v0.2.1" -docgenerator = "0.1.2-SNAPSHOT" +docgenerator = "main-SNAPSHOT" [libraries] kord-extensions-core = { module = "com.kotlindiscord.kord.extensions:kord-extensions", version.ref = "kord-extensions" } kord-extensions-phishing = { module = "com.kotlindiscord.kord.extensions:extra-phishing", version.ref = "kord-extensions" } kord-extensions-pluralkit = { module = "com.kotlindiscord.kord.extensions:extra-pluralkit", version.ref = "kord-extensions"} kord-extensions-unsafe = { module = "com.kotlindiscord.kord.extensions:unsafe", version.ref = "kord-extensions"} +kord-extensions-welcome = { module = "com.kotlindiscord.kord.extensions:extra-welcome", version.ref = "kord-welcome"} kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } logging = { module = "io.github.oshai:kotlin-logging", version.ref = "logging" } github-api = { module = "org.kohsuke:github-api", version.ref = "github-api" } kmongo = { module = "org.litote.kmongo:kmongo-coroutine-serialization", version.ref = "kmongo" } detekt = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt"} -cozy-welcome = {module = "org.quiltmc.community:module-welcome", version.ref = "cozy-welcome"} dma = { module = "org.hyacinthbots:discord-moderation-actions", version.ref = "dma"} docgenerator = { module = "org.hyacinthbots:doc-generator", version.ref = "docgenerator" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 63721 zcmb5Wb9gP!wgnp7wrv|bwr$&XvSZt}Z6`anZSUAlc9NHKf9JdJ;NJVr`=eI(_pMp0 zy1VAAG3FfAOI`{X1O)&90s;U4K;XLp008~hCjbEC_fbYfS%6kTR+JtXK>nW$ZR+`W ze|#J8f4A@M|F5BpfUJb5h>|j$jOe}0oE!`Zf6fM>CR?!y@zU(cL8NsKk`a z6tx5mAkdjD;J=LcJ;;Aw8p!v#ouk>mUDZF@ zK>yvw%+bKu+T{Nk@LZ;zkYy0HBKw06_IWcMHo*0HKpTsEFZhn5qCHH9j z)|XpN&{`!0a>Vl+PmdQc)Yg4A(AG-z!+@Q#eHr&g<9D?7E)_aEB?s_rx>UE9TUq|? z;(ggJt>9l?C|zoO@5)tu?EV0x_7T17q4fF-q3{yZ^ipUbKcRZ4Qftd!xO(#UGhb2y>?*@{xq%`(-`2T^vc=#< zx!+@4pRdk&*1ht2OWk^Z5IAQ0YTAXLkL{(D*$gENaD)7A%^XXrCchN&z2x+*>o2FwPFjWpeaL=!tzv#JOW#( z$B)Nel<+$bkH1KZv3&-}=SiG~w2sbDbAWarg%5>YbC|}*d9hBjBkR(@tyM0T)FO$# zPtRXukGPnOd)~z=?avu+4Co@wF}1T)-uh5jI<1$HLtyDrVak{gw`mcH@Q-@wg{v^c zRzu}hMKFHV<8w}o*yg6p@Sq%=gkd~;`_VGTS?L@yVu`xuGy+dH6YOwcP6ZE`_0rK% zAx5!FjDuss`FQ3eF|mhrWkjux(Pny^k$u_)dyCSEbAsecHsq#8B3n3kDU(zW5yE|( zgc>sFQywFj5}U*qtF9Y(bi*;>B7WJykcAXF86@)z|0-Vm@jt!EPoLA6>r)?@DIobIZ5Sx zsc@OC{b|3%vaMbyeM|O^UxEYlEMHK4r)V-{r)_yz`w1*xV0|lh-LQOP`OP`Pk1aW( z8DSlGN>Ts|n*xj+%If~+E_BxK)~5T#w6Q1WEKt{!Xtbd`J;`2a>8boRo;7u2M&iOop4qcy<)z023=oghSFV zST;?S;ye+dRQe>ygiJ6HCv4;~3DHtJ({fWeE~$H@mKn@Oh6Z(_sO>01JwH5oA4nvK zr5Sr^g+LC zLt(i&ecdmqsIJGNOSUyUpglvhhrY8lGkzO=0USEKNL%8zHshS>Qziu|`eyWP^5xL4 zRP122_dCJl>hZc~?58w~>`P_s18VoU|7(|Eit0-lZRgLTZKNq5{k zE?V=`7=R&ro(X%LTS*f+#H-mGo_j3dm@F_krAYegDLk6UV{`UKE;{YSsn$ z(yz{v1@p|p!0>g04!eRSrSVb>MQYPr8_MA|MpoGzqyd*$@4j|)cD_%^Hrd>SorF>@ zBX+V<@vEB5PRLGR(uP9&U&5=(HVc?6B58NJT_igiAH*q~Wb`dDZpJSKfy5#Aag4IX zj~uv74EQ_Q_1qaXWI!7Vf@ZrdUhZFE;L&P_Xr8l@GMkhc#=plV0+g(ki>+7fO%?Jb zl+bTy7q{w^pTb{>(Xf2q1BVdq?#f=!geqssXp z4pMu*q;iiHmA*IjOj4`4S&|8@gSw*^{|PT}Aw~}ZXU`6=vZB=GGeMm}V6W46|pU&58~P+?LUs%n@J}CSrICkeng6YJ^M? zS(W?K4nOtoBe4tvBXs@@`i?4G$S2W&;$z8VBSM;Mn9 zxcaEiQ9=vS|bIJ>*tf9AH~m&U%2+Dim<)E=}KORp+cZ^!@wI`h1NVBXu{@%hB2Cq(dXx_aQ9x3mr*fwL5!ZryQqi|KFJuzvP zK1)nrKZ7U+B{1ZmJub?4)Ln^J6k!i0t~VO#=q1{?T)%OV?MN}k5M{}vjyZu#M0_*u z8jwZKJ#Df~1jcLXZL7bnCEhB6IzQZ-GcoQJ!16I*39iazoVGugcKA{lhiHg4Ta2fD zk1Utyc5%QzZ$s3;p0N+N8VX{sd!~l*Ta3|t>lhI&G`sr6L~G5Lul`>m z{!^INm?J|&7X=;{XveF!(b*=?9NAp4y&r&N3(GKcW4rS(Ejk|Lzs1PrxPI_owB-`H zg3(Rruh^&)`TKA6+_!n>RdI6pw>Vt1_j&+bKIaMTYLiqhZ#y_=J8`TK{Jd<7l9&sY z^^`hmi7^14s16B6)1O;vJWOF$=$B5ONW;;2&|pUvJlmeUS&F;DbSHCrEb0QBDR|my zIs+pE0Y^`qJTyH-_mP=)Y+u^LHcuZhsM3+P||?+W#V!_6E-8boP#R-*na4!o-Q1 zVthtYhK{mDhF(&7Okzo9dTi03X(AE{8cH$JIg%MEQca`S zy@8{Fjft~~BdzWC(di#X{ny;!yYGK9b@=b|zcKZ{vv4D8i+`ilOPl;PJl{!&5-0!w z^fOl#|}vVg%=n)@_e1BrP)`A zKPgs`O0EO}Y2KWLuo`iGaKu1k#YR6BMySxQf2V++Wo{6EHmK>A~Q5o73yM z-RbxC7Qdh0Cz!nG+7BRZE>~FLI-?&W_rJUl-8FDIaXoNBL)@1hwKa^wOr1($*5h~T zF;%f^%<$p8Y_yu(JEg=c_O!aZ#)Gjh$n(hfJAp$C2he555W5zdrBqjFmo|VY+el;o z=*D_w|GXG|p0**hQ7~9-n|y5k%B}TAF0iarDM!q-jYbR^us(>&y;n^2l0C%@2B}KM zyeRT9)oMt97Agvc4sEKUEy%MpXr2vz*lb zh*L}}iG>-pqDRw7ud{=FvTD?}xjD)w{`KzjNom-$jS^;iw0+7nXSnt1R@G|VqoRhE%12nm+PH?9`(4rM0kfrZzIK9JU=^$YNyLvAIoxl#Q)xxDz!^0@zZ zSCs$nfcxK_vRYM34O<1}QHZ|hp4`ioX3x8(UV(FU$J@o%tw3t4k1QPmlEpZa2IujG&(roX_q*%e`Hq|);0;@k z0z=fZiFckp#JzW0p+2A+D$PC~IsakhJJkG(c;CqAgFfU0Z`u$PzG~-9I1oPHrCw&)@s^Dc~^)#HPW0Ra}J^=|h7Fs*<8|b13ZzG6MP*Q1dkoZ6&A^!}|hbjM{2HpqlSXv_UUg1U4gn z3Q)2VjU^ti1myodv+tjhSZp%D978m~p& z43uZUrraHs80Mq&vcetqfQpQP?m!CFj)44t8Z}k`E798wxg&~aCm+DBoI+nKq}&j^ zlPY3W$)K;KtEajks1`G?-@me7C>{PiiBu+41#yU_c(dITaqE?IQ(DBu+c^Ux!>pCj zLC|HJGU*v+!it1(;3e`6igkH(VA)-S+k(*yqxMgUah3$@C zz`7hEM47xr>j8^g`%*f=6S5n>z%Bt_Fg{Tvmr+MIsCx=0gsu_sF`q2hlkEmisz#Fy zj_0;zUWr;Gz}$BS%Y`meb(=$d%@Crs(OoJ|}m#<7=-A~PQbyN$x%2iXP2@e*nO0b7AwfH8cCUa*Wfu@b)D_>I*%uE4O3 z(lfnB`-Xf*LfC)E}e?%X2kK7DItK6Tf<+M^mX0Ijf_!IP>7c8IZX%8_#0060P{QMuV^B9i<^E`_Qf0pv9(P%_s8D`qvDE9LK9u-jB}J2S`(mCO&XHTS04Z5Ez*vl^T%!^$~EH8M-UdwhegL>3IQ*)(MtuH2Xt1p!fS4o~*rR?WLxlA!sjc2(O znjJn~wQ!Fp9s2e^IWP1C<4%sFF}T4omr}7+4asciyo3DntTgWIzhQpQirM$9{EbQd z3jz9vS@{aOqTQHI|l#aUV@2Q^Wko4T0T04Me4!2nsdrA8QY1%fnAYb~d2GDz@lAtfcHq(P7 zaMBAGo}+NcE-K*@9y;Vt3*(aCaMKXBB*BJcD_Qnxpt75r?GeAQ}*|>pYJE=uZb73 zC>sv)18)q#EGrTG6io*}JLuB_jP3AU1Uiu$D7r|2_zlIGb9 zjhst#ni)Y`$)!fc#reM*$~iaYoz~_Cy7J3ZTiPm)E?%`fbk`3Tu-F#`{i!l5pNEn5 zO-Tw-=TojYhzT{J=?SZj=Z8#|eoF>434b-DXiUsignxXNaR3 zm_}4iWU$gt2Mw5NvZ5(VpF`?X*f2UZDs1TEa1oZCif?Jdgr{>O~7}-$|BZ7I(IKW`{f;@|IZFX*R8&iT= zoWstN8&R;}@2Ka%d3vrLtR|O??ben;k8QbS-WB0VgiCz;<$pBmIZdN!aalyCSEm)crpS9dcD^Y@XT1a3+zpi-`D}e#HV<} z$Y(G&o~PvL-xSVD5D?JqF3?B9rxGWeb=oEGJ3vRp5xfBPlngh1O$yI95EL+T8{GC@ z98i1H9KhZGFl|;`)_=QpM6H?eDPpw~^(aFQWwyXZ8_EEE4#@QeT_URray*mEOGsGc z6|sdXtq!hVZo=d#+9^@lm&L5|q&-GDCyUx#YQiccq;spOBe3V+VKdjJA=IL=Zn%P} zNk=_8u}VhzFf{UYZV0`lUwcD&)9AFx0@Fc6LD9A6Rd1=ga>Mi0)_QxM2ddCVRmZ0d z+J=uXc(?5JLX3=)e)Jm$HS2yF`44IKhwRnm2*669_J=2LlwuF5$1tAo@ROSU@-y+;Foy2IEl2^V1N;fk~YR z?&EP8#t&m0B=?aJeuz~lHjAzRBX>&x=A;gIvb>MD{XEV zV%l-+9N-)i;YH%nKP?>f`=?#`>B(`*t`aiPLoQM(a6(qs4p5KFjDBN?8JGrf3z8>= zi7sD)c)Nm~x{e<^jy4nTx${P~cwz_*a>%0_;ULou3kHCAD7EYkw@l$8TN#LO9jC( z1BeFW`k+bu5e8Ns^a8dPcjEVHM;r6UX+cN=Uy7HU)j-myRU0wHd$A1fNI~`4;I~`zC)3ul#8#^rXVSO*m}Ag>c%_;nj=Nv$rCZ z*~L@C@OZg%Q^m)lc-kcX&a*a5`y&DaRxh6O*dfhLfF+fU5wKs(1v*!TkZidw*)YBP za@r`3+^IHRFeO%!ai%rxy;R;;V^Fr=OJlpBX;(b*3+SIw}7= zIq$*Thr(Zft-RlY)D3e8V;BmD&HOfX+E$H#Y@B3?UL5L~_fA-@*IB-!gItK7PIgG9 zgWuGZK_nuZjHVT_Fv(XxtU%)58;W39vzTI2n&)&4Dmq7&JX6G>XFaAR{7_3QB6zsT z?$L8c*WdN~nZGiscY%5KljQARN;`w$gho=p006z;n(qIQ*Zu<``TMO3n0{ARL@gYh zoRwS*|Niw~cR!?hE{m*y@F`1)vx-JRfqET=dJ5_(076st(=lFfjtKHoYg`k3oNmo_ zNbQEw8&sO5jAYmkD|Zaz_yUb0rC})U!rCHOl}JhbYIDLzLvrZVw0~JO`d*6f;X&?V=#T@ND*cv^I;`sFeq4 z##H5;gpZTb^0Hz@3C*~u0AqqNZ-r%rN3KD~%Gw`0XsIq$(^MEb<~H(2*5G^<2(*aI z%7}WB+TRlMIrEK#s0 z93xn*Ohb=kWFc)BNHG4I(~RPn-R8#0lqyBBz5OM6o5|>x9LK@%HaM}}Y5goCQRt2C z{j*2TtT4ne!Z}vh89mjwiSXG=%DURar~=kGNNaO_+Nkb+tRi~Rkf!7a$*QlavziD( z83s4GmQ^Wf*0Bd04f#0HX@ua_d8 z23~z*53ePD6@xwZ(vdl0DLc=>cPIOPOdca&MyR^jhhKrdQO?_jJh`xV3GKz&2lvP8 zEOwW6L*ufvK;TN{=S&R@pzV^U=QNk^Ec}5H z+2~JvEVA{`uMAr)?Kf|aW>33`)UL@bnfIUQc~L;TsTQ6>r-<^rB8uoNOJ>HWgqMI8 zSW}pZmp_;z_2O5_RD|fGyTxaxk53Hg_3Khc<8AUzV|ZeK{fp|Ne933=1&_^Dbv5^u zB9n=*)k*tjHDRJ@$bp9mrh}qFn*s}npMl5BMDC%Hs0M0g-hW~P*3CNG06G!MOPEQ_ zi}Qs-6M8aMt;sL$vlmVBR^+Ry<64jrm1EI1%#j?c?4b*7>)a{aDw#TfTYKq+SjEFA z(aJ&z_0?0JB83D-i3Vh+o|XV4UP+YJ$9Boid2^M2en@APw&wx7vU~t$r2V`F|7Qfo z>WKgI@eNBZ-+Og<{u2ZiG%>YvH2L3fNpV9J;WLJoBZda)01Rn;o@){01{7E#ke(7U zHK>S#qZ(N=aoae*4X!0A{)nu0R_sKpi1{)u>GVjC+b5Jyl6#AoQ-1_3UDovNSo`T> z?c-@7XX*2GMy?k?{g)7?Sv;SJkmxYPJPs!&QqB12ejq`Lee^-cDveVWL^CTUldb(G zjDGe(O4P=S{4fF=#~oAu>LG>wrU^z_?3yt24FOx>}{^lCGh8?vtvY$^hbZ)9I0E3r3NOlb9I?F-Yc=r$*~l`4N^xzlV~N zl~#oc>U)Yjl0BxV>O*Kr@lKT{Z09OXt2GlvE38nfs+DD7exl|&vT;)>VFXJVZp9Np zDK}aO;R3~ag$X*|hRVY3OPax|PG`@_ESc8E!mHRByJbZQRS38V2F__7MW~sgh!a>98Q2%lUNFO=^xU52|?D=IK#QjwBky-C>zOWlsiiM&1n z;!&1((Xn1$9K}xabq~222gYvx3hnZPg}VMF_GV~5ocE=-v>V=T&RsLBo&`)DOyIj* zLV{h)JU_y*7SdRtDajP_Y+rBkNN*1_TXiKwHH2&p51d(#zv~s#HwbNy?<+(=9WBvo zw2hkk2Dj%kTFhY+$T+W-b7@qD!bkfN#Z2ng@Pd=i3-i?xYfs5Z*1hO?kd7Sp^9`;Y zM2jeGg<-nJD1er@Pc_cSY7wo5dzQX44=%6rn}P_SRbpzsA{6B+!$3B0#;}qwO37G^ zL(V_5JK`XT?OHVk|{_$vQ|oNEpab*BO4F zUTNQ7RUhnRsU`TK#~`)$icsvKh~(pl=3p6m98@k3P#~upd=k*u20SNcb{l^1rUa)>qO997)pYRWMncC8A&&MHlbW?7i^7M`+B$hH~Y|J zd>FYOGQ;j>Zc2e7R{KK7)0>>nn_jYJy&o@sK!4G>-rLKM8Hv)f;hi1D2fAc$+six2 zyVZ@wZ6x|fJ!4KrpCJY=!Mq0;)X)OoS~{Lkh6u8J`eK%u0WtKh6B>GW_)PVc zl}-k`p09qwGtZ@VbYJC!>29V?Dr>>vk?)o(x?!z*9DJ||9qG-&G~#kXxbw{KKYy}J zQKa-dPt~M~E}V?PhW0R26xdA%1T*%ra6SguGu50YHngOTIv)@N|YttEXo#OZfgtP7;H?EeZZxo<}3YlYxtBq znJ!WFR^tmGf0Py}N?kZ(#=VtpC@%xJkDmfcCoBTxq zr_|5gP?u1@vJZbxPZ|G0AW4=tpb84gM2DpJU||(b8kMOV1S3|(yuwZJ&rIiFW(U;5 zUtAW`O6F6Zy+eZ1EDuP~AAHlSY-+A_eI5Gx)%*uro5tljy}kCZU*_d7)oJ>oQSZ3* zneTn`{gnNC&uJd)0aMBzAg021?YJ~b(fmkwZAd696a=0NzBAqBN54KuNDwa*no(^O z6p05bioXUR^uXjpTol*ppHp%1v9e)vkoUAUJyBx3lw0UO39b0?^{}yb!$yca(@DUn zCquRF?t=Zb9`Ed3AI6|L{eX~ijVH`VzSMheKoP7LSSf4g>md>`yi!TkoG5P>Ofp+n z(v~rW+(5L96L{vBb^g51B=(o)?%%xhvT*A5btOpw(TKh^g^4c zw>0%X!_0`{iN%RbVk+A^f{w-4-SSf*fu@FhruNL##F~sF24O~u zyYF<3el2b$$wZ_|uW#@Ak+VAGk#e|kS8nL1g>2B-SNMjMp^8;-FfeofY2fphFHO!{ z*!o4oTb{4e;S<|JEs<1_hPsmAlVNk?_5-Fp5KKU&d#FiNW~Y+pVFk@Cua1I{T+1|+ zHx6rFMor)7L)krbilqsWwy@T+g3DiH5MyVf8Wy}XbEaoFIDr~y;@r&I>FMW{ z?Q+(IgyebZ)-i4jNoXQhq4Muy9Fv+OxU;9_Jmn+<`mEC#%2Q_2bpcgzcinygNI!&^ z=V$)o2&Yz04~+&pPWWn`rrWxJ&}8khR)6B(--!9Q zubo}h+1T)>a@c)H^i``@<^j?|r4*{;tQf78(xn0g39IoZw0(CwY1f<%F>kEaJ zp9u|IeMY5mRdAlw*+gSN^5$Q)ShM<~E=(c8QM+T-Qk)FyKz#Sw0EJ*edYcuOtO#~Cx^(M7w5 z3)rl#L)rF|(Vun2LkFr!rg8Q@=r>9p>(t3Gf_auiJ2Xx9HmxYTa|=MH_SUlYL`mz9 zTTS$`%;D-|Jt}AP1&k7PcnfFNTH0A-*FmxstjBDiZX?}%u%Yq94$fUT&z6od+(Uk> zuqsld#G(b$G8tus=M!N#oPd|PVFX)?M?tCD0tS%2IGTfh}3YA3f&UM)W$_GNV8 zQo+a(ml2Km4o6O%gKTCSDNq+#zCTIQ1*`TIJh~k6Gp;htHBFnne))rlFdGqwC6dx2+La1&Mnko*352k0y z+tQcwndQlX`nc6nb$A9?<-o|r*%aWXV#=6PQic0Ok_D;q>wbv&j7cKc!w4~KF#-{6 z(S%6Za)WpGIWf7jZ3svNG5OLs0>vCL9{V7cgO%zevIVMH{WgP*^D9ws&OqA{yr|m| zKD4*07dGXshJHd#e%x%J+qmS^lS|0Bp?{drv;{@{l9ArPO&?Q5=?OO9=}h$oVe#3b z3Yofj&Cb}WC$PxmRRS)H%&$1-)z7jELS}!u!zQ?A^Y{Tv4QVt*vd@uj-^t2fYRzQj zfxGR>-q|o$3sGn^#VzZ!QQx?h9`njeJry}@x?|k0-GTTA4y3t2E`3DZ!A~D?GiJup z)8%PK2^9OVRlP(24P^4_<|D=H^7}WlWu#LgsdHzB%cPy|f8dD3|A^mh4WXxhLTVu_ z@abE{6Saz|Y{rXYPd4$tfPYo}ef(oQWZ=4Bct-=_9`#Qgp4ma$n$`tOwq#&E18$B; z@Bp)bn3&rEi0>fWWZ@7k5WazfoX`SCO4jQWwVuo+$PmSZn^Hz?O(-tW@*DGxuf)V1 zO_xm&;NVCaHD4dqt(-MlszI3F-p?0!-e$fbiCeuaw66h^TTDLWuaV<@C-`=Xe5WL) zwooG7h>4&*)p3pKMS3O!4>-4jQUN}iAMQ)2*70?hP~)TzzR?-f@?Aqy$$1Iy8VGG$ zMM?8;j!pUX7QQD$gRc_#+=raAS577ga-w?jd`vCiN5lu)dEUkkUPl9!?{$IJNxQys z*E4e$eF&n&+AMRQR2gcaFEjAy*r)G!s(P6D&TfoApMFC_*Ftx0|D0@E-=B7tezU@d zZ{hGiN;YLIoSeRS;9o%dEua4b%4R3;$SugDjP$x;Z!M!@QibuSBb)HY!3zJ7M;^jw zlx6AD50FD&p3JyP*>o+t9YWW8(7P2t!VQQ21pHJOcG_SXQD;(5aX#M6x##5H_Re>6lPyDCjxr*R(+HE%c&QN+b^tbT zXBJk?p)zhJj#I?&Y2n&~XiytG9!1ox;bw5Rbj~)7c(MFBb4>IiRATdhg zmiEFlj@S_hwYYI(ki{}&<;_7(Z0Qkfq>am z&LtL=2qc7rWguk3BtE4zL41@#S;NN*-jWw|7Kx7H7~_%7fPt;TIX}Ubo>;Rmj94V> zNB1=;-9AR7s`Pxn}t_6^3ahlq53e&!Lh85uG zec0vJY_6e`tg7LgfrJ3k!DjR)Bi#L@DHIrZ`sK=<5O0Ip!fxGf*OgGSpP@Hbbe&$9 z;ZI}8lEoC2_7;%L2=w?tb%1oL0V+=Z`7b=P&lNGY;yVBazXRYu;+cQDKvm*7NCxu&i;zub zAJh#11%?w>E2rf2e~C4+rAb-&$^vsdACs7 z@|Ra!OfVM(ke{vyiqh7puf&Yp6cd6{DptUteYfIRWG3pI+5< zBVBI_xkBAc<(pcb$!Y%dTW(b;B;2pOI-(QCsLv@U-D1XJ z(Gk8Q3l7Ws46Aktuj>|s{$6zA&xCPuXL-kB`CgYMs}4IeyG*P51IDwW?8UNQd+$i~ zlxOPtSi5L|gJcF@DwmJA5Ju8HEJ>o{{upwIpb!f{2(vLNBw`7xMbvcw<^{Fj@E~1( z?w`iIMieunS#>nXlmUcSMU+D3rX28f?s7z;X=se6bo8;5vM|O^(D6{A9*ChnGH!RG zP##3>LDC3jZPE4PH32AxrqPk|yIIrq~`aL-=}`okhNu9aT%q z1b)7iJ)CN=V#Ly84N_r7U^SH2FGdE5FpTO2 z630TF$P>GNMu8`rOytb(lB2};`;P4YNwW1<5d3Q~AX#P0aX}R2b2)`rgkp#zTxcGj zAV^cvFbhP|JgWrq_e`~exr~sIR$6p5V?o4Wym3kQ3HA+;Pr$bQ0(PmADVO%MKL!^q z?zAM8j1l4jrq|5X+V!8S*2Wl@=7*pPgciTVK6kS1Ge zMsd_u6DFK$jTnvVtE;qa+8(1sGBu~n&F%dh(&c(Zs4Fc#A=gG^^%^AyH}1^?|8quj zl@Z47h$){PlELJgYZCIHHL= z{U8O>Tw4x3<1{?$8>k-P<}1y9DmAZP_;(3Y*{Sk^H^A=_iSJ@+s5ktgwTXz_2$~W9>VVZsfwCm@s0sQ zeB50_yu@uS+e7QoPvdCwDz{prjo(AFwR%C?z`EL{1`|coJHQTk^nX=tvs1<0arUOJ z!^`*x&&BvTYmemyZ)2p~{%eYX=JVR?DYr(rNgqRMA5E1PR1Iw=prk=L2ldy3r3Vg@27IZx43+ywyzr-X*p*d@tZV+!U#~$-q=8c zgdSuh#r?b4GhEGNai)ayHQpk>5(%j5c@C1K3(W1pb~HeHpaqijJZa-e6vq_8t-^M^ zBJxq|MqZc?pjXPIH}70a5vt!IUh;l}<>VX<-Qcv^u@5(@@M2CHSe_hD$VG-eiV^V( zj7*9T0?di?P$FaD6oo?)<)QT>Npf6Og!GO^GmPV(Km0!=+dE&bk#SNI+C9RGQ|{~O*VC+tXK3!n`5 zHfl6>lwf_aEVV3`0T!aHNZLsj$paS$=LL(?b!Czaa5bbSuZ6#$_@LK<(7yrrl+80| z{tOFd=|ta2Z`^ssozD9BINn45NxUeCQis?-BKmU*Kt=FY-NJ+)8S1ecuFtN-M?&42 zl2$G>u!iNhAk*HoJ^4v^9#ORYp5t^wDj6|lx~5w45#E5wVqI1JQ~9l?nPp1YINf++ zMAdSif~_ETv@Er(EFBI^@L4BULFW>)NI+ejHFP*T}UhWNN`I)RRS8za? z*@`1>9ZB}An%aT5K=_2iQmfE;GcBVHLF!$`I99o5GO`O%O_zLr9AG18>&^HkG(;=V z%}c!OBQ~?MX(9h~tajX{=x)+!cbM7$YzTlmsPOdp2L-?GoW`@{lY9U3f;OUo*BwRB z8A+nv(br0-SH#VxGy#ZrgnGD(=@;HME;yd46EgWJ`EL%oXc&lFpc@Y}^>G(W>h_v_ zlN!`idhX+OjL+~T?19sroAFVGfa5tX-D49w$1g2g_-T|EpHL6}K_aX4$K=LTvwtlF zL*z}j{f+Uoe7{-px3_5iKPA<_7W=>Izkk)!l9ez2w%vi(?Y;i8AxRNLSOGDzNoqoI zP!1uAl}r=_871(G?y`i&)-7{u=%nxk7CZ_Qh#!|ITec zwQn`33GTUM`;D2POWnkqngqJhJRlM>CTONzTG}>^Q0wUunQyn|TAiHzyX2_%ATx%P z%7gW)%4rA9^)M<_%k@`Y?RbC<29sWU&5;@|9thf2#zf8z12$hRcZ!CSb>kUp=4N#y zl3hE#y6>kkA8VY2`W`g5Ip?2qC_BY$>R`iGQLhz2-S>x(RuWv)SPaGdl^)gGw7tjR zH@;jwk!jIaCgSg_*9iF|a);sRUTq30(8I(obh^|}S~}P4U^BIGYqcz;MPpC~Y@k_m zaw4WG1_vz2GdCAX!$_a%GHK**@IrHSkGoN>)e}>yzUTm52on`hYot7cB=oA-h1u|R ztH$11t?54Qg2L+i33FPFKKRm1aOjKST{l1*(nps`>sv%VqeVMWjl5+Gh+9);hIP8? zA@$?}Sc z3qIRpba+y5yf{R6G(u8Z^vkg0Fu&D-7?1s=QZU`Ub{-!Y`I?AGf1VNuc^L3v>)>i# z{DV9W$)>34wnzAXUiV^ZpYKw>UElrN_5Xj6{r_3| z$X5PK`e5$7>~9Dj7gK5ash(dvs`vwfk}&RD`>04;j62zoXESkFBklYaKm5seyiX(P zqQ-;XxlV*yg?Dhlx%xt!b0N3GHp@(p$A;8|%# zZ5m2KL|{on4nr>2_s9Yh=r5ScQ0;aMF)G$-9-Ca6%wA`Pa)i?NGFA|#Yi?{X-4ZO_ z^}%7%vkzvUHa$-^Y#aA+aiR5sa%S|Ebyn`EV<3Pc?ax_f>@sBZF1S;7y$CXd5t5=WGsTKBk8$OfH4v|0?0I=Yp}7c=WBSCg!{0n)XmiU;lfx)**zZaYqmDJelxk$)nZyx5`x$6R|fz(;u zEje5Dtm|a%zK!!tk3{i9$I2b{vXNFy%Bf{50X!x{98+BsDr_u9i>G5%*sqEX|06J0 z^IY{UcEbj6LDwuMh7cH`H@9sVt1l1#8kEQ(LyT@&+K}(ReE`ux8gb0r6L_#bDUo^P z3Ka2lRo52Hdtl_%+pwVs14=q`{d^L58PsU@AMf(hENumaxM{7iAT5sYmWh@hQCO^ zK&}ijo=`VqZ#a3vE?`7QW0ZREL17ZvDfdqKGD?0D4fg{7v%|Yj&_jcKJAB)>=*RS* zto8p6@k%;&^ZF>hvXm&$PCuEp{uqw3VPG$9VMdW5$w-fy2CNNT>E;>ejBgy-m_6`& z97L1p{%srn@O_JQgFpa_#f(_)eb#YS>o>q3(*uB;uZb605(iqM$=NK{nHY=+X2*G) zO3-_Xh%aG}fHWe*==58zBwp%&`mge<8uq8;xIxOd=P%9EK!34^E9sk|(Zq1QSz-JVeP12Fp)-`F|KY$LPwUE?rku zY@OJ)Z9A!ojfzfeyJ9;zv2EM7ZQB)AR5xGa-tMn^bl)FmoIiVyJ@!~@%{}qXXD&Ns zPnfe5U+&ohKefILu_1mPfLGuapX@btta5C#gPB2cjk5m4T}Nfi+Vfka!Yd(L?-c~5 z#ZK4VeQEXNPc4r$K00Fg>g#_W!YZ)cJ?JTS<&68_$#cZT-ME`}tcwqg3#``3M3UPvn+pi}(VNNx6y zFIMVb6OwYU(2`at$gHba*qrMVUl8xk5z-z~fb@Q3Y_+aXuEKH}L+>eW__!IAd@V}L zkw#s%H0v2k5-=vh$^vPCuAi22Luu3uKTf6fPo?*nvj$9(u)4$6tvF-%IM+3pt*cgs z_?wW}J7VAA{_~!?))?s6{M=KPpVhg4fNuU*|3THp@_(q!b*hdl{fjRVFWtu^1dV(f z6iOux9hi&+UK=|%M*~|aqFK{Urfl!TA}UWY#`w(0P!KMe1Si{8|o))Gy6d7;!JQYhgMYmXl?3FfOM2nQGN@~Ap6(G z3+d_5y@=nkpKAhRqf{qQ~k7Z$v&l&@m7Ppt#FSNzKPZM z8LhihcE6i=<(#87E|Wr~HKvVWhkll4iSK$^mUHaxgy8*K$_Zj;zJ`L$naPj+^3zTi z-3NTaaKnD5FPY-~?Tq6QHnmDDRxu0mh0D|zD~Y=vv_qig5r-cIbCpxlju&8Sya)@{ zsmv6XUSi)@(?PvItkiZEeN*)AE~I_?#+Ja-r8$(XiXei2d@Hi7Rx8+rZZb?ZLa{;@*EHeRQ-YDadz~M*YCM4&F-r;E#M+@CSJMJ0oU|PQ^ z=E!HBJDMQ2TN*Y(Ag(ynAL8%^v;=~q?s4plA_hig&5Z0x_^Oab!T)@6kRN$)qEJ6E zNuQjg|G7iwU(N8pI@_6==0CL;lRh1dQF#wePhmu@hADFd3B5KIH#dx(2A zp~K&;Xw}F_N6CU~0)QpQk7s$a+LcTOj1%=WXI(U=Dv!6 z{#<#-)2+gCyyv=Jw?Ab#PVkxPDeH|sAxyG`|Ys}A$PW4TdBv%zDz z^?lwrxWR<%Vzc8Sgt|?FL6ej_*e&rhqJZ3Y>k=X(^dytycR;XDU16}Pc9Vn0>_@H+ zQ;a`GSMEG64=JRAOg%~L)x*w{2re6DVprNp+FcNra4VdNjiaF0M^*>CdPkt(m150rCue?FVdL0nFL$V%5y6N z%eLr5%YN7D06k5ji5*p4v$UMM)G??Q%RB27IvH7vYr_^3>1D-M66#MN8tWGw>WED} z5AhlsanO=STFYFs)Il_0i)l)f<8qn|$DW7ZXhf5xI;m+7M5-%P63XFQrG9>DMqHc} zsgNU9nR`b}E^mL5=@7<1_R~j@q_2U^3h|+`7YH-?C=vme1C3m`Fe0HC>pjt6f_XMh zy~-i-8R46QNYneL4t@)<0VU7({aUO?aH`z4V2+kxgH5pYD5)wCh75JqQY)jIPN=U6 z+qi8cGiOtXG2tXm;_CfpH9ESCz#i5B(42}rBJJF$jh<1sbpj^8&L;gzGHb8M{of+} zzF^8VgML2O9nxBW7AvdEt90vp+#kZxWf@A)o9f9}vKJy9NDBjBW zSt=Hcs=YWCwnfY1UYx*+msp{g!w0HC<_SM!VL1(I2PE?CS}r(eh?{I)mQixmo5^p# zV?2R!R@3GV6hwTCrfHiK#3Orj>I!GS2kYhk1S;aFBD_}u2v;0HYFq}Iz1Z(I4oca4 zxquja8$+8JW_EagDHf$a1OTk5S97umGSDaj)gH=fLs9>_=XvVj^Xj9a#gLdk=&3tl zfmK9MNnIX9v{?%xdw7568 zNrZ|roYs(vC4pHB5RJ8>)^*OuyNC>x7ad)tB_}3SgQ96+-JT^Qi<`xi=)_=$Skwv~ zdqeT9Pa`LYvCAn&rMa2aCDV(TMI#PA5g#RtV|CWpgDYRA^|55LLN^uNh*gOU>Z=a06qJ;$C9z8;n-Pq=qZnc1zUwJ@t)L;&NN+E5m zRkQ(SeM8=l-aoAKGKD>!@?mWTW&~)uF2PYUJ;tB^my`r9n|Ly~0c%diYzqs9W#FTjy?h&X3TnH zXqA{QI82sdjPO->f=^K^f>N`+B`q9&rN0bOXO79S&a9XX8zund(kW7O76f4dcWhIu zER`XSMSFbSL>b;Rp#`CuGJ&p$s~G|76){d?xSA5wVg##_O0DrmyEYppyBr%fyWbbv zp`K84JwRNP$d-pJ!Qk|(RMr?*!wi1if-9G#0p>>1QXKXWFy)eB3ai)l3601q8!9JC zvU#ZWWDNKq9g6fYs?JQ)Q4C_cgTy3FhgKb8s&m)DdmL5zhNK#8wWg!J*7G7Qhe9VU zha?^AQTDpYcuN!B+#1dE*X{<#!M%zfUQbj=zLE{dW0XeQ7-oIsGY6RbkP2re@Q{}r_$iiH0xU%iN*ST`A)-EH6eaZB$GA#v)cLi z*MpA(3bYk$oBDKAzu^kJoSUsDd|856DApz={3u8sbQV@JnRkp2nC|)m;#T=DvIL-O zI4vh;g7824l}*`_p@MT4+d`JZ2%6NQh=N9bmgJ#q!hK@_<`HQq3}Z8Ij>3%~<*= zcv=!oT#5xmeGI92lqm9sGVE%#X$ls;St|F#u!?5Y7syhx6q#MVRa&lBmmn%$C0QzU z);*ldgwwCmzM3uglr}!Z2G+?& zf%Dpo&mD%2ZcNFiN-Z0f;c_Q;A%f@>26f?{d1kxIJD}LxsQkB47SAdwinfMILZdN3 zfj^HmTzS3Ku5BxY>ANutS8WPQ-G>v4^_Qndy==P3pDm+Xc?>rUHl-4+^%Sp5atOja z2oP}ftw-rqnb}+khR3CrRg^ibi6?QYk1*i^;kQGirQ=uB9Sd1NTfT-Rbv;hqnY4neE5H1YUrjS2m+2&@uXiAo- zrKUX|Ohg7(6F(AoP~tj;NZlV#xsfo-5reuQHB$&EIAhyZk;bL;k9ouDmJNBAun;H& zn;Of1z_Qj`x&M;5X;{s~iGzBQTY^kv-k{ksbE*Dl%Qf%N@hQCfY~iUw!=F-*$cpf2 z3wix|aLBV0b;W@z^%7S{>9Z^T^fLOI68_;l@+Qzaxo`nAI8emTV@rRhEKZ z?*z_{oGdI~R*#<2{bkz$G~^Qef}$*4OYTgtL$e9q!FY7EqxJ2`zk6SQc}M(k(_MaV zSLJnTXw&@djco1~a(vhBl^&w=$fa9{Sru>7g8SHahv$&Bl(D@(Zwxo_3r=;VH|uc5 zi1Ny)J!<(KN-EcQ(xlw%PNwK8U>4$9nVOhj(y0l9X^vP1TA>r_7WtSExIOsz`nDOP zs}d>Vxb2Vo2e5x8p(n~Y5ggAyvib>d)6?)|E@{FIz?G3PVGLf7-;BxaP;c?7ddH$z zA+{~k^V=bZuXafOv!RPsE1GrR3J2TH9uB=Z67gok+u`V#}BR86hB1xl}H4v`F+mRfr zYhortD%@IGfh!JB(NUNSDh+qDz?4ztEgCz&bIG-Wg7w-ua4ChgQR_c+z8dT3<1?uX z*G(DKy_LTl*Ea!%v!RhpCXW1WJO6F`bgS-SB;Xw9#! z<*K}=#wVu9$`Yo|e!z-CPYH!nj7s9dEPr-E`DXUBu0n!xX~&|%#G=BeM?X@shQQMf zMvr2!y7p_gD5-!Lnm|a@z8Of^EKboZsTMk%5VsJEm>VsJ4W7Kv{<|#4f-qDE$D-W>gWT%z-!qXnDHhOvLk=?^a1*|0j z{pW{M0{#1VcR5;F!!fIlLVNh_Gj zbnW(_j?0c2q$EHIi@fSMR{OUKBcLr{Y&$hrM8XhPByyZaXy|dd&{hYQRJ9@Fn%h3p7*VQolBIV@Eq`=y%5BU~3RPa^$a?ixp^cCg z+}Q*X+CW9~TL29@OOng(#OAOd!)e$d%sr}^KBJ-?-X&|4HTmtemxmp?cT3uA?md4% zT8yZ0U;6Rg6JHy3fJae{6TMGS?ZUX6+gGTT{Q{)SI85$5FD{g-eR%O0KMpWPY`4@O zx!hen1*8^E(*}{m^V_?}(b5k3hYo=T+$&M32+B`}81~KKZhY;2H{7O-M@vbCzuX0n zW-&HXeyr1%I3$@ns-V1~Lb@wIpkmx|8I~ob1Of7i6BTNysEwI}=!nU%q7(V_^+d*G z7G;07m(CRTJup!`cdYi93r^+LY+`M*>aMuHJm(A8_O8C#A*$!Xvddgpjx5)?_EB*q zgE8o5O>e~9IiSC@WtZpF{4Bj2J5eZ>uUzY%TgWF7wdDE!fSQIAWCP)V{;HsU3ap?4 znRsiiDbtN7i9hapO;(|Ew>Ip2TZSvK9Z^N21%J?OiA_&eP1{(Pu_=%JjKy|HOardq ze?zK^K zA%sjF64*Wufad%H<) z^|t>e*h+Z1#l=5wHexzt9HNDNXgM=-OPWKd^5p!~%SIl>Fo&7BvNpbf8{NXmH)o{r zO=aBJ;meX1^{O%q;kqdw*5k!Y7%t_30 zy{nGRVc&5qt?dBwLs+^Sfp;f`YVMSB#C>z^a9@fpZ!xb|b-JEz1LBX7ci)V@W+kvQ89KWA0T~Lj$aCcfW#nD5bt&Y_< z-q{4ZXDqVg?|0o)j1%l0^_it0WF*LCn-+)c!2y5yS7aZIN$>0LqNnkujV*YVes(v$ zY@_-!Q;!ZyJ}Bg|G-~w@or&u0RO?vlt5*9~yeoPV_UWrO2J54b4#{D(D>jF(R88u2 zo#B^@iF_%S>{iXSol8jpmsZuJ?+;epg>k=$d`?GSegAVp3n$`GVDvK${N*#L_1`44 z{w0fL{2%)0|E+qgZtjX}itZz^KJt4Y;*8uSK}Ft38+3>j|K(PxIXXR-t4VopXo#9# zt|F{LWr-?34y`$nLBVV_*UEgA6AUI65dYIbqpNq9cl&uLJ0~L}<=ESlOm?Y-S@L*d z<7vt}`)TW#f%Rp$Q}6@3=j$7Tze@_uZO@aMn<|si{?S}~maII`VTjs&?}jQ4_cut9$)PEqMukwoXobzaKx^MV z2fQwl+;LSZ$qy%Tys0oo^K=jOw$!YwCv^ei4NBVauL)tN%=wz9M{uf{IB(BxK|lT*pFkmNK_1tV`nb%jH=a0~VNq2RCKY(rG7jz!-D^k)Ec)yS%17pE#o6&eY+ z^qN(hQT$}5F(=4lgNQhlxj?nB4N6ntUY6(?+R#B?W3hY_a*)hnr4PA|vJ<6p`K3Z5Hy z{{8(|ux~NLUW=!?9Qe&WXMTAkQnLXg(g=I@(VG3{HE13OaUT|DljyWXPs2FE@?`iU z4GQlM&Q=T<4&v@Fe<+TuXiZQT3G~vZ&^POfmI1K2h6t4eD}Gk5XFGpbj1n_g*{qmD6Xy z`6Vv|lLZtLmrnv*{Q%xxtcWVj3K4M%$bdBk_a&ar{{GWyu#ljM;dII;*jP;QH z#+^o-A4np{@|Mz+LphTD0`FTyxYq#wY)*&Ls5o{0z9yg2K+K7ZN>j1>N&;r+Z`vI| zDzG1LJZ+sE?m?>x{5LJx^)g&pGEpY=fQ-4}{x=ru;}FL$inHemOg%|R*ZXPodU}Kh zFEd5#+8rGq$Y<_?k-}r5zgQ3jRV=ooHiF|@z_#D4pKVEmn5CGV(9VKCyG|sT9nc=U zEoT67R`C->KY8Wp-fEcjjFm^;Cg(ls|*ABVHq8clBE(;~K^b+S>6uj70g? z&{XQ5U&!Z$SO7zfP+y^8XBbiu*Cv-yJG|l-oe*!s5$@Lh_KpxYL2sx`B|V=dETN>5K+C+CU~a_3cI8{vbu$TNVdGf15*>D zz@f{zIlorkY>TRh7mKuAlN9A0>N>SV`X)+bEHms=mfYTMWt_AJtz_h+JMmrgH?mZt zm=lfdF`t^J*XLg7v+iS)XZROygK=CS@CvUaJo&w2W!Wb@aa?~Drtf`JV^cCMjngVZ zv&xaIBEo8EYWuML+vxCpjjY^s1-ahXJzAV6hTw%ZIy!FjI}aJ+{rE&u#>rs)vzuxz z+$5z=7W?zH2>Eb32dvgHYZtCAf!=OLY-pb4>Ae79rd68E2LkVPj-|jFeyqtBCCwiW zkB@kO_(3wFq)7qwV}bA=zD!*@UhT`geq}ITo%@O(Z5Y80nEX~;0-8kO{oB6|(4fQh z);73T!>3@{ZobPwRv*W?7m0Ml9GmJBCJd&6E?hdj9lV= z4flNfsc(J*DyPv?RCOx!MSvk(M952PJ-G|JeVxWVjN~SNS6n-_Ge3Q;TGE;EQvZg86%wZ`MB zSMQua(i*R8a75!6$QRO^(o7sGoomb+Y{OMy;m~Oa`;P9Yqo>?bJAhqXxLr7_3g_n>f#UVtxG!^F#1+y@os6x(sg z^28bsQ@8rw%Gxk-stAEPRbv^}5sLe=VMbkc@Jjimqjvmd!3E7+QnL>|(^3!R} zD-l1l7*Amu@j+PWLGHXXaFG0Ct2Q=}5YNUxEQHCAU7gA$sSC<5OGylNnQUa>>l%sM zyu}z6i&({U@x^hln**o6r2s-(C-L50tQvz|zHTqW!ir?w&V23tuYEDJVV#5pE|OJu z7^R!A$iM$YCe?8n67l*J-okwfZ+ZTkGvZ)tVPfR;|3gyFjF)8V zyXXN=!*bpyRg9#~Bg1+UDYCt0 ztp4&?t1X0q>uz;ann$OrZs{5*r`(oNvw=$7O#rD|Wuv*wIi)4b zGtq4%BX+kkagv3F9Id6~-c+1&?zny%w5j&nk9SQfo0k4LhdSU_kWGW7axkfpgR`8* z!?UTG*Zi_baA1^0eda8S|@&F z{)Rad0kiLjB|=}XFJhD(S3ssKlveFFmkN{Vl^_nb!o5M!RC=m)V&v2%e?ZoRC@h3> zJ(?pvToFd`*Zc@HFPL#=otWKwtuuQ_dT-Hr{S%pQX<6dqVJ8;f(o)4~VM_kEQkMR+ zs1SCVi~k>M`u1u2xc}>#D!V&6nOOh-E$O&SzYrjJdZpaDv1!R-QGA141WjQe2s0J~ zQ;AXG)F+K#K8_5HVqRoRM%^EduqOnS(j2)|ctA6Q^=|s_WJYU;Z%5bHp08HPL`YF2 zR)Ad1z{zh`=sDs^&V}J z%$Z$!jd7BY5AkT?j`eqMs%!Gm@T8)4w3GYEX~IwgE~`d|@T{WYHkudy(47brgHXx& zBL1yFG6!!!VOSmDxBpefy2{L_u5yTwja&HA!mYA#wg#bc-m%~8aRR|~AvMnind@zs zy>wkShe5&*un^zvSOdlVu%kHsEo>@puMQ`b1}(|)l~E{5)f7gC=E$fP(FC2=F<^|A zxeIm?{EE!3sO!Gr7e{w)Dx(uU#3WrFZ>ibmKSQ1tY?*-Nh1TDHLe+k*;{Rp!Bmd_m zb#^kh`Y*8l|9Cz2e{;RL%_lg{#^Ar+NH|3z*Zye>!alpt{z;4dFAw^^H!6ING*EFc z_yqhr8d!;%nHX9AKhFQZBGrSzfzYCi%C!(Q5*~hX>)0N`vbhZ@N|i;_972WSx*>LH z87?en(;2_`{_JHF`Sv6Wlps;dCcj+8IJ8ca6`DsOQCMb3n# z3)_w%FuJ3>fjeOOtWyq)ag|PmgQbC-s}KRHG~enBcIwqIiGW8R8jFeBNY9|YswRY5 zjGUxdGgUD26wOpwM#8a!Nuqg68*dG@VM~SbOroL_On0N6QdT9?)NeB3@0FCC?Z|E0 z6TPZj(AsPtwCw>*{eDEE}Gby>0q{*lI+g2e&(YQrsY&uGM{O~}(oM@YWmb*F zA0^rr5~UD^qmNljq$F#ARXRZ1igP`MQx4aS6*MS;Ot(1L5jF2NJ;de!NujUYg$dr# z=TEL_zTj2@>ZZN(NYCeVX2==~=aT)R30gETO{G&GM4XN<+!&W&(WcDP%oL8PyIVUC zs5AvMgh6qr-2?^unB@mXK*Dbil^y-GTC+>&N5HkzXtozVf93m~xOUHn8`HpX=$_v2 z61H;Z1qK9o;>->tb8y%#4H)765W4E>TQ1o0PFj)uTOPEvv&}%(_mG0ISmyhnQV33Z$#&yd{ zc{>8V8XK$3u8}04CmAQ#I@XvtmB*s4t8va?-IY4@CN>;)mLb_4!&P3XSw4pA_NzDb zORn!blT-aHk1%Jpi>T~oGLuh{DB)JIGZ9KOsciWs2N7mM1JWM+lna4vkDL?Q)z_Ct z`!mi0jtr+4*L&N7jk&LodVO#6?_qRGVaucqVB8*us6i3BTa^^EI0x%EREQSXV@f!lak6Wf1cNZ8>*artIJ(ADO*=<-an`3zB4d*oO*8D1K!f z*A@P1bZCNtU=p!742MrAj%&5v%Xp_dSX@4YCw%F|%Dk=u|1BOmo)HsVz)nD5USa zR~??e61sO(;PR)iaxK{M%QM_rIua9C^4ppVS$qCT9j2%?*em?`4Z;4@>I(c%M&#cH z>4}*;ej<4cKkbCAjjDsyKS8rIm90O)Jjgyxj5^venBx&7B!xLmzxW3jhj7sR(^3Fz z84EY|p1NauwXUr;FfZjdaAfh%ivyp+^!jBjJuAaKa!yCq=?T_)R!>16?{~p)FQ3LDoMyG%hL#pR!f@P%*;#90rs_y z@9}@r1BmM-SJ#DeuqCQk=J?ixDSwL*wh|G#us;dd{H}3*-Y7Tv5m=bQJMcH+_S`zVtf;!0kt*(zwJ zs+kedTm!A}cMiM!qv(c$o5K%}Yd0|nOd0iLjus&;s0Acvoi-PFrWm?+q9f^FslxGi z6ywB`QpL$rJzWDg(4)C4+!2cLE}UPCTBLa*_=c#*$b2PWrRN46$y~yST3a2$7hEH= zNjux+wna^AzQ=KEa_5#9Ph=G1{S0#hh1L3hQ`@HrVnCx{!fw_a0N5xV(iPdKZ-HOM za)LdgK}1ww*C_>V7hbQnTzjURJL`S%`6nTHcgS+dB6b_;PY1FsrdE8(2K6FN>37!62j_cBlui{jO^$dPkGHV>pXvW0EiOA zqW`YaSUBWg_v^Y5tPJfWLcLpsA8T zG)!x>pKMpt!lv3&KV!-um= zKCir6`bEL_LCFx4Z5bAFXW$g3Cq`?Q%)3q0r852XI*Der*JNuKUZ`C{cCuu8R8nkt z%pnF>R$uY8L+D!V{s^9>IC+bmt<05h**>49R*#vpM*4i0qRB2uPbg8{{s#9yC;Z18 zD7|4m<9qneQ84uX|J&f-g8a|nFKFt34@Bt{CU`v(SYbbn95Q67*)_Esl_;v291s=9 z+#2F2apZU4Tq=x+?V}CjwD(P=U~d<=mfEFuyPB`Ey82V9G#Sk8H_Ob_RnP3s?)S_3 zr%}Pb?;lt_)Nf>@zX~D~TBr;-LS<1I##8z`;0ZCvI_QbXNh8Iv)$LS=*gHr;}dgb=w5$3k2la1keIm|=7<-JD>)U%=Avl0Vj@+&vxn zt-)`vJxJr88D&!}2^{GPXc^nmRf#}nb$4MMkBA21GzB`-Or`-3lq^O^svO7Vs~FdM zv`NvzyG+0T!P8l_&8gH|pzE{N(gv_tgDU7SWeiI-iHC#0Ai%Ixn4&nt{5y3(GQs)i z&uA;~_0shP$0Wh0VooIeyC|lak__#KVJfxa7*mYmZ22@(<^W}FdKjd*U1CqSjNKW% z*z$5$=t^+;Ui=MoDW~A7;)Mj%ibX1_p4gu>RC}Z_pl`U*{_z@+HN?AF{_W z?M_X@o%w8fgFIJ$fIzBeK=v#*`mtY$HC3tqw7q^GCT!P$I%=2N4FY7j9nG8aIm$c9 zeKTxVKN!UJ{#W)zxW|Q^K!3s;(*7Gbn;e@pQBCDS(I|Y0euK#dSQ_W^)sv5pa%<^o zyu}3d?Lx`)3-n5Sy9r#`I{+t6x%I%G(iewGbvor&I^{lhu-!#}*Q3^itvY(^UWXgvthH52zLy&T+B)Pw;5>4D6>74 zO_EBS)>l!zLTVkX@NDqyN2cXTwsUVao7$HcqV2%t$YzdAC&T)dwzExa3*kt9d(}al zA~M}=%2NVNUjZiO7c>04YH)sRelXJYpWSn^aC$|Ji|E13a^-v2MB!Nc*b+=KY7MCm zqIteKfNkONq}uM;PB?vvgQvfKLPMB8u5+Am=d#>g+o&Ysb>dX9EC8q?D$pJH!MTAqa=DS5$cb+;hEvjwVfF{4;M{5U&^_+r zvZdu_rildI!*|*A$TzJ&apQWV@p{!W`=?t(o0{?9y&vM)V)ycGSlI3`;ps(vf2PUq zX745#`cmT*ra7XECC0gKkpu2eyhFEUb?;4@X7weEnLjXj_F~?OzL1U1L0|s6M+kIhmi%`n5vvDALMagi4`wMc=JV{XiO+^ z?s9i7;GgrRW{Mx)d7rj)?(;|b-`iBNPqdwtt%32se@?w4<^KU&585_kZ=`Wy^oLu9 z?DQAh5z%q;UkP48jgMFHTf#mj?#z|=w= z(q6~17Vn}P)J3M?O)x))%a5+>TFW3No~TgP;f}K$#icBh;rSS+R|}l鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34e..1af9e093 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a..1aa94a42 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt index c70d2413..27c591e5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt @@ -7,6 +7,7 @@ import com.kotlindiscord.kord.extensions.checks.hasPermission import com.kotlindiscord.kord.extensions.modules.extra.phishing.DetectionAction import com.kotlindiscord.kord.extensions.modules.extra.phishing.extPhishing import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.extPluralKit +import com.kotlindiscord.kord.extensions.modules.extra.welcome.welcomeChannel import dev.kord.common.entity.Permission import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent @@ -47,7 +48,6 @@ import org.hyacinthbots.lilybot.utils.database import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.kohsuke.github.GitHub import org.kohsuke.github.GitHubBuilder -import org.quiltmc.community.cozy.modules.welcome.welcomeChannel import java.io.IOException import kotlin.io.path.Path import kotlin.time.Duration.Companion.minutes diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WelcomeChannelCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WelcomeChannelCollection.kt index adc9e613..2b98e2e2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WelcomeChannelCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WelcomeChannelCollection.kt @@ -14,7 +14,7 @@ import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.WelcomeChannelData import org.koin.core.component.inject import org.litote.kmongo.eq -import org.quiltmc.community.cozy.modules.welcome.data.WelcomeChannelData as CozyWelcomeChannelData +import com.kotlindiscord.kord.extensions.modules.extra.welcome.data.WelcomeChannelData as KordExWelcomeChannelData /** * This class contains the functions for interacting with the [Welcome channel database][WelcomeChannelData]. This class @@ -26,7 +26,7 @@ import org.quiltmc.community.cozy.modules.welcome.data.WelcomeChannelData as Coz * @see setUrlForChannel * @see removeChannel */ -class WelcomeChannelCollection : KordExKoinComponent, CozyWelcomeChannelData { +class WelcomeChannelCollection : KordExKoinComponent, KordExWelcomeChannelData { private val db: Database by inject() @PublishedApi From 27b72119ad0facd829f3230c0dd23caaa61448ab Mon Sep 17 00:00:00 2001 From: NoComment Date: Wed, 31 Jan 2024 19:06:49 +0000 Subject: [PATCH 14/42] Update dependencies --- build.gradle.kts | 20 -------------------- gradle/libs.versions.toml | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 20 ++++++++++---------- 4 files changed, 13 insertions(+), 33 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 833d2f72..766ff4ed 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,26 +30,6 @@ repositories { url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") } - maven { - name = "Fabric" - url = uri("https://maven.fabricmc.net/") - } - - maven { - name = "QuiltMC (Releases)" - url = uri("https://maven.quiltmc.org/repository/release/") - } - - maven { - name = "QuiltMC (Snapshots)" - url = uri("https://maven.quiltmc.org/repository/snapshot/") - } - - maven { - name = "Shedaniel" - url = uri("https://maven.shedaniel.me") - } - maven { name = "JitPack" url = uri("https://jitpack.io") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73b50f31..556902ed 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # Plugins kotlin = "1.9.22" shadow = "8.1.1" -detekt = "1.23.4" +detekt = "1.23.5" git-hooks = "0.0.2" grgit = "5.2.1" blossom = "2.1.0" @@ -15,7 +15,7 @@ logback = "1.4.14" github-api = "1.318" kmongo = "4.11.0" dma = "v0.2.1" -docgenerator = "main-SNAPSHOT" +docgenerator = "0.1.3" [libraries] kord-extensions-core = { module = "com.kotlindiscord.kord.extensions:kord-extensions", version.ref = "kord-extensions" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e093..a80b22ce 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f..25da30db 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From c499974532768e46413dd946e0c1db187a4a48e5 Mon Sep 17 00:00:00 2001 From: NoComment Date: Thu, 8 Feb 2024 15:47:56 +0000 Subject: [PATCH 15/42] Fix cancelling reminders with moderation command. Turns out we were trying to cancel the reminder for the command executor haha very optimal --- .../org/hyacinthbots/lilybot/extensions/util/Reminders.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt index a212ee6d..ba1dea22 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt @@ -384,7 +384,7 @@ class Reminders : Extension() { } } - ReminderCollection().removeReminder(user.id, arguments.reminder) + ReminderCollection().removeReminder(arguments.user.id, arguments.reminder) markReminderCompleteOrCancelled( reminder.guildId, reminder.channelId, reminder.messageId, wasCancelled = true, From 631e8e2e10c54209349d7b52fc9798ecb5d0643b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 9 Feb 2024 16:34:24 +0000 Subject: [PATCH 16/42] Add 'Auto-invite Moderator Role' option (#382) * Add 'Auto-invite Moderator Role' option * Fix v7 migration * Fix compilation --- docs/commands.md | 1 + .../lilybot/database/entities/Config.kt | 1 + .../lilybot/database/migrations/Migrator.kt | 2 + .../database/migrations/config/configV7.kt | 15 ++++++ .../lilybot/extensions/config/Config.kt | 46 ++++++++++++++++--- .../extensions/events/ModThreadInviting.kt | 4 +- 6 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt diff --git a/docs/commands.md b/docs/commands.md index 53e00423..02ea5d53 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -47,6 +47,7 @@ Required Member Permissions: Manage Server * `warn-auto-punishments` - Whether to automatically punish users for reach a certain threshold on warns - Optional Boolean * `log-publicly` - Whether to log moderation publicly or not. - Optional Boolean * `ban-dm-message` - A custom message to send to users when they are banned. - Optional String + * `auto-invite-moderator-role` - Silently ping moderators to invite them to new threads. - Optional Boolean --- #### Command name: `config logging` diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt index 84a256af..519c9744 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt @@ -56,6 +56,7 @@ data class ModerationConfigData( val autoPunishOnWarn: Boolean?, val publicLogging: Boolean?, val banDmMessage: String?, + val autoInviteModeratorRole: Boolean? ) /** diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt index 3c8af692..8497b07d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt @@ -24,6 +24,7 @@ import org.hyacinthbots.lilybot.database.migrations.config.configV3 import org.hyacinthbots.lilybot.database.migrations.config.configV4 import org.hyacinthbots.lilybot.database.migrations.config.configV5 import org.hyacinthbots.lilybot.database.migrations.config.configV6 +import org.hyacinthbots.lilybot.database.migrations.config.configV7 import org.hyacinthbots.lilybot.database.migrations.main.mainV1 import org.hyacinthbots.lilybot.database.migrations.main.mainV2 import org.hyacinthbots.lilybot.database.migrations.main.mainV3 @@ -121,6 +122,7 @@ object Migrator : KordExKoinComponent { 4 -> ::configV4 5 -> ::configV5 6 -> ::configV6 + 7 -> ::configV7 else -> break }(db.configDatabase) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt new file mode 100644 index 00000000..6af25904 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt @@ -0,0 +1,15 @@ +package org.hyacinthbots.lilybot.database.migrations.config + +import org.hyacinthbots.lilybot.database.entities.ModerationConfigData +import org.litote.kmongo.coroutine.CoroutineDatabase +import org.litote.kmongo.exists +import org.litote.kmongo.setValue + +suspend fun configV7(db: CoroutineDatabase) { + with(db.getCollection("moderationConfigData")) { + updateMany( + ModerationConfigData::autoInviteModeratorRole exists false, + setValue(ModerationConfigData::autoInviteModeratorRole, null) + ) + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt index d8e42dc7..d873e6c5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt @@ -80,6 +80,7 @@ class Config : Extension() { null, null, null, + null, null ) ) @@ -134,7 +135,7 @@ class Config : Extension() { field { name = "Log publicly" value = when (arguments.logPublicly) { - true -> "True" + true -> "Enabled" false -> "Disabled" null -> "Disabled" } @@ -155,6 +156,14 @@ class Config : Extension() { name = "Ban DM Message" value = arguments.banDmMessage ?: "No custom Ban DM message set" } + field { + name = "Auto-invite Moderator Role" + value = when (arguments.autoInviteModeratorRole) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } footer { text = "Configured by ${user.asUserOrNull()?.username}" } @@ -175,7 +184,8 @@ class Config : Extension() { arguments.quickTimeoutLength, arguments.warnAutoPunishments, arguments.logPublicly, - arguments.banDmMessage + arguments.banDmMessage, + arguments.autoInviteModeratorRole ) ) @@ -632,20 +642,39 @@ class Config : Extension() { } field { name = "Action log" - value = - config.channel?.let { guild!!.getChannelOrNull(it)?.mention } ?: "Disabled" + value = config.channel?.let { guild!!.getChannelOrNull(it)?.mention } ?: "Disabled" } field { name = "Log publicly" value = when (config.publicLogging) { - true -> "True" + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } + field { + name = "Quick timeout length" + value = config.quickTimeoutLength.interval() ?: "No quick timeout length set" + } + field { + name = "Warning Auto-punishments" + value = when (config.autoPunishOnWarn) { + true -> "Enabled" false -> "Disabled" null -> "Disabled" } } field { name = "Ban DM Message" - value = config.banDmMessage ?: "None" + value = config.banDmMessage ?: "No custom Ban DM message set" + } + field { + name = "Auto-invite Moderator Role" + value = when (config.autoInviteModeratorRole) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } } timestamp = Clock.System.now() } @@ -765,6 +794,11 @@ class Config : Extension() { name = "ban-dm-message" description = "A custom message to send to users when they are banned." } + + val autoInviteModeratorRole by optionalBoolean { + name = "auto-invite-moderator-role" + description = "Silently ping moderators to invite them to new threads." + } } inner class LoggingArgs : Arguments() { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt index 0bb51484..fb3466ec 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt @@ -21,7 +21,7 @@ class ModThreadInviting : Extension() { anyGuild() failIf { event.channel.ownerId == kord.selfId || - event.channel.member != null + event.channel.member != null } } @@ -38,7 +38,7 @@ class ModThreadInviting : Extension() { val config = ModerationConfigCollection().getConfig(channel.guildId) ?: return@action - if (!config.enabled || config.role == null) return@action + if (!config.enabled || config.role == null || config.autoInviteModeratorRole != true) return@action val moderatorRole = channel.guild.getRoleOrNull(config.role) ?: return@action From 359886e3055a5e2d053d9419f0e92c6f8d50c822 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:38:17 +0000 Subject: [PATCH 17/42] Fix unpinning/pinning of old messages causing edit log (#381) --- .../hyacinthbots/lilybot/extensions/events/MessageEdit.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt index 7b839516..c3dbffdd 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt @@ -42,7 +42,8 @@ class MessageEdit : Extension() { requiredConfigs(ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) failIf { val message = event.message.asMessageOrNull() - message?.author?.isBot == true || event.old?.content == message?.content + message?.author?.isBot == true || event.old?.content == message?.content || + event.old?.content.isNullOrEmpty() } } action { @@ -60,7 +61,8 @@ class MessageEdit : Extension() { anyGuild() requiredConfigs(ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) failIf { - event.old?.content == event.message.asMessageOrNull()?.content + event.old?.content == event.message.asMessageOrNull()?.content || + event.old?.content.isNullOrEmpty() } } action { From 7b4a987c5baf3e1c4110625e7a483e54b9db5821 Mon Sep 17 00:00:00 2001 From: NoComment Date: Wed, 21 Feb 2024 12:25:55 +0000 Subject: [PATCH 18/42] Update dependencies --- gradle/libs.versions.toml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 556902ed..7785a02f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,25 +4,24 @@ kotlin = "1.9.22" shadow = "8.1.1" detekt = "1.23.5" git-hooks = "0.0.2" -grgit = "5.2.1" +grgit = "5.2.2" blossom = "2.1.0" # Libraries -kord-extensions = "1.7.1-20240117.210840-21" -kord-welcome = "1.7.1-20240117.210840-10" +kord-extensions = "1.7.2-20240221.102953-1" logging = "6.0.3" -logback = "1.4.14" +logback = "1.5.0" github-api = "1.318" kmongo = "4.11.0" dma = "v0.2.1" -docgenerator = "0.1.3" +docgenerator = "0.1.4" [libraries] kord-extensions-core = { module = "com.kotlindiscord.kord.extensions:kord-extensions", version.ref = "kord-extensions" } kord-extensions-phishing = { module = "com.kotlindiscord.kord.extensions:extra-phishing", version.ref = "kord-extensions" } kord-extensions-pluralkit = { module = "com.kotlindiscord.kord.extensions:extra-pluralkit", version.ref = "kord-extensions"} kord-extensions-unsafe = { module = "com.kotlindiscord.kord.extensions:unsafe", version.ref = "kord-extensions"} -kord-extensions-welcome = { module = "com.kotlindiscord.kord.extensions:extra-welcome", version.ref = "kord-welcome"} +kord-extensions-welcome = { module = "com.kotlindiscord.kord.extensions:extra-welcome", version.ref = "kord-extensions"} kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } logging = { module = "io.github.oshai:kotlin-logging", version.ref = "logging" } From fbfcde570b9e99f4a37a2c768f047134c990ad78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:55:14 +0100 Subject: [PATCH 19/42] Bump org.kohsuke:github-api from 1.318 to 1.319 (#385) Bumps [org.kohsuke:github-api](https://github.com/hub4j/github-api) from 1.318 to 1.319. - [Release notes](https://github.com/hub4j/github-api/releases) - [Changelog](https://github.com/hub4j/github-api/blob/main/CHANGELOG.md) - [Commits](https://github.com/hub4j/github-api/compare/github-api-1.318...github-api-1.319) --- updated-dependencies: - dependency-name: org.kohsuke:github-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 852daae7..3ff3b60e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ blossom = "2.1.0" kord-extensions = "1.8.0-20240319.115836-21" logging = "6.0.3" logback = "1.5.0" -github-api = "1.318" +github-api = "1.319" kmongo = "4.11.0" docgenerator = "0.1.5-SNAPSHOT" From 97674b2018d8d63d7b01ba2e6c1687657e8a267f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:56:12 +0100 Subject: [PATCH 20/42] Bump kotlin from 1.9.22 to 1.9.23 (#390) Bumps `kotlin` from 1.9.22 to 1.9.23. Updates `org.jetbrains.kotlin.jvm` from 1.9.22 to 1.9.23 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v1.9.23/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.22...v1.9.23) Updates `org.jetbrains.kotlin.plugin.serialization` from 1.9.22 to 1.9.23 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v1.9.23/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.22...v1.9.23) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin.jvm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jetbrains.kotlin.plugin.serialization dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3ff3b60e..8db2c797 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # Plugins -kotlin = "1.9.22" +kotlin = "1.9.23" shadow = "8.1.1" detekt = "1.23.5" git-hooks = "0.0.2" From 96b3cd641c3e6e464054c0dfd8b764c360fef929 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:57:48 +0100 Subject: [PATCH 21/42] Bump ch.qos.logback:logback-classic from 1.5.0 to 1.5.3 (#388) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.0 to 1.5.3. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.0...v_1.5.3) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: NoComment <67918617+NoComment1105@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8db2c797..39eb3086 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ blossom = "2.1.0" # Libraries kord-extensions = "1.8.0-20240319.115836-21" logging = "6.0.3" -logback = "1.5.0" +logback = "1.5.3" github-api = "1.319" kmongo = "4.11.0" docgenerator = "0.1.5-SNAPSHOT" From b82f7ef609b70763acab6debe893b7e05115fd4d Mon Sep 17 00:00:00 2001 From: NoComment Date: Tue, 2 Apr 2024 14:18:31 +0100 Subject: [PATCH 22/42] Create a moderation arguments class to shrink the arguments down --- .../moderation/ModerationCommands.kt | 168 ++++-------------- 1 file changed, 33 insertions(+), 135 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt index 7e89626a..a533d5e2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt @@ -956,72 +956,20 @@ class ModerationCommands : Extension() { } } - inner class BanArgs : Arguments() { - /** The user to ban. */ - val userArgument by user { - name = "user" - description = "Person to ban" - } - + inner class BanArgs : ModerationArguments() { /** The number of days worth of messages to delete. */ val messages by int { name = "delete-message-days" description = "The number of days worth of messages to delete" } - - /** The reason for the ban. */ - val reason by defaultingString { - name = "reason" - description = "The reason for the ban" - defaultValue = "No reason provided" - } - - /** Whether to DM the user or not. */ - val dm by defaultingBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the ban" - defaultValue = true - } - - /** An image that the user wishes to provide for context to the ban. */ - val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" - } } - inner class SoftBanArgs : Arguments() { - /** The user to soft-ban. */ - val userArgument by user { - name = "user" - description = "Person to Soft ban" - } - + inner class SoftBanArgs : ModerationArguments() { /** The number of days worth of messages to delete, defaults to 3 days. */ val messages by optionalInt { name = "delete-message-days" description = "The number of days worth of messages to delete" } - - /** The reason for the soft-ban. */ - val reason by defaultingString { - name = "reason" - description = "The reason for the ban" - defaultValue = "No reason provided" - } - - /** Whether to DM the user or not. */ - val dm by defaultingBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the soft-ban" - defaultValue = true - } - - /** An image that the user wishes to provide for context to the soft-ban. */ - val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" - } } inner class UnbanArgs : Arguments() { @@ -1039,66 +987,14 @@ class ModerationCommands : Extension() { } } - inner class KickArgs : Arguments() { - /** The user to kick. */ - val userArgument by user { - name = "user" - description = "Person to kick" - } - - /** The reason for the kick. */ - val reason by defaultingString { - name = "reason" - description = "The reason for the Kick" - defaultValue = "No reason provided" - } - - /** Whether to DM the user or not. */ - val dm by defaultingBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the kick" - defaultValue = true - } - - /** An image that the user wishes to provide for context to the kick. */ - val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" - } - } - - inner class TimeoutArgs : Arguments() { - /** The requested user to timeout. */ - val userArgument by user { - name = "user" - description = "Person to timeout" - } + inner class KickArgs : ModerationArguments() + inner class TimeoutArgs : ModerationArguments() { /** The time the timeout should last for. */ val duration by coalescingOptionalDuration { name = "duration" description = "Duration of timeout" } - - /** The reason for the timeout. */ - val reason by defaultingString { - name = "reason" - description = "Reason for timeout" - defaultValue = "No reason provided" - } - - /** Whether to DM the user or not. */ - val dm by defaultingBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the timeout" - defaultValue = true - } - - /** An image that the user wishes to provide for context to the kick. */ - val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" - } } inner class RemoveTimeoutArgs : Arguments() { @@ -1116,33 +1012,7 @@ class ModerationCommands : Extension() { } } - inner class WarnArgs : Arguments() { - /** The requested user to warn. */ - val userArgument by user { - name = "user" - description = "Person to warn" - } - - /** The reason for the warning. */ - val reason by defaultingString { - name = "reason" - description = "Reason for warning" - defaultValue = "No reason provided" - } - - /** Whether to DM the user or not. */ - val dm by defaultingBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the warning" - defaultValue = true - } - - /** An image that the user wishes to provide for context to the kick. */ - val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" - } - } + inner class WarnArgs : ModerationArguments() inner class RemoveWarnArgs : Arguments() { /** The requested user to remove the warning from. */ @@ -1160,6 +1030,34 @@ class ModerationCommands : Extension() { } } +open class ModerationArguments : Arguments() { + /** The target user to apply the action too. */ + val userArgument by user { + name = "user" + description = "The user to apply this action too" + } + + /** The reason for the action. */ + val reason by defaultingString { + name = "reason" + description = "Reason for action" + defaultValue = "No reason provided" + } + + /** Whether to DM the user or not. */ + val dm by defaultingBoolean { + name = "dm" + description = "Whether to send a direct message to the user about the action" + defaultValue = true + } + + /** An image that the user wishes to provide for context to the action. */ + val image by optionalAttachment { + name = "image" + description = "An image you'd like to provide as extra context for the action" + } +} + /** * Creates a log for timeouts produced by a number of warnings. * From aab071a5bee897a09473ddb9fb9043228076a652 Mon Sep 17 00:00:00 2001 From: NoComment Date: Tue, 2 Apr 2024 16:15:39 +0100 Subject: [PATCH 23/42] Revert "Create a moderation arguments class to shrink the arguments down" This reverts commit b82f7ef609b70763acab6debe893b7e05115fd4d. --- .../moderation/ModerationCommands.kt | 168 ++++++++++++++---- 1 file changed, 135 insertions(+), 33 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt index a533d5e2..7e89626a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt @@ -956,20 +956,72 @@ class ModerationCommands : Extension() { } } - inner class BanArgs : ModerationArguments() { + inner class BanArgs : Arguments() { + /** The user to ban. */ + val userArgument by user { + name = "user" + description = "Person to ban" + } + /** The number of days worth of messages to delete. */ val messages by int { name = "delete-message-days" description = "The number of days worth of messages to delete" } + + /** The reason for the ban. */ + val reason by defaultingString { + name = "reason" + description = "The reason for the ban" + defaultValue = "No reason provided" + } + + /** Whether to DM the user or not. */ + val dm by defaultingBoolean { + name = "dm" + description = "Whether to send a direct message to the user about the ban" + defaultValue = true + } + + /** An image that the user wishes to provide for context to the ban. */ + val image by optionalAttachment { + name = "image" + description = "An image you'd like to provide as extra context for the action" + } } - inner class SoftBanArgs : ModerationArguments() { + inner class SoftBanArgs : Arguments() { + /** The user to soft-ban. */ + val userArgument by user { + name = "user" + description = "Person to Soft ban" + } + /** The number of days worth of messages to delete, defaults to 3 days. */ val messages by optionalInt { name = "delete-message-days" description = "The number of days worth of messages to delete" } + + /** The reason for the soft-ban. */ + val reason by defaultingString { + name = "reason" + description = "The reason for the ban" + defaultValue = "No reason provided" + } + + /** Whether to DM the user or not. */ + val dm by defaultingBoolean { + name = "dm" + description = "Whether to send a direct message to the user about the soft-ban" + defaultValue = true + } + + /** An image that the user wishes to provide for context to the soft-ban. */ + val image by optionalAttachment { + name = "image" + description = "An image you'd like to provide as extra context for the action" + } } inner class UnbanArgs : Arguments() { @@ -987,14 +1039,66 @@ class ModerationCommands : Extension() { } } - inner class KickArgs : ModerationArguments() + inner class KickArgs : Arguments() { + /** The user to kick. */ + val userArgument by user { + name = "user" + description = "Person to kick" + } + + /** The reason for the kick. */ + val reason by defaultingString { + name = "reason" + description = "The reason for the Kick" + defaultValue = "No reason provided" + } + + /** Whether to DM the user or not. */ + val dm by defaultingBoolean { + name = "dm" + description = "Whether to send a direct message to the user about the kick" + defaultValue = true + } + + /** An image that the user wishes to provide for context to the kick. */ + val image by optionalAttachment { + name = "image" + description = "An image you'd like to provide as extra context for the action" + } + } + + inner class TimeoutArgs : Arguments() { + /** The requested user to timeout. */ + val userArgument by user { + name = "user" + description = "Person to timeout" + } - inner class TimeoutArgs : ModerationArguments() { /** The time the timeout should last for. */ val duration by coalescingOptionalDuration { name = "duration" description = "Duration of timeout" } + + /** The reason for the timeout. */ + val reason by defaultingString { + name = "reason" + description = "Reason for timeout" + defaultValue = "No reason provided" + } + + /** Whether to DM the user or not. */ + val dm by defaultingBoolean { + name = "dm" + description = "Whether to send a direct message to the user about the timeout" + defaultValue = true + } + + /** An image that the user wishes to provide for context to the kick. */ + val image by optionalAttachment { + name = "image" + description = "An image you'd like to provide as extra context for the action" + } } inner class RemoveTimeoutArgs : Arguments() { @@ -1012,13 +1116,18 @@ class ModerationCommands : Extension() { } } - inner class WarnArgs : ModerationArguments() - - inner class RemoveWarnArgs : Arguments() { - /** The requested user to remove the warning from. */ + inner class WarnArgs : Arguments() { + /** The requested user to warn. */ val userArgument by user { name = "user" - description = "Person to remove warn from" + description = "Person to warn" + } + + /** The reason for the warning. */ + val reason by defaultingString { + name = "reason" + description = "Reason for warning" + defaultValue = "No reason provided" } /** Whether to DM the user or not. */ @@ -1027,34 +1136,27 @@ class ModerationCommands : Extension() { description = "Whether to send a direct message to the user about the warning" defaultValue = true } - } -} - -open class ModerationArguments : Arguments() { - /** The target user to apply the action too. */ - val userArgument by user { - name = "user" - description = "The user to apply this action too" - } - /** The reason for the action. */ - val reason by defaultingString { - name = "reason" - description = "Reason for action" - defaultValue = "No reason provided" + /** An image that the user wishes to provide for context to the kick. */ + val image by optionalAttachment { + name = "image" + description = "An image you'd like to provide as extra context for the action" + } } - /** Whether to DM the user or not. */ - val dm by defaultingBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the action" - defaultValue = true - } + inner class RemoveWarnArgs : Arguments() { + /** The requested user to remove the warning from. */ + val userArgument by user { + name = "user" + description = "Person to remove warn from" + } - /** An image that the user wishes to provide for context to the action. */ - val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" + /** Whether to DM the user or not. */ + val dm by defaultingBoolean { + name = "dm" + description = "Whether to send a direct message to the user about the warning" + defaultValue = true + } } } From 8799f528c432bb007bf562270fd4fadee4c206b6 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:00:36 +0100 Subject: [PATCH 24/42] Allow multiple roles to be pinged on auto-threads (#386) * Allow multiple roles to be pinged in auto-threaded channels * Add migration for new issue * Fix doc file --- docs/commands.md | 114 +++++++++------- .../collections/AutoThreadingCollection.kt | 16 +++ .../database/entities/AutoThreadingData.kt | 3 +- .../lilybot/database/migrations/Migrator.kt | 2 + .../database/migrations/main/mainV10.kt | 12 ++ .../extensions/events/AutoThreading.kt | 126 +++++++++++++++++- 6 files changed, 223 insertions(+), 50 deletions(-) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt diff --git a/docs/commands.md b/docs/commands.md index a482491e..69fbe5b4 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -37,6 +37,71 @@ None * **Arguments**: * `channel` - The channel to view the auto-threading settings for. - Channel +--- +### Command name: `auto-threading add-roles` +**Description**: Add extra to threads in auto-threaded channels + +**Additional Information**: This command will add roles to be pinged alongside the default ping role for this auto-threaded channel + +**Required Member Permissions**: Manage Channels + +* **Arguments**: + * `role` - A role to invite to threads in this channel - Optional Role + +--- +### Command name: `auto-threading remove-roles` +**Description**: Remove extra from threads in auto-threaded channels + +**Additional Information**: This command will remove roles that have been added to be pinged alongside the default ping role for this auto-threaded channel + +**Required Member Permissions**: Manage Channels + +* **Arguments**: + * `role` - A role to invite to threads in this channel - Optional Role + +--- +### Command name: `clear count` +**Description**: Clear a specific count of messages + +**Required Member Permissions**: Manage Messages + +* **Arguments**: + * `messages` - Number of messages to delete - Int + * `author` - The author of the messages to clear - Optional User + +--- +### Command name: `clear before` +**Description**: Clear messages before a given message ID + +**Required Member Permissions**: Manage Messages + +* **Arguments**: + * `before` - The ID of the message to clear before - Snowflake + * `message-count` - The number of messages to clear - Optional Int/Long + * `author` - The author of the messages to clear - Optional User + +--- +### Command name: `clear after` +**Description**: Clear messages before a given message ID + +**Required Member Permissions**: Manage Messages + +* **Arguments**: + * `after` - The ID of the message to clear after - Snowflake + * `message-count` - The number of messages to clear - Optional Int/Long + * `author` - The author of the messages to clear - Optional User + +--- +### Command name: `clear between` +**Description**: Clear messages between 2 message IDs + +**Required Member Permissions**: Manage Messages + +* **Arguments**: + * `after` - The ID of the message to clear after - Snowflake + * `before` - The ID of the message to clear before - Snowflake + * `author` - The author of the messages to clear - Optional User + --- ### Command name: `config moderation` **Description**: Configure Lily's moderation system @@ -318,49 +383,6 @@ None * `dm` - Whether to send a direct message to the user about the kick - Defaulting Boolean * `image` - An image you'd like to provide as extra context for the action - Optional Attachment ---- -### Command name: `clear count` -**Description**: Clear a specific count of messages - -**Required Member Permissions**: Manage Messages - -* **Arguments**: - * `messages` - Number of messages to delete - Int - * `author` - The author of the messages to clear - Optional User - ---- -### Command name: `clear before` -**Description**: Clear messages before a given message ID - -**Required Member Permissions**: Manage Messages - -* **Arguments**: - * `before` - The ID of the message to clear before - Snowflake - * `message-count` - The number of messages to clear - Optional Int/Long - * `author` - The author of the messages to clear - Optional User - ---- -### Command name: `clear after` -**Description**: Clear messages before a given message ID - -**Required Member Permissions**: Manage Messages - -* **Arguments**: - * `after` - The ID of the message to clear after - Snowflake - * `message-count` - The number of messages to clear - Optional Int/Long - * `author` - The author of the messages to clear - Optional User - ---- -### Command name: `clear between` -**Description**: Clear messages between 2 message IDs - -**Required Member Permissions**: Manage Messages - -* **Arguments**: - * `after` - The ID of the message to clear after - Snowflake - * `before` - The ID of the message to clear before - Snowflake - * `author` - The author of the messages to clear - Optional User - --- ### Command name: `timeout` **Description**: Times out a user. @@ -717,8 +739,8 @@ None * `clear` - Whether to clear the channel before repopulating it - Defaulting Boolean --- -### Command name: `phishing-check` -**Description**: Check whether a given domain is a known phishing domain. +### Command name: `url-safety-check` +**Description**: Check whether a given domain is a known unsafe domain. * Arguments: * `domain` - Domain to check - String @@ -767,7 +789,7 @@ None ### Message Command: `Report` --- -### Message Command: `Phishing Check` +### Message Command: `URL Safety Check` --- ## User Commands diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/AutoThreadingCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/AutoThreadingCollection.kt index db1c6104..4f1ff27a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/AutoThreadingCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/AutoThreadingCollection.kt @@ -6,6 +6,7 @@ import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.AutoThreadingData import org.koin.core.component.inject import org.litote.kmongo.eq +import org.litote.kmongo.setValue /** * This class contains the functions for interacting with the [AutoThreading Database][AutoThreadingData]. This @@ -58,6 +59,21 @@ class AutoThreadingCollection : KordExKoinComponent { collection.insertOne(inputAutoThreadData) } + /** + * Updates the extra roles on an auto-threaded channel. + * + * @param inputChannelId The channel to update extra roles for + * @param newRoleIds The new list of role IDs to set + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun updateExtraRoles(inputChannelId: Snowflake, newRoleIds: List) { + collection.updateOne( + AutoThreadingData::channelId eq inputChannelId, + setValue(AutoThreadingData::extraRoleIds, newRoleIds) + ) + } + /** * Deletes an auto thread based off of the [inputChannelId]. * diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AutoThreadingData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AutoThreadingData.kt index c736f76a..cd6f2e33 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AutoThreadingData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AutoThreadingData.kt @@ -28,5 +28,6 @@ data class AutoThreadingData( val contentAwareNaming: Boolean, val mention: Boolean, val creationMessage: String?, - val addModsAndRole: Boolean + val addModsAndRole: Boolean, + val extraRoleIds: MutableList ) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt index 8497b07d..4cf1fcec 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt @@ -26,6 +26,7 @@ import org.hyacinthbots.lilybot.database.migrations.config.configV5 import org.hyacinthbots.lilybot.database.migrations.config.configV6 import org.hyacinthbots.lilybot.database.migrations.config.configV7 import org.hyacinthbots.lilybot.database.migrations.main.mainV1 +import org.hyacinthbots.lilybot.database.migrations.main.mainV10 import org.hyacinthbots.lilybot.database.migrations.main.mainV2 import org.hyacinthbots.lilybot.database.migrations.main.mainV3 import org.hyacinthbots.lilybot.database.migrations.main.mainV4 @@ -73,6 +74,7 @@ object Migrator : KordExKoinComponent { 7 -> ::mainV7 8 -> ::mainV8 9 -> ::mainV9 + 10 -> ::mainV10 else -> break }(db.mainDatabase) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt new file mode 100644 index 00000000..b7a156b3 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt @@ -0,0 +1,12 @@ +package org.hyacinthbots.lilybot.database.migrations.main + +import org.hyacinthbots.lilybot.database.entities.AutoThreadingData +import org.litote.kmongo.coroutine.CoroutineDatabase +import org.litote.kmongo.exists +import org.litote.kmongo.setValue + +suspend fun mainV10(db: CoroutineDatabase) { + with(db.getCollection()) { + updateMany(AutoThreadingData::extraRoleIds exists false, setValue(AutoThreadingData::extraRoleIds, emptyList())) + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt index ccd0d2ae..aa2854c8 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt @@ -46,6 +46,7 @@ import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.rest.builder.message.embed import kotlinx.coroutines.delay import kotlinx.datetime.Clock +import org.hyacinthbots.docgenerator.subCommandAdditionalDocumentation import org.hyacinthbots.lilybot.database.collections.AutoThreadingCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection @@ -136,7 +137,8 @@ class AutoThreading : Extension() { contentAwareNaming = arguments.contentAwareNaming, mention = arguments.mention, creationMessage = message, - addModsAndRole = arguments.addModsAndRole + addModsAndRole = arguments.addModsAndRole, + extraRoleIds = mutableListOf() ) ) @@ -312,6 +314,16 @@ class AutoThreading : Extension() { "None" } } + if (autoThread.extraRoleIds.isNotEmpty()) { + var mentions = "" + autoThread.extraRoleIds.forEach { + mentions += "${guild!!.getRoleOrNull(it)?.mention} " + } + field { + name = "Extra Ping Roles" + value = mentions + } + } field { name = "Prevent duplicates" value = autoThread.preventDuplicates.toString() @@ -340,6 +352,103 @@ class AutoThreading : Extension() { } } } + + ephemeralSubCommand(::ExtraRolesArgs) { + name = "add-roles" + description = "Add extra to threads in auto-threaded channels" + + subCommandAdditionalDocumentation { + extraInformation = "This command will add roles to be pinged alongside the default ping role " + + "for this auto-threaded channel" + } + + requirePermission(Permission.ManageChannels) + + check { + anyGuild() + hasPermission(Permission.ManageChannels) + requireBotPermissions(Permission.SendMessages) + botHasChannelPerms(Permissions(Permission.SendMessages)) + } + + action { + val autoThread = AutoThreadingCollection().getSingleAutoThread(event.interaction.channelId) + if (autoThread == null) { + respond { + content = "**Error:** This is not an auto-threaded channel!" + } + return@action + } + + if (!canPingRole(arguments.role, guild!!.id, this@ephemeralSubCommand.kord)) { + respond { + content = "Lily cannot mention this role. Please fix the role's permissions and try again." + } + return@action + } + + if (autoThread.extraRoleIds.contains(arguments.role!!.id)) { + respond { + content = "This role has already been added." + } + return@action + } + + val updatedRoles = autoThread.extraRoleIds + updatedRoles.add(arguments.role!!.id) + + AutoThreadingCollection().updateExtraRoles(event.interaction.channelId, updatedRoles) + + respond { + content = "Role (${arguments.role!!.mention}) added" + } + } + } + + ephemeralSubCommand(::ExtraRolesArgs) { + name = "remove-roles" + description = "Remove extra from threads in auto-threaded channels" + + subCommandAdditionalDocumentation { + extraInformation = "This command will remove roles that have been added to be pinged alongside " + + "the default ping role for this auto-threaded channel" + } + + requirePermission(Permission.ManageChannels) + + check { + anyGuild() + hasPermission(Permission.ManageChannels) + requireBotPermissions(Permission.SendMessages) + botHasChannelPerms(Permissions(Permission.SendMessages)) + } + + action { + val autoThread = AutoThreadingCollection().getSingleAutoThread(event.interaction.channelId) + if (autoThread == null) { + respond { + content = "**Error:** This is not an auto-threaded channel!" + } + return@action + } + + if (!autoThread.extraRoleIds.contains(arguments.role!!.id)) { + respond { + content = "This role has not been added." + } + return@action + } + + val updatedRoles = autoThread.extraRoleIds + updatedRoles.remove(arguments.role!!.id) + + AutoThreadingCollection().updateExtraRoles(event.interaction.channelId, updatedRoles) + + respond { + content = "Role (${arguments.role!!.mention}) removed" + } + } + } } event { @@ -459,6 +568,13 @@ class AutoThreading : Extension() { } } + inner class ExtraRolesArgs : Arguments() { + val role by optionalRole { + name = "role" + description = "A role to invite to threads in this channel" + } + } + inner class AutoThreadingViewArgs : Arguments() { val channel by channel { name = "channel" @@ -580,14 +696,18 @@ class AutoThreading : Extension() { if (moderatorRoleId != null) { moderatorRole = inputThread.guild.getRole(moderatorRoleId) } + var mentions = "" + inputOptions.extraRoleIds.forEach { + mentions += inputThread.guild.getRole(it).mention + } if (moderatorRole != null && moderatorRole.mentionable && inputOptions.addModsAndRole) { threadMessage.edit { - content = role.mention + moderatorRole.mention + content = role.mention + mentions + moderatorRole.mention } } else { threadMessage.edit { - content = role.mention + content = role.mention + mentions } } } From 1c8b510c5777090922950b72d22397ed5bfd3986 Mon Sep 17 00:00:00 2001 From: NoComment Date: Wed, 10 Apr 2024 13:15:09 +0100 Subject: [PATCH 25/42] Update dependencies --- gradle/libs.versions.toml | 6 +++--- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39eb3086..3a7bce39 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,16 +2,16 @@ # Plugins kotlin = "1.9.23" shadow = "8.1.1" -detekt = "1.23.5" +detekt = "1.23.6" git-hooks = "0.0.2" grgit = "5.2.2" blossom = "2.1.0" # Libraries kord-extensions = "1.8.0-20240319.115836-21" -logging = "6.0.3" +logging = "6.0.4" logback = "1.5.3" -github-api = "1.319" +github-api = "1.321" kmongo = "4.11.0" docgenerator = "0.1.5-SNAPSHOT" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch delta 34118 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 delta 34176 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 SGW6ptSs!X7{rG*o_g?|vpSEZK diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce..b82aa23a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From a61c526e358117a43479ea9819b898225d08b464 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:22:25 +0100 Subject: [PATCH 26/42] Implement temporary bans (#391) * Revert "Create a moderation arguments class to shrink the arguments down" This reverts commit b82f7ef609b70763acab6debe893b7e05115fd4d. * Implement temporary bans * Apply review comments - Remove temp-ban remove, incorporate into unban - Remove soft-ban, incorporate into main ban command * Fix typo in command option description --- docs/commands.md | 22 +- .../collections/ReminderCollection.kt | 2 +- .../collections/TemporaryBanCollection.kt | 79 ++++ .../database/entities/TemporaryBanData.kt | 25 ++ .../database/migrations/main/mainV10.kt | 1 + .../moderation/ModerationCommands.kt | 346 ++++++++++++++---- 6 files changed, 387 insertions(+), 88 deletions(-) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TemporaryBanData.kt diff --git a/docs/commands.md b/docs/commands.md index 69fbe5b4..24924bab 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -345,22 +345,32 @@ None * `user` - Person to ban - User * `delete-message-days` - The number of days worth of messages to delete - Int * `reason` - The reason for the ban - Defaulting String + * `soft-ban` - Weather to soft-ban this user (unban them once messages are deleted) - Defaulting Boolean * `dm` - Whether to send a direct message to the user about the ban - Defaulting Boolean * `image` - An image you'd like to provide as extra context for the action - Optional Attachment --- -### Command name: `soft-ban` -**Description**: Soft-bans a user. +### Command name: `temp-ban add` +**Description**: Temporarily bans a user **Required Member Permissions**: Ban Members -* Arguments: - * `user` - Person to Soft ban - User - * `delete-message-days` - The number of days worth of messages to delete - Optional Int/Long +* **Arguments**: + * `user` - Person to ban - User + * `delete-message-days` - The number of days worth of messages to delete - Int + * `duration` - The duration of the temporary ban. - Coalescing Duration * `reason` - The reason for the ban - Defaulting String - * `dm` - Whether to send a direct message to the user about the soft-ban - Defaulting Boolean + * `dm` - Whether to send a direct message to the user about the ban - Defaulting Boolean * `image` - An image you'd like to provide as extra context for the action - Optional Attachment +--- +### Command name: `temp-ban view-all` +**Description**: View all temporary bans for this guild + +**Required Member Permissions**: Ban Members + +* **Arguments**: +None --- ### Command name: `unban` **Description**: Unbans a user. diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt index 90e719d2..006a1263 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt @@ -11,7 +11,7 @@ import org.koin.core.component.inject import org.litote.kmongo.eq /** - * This class contains the functions for interacting with []the reminder database][ReminderData]. This + * This class contains the functions for interacting with [the reminder database][ReminderData]. This * class contains functions for setting reminders, getting reminders based of various parameters, removing reminders and * repeating them. * diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt new file mode 100644 index 00000000..7413f561 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt @@ -0,0 +1,79 @@ +package org.hyacinthbots.lilybot.database.collections + +import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent +import dev.kord.common.entity.Snowflake +import org.hyacinthbots.lilybot.database.Database +import org.hyacinthbots.lilybot.database.entities.TemporaryBanData +import org.koin.core.component.inject +import org.litote.kmongo.eq + +/** + * This class contains the functions for interacting with [the temporary ban database][TemporaryBanData]. This class + * contains functions for settings temporary bans, getting temporary bans based off of various parameters and removing + * them. + * + * @since 5.0.0 + * @see getAllTempBans + * @see getTempBansForGuild + * @see getUserTempBan + * @see setTempBan + * @see removeTempBan + */ +class TemporaryBanCollection : KordExKoinComponent { + private val db: Database by inject() + + @PublishedApi + internal val collection = db.mainDatabase.getCollection() + + /** + * Gets all the temporary bans currently in the database. + * + * @return A list of temporary bans in the database + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun getAllTempBans(): List = collection.find().toList() + + /** + * Gets all the temporary bans for a given guild. + * + * @param guildId The ID of the guild to get the bans in + * @return A list of Temporary bans for the given [guildId] + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun getTempBansForGuild(guildId: Snowflake): List = + collection.find(TemporaryBanData::guildId eq guildId).toList() + + /** + * Gets a temporary ban for a given user. + * + * @param guildId The ID of the guild the temporary ban occurred in + * @param bannedUserId The ID of the user that was temporarily banned + * @return The [TemporaryBanData] for the [bannedUserId] + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun getUserTempBan(guildId: Snowflake, bannedUserId: Snowflake): TemporaryBanData? = + collection.findOne(TemporaryBanData::guildId eq guildId, TemporaryBanData::bannedUserId eq bannedUserId) + + /** + * Sets a temporary ban. + * + * @param tempBanData The data for the temporary ban + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun setTempBan(tempBanData: TemporaryBanData) = collection.insertOne(tempBanData) + + /** + * Removes the temporary ban for a user in a given guild. This is called once a temporary ban is completed. + * + * @param guildId The guild the temporary ban is being removed from + * @param bannedUserId The ID of the user to remove the temporary ban from + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun removeTempBan(guildId: Snowflake, bannedUserId: Snowflake) = + collection.deleteOne(TemporaryBanData::guildId eq guildId, TemporaryBanData::bannedUserId eq bannedUserId) +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TemporaryBanData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TemporaryBanData.kt new file mode 100644 index 00000000..e955ca9f --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TemporaryBanData.kt @@ -0,0 +1,25 @@ +package org.hyacinthbots.lilybot.database.entities + +import dev.kord.common.entity.Snowflake +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +/** + * The data for temporary bans in a guild. + * + * @property guildId The ID of the guild the ban occurred in + * @property bannedUserId The ID of the user that was banned + * @property moderatorUserId The ID of the moderator that applied the ban + * @property startTime The time the ban was applied + * @property endTime The time the ban will complete + * + * @since 5.0.0 + */ +@Serializable +data class TemporaryBanData( + val guildId: Snowflake, + val bannedUserId: Snowflake, + val moderatorUserId: Snowflake, + val startTime: Instant, + val endTime: Instant +) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt index b7a156b3..cf2f47c6 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt @@ -9,4 +9,5 @@ suspend fun mainV10(db: CoroutineDatabase) { with(db.getCollection()) { updateMany(AutoThreadingData::extraRoleIds exists false, setValue(AutoThreadingData::extraRoleIds, emptyList())) } + db.createCollection("temporaryBanData") } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt index 7e89626a..2a4235c1 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt @@ -6,12 +6,13 @@ import com.kotlindiscord.kord.extensions.DISCORD_RED import com.kotlindiscord.kord.extensions.annotations.DoNotChain import com.kotlindiscord.kord.extensions.checks.hasPermissions import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand +import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingDuration import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingOptionalDuration import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingString import com.kotlindiscord.kord.extensions.commands.converters.impl.int import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalAttachment -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalInt import com.kotlindiscord.kord.extensions.commands.converters.impl.user import com.kotlindiscord.kord.extensions.components.components import com.kotlindiscord.kord.extensions.components.ephemeralStringSelectMenu @@ -19,11 +20,16 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralMessageCommand import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PluralKit +import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.pages.Page +import com.kotlindiscord.kord.extensions.pagination.pages.Pages import com.kotlindiscord.kord.extensions.time.TimestampType import com.kotlindiscord.kord.extensions.time.toDiscord import com.kotlindiscord.kord.extensions.utils.dm import com.kotlindiscord.kord.extensions.utils.isNullOrBot import com.kotlindiscord.kord.extensions.utils.kordExUserAgent +import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler +import com.kotlindiscord.kord.extensions.utils.scheduling.Task import com.kotlindiscord.kord.extensions.utils.timeout import com.kotlindiscord.kord.extensions.utils.timeoutUntil import com.kotlindiscord.kord.extensions.utils.toDuration @@ -34,10 +40,13 @@ import dev.kord.core.behavior.ban import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.edit +import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.behavior.interaction.followup.edit import dev.kord.core.behavior.reply +import dev.kord.core.entity.Guild import dev.kord.core.entity.Message import dev.kord.core.entity.User +import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.interaction.followup.EphemeralFollowupMessage import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.embed @@ -47,7 +56,9 @@ import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone import kotlinx.datetime.plus import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection +import org.hyacinthbots.lilybot.database.collections.TemporaryBanCollection import org.hyacinthbots.lilybot.database.collections.WarnCollection +import org.hyacinthbots.lilybot.database.entities.TemporaryBanData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.HYACINTH_GITHUB import org.hyacinthbots.lilybot.utils.baseModerationEmbed @@ -57,6 +68,7 @@ import org.hyacinthbots.lilybot.utils.interval import org.hyacinthbots.lilybot.utils.isBotOrModerator import org.hyacinthbots.lilybot.utils.modCommandChecks import kotlin.time.Duration +import kotlin.time.toDuration class ModerationCommands : Extension() { override val name = "moderation" @@ -65,8 +77,15 @@ class ModerationCommands : Extension() { "For more information about the warn system, please see [this document]" + "($HYACINTH_GITHUB/LilyBot/blob/main/docs/commands.md#name-warn)" + /** The scheduler that will track the time for un-banning in temp bans. */ + private val tempBanScheduler = Scheduler() + + /** The task that will run the [tempBanScheduler]. */ + private lateinit var tempBanTask: Task + @OptIn(DoNotChain::class) override suspend fun setup() { + tempBanTask = tempBanScheduler.schedule(120, repeat = true, callback = ::removeTempBans) ephemeralMessageCommand { name = "Moderate" locking = true @@ -486,21 +505,31 @@ class ModerationCommands : Extension() { } else { arguments.reason } + }\n${if (arguments.softBan) { + "You were soft-banned. You are free to rejoin without the need to be unbanned" + } else { + "" + } }" } } } getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { embed { - title = "Banned a User" - description = "${arguments.userArgument.mention} has been banned!" + if (arguments.softBan) { + title = "Soft-Banned a User" + description = "${arguments.userArgument.mention} has been soft-banned!" + } else { + title = "Banned a User" + description = "${arguments.userArgument.mention} has been banned!" + } baseModerationEmbed(arguments.reason, arguments.userArgument, user) image = arguments.image?.url dmNotificationStatusEmbedField(dmStatus, arguments.dm) timestamp = Clock.System.now() field { name = "Days of messages deleted:" - value = arguments.messages.toString() + value = if (arguments.softBan && arguments.messages == 0) "3" else arguments.messages.toString() inline = false } } @@ -508,89 +537,157 @@ class ModerationCommands : Extension() { if (modConfig?.publicLogging == true) { event.interaction.channel.createEmbed { - title = "Banned a user" - description = "${arguments.userArgument.mention} has been banned!" + if (arguments.softBan) { + title = "Soft-Banned a user" + description = "${arguments.userArgument.mention} has been soft-banned!" + } else { + title = "Banned a user" + description = "${arguments.userArgument.mention} has been banned!" + } color = DISCORD_BLACK } } guild?.ban(arguments.userArgument.id) { - reason = arguments.reason - deleteMessageDuration = DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) + reason = arguments.reason + if (arguments.softBan) " **SOFT-BAN**" else "" + deleteMessageDuration = if (arguments.softBan && arguments.messages == 0) { + DateTimePeriod(days = 3).toDuration(TimeZone.UTC) + } else { + DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) + } } + if (arguments.softBan) guild?.unban(arguments.userArgument.id, "User was soft-banned") + respond { - content = "Banned ${arguments.userArgument.mention}" + content = if (arguments.softBan) { "Soft-banned " } else { "Banned " } + arguments.userArgument.mention } } } - ephemeralSlashCommand(::SoftBanArgs) { - name = "soft-ban" - description = "Soft-bans a user." - - requirePermission(Permission.BanMembers) - - check { - modCommandChecks(Permission.BanMembers) - requireBotPermissions(Permission.BanMembers) - } + ephemeralSlashCommand { + name = "temp-ban" + description = "The parent command for temporary ban commands" - action { - isBotOrModerator(event.kord, arguments.userArgument, guild, "soft-ban") ?: return@action + ephemeralSubCommand(::TempBanArgs) { + name = "add" + description = "Temporarily bans a user" - // The discord limit for deleting days of messages in a ban is 7, so we should catch invalid inputs. - if (arguments.messages != null && (arguments.messages!! > 7 || arguments.messages!! < 0)) { - respond { - content = "Invalid `delete-message-days` parameter! This number must be between 0 and 7 days!" - } - return@action + requirePermission(Permission.BanMembers) + check { + modCommandChecks(Permission.BanMembers) + requireBotPermissions(Permission.BanMembers) } - val modConfig = ModerationConfigCollection().getConfig(guild!!.id) - var dmStatus: Message? = null - if (arguments.dm) { - dmStatus = arguments.userArgument.dm { + action { + isBotOrModerator(event.kord, arguments.userArgument, guild, "temp-ban add") + val now = Clock.System.now() + val duration = now.plus(arguments.duration, TimeZone.UTC) + val modConfig = ModerationConfigCollection().getConfig(guild!!.id) + var dmStatus: Message? = null + if (arguments.dm) { + dmStatus = arguments.userArgument.dm { + embed { + title = "You have been temporarily banned from ${guild?.fetchGuild()?.name}" + description = "**Reason:**\n${arguments.reason}\n\n" + + "You are banned until $duration" + } + } + } + getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { embed { - title = "You have been soft-banned from ${guild?.fetchGuild()?.name}" - description = "**Reason:**\n${arguments.reason}\n\n" + - "You are free to rejoin without the need to be unbanned" + title = "Temporarily banned a user" + description = + "${arguments.userArgument.mention} has been temporarily banned!" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, arguments.userArgument, user) + dmNotificationStatusEmbedField(dmStatus, arguments.dm) + field { + name = "Duration:" + value = + duration.toDiscord(TimestampType.Default) + " (${arguments.duration.interval()})" + } + timestamp = now } } - } - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - title = "Soft-Banned a User" - description = "${arguments.userArgument.mention} has been soft-banned!" - baseModerationEmbed(arguments.reason, arguments.userArgument, user) - image = arguments.image?.url - dmNotificationStatusEmbedField(dmStatus, arguments.dm) - timestamp = Clock.System.now() - field { - name = "Days of messages deleted:" - value = "${arguments.messages ?: "3"}" - inline = false + + if (modConfig?.publicLogging == true) { + event.interaction.channel.createEmbed { + title = "Temp Banned a user" + description = "${arguments.userArgument.mention} has been Temp Banned!" + color = DISCORD_BLACK } } - } - if (modConfig?.publicLogging == true) { - event.interaction.channel.createEmbed { - title = "Soft-Banned a user" - description = "${arguments.userArgument.mention} has been soft-banned!" - color = DISCORD_BLACK + TemporaryBanCollection().setTempBan( + TemporaryBanData(guild!!.id, arguments.userArgument.id, user.id, now, duration) + ) + + guild?.ban(arguments.userArgument.id) { + reason = arguments.reason + deleteMessageDuration = DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) + } + + respond { + content = "Temporarily banned ${arguments.userArgument.mention}" } } + } - guild?.ban(arguments.userArgument.id) { - reason = arguments.reason + " **SOFT-BAN**" - deleteMessageDuration = DateTimePeriod(days = arguments.messages ?: 3).toDuration(TimeZone.UTC) + ephemeralSubCommand { + name = "view-all" + description = "View all temporary bans for this guild" + + requirePermission(Permission.BanMembers) + + check { + modCommandChecks(Permission.BanMembers) + requireBotPermissions(Permission.BanMembers) } - guild?.unban(arguments.userArgument.id, "Soft-ban unban") + action { + val pagesObj = Pages() + val tempBans = TemporaryBanCollection().getTempBansForGuild(guild!!.id) + if (tempBans.isEmpty()) { + pagesObj.addPage( + Page { + description = "There are no temporary bans in this guild." + } + ) + } else { + tempBans.chunked(4).forEach { tempBan -> + var content = "" + tempBan.forEach { + content = """ + User: ${this@ephemeralSubCommand.kord.getUser(it.bannedUserId)?.username} + Moderator: ${guild?.getMemberOrNull(it.moderatorUserId)?.username} + Start Time: ${it.startTime.toDiscord(TimestampType.ShortDateTime)} (${ + it.startTime.toDiscord(TimestampType.RelativeTime) + }) + End Time: ${it.endTime.toDiscord(TimestampType.ShortDateTime)} (${ + it.endTime.toDiscord(TimestampType.RelativeTime) + }) + --- + """.trimIndent() + } + + pagesObj.addPage( + Page { + title = "Temporary bans for ${guild?.asGuildOrNull()?.name ?: "this guild"}" + description = content + } + ) + } + } - respond { - content = "Soft-banned ${arguments.userArgument.mention}" + val paginator = EphemeralResponsePaginator( + pages = pagesObj, + owner = event.interaction.user, + timeoutSeconds = 300, + interaction = interactionResponse + ) + + paginator.send() } } } @@ -607,22 +704,53 @@ class ModerationCommands : Extension() { } action { - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - title = "Unbanned a user" - description = "${arguments.userArgument.mention} has been unbanned!\n${ - arguments.userArgument.id - } (${arguments.userArgument.username})" - field { - name = "Reason:" - value = arguments.reason + val tempBan = TemporaryBanCollection().getUserTempBan(this.getGuild()!!.id, arguments.userArgument.id) + if (tempBan == null) { + guild?.unban(arguments.userArgument.id) + getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { + embed { + title = "Unbanned a user" + description = "${arguments.userArgument.mention} has been unbanned!\n${ + arguments.userArgument.id + } (${arguments.userArgument.username})" + field { + name = "Reason:" + value = arguments.reason + } + footer { + text = user.asUserOrNull()?.username ?: "Unable to get user username" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + timestamp = Clock.System.now() + color = DISCORD_GREEN } - footer { - text = user.asUserOrNull()?.username ?: "Unable to get user username" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + } else { + guild?.unban(arguments.userArgument.id) + TemporaryBanCollection().removeTempBan(guild!!.id, arguments.userArgument.id) + getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { + embed { + title = "Temporary Ban Removed" + description = "${arguments.userArgument.mention} has had their temporary ban removed!\n${ + arguments.userArgument.id + } (${arguments.userArgument.username})" + field { + name = "Reason:" + value = arguments.reason + } + field { + name = "Original Action taker:" + value = guild?.getMemberOrNull(tempBan.moderatorUserId)?.username + ?: this@ephemeralSlashCommand.kord.getUser(tempBan.moderatorUserId)?.username + ?: "Unable to get username" + } + footer { + text = user.asUserOrNull()?.username ?: "Unable to get user username" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + timestamp = Clock.System.now() + color = DISCORD_GREEN } - timestamp = Clock.System.now() - color = DISCORD_GREEN } } respond { @@ -956,6 +1084,49 @@ class ModerationCommands : Extension() { } } + private suspend fun removeTempBans() { + val tempBans = TemporaryBanCollection().getAllTempBans() + val dueTempBans = + tempBans.filter { it.endTime.toEpochMilliseconds() - Clock.System.now().toEpochMilliseconds() <= 0 } + + for (it in dueTempBans) { + var guild: Guild? + try { + guild = kord.getGuildOrNull(it.guildId) + } catch (_: KtorRequestException) { + TemporaryBanCollection().removeTempBan(it.guildId, it.bannedUserId) + continue + } + + if (guild == null) { + TemporaryBanCollection().removeTempBan(it.guildId, it.bannedUserId) + continue + } + + guild.unban(it.bannedUserId, "Temporary Ban expired") + val modChannelId = ModerationConfigCollection().getConfig(guild.id)?.channel + if (modChannelId != null) { + val modChannel = guild.getChannelOfOrNull(modChannelId) + modChannel?.createMessage { + embed { + title = "Temporary ban Completed" + description = "${kord.getUser(it.bannedUserId)?.username} has served their temporary ban" + field { + name = "Initial Ban date:" + value = it.startTime.toDiscord(TimestampType.ShortDateTime) + } + color = DISCORD_GREEN + footer { + text = "Initial action taker: ${kord.getUser(it.moderatorUserId)?.username}" + icon = kord.getUser(it.moderatorUserId)?.avatar?.cdnUrl?.toUrl() + } + } + } + } + TemporaryBanCollection().removeTempBan(it.guildId, it.bannedUserId) + } + } + inner class BanArgs : Arguments() { /** The user to ban. */ val userArgument by user { @@ -976,6 +1147,13 @@ class ModerationCommands : Extension() { defaultValue = "No reason provided" } + /** Weather to softban this user or not. */ + val softBan by defaultingBoolean { + name = "soft-ban" + description = "Weather to soft-ban this user (unban them once messages are deleted)" + defaultValue = false + } + /** Whether to DM the user or not. */ val dm by defaultingBoolean { name = "dm" @@ -990,20 +1168,26 @@ class ModerationCommands : Extension() { } } - inner class SoftBanArgs : Arguments() { - /** The user to soft-ban. */ + inner class TempBanArgs : Arguments() { + /** The user to ban. */ val userArgument by user { name = "user" - description = "Person to Soft ban" + description = "Person to ban" } - /** The number of days worth of messages to delete, defaults to 3 days. */ - val messages by optionalInt { + /** The number of days worth of messages to delete. */ + val messages by int { name = "delete-message-days" description = "The number of days worth of messages to delete" } - /** The reason for the soft-ban. */ + /** The duration of the temporary ban. */ + val duration by coalescingDuration { + name = "duration" + description = "The duration of the temporary ban." + } + + /** The reason for the ban. */ val reason by defaultingString { name = "reason" description = "The reason for the ban" @@ -1013,11 +1197,11 @@ class ModerationCommands : Extension() { /** Whether to DM the user or not. */ val dm by defaultingBoolean { name = "dm" - description = "Whether to send a direct message to the user about the soft-ban" + description = "Whether to send a direct message to the user about the ban" defaultValue = true } - /** An image that the user wishes to provide for context to the soft-ban. */ + /** An image that the user wishes to provide for context to the ban. */ val image by optionalAttachment { name = "image" description = "An image you'd like to provide as extra context for the action" From ce9eac207fd291405fca1c1711f78890728931e1 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Fri, 12 Apr 2024 22:11:47 +0100 Subject: [PATCH 27/42] Fix permissions being lost by locked channels when unlocked (#395) * Begin work on storing original permissions when locking channels * Complete work on storing original permission for locked channels --- .../hyacinthbots/lilybot/database/Cleanups.kt | 5 +- .../collections/LockedChannelCollection.kt | 71 ++++++++++++++++ .../database/entities/LockedChannelData.kt | 21 +++++ .../database/migrations/main/mainV10.kt | 1 + .../extensions/moderation/LockingCommands.kt | 82 +++++++++---------- 5 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/database/entities/LockedChannelData.kt diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt index 7d5671ac..a004e1bc 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt @@ -10,6 +10,7 @@ import mu.KotlinLogging import org.hyacinthbots.lilybot.database.Cleanups.cleanupGuildData import org.hyacinthbots.lilybot.database.Cleanups.cleanupThreadData import org.hyacinthbots.lilybot.database.collections.GithubCollection +import org.hyacinthbots.lilybot.database.collections.LockedChannelCollection import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.NewsChannelPublishingCollection @@ -56,14 +57,16 @@ object Cleanups : KordExKoinComponent { cleanupsLogger.info("Starting guild cleanup...") val leaveTimeData = guildLeaveTimeCollection.find().toList() var deletedGuildData = 0 + val now = Clock.System.now() leaveTimeData.forEach { // Calculate the time since Lily left the guild. - val leaveDuration = Clock.System.now() - it.guildLeaveTime + val leaveDuration = now - it.guildLeaveTime if (leaveDuration.inWholeDays > 30) { // If the bot has been out of the guild for more than 30 days, delete any related data. GithubCollection().removeDefaultRepo(it.guildId) + LockedChannelCollection().removeAllLockedChannels(it.guildId) LoggingConfigCollection().clearConfig(it.guildId) ModerationConfigCollection().clearConfig(it.guildId) NewsChannelPublishingCollection().clearAutoPublishingForGuild(it.guildId) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt new file mode 100644 index 00000000..2bffc04f --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt @@ -0,0 +1,71 @@ +package org.hyacinthbots.lilybot.database.collections + +import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent +import dev.kord.common.entity.Snowflake +import org.hyacinthbots.lilybot.database.Database +import org.hyacinthbots.lilybot.database.entities.LockedChannelData +import org.koin.core.component.inject +import org.litote.kmongo.eq + +/** + * This class contains the function for interacting with the [Locked Channel Database][LockedChannelData]. This class + * contains functions for getting, setting and removing locked channels + * + * @since 5.0.0 + * @see addLockedChannel + * @see removeLockedChannel + * @see removeAllLockedChannels + * @see getLockedChannel + */ +class LockedChannelCollection : KordExKoinComponent { + private val db: Database by inject() + + @PublishedApi + internal val collection = db.mainDatabase.getCollection() + + /** + * Adds the data about a newly locked channel to the database. + * + * @param data The [LockedChannelData] for the locked channel + * + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun addLockedChannel(data: LockedChannelData) = collection.insertOne(data) + + /** + * Removes a locked channel from the database. This is usually called when a channel is unlocked. + * + * @param inputGuildId The ID of the guild the locked channel is in + * @param inputChannelId The ID of the locked channel itself + * + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun removeLockedChannel(inputGuildId: Snowflake, inputChannelId: Snowflake) = + collection.deleteOne(LockedChannelData::guildId eq inputGuildId, LockedChannelData::channelId eq inputChannelId) + + /** + * Removes all locked channels for a given guild from the database. Used in guild cleanups. + * + * @param inputGuildId The ID of the guild to remove the locked channels from + * + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun removeAllLockedChannels(inputGuildId: Snowflake) = + collection.deleteMany(LockedChannelData::guildId eq inputGuildId) + + /** + * Gets a locked channel based on the input parameters. + * + * @param inputGuildId The ID of the guild the locked channel is in + * @param inputChannelId The ID of the channel to get the locked data for + * @return A [LockedChannelData] object for the given channel + * + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun getLockedChannel(inputGuildId: Snowflake, inputChannelId: Snowflake): LockedChannelData? = + collection.findOne(LockedChannelData::guildId eq inputGuildId, LockedChannelData::channelId eq inputChannelId) +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/LockedChannelData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/LockedChannelData.kt new file mode 100644 index 00000000..a739485a --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/LockedChannelData.kt @@ -0,0 +1,21 @@ +package org.hyacinthbots.lilybot.database.entities + +import dev.kord.common.entity.Snowflake +import kotlinx.serialization.Serializable + +/** + * The data for locked channels. + * + * @property guildId The ID of the guild the locked channel is in + * @property channelId The ID of the channel that is locked + * @property allowed The Discord Bit Set code for the allowed permissions, formatted as a string + * @property denied The Discord Bit Set code for the denied permissions, formatted as a string + * @since 5.0.0 + */ +@Serializable +data class LockedChannelData( + val guildId: Snowflake, + val channelId: Snowflake, + val allowed: String, + val denied: String, +) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt index cf2f47c6..05722cdd 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt @@ -9,5 +9,6 @@ suspend fun mainV10(db: CoroutineDatabase) { with(db.getCollection()) { updateMany(AutoThreadingData::extraRoleIds exists false, setValue(AutoThreadingData::extraRoleIds, emptyList())) } + db.createCollection("lockedChannelData") db.createCollection("temporaryBanData") } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt index abd3c9d8..6e5e8dc8 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt @@ -11,6 +11,7 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalChanne import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext +import dev.kord.common.DiscordBitSet import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.core.behavior.channel.asChannelOfOrNull @@ -22,6 +23,8 @@ import dev.kord.core.entity.channel.TextChannel import dev.kord.core.entity.channel.ThreadParentChannel import dev.kord.core.entity.channel.thread.TextChannelThread import kotlinx.datetime.Clock +import org.hyacinthbots.lilybot.database.collections.LockedChannelCollection +import org.hyacinthbots.lilybot.database.entities.LockedChannelData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.botHasChannelPerms import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms @@ -49,15 +52,9 @@ class LockingCommands : Extension() { check { anyGuild() - requiredConfigs( - ConfigOptions.MODERATION_ENABLED - ) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) - requireBotPermissions( - Permission.ManageChannels, - Permission.ManageRoles, - Permission.SendMessages - ) + requireBotPermissions(Permission.ManageChannels, Permission.ManageRoles) botHasChannelPerms(Permissions(Permission.ManageChannels)) } @@ -66,12 +63,28 @@ class LockingCommands : Extension() { val channelParent = getChannelParent(channelArg) val targetChannel = getTargetChannel(channelParent, channelArg) - val channelPerms = targetChannel!!.getPermissionOverwritesForRole(guild!!.id) - if (channelPerms != null && channelPerms.denied.contains(Permission.SendMessages)) { - respond { content = "This channel is already locked!" } + val currentChannelPerms = targetChannel?.getPermissionOverwritesForRole(guild!!.id) + if (currentChannelPerms == null) { + respond { + content = "There was an error getting the permissions for this channel. Please try again." + } return@action } + if (LockedChannelCollection().getLockedChannel(guild!!.id, targetChannel.id) != null) { + respond { content = "This channel is already locked" } + return@action + } + + LockedChannelCollection().addLockedChannel( + LockedChannelData( + guildId = guild!!.id, + channelId = targetChannel.id, + allowed = currentChannelPerms.data.allowed.code.value, + denied = currentChannelPerms.data.denied.code.value + ) + ) + val everyoneRole = guild!!.getRoleOrNull(guild!!.id) if (everyoneRole == null) { respond { content = "I was unable to get the `@everyone` role. Please try again." } @@ -119,15 +132,9 @@ class LockingCommands : Extension() { check { anyGuild() - requiredConfigs( - ConfigOptions.MODERATION_ENABLED - ) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) - requireBotPermissions( - Permission.ManageChannels, - Permission.ManageRoles, - Permission.SendMessages - ) + requireBotPermissions(Permission.ManageChannels, Permission.ManageRoles, Permission.SendMessages) } action { @@ -187,11 +194,7 @@ class LockingCommands : Extension() { anyGuild() requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) - requireBotPermissions( - Permission.ManageChannels, - Permission.ManageRoles, - Permission.SendMessages - ) + requireBotPermissions(Permission.ManageChannels, Permission.ManageRoles) botHasChannelPerms(Permissions(Permission.ManageChannels)) } @@ -209,30 +212,31 @@ class LockingCommands : Extension() { return@action } - val channelPerms = targetChannel!!.getPermissionOverwritesForRole(guild!!.id) + val channelPerms = targetChannel?.getPermissionOverwritesForRole(guild!!.id) if (channelPerms == null) { respond { content = "This channel is not locked!" } return@action } - if (!channelPerms.denied.contains(Permission.SendMessages)) { + val lockedChannel = LockedChannelCollection().getLockedChannel(guild!!.id, targetChannel.id) + if (lockedChannel == null) { respond { content = "This channel is not locked!" } return@action } targetChannel.editRolePermission(guild!!.id) { - denied -= Permission.SendMessages - denied -= Permission.SendMessagesInThreads - denied -= Permission.AddReactions - denied -= Permission.UseApplicationCommands + denied = Permissions.Builder(DiscordBitSet(lockedChannel.denied)).build() + allowed = Permissions.Builder(DiscordBitSet(lockedChannel.allowed)).build() } targetChannel.createEmbed { title = "Channel Unlocked" description = "This channel has been unlocked by a moderator.\n" + - "Please be aware of the rules when continuing discussion." + "Please be aware of the rules when continuing discussion." color = DISCORD_GREEN } + LockedChannelCollection().removeLockedChannel(guild!!.id, targetChannel.id) + respond { content = "${targetChannel.mention} has been unlocked." } val actionLog = @@ -258,15 +262,9 @@ class LockingCommands : Extension() { check { anyGuild() - requiredConfigs( - ConfigOptions.MODERATION_ENABLED - ) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) - requireBotPermissions( - Permission.ManageChannels, - Permission.ManageRoles, - Permission.SendMessages - ) + requireBotPermissions(Permission.ManageChannels, Permission.ManageRoles, Permission.SendMessages) } action { @@ -334,9 +332,9 @@ class LockingCommands : Extension() { * @since 4.8.0 */ private suspend inline fun EphemeralInteractionContext.getTargetChannel( - channelParent: TextChannel?, - channelArg: Channel? - ): TextChannel? { + channelParent: TextChannel?, + channelArg: Channel? + ): TextChannel? { val targetChannel = channelParent ?: channelArg?.asChannelOfOrNull() if (targetChannel == null) { respond { From 370c361bf47f78888739c1931d6233bf125a9dbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 May 2024 13:56:36 +0100 Subject: [PATCH 28/42] Bump ch.qos.logback:logback-classic from 1.5.3 to 1.5.6 (#398) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.3 to 1.5.6. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.3...v_1.5.6) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3a7bce39..f2d6d64a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ blossom = "2.1.0" # Libraries kord-extensions = "1.8.0-20240319.115836-21" logging = "6.0.4" -logback = "1.5.3" +logback = "1.5.6" github-api = "1.321" kmongo = "4.11.0" docgenerator = "0.1.5-SNAPSHOT" From 1307899e1dea2a34e0f4cef36060c765718741ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 May 2024 13:57:22 +0100 Subject: [PATCH 29/42] Bump io.github.oshai:kotlin-logging from 6.0.4 to 6.0.9 (#397) Bumps [io.github.oshai:kotlin-logging](https://github.com/oshai/kotlin-logging) from 6.0.4 to 6.0.9. - [Release notes](https://github.com/oshai/kotlin-logging/releases) - [Changelog](https://github.com/oshai/kotlin-logging/blob/master/ChangeLog-old.md) - [Commits](https://github.com/oshai/kotlin-logging/compare/6.0.4...6.0.9) --- updated-dependencies: - dependency-name: io.github.oshai:kotlin-logging dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: NoComment <67918617+NoComment1105@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f2d6d64a..8e1953f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ blossom = "2.1.0" # Libraries kord-extensions = "1.8.0-20240319.115836-21" -logging = "6.0.4" +logging = "6.0.9" logback = "1.5.6" github-api = "1.321" kmongo = "4.11.0" From 56b009fbb60b7c62ec82bea09c80b0b50c669eb6 Mon Sep 17 00:00:00 2001 From: NoComment Date: Thu, 13 Jun 2024 18:10:51 +0100 Subject: [PATCH 30/42] Fix lily being unable to unlock channels after she locks them --- docs/commands.md | 43 ------------------- .../extensions/moderation/LockingCommands.kt | 11 +++++ 2 files changed, 11 insertions(+), 43 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 3a52445e..24924bab 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -37,49 +37,6 @@ None * **Arguments**: * `channel` - The channel to view the auto-threading settings for. - Channel -Hopefull--- -### Command name: `clear count` -**Description**: Clear a specific count of messages - -**Required Member Permissions**: Manage Messages - -* **Arguments**: - * `messages` - Number of messages to delete - Int - * `author` - The author of the messages to clear - Optional User - ---- -### Command name: `clear before` -**Description**: Clear messages before a given message ID - -**Required Member Permissions**: Manage Messages - -* **Arguments**: - * `before` - The ID of the message to clear before - Snowflake - * `message-count` - The number of messages to clear - Optional Int/Long - * `author` - The author of the messages to clear - Optional User - ---- -### Command name: `clear after` -**Description**: Clear messages before a given message ID - -**Required Member Permissions**: Manage Messages - -* **Arguments**: - * `after` - The ID of the message to clear after - Snowflake - * `message-count` - The number of messages to clear - Optional Int/Long - * `author` - The author of the messages to clear - Optional User - ---- -### Command name: `clear between` -**Description**: Clear messages between 2 message IDs - -**Required Member Permissions**: Manage Messages - -* **Arguments**: - * `after` - The ID of the message to clear after - Snowflake - * `before` - The ID of the message to clear before - Snowflake - * `author` - The author of the messages to clear - Optional User - --- ### Command name: `auto-threading add-roles` **Description**: Add extra to threads in auto-threaded channels diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt index 6e5e8dc8..57c22683 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt @@ -16,6 +16,7 @@ import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.channel.createEmbed +import dev.kord.core.behavior.channel.editMemberPermission import dev.kord.core.behavior.channel.editRolePermission import dev.kord.core.behavior.edit import dev.kord.core.entity.channel.Channel @@ -107,6 +108,11 @@ class LockingCommands : Extension() { denied += Permission.UseApplicationCommands } + // Explicitly allow Lily send messages, so she can unlock the server again + targetChannel.editMemberPermission(this@ephemeralSlashCommand.kord.selfId) { + allowed += Permission.SendMessages + } + respond { content = "${targetChannel.mention} has been locked." } val actionLog = @@ -228,6 +234,11 @@ class LockingCommands : Extension() { allowed = Permissions.Builder(DiscordBitSet(lockedChannel.allowed)).build() } + // Remove the explicit allow to avoid any issues with the servers permission system + targetChannel.editMemberPermission(this@ephemeralSlashCommand.kord.selfId) { + allowed -= Permission.SendMessages + } + targetChannel.createEmbed { title = "Channel Unlocked" description = "This channel has been unlocked by a moderator.\n" + From b3d1f68b3adff2ea0b8d99e705a94aa585a38c6a Mon Sep 17 00:00:00 2001 From: NoComment Date: Thu, 13 Jun 2024 18:35:57 +0100 Subject: [PATCH 31/42] Hopefully avoid funny embeds appearing when messages don't change properly --- .../lilybot/extensions/events/MessageEdit.kt | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt index c3dbffdd..25cbbcb5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt @@ -10,6 +10,7 @@ import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PKMessage import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.ProxiedMessageUpdateEvent import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.UnProxiedMessageUpdateEvent import com.kotlindiscord.kord.extensions.utils.getJumpUrl +import com.kotlindiscord.kord.extensions.utils.isNullOrBot import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.Message @@ -40,11 +41,9 @@ class MessageEdit : Extension() { check { anyGuild() requiredConfigs(ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) - failIf { - val message = event.message.asMessageOrNull() - message?.author?.isBot == true || event.old?.content == message?.content || - event.old?.content.isNullOrEmpty() - } + failIf(event.message.asMessageOrNull()?.author.isNullOrBot()) + failIf(event.old?.content == event.message.asMessageOrNull()?.content) + failIf(event.old.trimmedContents() == null) } action { onMessageEdit(event.getMessageOrNull(), event.old, null) @@ -60,10 +59,8 @@ class MessageEdit : Extension() { check { anyGuild() requiredConfigs(ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) - failIf { - event.old?.content == event.message.asMessageOrNull()?.content || - event.old?.content.isNullOrEmpty() - } + failIf(event.old?.content == event.message.asMessageOrNull()?.content) + failIf(event.old.trimmedContents() == null) } action { onMessageEdit(event.getMessageOrNull(), event.old, event.pkMessage) From 9c383714d67607b96c02e3bb4f4e527f8d618cf6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:55:02 +0100 Subject: [PATCH 32/42] Bump org.kohsuke:github-api from 1.321 to 1.322 (#406) Bumps [org.kohsuke:github-api](https://github.com/hub4j/github-api) from 1.321 to 1.322. - [Release notes](https://github.com/hub4j/github-api/releases) - [Changelog](https://github.com/hub4j/github-api/blob/main/CHANGELOG.md) - [Commits](https://github.com/hub4j/github-api/compare/github-api-1.321...github-api-1.322) --- updated-dependencies: - dependency-name: org.kohsuke:github-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e1953f5..ff8222ff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ blossom = "2.1.0" kord-extensions = "1.8.0-20240319.115836-21" logging = "6.0.9" logback = "1.5.6" -github-api = "1.321" +github-api = "1.322" kmongo = "4.11.0" docgenerator = "0.1.5-SNAPSHOT" From 0680156cbea67818b22fb5777fc36dccdd525ebc Mon Sep 17 00:00:00 2001 From: NoComment Date: Mon, 1 Jul 2024 19:28:33 +0100 Subject: [PATCH 33/42] Update to Kotlin 2 --- gradle/libs.versions.toml | 10 +++++----- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- .../org/hyacinthbots/lilybot/database/Cleanups.kt | 12 +++++------- .../lilybot/database/migrations/Migrator.kt | 2 +- .../lilybot/extensions/util/StatusPing.kt | 2 +- .../kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt | 2 +- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff8222ff..dbb5c6fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # Plugins -kotlin = "1.9.23" +kotlin = "2.0.20-Beta1" shadow = "8.1.1" detekt = "1.23.6" git-hooks = "0.0.2" @@ -8,12 +8,12 @@ grgit = "5.2.2" blossom = "2.1.0" # Libraries -kord-extensions = "1.8.0-20240319.115836-21" -logging = "6.0.9" +kord-extensions = "1.9.0-20240701.141421-2" +logging = "7.0.0" logback = "1.5.6" github-api = "1.322" -kmongo = "4.11.0" -docgenerator = "0.1.5-SNAPSHOT" +kmongo = "5.1.0" +docgenerator = "0.2.0-SNAPSHOT" [libraries] kord-extensions-core = { module = "com.kotlindiscord.kord.extensions:kord-extensions", version.ref = "kord-extensions" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23a..a4413138 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42..b740cf13 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt index a004e1bc..aa4b05a7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt @@ -5,10 +5,8 @@ import dev.kord.core.Kord import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.rest.request.KtorRequestException +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.datetime.Clock -import mu.KotlinLogging -import org.hyacinthbots.lilybot.database.Cleanups.cleanupGuildData -import org.hyacinthbots.lilybot.database.Cleanups.cleanupThreadData import org.hyacinthbots.lilybot.database.collections.GithubCollection import org.hyacinthbots.lilybot.database.collections.LockedChannelCollection import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection @@ -54,7 +52,7 @@ object Cleanups : KordExKoinComponent { * @since 3.2.0 */ suspend fun cleanupGuildData(kord: Kord) { - cleanupsLogger.info("Starting guild cleanup...") + cleanupsLogger.info { "Starting guild cleanup..." } val leaveTimeData = guildLeaveTimeCollection.find().toList() var deletedGuildData = 0 val now = Clock.System.now() @@ -83,7 +81,7 @@ object Cleanups : KordExKoinComponent { } } - cleanupsLogger.info("Deleted old data for $deletedGuildData guilds from the database") + cleanupsLogger.info { "Deleted old data for $deletedGuildData guilds from the database" } } /** @@ -93,7 +91,7 @@ object Cleanups : KordExKoinComponent { * @since 3.2.0 */ suspend fun cleanupThreadData(kordInstance: Kord) { - cleanupsLogger.info("Starting thread cleanup...") + cleanupsLogger.info { "Starting thread cleanup..." } val threads = threadDataCollection.find().toList() var deletedThreads = 0 for (it in threads) { @@ -117,6 +115,6 @@ object Cleanups : KordExKoinComponent { continue } } - cleanupsLogger.info("Deleted $deletedThreads old threads from the database") + cleanupsLogger.info { "Deleted $deletedThreads old threads from the database" } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt index 4cf1fcec..0cae36cd 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt @@ -12,7 +12,7 @@ package org.hyacinthbots.lilybot.database.migrations import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.collections.ConfigMetaCollection import org.hyacinthbots.lilybot.database.collections.MainMetaCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt index 2422aacd..5e08401a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt @@ -3,9 +3,9 @@ package org.hyacinthbots.lilybot.extensions.util import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.HttpClient import io.ktor.client.request.get -import mu.KotlinLogging import org.hyacinthbots.lilybot.utils.ENV import kotlin.time.Duration.Companion.seconds diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index 22d5b62d..eba5d13c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -11,12 +11,12 @@ import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.RoleBehavior import dev.kord.core.entity.Message import dev.kord.core.supplier.EntitySupplyStrategy +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.flow.count import kotlinx.coroutines.runBlocking import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime -import mu.KotlinLogging import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.collections.ConfigMetaCollection import org.hyacinthbots.lilybot.database.collections.GalleryChannelCollection From f812228c51a45b04d705f08d96b9ba997d53ed9c Mon Sep 17 00:00:00 2001 From: NoComment Date: Tue, 2 Jul 2024 10:34:52 +0100 Subject: [PATCH 34/42] Update to Java 21 :sunglasses: --- .github/workflows/gradle.yml | 14 +++++++------- .github/workflows/main.yml | 14 +++++++------- .github/workflows/release.yml | 14 +++++++------- Dockerfile | 2 +- build.gradle.kts | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9408667c..bbe0d515 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,21 +14,21 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Build Artifacts - uses: gradle/gradle-build-action@v3 - with: - arguments: build --stacktrace - gradle-home-cache-cleanup: true + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Build Project + run: ./gradlew build --stacktrace - name: Upload build artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95758155..c7c5c537 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,21 +12,21 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Build Artifacts - uses: gradle/gradle-build-action@v3 - with: - arguments: build --stacktrace - gradle-home-cache-cleanup: true + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Build Project + run: ./gradlew build --stacktrace - name: Upload build artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d30bf7af..cdc39293 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,21 +13,21 @@ jobs: - name: Checkout sources uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Build Artifacts - uses: gradle/gradle-build-action@v3 - with: - arguments: build --stacktrace - gradle-home-cache-cleanup: true + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Build Project + run: ./gradlew build --stacktrace - name: Upload artifacts GitHub uses: AButler/upload-release-assets@v3.0 diff --git a/Dockerfile b/Dockerfile index 09b9f635..6ac53d0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17-jdk-slim +FROM openjdk:21-jdk-slim RUN mkdir /bot RUN mkdir /data diff --git a/build.gradle.kts b/build.gradle.kts index 3bc9a704..88687225 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,7 +73,7 @@ gitHooks { tasks { withType { compilerOptions { - jvmTarget.set(JvmTarget.fromTarget("17")) + jvmTarget.set(JvmTarget.fromTarget("21")) languageVersion.set(KotlinVersion.fromVersion(libs.plugins.kotlin.get().version.requiredVersion.substringBeforeLast("."))) incremental = true freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") From 17872cf95107b759b822adeecb441e632abecd1e Mon Sep 17 00:00:00 2001 From: NoComment Date: Tue, 16 Jul 2024 12:16:26 +0100 Subject: [PATCH 35/42] Update dependencies and gradle --- gradle/libs.versions.toml | 6 +++--- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43504 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 ++++- gradlew.bat | 2 ++ 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dbb5c6fc..a89738f6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # Plugins -kotlin = "2.0.20-Beta1" +kotlin = "2.0.10-RC" shadow = "8.1.1" detekt = "1.23.6" git-hooks = "0.0.2" @@ -8,10 +8,10 @@ grgit = "5.2.2" blossom = "2.1.0" # Libraries -kord-extensions = "1.9.0-20240701.141421-2" +kord-extensions = "1.9.0-20240714.071343-11" logging = "7.0.0" logback = "1.5.6" -github-api = "1.322" +github-api = "1.323" kmongo = "5.1.0" docgenerator = "0.2.0-SNAPSHOT" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..2c3521197d7c4586c843d1d3e9090525f1898cde 100644 GIT binary patch delta 8703 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 Ojj7^59?Sdh$^Qpp*~F>< delta 8662 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138..09523c0e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf13..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30db..9d21a218 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From 58143e73030ffb074b33cb2718530c3004f07a4a Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 20 Aug 2024 17:34:09 +0100 Subject: [PATCH 36/42] Update KordEx, Kotlin, Gradle, fix small K2 warnings (#413) * Update KordEx, Kotlin, Gradle, fix small K2 warnings * Yeet unnecessary Gradle task * Configure the about command * Address comments * Update KordEx to 2.1.0 Hopefully this is it! --- build.gradle.kts | 16 ++- docs/commands.md | 12 +-- gradle/libs.versions.toml | 17 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 43504 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- .../org/hyacinthbots/lilybot/LilyBot.kt | 94 ++++++++++++++++-- .../hyacinthbots/lilybot/database/Cleanups.kt | 2 +- .../collections/AutoThreadingCollection.kt | 2 +- .../database/collections/ConfigCollection.kt | 2 +- .../collections/GalleryChannelCollection.kt | 2 +- .../database/collections/GithubCollection.kt | 2 +- .../collections/GuildLeaveTimeCollection.kt | 2 +- .../collections/LockedChannelCollection.kt | 2 +- .../database/collections/MetaCollection.kt | 2 +- .../NewsChannelPublishingCollection.kt | 2 +- .../collections/ReminderCollection.kt | 2 +- .../collections/RoleMenuCollection.kt | 2 +- .../collections/RoleSubscriptionCollection.kt | 2 +- .../database/collections/StatusCollection.kt | 2 +- .../database/collections/TagsCollection.kt | 2 +- .../collections/TemporaryBanCollection.kt | 2 +- .../database/collections/ThreadsCollection.kt | 2 +- .../database/collections/UptimeCollection.kt | 2 +- .../database/collections/WarnCollection.kt | 2 +- .../collections/WelcomeChannelCollection.kt | 4 +- .../lilybot/database/entities/AdaptedData.kt | 2 +- .../lilybot/database/migrations/Migrator.kt | 2 +- .../database/storage/MongoDBDataAdapter.kt | 8 +- .../lilybot/extensions/config/Config.kt | 38 ++++--- .../lilybot/extensions/config/GuildLogging.kt | 4 +- .../extensions/events/AutoThreading.kt | 46 ++++----- .../extensions/events/MemberLogging.kt | 14 +-- .../extensions/events/MessageDelete.kt | 14 +-- .../lilybot/extensions/events/MessageEdit.kt | 22 ++-- .../extensions/events/ModThreadInviting.kt | 6 +- .../extensions/moderation/ClearCommands.kt | 24 ++--- .../extensions/moderation/LockingCommands.kt | 22 ++-- .../moderation/ModerationCommands.kt | 67 ++++++------- .../lilybot/extensions/moderation/Report.kt | 24 ++--- .../lilybot/extensions/util/GalleryChannel.kt | 24 ++--- .../lilybot/extensions/util/Github.kt | 26 ++--- .../extensions/util/GuildAnnouncements.kt | 16 +-- .../lilybot/extensions/util/InfoCommands.kt | 89 ++--------------- .../lilybot/extensions/util/ModUtilities.kt | 50 +++++----- .../extensions/util/NewsChannelPublishing.kt | 28 +++--- .../extensions/util/PublicUtilities.kt | 32 +++--- .../lilybot/extensions/util/Reminders.kt | 52 +++++----- .../lilybot/extensions/util/RoleMenu.kt | 54 +++++----- .../lilybot/extensions/util/StartupHooks.kt | 14 +-- .../lilybot/extensions/util/StatusPing.kt | 6 +- .../lilybot/extensions/util/Tags.kt | 38 +++---- .../lilybot/extensions/util/ThreadControl.kt | 28 +++--- .../lilybot/utils/ResponseHelper.kt | 2 +- .../lilybot/utils/_ConfigUtils.kt | 4 +- .../hyacinthbots/lilybot/utils/_Constants.kt | 4 +- .../lilybot/utils/_PermissionUtils.kt | 16 +-- .../org/hyacinthbots/lilybot/utils/_Utils.kt | 8 +- 57 files changed, 489 insertions(+), 476 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 88687225..43449c4a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,8 @@ plugins { group = "org.hyacinthbots.lilybot" version = "4.9.0" +val className = "org.hyacinthbots.lilybot.LilyBotKt" + repositories { mavenCentral() @@ -30,6 +32,11 @@ repositories { url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") } + maven { + name = "Kord Extensions (Snapshots)" + url = uri("https://snapshots-repo.kordex.dev") + } + maven { name = "JitPack" url = uri("https://jitpack.io") @@ -61,7 +68,7 @@ dependencies { } application { - mainClass.set("org.hyacinthbots.lilybot.LilyBotKt") + mainClass.set(className) } gitHooks { @@ -80,9 +87,14 @@ tasks { } } + java { // Should match the Kotlin compiler options ideally + sourceCompatibility = JavaVersion.toVersion("21") + targetCompatibility = JavaVersion.toVersion("21") + } + jar { manifest { - attributes("Main-Class" to "org.hyacinthbots.lilybot.LilyBotKt") + attributes("Main-Class" to className) } } diff --git a/docs/commands.md b/docs/commands.md index 24924bab..3820ecbe 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,5 +1,11 @@ ## Slash Commands +### Command name: `about` +**Description**: Learn about this bot + +* Arguments: +None +--- ### Command name: `auto-threading enable` **Description**: Automatically create a thread for each message sent in this channel. @@ -234,12 +240,6 @@ None ### Command name: `help` **Description**: Get help with using Lily! -* Arguments: -None ---- -### Command name: `info` -**Description**: Learn about Lily, and get uptime data! - * Arguments: None --- diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a89738f6..50e57a31 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # Plugins -kotlin = "2.0.10-RC" +kotlin = "2.0.10" shadow = "8.1.1" detekt = "1.23.6" git-hooks = "0.0.2" @@ -8,19 +8,20 @@ grgit = "5.2.2" blossom = "2.1.0" # Libraries -kord-extensions = "1.9.0-20240714.071343-11" +kord-extensions = "2.1.0-20240820.102632-1" logging = "7.0.0" logback = "1.5.6" github-api = "1.323" kmongo = "5.1.0" -docgenerator = "0.2.0-SNAPSHOT" +docgenerator = "0.2.1-SNAPSHOT" [libraries] -kord-extensions-core = { module = "com.kotlindiscord.kord.extensions:kord-extensions", version.ref = "kord-extensions" } -kord-extensions-phishing = { module = "com.kotlindiscord.kord.extensions:extra-phishing", version.ref = "kord-extensions" } -kord-extensions-pluralkit = { module = "com.kotlindiscord.kord.extensions:extra-pluralkit", version.ref = "kord-extensions"} -kord-extensions-unsafe = { module = "com.kotlindiscord.kord.extensions:unsafe", version.ref = "kord-extensions"} -kord-extensions-welcome = { module = "com.kotlindiscord.kord.extensions:extra-welcome", version.ref = "kord-extensions"} +kord-extensions-core = { module = "dev.kordex:kord-extensions", version.ref = "kord-extensions" } +kord-extensions-phishing = { module = "dev.kordex.modules:func-phishing", version.ref = "kord-extensions" } +kord-extensions-pluralkit = { module = "dev.kordex.modules:pluralkit", version.ref = "kord-extensions"} +kord-extensions-unsafe = { module = "dev.kordex.modules:dev-unsafe", version.ref = "kord-extensions"} +kord-extensions-welcome = { module = "dev.kordex.modules:func-welcome", version.ref = "kord-extensions"} + kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } logging = { module = "io.github.oshai:kotlin-logging", version.ref = "logging" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 2c3521197d7c4586c843d1d3e9090525f1898cde..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 3990 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0e..9355b415 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt index 6b2d0815..3b02244b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt @@ -2,19 +2,25 @@ package org.hyacinthbots.lilybot -import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.modules.extra.phishing.DetectionAction -import com.kotlindiscord.kord.extensions.modules.extra.phishing.extPhishing -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.extPluralKit -import com.kotlindiscord.kord.extensions.modules.extra.welcome.welcomeChannel import dev.kord.common.entity.Permission import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent +import dev.kordex.core.ExtensibleBot +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.time.TimestampType +import dev.kordex.core.time.toDiscord +import dev.kordex.data.api.DataCollection +import dev.kordex.modules.func.phishing.DetectionAction +import dev.kordex.modules.func.phishing.extPhishing +import dev.kordex.modules.func.welcome.welcomeChannel +import dev.kordex.modules.pluralkit.extPluralKit import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime import org.hyacinthbots.docgenerator.docsGenerator import org.hyacinthbots.docgenerator.enums.CommandTypes import org.hyacinthbots.docgenerator.enums.SupportedFileFormat +import org.hyacinthbots.lilybot.database.collections.UptimeCollection import org.hyacinthbots.lilybot.database.collections.WelcomeChannelCollection import org.hyacinthbots.lilybot.database.storage.MongoDBDataAdapter import org.hyacinthbots.lilybot.extensions.config.Config @@ -42,8 +48,10 @@ import org.hyacinthbots.lilybot.extensions.util.StartupHooks import org.hyacinthbots.lilybot.extensions.util.StatusPing import org.hyacinthbots.lilybot.extensions.util.Tags import org.hyacinthbots.lilybot.extensions.util.ThreadControl +import org.hyacinthbots.lilybot.internal.BuildInfo import org.hyacinthbots.lilybot.utils.BOT_TOKEN import org.hyacinthbots.lilybot.utils.ENVIRONMENT +import org.hyacinthbots.lilybot.utils.HYACINTH_GITHUB import org.hyacinthbots.lilybot.utils.database import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.kohsuke.github.GitHub @@ -60,6 +68,8 @@ val docFile = Path("./docs/commands.md") suspend fun main() { val bot = ExtensibleBot(BOT_TOKEN) { + dataCollectionMode = DataCollection.None + database(true) dataAdapter(::MongoDBDataAdapter) @@ -73,6 +83,78 @@ suspend fun main() { +Intent.GuildMembers } + about { + ephemeral = false + name = "Info about LilyBot" + + logoUrl = "https://github.com/HyacinthBots/LilyBot/blob/main/docs/lily-logo-transparent.png?raw=true" + + description = "Lily is a FOSS multi-purpose bot for Discord created by the HyacinthBots organization. " + + "Use `/help` for support or `/invite` to get an invite link." + + button { + name = "extensions.about.buttons.invite" + url = "https://discord.com/api/oauth2/authorize?client_id=876278900836139008" + + "&permissions=1151990787078&scope=bot%20applications.commands" + } + + button { + name = "Privacy Policy" + url = "$HYACINTH_GITHUB/LilyBot/blob/main/docs/privacy-policy.md" + } + + button { + name = "Terms of Service" + url = "$HYACINTH_GITHUB/.github/blob/main/terms-of-service.md" + } + + field { + name = "How can I support the continued development of Lily?" + + value = "Lily is developed primarily by NoComment#6411 and tempest#4510 " + + "in our free time. Neither of us have resources to invest in hosting, " + + "so financial donations via [Buy Me a Coffee]" + + "(https://buymeacoffee.com/Hyacinthbots) help keep Lily afloat. Currently, we run" + + "lily on a Hetzner cloud server, which we can afford in our current situation. " + + "We will also have domain costs for our website.\n\n" + + "Contributions of code & documentation are also incredibly appreciated, " + + "and you can read our [contributing guide]" + + "($HYACINTH_GITHUB/LilyBot/blob/main/CONTRIBUTING.md) " + + "or [development guide]" + + "($HYACINTH_GITHUB/LilyBot/blob/main/docs/development-guide.md) " + + "to get started." + } + + field { + name = "Version" + // To avoid IntelliJ shouting about build errors, use https://plugins.jetbrains.com/plugin/9407-pebble + value = + "${BuildInfo.LILY_VERSION} (${BuildInfo.BUILD_ID})" + inline = true + } + + field { + name = "Up Since" + value = "${ + UptimeCollection().get()?.onTime?.toLocalDateTime(TimeZone.UTC) + ?.time.toString().split(".")[0] + } ${UptimeCollection().get()?.onTime?.toLocalDateTime(TimeZone.UTC)?.date} UTC\n " + + "(${UptimeCollection().get()?.onTime?.toDiscord(TimestampType.RelativeTime) ?: "??"})" + inline = true + } + + field { + name = "Useful links" + value = + "Website: Coming Soon™️\n" + + "GitHub: ${HYACINTH_GITHUB}\n" + + "Buy Me a Coffee: https://buymeacoffee.com/HyacinthBots\n" + + "Twitter: https://twitter.com/HyacinthBots\n" + + "Email: `hyacinthbots@outlook.com`\n" + + "Discord: https://discord.gg/hy2329fcTZ" + } + } + // Add the extensions to the bot extensions { add(::AutoThreading) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt index aa4b05a7..0d82cb81 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt @@ -1,10 +1,10 @@ package org.hyacinthbots.lilybot.database -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.core.Kord import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.rest.request.KtorRequestException +import dev.kordex.core.koin.KordExKoinComponent import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.GithubCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/AutoThreadingCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/AutoThreadingCollection.kt index 4f1ff27a..c7d7489c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/AutoThreadingCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/AutoThreadingCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.AutoThreadingData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ConfigCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ConfigCollection.kt index 61752bca..2702c26d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ConfigCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ConfigCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.LoggingConfigData import org.hyacinthbots.lilybot.database.entities.ModerationConfigData diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GalleryChannelCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GalleryChannelCollection.kt index c9b75e79..ee27c237 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GalleryChannelCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GalleryChannelCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.GalleryChannelData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GithubCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GithubCollection.kt index 98732e2f..0df3551c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GithubCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GithubCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.GithubData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GuildLeaveTimeCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GuildLeaveTimeCollection.kt index 60b21c6b..0213c191 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GuildLeaveTimeCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GuildLeaveTimeCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import kotlinx.datetime.Instant import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.GuildLeaveTimeData diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt index 2bffc04f..cf783ae5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.LockedChannelData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/MetaCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/MetaCollection.kt index 01ae94aa..79cc1733 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/MetaCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/MetaCollection.kt @@ -1,6 +1,6 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.ConfigMetaData import org.hyacinthbots.lilybot.database.entities.MainMetaData diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/NewsChannelPublishingCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/NewsChannelPublishingCollection.kt index 2851da56..84ab4a51 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/NewsChannelPublishingCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/NewsChannelPublishingCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.NewsChannelPublishingData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt index 006a1263..e32aa144 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone import kotlinx.datetime.plus diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleMenuCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleMenuCollection.kt index f8319ee5..037c5d11 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleMenuCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleMenuCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.RoleMenuData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleSubscriptionCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleSubscriptionCollection.kt index 027e5272..d103759e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleSubscriptionCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleSubscriptionCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.RoleSubscriptionData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/StatusCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/StatusCollection.kt index aaccbba8..c6554147 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/StatusCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/StatusCollection.kt @@ -1,6 +1,6 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.StatusData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TagsCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TagsCollection.kt index 89584046..a006389a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TagsCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TagsCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.TagsData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt index 7413f561..01fb1de6 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.TemporaryBanData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ThreadsCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ThreadsCollection.kt index c65ad4ee..a66899a3 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ThreadsCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ThreadsCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.ThreadData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/UptimeCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/UptimeCollection.kt index f7f4057e..3cae0c85 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/UptimeCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/UptimeCollection.kt @@ -1,6 +1,6 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent +import dev.kordex.core.koin.KordExKoinComponent import kotlinx.datetime.Instant import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.UptimeData diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WarnCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WarnCollection.kt index cb64da4d..602363f5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WarnCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WarnCollection.kt @@ -1,7 +1,7 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.WarnData import org.koin.core.component.inject diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WelcomeChannelCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WelcomeChannelCollection.kt index 2b98e2e2..da0cd6c9 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WelcomeChannelCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/WelcomeChannelCollection.kt @@ -7,14 +7,14 @@ package org.hyacinthbots.lilybot.database.collections -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.common.entity.Snowflake import dev.kord.core.Kord +import dev.kordex.core.koin.KordExKoinComponent import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.entities.WelcomeChannelData import org.koin.core.component.inject import org.litote.kmongo.eq -import com.kotlindiscord.kord.extensions.modules.extra.welcome.data.WelcomeChannelData as KordExWelcomeChannelData +import dev.kordex.modules.func.welcome.data.WelcomeChannelData as KordExWelcomeChannelData /** * This class contains the functions for interacting with the [Welcome channel database][WelcomeChannelData]. This class diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AdaptedData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AdaptedData.kt index c41a4716..290b45aa 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AdaptedData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AdaptedData.kt @@ -5,8 +5,8 @@ */ package org.hyacinthbots.lilybot.database.entities -import com.kotlindiscord.kord.extensions.storage.StorageType import dev.kord.common.entity.Snowflake +import dev.kordex.core.storage.StorageType import kotlinx.serialization.Serializable @Serializable diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt index 0cae36cd..96f3cee6 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt @@ -11,7 +11,7 @@ package org.hyacinthbots.lilybot.database.migrations -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent +import dev.kordex.core.koin.KordExKoinComponent import io.github.oshai.kotlinlogging.KotlinLogging import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.collections.ConfigMetaCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/storage/MongoDBDataAdapter.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/storage/MongoDBDataAdapter.kt index 6790aa07..d3b443bd 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/storage/MongoDBDataAdapter.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/storage/MongoDBDataAdapter.kt @@ -6,11 +6,11 @@ package org.hyacinthbots.lilybot.database.storage -import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent -import com.kotlindiscord.kord.extensions.storage.Data -import com.kotlindiscord.kord.extensions.storage.DataAdapter -import com.kotlindiscord.kord.extensions.storage.StorageUnit import com.mongodb.client.model.Filters.and +import dev.kordex.core.koin.KordExKoinComponent +import dev.kordex.core.storage.Data +import dev.kordex.core.storage.DataAdapter +import dev.kordex.core.storage.StorageUnit import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.serializer diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt index d873e6c5..d1118e86 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt @@ -1,25 +1,5 @@ package org.hyacinthbots.lilybot.extensions.config -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.stringChoice -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.boolean -import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingOptionalDuration -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalBoolean -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalChannel -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalRole -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalString -import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI -import com.kotlindiscord.kord.extensions.modules.unsafe.extensions.unsafeSubCommand -import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialSlashCommandResponse -import com.kotlindiscord.kord.extensions.modules.unsafe.types.ackEphemeral -import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral -import com.kotlindiscord.kord.extensions.utils.botHasPermissions import dev.kord.common.entity.Permission import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.getChannelOfOrNull @@ -27,6 +7,24 @@ import dev.kord.core.behavior.interaction.modal import dev.kord.core.entity.channel.TextChannel import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.embed +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.converters.impl.stringChoice +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.converters.impl.boolean +import dev.kordex.core.commands.converters.impl.coalescingOptionalDuration +import dev.kordex.core.commands.converters.impl.optionalBoolean +import dev.kordex.core.commands.converters.impl.optionalChannel +import dev.kordex.core.commands.converters.impl.optionalRole +import dev.kordex.core.commands.converters.impl.optionalString +import dev.kordex.core.components.forms.ModalForm +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.utils.botHasPermissions +import dev.kordex.modules.dev.unsafe.annotations.UnsafeAPI +import dev.kordex.modules.dev.unsafe.commands.slash.InitialSlashCommandResponse +import dev.kordex.modules.dev.unsafe.extensions.unsafeSubCommand import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/GuildLogging.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/GuildLogging.kt index c51e7b21..5640e868 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/GuildLogging.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/GuildLogging.kt @@ -1,10 +1,10 @@ package org.hyacinthbots.lilybot.extensions.config -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.event import dev.kord.core.event.guild.GuildCreateEvent import dev.kord.core.event.guild.GuildDeleteEvent import dev.kord.rest.request.KtorRequestException +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.event import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.GuildLeaveTimeCollection import java.util.concurrent.CancellationException diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt index e88c7b16..bbd641ec 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt @@ -1,28 +1,5 @@ package org.hyacinthbots.lilybot.extensions.events -import com.kotlindiscord.kord.extensions.DISCORD_BLACK -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.channel -import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalRole -import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PKMessage -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.PKMessageCreateEvent -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.ProxiedMessageCreateEvent -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.UnProxiedMessageCreateEvent -import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI -import com.kotlindiscord.kord.extensions.modules.unsafe.extensions.unsafeSubCommand -import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialSlashCommandResponse -import com.kotlindiscord.kord.extensions.modules.unsafe.types.ackEphemeral -import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral -import com.kotlindiscord.kord.extensions.utils.delete -import com.kotlindiscord.kord.extensions.utils.respond import dev.kord.common.entity.ArchiveDuration import dev.kord.common.entity.ChannelType import dev.kord.common.entity.MessageType @@ -44,6 +21,27 @@ import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.core.event.channel.thread.ThreadChannelCreateEvent import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_BLACK +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.converters.impl.channel +import dev.kordex.core.commands.converters.impl.defaultingBoolean +import dev.kordex.core.commands.converters.impl.optionalRole +import dev.kordex.core.components.forms.ModalForm +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.extensions.event +import dev.kordex.core.utils.delete +import dev.kordex.core.utils.respond +import dev.kordex.modules.dev.unsafe.annotations.UnsafeAPI +import dev.kordex.modules.dev.unsafe.commands.slash.InitialSlashCommandResponse +import dev.kordex.modules.dev.unsafe.extensions.unsafeSubCommand +import dev.kordex.modules.pluralkit.api.PKMessage +import dev.kordex.modules.pluralkit.events.PKMessageCreateEvent +import dev.kordex.modules.pluralkit.events.ProxiedMessageCreateEvent +import dev.kordex.modules.pluralkit.events.UnProxiedMessageCreateEvent import kotlinx.datetime.Clock import org.hyacinthbots.docgenerator.subCommandAdditionalDocumentation import org.hyacinthbots.lilybot.database.collections.AutoThreadingCollection @@ -259,7 +257,7 @@ class AutoThreading : Extension() { var responseContent: String? = null autoThreads.forEach { responseContent += "\n<#${it.channelId}>" - if (responseContent!!.length > 4080) { + if (responseContent.length > 4080) { responseContent += "(List trimmed.)" return@forEach } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt index fc899de9..84c88077 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt @@ -1,12 +1,5 @@ package org.hyacinthbots.lilybot.extensions.events -import com.kotlindiscord.kord.extensions.DISCORD_GREEN -import com.kotlindiscord.kord.extensions.DISCORD_RED -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.guildFor -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.utils.botHasPermissions import dev.kord.common.entity.Permission import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.channel.createMessage @@ -15,6 +8,13 @@ import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.guild.MemberJoinEvent import dev.kord.core.event.guild.MemberLeaveEvent import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_GREEN +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.guildFor +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.event +import dev.kordex.core.utils.botHasPermissions import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt index e2516609..b6b14c1a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt @@ -1,12 +1,5 @@ package org.hyacinthbots.lilybot.extensions.events -import com.kotlindiscord.kord.extensions.DISCORD_PINK -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PKMessage -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.ProxiedMessageDeleteEvent -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.UnProxiedMessageDeleteEvent import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.channel.createMessage @@ -15,6 +8,13 @@ import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.message.MessageBulkDeleteEvent import dev.kord.rest.builder.message.create.UserMessageCreateBuilder import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_PINK +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.event +import dev.kordex.modules.pluralkit.api.PKMessage +import dev.kordex.modules.pluralkit.events.ProxiedMessageDeleteEvent +import dev.kordex.modules.pluralkit.events.UnProxiedMessageDeleteEvent import io.ktor.client.request.forms.ChannelProvider import io.ktor.util.cio.toByteReadChannel import kotlinx.datetime.Clock diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt index 25cbbcb5..31ca546f 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt @@ -1,21 +1,21 @@ package org.hyacinthbots.lilybot.extensions.events -import com.kotlindiscord.kord.extensions.DISCORD_YELLOW -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.components.components -import com.kotlindiscord.kord.extensions.components.linkButton -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PKMessage -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.ProxiedMessageUpdateEvent -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.UnProxiedMessageUpdateEvent -import com.kotlindiscord.kord.extensions.utils.getJumpUrl -import com.kotlindiscord.kord.extensions.utils.isNullOrBot import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_YELLOW +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.components.components +import dev.kordex.core.components.linkButton +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.event +import dev.kordex.core.utils.getJumpUrl +import dev.kordex.core.utils.isNullOrBot +import dev.kordex.modules.pluralkit.api.PKMessage +import dev.kordex.modules.pluralkit.events.ProxiedMessageUpdateEvent +import dev.kordex.modules.pluralkit.events.UnProxiedMessageUpdateEvent import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.attachmentsAndProxiedMessageInfo diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt index 1e54960e..5e2dc3c8 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt @@ -1,12 +1,12 @@ package org.hyacinthbots.lilybot.extensions.events -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.event import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.edit import dev.kord.core.event.channel.thread.ThreadChannelCreateEvent import dev.kord.core.supplier.EntitySupplyStrategy +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.event import org.hyacinthbots.lilybot.database.collections.AutoThreadingCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.utils.canPingRole diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ClearCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ClearCommands.kt index db8aa988..7cf90848 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ClearCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ClearCommands.kt @@ -1,17 +1,5 @@ package org.hyacinthbots.lilybot.extensions.moderation -import com.kotlindiscord.kord.extensions.DISCORD_BLACK -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommandContext -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.int -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalInt -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalUser -import com.kotlindiscord.kord.extensions.commands.converters.impl.snowflake -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.common.entity.Snowflake @@ -20,6 +8,18 @@ import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.entity.User import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.supplier.EntitySupplyStrategy +import dev.kordex.core.DISCORD_BLACK +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.EphemeralSlashCommandContext +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.converters.impl.int +import dev.kordex.core.commands.converters.impl.optionalInt +import dev.kordex.core.commands.converters.impl.optionalUser +import dev.kordex.core.commands.converters.impl.snowflake +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt index 57c22683..2ba68712 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt @@ -1,16 +1,5 @@ package org.hyacinthbots.lilybot.extensions.moderation -import com.kotlindiscord.kord.extensions.DISCORD_GREEN -import com.kotlindiscord.kord.extensions.DISCORD_RED -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingString -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalChannel -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext import dev.kord.common.DiscordBitSet import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions @@ -23,6 +12,17 @@ import dev.kord.core.entity.channel.Channel import dev.kord.core.entity.channel.TextChannel import dev.kord.core.entity.channel.ThreadParentChannel import dev.kord.core.entity.channel.thread.TextChannelThread +import dev.kordex.core.DISCORD_GREEN +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.converters.impl.defaultingString +import dev.kordex.core.commands.converters.impl.optionalChannel +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.types.EphemeralInteractionContext import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.LockedChannelCollection import org.hyacinthbots.lilybot.database.entities.LockedChannelData diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt index 2b6f3975..4f157ffa 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt @@ -1,38 +1,5 @@ package org.hyacinthbots.lilybot.extensions.moderation -import com.kotlindiscord.kord.extensions.DISCORD_BLACK -import com.kotlindiscord.kord.extensions.DISCORD_GREEN -import com.kotlindiscord.kord.extensions.DISCORD_RED -import com.kotlindiscord.kord.extensions.annotations.DoNotChain -import com.kotlindiscord.kord.extensions.checks.hasPermissions -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingDuration -import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingOptionalDuration -import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean -import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingString -import com.kotlindiscord.kord.extensions.commands.converters.impl.int -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalAttachment -import com.kotlindiscord.kord.extensions.commands.converters.impl.user -import com.kotlindiscord.kord.extensions.components.components -import com.kotlindiscord.kord.extensions.components.ephemeralStringSelectMenu -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralMessageCommand -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PluralKit -import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator -import com.kotlindiscord.kord.extensions.pagination.pages.Page -import com.kotlindiscord.kord.extensions.pagination.pages.Pages -import com.kotlindiscord.kord.extensions.time.TimestampType -import com.kotlindiscord.kord.extensions.time.toDiscord -import com.kotlindiscord.kord.extensions.utils.dm -import com.kotlindiscord.kord.extensions.utils.isNullOrBot -import com.kotlindiscord.kord.extensions.utils.kordExUserAgent -import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler -import com.kotlindiscord.kord.extensions.utils.scheduling.Task -import com.kotlindiscord.kord.extensions.utils.timeout -import com.kotlindiscord.kord.extensions.utils.timeoutUntil -import com.kotlindiscord.kord.extensions.utils.toDuration import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.common.entity.Snowflake @@ -51,6 +18,39 @@ import dev.kord.core.entity.interaction.followup.EphemeralFollowupMessage import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.embed import dev.kord.rest.request.KtorRequestException +import dev.kordex.core.DISCORD_BLACK +import dev.kordex.core.DISCORD_GREEN +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.annotations.DoNotChain +import dev.kordex.core.checks.hasPermissions +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.converters.impl.coalescingDuration +import dev.kordex.core.commands.converters.impl.coalescingOptionalDuration +import dev.kordex.core.commands.converters.impl.defaultingBoolean +import dev.kordex.core.commands.converters.impl.defaultingString +import dev.kordex.core.commands.converters.impl.int +import dev.kordex.core.commands.converters.impl.optionalAttachment +import dev.kordex.core.commands.converters.impl.user +import dev.kordex.core.components.components +import dev.kordex.core.components.ephemeralStringSelectMenu +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralMessageCommand +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.pagination.EphemeralResponsePaginator +import dev.kordex.core.pagination.pages.Page +import dev.kordex.core.pagination.pages.Pages +import dev.kordex.core.time.TimestampType +import dev.kordex.core.time.toDiscord +import dev.kordex.core.utils.dm +import dev.kordex.core.utils.isNullOrBot +import dev.kordex.core.utils.kordExUserAgent +import dev.kordex.core.utils.scheduling.Scheduler +import dev.kordex.core.utils.scheduling.Task +import dev.kordex.core.utils.timeout +import dev.kordex.core.utils.timeoutUntil +import dev.kordex.core.utils.toDuration +import dev.kordex.modules.pluralkit.api.PluralKit import kotlinx.datetime.Clock import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone @@ -68,7 +68,6 @@ import org.hyacinthbots.lilybot.utils.interval import org.hyacinthbots.lilybot.utils.isBotOrModerator import org.hyacinthbots.lilybot.utils.modCommandChecks import kotlin.time.Duration -import kotlin.time.toDuration class ModerationCommands : Extension() { override val name = "moderation" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt index 19dcfe21..294d86a8 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt @@ -1,17 +1,5 @@ package org.hyacinthbots.lilybot.extensions.moderation -import com.kotlindiscord.kord.extensions.DISCORD_RED -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.components.components -import com.kotlindiscord.kord.extensions.components.ephemeralButton -import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.components.linkButton -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralMessageCommand -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.utils.getJumpUrl import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.UserBehavior @@ -21,6 +9,18 @@ import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.converters.impl.string +import dev.kordex.core.components.components +import dev.kordex.core.components.ephemeralButton +import dev.kordex.core.components.forms.ModalForm +import dev.kordex.core.components.linkButton +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralMessageCommand +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.utils.getJumpUrl import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt index b1b0b083..f424bcf6 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt @@ -1,17 +1,5 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.DISCORD_GREEN -import com.kotlindiscord.kord.extensions.DISCORD_RED -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.guildFor -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.utils.delete -import com.kotlindiscord.kord.extensions.utils.permissionsForMember -import com.kotlindiscord.kord.extensions.utils.respond import dev.kord.common.entity.MessageType import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions @@ -22,6 +10,18 @@ import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.exception.EntityNotFoundException import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_GREEN +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.guildFor +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.extensions.event +import dev.kordex.core.utils.delete +import dev.kordex.core.utils.permissionsForMember +import dev.kordex.core.utils.respond import kotlinx.coroutines.delay import org.hyacinthbots.lilybot.database.collections.GalleryChannelCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt index db957cc4..5deab8cf 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt @@ -1,19 +1,19 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.DISCORD_RED -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.int -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalString -import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand -import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import dev.kord.common.entity.Permission import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.application.slash.publicSubCommand +import dev.kordex.core.commands.converters.impl.int +import dev.kordex.core.commands.converters.impl.optionalString +import dev.kordex.core.commands.converters.impl.string +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.publicSlashCommand +import dev.kordex.core.sentry.BreadcrumbType import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.GithubCollection import org.hyacinthbots.lilybot.github @@ -266,7 +266,7 @@ class Github : Extension() { labels.add(label.name) } - if (labels.size > 0) { + if (labels.isNotEmpty()) { field { name = "Labels:" value = labels.joinToString(", ") diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt index 26ad758b..05028f5a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt @@ -1,13 +1,5 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalSnowflake -import com.kotlindiscord.kord.extensions.components.components -import com.kotlindiscord.kord.extensions.components.ephemeralButton -import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import dev.kord.common.Color import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission @@ -15,6 +7,14 @@ import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.interaction.followup.edit import dev.kord.core.entity.interaction.followup.EphemeralFollowupMessage import dev.kord.rest.request.KtorRequestException +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.converters.impl.optionalSnowflake +import dev.kordex.core.components.components +import dev.kordex.core.components.ephemeralButton +import dev.kordex.core.components.forms.ModalForm +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand import kotlinx.coroutines.flow.toList import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.TEST_GUILD_ID diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt index fad8ef8f..3c81fd64 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt @@ -1,18 +1,12 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.DISCORD_BLURPLE -import com.kotlindiscord.kord.extensions.components.components -import com.kotlindiscord.kord.extensions.components.linkButton -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand -import com.kotlindiscord.kord.extensions.time.TimestampType -import com.kotlindiscord.kord.extensions.time.toDiscord import dev.kord.rest.builder.message.create.MessageCreateBuilder import dev.kord.rest.builder.message.embed -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime -import org.hyacinthbots.lilybot.database.collections.UptimeCollection -import org.hyacinthbots.lilybot.internal.BuildInfo +import dev.kordex.core.DISCORD_BLURPLE +import dev.kordex.core.components.components +import dev.kordex.core.components.linkButton +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.publicSlashCommand import org.hyacinthbots.lilybot.utils.HYACINTH_GITHUB /** @@ -44,7 +38,7 @@ class InfoCommands : Extension() { title = "What is LilyBot?" description = "Lily is a FOSS multi-purpose bot for Discord created by " + "the HyacinthBots organization. " + - "Use `/info` to learn more, or `/invite` to get an invite link." + "Use `/about` to learn more, or `/invite` to get an invite link." field { name = "How do I configure Lily?" @@ -86,77 +80,6 @@ class InfoCommands : Extension() { } } - /** - * A command that creates an embed providing basic info about Lily and uptime data. - * - * @author NoComment1105 - * @author tempest15 - * @since 4.4.0 - */ - publicSlashCommand { - name = "info" - description = "Learn about Lily, and get uptime data!" - - action { - respond { - embed { - thumbnail { - url = event.kord.getSelf().avatar?.cdnUrl!!.toUrl() - } - title = "Info about LilyBot" - description = "Lily is a FOSS multi-purpose bot for Discord created by " + - "the HyacinthBots organization. " + - "Use `/help` for support or `/invite` to get an invite link." - - field { - name = "How can I support the continued development of Lily?" - value = "Lily is developed primarily by NoComment#6411 and tempest#4510 " + - "in our free time. Neither of us have resources to invest in hosting, " + - "so financial donations via [Buy Me a Coffee]" + - "(https://buymeacoffee.com/Hyacinthbots) help keep Lily afloat. Currently, we run" + - "lily on a Hetzner cloud server, which we can afford in our current situation. " + - "We will also have domain costs for our website.\n\n" + - "Contributions of code & documentation are also incredibly appreciated, " + - "and you can read our [contributing guide]" + - "($HYACINTH_GITHUB/LilyBot/blob/main/CONTRIBUTING.md) " + - "or [development guide]" + - "($HYACINTH_GITHUB/LilyBot/blob/main/docs/development-guide.md) " + - "to get started." - } - field { - name = "Version" - // To avoid IntelliJ shouting about build errors, use https://plugins.jetbrains.com/plugin/9407-pebble - value = - "${BuildInfo.LILY_VERSION} (${BuildInfo.BUILD_ID})" - inline = true - } - field { - name = "Up Since" - value = "${ - UptimeCollection().get()?.onTime?.toLocalDateTime(TimeZone.UTC) - ?.time.toString().split(".")[0] - } ${UptimeCollection().get()?.onTime?.toLocalDateTime(TimeZone.UTC)?.date} UTC\n " + - "(${UptimeCollection().get()?.onTime?.toDiscord(TimestampType.RelativeTime) ?: "??"})" - inline = true - } - field { - name = "Useful links" - value = - "Website: Coming Soon™️\n" + - "GitHub: ${HYACINTH_GITHUB}\n" + - "Buy Me a Coffee: https://buymeacoffee.com/HyacinthBots\n" + - "Twitter: https://twitter.com/HyacinthBots\n" + - "Email: `hyacinthbots@outlook.com`\n" + - "Discord: https://discord.gg/hy2329fcTZ" - } - color = DISCORD_BLURPLE - } - - buttons() - } - } - } - /** * A command that responds to the user with a link to invite Lily to their server. * diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt index de71d44a..5c186ff1 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt @@ -1,30 +1,5 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.DISCORD_BLACK -import com.kotlindiscord.kord.extensions.DISCORD_BLURPLE -import com.kotlindiscord.kord.extensions.DISCORD_WHITE -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.guildFor -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean -import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingColor -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalBoolean -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalChannel -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalColour -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalString -import com.kotlindiscord.kord.extensions.commands.converters.impl.snowflake -import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.components.components -import com.kotlindiscord.kord.extensions.components.ephemeralButton -import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.components.linkButton -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.utils.getJumpUrl -import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler -import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions @@ -40,6 +15,31 @@ import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.interaction.followup.EphemeralFollowupMessage import dev.kord.rest.builder.message.embed import dev.kord.rest.request.KtorRequestException +import dev.kordex.core.DISCORD_BLACK +import dev.kordex.core.DISCORD_BLURPLE +import dev.kordex.core.DISCORD_WHITE +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.guildFor +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.converters.impl.defaultingBoolean +import dev.kordex.core.commands.converters.impl.defaultingColor +import dev.kordex.core.commands.converters.impl.optionalBoolean +import dev.kordex.core.commands.converters.impl.optionalChannel +import dev.kordex.core.commands.converters.impl.optionalColour +import dev.kordex.core.commands.converters.impl.optionalString +import dev.kordex.core.commands.converters.impl.snowflake +import dev.kordex.core.commands.converters.impl.string +import dev.kordex.core.components.components +import dev.kordex.core.components.ephemeralButton +import dev.kordex.core.components.forms.ModalForm +import dev.kordex.core.components.linkButton +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.utils.getJumpUrl +import dev.kordex.core.utils.scheduling.Scheduler +import dev.kordex.core.utils.scheduling.Task import kotlinx.coroutines.flow.toList import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.AutoThreadingCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt index 936bf597..e870a726 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt @@ -1,19 +1,5 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.DISCORD_RED -import com.kotlindiscord.kord.extensions.DISCORD_YELLOW -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.channelType -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.channel -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator -import com.kotlindiscord.kord.extensions.pagination.pages.Page -import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.common.Locale import dev.kord.common.asJavaLocale import dev.kord.common.entity.ChannelType @@ -23,6 +9,20 @@ import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.entity.channel.NewsChannel import dev.kord.core.event.message.MessageCreateEvent +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.DISCORD_YELLOW +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.channelType +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.converters.impl.channel +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.extensions.event +import dev.kordex.core.pagination.EphemeralResponsePaginator +import dev.kordex.core.pagination.pages.Page +import dev.kordex.core.pagination.pages.Pages import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.NewsChannelPublishingCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt index b9dd03e3..102a3d32 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt @@ -1,21 +1,5 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.DISCORD_GREEN -import com.kotlindiscord.kord.extensions.DISCORD_RED -import com.kotlindiscord.kord.extensions.DISCORD_YELLOW -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.guildFor -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.components.components -import com.kotlindiscord.kord.extensions.components.ephemeralButton -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand -import com.kotlindiscord.kord.extensions.utils.dm -import com.kotlindiscord.kord.extensions.utils.getTopRole -import com.kotlindiscord.kord.extensions.utils.hasPermission import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.core.behavior.channel.createEmbed @@ -26,6 +10,22 @@ import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.rest.builder.message.embed import dev.kord.rest.request.KtorRequestException +import dev.kordex.core.DISCORD_GREEN +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.DISCORD_YELLOW +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.guildFor +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.converters.impl.string +import dev.kordex.core.components.components +import dev.kordex.core.components.ephemeralButton +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.extensions.publicSlashCommand +import dev.kordex.core.utils.dm +import dev.kordex.core.utils.getTopRole +import dev.kordex.core.utils.hasPermission import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt index ba1dea22..d455b00b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt @@ -1,31 +1,5 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.guildFor -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.stringChoice -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.boolean -import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingDuration -import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingOptionalDuration -import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean -import com.kotlindiscord.kord.extensions.commands.converters.impl.long -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalString -import com.kotlindiscord.kord.extensions.commands.converters.impl.user -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand -import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator -import com.kotlindiscord.kord.extensions.pagination.pages.Page -import com.kotlindiscord.kord.extensions.pagination.pages.Pages -import com.kotlindiscord.kord.extensions.time.TimestampType -import com.kotlindiscord.kord.extensions.time.toDiscord -import com.kotlindiscord.kord.extensions.utils.botHasPermissions -import com.kotlindiscord.kord.extensions.utils.dm -import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler -import com.kotlindiscord.kord.extensions.utils.scheduling.Task -import com.kotlindiscord.kord.extensions.utils.toDuration import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.common.entity.Snowflake @@ -38,6 +12,32 @@ import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.MessageCreateBuilder import dev.kord.rest.builder.message.embed import dev.kord.rest.request.KtorRequestException +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.guildFor +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.converters.impl.stringChoice +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.application.slash.publicSubCommand +import dev.kordex.core.commands.converters.impl.boolean +import dev.kordex.core.commands.converters.impl.coalescingDuration +import dev.kordex.core.commands.converters.impl.coalescingOptionalDuration +import dev.kordex.core.commands.converters.impl.defaultingBoolean +import dev.kordex.core.commands.converters.impl.long +import dev.kordex.core.commands.converters.impl.optionalString +import dev.kordex.core.commands.converters.impl.user +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.publicSlashCommand +import dev.kordex.core.pagination.EphemeralResponsePaginator +import dev.kordex.core.pagination.pages.Page +import dev.kordex.core.pagination.pages.Pages +import dev.kordex.core.time.TimestampType +import dev.kordex.core.time.toDiscord +import dev.kordex.core.utils.botHasPermissions +import dev.kordex.core.utils.dm +import dev.kordex.core.utils.scheduling.Scheduler +import dev.kordex.core.utils.scheduling.Task +import dev.kordex.core.utils.toDuration import kotlinx.datetime.Clock import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt index 4f83ff5e..ba1781fd 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt @@ -1,26 +1,5 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.DISCORD_BLACK -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.checks.hasPermissions -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommandContext -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean -import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingColor -import com.kotlindiscord.kord.extensions.commands.converters.impl.role -import com.kotlindiscord.kord.extensions.commands.converters.impl.snowflake -import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.components.components -import com.kotlindiscord.kord.extensions.components.ephemeralButton -import com.kotlindiscord.kord.extensions.components.ephemeralStringSelectMenu -import com.kotlindiscord.kord.extensions.components.linkButton -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.utils.getJumpUrl -import com.kotlindiscord.kord.extensions.utils.getTopRole import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions @@ -35,6 +14,27 @@ import dev.kord.core.entity.Message import dev.kord.core.entity.Role import dev.kord.core.event.interaction.GuildButtonInteractionCreateEvent import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_BLACK +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.checks.hasPermissions +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.EphemeralSlashCommandContext +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.converters.impl.defaultingBoolean +import dev.kordex.core.commands.converters.impl.defaultingColor +import dev.kordex.core.commands.converters.impl.role +import dev.kordex.core.commands.converters.impl.snowflake +import dev.kordex.core.commands.converters.impl.string +import dev.kordex.core.components.components +import dev.kordex.core.components.ephemeralButton +import dev.kordex.core.components.ephemeralStringSelectMenu +import dev.kordex.core.components.linkButton +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.extensions.event +import dev.kordex.core.utils.getJumpUrl +import dev.kordex.core.utils.getTopRole import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.toList @@ -100,13 +100,13 @@ class RoleMenu : Extension() { } // While we don't normally edit in components, in this case we need the message ID. - menuMessage!!.edit { + menuMessage.edit { val components = components { ephemeralButton { label = "Select roles" style = ButtonStyle.Primary - id = "role-menu${menuMessage!!.id}" + id = "role-menu${menuMessage.id}" action { } } @@ -116,7 +116,7 @@ class RoleMenu : Extension() { } RoleMenuCollection().setRoleMenu( - menuMessage!!.id, + menuMessage.id, channel.id, guild!!.id, mutableListOf(arguments.initialRole.id) @@ -154,7 +154,7 @@ class RoleMenu : Extension() { components { linkButton { label = "Jump to role menu" - url = menuMessage!!.getJumpUrl() + url = menuMessage.getJumpUrl() } } } @@ -371,7 +371,7 @@ class RoleMenu : Extension() { roles.add(newRole.id) } else { - utilsLogger.debug("skipped creating new roles") + utilsLogger.debug { "skipped creating new roles" } roles.add(existingRole.id) } } @@ -707,7 +707,7 @@ class RoleMenu : Extension() { return@action } - if (!config!!.subscribableRoles.contains(arguments.role.id)) { + if (!config.subscribableRoles.contains(arguments.role.id)) { respond { content = "That is not a subscribable role." } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt index 186b1bcc..62c87a6b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt @@ -1,16 +1,16 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.DISCORD_GREEN -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.time.TimestampType -import com.kotlindiscord.kord.extensions.time.toDiscord -import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler -import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.entity.channel.NewsChannel import dev.kord.core.event.gateway.ReadyEvent +import dev.kordex.core.DISCORD_GREEN +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.event +import dev.kordex.core.time.TimestampType +import dev.kordex.core.time.toDiscord +import dev.kordex.core.utils.scheduling.Scheduler +import dev.kordex.core.utils.scheduling.Task import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.Cleanups import org.hyacinthbots.lilybot.database.collections.UptimeCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt index 5e08401a..e2debbad 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt @@ -1,8 +1,8 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler -import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kordex.core.extensions.Extension +import dev.kordex.core.utils.scheduling.Scheduler +import dev.kordex.core.utils.scheduling.Task import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.HttpClient import io.ktor.client.request.get diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt index 61893e06..13b01b56 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt @@ -1,24 +1,5 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.DISCORD_BLURPLE -import com.kotlindiscord.kord.extensions.DISCORD_GREEN -import com.kotlindiscord.kord.extensions.DISCORD_RED -import com.kotlindiscord.kord.extensions.DISCORD_YELLOW -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.optionalStringChoice -import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.stringChoice -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalString -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalUser -import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand -import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator -import com.kotlindiscord.kord.extensions.pagination.pages.Page -import com.kotlindiscord.kord.extensions.pagination.pages.Pages -import com.kotlindiscord.kord.extensions.utils.suggestStringMap import dev.kord.common.Locale import dev.kord.common.asJavaLocale import dev.kord.common.entity.Permission @@ -30,6 +11,25 @@ import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_BLURPLE +import dev.kordex.core.DISCORD_GREEN +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.DISCORD_YELLOW +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.converters.impl.optionalStringChoice +import dev.kordex.core.commands.application.slash.converters.impl.stringChoice +import dev.kordex.core.commands.converters.impl.optionalString +import dev.kordex.core.commands.converters.impl.optionalUser +import dev.kordex.core.commands.converters.impl.string +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.extensions.publicSlashCommand +import dev.kordex.core.pagination.EphemeralResponsePaginator +import dev.kordex.core.pagination.pages.Page +import dev.kordex.core.pagination.pages.Pages +import dev.kordex.core.utils.suggestStringMap import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.TagsCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt index 9c212578..5b1e92c2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt @@ -6,20 +6,6 @@ package org.hyacinthbots.lilybot.extensions.util -import com.kotlindiscord.kord.extensions.DISCORD_FUCHSIA -import com.kotlindiscord.kord.extensions.checks.isInThread -import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommandContext -import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand -import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean -import com.kotlindiscord.kord.extensions.commands.converters.impl.member -import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.components.components -import com.kotlindiscord.kord.extensions.components.ephemeralButton -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand -import com.kotlindiscord.kord.extensions.extensions.event -import com.kotlindiscord.kord.extensions.utils.hasPermission import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions @@ -36,6 +22,20 @@ import dev.kord.core.entity.interaction.response.EphemeralMessageInteractionResp import dev.kord.core.event.channel.thread.ThreadUpdateEvent import dev.kord.core.exception.EntityNotFoundException import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_FUCHSIA +import dev.kordex.core.checks.isInThread +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.EphemeralSlashCommandContext +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.commands.converters.impl.defaultingBoolean +import dev.kordex.core.commands.converters.impl.member +import dev.kordex.core.commands.converters.impl.string +import dev.kordex.core.components.components +import dev.kordex.core.components.ephemeralButton +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.core.extensions.event +import dev.kordex.core.utils.hasPermission import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt index d79f7d77..75d93295 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt @@ -1,11 +1,11 @@ package org.hyacinthbots.lilybot.utils -import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PKMessage import dev.kord.core.behavior.UserBehavior import dev.kord.core.entity.Guild import dev.kord.core.entity.Message import dev.kord.core.entity.User import dev.kord.rest.builder.message.EmbedBuilder +import dev.kordex.modules.pluralkit.api.PKMessage /** * This is the base moderation embed for all moderation actions. This should be posted to the action log of a guild. diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt index aac26629..aff31835 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt @@ -1,8 +1,8 @@ package org.hyacinthbots.lilybot.utils -import com.kotlindiscord.kord.extensions.checks.guildFor -import com.kotlindiscord.kord.extensions.checks.types.CheckContext import dev.kord.common.entity.Snowflake +import dev.kordex.core.checks.guildFor +import dev.kordex.core.checks.types.CheckContext import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Constants.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Constants.kt index 0bcc99a4..33d960a4 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Constants.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Constants.kt @@ -1,8 +1,8 @@ package org.hyacinthbots.lilybot.utils -import com.kotlindiscord.kord.extensions.utils.env -import com.kotlindiscord.kord.extensions.utils.envOrNull import dev.kord.common.entity.Snowflake +import dev.kordex.core.utils.env +import dev.kordex.core.utils.envOrNull /** The Bot Token. */ val BOT_TOKEN = env("TOKEN") diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt index a6a7fab3..62cd9c4b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt @@ -1,13 +1,5 @@ package org.hyacinthbots.lilybot.utils -import com.kotlindiscord.kord.extensions.checks.anyGuild -import com.kotlindiscord.kord.extensions.checks.channelFor -import com.kotlindiscord.kord.extensions.checks.hasPermission -import com.kotlindiscord.kord.extensions.checks.types.CheckContext -import com.kotlindiscord.kord.extensions.checks.types.CheckContextWithCache -import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext -import com.kotlindiscord.kord.extensions.utils.botHasPermissions -import com.kotlindiscord.kord.extensions.utils.getTopRole import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.core.Kord @@ -20,6 +12,14 @@ import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.channel.NewsChannel import dev.kord.core.entity.channel.TextChannel import dev.kord.core.entity.channel.thread.ThreadChannel +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.channelFor +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.checks.types.CheckContext +import dev.kordex.core.checks.types.CheckContextWithCache +import dev.kordex.core.types.EphemeralInteractionContext +import dev.kordex.core.utils.botHasPermissions +import dev.kordex.core.utils.getTopRole import kotlinx.coroutines.flow.toList import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index eba5d13c..b36fd082 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -1,9 +1,5 @@ package org.hyacinthbots.lilybot.utils -import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.utils.hasPermission -import com.kotlindiscord.kord.extensions.utils.loadModule import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake import dev.kord.core.Kord @@ -11,6 +7,10 @@ import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.RoleBehavior import dev.kord.core.entity.Message import dev.kord.core.supplier.EntitySupplyStrategy +import dev.kordex.core.builders.ExtensibleBotBuilder +import dev.kordex.core.extensions.Extension +import dev.kordex.core.utils.hasPermission +import dev.kordex.core.utils.loadModule import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.flow.count import kotlinx.coroutines.runBlocking From dc16c086374565706efdca62ba569ad7c0f07125 Mon Sep 17 00:00:00 2001 From: NoComment Date: Sun, 25 Aug 2024 11:11:07 +0100 Subject: [PATCH 37/42] Update to KordEx 2.2.0, other dependencies --- .github/workflows/gradle.yml | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/release.yml | 2 +- gradle/libs.versions.toml | 8 +- .../org/hyacinthbots/lilybot/LilyBot.kt | 160 +++++++++--------- 5 files changed, 91 insertions(+), 83 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index bbe0d515..4954de0d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -25,7 +25,7 @@ jobs: run: chmod +x gradlew - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + uses: gradle/actions/setup-gradle@v4 - name: Build Project run: ./gradlew build --stacktrace diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c7c5c537..6b6b9837 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: run: chmod +x gradlew - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + uses: gradle/actions/setup-gradle@v4 - name: Build Project run: ./gradlew build --stacktrace diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cdc39293..9dbf6e9d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: run: chmod +x gradlew - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + uses: gradle/actions/setup-gradle@v4 - name: Build Project run: ./gradlew build --stacktrace diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 50e57a31..5572feb7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,12 +8,12 @@ grgit = "5.2.2" blossom = "2.1.0" # Libraries -kord-extensions = "2.1.0-20240820.102632-1" +kord-extensions = "2.2.0-20240824.203242-3" logging = "7.0.0" -logback = "1.5.6" -github-api = "1.323" +logback = "1.5.7" +github-api = "1.324" kmongo = "5.1.0" -docgenerator = "0.2.1-SNAPSHOT" +docgenerator = "0.2.2-SNAPSHOT" [libraries] kord-extensions-core = { module = "dev.kordex:kord-extensions", version.ref = "kord-extensions" } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt index 3b02244b..8b5ade7f 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt @@ -5,6 +5,8 @@ package org.hyacinthbots.lilybot import dev.kord.common.entity.Permission import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent +import dev.kord.rest.builder.message.actionRow +import dev.kord.rest.builder.message.embed import dev.kordex.core.ExtensibleBot import dev.kordex.core.checks.hasPermission import dev.kordex.core.time.TimestampType @@ -85,73 +87,79 @@ suspend fun main() { about { ephemeral = false - name = "Info about LilyBot" - - logoUrl = "https://github.com/HyacinthBots/LilyBot/blob/main/docs/lily-logo-transparent.png?raw=true" - - description = "Lily is a FOSS multi-purpose bot for Discord created by the HyacinthBots organization. " + - "Use `/help` for support or `/invite` to get an invite link." - - button { - name = "extensions.about.buttons.invite" - url = "https://discord.com/api/oauth2/authorize?client_id=876278900836139008" + - "&permissions=1151990787078&scope=bot%20applications.commands" - } - - button { - name = "Privacy Policy" - url = "$HYACINTH_GITHUB/LilyBot/blob/main/docs/privacy-policy.md" - } - - button { - name = "Terms of Service" - url = "$HYACINTH_GITHUB/.github/blob/main/terms-of-service.md" - } - - field { - name = "How can I support the continued development of Lily?" - - value = "Lily is developed primarily by NoComment#6411 and tempest#4510 " + - "in our free time. Neither of us have resources to invest in hosting, " + - "so financial donations via [Buy Me a Coffee]" + - "(https://buymeacoffee.com/Hyacinthbots) help keep Lily afloat. Currently, we run" + - "lily on a Hetzner cloud server, which we can afford in our current situation. " + - "We will also have domain costs for our website.\n\n" + - "Contributions of code & documentation are also incredibly appreciated, " + - "and you can read our [contributing guide]" + - "($HYACINTH_GITHUB/LilyBot/blob/main/CONTRIBUTING.md) " + - "or [development guide]" + - "($HYACINTH_GITHUB/LilyBot/blob/main/docs/development-guide.md) " + - "to get started." - } - - field { - name = "Version" - // To avoid IntelliJ shouting about build errors, use https://plugins.jetbrains.com/plugin/9407-pebble - value = - "${BuildInfo.LILY_VERSION} (${BuildInfo.BUILD_ID})" - inline = true - } - - field { - name = "Up Since" - value = "${ - UptimeCollection().get()?.onTime?.toLocalDateTime(TimeZone.UTC) - ?.time.toString().split(".")[0] - } ${UptimeCollection().get()?.onTime?.toLocalDateTime(TimeZone.UTC)?.date} UTC\n " + - "(${UptimeCollection().get()?.onTime?.toDiscord(TimestampType.RelativeTime) ?: "??"})" - inline = true - } - - field { - name = "Useful links" - value = - "Website: Coming Soon™️\n" + - "GitHub: ${HYACINTH_GITHUB}\n" + - "Buy Me a Coffee: https://buymeacoffee.com/HyacinthBots\n" + - "Twitter: https://twitter.com/HyacinthBots\n" + - "Email: `hyacinthbots@outlook.com`\n" + - "Discord: https://discord.gg/hy2329fcTZ" + general { + message { locale -> + embed { + title = "Info about LilyBot" + + thumbnail { + url = + "https://github.com/HyacinthBots/LilyBot/blob/main/docs/lily-logo-transparent.png?raw=true" + } + + description = + "Lily is a FOSS multi-purpose bot for Discord created by the HyacinthBots organization. " + + "Use `/help` for support or `/invite` to get an invite link." + field { + name = "How can I support the continued development of Lily?" + + value = "Lily is developed primarily by NoComment#6411 in their free time. Hyacinth " + + "doesn't have the resources to invest in hosting, so financial donations via " + + "[Buy Me a Coffee](https://buymeacoffee.com/Hyacinthbots) help keep Lily afloat. " + + "Currently, we run lily on a Hetzner cloud server, which we can afford in our " + + "current situation. We also have domain costs for our website.\n\nContributions of " + + "code & documentation are also incredibly appreciated, and you can read our " + + "[contributing guide]($HYACINTH_GITHUB/LilyBot/blob/main/CONTRIBUTING.md) or " + + "[development guide]($HYACINTH_GITHUB/LilyBot/blob/main/docs/development-guide.md) " + + "to get started." + } + + field { + name = "Version" + // To avoid IntelliJ shouting about build errors, use https://plugins.jetbrains.com/plugin/9407-pebble + value = "${BuildInfo.LILY_VERSION} (${BuildInfo.BUILD_ID})" + inline = true + } + + field { + name = "Up Since" + value = "${ + UptimeCollection().get()?.onTime?.toLocalDateTime(TimeZone.UTC) + ?.time.toString().split(".")[0] + } ${UptimeCollection().get()?.onTime?.toLocalDateTime(TimeZone.UTC)?.date} UTC\n " + + "(${UptimeCollection().get()?.onTime?.toDiscord(TimestampType.RelativeTime) ?: "??"})" + inline = true + } + + field { + name = "Useful links" + value = + "Website: Coming Soon™️\n" + + "GitHub: ${HYACINTH_GITHUB}\n" + + "Buy Me a Coffee: https://buymeacoffee.com/HyacinthBots\n" + + "Twitter: https://twitter.com/HyacinthBots\n" + + "Email: `hyacinthbots@outlook.com`\n" + + "Discord: https://discord.gg/hy2329fcTZ" + } + } + + actionRow { + linkButton( + "https://discord.com/api/oauth2/authorize?client_id=876278900836139008&" + + "permissions=1151990787078&scope=bot%20applications.commands" + ) { + label = "extensions.about.buttons.invite" + } + + linkButton("$HYACINTH_GITHUB/LilyBot/blob/main/docs/privacy-policy.md") { + label = "Privacy Policy" + } + + linkButton("$HYACINTH_GITHUB/.github/blob/main/terms-of-service.md") { + label = "Terms of Service" + } + } + } } } @@ -186,17 +194,17 @@ suspend fun main() { The welcome channel extension allows users to designate a YAML file to create a channel with a variety of pre-built blocks. */ - welcomeChannel(WelcomeChannelCollection()) { - staffCommandCheck { - hasPermission(Permission.BanMembers) - } + welcomeChannel(WelcomeChannelCollection()) { + staffCommandCheck { + hasPermission(Permission.BanMembers) + } - getLogChannel { _, guild -> - getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild) - } + getLogChannel { _, guild -> + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild) + } - refreshDuration = 5.minutes - } + refreshDuration = 5.minutes + } /* The anti-phishing extension automatically deletes and logs scam links. It also allows users to check links From 7d2b6ff8bf74579b6b968990f7ec7ad3c6c45629 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Sun, 25 Aug 2024 11:43:41 +0100 Subject: [PATCH 38/42] Package restructure (#417) * Refactor package structure attempt 2 * Fix package references :D * Split out config view and clear --- .editorconfig | 2 - detekt.yml | 3 + docs/commands.md | 30 +- .../org/hyacinthbots/lilybot/LilyBot.kt | 50 +- .../lilybot/extensions/config/Config.kt | 892 ------------------ .../extensions/config/ConfigExtension.kt | 32 + .../config/commands/ConfigClearCommand.kt | 150 +++ .../config/commands/ConfigViewCommand.kt | 180 ++++ .../extensions/logging/config/LoggingArgs.kt | 43 + .../logging/config/LoggingCommand.kt | 231 +++++ .../logging/config/PublicLoggingModal.kt | 24 + .../events}/GuildLogging.kt | 2 +- .../{ => logging}/events/MemberLogging.kt | 2 +- .../{ => logging}/events/MessageDelete.kt | 2 +- .../{ => logging}/events/MessageEdit.kt | 2 +- .../{ => commands}/ClearCommands.kt | 2 +- .../{ => commands}/LockingCommands.kt | 2 +- .../commands}/ModUtilities.kt | 2 +- .../{ => commands}/ModerationCommands.kt | 3 +- .../moderation/{ => commands}/Report.kt | 2 +- .../moderation/config/ModerationArgs.kt | 51 + .../moderation/config/ModerationCommand.kt | 177 ++++ .../{ => utils}/ModerationActions.kt | 2 +- .../{events => threads}/AutoThreading.kt | 2 +- .../{events => threads}/ModThreadInviting.kt | 2 +- .../{util => threads}/ThreadControl.kt | 2 +- .../commands}/GalleryChannel.kt | 2 +- .../{util => utils/commands}/Github.kt | 2 +- .../commands}/GuildAnnouncements.kt | 2 +- .../{util => utils/commands}/InfoCommands.kt | 2 +- .../commands}/NewsChannelPublishing.kt | 2 +- .../commands}/PublicUtilities.kt | 2 +- .../{util => utils/commands}/Reminders.kt | 2 +- .../{util => utils/commands}/RoleMenu.kt | 2 +- .../{util => utils/commands}/StartupHooks.kt | 2 +- .../{util => utils/commands}/StatusPing.kt | 2 +- .../{util => utils/commands}/Tags.kt | 2 +- .../extensions/utils/config/UtilityArgs.kt | 11 + .../extensions/utils/config/UtilityCommand.kt | 87 ++ 39 files changed, 1054 insertions(+), 958 deletions(-) delete mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigViewCommand.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingArgs.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/PublicLoggingModal.kt rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{config => logging/events}/GuildLogging.kt (95%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{ => logging}/events/MemberLogging.kt (98%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{ => logging}/events/MessageDelete.kt (98%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{ => logging}/events/MessageEdit.kt (98%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/{ => commands}/ClearCommands.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/{ => commands}/LockingCommands.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => moderation/commands}/ModUtilities.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/{ => commands}/ModerationCommands.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/{ => commands}/Report.kt (99%) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/{ => utils}/ModerationActions.kt (54%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{events => threads}/AutoThreading.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{events => threads}/ModThreadInviting.kt (96%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => threads}/ThreadControl.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/GalleryChannel.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/Github.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/GuildAnnouncements.kt (98%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/InfoCommands.kt (98%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/NewsChannelPublishing.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/PublicUtilities.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/Reminders.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/RoleMenu.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/StartupHooks.kt (97%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/StatusPing.kt (93%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{util => utils/commands}/Tags.kt (99%) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityArgs.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityCommand.kt diff --git a/.editorconfig b/.editorconfig index 6887b9a8..04a71793 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,8 +12,6 @@ indent_style = tab [*.kt] indent_style = tab -ij_kotlin_name_count_to_use_star_import = 99999 -ij_kotlin_name_count_to_use_star_import_for_members = 99999 [*.properties] indent_style = space diff --git a/detekt.yml b/detekt.yml index 4fa127fc..2f5db37a 100644 --- a/detekt.yml +++ b/detekt.yml @@ -175,6 +175,7 @@ formatting: active: true autoCorrect: true NoWildcardImports: + active: false packagesToUseImportOnDemandProperty: "" PackageName: active: true @@ -361,3 +362,5 @@ style: active: true UseRequireNotNull: active: true + WildcardImport: + active: false diff --git a/docs/commands.md b/docs/commands.md index 3820ecbe..37d4a9ce 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -108,6 +108,21 @@ None * `before` - The ID of the message to clear before - Snowflake * `author` - The author of the messages to clear - Optional User +--- +### Command name: `config logging` +**Description**: Configure Lily's logging system + +**Required Member Permissions**: Manage Server + +* **Arguments**: + * `enable-delete-logs` - Enable logging of message deletions - Boolean + * `enable-edit-logs` - Enable logging of message edits - Boolean + * `enable-member-logging` - Enable logging of members joining and leaving the guild - Boolean + * `enable-public-member-logging` - Enable logging of members joining and leaving the guild with a public message and ping if enabled - Boolean + * `message-logs` - The channel for logging message deletions - Optional Channel + * `member-log` - The channel for logging members joining and leaving the guild - Optional Channel + * `public-member-log` - The channel for the public logging of members joining and leaving the guild - Optional Channel + --- ### Command name: `config moderation` **Description**: Configure Lily's moderation system @@ -124,21 +139,6 @@ None * `ban-dm-message` - A custom message to send to users when they are banned. - Optional String * `auto-invite-moderator-role` - Silently ping moderators to invite them to new threads. - Optional Boolean ---- -### Command name: `config logging` -**Description**: Configure Lily's logging system - -**Required Member Permissions**: Manage Server - -* **Arguments**: - * `enable-delete-logs` - Enable logging of message deletions - Boolean - * `enable-edit-logs` - Enable logging of message edits - Boolean - * `enable-member-logging` - Enable logging of members joining and leaving the guild - Boolean - * `enable-public-member-logging` - Enable logging of members joining and leaving the guild with a public message and ping if enabled - Boolean - * `message-logs` - The channel for logging message deletions - Optional Channel - * `member-log` - The channel for logging members joining and leaving the guild - Optional Channel - * `public-member-log` - The channel for the public logging of members joining and leaving the guild - Optional Channel - --- ### Command name: `config utility` **Description**: Configure Lily's utility settings diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt index 8b5ade7f..376ba8c0 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt @@ -25,31 +25,31 @@ import org.hyacinthbots.docgenerator.enums.SupportedFileFormat import org.hyacinthbots.lilybot.database.collections.UptimeCollection import org.hyacinthbots.lilybot.database.collections.WelcomeChannelCollection import org.hyacinthbots.lilybot.database.storage.MongoDBDataAdapter -import org.hyacinthbots.lilybot.extensions.config.Config +import org.hyacinthbots.lilybot.extensions.config.ConfigExtension import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.GuildLogging -import org.hyacinthbots.lilybot.extensions.events.AutoThreading -import org.hyacinthbots.lilybot.extensions.events.MemberLogging -import org.hyacinthbots.lilybot.extensions.events.MessageDelete -import org.hyacinthbots.lilybot.extensions.events.MessageEdit -import org.hyacinthbots.lilybot.extensions.events.ModThreadInviting -import org.hyacinthbots.lilybot.extensions.moderation.ClearCommands -import org.hyacinthbots.lilybot.extensions.moderation.LockingCommands -import org.hyacinthbots.lilybot.extensions.moderation.ModerationCommands -import org.hyacinthbots.lilybot.extensions.moderation.Report -import org.hyacinthbots.lilybot.extensions.util.GalleryChannel -import org.hyacinthbots.lilybot.extensions.util.Github -import org.hyacinthbots.lilybot.extensions.util.GuildAnnouncements -import org.hyacinthbots.lilybot.extensions.util.InfoCommands -import org.hyacinthbots.lilybot.extensions.util.ModUtilities -import org.hyacinthbots.lilybot.extensions.util.NewsChannelPublishing -import org.hyacinthbots.lilybot.extensions.util.PublicUtilities -import org.hyacinthbots.lilybot.extensions.util.Reminders -import org.hyacinthbots.lilybot.extensions.util.RoleMenu -import org.hyacinthbots.lilybot.extensions.util.StartupHooks -import org.hyacinthbots.lilybot.extensions.util.StatusPing -import org.hyacinthbots.lilybot.extensions.util.Tags -import org.hyacinthbots.lilybot.extensions.util.ThreadControl +import org.hyacinthbots.lilybot.extensions.logging.events.GuildLogging +import org.hyacinthbots.lilybot.extensions.logging.events.MemberLogging +import org.hyacinthbots.lilybot.extensions.logging.events.MessageDelete +import org.hyacinthbots.lilybot.extensions.logging.events.MessageEdit +import org.hyacinthbots.lilybot.extensions.moderation.commands.ClearCommands +import org.hyacinthbots.lilybot.extensions.moderation.commands.LockingCommands +import org.hyacinthbots.lilybot.extensions.moderation.commands.ModUtilities +import org.hyacinthbots.lilybot.extensions.moderation.commands.ModerationCommands +import org.hyacinthbots.lilybot.extensions.moderation.commands.Report +import org.hyacinthbots.lilybot.extensions.threads.AutoThreading +import org.hyacinthbots.lilybot.extensions.threads.ModThreadInviting +import org.hyacinthbots.lilybot.extensions.threads.ThreadControl +import org.hyacinthbots.lilybot.extensions.utils.commands.GalleryChannel +import org.hyacinthbots.lilybot.extensions.utils.commands.Github +import org.hyacinthbots.lilybot.extensions.utils.commands.GuildAnnouncements +import org.hyacinthbots.lilybot.extensions.utils.commands.InfoCommands +import org.hyacinthbots.lilybot.extensions.utils.commands.NewsChannelPublishing +import org.hyacinthbots.lilybot.extensions.utils.commands.PublicUtilities +import org.hyacinthbots.lilybot.extensions.utils.commands.Reminders +import org.hyacinthbots.lilybot.extensions.utils.commands.RoleMenu +import org.hyacinthbots.lilybot.extensions.utils.commands.StartupHooks +import org.hyacinthbots.lilybot.extensions.utils.commands.StatusPing +import org.hyacinthbots.lilybot.extensions.utils.commands.Tags import org.hyacinthbots.lilybot.internal.BuildInfo import org.hyacinthbots.lilybot.utils.BOT_TOKEN import org.hyacinthbots.lilybot.utils.ENVIRONMENT @@ -167,7 +167,7 @@ suspend fun main() { extensions { add(::AutoThreading) add(::ClearCommands) - add(::Config) + add(::ConfigExtension) add(::GalleryChannel) add(::Github) add(::GuildAnnouncements) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt deleted file mode 100644 index d1118e86..00000000 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ /dev/null @@ -1,892 +0,0 @@ -package org.hyacinthbots.lilybot.extensions.config - -import dev.kord.common.entity.Permission -import dev.kord.core.behavior.channel.createMessage -import dev.kord.core.behavior.getChannelOfOrNull -import dev.kord.core.behavior.interaction.modal -import dev.kord.core.entity.channel.TextChannel -import dev.kord.rest.builder.message.EmbedBuilder -import dev.kord.rest.builder.message.embed -import dev.kordex.core.checks.anyGuild -import dev.kordex.core.checks.hasPermission -import dev.kordex.core.commands.Arguments -import dev.kordex.core.commands.application.slash.converters.impl.stringChoice -import dev.kordex.core.commands.application.slash.ephemeralSubCommand -import dev.kordex.core.commands.converters.impl.boolean -import dev.kordex.core.commands.converters.impl.coalescingOptionalDuration -import dev.kordex.core.commands.converters.impl.optionalBoolean -import dev.kordex.core.commands.converters.impl.optionalChannel -import dev.kordex.core.commands.converters.impl.optionalRole -import dev.kordex.core.commands.converters.impl.optionalString -import dev.kordex.core.components.forms.ModalForm -import dev.kordex.core.extensions.Extension -import dev.kordex.core.extensions.ephemeralSlashCommand -import dev.kordex.core.utils.botHasPermissions -import dev.kordex.modules.dev.unsafe.annotations.UnsafeAPI -import dev.kordex.modules.dev.unsafe.commands.slash.InitialSlashCommandResponse -import dev.kordex.modules.dev.unsafe.extensions.unsafeSubCommand -import kotlinx.datetime.Clock -import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection -import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection -import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection -import org.hyacinthbots.lilybot.database.entities.LoggingConfigData -import org.hyacinthbots.lilybot.database.entities.ModerationConfigData -import org.hyacinthbots.lilybot.database.entities.PublicMemberLogData -import org.hyacinthbots.lilybot.database.entities.UtilityConfigData -import org.hyacinthbots.lilybot.utils.canPingRole -import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms -import org.hyacinthbots.lilybot.utils.interval -import org.hyacinthbots.lilybot.utils.trimmedContents - -class Config : Extension() { - override val name: String = "config" - - @OptIn(UnsafeAPI::class) - override suspend fun setup() { - ephemeralSlashCommand { - name = "config" - description = "Configure Lily's settings" - - ephemeralSubCommand(::ModerationArgs) { - name = "moderation" - description = "Configure Lily's moderation system" - - requirePermission(Permission.ManageGuild) - - check { - anyGuild() - hasPermission(Permission.ManageGuild) - } - - action { - val moderationConfig = ModerationConfigCollection().getConfig(guild!!.id) - if (moderationConfig != null) { - respond { - content = "You already have a moderation configuration set. " + - "Please clear it before attempting to set a new one." - } - return@action - } - - if (!arguments.enabled) { - ModerationConfigCollection().setConfig( - ModerationConfigData( - guild!!.id, - false, - null, - null, - null, - null, - null, - null, - null - ) - ) - respond { - content = "Moderation system disabled." - } - return@action - } - - if ( - arguments.moderatorRole != null && arguments.modActionLog == null || - arguments.moderatorRole == null && arguments.modActionLog != null - ) { - respond { - content = - "You must set both the moderator role and the action log channel to use the moderation configuration." - } - return@action - } - - if (!canPingRole(arguments.moderatorRole, guild!!.id, this@ephemeralSubCommand.kord)) { - respond { - content = - "I cannot use the role: ${arguments.moderatorRole!!.mention}, because it is not mentionable by " + - "regular users. Please enable this in the role settings, or use a different role." - } - return@action - } - - val modActionLog: TextChannel? - if (arguments.enabled && arguments.modActionLog != null) { - modActionLog = guild!!.getChannelOfOrNull(arguments.modActionLog!!.id) - if (modActionLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { - respond { - content = "The mod action log you've selected is invalid, or I can't view it. " + - "Please attempt to resolve this and try again." - } - return@action - } - } - - suspend fun EmbedBuilder.moderationEmbed() { - title = "Configuration: Moderation" - field { - name = "Moderators" - value = arguments.moderatorRole?.mention ?: "Disabled" - } - field { - name = "Action log" - value = arguments.modActionLog?.mention ?: "Disabled" - } - field { - name = "Log publicly" - value = when (arguments.logPublicly) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } - } - field { - name = "Quick timeout length" - value = arguments.quickTimeoutLength.interval() ?: "No quick timeout length set" - } - field { - name = "Warning Auto-punishments" - value = when (arguments.warnAutoPunishments) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } - } - field { - name = "Ban DM Message" - value = arguments.banDmMessage ?: "No custom Ban DM message set" - } - field { - name = "Auto-invite Moderator Role" - value = when (arguments.autoInviteModeratorRole) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } - } - footer { - text = "Configured by ${user.asUserOrNull()?.username}" - } - } - - respond { - embed { - moderationEmbed() - } - } - - ModerationConfigCollection().setConfig( - ModerationConfigData( - guild!!.id, - arguments.enabled, - arguments.modActionLog?.id, - arguments.moderatorRole?.id, - arguments.quickTimeoutLength, - arguments.warnAutoPunishments, - arguments.logPublicly, - arguments.banDmMessage, - arguments.autoInviteModeratorRole - ) - ) - - val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) - - if (utilityLog == null) { - respond { - content = "Consider setting a utility config to log changes to configurations." - } - return@action - } - - utilityLog.createMessage { - embed { - moderationEmbed() - } - } - } - } - - unsafeSubCommand(::LoggingArgs) { - name = "logging" - description = "Configure Lily's logging system" - - initialResponse = InitialSlashCommandResponse.None - - requirePermission(Permission.ManageGuild) - - check { - anyGuild() - hasPermission(Permission.ManageGuild) - } - - action { - val loggingConfig = LoggingConfigCollection().getConfig(guild!!.id) - if (loggingConfig != null) { - ackEphemeral() - respondEphemeral { - content = "You already have a logging configuration set. " + - "Please clear it before attempting to set a new one." - } - return@action - } - - if (arguments.enableMemberLogging && arguments.memberLog == null) { - ackEphemeral() - respondEphemeral { - content = "You must specify a channel to log members joining and leaving to!" - } - return@action - } else if ((arguments.enableMessageDeleteLogs || arguments.enableMessageEditLogs) && - arguments.messageLogs == null - ) { - ackEphemeral() - respondEphemeral { content = "You must specify a channel to log deleted/edited messages to!" } - return@action - } else if (arguments.enablePublicMemberLogging && arguments.publicMemberLog == null) { - ackEphemeral() - respondEphemeral { - content = "You must specify a channel to publicly log members joining and leaving to!" - } - return@action - } - - val memberLog: TextChannel? - if (arguments.enableMemberLogging && arguments.memberLog != null) { - memberLog = guild!!.getChannelOfOrNull(arguments.memberLog!!.id) - if (memberLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { - ackEphemeral() - respondEphemeral { - content = "The member log you've selected is invalid, or I can't view it. " + - "Please attempt to resolve this and try again." - } - return@action - } - } - - val messageLog: TextChannel? - if ((arguments.enableMessageDeleteLogs || arguments.enableMessageEditLogs) && arguments.messageLogs != null) { - messageLog = guild!!.getChannelOfOrNull(arguments.messageLogs!!.id) - if (messageLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { - ackEphemeral() - respondEphemeral { - content = "The message log you've selected is invalid, or I can't view it. " + - "Please attempt to resolve this and try again." - } - return@action - } - } - - val publicMemberLog: TextChannel? - if (arguments.enablePublicMemberLogging && arguments.publicMemberLog != null) { - publicMemberLog = guild!!.getChannelOfOrNull(arguments.publicMemberLog!!.id) - if (publicMemberLog?.botHasPermissions( - Permission.ViewChannel, - Permission.SendMessages - ) != true - ) { - ackEphemeral() - respondEphemeral { - content = "The public member log you've selected is invalid, or I can't view it. " + - "Please attempt to resolve this and try again." - } - return@action - } - } - - suspend fun EmbedBuilder.loggingEmbed() { - title = "Configuration: Logging" - field { - name = "Message Delete Logs" - value = if (arguments.enableMessageDeleteLogs && arguments.messageLogs != null) { - arguments.messageLogs!!.mention - } else { - "Disabled" - } - } - field { - name = "Message Edit Logs" - value = if (arguments.enableMessageEditLogs && arguments.messageLogs != null) { - arguments.messageLogs!!.mention - } else { - "Disabled" - } - } - field { - name = "Member Logs" - value = if (arguments.enableMemberLogging && arguments.memberLog != null) { - arguments.memberLog!!.mention - } else { - "Disabled" - } - } - - field { - name = "Public Member logs" - value = if (arguments.enablePublicMemberLogging && arguments.publicMemberLog != null) { - arguments.publicMemberLog!!.mention - } else { - "Disabled" - } - } - if (arguments.enableMemberLogging && arguments.publicMemberLog != null) { - val config = LoggingConfigCollection().getConfig(guild!!.id) - if (config != null) { - field { - name = "Join Message" - value = config.publicMemberLogData?.joinMessage.trimmedContents(256)!! - } - field { - name = "Leave Message" - value = config.publicMemberLogData?.leaveMessage.trimmedContents(256)!! - } - field { - name = "Ping on join" - value = config.publicMemberLogData?.pingNewUsers.toString() - } - } - } - - footer { - text = "Configured by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } - - var publicMemberLogData: PublicMemberLogData? = null - if (arguments.enablePublicMemberLogging) { - val modalObj = LoggingModal() - - this@unsafeSubCommand.componentRegistry.register(modalObj) - - event.interaction.modal( - modalObj.title, - modalObj.id - ) { - modalObj.applyToBuilder(this, getLocale(), null) - } - - modalObj.awaitCompletion { modalSubmitInteraction -> - interactionResponse = modalSubmitInteraction?.deferEphemeralMessageUpdate() - } - - publicMemberLogData = PublicMemberLogData( - modalObj.ping.value == "yes", - modalObj.joinMessage.value, - modalObj.leaveMessage.value - ) - } - - LoggingConfigCollection().setConfig( - LoggingConfigData( - guild!!.id, - arguments.enableMessageDeleteLogs, - arguments.enableMessageEditLogs, - arguments.messageLogs?.id, - arguments.enableMemberLogging, - arguments.memberLog?.id, - arguments.enablePublicMemberLogging, - arguments.publicMemberLog?.id, - publicMemberLogData - ) - ) - - if (!arguments.enablePublicMemberLogging) { - ackEphemeral() - } - respondEphemeral { - embed { loggingEmbed() } - } - - val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!) - - if (utilityLog == null) { - respondEphemeral { - content = "Consider setting a utility config to log changes to configurations." - } - return@action - } - - utilityLog.createMessage { - embed { - loggingEmbed() - } - } - } - } - - ephemeralSubCommand(::UtilityArgs) { - name = "utility" - description = "Configure Lily's utility settings" - - requirePermission(Permission.ManageGuild) - - check { - anyGuild() - hasPermission(Permission.ManageGuild) - } - - action { - val utilityConfig = UtilityConfigCollection().getConfig(guild!!.id) - - if (utilityConfig != null) { - respond { - content = "You already have a utility configuration set. " + - "Please clear it before attempting to set a new one." - } - return@action - } - - var utilityLog: TextChannel? = null - if (arguments.utilityLogChannel != null) { - utilityLog = guild!!.getChannelOfOrNull(arguments.utilityLogChannel!!.id) - if (utilityLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { - respond { - content = "The utility log you've selected is invalid, or I can't view it. " + - "Please attempt to resolve this and try again." - } - return@action - } - } - - suspend fun EmbedBuilder.utilityEmbed() { - title = "Configuration: Utility" - field { - name = "Utility Log" - value = if (arguments.utilityLogChannel != null) { - "${arguments.utilityLogChannel!!.mention} ${arguments.utilityLogChannel!!.data.name.value}" - } else { - "Disabled" - } - } - - footer { - text = "Configured by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } - - respond { - embed { - utilityEmbed() - } - } - - UtilityConfigCollection().setConfig( - UtilityConfigData( - guild!!.id, - arguments.utilityLogChannel?.id - ) - ) - - utilityLog?.createMessage { - embed { - utilityEmbed() - } - } - } - } - - ephemeralSubCommand(::ClearArgs) { - name = "clear" - description = "Clear a config type" - - requirePermission(Permission.ManageGuild) - - check { - anyGuild() - hasPermission(Permission.ManageGuild) - } - - action { - suspend fun logClear() { - val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) - - if (utilityLog == null) { - respond { - content = "Consider setting a utility config to log changes to configurations." - } - return - } - - utilityLog.createMessage { - embed { - title = "Configuration Cleared: ${arguments.config[0]}${ - arguments.config.substring(1, arguments.config.length).lowercase() - }" - footer { - text = "Config cleared by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } - } - } - - when (arguments.config) { - ConfigType.MODERATION.name -> { - ModerationConfigCollection().getConfig(guild!!.id) ?: run { - respond { - content = "No moderation configuration exists to clear!" - } - return@action - } - - logClear() - - ModerationConfigCollection().clearConfig(guild!!.id) - respond { - embed { - title = "Config cleared: Moderation" - footer { - text = "Config cleared by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } - } - } - - ConfigType.LOGGING.name -> { - LoggingConfigCollection().getConfig(guild!!.id) ?: run { - respond { - content = "No logging configuration exists to clear!" - } - return@action - } - - logClear() - - LoggingConfigCollection().clearConfig(guild!!.id) - respond { - embed { - title = "Config cleared: Logging" - footer { - text = "Config cleared by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } - } - } - - ConfigType.UTILITY.name -> { - UtilityConfigCollection().getConfig(guild!!.id) ?: run { - respond { - content = "No utility configuration exists to clear" - } - return@action - } - - logClear() - - UtilityConfigCollection().clearConfig(guild!!.id) - respond { - embed { - title = "Config cleared: Utility" - footer { - text = "Config cleared by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } - } - } - - ConfigType.ALL.name -> { - ModerationConfigCollection().clearConfig(guild!!.id) - LoggingConfigCollection().clearConfig(guild!!.id) - UtilityConfigCollection().clearConfig(guild!!.id) - respond { - embed { - title = "All configs cleared" - footer { - text = "Configs cleared by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } - } - } - } - } - } - - ephemeralSubCommand(::ViewArgs) { - name = "view" - description = "View the current config that you have set" - - requirePermission(Permission.ManageGuild) - - check { - anyGuild() - hasPermission(Permission.ManageGuild) - } - - action { - when (arguments.config) { - ConfigType.MODERATION.name -> { - val config = ModerationConfigCollection().getConfig(guild!!.id) - if (config == null) { - respond { - content = "There is no moderation config for this guild" - } - return@action - } - - respond { - embed { - title = "Current moderation config" - description = "This is the current moderation config for this guild" - field { - name = "Enabled/Disabled" - value = if (config.enabled) "Enabled" else "Disabled" - } - field { - name = "Moderators" - value = config.role?.let { guild!!.getRoleOrNull(it)?.mention } ?: "Disabled" - } - field { - name = "Action log" - value = config.channel?.let { guild!!.getChannelOrNull(it)?.mention } ?: "Disabled" - } - field { - name = "Log publicly" - value = when (config.publicLogging) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } - } - field { - name = "Quick timeout length" - value = config.quickTimeoutLength.interval() ?: "No quick timeout length set" - } - field { - name = "Warning Auto-punishments" - value = when (config.autoPunishOnWarn) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } - } - field { - name = "Ban DM Message" - value = config.banDmMessage ?: "No custom Ban DM message set" - } - field { - name = "Auto-invite Moderator Role" - value = when (config.autoInviteModeratorRole) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } - } - timestamp = Clock.System.now() - } - } - } - - ConfigType.LOGGING.name -> { - val config = LoggingConfigCollection().getConfig(guild!!.id) - if (config == null) { - respond { - content = "There is no logging config for this guild" - } - return@action - } - - respond { - embed { - title = "Current logging config" - description = "This is the current logging config for this guild" - field { - name = "Message delete logs" - value = if (config.enableMessageDeleteLogs) { - "Enabled\n" + - "* ${guild!!.getChannelOrNull(config.messageChannel!!)?.mention ?: "Unable to get channel mention"} (" + - "${guild!!.getChannelOrNull(config.messageChannel)?.name ?: "Unable to get channel name"})" - } else { - "Disabled" - } - } - field { - name = "Message edit logs" - value = if (config.enableMessageEditLogs) { - "Enabled\n" + - "* ${guild!!.getChannelOrNull(config.messageChannel!!)?.mention ?: "Unable to get channel mention"} (" + - "${guild!!.getChannelOrNull(config.messageChannel)?.name ?: "Unable to get channel mention"})" - } else { - "Disabled" - } - } - field { - name = "Member logs" - value = if (config.enableMemberLogs) { - "Enabled\n" + - "* ${guild!!.getChannelOrNull(config.memberLog!!)?.mention ?: "Unable to get channel mention"} (" + - "${guild!!.getChannelOrNull(config.memberLog)?.name ?: "Unable to get channel mention."})" - } else { - "Disabled" - } - } - timestamp = Clock.System.now() - } - } - } - - ConfigType.UTILITY.name -> { - val config = UtilityConfigCollection().getConfig(guild!!.id) - if (config == null) { - respond { - content = "There is no utility config for this guild" - } - return@action - } - - respond { - embed { - title = "Current utility config" - description = "This is the current utility config for this guild" - field { - name = "Channel" - value = - "${ - config.utilityLogChannel?.let { guild!!.getChannelOrNull(it)?.mention } ?: "None" - } ${config.utilityLogChannel?.let { guild!!.getChannelOrNull(it)?.name } ?: ""}" - } - timestamp = Clock.System.now() - } - } - } - } - } - } - } - } - - inner class ModerationArgs : Arguments() { - val enabled by boolean { - name = "enable-moderation" - description = "Whether to enable the moderation system" - } - - val moderatorRole by optionalRole { - name = "moderator-role" - description = "The role of your moderators, used for pinging in message logs." - } - - val modActionLog by optionalChannel { - name = "action-log" - description = "The channel used to store moderator actions." - } - - val quickTimeoutLength by coalescingOptionalDuration { - name = "quick-timeout-length" - description = "The length of timeouts to use for quick timeouts" - } - - val warnAutoPunishments by optionalBoolean { - name = "warn-auto-punishments" - description = "Whether to automatically punish users for reach a certain threshold on warns" - } - - val logPublicly by optionalBoolean { - name = "log-publicly" - description = "Whether to log moderation publicly or not." - } - - val banDmMessage by optionalString { - name = "ban-dm-message" - description = "A custom message to send to users when they are banned." - } - - val autoInviteModeratorRole by optionalBoolean { - name = "auto-invite-moderator-role" - description = "Silently ping moderators to invite them to new threads." - } - } - - inner class LoggingArgs : Arguments() { - val enableMessageDeleteLogs by boolean { - name = "enable-delete-logs" - description = "Enable logging of message deletions" - } - - val enableMessageEditLogs by boolean { - name = "enable-edit-logs" - description = "Enable logging of message edits" - } - - val enableMemberLogging by boolean { - name = "enable-member-logging" - description = "Enable logging of members joining and leaving the guild" - } - - val enablePublicMemberLogging by boolean { - name = "enable-public-member-logging" - description = - "Enable logging of members joining and leaving the guild with a public message and ping if enabled" - } - - val messageLogs by optionalChannel { - name = "message-logs" - description = "The channel for logging message deletions" - } - - val memberLog by optionalChannel { - name = "member-log" - description = "The channel for logging members joining and leaving the guild" - } - - val publicMemberLog by optionalChannel { - name = "public-member-log" - description = "The channel for the public logging of members joining and leaving the guild" - } - } - - inner class UtilityArgs : Arguments() { - val utilityLogChannel by optionalChannel { - name = "utility-log" - description = "The channel to log various utility actions too." - } - } - - inner class ClearArgs : Arguments() { - val config by stringChoice { - name = "config-type" - description = "The type of config to clear" - choices = mutableMapOf( - "moderation" to ConfigType.MODERATION.name, - "logging" to ConfigType.LOGGING.name, - "utility" to ConfigType.UTILITY.name, - "all" to ConfigType.ALL.name - ) - } - } - - inner class ViewArgs : Arguments() { - val config by stringChoice { - name = "config-type" - description = "The type of config to clear" - choices = mutableMapOf( - "moderation" to ConfigType.MODERATION.name, - "logging" to ConfigType.LOGGING.name, - "utility" to ConfigType.UTILITY.name, - ) - } - } - - inner class LoggingModal : ModalForm() { - override var title = "Public logging configuration" - - val joinMessage = paragraphText { - label = "What would you like sent when a user joins" - placeholder = "Welcome to the server!" - required = true - } - - val leaveMessage = paragraphText { - label = "What would you like sent when a user leaves" - placeholder = "Adiós amigo!" - required = true - } - - val ping = lineText { - label = "Type `yes` to ping new users when they join" - placeholder = "Defaults to false if input is invalid or not `yes`" - } - } -} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt new file mode 100644 index 00000000..3f3e9b86 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt @@ -0,0 +1,32 @@ +package org.hyacinthbots.lilybot.extensions.config + +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.ephemeralSlashCommand +import dev.kordex.modules.dev.unsafe.annotations.UnsafeAPI +import org.hyacinthbots.lilybot.extensions.config.commands.configClearCommand +import org.hyacinthbots.lilybot.extensions.config.commands.configViewCommand +import org.hyacinthbots.lilybot.extensions.logging.config.loggingCommand +import org.hyacinthbots.lilybot.extensions.moderation.config.moderationCommand +import org.hyacinthbots.lilybot.extensions.utils.config.utilityCommand + +class ConfigExtension : Extension() { + override val name: String = "config" + + @OptIn(UnsafeAPI::class) + override suspend fun setup() { + ephemeralSlashCommand { + name = "config" + description = "Configure Lily's settings" + + loggingCommand() + + moderationCommand() + + utilityCommand() + + configClearCommand() + + configViewCommand() + } + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt new file mode 100644 index 00000000..9c2dce01 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt @@ -0,0 +1,150 @@ +package org.hyacinthbots.lilybot.extensions.config.commands + +import dev.kord.common.entity.Permission +import dev.kord.core.behavior.channel.createMessage +import dev.kord.rest.builder.message.embed +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.SlashCommand +import dev.kordex.core.commands.application.slash.converters.impl.stringChoice +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection +import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection +import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection +import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.extensions.config.ConfigType +import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms + +suspend fun SlashCommand<*, *, *>.configClearCommand() = ephemeralSubCommand(::ClearArgs) { + name = "clear" + description = "Clear a config type" + + requirePermission(Permission.ManageGuild) + + check { + anyGuild() + hasPermission(Permission.ManageGuild) + } + + action { + suspend fun logClear() { + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + + if (utilityLog == null) { + respond { + content = "Consider setting a utility config to log changes to configurations." + } + return + } + + utilityLog.createMessage { + embed { + title = "Configuration Cleared: ${arguments.config[0]}${ + arguments.config.substring(1, arguments.config.length).lowercase() + }" + footer { + text = "Config cleared by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + } + } + } + + when (arguments.config) { + ConfigType.MODERATION.name -> { + ModerationConfigCollection().getConfig(guild!!.id) ?: run { + respond { + content = "No moderation configuration exists to clear!" + } + return@action + } + + logClear() + + ModerationConfigCollection().clearConfig(guild!!.id) + respond { + embed { + title = "Config cleared: Moderation" + footer { + text = "Config cleared by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + } + } + } + + ConfigType.LOGGING.name -> { + LoggingConfigCollection().getConfig(guild!!.id) ?: run { + respond { + content = "No logging configuration exists to clear!" + } + return@action + } + + logClear() + + LoggingConfigCollection().clearConfig(guild!!.id) + respond { + embed { + title = "Config cleared: Logging" + footer { + text = "Config cleared by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + } + } + } + + ConfigType.UTILITY.name -> { + UtilityConfigCollection().getConfig(guild!!.id) ?: run { + respond { + content = "No utility configuration exists to clear" + } + return@action + } + + logClear() + + UtilityConfigCollection().clearConfig(guild!!.id) + respond { + embed { + title = "Config cleared: Utility" + footer { + text = "Config cleared by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + } + } + } + + ConfigType.ALL.name -> { + ModerationConfigCollection().clearConfig(guild!!.id) + LoggingConfigCollection().clearConfig(guild!!.id) + UtilityConfigCollection().clearConfig(guild!!.id) + respond { + embed { + title = "All configs cleared" + footer { + text = "Configs cleared by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + } + } + } + } + } +} + +class ClearArgs : Arguments() { + val config by stringChoice { + name = "config-type" + description = "The type of config to clear" + choices = mutableMapOf( + "moderation" to ConfigType.MODERATION.name, + "logging" to ConfigType.LOGGING.name, + "utility" to ConfigType.UTILITY.name, + "all" to ConfigType.ALL.name + ) + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigViewCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigViewCommand.kt new file mode 100644 index 00000000..79fa7ea0 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigViewCommand.kt @@ -0,0 +1,180 @@ +package org.hyacinthbots.lilybot.extensions.config.commands + +import dev.kord.common.entity.Permission +import dev.kord.rest.builder.message.embed +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.SlashCommand +import dev.kordex.core.commands.application.slash.converters.impl.stringChoice +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import kotlinx.datetime.Clock +import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection +import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection +import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection +import org.hyacinthbots.lilybot.extensions.config.ConfigType +import org.hyacinthbots.lilybot.utils.interval + +suspend fun SlashCommand<*, *, *>.configViewCommand() = ephemeralSubCommand(::ViewArgs) { + name = "view" + description = "View the current config that you have set" + + requirePermission(Permission.ManageGuild) + + check { + anyGuild() + hasPermission(Permission.ManageGuild) + } + + action { + when (arguments.config) { + ConfigType.MODERATION.name -> { + val config = ModerationConfigCollection().getConfig(guild!!.id) + if (config == null) { + respond { + content = "There is no moderation config for this guild" + } + return@action + } + + respond { + embed { + title = "Current moderation config" + description = "This is the current moderation config for this guild" + field { + name = "Enabled/Disabled" + value = if (config.enabled) "Enabled" else "Disabled" + } + field { + name = "Moderators" + value = config.role?.let { guild!!.getRoleOrNull(it)?.mention } ?: "Disabled" + } + field { + name = "Action log" + value = + config.channel?.let { guild!!.getChannelOrNull(it)?.mention } ?: "Disabled" + } + field { + name = "Log publicly" + value = when (config.publicLogging) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } + field { + name = "Quick timeout length" + value = config.quickTimeoutLength.interval() ?: "No quick timeout length set" + } + field { + name = "Warning Auto-punishments" + value = when (config.autoPunishOnWarn) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } + field { + name = "Ban DM Message" + value = config.banDmMessage ?: "No custom Ban DM message set" + } + field { + name = "Auto-invite Moderator Role" + value = when (config.autoInviteModeratorRole) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } + timestamp = Clock.System.now() + } + } + } + + ConfigType.LOGGING.name -> { + val config = LoggingConfigCollection().getConfig(guild!!.id) + if (config == null) { + respond { + content = "There is no logging config for this guild" + } + return@action + } + + respond { + embed { + title = "Current logging config" + description = "This is the current logging config for this guild" + field { + name = "Message delete logs" + value = if (config.enableMessageDeleteLogs) { + "Enabled\n" + + "* ${guild!!.getChannelOrNull(config.messageChannel!!)?.mention ?: "Unable to get channel mention"} (" + + "${guild!!.getChannelOrNull(config.messageChannel)?.name ?: "Unable to get channel name"})" + } else { + "Disabled" + } + } + field { + name = "Message edit logs" + value = if (config.enableMessageEditLogs) { + "Enabled\n" + + "* ${guild!!.getChannelOrNull(config.messageChannel!!)?.mention ?: "Unable to get channel mention"} (" + + "${guild!!.getChannelOrNull(config.messageChannel)?.name ?: "Unable to get channel mention"})" + } else { + "Disabled" + } + } + field { + name = "Member logs" + value = if (config.enableMemberLogs) { + "Enabled\n" + + "* ${guild!!.getChannelOrNull(config.memberLog!!)?.mention ?: "Unable to get channel mention"} (" + + "${guild!!.getChannelOrNull(config.memberLog)?.name ?: "Unable to get channel mention."})" + } else { + "Disabled" + } + } + timestamp = Clock.System.now() + } + } + } + + ConfigType.UTILITY.name -> { + val config = UtilityConfigCollection().getConfig(guild!!.id) + if (config == null) { + respond { + content = "There is no utility config for this guild" + } + return@action + } + + respond { + embed { + title = "Current utility config" + description = "This is the current utility config for this guild" + field { + name = "Channel" + value = + "${ + config.utilityLogChannel?.let { guild!!.getChannelOrNull(it)?.mention } ?: "None" + } ${config.utilityLogChannel?.let { guild!!.getChannelOrNull(it)?.name } ?: ""}" + } + timestamp = Clock.System.now() + } + } + } + } + } +} + +class ViewArgs : Arguments() { + val config by stringChoice { + name = "config-type" + description = "The type of config to clear" + choices = mutableMapOf( + "moderation" to ConfigType.MODERATION.name, + "logging" to ConfigType.LOGGING.name, + "utility" to ConfigType.UTILITY.name, + ) + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingArgs.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingArgs.kt new file mode 100644 index 00000000..9ecb08d1 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingArgs.kt @@ -0,0 +1,43 @@ +package org.hyacinthbots.lilybot.extensions.logging.config + +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.converters.impl.boolean +import dev.kordex.core.commands.converters.impl.optionalChannel + +class LoggingArgs : Arguments() { + val enableMessageDeleteLogs by boolean { + name = "enable-delete-logs" + description = "Enable logging of message deletions" + } + + val enableMessageEditLogs by boolean { + name = "enable-edit-logs" + description = "Enable logging of message edits" + } + + val enableMemberLogging by boolean { + name = "enable-member-logging" + description = "Enable logging of members joining and leaving the guild" + } + + val enablePublicMemberLogging by boolean { + name = "enable-public-member-logging" + description = + "Enable logging of members joining and leaving the guild with a public message and ping if enabled" + } + + val messageLogs by optionalChannel { + name = "message-logs" + description = "The channel for logging message deletions" + } + + val memberLog by optionalChannel { + name = "member-log" + description = "The channel for logging members joining and leaving the guild" + } + + val publicMemberLog by optionalChannel { + name = "public-member-log" + description = "The channel for the public logging of members joining and leaving the guild" + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt new file mode 100644 index 00000000..fbe41041 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt @@ -0,0 +1,231 @@ +package org.hyacinthbots.lilybot.extensions.logging.config + +import dev.kord.common.entity.Permission +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.getChannelOfOrNull +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.entity.channel.TextChannel +import dev.kord.rest.builder.message.EmbedBuilder +import dev.kord.rest.builder.message.embed +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.application.slash.SlashCommand +import dev.kordex.core.utils.botHasPermissions +import dev.kordex.modules.dev.unsafe.annotations.UnsafeAPI +import dev.kordex.modules.dev.unsafe.commands.slash.InitialSlashCommandResponse +import dev.kordex.modules.dev.unsafe.extensions.unsafeSubCommand +import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection +import org.hyacinthbots.lilybot.database.entities.LoggingConfigData +import org.hyacinthbots.lilybot.database.entities.PublicMemberLogData +import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.trimmedContents + +@OptIn(UnsafeAPI::class) +suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingArgs) { + name = "logging" + description = "Configure Lily's logging system" + + initialResponse = InitialSlashCommandResponse.None + + requirePermission(Permission.ManageGuild) + + check { + anyGuild() + hasPermission(Permission.ManageGuild) + } + + action { + val loggingConfig = LoggingConfigCollection().getConfig(guild!!.id) + if (loggingConfig != null) { + ackEphemeral() + respondEphemeral { + content = "You already have a logging configuration set. " + + "Please clear it before attempting to set a new one." + } + return@action + } + + if (arguments.enableMemberLogging && arguments.memberLog == null) { + ackEphemeral() + respondEphemeral { + content = "You must specify a channel to log members joining and leaving to!" + } + return@action + } else if ((arguments.enableMessageDeleteLogs || arguments.enableMessageEditLogs) && + arguments.messageLogs == null + ) { + ackEphemeral() + respondEphemeral { content = "You must specify a channel to log deleted/edited messages to!" } + return@action + } else if (arguments.enablePublicMemberLogging && arguments.publicMemberLog == null) { + ackEphemeral() + respondEphemeral { + content = "You must specify a channel to publicly log members joining and leaving to!" + } + return@action + } + + val memberLog: TextChannel? + if (arguments.enableMemberLogging && arguments.memberLog != null) { + memberLog = guild!!.getChannelOfOrNull(arguments.memberLog!!.id) + if (memberLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { + ackEphemeral() + respondEphemeral { + content = "The member log you've selected is invalid, or I can't view it. " + + "Please attempt to resolve this and try again." + } + return@action + } + } + + val messageLog: TextChannel? + if ((arguments.enableMessageDeleteLogs || arguments.enableMessageEditLogs) && arguments.messageLogs != null) { + messageLog = guild!!.getChannelOfOrNull(arguments.messageLogs!!.id) + if (messageLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { + ackEphemeral() + respondEphemeral { + content = "The message log you've selected is invalid, or I can't view it. " + + "Please attempt to resolve this and try again." + } + return@action + } + } + + val publicMemberLog: TextChannel? + if (arguments.enablePublicMemberLogging && arguments.publicMemberLog != null) { + publicMemberLog = guild!!.getChannelOfOrNull(arguments.publicMemberLog!!.id) + if (publicMemberLog?.botHasPermissions( + Permission.ViewChannel, + Permission.SendMessages + ) != true + ) { + ackEphemeral() + respondEphemeral { + content = "The public member log you've selected is invalid, or I can't view it. " + + "Please attempt to resolve this and try again." + } + return@action + } + } + + suspend fun EmbedBuilder.loggingEmbed() { + title = "Configuration: Logging" + field { + name = "Message Delete Logs" + value = if (arguments.enableMessageDeleteLogs && arguments.messageLogs != null) { + arguments.messageLogs!!.mention + } else { + "Disabled" + } + } + field { + name = "Message Edit Logs" + value = if (arguments.enableMessageEditLogs && arguments.messageLogs != null) { + arguments.messageLogs!!.mention + } else { + "Disabled" + } + } + field { + name = "Member Logs" + value = if (arguments.enableMemberLogging && arguments.memberLog != null) { + arguments.memberLog!!.mention + } else { + "Disabled" + } + } + + field { + name = "Public Member logs" + value = if (arguments.enablePublicMemberLogging && arguments.publicMemberLog != null) { + arguments.publicMemberLog!!.mention + } else { + "Disabled" + } + } + if (arguments.enableMemberLogging && arguments.publicMemberLog != null) { + val config = LoggingConfigCollection().getConfig(guild!!.id) + if (config != null) { + field { + name = "Join Message" + value = config.publicMemberLogData?.joinMessage.trimmedContents(256)!! + } + field { + name = "Leave Message" + value = config.publicMemberLogData?.leaveMessage.trimmedContents(256)!! + } + field { + name = "Ping on join" + value = config.publicMemberLogData?.pingNewUsers.toString() + } + } + } + + footer { + text = "Configured by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + } + + var publicMemberLogData: PublicMemberLogData? = null + if (arguments.enablePublicMemberLogging) { + val modalObj = PublicLoggingModal() + + this@unsafeSubCommand.componentRegistry.register(modalObj) + + event.interaction.modal( + modalObj.title, + modalObj.id + ) { + modalObj.applyToBuilder(this, getLocale(), null) + } + + modalObj.awaitCompletion { modalSubmitInteraction -> + interactionResponse = modalSubmitInteraction?.deferEphemeralMessageUpdate() + } + + publicMemberLogData = PublicMemberLogData( + modalObj.ping.value == "yes", + modalObj.joinMessage.value, + modalObj.leaveMessage.value + ) + } + + LoggingConfigCollection().setConfig( + LoggingConfigData( + guild!!.id, + arguments.enableMessageDeleteLogs, + arguments.enableMessageEditLogs, + arguments.messageLogs?.id, + arguments.enableMemberLogging, + arguments.memberLog?.id, + arguments.enablePublicMemberLogging, + arguments.publicMemberLog?.id, + publicMemberLogData + ) + ) + + if (!arguments.enablePublicMemberLogging) { + ackEphemeral() + } + respondEphemeral { + embed { loggingEmbed() } + } + + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!) + + if (utilityLog == null) { + respondEphemeral { + content = "Consider setting a utility config to log changes to configurations." + } + return@action + } + + utilityLog.createMessage { + embed { + loggingEmbed() + } + } + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/PublicLoggingModal.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/PublicLoggingModal.kt new file mode 100644 index 00000000..28928317 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/PublicLoggingModal.kt @@ -0,0 +1,24 @@ +package org.hyacinthbots.lilybot.extensions.logging.config + +import dev.kordex.core.components.forms.ModalForm + +class PublicLoggingModal : ModalForm() { + override var title = "Public logging configuration" + + val joinMessage = paragraphText { + label = "What would you like sent when a user joins" + placeholder = "Welcome to the server!" + required = true + } + + val leaveMessage = paragraphText { + label = "What would you like sent when a user leaves" + placeholder = "Adiós amigo!" + required = true + } + + val ping = lineText { + label = "Type `yes` to ping new users when they join" + placeholder = "Defaults to false if input is invalid or not `yes`" + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/GuildLogging.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/GuildLogging.kt similarity index 95% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/GuildLogging.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/GuildLogging.kt index 5640e868..35a84898 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/GuildLogging.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/GuildLogging.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.config +package org.hyacinthbots.lilybot.extensions.logging.events import dev.kord.core.event.guild.GuildCreateEvent import dev.kord.core.event.guild.GuildDeleteEvent diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt similarity index 98% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt index 84c88077..d446d57b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.events +package org.hyacinthbots.lilybot.extensions.logging.events import dev.kord.common.entity.Permission import dev.kord.core.behavior.channel.createEmbed diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageDelete.kt similarity index 98% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageDelete.kt index b6b14c1a..9c602189 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageDelete.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.events +package org.hyacinthbots.lilybot.extensions.logging.events import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.channel.createEmbed diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageEdit.kt similarity index 98% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageEdit.kt index 31ca546f..ecb05524 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageEdit.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.events +package org.hyacinthbots.lilybot.extensions.logging.events import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.channel.createMessage diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ClearCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ClearCommands.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ClearCommands.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ClearCommands.kt index 7cf90848..9ad4cc7c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ClearCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ClearCommands.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.moderation +package org.hyacinthbots.lilybot.extensions.moderation.commands import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/LockingCommands.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/LockingCommands.kt index 2ba68712..b7994e75 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/LockingCommands.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.moderation +package org.hyacinthbots.lilybot.extensions.moderation.commands import dev.kord.common.DiscordBitSet import dev.kord.common.entity.Permission diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModUtilities.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModUtilities.kt index 5c186ff1..1654b12f 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModUtilities.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.moderation.commands import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt index 4f157ffa..764da176 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.moderation +package org.hyacinthbots.lilybot.extensions.moderation.commands import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions @@ -60,6 +60,7 @@ import org.hyacinthbots.lilybot.database.collections.TemporaryBanCollection import org.hyacinthbots.lilybot.database.collections.WarnCollection import org.hyacinthbots.lilybot.database.entities.TemporaryBanData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.extensions.moderation.utils.ModerationActions import org.hyacinthbots.lilybot.utils.HYACINTH_GITHUB import org.hyacinthbots.lilybot.utils.baseModerationEmbed import org.hyacinthbots.lilybot.utils.dmNotificationStatusEmbedField diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/Report.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/Report.kt index 294d86a8..a78b5fe3 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/Report.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.moderation +package org.hyacinthbots.lilybot.extensions.moderation.commands import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Snowflake diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt new file mode 100644 index 00000000..cadb36a0 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt @@ -0,0 +1,51 @@ +package org.hyacinthbots.lilybot.extensions.moderation.config + +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.converters.impl.boolean +import dev.kordex.core.commands.converters.impl.coalescingOptionalDuration +import dev.kordex.core.commands.converters.impl.optionalBoolean +import dev.kordex.core.commands.converters.impl.optionalChannel +import dev.kordex.core.commands.converters.impl.optionalRole +import dev.kordex.core.commands.converters.impl.optionalString + +class ModerationArgs : Arguments() { + val enabled by boolean { + name = "enable-moderation" + description = "Whether to enable the moderation system" + } + + val moderatorRole by optionalRole { + name = "moderator-role" + description = "The role of your moderators, used for pinging in message logs." + } + + val modActionLog by optionalChannel { + name = "action-log" + description = "The channel used to store moderator actions." + } + + val quickTimeoutLength by coalescingOptionalDuration { + name = "quick-timeout-length" + description = "The length of timeouts to use for quick timeouts" + } + + val warnAutoPunishments by optionalBoolean { + name = "warn-auto-punishments" + description = "Whether to automatically punish users for reach a certain threshold on warns" + } + + val logPublicly by optionalBoolean { + name = "log-publicly" + description = "Whether to log moderation publicly or not." + } + + val banDmMessage by optionalString { + name = "ban-dm-message" + description = "A custom message to send to users when they are banned." + } + + val autoInviteModeratorRole by optionalBoolean { + name = "auto-invite-moderator-role" + description = "Silently ping moderators to invite them to new threads." + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt new file mode 100644 index 00000000..f018f5b7 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt @@ -0,0 +1,177 @@ +package org.hyacinthbots.lilybot.extensions.moderation.config + +import dev.kord.common.entity.Permission +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.getChannelOfOrNull +import dev.kord.core.entity.channel.TextChannel +import dev.kord.rest.builder.message.EmbedBuilder +import dev.kord.rest.builder.message.embed +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.application.slash.SlashCommand +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.utils.botHasPermissions +import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection +import org.hyacinthbots.lilybot.database.entities.ModerationConfigData +import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.utils.canPingRole +import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.interval + +suspend fun SlashCommand<*, *, *>.moderationCommand() = + ephemeralSubCommand(::ModerationArgs) { + name = "moderation" + description = "Configure Lily's moderation system" + + requirePermission(Permission.ManageGuild) + + check { + anyGuild() + hasPermission(Permission.ManageGuild) + } + + action { + val moderationConfig = ModerationConfigCollection().getConfig(guild!!.id) + if (moderationConfig != null) { + respond { + content = "You already have a moderation configuration set. " + + "Please clear it before attempting to set a new one." + } + return@action + } + + if (!arguments.enabled) { + ModerationConfigCollection().setConfig( + ModerationConfigData( + guild!!.id, + false, + null, + null, + null, + null, + null, + null, + null + ) + ) + respond { + content = "Moderation system disabled." + } + return@action + } + + if ( + arguments.moderatorRole != null && arguments.modActionLog == null || + arguments.moderatorRole == null && arguments.modActionLog != null + ) { + respond { + content = + "You must set both the moderator role and the action log channel to use the moderation configuration." + } + return@action + } + + if (!canPingRole(arguments.moderatorRole, guild!!.id, this@moderationCommand.kord)) { + respond { + content = + "I cannot use the role: ${arguments.moderatorRole!!.mention}, because it is not mentionable by " + + "regular users. Please enable this in the role settings, or use a different role." + } + return@action + } + + val modActionLog: TextChannel? + if (arguments.enabled && arguments.modActionLog != null) { + modActionLog = guild!!.getChannelOfOrNull(arguments.modActionLog!!.id) + if (modActionLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { + respond { + content = "The mod action log you've selected is invalid, or I can't view it. " + + "Please attempt to resolve this and try again." + } + return@action + } + } + + suspend fun EmbedBuilder.moderationEmbed() { + title = "Configuration: Moderation" + field { + name = "Moderators" + value = arguments.moderatorRole?.mention ?: "Disabled" + } + field { + name = "Action log" + value = arguments.modActionLog?.mention ?: "Disabled" + } + field { + name = "Log publicly" + value = when (arguments.logPublicly) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } + field { + name = "Quick timeout length" + value = arguments.quickTimeoutLength.interval() ?: "No quick timeout length set" + } + field { + name = "Warning Auto-punishments" + value = when (arguments.warnAutoPunishments) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } + field { + name = "Ban DM Message" + value = arguments.banDmMessage ?: "No custom Ban DM message set" + } + field { + name = "Auto-invite Moderator Role" + value = when (arguments.autoInviteModeratorRole) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } + footer { + text = "Configured by ${user.asUserOrNull()?.username}" + } + } + + respond { + embed { + moderationEmbed() + } + } + + ModerationConfigCollection().setConfig( + ModerationConfigData( + guild!!.id, + arguments.enabled, + arguments.modActionLog?.id, + arguments.moderatorRole?.id, + arguments.quickTimeoutLength, + arguments.warnAutoPunishments, + arguments.logPublicly, + arguments.banDmMessage, + arguments.autoInviteModeratorRole + ) + ) + + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + + if (utilityLog == null) { + respond { + content = "Consider setting a utility config to log changes to configurations." + } + return@action + } + + utilityLog.createMessage { + embed { + moderationEmbed() + } + } + } + } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationActions.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/utils/ModerationActions.kt similarity index 54% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationActions.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/utils/ModerationActions.kt index 86ca719d..62f22296 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationActions.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/utils/ModerationActions.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.moderation +package org.hyacinthbots.lilybot.extensions.moderation.utils enum class ModerationActions { BAN, diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/AutoThreading.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/AutoThreading.kt index bbd641ec..6ce9928b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/AutoThreading.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/AutoThreading.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.events +package org.hyacinthbots.lilybot.extensions.threads import dev.kord.common.entity.ArchiveDuration import dev.kord.common.entity.ChannelType diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/ModThreadInviting.kt similarity index 96% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/ModThreadInviting.kt index 5e2dc3c8..cc07b5b3 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ModThreadInviting.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/ModThreadInviting.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.events +package org.hyacinthbots.lilybot.extensions.threads import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.edit diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/ThreadControl.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/ThreadControl.kt index 5b1e92c2..64aff562 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/ThreadControl.kt @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.threads import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GalleryChannel.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GalleryChannel.kt index f424bcf6..84733c03 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GalleryChannel.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kord.common.entity.MessageType import dev.kord.common.entity.Permission diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Github.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Github.kt index 5deab8cf..fe300ecf 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Github.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Github.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kord.common.entity.Permission import dev.kord.rest.builder.message.embed diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GuildAnnouncements.kt similarity index 98% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GuildAnnouncements.kt index 05028f5a..dfe5bfb0 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GuildAnnouncements.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kord.common.Color import dev.kord.common.entity.ButtonStyle diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/InfoCommands.kt similarity index 98% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/InfoCommands.kt index 3c81fd64..c668b15e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/InfoCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/InfoCommands.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kord.rest.builder.message.create.MessageCreateBuilder import dev.kord.rest.builder.message.embed diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/NewsChannelPublishing.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/NewsChannelPublishing.kt index e870a726..6e3db35a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/NewsChannelPublishing.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kord.common.Locale import dev.kord.common.asJavaLocale diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/PublicUtilities.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/PublicUtilities.kt index 102a3d32..a901a97c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/PublicUtilities.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Reminders.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Reminders.kt index d455b00b..8eeb2d38 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Reminders.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/RoleMenu.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/RoleMenu.kt index ba1781fd..c16a7dd9 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/RoleMenu.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StartupHooks.kt similarity index 97% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StartupHooks.kt index 62c87a6b..4ac0a0b2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StartupHooks.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.getChannelOfOrNull diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StatusPing.kt similarity index 93% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StatusPing.kt index e2debbad..d1b7b12d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StatusPing.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StatusPing.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kordex.core.extensions.Extension import dev.kordex.core.utils.scheduling.Scheduler diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Tags.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Tags.kt index 13b01b56..973e4d0c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Tags.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.util +package org.hyacinthbots.lilybot.extensions.utils.commands import dev.kord.common.Locale import dev.kord.common.asJavaLocale diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityArgs.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityArgs.kt new file mode 100644 index 00000000..910c1df9 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityArgs.kt @@ -0,0 +1,11 @@ +package org.hyacinthbots.lilybot.extensions.utils.config + +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.converters.impl.optionalChannel + +class UtilityArgs : Arguments() { + val utilityLogChannel by optionalChannel { + name = "utility-log" + description = "The channel to log various utility actions too." + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityCommand.kt new file mode 100644 index 00000000..3ded444b --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityCommand.kt @@ -0,0 +1,87 @@ +package org.hyacinthbots.lilybot.extensions.utils.config + +import dev.kord.common.entity.Permission +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.getChannelOfOrNull +import dev.kord.core.entity.channel.TextChannel +import dev.kord.rest.builder.message.EmbedBuilder +import dev.kord.rest.builder.message.embed +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.checks.hasPermission +import dev.kordex.core.commands.application.slash.SlashCommand +import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import dev.kordex.core.utils.botHasPermissions +import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection +import org.hyacinthbots.lilybot.database.entities.UtilityConfigData + +suspend fun SlashCommand<*, *, *>.utilityCommand() = ephemeralSubCommand(::UtilityArgs) { + name = "utility" + description = "Configure Lily's utility settings" + + requirePermission(Permission.ManageGuild) + + check { + anyGuild() + hasPermission(Permission.ManageGuild) + } + + action { + val utilityConfig = UtilityConfigCollection().getConfig(guild!!.id) + + if (utilityConfig != null) { + respond { + content = "You already have a utility configuration set. " + + "Please clear it before attempting to set a new one." + } + return@action + } + + var utilityLog: TextChannel? = null + if (arguments.utilityLogChannel != null) { + utilityLog = guild!!.getChannelOfOrNull(arguments.utilityLogChannel!!.id) + if (utilityLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { + respond { + content = "The utility log you've selected is invalid, or I can't view it. " + + "Please attempt to resolve this and try again." + } + return@action + } + } + + suspend fun EmbedBuilder.utilityEmbed() { + title = "Configuration: Utility" + field { + name = "Utility Log" + value = if (arguments.utilityLogChannel != null) { + "${arguments.utilityLogChannel!!.mention} ${arguments.utilityLogChannel!!.data.name.value}" + } else { + "Disabled" + } + } + + footer { + text = "Configured by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + } + + respond { + embed { + utilityEmbed() + } + } + + UtilityConfigCollection().setConfig( + UtilityConfigData( + guild!!.id, + arguments.utilityLogChannel?.id + ) + ) + + utilityLog?.createMessage { + embed { + utilityEmbed() + } + } + } +} From 378f22481ba37f5c5b0053d4a35eaa8f305c1e90 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Thu, 29 Aug 2024 13:17:28 +0100 Subject: [PATCH 39/42] More event logging (#410) * Start work on audit-log event based logging * Refactor it all because yes * Create action transferring database to move important information over to the event action * Log Kicks, send quick actions to db, generalise channel creation logs * Start work on channel updates * Add the rest of the channel update fields * Fully set up channel edit logging * Add scheduled event logging * Invite events and Role events * Thread creates and deletes logging + updated kordex * Log timeouts created by lily * Fix timeout logging, yeehaw actual code is done now * Add docs * Fix package references * Remove the logging, however funny it was to me it is not needed * Complete merge * Add configuration, split down into two event categories * Update migrations for utility config * Reduce code duplication and rename some packages * Fix formatting of applied tags --- .gitignore | 3 + .../org/hyacinthbots/lilybot/LilyBot.kt | 26 +- .../collections/ModerationActionCollection.kt | 117 +++ .../lilybot/database/entities/Config.kt | 6 +- .../database/entities/ModerationActionData.kt | 66 ++ .../database/migrations/config/configV7.kt | 19 + .../database/migrations/main/mainV10.kt | 1 + .../extensions/config/ConfigExtension.kt | 2 +- .../extensions/config/utils/ConfigEmbeds.kt | 150 ++++ .../logging/config/LoggingCommand.kt | 72 +- .../logging/events/MemberLogging.kt | 19 + .../moderation/commands/ModerationCommands.kt | 383 +++++----- .../moderation/config/ModerationCommand.kt | 54 +- .../moderation/events/ModerationEvents.kt | 304 ++++++++ ...derationActions.kt => ModerationAction.kt} | 8 +- .../commands/GalleryChannel.kt | 4 +- .../{utils => utility}/commands/Github.kt | 18 +- .../commands/GuildAnnouncements.kt | 2 +- .../commands/InfoCommands.kt | 2 +- .../commands/NewsChannelPublishing.kt | 2 +- .../commands/PublicUtilities.kt | 4 +- .../{utils => utility}/commands/Reminders.kt | 2 +- .../{utils => utility}/commands/RoleMenu.kt | 2 +- .../commands/StartupHooks.kt | 2 +- .../{utils => utility}/commands/StatusPing.kt | 2 +- .../{utils => utility}/commands/Tags.kt | 2 +- .../extensions/utility/config/UtilityArgs.kt | 29 + .../config/UtilityCommand.kt | 31 +- .../utility/events/UtilityEvents.kt | 677 ++++++++++++++++++ .../extensions/utils/config/UtilityArgs.kt | 11 - .../lilybot/utils/ResponseHelper.kt | 32 +- .../lilybot/utils/_PermissionUtils.kt | 36 +- .../org/hyacinthbots/lilybot/utils/_Utils.kt | 11 + 33 files changed, 1673 insertions(+), 426 deletions(-) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ModerationActionCollection.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ModerationActionData.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/events/ModerationEvents.kt rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/utils/{ModerationActions.kt => ModerationAction.kt} (51%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/GalleryChannel.kt (98%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/Github.kt (97%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/GuildAnnouncements.kt (98%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/InfoCommands.kt (98%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/NewsChannelPublishing.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/PublicUtilities.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/Reminders.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/RoleMenu.kt (99%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/StartupHooks.kt (97%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/StatusPing.kt (93%) rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/commands/Tags.kt (99%) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityArgs.kt rename src/main/kotlin/org/hyacinthbots/lilybot/extensions/{utils => utility}/config/UtilityCommand.kt (74%) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/events/UtilityEvents.kt delete mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityArgs.kt diff --git a/.gitignore b/.gitignore index 16f36025..9d4af58b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ build/ out/ classes/ +# kotlin +.kotlin/ + # eclipse *.launch diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt index 376ba8c0..e1be17d7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt @@ -36,20 +36,22 @@ import org.hyacinthbots.lilybot.extensions.moderation.commands.LockingCommands import org.hyacinthbots.lilybot.extensions.moderation.commands.ModUtilities import org.hyacinthbots.lilybot.extensions.moderation.commands.ModerationCommands import org.hyacinthbots.lilybot.extensions.moderation.commands.Report +import org.hyacinthbots.lilybot.extensions.moderation.events.ModerationEvents import org.hyacinthbots.lilybot.extensions.threads.AutoThreading import org.hyacinthbots.lilybot.extensions.threads.ModThreadInviting import org.hyacinthbots.lilybot.extensions.threads.ThreadControl -import org.hyacinthbots.lilybot.extensions.utils.commands.GalleryChannel -import org.hyacinthbots.lilybot.extensions.utils.commands.Github -import org.hyacinthbots.lilybot.extensions.utils.commands.GuildAnnouncements -import org.hyacinthbots.lilybot.extensions.utils.commands.InfoCommands -import org.hyacinthbots.lilybot.extensions.utils.commands.NewsChannelPublishing -import org.hyacinthbots.lilybot.extensions.utils.commands.PublicUtilities -import org.hyacinthbots.lilybot.extensions.utils.commands.Reminders -import org.hyacinthbots.lilybot.extensions.utils.commands.RoleMenu -import org.hyacinthbots.lilybot.extensions.utils.commands.StartupHooks -import org.hyacinthbots.lilybot.extensions.utils.commands.StatusPing -import org.hyacinthbots.lilybot.extensions.utils.commands.Tags +import org.hyacinthbots.lilybot.extensions.utility.commands.GalleryChannel +import org.hyacinthbots.lilybot.extensions.utility.commands.Github +import org.hyacinthbots.lilybot.extensions.utility.commands.GuildAnnouncements +import org.hyacinthbots.lilybot.extensions.utility.commands.InfoCommands +import org.hyacinthbots.lilybot.extensions.utility.commands.NewsChannelPublishing +import org.hyacinthbots.lilybot.extensions.utility.commands.PublicUtilities +import org.hyacinthbots.lilybot.extensions.utility.commands.Reminders +import org.hyacinthbots.lilybot.extensions.utility.commands.RoleMenu +import org.hyacinthbots.lilybot.extensions.utility.commands.StartupHooks +import org.hyacinthbots.lilybot.extensions.utility.commands.StatusPing +import org.hyacinthbots.lilybot.extensions.utility.commands.Tags +import org.hyacinthbots.lilybot.extensions.utility.events.UtilityEvents import org.hyacinthbots.lilybot.internal.BuildInfo import org.hyacinthbots.lilybot.utils.BOT_TOKEN import org.hyacinthbots.lilybot.utils.ENVIRONMENT @@ -177,6 +179,7 @@ suspend fun main() { add(::MemberLogging) add(::MessageDelete) add(::MessageEdit) + add(::ModerationEvents) add(::ModThreadInviting) add(::ModUtilities) add(::ModerationCommands) @@ -189,6 +192,7 @@ suspend fun main() { add(::StatusPing) add(::Tags) add(::ThreadControl) + add(::UtilityEvents) /* The welcome channel extension allows users to designate a YAML file to create a channel with diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ModerationActionCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ModerationActionCollection.kt new file mode 100644 index 00000000..da0dba27 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ModerationActionCollection.kt @@ -0,0 +1,117 @@ +package org.hyacinthbots.lilybot.database.collections + +import dev.kord.common.entity.Snowflake +import dev.kordex.core.koin.KordExKoinComponent +import org.hyacinthbots.lilybot.database.Database +import org.hyacinthbots.lilybot.database.entities.ActionData +import org.hyacinthbots.lilybot.database.entities.ModerationActionData +import org.hyacinthbots.lilybot.extensions.moderation.utils.ModerationAction +import org.koin.core.component.inject +import org.litote.kmongo.eq + +/** + * This class contains the function for interacting with the [Moderation Action Database][ModerationActionData]. This + * class contains functions for getting, setting, removing and ignoring actions + * + * @since 5.0.0 + * @see addAction + * @see removeAction + * @see getAction + * @see declareActionToIgnore + * @see shouldIgnoreAction + */ +class ModerationActionCollection : KordExKoinComponent { + private val db: Database by inject() + + @PublishedApi + internal val collection = db.mainDatabase.getCollection() + + /** + * Adds an action that occurred. + * + * @param action The type of action you're adding + * @param guildId The ID of the guild the action occurred in + * @param targetUserId The ID of the user this action happened to + * @param data The [ActionData] for the action + * @param ignore Whether to ignore the action or not. Defaults to false + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun addAction( + action: ModerationAction, + guildId: Snowflake, + targetUserId: Snowflake, + data: ActionData, + ignore: Boolean = false + ) = collection.insertOne(ModerationActionData(action, guildId, targetUserId, data, ignore)) + + /** + * Removes an action that occurred. + * + * @param type The type of action you're removing + * @param guildId The ID of the guild the action occurred in + * @param targetUserId The ID of the user this action happened to + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun removeAction(type: ModerationAction, guildId: Snowflake, targetUserId: Snowflake) = + collection.deleteOne( + ModerationActionData::actionType eq type, + ModerationActionData::guildId eq guildId, + ModerationActionData::targetUserId eq targetUserId + ) + + /** + * Gets an action that occurred. + * + * @param type The type of action you're looking for + * @param guildId The ID of the guild the action occurred in + * @param targetUserId The ID of the user this action happened to + * @return The [data][ModerationActionData] for the event. Can be null if there is no action. + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun getAction( + type: ModerationAction, + guildId: Snowflake, + targetUserId: Snowflake + ): ModerationActionData? = + collection.findOne( + ModerationActionData::actionType eq type, + ModerationActionData::guildId eq guildId, + ModerationActionData::targetUserId eq targetUserId + ) + + /** + * Sets an action as ignored. Convenience function more than anything + * + * @param type The type of action you're looking for + * @param guildId The ID of the guild the action occurred in + * @param targetUserId The ID of the user this action happened to + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun declareActionToIgnore(type: ModerationAction, guildId: Snowflake, targetUserId: Snowflake) = + addAction(type, guildId, targetUserId, ActionData(null, null, null, null, null, null, null), true) + + /** + * Checks if an action should be ignored or not. Convenience function more than anything. + * + * @param type The type of action you're looking for + * @param guildId The ID of the guild the action occurred in + * @param targetUserId The ID of the user this action happened to + * @return True if the action should be ignored, false if otherwise + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun shouldIgnoreAction( + type: ModerationAction, + guildId: Snowflake, + targetUserId: Snowflake + ): Boolean? = + collection.findOne( + ModerationActionData::actionType eq type, + ModerationActionData::guildId eq guildId, + ModerationActionData::targetUserId eq targetUserId + )?.ignore +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt index 519c9744..84723772 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt @@ -70,5 +70,9 @@ data class ModerationConfigData( @Serializable data class UtilityConfigData( val guildId: Snowflake, - val utilityLogChannel: Snowflake? + val utilityLogChannel: Snowflake?, + val logChannelUpdates: Boolean, + val logEventUpdates: Boolean, + val logInviteUpdates: Boolean, + val logRoleUpdates: Boolean ) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ModerationActionData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ModerationActionData.kt new file mode 100644 index 00000000..834b76ed --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ModerationActionData.kt @@ -0,0 +1,66 @@ +package org.hyacinthbots.lilybot.database.entities + +import dev.kord.common.entity.Snowflake +import kotlinx.datetime.DateTimePeriod +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable +import org.hyacinthbots.lilybot.extensions.moderation.utils.ModerationAction + +/** + * The data for Moderation action. + * + * @property actionType The type of action you're adding + * @property guildId The ID of the guild the action occurred in + * @property targetUserId The ID of the user this action happened to + * @property data The [ActionData] for the action + * @property ignore Whether to ignore the action or not. Defaults to false + * @since 5.0.0 + */ +@Serializable +data class ModerationActionData( + val actionType: ModerationAction, + val guildId: Snowflake, + val targetUserId: Snowflake, + val data: ActionData, + val ignore: Boolean = false +) + +/** + * Further, more in-depth data about a [moderation action][ModerationActionData]. + * + * @property actioner The ID of the user that requested the action + * @property deletedMessages The amount of messages deleted in the action + * @property timeData The [TimeData] for the action + * @property reason The reason for the action + * @property dmOutcome The outcome of trying to send a DM to the user + * @property dmOverride Whether the DM sending function was override + * @property imageUrl The URL for the image attached to the action + * @since 5.0.0 + */ +@Serializable +data class ActionData( + val actioner: Snowflake?, + val deletedMessages: Int?, + val timeData: TimeData?, + val reason: String?, + val dmOutcome: Boolean?, + val dmOverride: Boolean?, + val imageUrl: String? +) + +/** + * Further, more in-depth data about the [time data for actions][ActionData.timeData]. + * + * @property durationDtp The Duration as a [DateTimePeriod] + * @property durationInst The Duration as an [Instant] + * @property start The start [Instant] of the action + * @property end The end [Instant] of the action + * @since 5.0.0 + */ +@Serializable +data class TimeData( + val durationDtp: DateTimePeriod?, + val durationInst: Instant?, + val start: Instant? = null, + val end: Instant? = null, +) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt index 6af25904..d72a7152 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt @@ -1,6 +1,7 @@ package org.hyacinthbots.lilybot.database.migrations.config import org.hyacinthbots.lilybot.database.entities.ModerationConfigData +import org.hyacinthbots.lilybot.database.entities.UtilityConfigData import org.litote.kmongo.coroutine.CoroutineDatabase import org.litote.kmongo.exists import org.litote.kmongo.setValue @@ -12,4 +13,22 @@ suspend fun configV7(db: CoroutineDatabase) { setValue(ModerationConfigData::autoInviteModeratorRole, null) ) } + with(db.getCollection("utilityConfigData")) { + updateMany( + UtilityConfigData::logChannelUpdates exists false, + setValue(UtilityConfigData::logChannelUpdates, false) + ) + updateMany( + UtilityConfigData::logEventUpdates exists false, + setValue(UtilityConfigData::logEventUpdates, false) + ) + updateMany( + UtilityConfigData::logInviteUpdates exists false, + setValue(UtilityConfigData::logInviteUpdates, false) + ) + updateMany( + UtilityConfigData::logRoleUpdates exists false, + setValue(UtilityConfigData::logRoleUpdates, false) + ) + } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt index 05722cdd..b5780198 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt @@ -11,4 +11,5 @@ suspend fun mainV10(db: CoroutineDatabase) { } db.createCollection("lockedChannelData") db.createCollection("temporaryBanData") + db.createCollection("moderationActionCacheData") } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt index 3f3e9b86..454b113b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt @@ -7,7 +7,7 @@ import org.hyacinthbots.lilybot.extensions.config.commands.configClearCommand import org.hyacinthbots.lilybot.extensions.config.commands.configViewCommand import org.hyacinthbots.lilybot.extensions.logging.config.loggingCommand import org.hyacinthbots.lilybot.extensions.moderation.config.moderationCommand -import org.hyacinthbots.lilybot.extensions.utils.config.utilityCommand +import org.hyacinthbots.lilybot.extensions.utility.config.utilityCommand class ConfigExtension : Extension() { override val name: String = "config" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt new file mode 100644 index 00000000..2841e4a5 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt @@ -0,0 +1,150 @@ +package org.hyacinthbots.lilybot.extensions.config.utils + +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.UserBehavior +import dev.kord.rest.builder.message.EmbedBuilder +import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection +import org.hyacinthbots.lilybot.extensions.logging.config.LoggingArgs +import org.hyacinthbots.lilybot.extensions.moderation.config.ModerationArgs +import org.hyacinthbots.lilybot.extensions.utility.config.UtilityArgs +import org.hyacinthbots.lilybot.utils.interval +import org.hyacinthbots.lilybot.utils.trimmedContents + +suspend fun EmbedBuilder.utilityEmbed(arguments: UtilityArgs, user: UserBehavior) { + title = "Configuration: Utility" + field { + name = "Utility Log" + value = if (arguments.utilityLogChannel != null) { + "${arguments.utilityLogChannel!!.mention} ${arguments.utilityLogChannel!!.data.name.value}" + } else { + "Disabled" + } + } + field { + name = "Log Channel updates" + value = if (arguments.logChannelUpdates) "Yes" else "No" + } + field { + name = "Log Event updates" + value = if (arguments.logEventUpdates) "Yes" else "No" + } + field { + name = "Log Invite updates" + value = if (arguments.logInviteUpdates) "Yes" else "No" + } + field { + name = "Log Role updates" + value = if (arguments.logRoleUpdates) "Yes" else "No" + } + + footer { + text = "Configured by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } +} + +suspend fun EmbedBuilder.moderationEmbed(arguments: ModerationArgs, user: UserBehavior) { + title = "Configuration: Moderation" + field { + name = "Moderators" + value = arguments.moderatorRole?.mention ?: "Disabled" + } + field { + name = "Action log" + value = arguments.modActionLog?.mention ?: "Disabled" + } + field { + name = "Log publicly" + value = when (arguments.logPublicly) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } + field { + name = "Quick timeout length" + value = arguments.quickTimeoutLength.interval() ?: "No quick timeout length set" + } + field { + name = "Warning Auto-punishments" + value = when (arguments.warnAutoPunishments) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } + field { + name = "Ban DM Message" + value = arguments.banDmMessage ?: "No custom Ban DM message set" + } + field { + name = "Auto-invite Moderator Role" + value = when (arguments.autoInviteModeratorRole) { + true -> "Enabled" + false -> "Disabled" + null -> "Disabled" + } + } + footer { + text = "Configured by ${user.asUserOrNull()?.username}" + } +} + +suspend fun EmbedBuilder.loggingEmbed(arguments: LoggingArgs, guild: GuildBehavior?, user: UserBehavior) { + title = "Configuration: Logging" + field { + name = "Message Delete Logs" + value = if (arguments.enableMessageDeleteLogs && arguments.messageLogs != null) { + arguments.messageLogs!!.mention + } else { + "Disabled" + } + } + field { + name = "Message Edit Logs" + value = if (arguments.enableMessageEditLogs && arguments.messageLogs != null) { + arguments.messageLogs!!.mention + } else { + "Disabled" + } + } + field { + name = "Member Logs" + value = if (arguments.enableMemberLogging && arguments.memberLog != null) { + arguments.memberLog!!.mention + } else { + "Disabled" + } + } + + field { + name = "Public Member logs" + value = if (arguments.enablePublicMemberLogging && arguments.publicMemberLog != null) { + arguments.publicMemberLog!!.mention + } else { + "Disabled" + } + } + if (arguments.enableMemberLogging && arguments.publicMemberLog != null) { + val config = LoggingConfigCollection().getConfig(guild!!.id) + if (config != null) { + field { + name = "Join Message" + value = config.publicMemberLogData?.joinMessage.trimmedContents(256)!! + } + field { + name = "Leave Message" + value = config.publicMemberLogData?.leaveMessage.trimmedContents(256)!! + } + field { + name = "Ping on join" + value = config.publicMemberLogData?.pingNewUsers.toString() + } + } + } + + footer { + text = "Configured by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt index fbe41041..91c25685 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt @@ -5,7 +5,6 @@ import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.behavior.interaction.modal import dev.kord.core.entity.channel.TextChannel -import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.embed import dev.kordex.core.checks.anyGuild import dev.kordex.core.checks.hasPermission @@ -18,8 +17,8 @@ import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.entities.LoggingConfigData import org.hyacinthbots.lilybot.database.entities.PublicMemberLogData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.extensions.config.utils.loggingEmbed import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms -import org.hyacinthbots.lilybot.utils.trimmedContents @OptIn(UnsafeAPI::class) suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingArgs) { @@ -109,65 +108,6 @@ suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingA } } - suspend fun EmbedBuilder.loggingEmbed() { - title = "Configuration: Logging" - field { - name = "Message Delete Logs" - value = if (arguments.enableMessageDeleteLogs && arguments.messageLogs != null) { - arguments.messageLogs!!.mention - } else { - "Disabled" - } - } - field { - name = "Message Edit Logs" - value = if (arguments.enableMessageEditLogs && arguments.messageLogs != null) { - arguments.messageLogs!!.mention - } else { - "Disabled" - } - } - field { - name = "Member Logs" - value = if (arguments.enableMemberLogging && arguments.memberLog != null) { - arguments.memberLog!!.mention - } else { - "Disabled" - } - } - - field { - name = "Public Member logs" - value = if (arguments.enablePublicMemberLogging && arguments.publicMemberLog != null) { - arguments.publicMemberLog!!.mention - } else { - "Disabled" - } - } - if (arguments.enableMemberLogging && arguments.publicMemberLog != null) { - val config = LoggingConfigCollection().getConfig(guild!!.id) - if (config != null) { - field { - name = "Join Message" - value = config.publicMemberLogData?.joinMessage.trimmedContents(256)!! - } - field { - name = "Leave Message" - value = config.publicMemberLogData?.leaveMessage.trimmedContents(256)!! - } - field { - name = "Ping on join" - value = config.publicMemberLogData?.pingNewUsers.toString() - } - } - } - - footer { - text = "Configured by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } - var publicMemberLogData: PublicMemberLogData? = null if (arguments.enablePublicMemberLogging) { val modalObj = PublicLoggingModal() @@ -209,9 +149,7 @@ suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingA if (!arguments.enablePublicMemberLogging) { ackEphemeral() } - respondEphemeral { - embed { loggingEmbed() } - } + respondEphemeral { embed { loggingEmbed(arguments, guild, user) } } val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!) @@ -222,10 +160,6 @@ suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingA return@action } - utilityLog.createMessage { - embed { - loggingEmbed() - } - } + utilityLog.createMessage { embed { loggingEmbed(arguments, guild, user) } } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt index d446d57b..49c10cff 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt @@ -17,7 +17,11 @@ import dev.kordex.core.extensions.event import dev.kordex.core.utils.botHasPermissions import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection +import org.hyacinthbots.lilybot.database.collections.ModerationActionCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.extensions.moderation.utils.ModerationAction +import org.hyacinthbots.lilybot.utils.baseModerationEmbed +import org.hyacinthbots.lilybot.utils.dmNotificationStatusEmbedField import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.requiredConfigs @@ -117,6 +121,21 @@ class MemberLogging : Extension() { color = DISCORD_RED } + // Check if the member was kicked, and send a log if they were. + val kickData = ModerationActionCollection().getAction(ModerationAction.KICK, event.guildId, event.user.id) + if (kickData != null) { + val targetUser = event.kord.getUser(kickData.targetUserId)!! + val actioner = kickData.data.actioner?.let { event.kord.getUser(it) }!! + getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, event.guild)?.createEmbed { + title = "Kicked a user" + description = "${targetUser.mention} has been kicked" + image = kickData.data.imageUrl + baseModerationEmbed(kickData.data.reason, targetUser, actioner) + dmNotificationStatusEmbedField(kickData.data.dmOutcome != null, kickData.data.dmOutcome) + timestamp = Clock.System.now() + } + } + if (config != null && config.enablePublicMemberLogs) { var publicLog = guildFor(event)?.getChannelOfOrNull(config.publicMemberLog!!) val permissions = publicLog?.botHasPermissions(Permission.SendMessages, Permission.EmbedLinks) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt index 764da176..686d1212 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt @@ -7,13 +7,11 @@ import dev.kord.core.behavior.ban import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.edit -import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.behavior.interaction.followup.edit import dev.kord.core.behavior.reply import dev.kord.core.entity.Guild import dev.kord.core.entity.Message import dev.kord.core.entity.User -import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.interaction.followup.EphemeralFollowupMessage import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.embed @@ -55,12 +53,15 @@ import kotlinx.datetime.Clock import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone import kotlinx.datetime.plus +import org.hyacinthbots.lilybot.database.collections.ModerationActionCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.TemporaryBanCollection import org.hyacinthbots.lilybot.database.collections.WarnCollection +import org.hyacinthbots.lilybot.database.entities.ActionData import org.hyacinthbots.lilybot.database.entities.TemporaryBanData +import org.hyacinthbots.lilybot.database.entities.TimeData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.moderation.utils.ModerationActions +import org.hyacinthbots.lilybot.extensions.moderation.utils.ModerationAction import org.hyacinthbots.lilybot.utils.HYACINTH_GITHUB import org.hyacinthbots.lilybot.utils.baseModerationEmbed import org.hyacinthbots.lilybot.utils.dmNotificationStatusEmbedField @@ -137,23 +138,23 @@ class ModerationCommands : Extension() { placeholder = "Select action..." maximumChoices = 1 // Prevent selecting multiple options at once - option(label = "Ban user", ModerationActions.BAN.name) { + option("Ban user", ModerationAction.BAN.name) { description = "Ban the user that sent this message" } - option("Soft-ban", ModerationActions.SOFT_BAN.name) { + option("Soft-ban", ModerationAction.SOFT_BAN.name) { description = "Soft-ban the user that sent this message" } - option("Kick", ModerationActions.KICK.name) { + option("Kick", ModerationAction.KICK.name) { description = "Kick the user that sent this message" } - option("Timeout", ModerationActions.TIMEOUT.name) { + option("Timeout", ModerationAction.TIMEOUT.name) { description = "Timeout the user that sent this message" } - option("Warn", ModerationActions.WARN.name) { + option("Warn", ModerationAction.WARN.name) { description = "Warn the user that sent this message" } @@ -169,7 +170,7 @@ class ModerationCommands : Extension() { val modConfig = ModerationConfigCollection().getConfig(guild!!.id) when (option) { - ModerationActions.BAN.name -> { + ModerationAction.BAN.name -> { val dm = sender.dm { embed { title = "You have been banned from ${guild?.asGuildOrNull()?.name}" @@ -178,6 +179,18 @@ class ModerationCommands : Extension() { color = DISCORD_GREEN } } + ModerationActionCollection().addAction( + ModerationAction.BAN, guild!!.id, senderId, + ActionData( + user.id, + null, + null, + "Quick banned $reasonSuffix", + dm != null, + true, + null + ) + ) sender.ban { reason = @@ -193,7 +206,7 @@ class ModerationCommands : Extension() { "for sending this message." } } - } catch (e: KtorRequestException) { + } catch (_: KtorRequestException) { channel.createEmbed { title = "Banned." description = "${sender.mention} user was banned " + @@ -202,27 +215,13 @@ class ModerationCommands : Extension() { } } - loggingChannel.createEmbed { - title = "Banned a user" - description = "${ - sender.mention - } has been banned!" - baseModerationEmbed( - "Quick banned via moderate menu $reasonSuffix", - sender, - user - ) - dmNotificationStatusEmbedField(dm, true) - timestamp = Clock.System.now() - } - menuMessage?.edit { content = "Banned a user." components { removeAll() } } } - ModerationActions.SOFT_BAN.name -> { + ModerationAction.SOFT_BAN.name -> { val dm = sender.dm { embed { title = "You have been soft-banned from ${guild?.asGuildOrNull()?.name}" @@ -232,11 +231,28 @@ class ModerationCommands : Extension() { } } + ModerationActionCollection().addAction( + ModerationAction.SOFT_BAN, guild!!.id, senderId, + ActionData( + user.id, + null, + null, + "Quick banned $reasonSuffix", + dm != null, + true, + null + ) + ) + sender.ban { reason = "Quick soft-banned $reasonSuffix" } + ModerationActionCollection().shouldIgnoreAction( + ModerationAction.SOFT_BAN, guild!!.id, senderId + ) + guild!!.unban(senderId, "Quick soft-ban unban") if (modConfig?.publicLogging != null && modConfig.publicLogging == true) { @@ -248,7 +264,7 @@ class ModerationCommands : Extension() { "for sending this message." } } - } catch (e: KtorRequestException) { + } catch (_: KtorRequestException) { channel.createEmbed { title = "Soft-Banned." description = "${sender.mention} user was soft-banned " + @@ -257,27 +273,13 @@ class ModerationCommands : Extension() { } } - loggingChannel.createEmbed { - title = "Soft-Banned a user" - description = "${ - sender.mention - } has been soft-banned!" - baseModerationEmbed( - "Quick soft-banned via moderate menu $reasonSuffix", - sender, - user - ) - dmNotificationStatusEmbedField(dm, true) - timestamp = Clock.System.now() - } - menuMessage?.edit { content = "Soft-Banned a user." components { removeAll() } } } - ModerationActions.KICK.name -> { + ModerationAction.KICK.name -> { val dm = sender.dm { embed { title = "You have been kicked from ${guild?.asGuildOrNull()?.name}" @@ -296,7 +298,7 @@ class ModerationCommands : Extension() { "for sending this message." } } - } catch (e: KtorRequestException) { + } catch (_: KtorRequestException) { channel.createEmbed { title = "Kicked." description = "${sender.mention} user was kicked " + @@ -305,19 +307,18 @@ class ModerationCommands : Extension() { } } - loggingChannel.createEmbed { - title = "Kicked a user" - description = "${ - sender.mention - } has been kicked!" - baseModerationEmbed( + ModerationActionCollection().addAction( + ModerationAction.KICK, guild!!.id, senderId, + ActionData( + user.id, + null, + null, "Quick kicked via moderate menu $reasonSuffix", - sender, - user + dm != null, + true, + null ) - dmNotificationStatusEmbedField(dm, true) - timestamp = Clock.System.now() - } + ) menuMessage?.edit { content = "Kicked a user." @@ -325,7 +326,7 @@ class ModerationCommands : Extension() { } } - ModerationActions.TIMEOUT.name -> { + ModerationAction.TIMEOUT.name -> { val timeoutTime = ModerationConfigCollection().getConfig(guild!!.id)?.quickTimeoutLength if (timeoutTime == null) { @@ -356,7 +357,7 @@ class ModerationCommands : Extension() { "${timeoutTime.interval()} for sending this message." } } - } catch (e: KtorRequestException) { + } catch (_: KtorRequestException) { channel.createEmbed { title = "Timed-out." description = "${sender.mention} user was timed-out for " + @@ -365,24 +366,18 @@ class ModerationCommands : Extension() { } } - loggingChannel.createEmbed { - title = "Timed-out a user" - description = "${ - sender.mention - } has be timed-out!" - baseModerationEmbed( + ModerationActionCollection().addAction( + ModerationAction.TIMEOUT, guild!!.id, senderId, + ActionData( + user.id, + null, + TimeData(timeoutTime, null, null, null), "Quick timed-out via moderate menu $reasonSuffix", - sender, - user + dm != null, + null, + null ) - dmNotificationStatusEmbedField(dm, true) - field { - name = "Length" - value = modConfig?.quickTimeoutLength.interval() - ?: "Failed to load timeout length" - } - timestamp = Clock.System.now() - } + ) menuMessage?.edit { content = "Timed-out a user." @@ -390,7 +385,7 @@ class ModerationCommands : Extension() { } } - ModerationActions.WARN.name -> { + ModerationAction.WARN.name -> { WarnCollection().setWarn(senderId, guild!!.id, false) val strikes = WarnCollection().getWarn(senderId, guild!!.id)?.strikes @@ -454,7 +449,7 @@ class ModerationCommands : Extension() { "for sending this message." } } - } catch (e: KtorRequestException) { + } catch (_: KtorRequestException) { channel.createEmbed { title = "Warned." description = "${sender.mention} user was warned " + @@ -505,35 +500,30 @@ class ModerationCommands : Extension() { } else { arguments.reason } - }\n${if (arguments.softBan) { - "You were soft-banned. You are free to rejoin without the need to be unbanned" - } else { - "" - } + }\n${ + if (arguments.softBan) { + "You were soft-banned. You are free to rejoin without the need to be unbanned" + } else { + "" + } }" } } } - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - if (arguments.softBan) { - title = "Soft-Banned a User" - description = "${arguments.userArgument.mention} has been soft-banned!" - } else { - title = "Banned a User" - description = "${arguments.userArgument.mention} has been banned!" - } - baseModerationEmbed(arguments.reason, arguments.userArgument, user) - image = arguments.image?.url - dmNotificationStatusEmbedField(dmStatus, arguments.dm) - timestamp = Clock.System.now() - field { - name = "Days of messages deleted:" - value = if (arguments.softBan && arguments.messages == 0) "3" else arguments.messages.toString() - inline = false - } - } - } + + ModerationActionCollection().addAction( + if (arguments.softBan) ModerationAction.SOFT_BAN else ModerationAction.BAN, + guild!!.id, arguments.userArgument.id, + ActionData( + user.id, + arguments.messages, + null, + arguments.reason, + dmStatus != null, + arguments.dm, + arguments.image?.url + ) + ) if (modConfig?.publicLogging == true) { event.interaction.channel.createEmbed { @@ -557,10 +547,15 @@ class ModerationCommands : Extension() { } } - if (arguments.softBan) guild?.unban(arguments.userArgument.id, "User was soft-banned") + if (arguments.softBan) { + ModerationActionCollection().declareActionToIgnore( + ModerationAction.UNBAN, guild?.id!!, arguments.userArgument.id + ) + guild?.unban(arguments.userArgument.id, "User was soft-banned. **SOFT-BAN**") + } respond { - content = if (arguments.softBan) { "Soft-banned " } else { "Banned " } + arguments.userArgument.mention + content = if (arguments.softBan) "Soft-banned " else "Banned " + arguments.userArgument.mention } } } @@ -594,22 +589,19 @@ class ModerationCommands : Extension() { } } } - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - title = "Temporarily banned a user" - description = - "${arguments.userArgument.mention} has been temporarily banned!" - image = arguments.image?.url - baseModerationEmbed(arguments.reason, arguments.userArgument, user) - dmNotificationStatusEmbedField(dmStatus, arguments.dm) - field { - name = "Duration:" - value = - duration.toDiscord(TimestampType.Default) + " (${arguments.duration.interval()})" - } - timestamp = now - } - } + + ModerationActionCollection().addAction( + ModerationAction.TEMP_BAN, guild!!.id, arguments.userArgument.id, + ActionData( + user.id, + arguments.messages, + TimeData(arguments.duration, duration), + arguments.reason, + dmStatus != null, + arguments.dm, + arguments.image?.url + ) + ) if (modConfig?.publicLogging == true) { event.interaction.channel.createEmbed { @@ -624,7 +616,7 @@ class ModerationCommands : Extension() { ) guild?.ban(arguments.userArgument.id) { - reason = arguments.reason + reason = arguments.reason + " **TEMPORARY-BAN**" deleteMessageDuration = DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) } @@ -706,52 +698,22 @@ class ModerationCommands : Extension() { action { val tempBan = TemporaryBanCollection().getUserTempBan(this.getGuild()!!.id, arguments.userArgument.id) if (tempBan == null) { - guild?.unban(arguments.userArgument.id) - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - title = "Unbanned a user" - description = "${arguments.userArgument.mention} has been unbanned!\n${ - arguments.userArgument.id - } (${arguments.userArgument.username})" - field { - name = "Reason:" - value = arguments.reason - } - footer { - text = user.asUserOrNull()?.username ?: "Unable to get user username" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - timestamp = Clock.System.now() - color = DISCORD_GREEN - } - } + ModerationActionCollection().addAction( + ModerationAction.UNBAN, guild!!.id, arguments.userArgument.id, + ActionData( + user.id, null, null, arguments.reason, null, null, null + ) + ) + guild?.unban(arguments.userArgument.id, arguments.reason) } else { - guild?.unban(arguments.userArgument.id) + ModerationActionCollection().addAction( + ModerationAction.UNBAN, guild!!.id, arguments.userArgument.id, + ActionData( + user.id, null, null, arguments.reason + "**TEMPORARY-BAN", null, null, null + ) + ) + guild?.unban(arguments.userArgument.id, arguments.reason + "**TEMPORARY-BAN**") TemporaryBanCollection().removeTempBan(guild!!.id, arguments.userArgument.id) - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - title = "Temporary Ban Removed" - description = "${arguments.userArgument.mention} has had their temporary ban removed!\n${ - arguments.userArgument.id - } (${arguments.userArgument.username})" - field { - name = "Reason:" - value = arguments.reason - } - field { - name = "Original Action taker:" - value = guild?.getMemberOrNull(tempBan.moderatorUserId)?.username - ?: this@ephemeralSlashCommand.kord.getUser(tempBan.moderatorUserId)?.username - ?: "Unable to get username" - } - footer { - text = user.asUserOrNull()?.username ?: "Unable to get user username" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - timestamp = Clock.System.now() - color = DISCORD_GREEN - } - } } respond { content = "Unbanned user." @@ -783,16 +745,12 @@ class ModerationCommands : Extension() { } } } - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - title = "Kicked a user" - description = "${arguments.userArgument.mention} has been kicked!" - image = arguments.image?.url - baseModerationEmbed(arguments.reason, arguments.userArgument, user) - dmNotificationStatusEmbedField(dmStatus, arguments.dm) - timestamp = Clock.System.now() - } - } + ModerationActionCollection().addAction( + ModerationAction.KICK, guild!!.id, arguments.userArgument.id, + ActionData( + user.id, null, null, arguments.reason, dmStatus != null, arguments.dm, arguments.image?.url + ) + ) if (modConfig?.publicLogging == true) { event.interaction.channel.createEmbed { @@ -840,20 +798,19 @@ class ModerationCommands : Extension() { } } - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - title = "Timeout" - image = arguments.image?.url - baseModerationEmbed(arguments.reason, arguments.userArgument, user) - dmNotificationStatusEmbedField(dmStatus, arguments.dm) - timestamp = Clock.System.now() - field { - name = "Duration:" - value = duration.toDiscord(TimestampType.Default) + " (${durationArg.interval()})" - inline = false - } - } - } + ModerationActionCollection().addAction( + ModerationAction.TIMEOUT, guild!!.id, arguments.userArgument.id, + ActionData( + user.id, + null, + TimeData(durationArg, duration, Clock.System.now(), duration), + arguments.reason, + dmStatus != null, + arguments.dm, + arguments.image?.url + ) + ) + if (modConfig?.publicLogging == true) { event.interaction.channel.createEmbed { title = "Timeout" @@ -898,23 +855,13 @@ class ModerationCommands : Extension() { } } } - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - title = "Timeout Removed" - dmNotificationStatusEmbedField(dmStatus, arguments.dm) - field { - name = "User:" - value = "${arguments.userArgument.username} \n${arguments.userArgument.id}" - inline = false - } - footer { - text = "Requested by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - timestamp = Clock.System.now() - color = DISCORD_BLACK - } - } + + ModerationActionCollection().addAction( + ModerationAction.REMOVE_TIMEOUT, guild!!.id, arguments.userArgument.id, + ActionData( + user.id, null, null, null, dmStatus != null, arguments.dm, null + ) + ) arguments.userArgument.asMemberOrNull(guild!!.id)?.edit { timeoutUntil = null @@ -1111,26 +1058,20 @@ class ModerationCommands : Extension() { continue } + ModerationActionCollection().addAction( + ModerationAction.UNBAN, guild.id, it.bannedUserId, + ActionData( + it.moderatorUserId, + null, + TimeData(null, null, it.startTime, it.endTime), + "**temporary-ban-expire**", + null, + null, + null + ) + ) + guild.unban(it.bannedUserId, "Temporary Ban expired") - val modChannelId = ModerationConfigCollection().getConfig(guild.id)?.channel - if (modChannelId != null) { - val modChannel = guild.getChannelOfOrNull(modChannelId) - modChannel?.createMessage { - embed { - title = "Temporary ban Completed" - description = "${kord.getUser(it.bannedUserId)?.username} has served their temporary ban" - field { - name = "Initial Ban date:" - value = it.startTime.toDiscord(TimestampType.ShortDateTime) - } - color = DISCORD_GREEN - footer { - text = "Initial action taker: ${kord.getUser(it.moderatorUserId)?.username}" - icon = kord.getUser(it.moderatorUserId)?.avatar?.cdnUrl?.toUrl() - } - } - } - } TemporaryBanCollection().removeTempBan(it.guildId, it.bannedUserId) } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt index f018f5b7..df1e464f 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt @@ -4,7 +4,6 @@ import dev.kord.common.entity.Permission import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.entity.channel.TextChannel -import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.embed import dev.kordex.core.checks.anyGuild import dev.kordex.core.checks.hasPermission @@ -14,9 +13,9 @@ import dev.kordex.core.utils.botHasPermissions import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.entities.ModerationConfigData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.extensions.config.utils.moderationEmbed import org.hyacinthbots.lilybot.utils.canPingRole import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms -import org.hyacinthbots.lilybot.utils.interval suspend fun SlashCommand<*, *, *>.moderationCommand() = ephemeralSubCommand(::ModerationArgs) { @@ -92,56 +91,9 @@ suspend fun SlashCommand<*, *, *>.moderationCommand() = } } - suspend fun EmbedBuilder.moderationEmbed() { - title = "Configuration: Moderation" - field { - name = "Moderators" - value = arguments.moderatorRole?.mention ?: "Disabled" - } - field { - name = "Action log" - value = arguments.modActionLog?.mention ?: "Disabled" - } - field { - name = "Log publicly" - value = when (arguments.logPublicly) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } - } - field { - name = "Quick timeout length" - value = arguments.quickTimeoutLength.interval() ?: "No quick timeout length set" - } - field { - name = "Warning Auto-punishments" - value = when (arguments.warnAutoPunishments) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } - } - field { - name = "Ban DM Message" - value = arguments.banDmMessage ?: "No custom Ban DM message set" - } - field { - name = "Auto-invite Moderator Role" - value = when (arguments.autoInviteModeratorRole) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } - } - footer { - text = "Configured by ${user.asUserOrNull()?.username}" - } - } - respond { embed { - moderationEmbed() + moderationEmbed(arguments, user) } } @@ -170,7 +122,7 @@ suspend fun SlashCommand<*, *, *>.moderationCommand() = utilityLog.createMessage { embed { - moderationEmbed() + moderationEmbed(arguments, user) } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/events/ModerationEvents.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/events/ModerationEvents.kt new file mode 100644 index 00000000..8b4d551d --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/events/ModerationEvents.kt @@ -0,0 +1,304 @@ +package org.hyacinthbots.lilybot.extensions.moderation.events + +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.behavior.channel.createEmbed +import dev.kord.core.event.guild.BanAddEvent +import dev.kord.core.event.guild.BanRemoveEvent +import dev.kord.core.event.guild.MemberUpdateEvent +import dev.kordex.core.DISCORD_GREEN +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.DISCORD_YELLOW +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.event +import dev.kordex.core.time.TimestampType +import dev.kordex.core.time.toDiscord +import dev.kordex.core.utils.timeoutUntil +import kotlinx.datetime.Clock +import org.hyacinthbots.lilybot.database.collections.ModerationActionCollection +import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection +import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.extensions.moderation.utils.ModerationAction +import org.hyacinthbots.lilybot.utils.baseModerationEmbed +import org.hyacinthbots.lilybot.utils.dmNotificationStatusEmbedField +import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.interval + +class ModerationEvents : Extension() { + override val name: String = "moderation-events" + + override suspend fun setup() { + event { + check { anyGuild() } + action { + // Do not log if the moderation system is disabled + if (ModerationConfigCollection().getConfig(event.guildId)?.enabled != true) return@action + // If the ban doesn't exist then... ???? + event.getBanOrNull() ?: return@action + var existingAction = ModerationActionCollection().getAction( + ModerationAction.BAN, event.guildId, event.user.id + ) + if (existingAction == null) { + existingAction = ModerationActionCollection().getAction( + ModerationAction.SOFT_BAN, event.guildId, event.user.id + ) + } + + if (existingAction == null) { + existingAction = ModerationActionCollection().getAction( + ModerationAction.TEMP_BAN, event.guildId, event.user.id + ) + } + + if (existingAction != null && existingAction.targetUserId != event.user.id) { + return@action + } + + if (existingAction != null) { + getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, event.getGuild())?.createEmbed { + title = when (existingAction.actionType) { + ModerationAction.BAN -> "Banned a user" + ModerationAction.SOFT_BAN -> "Soft-banned a user" + ModerationAction.TEMP_BAN -> "Temp-banned a user" + else -> null // Theoretically this should never occur but the compiler cries otherwise + } + + description = "${event.user.mention} has been ${ + if (existingAction.data.reason?.contains("quick ban", true) == false) { + when (existingAction.actionType) { + ModerationAction.BAN -> "banned" + ModerationAction.SOFT_BAN -> "soft-banned" + ModerationAction.TEMP_BAN -> "temporarily banned" + else -> null // Again should theoretically never occur, but compiler + } + } else { + when (existingAction.actionType) { + ModerationAction.BAN -> existingAction.data.reason + ModerationAction.SOFT_BAN -> existingAction.data.reason + else -> null + } + } + }" + baseModerationEmbed( + existingAction.data.reason, + event.user, + UserBehavior(existingAction.data.actioner!!, kord) + ) + image = existingAction.data.imageUrl + dmNotificationStatusEmbedField(existingAction.data.dmOutcome, existingAction.data.dmOverride) + timestamp = Clock.System.now() + if (existingAction.data.deletedMessages != null) { + field { + name = "Days of messages deleted" + value = + if (existingAction.actionType == ModerationAction.SOFT_BAN && + existingAction.data.deletedMessages == 0 + ) { + "3" + } else { + existingAction.data.deletedMessages.toString() + } + inline = false + } + } + if (existingAction.data.timeData != null) { + field { + name = "Duration:" + value = + existingAction.data.timeData.durationInst?.toDiscord(TimestampType.Default) + + " (${existingAction.data.timeData.durationDtp?.interval()})" + } + } + } + + ModerationActionCollection().removeAction( + existingAction.actionType, + event.guildId, + event.user.id + ) + } else { + getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, event.getGuild())?.createEmbed { + title = "Banned a User" + description = "${event.user.mention} has been banned!" + baseModerationEmbed(event.getBan().reason, event.user, null) + timestamp = Clock.System.now() + } + } + } + } + event { + check { anyGuild() } + action { + // Do not log if the moderation system is disabled + if (ModerationConfigCollection().getConfig(event.guildId)?.enabled != true) return@action + val ignore = ModerationActionCollection().shouldIgnoreAction( + ModerationAction.UNBAN, + event.guildId, + event.user.id + ) + if (ignore != null && ignore) { + return@action + } + + val existingAction = + ModerationActionCollection().getAction(ModerationAction.UNBAN, event.guildId, event.user.id) + if (existingAction != null && existingAction.targetUserId != event.user.id) { + return@action + } + + if (existingAction != null) { + val isTempUnban = existingAction.data.reason?.contains("**temporary-ban**", true) == true + + getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, event.getGuild())?.createEmbed { + title = if (isTempUnban) "Temporary ban removed" else "Unbanned a user" + description = "${event.user.mention} has " + + "${if (isTempUnban) "had their temporary ban removed!" else "been unbanned!"}\n" + + "${event.user.id} (${event.user.username})" + if (existingAction.data.reason?.contains("**temporary-ban-expire**") == false) { + field { + name = "Reason:" + value = existingAction.data.reason.toString() + } + } else { + field { + name = "Initial Ban date:" + value = existingAction.data.timeData?.start?.toDiscord(TimestampType.ShortDateTime) + .toString() + } + } + + if (existingAction.data.actioner != null) { + val user = UserBehavior(existingAction.data.actioner, kord).asUserOrNull() + footer { + text = user?.username ?: "Unable to get username" + icon = user?.avatar?.cdnUrl?.toUrl() + } + } + timestamp = Clock.System.now() + color = DISCORD_GREEN + } + ModerationActionCollection().removeAction( + existingAction.actionType, + event.guildId, + event.user.id + ) + } + } + } + event { + check { anyGuild() } + action { + // Do not log if the moderation system is disabled + if (ModerationConfigCollection().getConfig(event.guildId)?.enabled != true) return@action + val channel = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, event.guild) + if (event.old?.timeoutUntil != event.member.timeoutUntil) { + var existingAction = + ModerationActionCollection().getAction(ModerationAction.TIMEOUT, event.guildId, event.member.id) + + if (existingAction == null) { + existingAction = + ModerationActionCollection().getAction( + ModerationAction.REMOVE_TIMEOUT, + event.guildId, + event.member.id + ) + } + + if (existingAction != null) { + val data = existingAction.data + val isRemove = existingAction.actionType == ModerationAction.REMOVE_TIMEOUT + val user = UserBehavior(existingAction.targetUserId, kord).asUserOrNull() + val actioner = data.actioner?.let { UserBehavior(it, kord) }?.asUserOrNull() + channel?.createEmbed { + if (isRemove) { + title = "Timeout removed" + dmNotificationStatusEmbedField(data.dmOutcome, data.dmOverride) + field { + name = "User:" + value = "${user?.username} (${user?.id})" + } + footer { + text = "Requested by ${actioner?.username}" + icon = user?.avatar?.cdnUrl?.toUrl() + } + timestamp = Clock.System.now() + color = DISCORD_GREEN + } else { + title = "Timeout" + image = data.imageUrl + baseModerationEmbed(data.reason, event.member, user) + dmNotificationStatusEmbedField(data.dmOutcome, data.dmOverride) + timestamp = data.timeData?.start + color = DISCORD_RED + field { + name = "Duration" + value = data.timeData?.durationInst?.toDiscord(TimestampType.Default) + + " (${data.timeData?.durationDtp.interval()})" + } + } + } + + ModerationActionCollection().removeAction( + existingAction.actionType, + event.guild.id, + event.member.id + ) + } else { + channel?.createEmbed { + if (event.member.timeoutUntil != null) { + title = "Timeout" + color = DISCORD_RED + field { + name = "Duration" + value = event.member.timeoutUntil?.toDiscord(TimestampType.Default) ?: "0" + } + } else { + title = "Removed timeout" + color = DISCORD_GREEN + } + field { + name = "User" + value = event.member.mention + "(${event.member.id})" + } + description = "via Discord menus" + timestamp = Clock.System.now() + } + } + } else { + val newRoles = mutableListOf() + val oldRoles = mutableListOf() + event.member.roleBehaviors.forEach { newRoles.add(it.mention) } + event.old?.roleBehaviors?.forEach { oldRoles.add(it.mention) } + channel?.createEmbed { + title = "Member updated" + description = "${event.member.username} has been updated" + if (event.member.nickname != event.old?.nickname) { + field { + name = "Nickname change" + value = "Old: ${event.old?.nickname}\nNew: ${event.member.nickname}" + } + } + if (event.member.isOwner() != event.old?.isOwner()) { + field { + name = "Became server owner" + } + } + if (event.member.roles != event.old?.roles) { + field { + name = "New Roles" + value = newRoles.joinToString(", ") + inline = true + } + field { + name = "Old roles" + value = oldRoles.joinToString(", ") + inline = true + } + } + color = DISCORD_YELLOW + } + } + } + } + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/utils/ModerationActions.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/utils/ModerationAction.kt similarity index 51% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/utils/ModerationActions.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/utils/ModerationAction.kt index 62f22296..7d655770 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/utils/ModerationActions.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/utils/ModerationAction.kt @@ -1,9 +1,13 @@ package org.hyacinthbots.lilybot.extensions.moderation.utils -enum class ModerationActions { +enum class ModerationAction { BAN, SOFT_BAN, + TEMP_BAN, + UNBAN, KICK, + TIMEOUT, + REMOVE_TIMEOUT, WARN, - TIMEOUT + REMOVE_WARN } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GalleryChannel.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GalleryChannel.kt similarity index 98% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GalleryChannel.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GalleryChannel.kt index 84733c03..b4c9c903 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GalleryChannel.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GalleryChannel.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kord.common.entity.MessageType import dev.kord.common.entity.Permission @@ -222,7 +222,7 @@ class GalleryChannel : Extension() { // Delete the explanation after 3 seconds. If an exception is thrown, the // message has already been deleted response.delete(2.5.seconds.inWholeMilliseconds) - } catch (e: EntityNotFoundException) { + } catch (_: EntityNotFoundException) { // The message that we're attempting to delete has already been deleted. } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Github.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt similarity index 97% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Github.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt index fe300ecf..c844f64f 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Github.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kord.common.entity.Permission import dev.kord.rest.builder.message.embed @@ -93,7 +93,7 @@ class Github : Extension() { } else { try { github.getRepository(repository)?.getIssue(arguments.issue) - } catch (e: GHFileNotFoundException) { + } catch (_: GHFileNotFoundException) { respond { embed { title = "Unable to find issue number! Make sure this issue exists" @@ -102,7 +102,7 @@ class Github : Extension() { return@action } } - } catch (e: IOException) { + } catch (_: IOException) { val iterator: PagedIterator? = github.searchIssues() ?.q("${arguments.issue} repo:$repository") ?.order(GHDirection.DESC) @@ -112,7 +112,7 @@ class Github : Extension() { // Run a quick check on the iterator, in case the repository owner org/user doesn't exist try { iterator!!.hasNext() - } catch (e: GHException) { + } catch (_: GHException) { respond { embed { title = "Unable to access repository, make sure this repository exists!" @@ -140,7 +140,7 @@ class Github : Extension() { val num = issue!!.number try { issue = github.getRepository(repository)?.getIssue(num) - } catch (e: GHFileNotFoundException) { + } catch (_: GHFileNotFoundException) { respond { embed { title = "Unable to find issue number! Make sure this issue exists" @@ -245,7 +245,7 @@ class Github : Extension() { inline = false } } - } catch (ioException: IOException) { + } catch (_: IOException) { field { name = "Author:" value = "Unknown Author" @@ -325,7 +325,7 @@ class Github : Extension() { category = "extensions.util.Github.repository.getRepository" message = "Repository found" } - } catch (exception: IOException) { + } catch (_: IOException) { sentry.breadcrumb(BreadcrumbType.Error) { category = "extensions.util.Github.repository.getRepository" message = "Repository not found" @@ -406,7 +406,7 @@ class Github : Extension() { category = "extensions.util.Github.user.getUser" message = "User found" } - } catch (exception: IOException) { + } catch (_: IOException) { sentry.breadcrumb(BreadcrumbType.Error) { category = "extensions.util.Github.user.getUser" message = "Unable to find user" @@ -550,7 +550,7 @@ class Github : Extension() { } else { github.getRepository(arguments.defaultRepo) } - } catch (e: IOException) { + } catch (_: IOException) { respond { content = "GitHub repository not found! Please make sure this repository exists" } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GuildAnnouncements.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GuildAnnouncements.kt similarity index 98% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GuildAnnouncements.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GuildAnnouncements.kt index dfe5bfb0..08e5e67b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/GuildAnnouncements.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GuildAnnouncements.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kord.common.Color import dev.kord.common.entity.ButtonStyle diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/InfoCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/InfoCommands.kt similarity index 98% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/InfoCommands.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/InfoCommands.kt index c668b15e..35a61b85 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/InfoCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/InfoCommands.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kord.rest.builder.message.create.MessageCreateBuilder import dev.kord.rest.builder.message.embed diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/NewsChannelPublishing.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/NewsChannelPublishing.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/NewsChannelPublishing.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/NewsChannelPublishing.kt index 6e3db35a..7f79935d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/NewsChannelPublishing.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/NewsChannelPublishing.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kord.common.Locale import dev.kord.common.asJavaLocale diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/PublicUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/PublicUtilities.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/PublicUtilities.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/PublicUtilities.kt index a901a97c..9b51cc7e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/PublicUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/PublicUtilities.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission @@ -281,7 +281,7 @@ class PublicUtilities : Extension() { } } } - } catch (e: KtorRequestException) { + } catch (_: KtorRequestException) { // Avoid hard failing on permission error, since the public won't know what it means respond { content = "Error sending message to moderators. Please ask the moderators to check" + diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Reminders.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Reminders.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Reminders.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Reminders.kt index 8eeb2d38..99aa2d62 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Reminders.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Reminders.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/RoleMenu.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/RoleMenu.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/RoleMenu.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/RoleMenu.kt index c16a7dd9..5ce716c2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/RoleMenu.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/RoleMenu.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StartupHooks.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/StartupHooks.kt similarity index 97% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StartupHooks.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/StartupHooks.kt index 4ac0a0b2..3fc602b7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StartupHooks.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/StartupHooks.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.getChannelOfOrNull diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StatusPing.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/StatusPing.kt similarity index 93% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StatusPing.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/StatusPing.kt index d1b7b12d..bfac3ca0 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/StatusPing.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/StatusPing.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kordex.core.extensions.Extension import dev.kordex.core.utils.scheduling.Scheduler diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Tags.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Tags.kt similarity index 99% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Tags.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Tags.kt index 973e4d0c..20c1a808 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/commands/Tags.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Tags.kt @@ -1,4 +1,4 @@ -package org.hyacinthbots.lilybot.extensions.utils.commands +package org.hyacinthbots.lilybot.extensions.utility.commands import dev.kord.common.Locale import dev.kord.common.asJavaLocale diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityArgs.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityArgs.kt new file mode 100644 index 00000000..36c30a76 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityArgs.kt @@ -0,0 +1,29 @@ +package org.hyacinthbots.lilybot.extensions.utility.config + +import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.converters.impl.defaultingBoolean +import dev.kordex.core.commands.converters.impl.optionalChannel + +class UtilityArgs : Arguments() { + val utilityLogChannel by optionalChannel { + name = "utility-log" + description = "The channel to log various utility actions too." + } + val logChannelUpdates by defaultingBoolean { + name = "log-channel-updates" + description = "Whether to log changes made to channels in this guild." + defaultValue = false + } + val logEventUpdates by defaultingBoolean { + name = "log-event-updates" + description = "Whether to log changes made to scheduled events in this guild." + } + val logInviteUpdates by defaultingBoolean { + name = "log-invite-updates" + description = "Whether to log changes made to invites in this guild." + } + val logRoleUpdates by defaultingBoolean { + name = "log-role-updates" + description = "Whether to log changes made to roles in this guild." + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityCommand.kt similarity index 74% rename from src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityCommand.kt rename to src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityCommand.kt index 3ded444b..f3f77feb 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityCommand.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityCommand.kt @@ -1,10 +1,9 @@ -package org.hyacinthbots.lilybot.extensions.utils.config +package org.hyacinthbots.lilybot.extensions.utility.config import dev.kord.common.entity.Permission import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.entity.channel.TextChannel -import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.embed import dev.kordex.core.checks.anyGuild import dev.kordex.core.checks.hasPermission @@ -13,6 +12,7 @@ import dev.kordex.core.commands.application.slash.ephemeralSubCommand import dev.kordex.core.utils.botHasPermissions import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.database.entities.UtilityConfigData +import org.hyacinthbots.lilybot.extensions.config.utils.utilityEmbed suspend fun SlashCommand<*, *, *>.utilityCommand() = ephemeralSubCommand(::UtilityArgs) { name = "utility" @@ -48,39 +48,26 @@ suspend fun SlashCommand<*, *, *>.utilityCommand() = ephemeralSubCommand(::Utili } } - suspend fun EmbedBuilder.utilityEmbed() { - title = "Configuration: Utility" - field { - name = "Utility Log" - value = if (arguments.utilityLogChannel != null) { - "${arguments.utilityLogChannel!!.mention} ${arguments.utilityLogChannel!!.data.name.value}" - } else { - "Disabled" - } - } - - footer { - text = "Configured by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } - respond { embed { - utilityEmbed() + utilityEmbed(arguments, user) } } UtilityConfigCollection().setConfig( UtilityConfigData( guild!!.id, - arguments.utilityLogChannel?.id + arguments.utilityLogChannel?.id, + arguments.logChannelUpdates, + arguments.logEventUpdates, + arguments.logInviteUpdates, + arguments.logRoleUpdates ) ) utilityLog?.createMessage { embed { - utilityEmbed() + utilityEmbed(arguments, user) } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/events/UtilityEvents.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/events/UtilityEvents.kt new file mode 100644 index 00000000..fe84294c --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/events/UtilityEvents.kt @@ -0,0 +1,677 @@ +package org.hyacinthbots.lilybot.extensions.utility.events + +import dev.kord.common.entity.ChannelType +import dev.kord.common.entity.ForumTag +import dev.kord.common.entity.GuildScheduledEventPrivacyLevel +import dev.kord.common.entity.optional.value +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.channel.asChannelOfOrNull +import dev.kord.core.behavior.channel.createEmbed +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.entity.channel.Category +import dev.kord.core.entity.channel.Channel +import dev.kord.core.entity.channel.ForumChannel +import dev.kord.core.event.channel.ChannelCreateEvent +import dev.kord.core.event.channel.ChannelDeleteEvent +import dev.kord.core.event.channel.ChannelUpdateEvent +import dev.kord.core.event.channel.thread.ThreadChannelCreateEvent +import dev.kord.core.event.channel.thread.ThreadChannelDeleteEvent +import dev.kord.core.event.guild.GuildScheduledEventCreateEvent +import dev.kord.core.event.guild.GuildScheduledEventDeleteEvent +import dev.kord.core.event.guild.GuildScheduledEventEvent +import dev.kord.core.event.guild.GuildScheduledEventUpdateEvent +import dev.kord.core.event.guild.InviteCreateEvent +import dev.kord.core.event.guild.InviteDeleteEvent +import dev.kord.core.event.role.RoleCreateEvent +import dev.kord.core.event.role.RoleDeleteEvent +import dev.kord.core.event.role.RoleUpdateEvent +import dev.kord.rest.builder.message.EmbedBuilder +import dev.kord.rest.builder.message.embed +import dev.kordex.core.DISCORD_GREEN +import dev.kordex.core.DISCORD_RED +import dev.kordex.core.DISCORD_YELLOW +import dev.kordex.core.checks.anyGuild +import dev.kordex.core.extensions.Extension +import dev.kordex.core.extensions.event +import dev.kordex.core.time.TimestampType +import dev.kordex.core.time.toDiscord +import kotlinx.datetime.Clock +import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection +import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.utils.afterDot +import org.hyacinthbots.lilybot.utils.formatPermissionSet +import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.ifNullOrEmpty +import kotlin.collections.forEach + +/** A String identifier to use for the permission map to get allowed permissions. */ +private const val ALLOWED = "Allowed" + +/** A String identifier to use for the permission map to get denied permissions. */ +private const val DENIED = "Denied" + +class UtilityEvents : Extension() { + override val name: String = "utility-events" + + override suspend fun setup() { + event { + check { + failIf { + // We get more detail from the specific event + event.channel.type != ChannelType.PublicGuildThread || event.channel.type != ChannelType.PrivateThread + } + } + action { + val guildId = event.channel.data.guildId.value + // Do not log if the channel logging option is false + if (guildId?.let { UtilityConfigCollection().getConfig(it)?.logChannelUpdates } == false) return@action + val guild = guildId?.let { GuildBehavior(it, event.kord) } + val perms = formatPermissionsForDisplay(guild, event.channel) + var allowed = perms.getValue(ALLOWED) + var denied = perms.getValue(DENIED) + + if (allowed.isBlank()) allowed = "None overrides set" + if (denied.isBlank()) denied = "None overrides set" + + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!)?.createEmbed { + title = "${writeChannelType(event.channel.type)} Created" + description = "${event.channel.mention} (${event.channel.data.name.value}) was created." + field { + name = "Allowed Permissions" + value = allowed + inline = true + } + field { + name = "Denied Permissions" + value = denied + inline = true + } + timestamp = Clock.System.now() + color = DISCORD_GREEN + } + } + } + event { + action { + val guildId = event.channel.data.guildId.value + // Do not log if the channel logging option is false + if (guildId?.let { UtilityConfigCollection().getConfig(it)?.logChannelUpdates } == false) return@action + val guild = guildId?.let { GuildBehavior(it, event.kord) } + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!)?.createEmbed { + title = "${writeChannelType(event.channel.type)} Deleted" + description = "`${event.channel.data.name.value}` was deleted." + timestamp = Clock.System.now() + color = DISCORD_RED + } + } + } + event { + check { + failIf { + // Data from old threads is almost always null, so we should not try to display changes + event.channel.type != ChannelType.PublicGuildThread || event.channel.type != ChannelType.PrivateThread + } + } + action { + val guildId = event.channel.data.guildId.value + // Do not log if the channel logging option is false + if (guildId?.let { UtilityConfigCollection().getConfig(it)?.logChannelUpdates } == false) return@action + val guild = guildId?.let { GuildBehavior(it, event.kord) } + val oldPerms = formatPermissionsForDisplay(guild, event.old) + val newPerms = formatPermissionsForDisplay(guild, event.channel) + val oldAllowed = oldPerms.getValue(ALLOWED) + val oldDenied = oldPerms.getValue(DENIED) + val newAllowed = newPerms.getValue(ALLOWED) + val newDenied = newPerms.getValue(DENIED) + val oldData = event.old?.data + val newData = event.channel.data + val oldAppliedTags = mutableListOf() + newData.appliedTags.value?.forEach { tag -> + event.old?.asChannelOrNull()?.data?.availableTags?.value?.filter { it.id == tag } + ?.get(0)?.name?.let { oldAppliedTags.add(it) } + } + val newAppliedTags = mutableListOf() + newData.appliedTags.value?.forEach { tag -> + event.channel.asChannelOrNull()?.data?.availableTags?.value?.filter { it.id == tag } + ?.get(0)?.name?.let { newAppliedTags.add(it) } + } + + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!)?.createEmbed { + title = "${writeChannelType(event.channel.type)} Updated" + oldNewEmbedField( + "Type Change", writeChannelType(event.old?.type), writeChannelType(event.channel.type) + ) + oldNewEmbedField("Name Change", oldData?.name?.value, newData.name.value) + oldNewEmbedField("Topic Changed", oldData?.topic?.value, newData.topic.value) + oldNewEmbedField( + "Parent Category Change", + kord.getChannelOf(oldData?.parentId.value!!)?.mention, + kord.getChannelOf(newData.parentId.value!!)?.mention + ) + if (event.channel.data.nsfw != event.old?.data?.nsfw) { + field { + name = "NSFW Setting" + value = event.channel.data.nsfw.discordBoolean.toString() + } + } + oldNewEmbedField("Position Changed", oldData?.position.value, newData.position.value) + oldNewEmbedField( + "Slowmode time changed", + oldData?.rateLimitPerUser?.value?.toString() ?: "0", + newData.rateLimitPerUser.value?.toString() ?: "0" + ) + oldNewEmbedField("Bitrate changed", oldData?.bitrate.value, newData.bitrate.value) + oldNewEmbedField("User limit changed", oldData?.userLimit.value ?: 0, newData.userLimit.value ?: 0) + oldNewEmbedField( + "Region Changed", + oldData?.rtcRegion?.value ?: "Automatic", + newData.rtcRegion.value ?: "Automatic" + ) + oldNewEmbedField( + "Video Quality Changed", + oldData?.videoQualityMode?.value.afterDot(), + newData.videoQualityMode.value.afterDot() + ) + oldNewEmbedField( + "Default Auto-Archive Duration", + oldData?.defaultAutoArchiveDuration?.value?.duration.toString(), + newData.defaultAutoArchiveDuration.value?.duration.toString() + ) + oldNewEmbedField( + "Default Sort Changed", + oldData?.defaultSortOrder?.value.afterDot(), + newData.defaultSortOrder.value.afterDot() + ) + oldNewEmbedField( + "Default Layout Changed", + oldData?.defaultForumLayout?.value.afterDot(), + newData.defaultForumLayout.value.afterDot() + ) + oldNewEmbedField( + "Available tags Changed", + formatAvailableTags(oldData?.availableTags?.value), + formatAvailableTags(newData.availableTags.value) + ) + oldNewEmbedField( + "Applied tags Changed", + oldAppliedTags.joinToString(", "), + newAppliedTags.joinToString(", ") + ) + oldNewEmbedField( + "Default Reaction Emoji Changed", + oldData?.defaultReactionEmoji?.value?.emojiName, + newData.defaultReactionEmoji.value?.emojiName + ) + oldNewEmbedField( + "Default Thread Slowmode Changed", + oldData?.defaultThreadRateLimitPerUser?.value.toString(), + newData.defaultThreadRateLimitPerUser.value.toString() + ) + if (oldAllowed != newAllowed) { + field { + name = "New Allowed Permissions" + value = newAllowed + inline = true + } + field { + name = "Old Allowed Permissions" + value = oldAllowed + inline = true + } + } + if (oldDenied != newDenied) { + field { + name = "New Denied Permissions" + value = newDenied + inline = false + } + field { + name = "Old Denied Permissions" + value = oldDenied + inline = true + } + } + color = DISCORD_YELLOW + timestamp = Clock.System.now() + } + } + } + event { + check { anyGuild() } + action { + // Do not log if event updates are disabled + if (UtilityConfigCollection().getConfig(event.guildId)?.logEventUpdates == false) return@action + val guild = GuildBehavior(event.guildId, kord) + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { + title = "Scheduled Event Created!" + description = "An event has been created!" + guildEventEmbed(event, guild) + // This appears to exist in the front end but the api doesn't have it anywhere, api payloads contain + // a recurrence field that would fill this but the api doesn't mention it +// field { +// name = "Repeating Frequency" +// value = event.scheduledEvent. +// } + field { + name = "Guild Members only" + value = if (event.scheduledEvent.privacyLevel == GuildScheduledEventPrivacyLevel.GuildOnly) { + "True" + } else { + "False" + } + } + color = DISCORD_GREEN + footer { + text = + "Created by ${event.scheduledEvent.creatorId?.let { guild.getMemberOrNull(it) }?.username}" + icon = + event.scheduledEvent.creatorId?.let { guild.getMemberOrNull(it) }?.avatar?.cdnUrl?.toUrl() + } + } + } + } + event { + check { anyGuild() } + action { + // Do not log if event updates are disabled + if (UtilityConfigCollection().getConfig(event.guildId)?.logEventUpdates == false) return@action + val guild = GuildBehavior(event.guildId, kord) + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { + title = "Scheduled Event Deleted!" + description = "An event has been deleted" + guildEventEmbed(event, guild) + color = DISCORD_RED + footer { + text = + "Originally created by ${event.scheduledEvent.creatorId?.let { guild.getMemberOrNull(it) }?.username}" + icon = + event.scheduledEvent.creatorId?.let { guild.getMemberOrNull(it) }?.avatar?.cdnUrl?.toUrl() + } + } + } + } + event { + check { anyGuild() } + action { + // Do not log if event updates are disabled + if (UtilityConfigCollection().getConfig(event.guildId)?.logEventUpdates == false) return@action + val guild = GuildBehavior(event.guildId, kord) + val oldEvent = event.oldEvent + val newEvent = event.scheduledEvent + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { + title = "Scheduled Event Updated!" + description = "An event has been updated" + oldNewEmbedField("Name Changed", oldEvent?.name, newEvent.name) + oldNewEmbedField("Description Changed", oldEvent?.description, newEvent.description) + oldNewEmbedField( + "Location Changed", + oldEvent?.channelId?.let { guild.getChannelOrNull(it) }?.mention ?: "Unable to get channel", + newEvent.channelId?.let { guild.getChannelOrNull(it) }?.mention ?: "Unable to get channel" + ) + oldNewEmbedField( + "Start time changed", + oldEvent?.scheduledStartTime?.toDiscord(TimestampType.ShortDateTime), + newEvent.scheduledStartTime.toDiscord(TimestampType.ShortDateTime) + ) + color = DISCORD_YELLOW + } + } + } + event { + check { anyGuild() } + action { + // Do not log if invite updates are disabled + if (event.guildId?.let { UtilityConfigCollection().getConfig(it) }?.logInviteUpdates == false) return@action + val guild = event.guildId?.let { GuildBehavior(it, kord) } ?: return@action + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { + title = "Invite link created" + description = "An invite has been created" + field { + name = "Code" + value = event.code + } + field { + name = "Target Channel" + value = event.channel.mention + } + field { + name = "Max uses" + value = event.maxUses.toString() + } + field { + name = "Duration of Invite" + value = event.maxAge.toIsoString() + } + field { + name = "Temporary Membership invite" + value = event.isTemporary.toString() + } + footer { + text = "Created by ${event.getInviterAsMemberOrNull()?.mention}" + icon = event.getInviterAsMemberOrNull()?.avatar?.cdnUrl?.toUrl() + } + timestamp = Clock.System.now() + color = DISCORD_GREEN + } + } + } + event { + check { anyGuild() } + action { + // Do not log if invite updates are disabled + if (event.guildId?.let { UtilityConfigCollection().getConfig(it) }?.logInviteUpdates == false) return@action + val guild = event.guildId?.let { GuildBehavior(it, kord) } ?: return@action + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { + title = "Invite link deleted" + description = "An invite has been deleted" + field { + name = "Code" + value = event.code + } + field { + name = "Target Channel" + value = event.channel.mention + } + timestamp = Clock.System.now() + color = DISCORD_RED + } + } + } + event { + action { + // Do not log if role updates are disabled + if (UtilityConfigCollection().getConfig(event.guildId)?.logRoleUpdates == false) return@action + val guild = GuildBehavior(event.guildId, kord) + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { + title = "Created a Role" + description = "A new role has been created" + field { + name = "Role name" + value = event.role.name + inline = true + } + field { + name = "Role Color" + value = event.role.color.rgb.toString() + inline = true + } + field { + name = "Position" + value = event.role.rawPosition.toString() + inline = true + } + field { + name = "Display separately?" + value = event.role.hoisted.toString() + inline = true + } + field { + name = "Mentionable" + value = event.role.mentionable.toString() + inline = true + } + field { + name = "Icon" + value = event.role.icon?.cdnUrl?.toUrl() ?: "No icon" + inline = true + } + field { + name = "Emoji" + value = event.role.unicodeEmoji ?: "No emoji" + inline = true + } + field { + name = "Managed by integration?" + value = event.role.managed.toString() + inline = true + } + field { + name = "Permissions" + value = formatPermissionSet(event.role.permissions) + } + color = DISCORD_GREEN + timestamp = Clock.System.now() + } + } + } + event { + action { + // Do not log if role updates are disabled + if (UtilityConfigCollection().getConfig(event.guildId)?.logRoleUpdates == false) return@action + val guild = GuildBehavior(event.guildId, kord) + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { + title = "Role deleted" + description = "A role has been deleted" + field { + name = "Role name" + value = event.role?.name ?: "Unable to get name" + } + color = DISCORD_RED + timestamp = Clock.System.now() + } + } + } + event { + action { + // Do not log if role updates are disabled + if (UtilityConfigCollection().getConfig(event.guildId)?.logRoleUpdates == false) return@action + val guild = GuildBehavior(event.guildId, kord) + val channel = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild) + channel?.createMessage { + embed { + title = "Updated a Role" + description = "A role has been updated" + oldNewEmbedField("Name changed", event.old?.name, event.role.name) + oldNewEmbedField("Display separately setting changed", event.old?.hoisted, event.role.hoisted) + oldNewEmbedField("Mentionable setting changed", event.old?.mentionable, event.role.mentionable) + oldNewEmbedField("Position changed", event.old?.getPosition(), event.role.getPosition()) + oldNewEmbedField( + "Icon changed", + event.old?.icon?.cdnUrl?.toUrl() ?: "No icon", + event.role.icon?.cdnUrl?.toUrl() ?: "No icon" + ) + oldNewEmbedField( + "Emoji changed", event.old?.unicodeEmoji ?: "No icon", event.role.unicodeEmoji ?: "No icon" + ) + oldNewEmbedField( + "Permissions changed", + event.old?.permissions?.let { formatPermissionSet(it) } ?: "Unable to get permissions", + formatPermissionSet(event.role.permissions) + ) + color = DISCORD_GREEN + timestamp = Clock.System.now() + } + if (event.old?.color != event.role.color) { + embed { + description = "Old color" + color = if (event.old?.color?.rgb != 0) event.old?.color else null + } + embed { + description = "New color" + color = event.role.color + } + } + } + } + } + event { + check { anyGuild() } + action { + // Do not log if the channel logging option is false + if (UtilityConfigCollection().getConfig(event.channel.guild.id)?.logChannelUpdates == false) return@action + val appliedTags = mutableListOf() + event.channel.appliedTags.forEach { tag -> + event.channel.parent.asChannelOfOrNull() + ?.availableTags?.filter { it.id == tag }?.get(0)?.name?.let { appliedTags.add(it) } + } + + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, event.channel.guild)?.createEmbed { + title = "${writeChannelType(event.channel.type)} Created" + description = "${event.channel.mention} (${event.channel.data.name.value}) was created." + field { + name = "Parent Channel" + value = "${event.channel.parent.mention} (`${event.channel.parent.asChannelOrNull()?.name}`)" + } + field { + name = "Archive duration" + value = event.channel.autoArchiveDuration.duration.toString() + } + if (appliedTags.isNotEmpty()) { + field { + name = "Applied tags" + value = appliedTags.joinToString(", ") + } + } + timestamp = Clock.System.now() + color = DISCORD_GREEN + } + } + } + event { + check { anyGuild() } + action { + // Do not log if the channel logging option is false + if (UtilityConfigCollection().getConfig(event.channel.guild.id)?.logChannelUpdates == false) return@action + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, event.channel.guild)?.createEmbed { + title = "${writeChannelType(event.channel.type)} Deleted" + description = "`${event.channel.data.name.value}` was deleted." + timestamp = Clock.System.now() + color = DISCORD_RED + } + } + } + // No thread update event because the old object is almost always null, meaning that displaying the changes is + // effectively pointless because no original values are available. + } + + /** + * Compare two values to see if they've changed and format an embed field indicating the old value versus the new + * value. + * + * @param detailName The title for the embed field, the information on what has changed + * @param oldValue The original value + * @param newValue The new value + * @author NoComment1105 + * @since 5.0.0 + */ + private fun EmbedBuilder.oldNewEmbedField(detailName: String, oldValue: String?, newValue: String?) { + if (newValue != oldValue) { + field { + name = detailName + value = "Old: $oldValue\nNew: $newValue" + } + } + } + + /** + * A version of [oldNewEmbedField] that takes integers and converts them itself. + * + * @see oldNewEmbedField + * @author NoComment1105 + * @since 5.0.0 + */ + private fun EmbedBuilder.oldNewEmbedField(detailName: String, oldValue: Int?, newValue: Int?) = + oldNewEmbedField(detailName, oldValue.toString(), newValue.toString()) + + /** + * A version of [oldNewEmbedField] that takes booleans and converts them itself. + * + * @see oldNewEmbedField + * @author NoComment1105 + * @since 5.0.0 + */ + private fun EmbedBuilder.oldNewEmbedField(detailName: String, oldValue: Boolean?, newValue: Boolean?) = + oldNewEmbedField(detailName, oldValue.toString(), newValue.toString()) + + /** + * Writes a [ChannelType] into a String to use as a reasonable title. + * + * @param type The type of the channel + * @return A String for the channel title + * @author NoComment1105 + * @since 5.0.0 + */ + private fun writeChannelType(type: ChannelType?): String? = when (type) { + ChannelType.GuildCategory -> "Category" + ChannelType.GuildNews -> "Announcement Channel" + ChannelType.GuildForum -> "Forum Channel" + ChannelType.GuildStageVoice -> "Stage Channel" + ChannelType.GuildText -> "Text Channel" + ChannelType.GuildVoice -> "Voice Channel" + ChannelType.PublicGuildThread -> "Thread" + ChannelType.PrivateThread -> "Private Thread" + else -> null + } + + /** + * Formats the permission overwrites for a [channel] to a string map of allowed and denied permissions. + * + * @param guild The guild the channel is in + * @param channel The channel object to get the permissions for + * @return A [Map] of strings for allowed and denied permissions + * @author NoComment1105 + * @since 5.0.0 + */ + private suspend inline fun formatPermissionsForDisplay( + guild: GuildBehavior?, + channel: Channel? + ): Map { + val map = mutableMapOf() + map[ALLOWED] = "" + map[DENIED] = "" + channel?.data?.permissionOverwrites?.value?.forEach { + map[ALLOWED] += "${guild!!.getRoleOrNull(it.id)?.mention}: ${formatPermissionSet(it.allow)}\n" + map[DENIED] += "${guild.getRoleOrNull(it.id)?.mention}: ${formatPermissionSet(it.deny)}\n" + } + return map + } + + /** + * Formats the Available tags ([ForumTag]) into a readable bullet pointed display for the update embed. + * + * @param tagList A List of [ForumTag]s to format + * @return The formated string from [tagList] + * @author NoComment1105 + * @since 5.0.0 + */ + private fun formatAvailableTags(tagList: List?): String { + var tagString = "" + tagList?.forEach { + tagString += "\n* Name: ${it.name}\n* Moderated: ${it.moderated}\n" + + "* Emoji: ${if (it.emojiId != null) "" else it.emojiName}\n---" + } + return tagString.ifNullOrEmpty { "None" } + } + + /** + * Fills out the content for Guild event updates to avoid repeating code. + * + * @param event The event instance + * @param guild The guild for the event + * @author NoComment1105 + * @since 5.0.0 + */ + private suspend fun EmbedBuilder.guildEventEmbed(event: GuildScheduledEventEvent, guild: GuildBehavior) { + field { + name = "Event Name" + value = event.scheduledEvent.name + } + field { + name = "Event Description" + value = event.scheduledEvent.description ?: "No description provided" + } + field { + name = "Event location" + value = if (event.scheduledEvent.channelId != null) { + guild.getChannelOrNull(event.scheduledEvent.channelId!!)?.mention ?: "Unable to get channel" + } else { + "External event, no channel." + } + } + field { + name = "Start time" + value = event.scheduledEvent.scheduledStartTime.toDiscord(TimestampType.ShortDateTime) + } + image = event.scheduledEvent.image?.cdnUrl?.toUrl().ifNullOrEmpty { "No image" } + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityArgs.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityArgs.kt deleted file mode 100644 index 910c1df9..00000000 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utils/config/UtilityArgs.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.hyacinthbots.lilybot.extensions.utils.config - -import dev.kordex.core.commands.Arguments -import dev.kordex.core.commands.converters.impl.optionalChannel - -class UtilityArgs : Arguments() { - val utilityLogChannel by optionalChannel { - name = "utility-log" - description = "The channel to log various utility actions too." - } -} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt index 75d93295..5c86d07b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt @@ -17,7 +17,7 @@ import dev.kordex.modules.pluralkit.api.PKMessage * @author NoComment1105 * @since 3.0.0 */ -suspend inline fun EmbedBuilder.baseModerationEmbed(reason: String?, targetUser: User?, commandUser: UserBehavior) { +suspend inline fun EmbedBuilder.baseModerationEmbed(reason: String?, targetUser: User?, commandUser: UserBehavior?) { field { name = "User:" value = "${targetUser?.username ?: "Unable to get user"}\n${targetUser?.id ?: ""}" @@ -28,9 +28,11 @@ suspend inline fun EmbedBuilder.baseModerationEmbed(reason: String?, targetUser: value = reason ?: "No reason provided" inline = false } - footer { - text = "Requested by ${commandUser.asUserOrNull()?.username}" - icon = commandUser.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + if (commandUser != null) { + footer { + text = "Requested by ${commandUser.asUserOrNull()?.username}" + icon = commandUser.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } } } @@ -57,6 +59,28 @@ fun EmbedBuilder.dmNotificationStatusEmbedField(dm: Message?, override: Boolean) } } +/** + * This function uses a success variable and checks to see if it succeeded in sending the user a DM. + * + * @param success Whether the DM was a success or not + * @param override Whether the DM was forcefully disabled by the command runner. + * @author NoComment1105 + * @since 5.0.0 + */ +fun EmbedBuilder.dmNotificationStatusEmbedField(success: Boolean?, override: Boolean?) { + field { + name = "User Notification:" + value = if (success != null && success) { + "User notified with direct message" + } else if (override != null && !override) { + "DM Notification Disabled" + } else { + "Failed to notify user with direct message" + } + inline = false + } +} + /** * This function removed duplicated code from MessageDelete and MessageEdit. * It holds attachment and PluralKit info fields for the logging embeds. diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt index 62cd9c4b..b3e4960f 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt @@ -131,18 +131,7 @@ suspend inline fun CheckContext<*>.botHasChannelPerms(permissions: Permissions) val eventChannel = channelFor(event)?.asChannelOrNull() ?: return - val permissionsSet: MutableSet = mutableSetOf() - var count = 0 - permissions.values.forEach { _ -> - permissionsSet.add( - permissions.values.toString() - .split(",")[count] - .split(".")[1] - .replace("[", "`") - .replace("]", "`") - ) - count++ - } + val permissionsSet: String = formatPermissionSet(permissions) /* Use `TextChannel` when the channel is a Text channel */ if (eventChannel is TextChannel) { @@ -268,3 +257,26 @@ suspend fun CheckContextWithCache<*>.modCommandChecks(actionPermission: Permissi requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(actionPermission) } + +/** + * Formats [Permissions] into a readable string list, returning "None" if there are no permissions there. + * + * @param permissions The [Permissions] to format + * @return A string containing the permissions + * @author NoComment1105 + * @since 5.0.0 + */ +fun formatPermissionSet(permissions: Permissions): String { + val permissionsSet: MutableSet = mutableSetOf() + var count = 0 + permissions.values.forEach { _ -> + permissionsSet.add( + permissions.values.toString() + .split(",")[count] + .split(".")[1] + .replace("]", "") + ) + count++ + } + return permissionsSet.toString().replace("[", "").replace("]", "").ifEmpty { "None" } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index b36fd082..33700ddd 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -24,6 +24,7 @@ import org.hyacinthbots.lilybot.database.collections.GithubCollection import org.hyacinthbots.lilybot.database.collections.GuildLeaveTimeCollection import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.MainMetaCollection +import org.hyacinthbots.lilybot.database.collections.ModerationActionCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.NewsChannelPublishingCollection import org.hyacinthbots.lilybot.database.collections.ReminderCollection @@ -237,6 +238,7 @@ suspend inline fun ExtensibleBotBuilder.database(migrate: Boolean) { single { GuildLeaveTimeCollection() } bind GuildLeaveTimeCollection::class single { LoggingConfigCollection() } bind LoggingConfigCollection::class single { MainMetaCollection() } bind MainMetaCollection::class + single { ModerationActionCollection() } bind ModerationActionCollection::class single { ModerationConfigCollection() } bind ModerationConfigCollection::class single { NewsChannelPublishingCollection() } bind NewsChannelPublishingCollection::class single { ReminderCollection() } bind ReminderCollection::class @@ -259,3 +261,12 @@ suspend inline fun ExtensibleBotBuilder.database(migrate: Boolean) { } } } + +/** + * Takes a value [T], converts it to a string and returns the part after a full-stop/period. + * Generally used on class values, i.e. ArchiveDuration.Day to return just "Day" + * + * @author NoComment1105 + * @since 5.0.0 + */ +fun T?.afterDot() = this.toString().substringAfter(".") From 1998d2e532a2de3b863956b2e7c3e0c7a1b2e994 Mon Sep 17 00:00:00 2001 From: NoComment Date: Thu, 29 Aug 2024 14:13:35 +0100 Subject: [PATCH 40/42] Reduce some duplication here and there --- .../lilybot/database/entities/AdaptedData.kt | 4 +- .../config/commands/ConfigClearCommand.kt | 170 +++++++++--------- .../extensions/config/utils/ConfigEmbeds.kt | 2 + .../extensions/utility/commands/Github.kt | 31 +--- .../lilybot/utils/_ConfigUtils.kt | 87 ++++----- .../lilybot/utils/_PermissionUtils.kt | 2 +- 6 files changed, 123 insertions(+), 173 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AdaptedData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AdaptedData.kt index 290b45aa..34e14542 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AdaptedData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/AdaptedData.kt @@ -1,7 +1,7 @@ /* * This code was utilized from [cozy](https://github.com/QuiltMC/cozy-discord) by QuiltMC -* and hence is subject to the terms of the Mozilla Public License V. 2.0 -* A copy of this license can be found at https://mozilla.org/MPL/2.0/. +* and hence is subject to the terms of the Mozilla Public Licence V. 2.0 +* A copy of this licence can be found at https://mozilla.org/MPL/2.0/. */ package org.hyacinthbots.lilybot.database.entities diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt index 9c2dce01..2e637d52 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt @@ -6,6 +6,7 @@ import dev.kord.rest.builder.message.embed import dev.kordex.core.checks.anyGuild import dev.kordex.core.checks.hasPermission import dev.kordex.core.commands.Arguments +import dev.kordex.core.commands.application.slash.EphemeralSlashCommandContext import dev.kordex.core.commands.application.slash.SlashCommand import dev.kordex.core.commands.application.slash.converters.impl.stringChoice import dev.kordex.core.commands.application.slash.ephemeralSubCommand @@ -28,110 +29,109 @@ suspend fun SlashCommand<*, *, *>.configClearCommand() = ephemeralSubCommand(::C } action { - suspend fun logClear() { - val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + when (arguments.config) { + ConfigType.MODERATION.name -> clearConfig(ConfigType.MODERATION, arguments) - if (utilityLog == null) { - respond { - content = "Consider setting a utility config to log changes to configurations." - } - return - } + ConfigType.LOGGING.name -> clearConfig(ConfigType.LOGGING, arguments) - utilityLog.createMessage { - embed { - title = "Configuration Cleared: ${arguments.config[0]}${ - arguments.config.substring(1, arguments.config.length).lowercase() - }" - footer { - text = "Config cleared by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } - } + ConfigType.UTILITY.name -> clearConfig(ConfigType.UTILITY, arguments) + + ConfigType.ALL.name -> clearConfig(ConfigType.ALL, arguments) } - when (arguments.config) { - ConfigType.MODERATION.name -> { - ModerationConfigCollection().getConfig(guild!!.id) ?: run { - respond { - content = "No moderation configuration exists to clear!" - } - return@action + respond { + embed { + title = if (arguments.config == ConfigType.ALL.name) { + "All configs cleared" + } else { + "Config cleared: ${arguments.config}" } - - logClear() - - ModerationConfigCollection().clearConfig(guild!!.id) - respond { - embed { - title = "Config cleared: Moderation" - footer { - text = "Config cleared by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } + footer { + text = "Config cleared by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } + } + } +} - ConfigType.LOGGING.name -> { - LoggingConfigCollection().getConfig(guild!!.id) ?: run { - respond { - content = "No logging configuration exists to clear!" - } - return@action - } - - logClear() - - LoggingConfigCollection().clearConfig(guild!!.id) +/** + * Handles the clearing of a configuration(s) based on the [args] and [type]. + * + * @param type The type of config to clear + * @param args The [ClearArgs] for the clearing of the config + * @author NoComment1105 + * @since 5.0.0 + */ +private suspend fun EphemeralSlashCommandContext<*, *>.clearConfig(type: ConfigType, args: ClearArgs) { + when (type) { + ConfigType.MODERATION -> { + ModerationConfigCollection().getConfig(guild!!.id) ?: run { respond { - embed { - title = "Config cleared: Logging" - footer { - text = "Config cleared by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } + content = "No moderation configuration exists to clear" } + return } + logClear(args) + ModerationConfigCollection().clearConfig(guild!!.id) + } - ConfigType.UTILITY.name -> { - UtilityConfigCollection().getConfig(guild!!.id) ?: run { - respond { - content = "No utility configuration exists to clear" - } - return@action - } - - logClear() - - UtilityConfigCollection().clearConfig(guild!!.id) + ConfigType.LOGGING -> { + LoggingConfigCollection().getConfig(guild!!.id) ?: run { respond { - embed { - title = "Config cleared: Utility" - footer { - text = "Config cleared by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } + content = "No logging configuration exists to clear" } } + logClear(args) + LoggingConfigCollection().clearConfig(guild!!.id) + } - ConfigType.ALL.name -> { - ModerationConfigCollection().clearConfig(guild!!.id) - LoggingConfigCollection().clearConfig(guild!!.id) - UtilityConfigCollection().clearConfig(guild!!.id) + ConfigType.UTILITY -> { + UtilityConfigCollection().getConfig(guild!!.id) ?: run { respond { - embed { - title = "All configs cleared" - footer { - text = "Configs cleared by ${user.asUserOrNull()?.username}" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - } - } + content = "No utility configuration exists to clear" } } + logClear(args) + UtilityConfigCollection().clearConfig(guild!!.id) + } + + ConfigType.ALL -> { + ModerationConfigCollection().clearConfig(guild!!.id) + LoggingConfigCollection().clearConfig(guild!!.id) + UtilityConfigCollection().clearConfig(guild!!.id) + } + } +} + +/** + * Log the clearing of a configuration to the utility log, should there still be one. + * + * @param arguments The [ClearArgs] for the clearing of the config + * @author NoComment1105 + * @since 5.0.0 + */ +suspend fun EphemeralSlashCommandContext<*, *>.logClear(arguments: ClearArgs) { + // Skip this if the utility config is cleared or all configs are cleared + if (arguments.config == ConfigType.UTILITY.name || arguments.config == ConfigType.ALL.name) return + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + + if (utilityLog == null) { + respond { + content = "Consider setting a utility config to log changes to configurations." + } + return + } + + utilityLog.createMessage { + embed { + title = "Configuration Cleared: ${arguments.config[0]}${ + arguments.config.substring(1, arguments.config.length).lowercase() + }" + footer { + text = "Config cleared by ${user.asUserOrNull()?.username}" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt index 2841e4a5..aace5afa 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt @@ -1,3 +1,5 @@ +@file:Suppress("DuplicatedCode") + package org.hyacinthbots.lilybot.extensions.config.utils import dev.kord.core.behavior.GuildBehavior diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt index c844f64f..9171d11d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt @@ -69,10 +69,6 @@ class Github : Extension() { } // Clarify the input is formatted correctly, inform the user if not. if (!repository.contains("/")) { - sentry.breadcrumb(BreadcrumbType.Error) { - category = "extensions.util.Github.issue.InputCheck" - message = "Input missing /" - } respond { embed { title = "Make sure your repository input is formatted like this:" @@ -124,11 +120,6 @@ class Github : Extension() { if (iterator.hasNext()) { issue = iterator.next() } else { - sentry.breadcrumb(BreadcrumbType.Error) { - category = "extensions.util.Github.issue.getIssue" - message = "Unable to find issue" - } - respond { embed { title = "Invalid issue number. Make sure this issue exists!" @@ -167,10 +158,6 @@ class Github : Extension() { merged = pull.isMerged draft = pull.isDraft } catch (ioException: IOException) { - sentry.breadcrumb(BreadcrumbType.Error) { - category = "extensions.util.Github.issue.CheckPRStatus" - message = "Error initializing PR wtf" - } ioException.printStackTrace() title = "Error!" description = "Error occurred initializing Pull Request. How did this happen?" @@ -295,12 +282,8 @@ class Github : Extension() { } return@action } - // Clarify the input is formatted correctly, inform the user if not + if (!repository.contains("/")) { - sentry.breadcrumb(BreadcrumbType.Error) { - category = "extensions.util.Github.repository.InputCheck" - message = "Input missing /" - } respond { embed { title = "Make sure your input is formatted like this:" @@ -321,10 +304,6 @@ class Github : Extension() { } else { github.getRepository(repository) } - sentry.breadcrumb(BreadcrumbType.Info) { - category = "extensions.util.Github.repository.getRepository" - message = "Repository found" - } } catch (_: IOException) { sentry.breadcrumb(BreadcrumbType.Error) { category = "extensions.util.Github.repository.getRepository" @@ -402,15 +381,7 @@ class Github : Extension() { } else { github.getUser(arguments.username) } - sentry.breadcrumb(BreadcrumbType.Info) { - category = "extensions.util.Github.user.getUser" - message = "User found" - } } catch (_: IOException) { - sentry.breadcrumb(BreadcrumbType.Error) { - category = "extensions.util.Github.user.getUser" - message = "Unable to find user" - } respond { embed { title = "Invalid Username. Make sure this user exists!" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt index aff31835..2ea5b0e6 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt @@ -28,15 +28,29 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO fail("There are no config options provided in the code. Please inform the developers immediately!") } + val moderationConfig = ModerationConfigCollection().getConfig(guildFor(event)!!.id) + if (moderationConfig == null) { + fail("Unable to access moderation config for this guild! Please inform a member of staff.") + return + } + + val loggingConfig = LoggingConfigCollection().getConfig(guildFor(event)!!.id) + if (loggingConfig == null) { + fail("Unable to access logging config for this guild! Please inform a member of staff.") + return + } + + val utilityConfig = UtilityConfigCollection().getConfig(guildFor(event)!!.id) + if (utilityConfig == null) { + fail("Unable to access logging config for this guild! Please inform a member of staff.") + return + } + // Look at the config options and check the presence of the config in the database. for (option in configOptions) { when (option) { ConfigOptions.MODERATION_ENABLED -> { - val moderationConfig = ModerationConfigCollection().getConfig(guildFor(event)!!.id) - if (moderationConfig == null) { - fail("Unable to access moderation config for this guild! Please inform a member of staff.") - break - } else if (!moderationConfig.enabled) { + if (!moderationConfig.enabled) { fail("Moderation is disabled for this guild!") break } else { @@ -45,11 +59,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO } ConfigOptions.MODERATOR_ROLE -> { - val moderationConfig = ModerationConfigCollection().getConfig(guildFor(event)!!.id) - if (moderationConfig == null) { - fail("Unable to access moderation config for this guild! Please inform a member of staff.") - break - } else if (moderationConfig.role == null) { + if (moderationConfig.role == null) { fail("A moderator role has not been set for this guild!") break } else { @@ -58,11 +68,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO } ConfigOptions.ACTION_LOG -> { - val moderationConfig = ModerationConfigCollection().getConfig(guildFor(event)!!.id) - if (moderationConfig == null) { - fail("Unable to access moderation config for this guild! Please inform a member of staff.") - break - } else if (moderationConfig.channel == null) { + if (moderationConfig.channel == null) { fail("An action log has not been set for this guild!") break } else { @@ -71,11 +77,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO } ConfigOptions.LOG_PUBLICLY -> { - val moderationConfig = ModerationConfigCollection().getConfig(guildFor(event)!!.id) - if (moderationConfig == null) { - fail("Unable to access moderation config for this guild! Please inform a member of staff.") - break - } else if (moderationConfig.publicLogging == null) { + if (moderationConfig.publicLogging == null) { fail("Public logging has not been enabled for this guild!") break } else { @@ -84,11 +86,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO } ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED -> { - val loggingConfig = LoggingConfigCollection().getConfig(guildFor(event)!!.id) - if (loggingConfig == null) { - fail("Unable to access logging config for this guild! Please inform a member of staff.") - break - } else if (!loggingConfig.enableMessageDeleteLogs) { + if (!loggingConfig.enableMessageDeleteLogs) { fail("Message delete logging is disabled for this guild!") break } else { @@ -97,11 +95,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO } ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED -> { - val loggingConfig = LoggingConfigCollection().getConfig(guildFor(event)!!.id) - if (loggingConfig == null) { - fail("Unable to access logging config for this guild! Please inform a member of staff.") - break - } else if (!loggingConfig.enableMessageEditLogs) { + if (!loggingConfig.enableMessageEditLogs) { fail("Message edit logging is disabled for this guild!") break } else { @@ -110,11 +104,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO } ConfigOptions.MESSAGE_LOG -> { - val loggingConfig = LoggingConfigCollection().getConfig(guildFor(event)!!.id) - if (loggingConfig == null) { - fail("Unable to access logging config for this guild! Please inform a member of staff.") - break - } else if (loggingConfig.messageChannel == null) { + if (loggingConfig.messageChannel == null) { fail("A message log has not been set for this guild!") break } else { @@ -123,11 +113,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO } ConfigOptions.MEMBER_LOGGING_ENABLED -> { - val loggingConfig = LoggingConfigCollection().getConfig(guildFor(event)!!.id) - if (loggingConfig == null) { - fail("Unable to access logging config for this guild! Please inform a member of staff.") - break - } else if (!loggingConfig.enableMemberLogs) { + if (!loggingConfig.enableMemberLogs) { fail("Member logging is disabled for this guild!") break } else { @@ -136,11 +122,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO } ConfigOptions.MEMBER_LOG -> { - val loggingConfig = LoggingConfigCollection().getConfig(guildFor(event)!!.id) - if (loggingConfig == null) { - fail("Unable to access logging config for this guild! Please inform a member of staff.") - break - } else if (loggingConfig.memberLog == null) { + if (loggingConfig.memberLog == null) { fail("A member log has not been set for this guild") break } else { @@ -149,11 +131,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO } ConfigOptions.UTILITY_LOG -> { - val utilityConfig = UtilityConfigCollection().getConfig(guildFor(event)!!.id) - if (utilityConfig == null) { - fail("Unable to access utility config for this guild! Please inform a member of staff.") - break - } else if (utilityConfig.utilityLogChannel == null) { + if (utilityConfig.utilityLogChannel == null) { fail("A utility log has not been set for this guild") break } else { @@ -174,7 +152,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO */ suspend inline fun configIsUsable(guildId: Snowflake, option: ConfigOptions): Boolean { when (option) { - ConfigOptions.MODERATION_ENABLED -> return ModerationConfigCollection().getConfig(guildId)?.enabled ?: false + ConfigOptions.MODERATION_ENABLED -> return ModerationConfigCollection().getConfig(guildId)?.enabled == true ConfigOptions.MODERATOR_ROLE -> { val moderationConfig = ModerationConfigCollection().getConfig(guildId) ?: return false @@ -192,18 +170,17 @@ suspend inline fun configIsUsable(guildId: Snowflake, option: ConfigOptions): Bo } ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED -> - return LoggingConfigCollection().getConfig(guildId)?.enableMessageDeleteLogs ?: false + return LoggingConfigCollection().getConfig(guildId)?.enableMessageDeleteLogs == true ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED -> - return LoggingConfigCollection().getConfig(guildId)?.enableMessageEditLogs ?: false + return LoggingConfigCollection().getConfig(guildId)?.enableMessageEditLogs == true ConfigOptions.MESSAGE_LOG -> { val loggingConfig = LoggingConfigCollection().getConfig(guildId) ?: return false return loggingConfig.messageChannel != null } - ConfigOptions.MEMBER_LOGGING_ENABLED -> return LoggingConfigCollection().getConfig(guildId)?.enableMemberLogs - ?: false + ConfigOptions.MEMBER_LOGGING_ENABLED -> return LoggingConfigCollection().getConfig(guildId)?.enableMemberLogs == true ConfigOptions.MEMBER_LOG -> { val loggingConfig = LoggingConfigCollection().getConfig(guildId) ?: return false diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt index b3e4960f..bbb4e912 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt @@ -176,7 +176,7 @@ suspend inline fun CheckContext<*>.botHasChannelPerms(permissions: Permissions) * point the check fails, null is returned. This should be handled with an elvis operator to return the action in the * code. * - * @param kord The kord instance so the self of the bot can be gotten if needed + * @param kord The kord instance so the self of the bot can be got if needed * @param user The target user in the command * @param guild The guild the command was run in * @param commandName The name of the command. Used for the responses and error message From 22d399fb64ea23c0ef1096ddad10be4eba286734 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:27:25 +0100 Subject: [PATCH 41/42] Dm default false (#421) * Theoretically make the DM option default to a certain value. Needs testing :) * Forgot migration :sunglasses: --- .../lilybot/database/entities/Config.kt | 1 + .../database/migrations/config/configV7.kt | 4 ++ .../extensions/config/utils/ConfigEmbeds.kt | 8 ++++ .../moderation/commands/ModerationCommands.kt | 41 ++++++++++--------- .../moderation/config/ModerationArgs.kt | 5 +++ .../moderation/config/ModerationCommand.kt | 2 + 6 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt index 84723772..6ffe64af 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt @@ -55,6 +55,7 @@ data class ModerationConfigData( val quickTimeoutLength: DateTimePeriod?, val autoPunishOnWarn: Boolean?, val publicLogging: Boolean?, + val dmDefault: Boolean?, val banDmMessage: String?, val autoInviteModeratorRole: Boolean? ) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt index d72a7152..7c25c303 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV7.kt @@ -12,6 +12,10 @@ suspend fun configV7(db: CoroutineDatabase) { ModerationConfigData::autoInviteModeratorRole exists false, setValue(ModerationConfigData::autoInviteModeratorRole, null) ) + updateMany( + ModerationConfigData::dmDefault exists false, + setValue(ModerationConfigData::dmDefault, true) + ) } with(db.getCollection("utilityConfigData")) { updateMany( diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt index aace5afa..2fc8d697 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt @@ -75,6 +75,14 @@ suspend fun EmbedBuilder.moderationEmbed(arguments: ModerationArgs, user: UserBe null -> "Disabled" } } + field { + name = "DM Default" + value = when (arguments.dmDefault) { + true -> "DM argument will default to true" + false -> "DM argument will default to false" + null -> "DM argument will default to false" + } + } field { name = "Ban DM Message" value = arguments.banDmMessage ?: "No custom Ban DM message set" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt index 686d1212..274285a8 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt @@ -29,6 +29,7 @@ import dev.kordex.core.commands.converters.impl.defaultingBoolean import dev.kordex.core.commands.converters.impl.defaultingString import dev.kordex.core.commands.converters.impl.int import dev.kordex.core.commands.converters.impl.optionalAttachment +import dev.kordex.core.commands.converters.impl.optionalBoolean import dev.kordex.core.commands.converters.impl.user import dev.kordex.core.components.components import dev.kordex.core.components.ephemeralStringSelectMenu @@ -488,7 +489,8 @@ class ModerationCommands : Extension() { val modConfig = ModerationConfigCollection().getConfig(guild!!.id) var dmStatus: Message? = null - if (arguments.dm) { + val dmControl = if (arguments.dm != null) arguments.dm!! else modConfig?.dmDefault == true + if (dmControl) { dmStatus = arguments.userArgument.dm { embed { title = "You have been banned from ${guild?.asGuildOrNull()?.name}" @@ -580,7 +582,8 @@ class ModerationCommands : Extension() { val duration = now.plus(arguments.duration, TimeZone.UTC) val modConfig = ModerationConfigCollection().getConfig(guild!!.id) var dmStatus: Message? = null - if (arguments.dm) { + val dmControl = if (arguments.dm != null) arguments.dm!! else modConfig?.dmDefault == true + if (dmControl) { dmStatus = arguments.userArgument.dm { embed { title = "You have been temporarily banned from ${guild?.fetchGuild()?.name}" @@ -737,7 +740,8 @@ class ModerationCommands : Extension() { val modConfig = ModerationConfigCollection().getConfig(guild!!.id) var dmStatus: Message? = null - if (arguments.dm) { + val dmControl = if (arguments.dm != null) arguments.dm!! else modConfig?.dmDefault == true + if (dmControl) { dmStatus = arguments.userArgument.dm { embed { title = "You have been kicked from ${guild?.fetchGuild()?.name}" @@ -787,7 +791,8 @@ class ModerationCommands : Extension() { isBotOrModerator(event.kord, arguments.userArgument, guild, "timeout") ?: return@action var dmStatus: Message? = null - if (arguments.dm) { + val dmControl = if (arguments.dm != null) arguments.dm!! else modConfig?.dmDefault == true + if (dmControl) { dmStatus = arguments.userArgument.dm { embed { title = "You have been timed out in ${guild?.fetchGuild()?.name}" @@ -846,8 +851,10 @@ class ModerationCommands : Extension() { } action { + val config = ModerationConfigCollection().getConfig(guild!!.id) var dmStatus: Message? = null - if (arguments.dm) { + val dmControl = if (arguments.dm != null) arguments.dm!! else config?.dmDefault == true + if (dmControl) { dmStatus = arguments.userArgument.dm { embed { title = "Timeout removed in ${guild!!.asGuildOrNull()?.name}" @@ -901,7 +908,9 @@ class ModerationCommands : Extension() { var dmStatus: Message? = null - if (arguments.dm) { + val dmControl = if (arguments.dm != null) arguments.dm!! else config.dmDefault == true + + if (dmControl) { val warnText = if (config.autoPunishOnWarn == false) { "No moderation action has been taken.\n $warnSuffix" } else { @@ -937,7 +946,7 @@ class ModerationCommands : Extension() { title = "Warning" image = arguments.image?.url baseModerationEmbed(arguments.reason, arguments.userArgument, user) - dmNotificationStatusEmbedField(dmStatus, arguments.dm) + dmNotificationStatusEmbedField(dmStatus, dmControl) timestamp = Clock.System.now() field { name = "Total strikes" @@ -1104,10 +1113,9 @@ class ModerationCommands : Extension() { } /** Whether to DM the user or not. */ - val dm by defaultingBoolean { + val dm by optionalBoolean { name = "dm" description = "Whether to send a direct message to the user about the ban" - defaultValue = true } /** An image that the user wishes to provide for context to the ban. */ @@ -1144,10 +1152,9 @@ class ModerationCommands : Extension() { } /** Whether to DM the user or not. */ - val dm by defaultingBoolean { + val dm by optionalBoolean { name = "dm" description = "Whether to send a direct message to the user about the ban" - defaultValue = true } /** An image that the user wishes to provide for context to the ban. */ @@ -1187,10 +1194,9 @@ class ModerationCommands : Extension() { } /** Whether to DM the user or not. */ - val dm by defaultingBoolean { + val dm by optionalBoolean { name = "dm" description = "Whether to send a direct message to the user about the kick" - defaultValue = true } /** An image that the user wishes to provide for context to the kick. */ @@ -1221,10 +1227,9 @@ class ModerationCommands : Extension() { } /** Whether to DM the user or not. */ - val dm by defaultingBoolean { + val dm by optionalBoolean { name = "dm" description = "Whether to send a direct message to the user about the timeout" - defaultValue = true } /** An image that the user wishes to provide for context to the kick. */ @@ -1242,10 +1247,9 @@ class ModerationCommands : Extension() { } /** Whether to DM the user about the timeout removal or not. */ - val dm by defaultingBoolean { + val dm by optionalBoolean { name = "dm" description = "Whether to dm the user about this or not" - defaultValue = true } } @@ -1264,10 +1268,9 @@ class ModerationCommands : Extension() { } /** Whether to DM the user or not. */ - val dm by defaultingBoolean { + val dm by optionalBoolean { name = "dm" description = "Whether to send a direct message to the user about the warning" - defaultValue = true } /** An image that the user wishes to provide for context to the kick. */ diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt index cadb36a0..f5cb47fb 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt @@ -39,6 +39,11 @@ class ModerationArgs : Arguments() { description = "Whether to log moderation publicly or not." } + val dmDefault by optionalBoolean { + name = "dm-default" + description = "The default value for whether to DM a user in a ban action or not." + } + val banDmMessage by optionalString { name = "ban-dm-message" description = "A custom message to send to users when they are banned." diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt index df1e464f..9660b247 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt @@ -50,6 +50,7 @@ suspend fun SlashCommand<*, *, *>.moderationCommand() = null, null, null, + null, null ) ) @@ -106,6 +107,7 @@ suspend fun SlashCommand<*, *, *>.moderationCommand() = arguments.quickTimeoutLength, arguments.warnAutoPunishments, arguments.logPublicly, + arguments.dmDefault, arguments.banDmMessage, arguments.autoInviteModeratorRole ) From d3c905c500380fa9b01c5751d0e89a7db59414c7 Mon Sep 17 00:00:00 2001 From: NoComment Date: Fri, 20 Sep 2024 11:32:06 +0100 Subject: [PATCH 42/42] Well, lets do this I guess --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 43449c4a..40ba2bbd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ plugins { } group = "org.hyacinthbots.lilybot" -version = "4.9.0" +version = "5.0.0" val className = "org.hyacinthbots.lilybot.LilyBotKt" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5572feb7..ed2ea567 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # Plugins kotlin = "2.0.10" shadow = "8.1.1" -detekt = "1.23.6" +detekt = "1.23.7" git-hooks = "0.0.2" grgit = "5.2.2" blossom = "2.1.0" @@ -10,8 +10,8 @@ blossom = "2.1.0" # Libraries kord-extensions = "2.2.0-20240824.203242-3" logging = "7.0.0" -logback = "1.5.7" -github-api = "1.324" +logback = "1.5.8" +github-api = "1.325" kmongo = "5.1.0" docgenerator = "0.2.2-SNAPSHOT"