diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..c677bb9f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +*.kt text eol=lf diff=kotlin +*.kts text eol=lf diff=kotlin + +gradlew text eol=lf +*.bat text eol=crlf + +*.md text eol=lf diff=markdown + +*.properties text eol=lf +*.yml text eol=lf + +*.class binary +*.jar binary +*.png binary diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index d18666b3..9a088451 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -28,6 +28,7 @@ jobs: uses: gradle/gradle-build-action@v2 with: arguments: build --stacktrace + gradle-home-cache-cleanup: true - name: Upload build artifacts uses: actions/upload-artifact@v3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c19e9c1c..dab2a99a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,6 +26,7 @@ jobs: uses: gradle/gradle-build-action@v2 with: arguments: build --stacktrace + gradle-home-cache-cleanup: true - name: Upload build artifacts uses: actions/upload-artifact@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88d2f6cc..0056bebf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,7 @@ jobs: uses: gradle/gradle-build-action@v2 with: arguments: build --stacktrace + gradle-home-cache-cleanup: true - name: Upload artifacts GitHub uses: AButler/upload-release-assets@v2.0 diff --git a/build.gradle.kts b/build.gradle.kts index 153686fb..f2f60379 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @Suppress("DSL_SCOPE_VIOLATION") @@ -14,7 +16,7 @@ plugins { } group = "org.hyacinthbots.lilybot" -version = "4.7.0" +version = "4.8.0" repositories { mavenCentral() @@ -84,13 +86,11 @@ gitHooks { tasks { withType { - kotlinOptions { - jvmTarget = "17" - languageVersion = libs.plugins.kotlin.get().version.requiredVersion.substringBeforeLast(".") + compilerOptions { + jvmTarget.set(JvmTarget.fromTarget("17")) + languageVersion.set(KotlinVersion.fromVersion(libs.plugins.kotlin.get().version.requiredVersion.substringBeforeLast("."))) incremental = true - freeCompilerArgs = listOf( - "-opt-in=kotlin.RequiresOptIn" - ) + freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") } } diff --git a/docs/changelogs/4.x.x/4.8.0.md b/docs/changelogs/4.x.x/4.8.0.md new file mode 100644 index 00000000..53229270 --- /dev/null +++ b/docs/changelogs/4.x.x/4.8.0.md @@ -0,0 +1,18 @@ +# LilyBot 4.8.0 + +This update fixes bugs and adds a couple of new features +You can find the full changelog below + +New: +* Automatically applies she-her and it-its roles to Lily when a pronoun role menu is created +* Allow users to subscribe to roles in a similar fashion to role menus, but with commands + +Change: +* Update to Kotlin 1.8.10, Gradle 8.0.1 and other dependencies (Internal) +* Removed configDb parameter from migrations (Internal) + +Fix: +* Remove deprecated kord functions (Internal) +* Fix reminder IDs sometimes overlapping causing duplicate entries and errors + +You can find a list of all the commits in this update [here](https://github.com/hyacinthbots/LilyBot/compare/v4.6.3...v4.7.0) diff --git a/docs/commands.md b/docs/commands.md index 45afd334..27da9e17 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -510,6 +510,28 @@ Required Member Permissions: Manage Messages * **Arguments**: None +--- +#### Command name: `role-subscription update` +**Description**: Update your role subscription + +* **Arguments**: +None +--- +#### Command name: `role-subscription add-role` +**Description**: Add a role that can be added through role subscription commands +Required Member Permissions: Manage Server, Manage Roles + +* **Arguments**: + * `role` - A role to add or remove from the subscribable roles - Role + +--- +#### Command name: `role-subscription remove-role` +**Description**: Remove a role that can be added through role subscription commands +Required Member Permissions: Manage Server, Manage Roles + +* **Arguments**: + * `role` - A role to add or remove from the subscribable roles - Role + --- ### Command name: `tag-preview` Description: Preview a tag's contents without sending it publicly. diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbf..ccebba77 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f398c33c..fc10b601 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-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d..79a61d42 100755 --- a/gradlew +++ b/gradlew @@ -144,7 +144,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=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +152,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=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac diff --git a/libs.versions.toml b/libs.versions.toml index cd62e84e..449d4a9b 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -1,21 +1,21 @@ [versions] # Plugins -kotlin = "1.8.0" -shadow = "7.1.2" +kotlin = "1.8.10" +shadow = "8.1.0" detekt = "1.22.0" git-hooks = "0.0.2" grgit = "5.0.0" blossom = "1.3.1" # Libraries -kord-extensions = "1.5.6-20230208.121744-1" -logging = "3.0.4" +kord-extensions = "1.5.6-20230224.151334-5" +logging = "3.0.5" logback = "1.4.5" -github-api = "1.313" +github-api = "1.314" kmongo = "4.8.0" cozy-welcome = "1.0-SNAPSHOT" -dma = "v0.2.0" -docgenerator = "0.1.2" +dma = "0.2.0-SNAPSHOT" +docgenerator = "0.1.2-SNAPSHOT" [libraries] kord-extensions-core = { module = "com.kotlindiscord.kord.extensions:kord-extensions", version.ref = "kord-extensions" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 611c1f45..5746ff1b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,6 @@ rootProject.name = "LilyBot" dependencyResolutionManagement { - @Suppress("UnstableApiUsage") versionCatalogs { create("libs") { from(files("libs.versions.toml")) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt index 57a8550f..b34c1141 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt @@ -16,6 +16,7 @@ import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.NewsChannelPublishingCollection import org.hyacinthbots.lilybot.database.collections.ReminderCollection import org.hyacinthbots.lilybot.database.collections.RoleMenuCollection +import org.hyacinthbots.lilybot.database.collections.RoleSubscriptionCollection import org.hyacinthbots.lilybot.database.collections.TagsCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection @@ -70,6 +71,7 @@ object Cleanups : KordExKoinComponent { NewsChannelPublishingCollection().clearAutoPublishingForGuild(it.guildId) ReminderCollection().removeGuildReminders(it.guildId) RoleMenuCollection().removeAllRoleMenus(it.guildId) + RoleSubscriptionCollection().removeAllSubscribableRoles(it.guildId) TagsCollection().clearTags(it.guildId) ThreadsCollection().removeGuildThreads(it.guildId) UtilityConfigCollection().clearConfig(it.guildId) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleSubscriptionCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleSubscriptionCollection.kt new file mode 100644 index 00000000..027e5272 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RoleSubscriptionCollection.kt @@ -0,0 +1,107 @@ +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.RoleSubscriptionData +import org.koin.core.component.inject +import org.litote.kmongo.eq + +/** + * This class contains the functions for interacting with the [Role Subscription database][RoleSubscriptionData]. This + * class contains the functions for getting, adding, removing and clearing subscribable roles. + * + * @since 4.9.0 + * @see getSubscribableRoles + * @see createSubscribableRoleRecord + * @see addSubscribableRole + * @see removeSubscribableRole + * @see removeAllSubscribableRoles + */ +class RoleSubscriptionCollection : KordExKoinComponent { + private val db: Database by inject() + + @PublishedApi + internal val collection = db.mainDatabase.getCollection() + + /** + * Gets the roles that are subscribable for a given guild. + * + * @param inputGuildId The guild to get the roles for + * @return The [RoleSubscriptionData] for the guild + * + * @author NoComment1105 + * @since 4.9.0 + */ + suspend inline fun getSubscribableRoles(inputGuildId: Snowflake): RoleSubscriptionData? = + collection.findOne(RoleSubscriptionData::guildId eq inputGuildId) + + /** + * Creates a subscribable role record in the database. This should only be used if a record does not already exist. + * + * @param inputGuildId The ID of the guild to create the record for + * + * @author NoComment1105 + * @since 4.9.0 + */ + suspend inline fun createSubscribableRoleRecord(inputGuildId: Snowflake) = + collection.insertOne(RoleSubscriptionData(inputGuildId, mutableListOf())) + + /** + * Adds a role to the subscribable role list. + * + * @param inputGuildId The ID of the guild to add to the list + * @param inputRoleId The ID of the role to add + * @return True if the transaction was a success, false if it was not, null if the collection does not exist + * + * @author NoComment1105 + * @since 4.9.0 + */ + suspend inline fun addSubscribableRole(inputGuildId: Snowflake, inputRoleId: Snowflake): Boolean? { + val col = collection.findOne(RoleSubscriptionData::guildId eq inputGuildId) ?: return null + val newRoleList = col.subscribableRoles + if (newRoleList.contains(inputRoleId)) return false else newRoleList.add(inputRoleId) + collection.updateOne( + RoleSubscriptionData::guildId eq inputGuildId, + RoleSubscriptionData(inputGuildId, newRoleList) + ) + return true + } + + /** + * Removes a role to the subscribable role list. + * + * @param inputGuildId The ID of the guild to alter the list of + * @param inputRoleId The ID of the role to rem,ove + * @return True if the transaction was a success, false if it was not, null if the collection does not exist + * + * @author NoComment1105 + * @since 4.9.0 + */ + suspend inline fun removeSubscribableRole(inputGuildId: Snowflake, inputRoleId: Snowflake): Boolean? { + val col = collection.findOne(RoleSubscriptionData::guildId eq inputGuildId) ?: return null + val newRoleList = col.subscribableRoles + if (!newRoleList.contains(inputRoleId)) { + return false + } else { + val removal = newRoleList.remove(inputRoleId) + if (!removal) return false + } + collection.updateOne( + RoleSubscriptionData::guildId eq inputGuildId, + RoleSubscriptionData(inputGuildId, newRoleList) + ) + return true + } + + /** + * Removes all subscribable roles for a guild. + * + * @param inputGuildId The ID of the guild to remove subscribable roles for + * + * @author NoComment1105 + * @since 4.9.0 + */ + suspend inline fun removeAllSubscribableRoles(inputGuildId: Snowflake) = + collection.deleteOne(RoleSubscriptionData::guildId eq inputGuildId) +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RoleSubscriptionData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RoleSubscriptionData.kt new file mode 100644 index 00000000..59290937 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RoleSubscriptionData.kt @@ -0,0 +1,17 @@ +package org.hyacinthbots.lilybot.database.entities + +import dev.kord.common.entity.Snowflake +import kotlinx.serialization.Serializable + +/** + * The data for role subscriptions. + * + * @property guildId The ID of the guild the subscription roles are for + * @property subscribableRoles The roles that can be subscribed too. + * @since 4.9.0 + */ +@Serializable +data class RoleSubscriptionData( + val guildId: Snowflake, + val subscribableRoles: 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 30f7aa3a..3138c187 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt @@ -57,8 +57,9 @@ object Migrator : KordExKoinComponent { 5 -> ::mainV5 6 -> ::mainV6 7 -> ::mainV7 + 8 -> ::mainV8 else -> break - }(db.mainDatabase, db.configDatabase) + }(db.mainDatabase) logger.info { "Migrated main database to version $nextVersion." } } catch (t: Throwable) { @@ -105,7 +106,7 @@ object Migrator : KordExKoinComponent { 3 -> ::configV3 4 -> ::configV4 else -> break - }(db.configDatabase, db.mainDatabase) + }(db.configDatabase) logger.info { "Migrated config database to version $nextVersion" } } catch (t: Throwable) { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV1.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV1.kt index 70e8f620..d63e0460 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV1.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV1.kt @@ -5,8 +5,7 @@ import org.litote.kmongo.coroutine.CoroutineDatabase import org.litote.kmongo.exists import org.litote.kmongo.setValue -@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER") -suspend fun configV1(configDb: CoroutineDatabase, mainDb: CoroutineDatabase) { +suspend fun configV1(configDb: CoroutineDatabase) { with(configDb.getCollection("loggingConfigData")) { updateMany( LoggingConfigData::enableMessageEditLogs exists false, diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV2.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV2.kt index b503c928..fa6afca6 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV2.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV2.kt @@ -5,8 +5,7 @@ import org.litote.kmongo.coroutine.CoroutineDatabase import org.litote.kmongo.exists import org.litote.kmongo.setValue -@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER") -suspend fun configV2(db: CoroutineDatabase, mainDb: CoroutineDatabase) { +suspend fun configV2(db: CoroutineDatabase) { with(db.getCollection("moderationConfigData")) { updateMany( ModerationConfigData::quickTimeoutLength exists false, diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV3.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV3.kt index ec5a5b98..857d8022 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV3.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV3.kt @@ -5,8 +5,7 @@ import org.litote.kmongo.coroutine.CoroutineDatabase import org.litote.kmongo.exists import org.litote.kmongo.setValue -@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER") -suspend fun configV3(db: CoroutineDatabase, mainDb: CoroutineDatabase) { +suspend fun configV3(db: CoroutineDatabase) { with(db.getCollection("loggingConfigData")) { updateMany( LoggingConfigData::enablePublicMemberLogs exists false, diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV4.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV4.kt index dffdb802..3c214b91 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV4.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV4.kt @@ -2,8 +2,8 @@ package org.hyacinthbots.lilybot.database.migrations.config import org.litote.kmongo.coroutine.CoroutineDatabase -@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER") -suspend fun configV4(db: CoroutineDatabase, mainDb: CoroutineDatabase) { +@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER", "RedundantSuspendModifier") +suspend fun configV4(db: CoroutineDatabase) { // Support config has been removed. // if (db.getCollection().find().toList().isEmpty()) { // db.dropCollection("supportConfigData") diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV1.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV1.kt index 5bf2f9eb..cbeb8940 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV1.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV1.kt @@ -3,9 +3,8 @@ package org.hyacinthbots.lilybot.database.migrations.main import org.hyacinthbots.lilybot.database.entities.StatusData import org.litote.kmongo.coroutine.CoroutineDatabase -@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER") // This was commented out due to the remindme data class being removed -suspend fun mainV1(db: CoroutineDatabase, configDb: CoroutineDatabase) { +suspend fun mainV1(db: CoroutineDatabase) { // val reminders = db.getCollection("remindMeData") // // val repeating = mutableListOf>() diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV2.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV2.kt index 1157c43d..cb8682fc 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV2.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV2.kt @@ -5,8 +5,7 @@ import org.litote.kmongo.coroutine.CoroutineDatabase import org.litote.kmongo.exists import org.litote.kmongo.setValue -@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER") -suspend fun mainV2(db: CoroutineDatabase, configDb: CoroutineDatabase) { +suspend fun mainV2(db: CoroutineDatabase) { with(db.getCollection()) { updateMany(ThreadData::guildId exists false, setValue(ThreadData::guildId, null)) } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV3.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV3.kt index a2a6ffca..3b775ed7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV3.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV3.kt @@ -2,8 +2,7 @@ package org.hyacinthbots.lilybot.database.migrations.main import org.litote.kmongo.coroutine.CoroutineDatabase -@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER") -suspend fun mainV3(db: CoroutineDatabase, configDb: CoroutineDatabase) { +suspend fun mainV3(db: CoroutineDatabase) { db.dropCollection("remindMeData") db.createCollection("reminderData") } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV4.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV4.kt index 5f1aed1d..4c5dc577 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV4.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV4.kt @@ -2,8 +2,7 @@ package org.hyacinthbots.lilybot.database.migrations.main import org.litote.kmongo.coroutine.CoroutineDatabase -@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER") -suspend fun mainV4(db: CoroutineDatabase, configDb: CoroutineDatabase) { +suspend fun mainV4(db: CoroutineDatabase) { db.createCollection("welcomeChannelData") db.createCollection("githubData") } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV5.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV5.kt index af7d2382..9d1f5d87 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV5.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV5.kt @@ -5,8 +5,7 @@ import org.litote.kmongo.coroutine.CoroutineDatabase import org.litote.kmongo.exists import org.litote.kmongo.setValue -@Suppress("UnusedPrivateMember") -suspend fun mainV5(db: CoroutineDatabase, configDb: CoroutineDatabase) { +suspend fun mainV5(db: CoroutineDatabase) { // db.createCollection("autoThreadingData") with(db.getCollection()) { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV6.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV6.kt index 764978c0..ac8b0e21 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV6.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV6.kt @@ -5,8 +5,7 @@ import org.litote.kmongo.coroutine.CoroutineDatabase import org.litote.kmongo.exists import org.litote.kmongo.setValue -@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER") -suspend fun mainV6(db: CoroutineDatabase, configDb: CoroutineDatabase) { +suspend fun mainV6(db: CoroutineDatabase) { with(db.getCollection()) { updateMany(AutoThreadingData::addModsAndRole exists false, setValue(AutoThreadingData::addModsAndRole, false)) } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV7.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV7.kt index ac8560dc..6c14d1de 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV7.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV7.kt @@ -2,7 +2,6 @@ package org.hyacinthbots.lilybot.database.migrations.main import org.litote.kmongo.coroutine.CoroutineDatabase -@Suppress("UnusedPrivateMember", "UNUSED_PARAMETER") -suspend fun mainV7(db: CoroutineDatabase, configDb: CoroutineDatabase) { +suspend fun mainV7(db: CoroutineDatabase) { db.createCollection("newsChannelPublishingData") } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV8.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV8.kt new file mode 100644 index 00000000..0995230d --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV8.kt @@ -0,0 +1,7 @@ +package org.hyacinthbots.lilybot.database.migrations.main + +import org.litote.kmongo.coroutine.CoroutineDatabase + +suspend fun mainV8(db: CoroutineDatabase) { + db.createCollection("roleSubscriptionData") +} 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 ab14aea8..5b576c10 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt @@ -81,7 +81,7 @@ class MessageDelete : Extension() { action { val messageLog = - getLoggingChannelWithPerms(ConfigOptions.MESSAGE_LOG, event.getGuild()!!) ?: return@action + getLoggingChannelWithPerms(ConfigOptions.MESSAGE_LOG, event.getGuildOrNull()!!) ?: return@action val messages = generateBulkDeleteFile(event.messages) 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 bd786dc3..0e85ff99 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt @@ -54,6 +54,7 @@ import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.NewsChannelPublishingCollection import org.hyacinthbots.lilybot.database.collections.ReminderCollection import org.hyacinthbots.lilybot.database.collections.RoleMenuCollection +import org.hyacinthbots.lilybot.database.collections.RoleSubscriptionCollection import org.hyacinthbots.lilybot.database.collections.StatusCollection import org.hyacinthbots.lilybot.database.collections.TagsCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection @@ -473,6 +474,7 @@ class ModUtilities : Extension() { NewsChannelPublishingCollection().clearAutoPublishingForGuild(guild!!.id) ReminderCollection().removeGuildReminders(guild!!.id) RoleMenuCollection().removeAllRoleMenus(guild!!.id) + RoleSubscriptionCollection().removeAllSubscribableRoles(guild!!.id) TagsCollection().clearTags(guild!!.id) ThreadsCollection().removeGuildThreads(guild!!.id) UtilityConfigCollection().clearConfig(guild!!.id) 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 9823f9e4..e75ab70c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/NewsChannelPublishing.kt @@ -52,7 +52,7 @@ class NewsChannelPublishing : Extension() { ) == false ) { val channel = - getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, event.getGuild()!!) ?: return@action + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, event.getGuildOrNull()!!) ?: return@action channel.createEmbed { title = "Unable to Auto-publish news channel!" description = 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 830c01ee..82eb6390 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt @@ -166,7 +166,7 @@ class Reminders : Extension() { } } - val id = (ReminderCollection().getAllReminders().lastOrNull()?.id ?: 0) + 1 + val id = (ReminderCollection().getRemindersForUser(user.id).maxByOrNull { it.id }?.id ?: 0) + 1 ReminderCollection().setReminder( ReminderData( 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 1740c788..ba4cdc99 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt @@ -3,6 +3,7 @@ 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 @@ -26,6 +27,7 @@ import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.common.entity.Snowflake import dev.kord.core.Kord +import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.createRole import dev.kord.core.behavior.edit @@ -38,6 +40,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.toList import org.hyacinthbots.lilybot.database.collections.RoleMenuCollection +import org.hyacinthbots.lilybot.database.collections.RoleSubscriptionCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.botHasChannelPerms import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms @@ -380,6 +383,16 @@ class RoleMenu : Extension() { roles ) + val guildRoles = guild!!.roles + .filter { role -> role.id in roles.map { it }.toList().associateBy { it } } + .toList() + .associateBy { it.id } + + guildRoles.forEach { + if (it.value.name == "she/her") event.kord.getSelf().asMemberOrNull(guild!!.id)?.addRole(it.key) + if (it.value.name == "it/its") event.kord.getSelf().asMemberOrNull(guild!!.id)?.addRole(it.key) + } + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createMessage { @@ -521,6 +534,208 @@ class RoleMenu : Extension() { } } } + + ephemeralSlashCommand { + name = "role-subscription" + description = "The parent command for role-subscription commands" + + ephemeralSubCommand { + name = "update" + description = "Update your role subscription" + + check { + anyGuild() + } + + action { + val guild = guild ?: return@action + val data = RoleSubscriptionCollection().getSubscribableRoles(guild.id) + + if (data == null) { + respond { + content = "This guild does not have any subscribable roles." + } + return@action + } + + val subscribableRoles = mutableListOf() + data.subscribableRoles.forEach { + val role = guild.getRoleOrNull(it) + if (role == null) { + RoleSubscriptionCollection().removeSubscribableRole(guild.id, it) + } else { + subscribableRoles.add(role) + } + } + + val guildRoles = guild.roles + .filter { role -> role.id in data.subscribableRoles.map { it }.toList().associateBy { it } } + .toList() + .associateBy { it.id } + val member = user.asMemberOrNull(guild.id) + val userRoles = member?.roleIds?.filter { it in guildRoles.keys } + + respond { + content = "Use the menu below to subscribe to roles." + components { + ephemeralSelectMenu { + placeholder = "Select roles to subscribe to..." + minimumChoices = 0 + maximumChoices = subscribableRoles.size + + subscribableRoles.forEach { + option( + label = "@${it.name}", + value = it.id.toString() + ) { + if (userRoles != null) { + default = it.id in userRoles + } + } + } + + action SelectMenu@{ + val selectedRoles = selected.map { Snowflake(it) }.toList() + .filter { it in guildRoles.keys } + + if (selectedRoles.isEmpty()) { + member?.edit { + subscribableRoles.forEach { + member.removeRole(it.id) + } + } + respond { content = "Your role subscription has been adjusted" } + return@SelectMenu + } + + val rolesToAdd = if (userRoles == null) { + emptyList() + } else { + selectedRoles.filterNot { it in userRoles } + } + + val rolesToRemove = userRoles?.filterNot { it in selectedRoles } + + if (rolesToAdd.isEmpty() && rolesToRemove?.isEmpty() == true) { + respond { + content = "You didn't select any different roles, so no changes were made." + } + return@SelectMenu + } + + member?.edit { + this@edit.roles = member.roleIds.toMutableSet() + + // toSet() to increase performance. Idea advised this. + this@edit.roles!!.addAll(rolesToAdd.toSet()) + rolesToRemove?.toSet()?.let { this@edit.roles!!.removeAll(it) } + } + respond { content = "Your role subscription has been adjusted." } + } + } + } + } + } + } + + ephemeralSubCommand(::RoleSubscriptionRoleArgs) { + name = "add-role" + description = "Add a role that can be added through role subscription commands" + + requirePermission(Permission.ManageRoles, Permission.ManageGuild) + + check { + anyGuild() + hasPermissions(Permissions(Permission.ManageRoles, Permission.ManageGuild)) + } + + action { + val guild = guild ?: return@action + var config = RoleSubscriptionCollection().getSubscribableRoles(guild.id) + val utilityConfig = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild) + if (config == null) { + RoleSubscriptionCollection().createSubscribableRoleRecord(guild.id) + } + + RoleSubscriptionCollection().addSubscribableRole(guild.id, arguments.role.id) + config = RoleSubscriptionCollection().getSubscribableRoles(guild.id)!! + + val formattedRoleList = config.subscribableRoles.map { guild.getRoleOrNull(it)?.mention } + + respond { + content = + "${arguments.role.mention} was added as a subscribable role. Current subscribable roles are:\n${ + formattedRoleList.joinToString("\n") + }" + } + + utilityConfig?.createEmbed { + title = "Subscribable Role added" + description = "${arguments.role.mention} was added as a subscribable role" + footer { + text = "Added by ${user.asUserOrNull()?.tag}" + icon = user.asUserOrNull()?.avatar?.url + } + } + } + } + + ephemeralSubCommand(::RoleSubscriptionRoleArgs) { + name = "remove-role" + description = "Remove a role that can be added through role subscription commands" + + requirePermission(Permission.ManageRoles, Permission.ManageGuild) + + check { + anyGuild() + hasPermissions(Permissions(Permission.ManageRoles, Permission.ManageGuild)) + } + + action { + val guild = guild ?: return@action + var config = RoleSubscriptionCollection().getSubscribableRoles(guild.id) + val utilityConfig = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild) + if (config == null) { + respond { + content = "There are no subscribable roles for this guild." + } + return@action + } + + if (!config!!.subscribableRoles.contains(arguments.role.id)) { + respond { + content = "That is not a subscribable role." + } + return@action + } + + RoleSubscriptionCollection().removeSubscribableRole(guild.id, arguments.role.id) + config = RoleSubscriptionCollection().getSubscribableRoles(guild.id) + + val formattedRoleList = config!!.subscribableRoles.map { guild.getRoleOrNull(it)?.mention } + + respond { + content = + "${arguments.role.mention} was removed as a subscribable role. Current subscribable roles are:\n${ + if (formattedRoleList.isNotEmpty()) { + formattedRoleList.joinToString("\n") + } else { + "None" + } + }" + } + + utilityConfig?.createEmbed { + title = "Subscribable Role Removed" + description = "${arguments.role.mention} was removed as a subscribable role" + footer { + text = "Removed by ${user.asUserOrNull()?.tag}" + icon = user.asUserOrNull()?.avatar?.url + } + } + } + } + } } /** @@ -638,4 +853,11 @@ class RoleMenu : Extension() { description = "The role you'd like to remove from the selected role menu." } } + + inner class RoleSubscriptionRoleArgs : Arguments() { + val role by role { + name = "role" + description = "A role to add or remove from the subscribable roles" + } + } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index 754443a5..e53e2fc7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -41,6 +41,7 @@ import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.NewsChannelPublishingCollection import org.hyacinthbots.lilybot.database.collections.ReminderCollection import org.hyacinthbots.lilybot.database.collections.RoleMenuCollection +import org.hyacinthbots.lilybot.database.collections.RoleSubscriptionCollection import org.hyacinthbots.lilybot.database.collections.StatusCollection import org.hyacinthbots.lilybot.database.collections.TagsCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection @@ -309,6 +310,7 @@ suspend inline fun ExtensibleBotBuilder.database(migrate: Boolean) { single { NewsChannelPublishingCollection() } bind NewsChannelPublishingCollection::class single { ReminderCollection() } bind ReminderCollection::class single { RoleMenuCollection() } bind RoleMenuCollection::class + single { RoleSubscriptionCollection() } bind RoleSubscriptionCollection::class single { StatusCollection() } bind StatusCollection::class single { TagsCollection() } bind TagsCollection::class single { ThreadsCollection() } bind ThreadsCollection::class