diff --git a/.idea/git_toolbox_blame.xml b/.idea/git_toolbox_blame.xml
new file mode 100644
index 00000000..7dc12496
--- /dev/null
+++ b/.idea/git_toolbox_blame.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 1cb65401..3e7c8bdd 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -106,5 +106,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index fe63bb67..996b1512 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/modules/CozyDiscord.main.iml b/.idea/modules/CozyDiscord.main.iml
index f3d2bce9..bf88872f 100644
--- a/.idea/modules/CozyDiscord.main.iml
+++ b/.idea/modules/CozyDiscord.main.iml
@@ -9,4 +9,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index b0a96881..a612ad98 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,3 +1,373 @@
-This Source Code Form is subject to the terms of the Mozilla Public
-License, v. 2.0. If a copy of the MPL was not distributed with this
-file, You can obtain one at https://mozilla.org/MPL/2.0/.
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/build.gradle.kts b/build.gradle.kts
index f144acea..7bd2e96c 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -10,46 +10,12 @@ plugins {
`cozy-module`
`shadow-module`
- application
-
id("com.github.jakemarsden.git-hooks")
}
allprojects {
repositories {
mavenLocal()
- mavenCentral()
- google()
-
- maven {
- name = "Sonatype Snapshots (Legacy)"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
- }
-
- maven {
- name = "Sonatype Snapshots"
- 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 = "JitPack"
- url = uri("https://jitpack.io")
- }
}
}
@@ -57,23 +23,12 @@ dependencies {
detektPlugins(libs.detekt)
detektPlugins(libs.detekt.libraries)
- ksp(libs.kordex.annotationProcessor)
-
implementation(libs.excelkt)
implementation(libs.kmongo)
implementation(libs.rgxgen)
implementation(libs.ktor.client.encoding)
- implementation(libs.kordex.annotations)
- implementation(libs.kordex.core)
- implementation(libs.kordex.mappings)
- implementation(libs.kordex.phishing)
- implementation(libs.kordex.pluralkit)
- implementation(libs.kordex.tags)
- implementation(libs.kordex.unsafe)
- implementation(libs.kordex.welcome)
-
implementation(libs.commons.text)
implementation(libs.homoglyph)
implementation(libs.jansi)
@@ -94,7 +49,20 @@ dependencies {
implementation(projects.moduleLogParser)
implementation(projects.moduleModeration)
implementation(projects.moduleRoleSync)
- implementation(projects.moduleUserCleanup)
+}
+
+kordEx {
+ bot {
+ mainClass = "org.quiltmc.community.AppKt"
+ }
+
+ module("annotations")
+ module("extra-mappings")
+ module("extra-phishing")
+ module("extra-pluralkit")
+ module("extra-tags")
+ module("extra-welcome")
+ module("unsafe")
}
graphql {
@@ -106,11 +74,6 @@ graphql {
}
}
-application {
- // This is deprecated, but the Shadow plugin requires it
- mainClass = "org.quiltmc.community.AppKt"
-}
-
gitHooks {
setHooks(
mapOf("pre-commit" to "updateLicense detekt")
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 0d73e404..a9800e4f 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -17,15 +17,14 @@ dependencies {
implementation(gradleApi())
implementation(localGroovy())
- implementation(kotlin("gradle-plugin", version = "1.9.23"))
- implementation(kotlin("serialization", version = "1.9.23"))
+ implementation(kotlin("gradle-plugin", version = "2.0.20-Beta1"))
+ implementation(kotlin("serialization", version = "2.0.20-Beta1"))
- implementation("gradle.plugin.org.cadixdev.gradle", "licenser", "0.6.1")
implementation("com.github.jakemarsden", "git-hooks-gradle-plugin", "0.0.2")
- implementation("com.google.devtools.ksp", "com.google.devtools.ksp.gradle.plugin", "1.9.23-1.0.19")
- implementation("io.gitlab.arturbosch.detekt", "detekt-gradle-plugin", "1.23.5")
-// implementation("org.ec4j.editorconfig", "org.ec4j.editorconfig.gradle.plugin", "0.0.3")
-
- implementation("com.expediagroup.graphql", "com.expediagroup.graphql.gradle.plugin", "7.0.2")
+ implementation("com.expediagroup.graphql", "com.expediagroup.graphql.gradle.plugin", "7.1.4")
implementation("com.github.johnrengelman.shadow", "com.github.johnrengelman.shadow.gradle.plugin", "8.1.1")
+ implementation("com.google.devtools.ksp", "com.google.devtools.ksp.gradle.plugin", "2.0.20-Beta1-1.0.22")
+ implementation("dev.kordex.gradle.plugins", "kordex", "1.1.3")
+ implementation("dev.yumi", "yumi-gradle-licenser", "1.2.0")
+ implementation("io.gitlab.arturbosch.detekt", "detekt-gradle-plugin", "1.23.6")
}
diff --git a/buildSrc/src/main/kotlin/cozy-module.gradle.kts b/buildSrc/src/main/kotlin/cozy-module.gradle.kts
index 27ae4350..60df1fce 100644
--- a/buildSrc/src/main/kotlin/cozy-module.gradle.kts
+++ b/buildSrc/src/main/kotlin/cozy-module.gradle.kts
@@ -4,28 +4,21 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-import org.cadixdev.gradle.licenser.LicenseExtension
-//import org.ec4j.gradle.EditorconfigCheckTask
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
id("com.expediagroup.graphql")
id("com.google.devtools.ksp")
+ id("dev.kordex.gradle.kordex")
+ id("dev.yumi.gradle.licenser")
id("io.gitlab.arturbosch.detekt")
- id("org.cadixdev.licenser")
-// id("org.ec4j.editorconfig")
}
group = "org.quiltmc.community"
version = "1.0.1-SNAPSHOT"
repositories {
- mavenLocal()
- google()
-
maven {
name = "Sleeping Town"
url = uri("https://repo.sleeping.town")
@@ -34,41 +27,6 @@ repositories {
includeGroup("com.unascribed")
}
}
-
- maven {
- name = "Sonatype Snapshots"
- url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots")
- }
-
- maven {
- name = "Sonatype Snapshots (Legacy)"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
- }
-
- 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 = "Fabric"
- url = uri("https://maven.fabricmc.net")
- }
-
- maven {
- name = "JitPack"
- url = uri("https://jitpack.io")
- }
}
configurations.all {
@@ -76,60 +34,25 @@ configurations.all {
resolutionStrategy.cacheChangingModulesFor(10, "seconds")
}
-tasks {
- afterEvaluate {
- withType().configureEach {
- kotlinOptions {
- jvmTarget = "17"
- freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
- }
- }
-
- license {
- header(rootDir.toPath().resolve("LICENSE"))
- }
-
- java {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- }
-
-// check {
-// dependsOn("editorconfigCheck")
-// }
-//
-// editorconfig {
-// excludes = mutableListOf(
-// "build",
-// ".*/**",
-// )
-// }
- }
-}
-
detekt {
buildUponDefaultConfig = true
config.from(rootProject.files("detekt.yml"))
}
-// Credit to ZML for this workaround.
-// https://github.com/CadixDev/licenser/issues/6#issuecomment-817048318
-extensions.configure(LicenseExtension::class.java) {
- exclude {
- it.file.startsWith(buildDir)
- }
+license {
+ rule(rootProject.file("codeformat/HEADER"))
}
sourceSets {
main {
java {
- srcDir(file("$buildDir/generated/ksp/main/kotlin/"))
+ srcDir(file("${layout.buildDirectory.get()}/generated/ksp/main/kotlin/"))
}
}
test {
java {
- srcDir(file("$buildDir/generated/ksp/test/kotlin/"))
+ srcDir(file("${layout.buildDirectory.get()}/generated/ksp/test/kotlin/"))
}
}
}
@@ -139,3 +62,11 @@ val sourceJar = task("sourceJar", Jar::class) {
archiveClassifier.set("sources")
from(sourceSets.main.get().allSource)
}
+
+kordEx {
+ // Required due to that GraphQL client, because one of their other modules uses Spring 3, apparently.
+ // No, I don't think that's a good reason either.
+ // -- gdude
+
+ jvmTarget = 17
+}
diff --git a/buildSrc/src/main/kotlin/shadow-module.gradle.kts b/buildSrc/src/main/kotlin/shadow-module.gradle.kts
index 1a1c887e..37214f54 100644
--- a/buildSrc/src/main/kotlin/shadow-module.gradle.kts
+++ b/buildSrc/src/main/kotlin/shadow-module.gradle.kts
@@ -1,11 +1,11 @@
-import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
-
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+
plugins {
id("com.github.johnrengelman.shadow")
}
diff --git a/codeformat/HEADER b/codeformat/HEADER
new file mode 100644
index 00000000..b0a96881
--- /dev/null
+++ b/codeformat/HEADER
@@ -0,0 +1,3 @@
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at https://mozilla.org/MPL/2.0/.
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b02619dd..81a6f0d7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,28 +1,27 @@
[versions]
-detekt = "1.23.5"
-kotlin = "1.9.23"
-graphql = "7.0.2"
+detekt = "1.23.6"
+kotlin = "2.0.20-Beta1"
+graphql = "7.1.4"
autolink = "0.11.0"
commons-text = "1.11.0"
excelkt = "1.0.2"
flexver = "1.1.1"
-groovy = "3.0.21"
+groovy = "3.0.22"
homoglyph = "1.2.1"
jansi = "2.4.1"
-jsoup = "1.17.2"
-kaml = "0.58.0"
+jsoup = "1.18.1"
+kaml = "0.60.0"
kmongo = "4.11.0"
-kordex = "1.8.0-SNAPSHOT"
kotlintest = "3.4.2"
-ktor = "2.3.9"
-kx-ser = "1.6.3"
-logback = "1.5.3"
+ktor = "2.3.12"
+kx-ser = "1.7.1"
+logback = "1.5.6"
logback-groovy = "1.14.5"
-logging = "6.0.3"
+logging = "7.0.0"
moshi = "1.15.1"
rgxgen = "2.0"
-semver = "1.4.2"
+semver = "2.0.0"
[libraries]
autolink = { module = "org.nibor.autolink:autolink", version.ref = "autolink" }
@@ -38,15 +37,6 @@ jansi = { module = "org.fusesource.jansi:jansi", version.ref = "jansi" }
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
kaml = { module = "com.charleskorn.kaml:kaml", version.ref = "kaml" }
kmongo = { module = "org.litote.kmongo:kmongo-coroutine-serialization", version.ref = "kmongo" }
-kordex-annotations = { module = "com.kotlindiscord.kord.extensions:annotations", version.ref = "kordex" }
-kordex-annotationProcessor = { module = "com.kotlindiscord.kord.extensions:annotation-processor", version.ref = "kordex" }
-kordex-core = { module = "com.kotlindiscord.kord.extensions:kord-extensions", version.ref = "kordex" }
-kordex-mappings = { module = "com.kotlindiscord.kord.extensions:extra-mappings", version.ref = "kordex" }
-kordex-phishing = { module = "com.kotlindiscord.kord.extensions:extra-phishing", version.ref = "kordex" }
-kordex-pluralkit = { module = "com.kotlindiscord.kord.extensions:extra-pluralkit", version.ref = "kordex" }
-kordex-unsafe = { module = "com.kotlindiscord.kord.extensions:unsafe", version.ref = "kordex" }
-kordex-tags = { module = "com.kotlindiscord.kord.extensions:extra-tags", version.ref = "kordex" }
-kordex-welcome = { module = "com.kotlindiscord.kord.extensions:extra-welcome", version.ref = "kordex" }
kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 943f0cbf..d64cd491 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 509c4a29..09523c0e 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
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.9-bin.zip
networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 79a61d42..1aa94a42 100755
--- a/gradlew
+++ b/gradlew
@@ -83,10 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-# 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"'
+# 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
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,10 +131,13 @@ location of your Java installation."
fi
else
JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
@@ -144,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
@@ -152,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
@@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done
fi
-# 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.
+
+# 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, 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/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
diff --git a/module-ama/build.gradle.kts b/module-ama/build.gradle.kts
index aeb5d31b..1ea0d550 100644
--- a/module-ama/build.gradle.kts
+++ b/module-ama/build.gradle.kts
@@ -8,15 +8,14 @@ dependencies {
detektPlugins(libs.detekt)
detektPlugins(libs.detekt.libraries)
- ksp(libs.kordex.annotationProcessor)
-
- implementation(libs.kordex.annotations)
- implementation(libs.kordex.core)
- implementation(libs.kordex.unsafe)
- implementation(libs.kordex.pluralkit)
-
implementation(libs.logging)
implementation(platform(libs.kotlin.bom))
implementation(libs.kotlin.stdlib)
}
+
+kordEx {
+ module("annotations")
+ module("extra-pluralkit")
+ module("unsafe")
+}
diff --git a/module-ama/src/main/kotlin/org/quiltmc/community/cozy/modules/ama/_PkUtils.kt b/module-ama/src/main/kotlin/org/quiltmc/community/cozy/modules/ama/_PkUtils.kt
index a33140c6..334cb48b 100644
--- a/module-ama/src/main/kotlin/org/quiltmc/community/cozy/modules/ama/_PkUtils.kt
+++ b/module-ama/src/main/kotlin/org/quiltmc/community/cozy/modules/ama/_PkUtils.kt
@@ -24,7 +24,6 @@ import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.delay
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
-import kotlinx.datetime.toInstant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@@ -99,7 +98,7 @@ private suspend inline fun get(url: String, block: HttpRequestBuilder.() -> Unit
while (true) {
val response = client.get(url, block)
if (response.status == HttpStatusCode.TooManyRequests) {
- val retryAfter = response.headers["X-RateLimit-Reset"]?.toInstant()
+ val retryAfter = response.headers["X-RateLimit-Reset"]?.let { Instant.parse(it) }
if (retryAfter != null) {
val waitTime = retryAfter.toEpochMilliseconds() - Clock.System.now().toEpochMilliseconds()
if (waitTime > 0) {
diff --git a/module-log-parser/build.gradle.kts b/module-log-parser/build.gradle.kts
index de0e942a..a6c30d78 100644
--- a/module-log-parser/build.gradle.kts
+++ b/module-log-parser/build.gradle.kts
@@ -14,17 +14,8 @@ dependencies {
detektPlugins(libs.detekt)
detektPlugins(libs.detekt.libraries)
- ksp(libs.kordex.annotationProcessor)
-
implementation(libs.ktor.client.cio)
- implementation(libs.kordex.annotations)
- implementation(libs.kordex.core)
- implementation(libs.kordex.unsafe)
-
- // Optional for bots that don't need it
- compileOnly(libs.kordex.pluralkit)
-
implementation(libs.autolink)
implementation(libs.flexver)
implementation(libs.jsoup)
@@ -35,3 +26,9 @@ dependencies {
implementation(platform(libs.kotlin.bom))
implementation(libs.kotlin.stdlib)
}
+
+kordEx {
+ module("annotations")
+ module("extra-pluralkit")
+ module("unsafe")
+}
diff --git a/module-log-parser/src/main/kotlin/org/quiltmc/community/cozy/modules/logs/Utils.kt b/module-log-parser/src/main/kotlin/org/quiltmc/community/cozy/modules/logs/Utils.kt
index b36c970a..1925391f 100644
--- a/module-log-parser/src/main/kotlin/org/quiltmc/community/cozy/modules/logs/Utils.kt
+++ b/module-log-parser/src/main/kotlin/org/quiltmc/community/cozy/modules/logs/Utils.kt
@@ -14,6 +14,7 @@ import org.nibor.autolink.LinkExtractor
import org.nibor.autolink.LinkType
import org.quiltmc.community.cozy.modules.logs.config.LogParserConfig
import org.quiltmc.community.cozy.modules.logs.config.SimpleLogParserConfig
+import java.net.URI
import java.net.URL
public inline fun ExtensibleBotBuilder.ExtensionsBuilder.extLogParser(
@@ -38,7 +39,7 @@ public val linkExtractor: LinkExtractor = LinkExtractor.builder()
public fun String.parseUrls(): List =
linkExtractor.extractLinks(this).map {
- URL(this.substring(it.beginIndex, it.endIndex))
+ URI(this.substring(it.beginIndex, it.endIndex)).toURL()
}
public fun String.versionCompare(other: String): Int =
diff --git a/module-log-parser/src/main/kotlin/org/quiltmc/community/cozy/modules/logs/parsers/LauncherParser.kt b/module-log-parser/src/main/kotlin/org/quiltmc/community/cozy/modules/logs/parsers/LauncherParser.kt
index 1f65061e..e086d3be 100644
--- a/module-log-parser/src/main/kotlin/org/quiltmc/community/cozy/modules/logs/parsers/LauncherParser.kt
+++ b/module-log-parser/src/main/kotlin/org/quiltmc/community/cozy/modules/logs/parsers/LauncherParser.kt
@@ -46,8 +46,8 @@ public class LauncherParser : LogParser() {
}
if (launcher == Launcher.Prism && log.launcherVersion != null) {
- val version: Version = Version.valueOf(log.launcherVersion!!)
- if (version.compareTo(Version.valueOf("7.0")) < 0) {
+ val version: Version = Version.parse(log.launcherVersion!!)
+ if (version.compareTo(Version.parse("7.0")) < 0) {
log.addMessage(
"**It looks like you're using an outdated version of Prism.** +" +
"Please note that Prism [formerly broke uploaded logs]" +
diff --git a/module-moderation/build.gradle.kts b/module-moderation/build.gradle.kts
index d0cf9bc6..d259e0d2 100644
--- a/module-moderation/build.gradle.kts
+++ b/module-moderation/build.gradle.kts
@@ -14,14 +14,13 @@ dependencies {
detektPlugins(libs.detekt)
detektPlugins(libs.detekt.libraries)
- ksp(libs.kordex.annotationProcessor)
-
- implementation(libs.kordex.annotations)
- implementation(libs.kordex.core)
- implementation(libs.kordex.pluralkit)
-
implementation(libs.logging)
implementation(platform(libs.kotlin.bom))
implementation(libs.kotlin.stdlib)
}
+
+kordEx {
+ module("annotations")
+ module("extra-pluralkit")
+}
diff --git a/module-role-sync/build.gradle.kts b/module-role-sync/build.gradle.kts
index 592b9e43..499bb963 100644
--- a/module-role-sync/build.gradle.kts
+++ b/module-role-sync/build.gradle.kts
@@ -14,13 +14,12 @@ dependencies {
detektPlugins(libs.detekt)
detektPlugins(libs.detekt.libraries)
- ksp(libs.kordex.annotationProcessor)
-
- implementation(libs.kordex.annotations)
- implementation(libs.kordex.core)
-
implementation(libs.logging)
implementation(platform(libs.kotlin.bom))
implementation(libs.kotlin.stdlib)
}
+
+kordEx {
+ module("annotations")
+}
diff --git a/module-user-cleanup/build.gradle.kts b/module-user-cleanup/build.gradle.kts
deleted file mode 100644
index 592b9e43..00000000
--- a/module-user-cleanup/build.gradle.kts
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-plugins {
- `api-module`
- `cozy-module`
- `published-module`
-}
-
-dependencies {
- detektPlugins(libs.detekt)
- detektPlugins(libs.detekt.libraries)
-
- ksp(libs.kordex.annotationProcessor)
-
- implementation(libs.kordex.annotations)
- implementation(libs.kordex.core)
-
- implementation(libs.logging)
-
- implementation(platform(libs.kotlin.bom))
- implementation(libs.kotlin.stdlib)
-}
diff --git a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/UserCleanupExtension.kt b/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/UserCleanupExtension.kt
deleted file mode 100644
index 10b624c1..00000000
--- a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/UserCleanupExtension.kt
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.cozy.modules.cleanup
-
-import com.kotlindiscord.kord.extensions.DISCORD_BLURPLE
-import com.kotlindiscord.kord.extensions.DISCORD_FUCHSIA
-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.converters.impl.defaultingBoolean
-import com.kotlindiscord.kord.extensions.extensions.Extension
-import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand
-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.createMessage
-import dev.kord.core.entity.Guild
-import dev.kord.core.entity.Member
-import io.github.oshai.kotlinlogging.KotlinLogging
-import io.ktor.client.request.forms.*
-import io.ktor.utils.io.jvm.javaio.*
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.toList
-import kotlinx.datetime.Clock
-import kotlinx.datetime.toJavaInstant
-import org.quiltmc.community.cozy.modules.cleanup.config.UserCleanupConfig
-import java.time.ZoneId
-import java.time.format.DateTimeFormatter
-import java.time.format.FormatStyle
-
-private const val MEMBER_CHUNK_SIZE = 15
-
-/**
- * User cleanup extension, handles the cleanup of pending users after they've lurked for a while.
- */
-public class UserCleanupExtension(
- private val config: UserCleanupConfig
-) : Extension() {
- override val name: String = UserCleanupPlugin.id
- private lateinit var instantFormatter: DateTimeFormatter
-
- private val logger = KotlinLogging.logger {}
- private val scheduler = Scheduler()
-
- private lateinit var task: Task
-
- override suspend fun setup() {
- instantFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
- .withLocale(config.dateFormattingLocale)
- .withZone(ZoneId.of("UTC"))
-
- if (config.runAutomatically) {
- task = scheduler.schedule(config.getTaskDelay(), pollingSeconds = 60, repeat = true, callback = ::taskRun)
- }
-
- if (!config.registerCommand) {
- return
- }
-
- ephemeralSlashCommand(::CleanupArgs) {
- name = "cleanup-users"
- description = "Clean up user accounts that haven't passed member screening"
-
- allowInDms = false
-
- check { anyGuild() }
-
- config.getCommandChecks().forEach(::check)
-
- check {
- val guild = guildFor(event)?.asGuild()
-
- if (guild != null) {
- failIf("This server isn't configured for user cleanup.") { !config.checkGuild(guild) }
- }
- }
-
- action {
- val removed = processGuild(guild!!.asGuild(), arguments.dryRun)
-
- if (removed.isEmpty()) {
- respond {
- content = "It doesn't look like there's anyone that needs to be removed."
- }
-
- return@action
- }
-
- editingPaginator {
- removed.chunked(MEMBER_CHUNK_SIZE).forEach { chunk ->
- page {
- color = DISCORD_FUCHSIA
- title = "User Cleanup"
-
- if (arguments.dryRun) {
- color = DISCORD_BLURPLE
- title += " (dry-run)"
- }
-
- description = "**Mention** | **Tag** | **Join date**\n\n"
-
- chunk.forEach { member ->
- description += "${member.mention} | ${member.tag} |" +
- "${member.joinedAt.toDiscord(TimestampType.Default)}\n"
- }
- }
- }
- }.send()
- }
- }
- }
-
- private suspend fun processGuild(guild: Guild, dryRun: Boolean): MutableSet {
- val pendingDuration = config.getMaxPendingDuration()
- val removed: MutableSet = mutableSetOf()
- val now = Clock.System.now()
-
- guild.members
- .filter { it.isPending && !it.isBot }
- .filter { (it.joinedAt + pendingDuration) <= now }
- .toList()
- .forEach {
- if (!dryRun) {
- it.kick("Didn't pass member screening quickly enough.")
- }
-
- removed += it
- }
-
- return removed
- }
-
- private suspend fun taskRun() {
- val guilds = kord.guilds
- .filter { config.checkGuild(it) }
- .toList()
-
- guilds.forEach { guild ->
- val removed = processGuild(guild, false)
-
- if (removed.isNotEmpty()) {
- logger.info { "Removed ${removed.size} users from ${guild.name} (${guild.id})" }
-
- val table = "| User ID | Tag | Join Date (UTC) |\n" +
- "| ------- | --- | --------------- |\n" +
-
- removed.joinToString("\n") {
- "| ${it.id} | ${it.tag} | ${instantFormatter.format(it.joinedAt.toJavaInstant())} |"
- }
-
- config.getLoggingChannel(guild).createMessage {
- content = "**User Cleanup:** Cleaned up ${removed.size} users that didn't pass member " +
- "screening quickly enough."
-
- addFile("users.md", ChannelProvider { table.byteInputStream().toByteReadChannel() })
- }
- }
- }
- }
-
- override suspend fun unload() {
- super.unload()
-
- if (::task.isInitialized) {
- task.cancel()
- }
- }
-
- internal inner class CleanupArgs : Arguments() {
- internal val dryRun by defaultingBoolean {
- name = "dry-run"
- description = "Whether to preview the members to kick instead of actually kicking them"
-
- defaultValue = true
- }
- }
-}
diff --git a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/UserCleanupPlugin.kt b/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/UserCleanupPlugin.kt
deleted file mode 100644
index c81a4c18..00000000
--- a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/UserCleanupPlugin.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.cozy.modules.cleanup
-
-import com.kotlindiscord.kord.extensions.plugins.KordExPlugin
-import com.kotlindiscord.kord.extensions.plugins.annotations.plugins.WiredPlugin
-import org.pf4j.PluginWrapper
-
-/**
- * Plugin containing the [UserCleanupExtension], which removes pending users after they've lurked for a while.
- */
-@WiredPlugin(
- id = UserCleanupPlugin.id,
- version = "1.0.1-SNAPSHOT",
-
- author = "QuiltMC",
- description = "Automatic cleanup of lurking users that don't pass member screening",
- license = "Mozilla Public License 2.0"
-)
-public class UserCleanupPlugin(wrapper: PluginWrapper) : KordExPlugin(wrapper) {
- override suspend fun setup() {
-// TODO: We can't really use the plugin system just yet since it doesn't currently support any configuration tooling
-// extension(::UserCleanupExtension)
- }
-
- public companion object {
- public const val id: String = "quiltmc-user-cleanup"
- }
-}
diff --git a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/_Types.kt b/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/_Types.kt
deleted file mode 100644
index 272efb90..00000000
--- a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/_Types.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-@file:Suppress("Filename")
-
-package org.quiltmc.community.cozy.modules.cleanup
-
-import dev.kord.core.entity.Guild
-
-public typealias GuildPredicate = suspend (guild: Guild) -> Boolean
diff --git a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/_Utils.kt b/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/_Utils.kt
deleted file mode 100644
index 469adab2..00000000
--- a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/_Utils.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.cozy.modules.cleanup
-
-import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder
-import org.quiltmc.community.cozy.modules.cleanup.config.SimpleUserCleanupConfig
-import org.quiltmc.community.cozy.modules.cleanup.config.UserCleanupConfig
-
-public fun ExtensibleBotBuilder.ExtensionsBuilder.userCleanup(config: UserCleanupConfig) {
- add { UserCleanupExtension(config) }
-}
-
-public fun ExtensibleBotBuilder.ExtensionsBuilder.userCleanup(body: SimpleUserCleanupConfig.Builder.() -> Unit): Unit =
- userCleanup(SimpleUserCleanupConfig(body))
diff --git a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/config/SimpleUserCleanupConfig.kt b/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/config/SimpleUserCleanupConfig.kt
deleted file mode 100644
index 368869e7..00000000
--- a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/config/SimpleUserCleanupConfig.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.cozy.modules.cleanup.config
-
-import com.kotlindiscord.kord.extensions.checks.types.Check
-import dev.kord.core.entity.Guild
-import dev.kord.core.entity.channel.GuildMessageChannel
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterIsInstance
-import kotlinx.coroutines.flow.lastOrNull
-import org.quiltmc.community.cozy.modules.cleanup.GuildPredicate
-import java.util.*
-import kotlin.time.Duration
-
-public class SimpleUserCleanupConfig(builder: Builder) : UserCleanupConfig() {
- override val dateFormattingLocale: Locale = builder.dateFormattingLocale
- override val registerCommand: Boolean = builder.registerCommand
- override val runAutomatically: Boolean = builder.runAutomatically
-
- private val taskDelay: Duration = builder.taskDelay!!
- private val maxPendingDuration: Duration = builder.maxPendingDuration!!
- private val loggingChannelName: String = builder.loggingChannelName!!
-
- private val guildPredicates: MutableList = builder.guildPredicates
- private val commandChecks: MutableList> = builder.commandChecks
-
- init {
- guildPredicates.add { guild ->
- getLoggingChannelOrNull(guild) != null
- }
- }
-
- override suspend fun checkGuild(guild: Guild): Boolean =
- guildPredicates.all { it(guild) }
-
- override suspend fun getCommandChecks(): List> =
- commandChecks
-
- override suspend fun getLoggingChannelOrNull(guild: Guild): GuildMessageChannel? =
- guild.channels
- .filterIsInstance()
- .filter { channel -> channel.name.equals(loggingChannelName, true) }
- .lastOrNull()
-
- override suspend fun getMaxPendingDuration(): Duration =
- maxPendingDuration
-
- override suspend fun getTaskDelay(): Duration =
- taskDelay
-
- public class Builder {
- public var dateFormattingLocale: Locale = Locale.UK
- public var taskDelay: Duration? = null
- public var maxPendingDuration: Duration? = null
- public var loggingChannelName: String? = null
-
- public var runAutomatically: Boolean = true
- public var registerCommand: Boolean = true
-
- internal val commandChecks: MutableList> = mutableListOf()
- internal val guildPredicates: MutableList = mutableListOf()
-
- public fun validate() {
- if (taskDelay == null) {
- error("Required property not set: taskDelay")
- }
-
- if (maxPendingDuration == null) {
- error("Required property not set: maxPendingDuration")
- }
-
- if (loggingChannelName == null) {
- error("Required property not set: loggingChannelName")
- }
- }
-
- public fun commandCheck(body: Check<*>) {
- commandChecks.add(body)
- }
-
- public fun guildPredicate(body: GuildPredicate) {
- guildPredicates.add(body)
- }
- }
-}
-
-public inline fun SimpleUserCleanupConfig(body: SimpleUserCleanupConfig.Builder.() -> Unit): SimpleUserCleanupConfig {
- val builder = SimpleUserCleanupConfig.Builder()
-
- body(builder)
-
- return SimpleUserCleanupConfig(builder)
-}
diff --git a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/config/UserCleanupConfig.kt b/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/config/UserCleanupConfig.kt
deleted file mode 100644
index 152a3aa4..00000000
--- a/module-user-cleanup/src/main/kotlin/org/quiltmc/community/cozy/modules/cleanup/config/UserCleanupConfig.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.cozy.modules.cleanup.config
-
-import com.kotlindiscord.kord.extensions.checks.types.Check
-import dev.kord.core.entity.Guild
-import dev.kord.core.entity.channel.GuildMessageChannel
-import java.util.*
-import kotlin.time.Duration
-
-/**
- * Abstract class representing the configuration for the user cleanup plugin.
- *
- * Extend this class to implement your configuration loader. If you only need a basic config, then you can
- * take a look at [SimpleUserCleanupConfig] instead.
- */
-public abstract class UserCleanupConfig {
- /** Locale to use for formatting user join dates. **/
- public abstract val dateFormattingLocale: Locale
-
- /** Whether to run user cleanups automatically via scheduled task. **/
- public abstract val runAutomatically: Boolean
-
- /** Whether to register a slash command for running cleanups manually. **/
- public abstract val registerCommand: Boolean
-
- /**
- * Override this to specify the channel where cleanup operations should be logged, given the guild being
- * cleaned up. Return `null` if no channel could be found.
- */
- public abstract suspend fun getLoggingChannelOrNull(guild: Guild): GuildMessageChannel?
-
- /** Override this to supply a [Duration] representing how long to wait before kicking a pending user. **/
- public abstract suspend fun getMaxPendingDuration(): Duration
-
- /** Override this to supply a [Duration] representing the time between task runs. **/
- public abstract suspend fun getTaskDelay(): Duration
-
- /**
- * Wrapping function for [getLoggingChannelOrNull], with a not-null assertion.
- */
- public open suspend fun getLoggingChannel(guild: Guild): GuildMessageChannel =
- getLoggingChannelOrNull(guild)!!
-
- /**
- * Override this to provide a check against the given guild, returning `true` if cleanup operations should run
- * on the given guild, or `false` otherwise.
- */
- public open suspend fun checkGuild(guild: Guild): Boolean = true
-
- /**
- * Override this to provide a list of KordEx [Check]s that must pass for the cleanup command to be executed.
- */
- public open suspend fun getCommandChecks(): List> = listOf()
-}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index ebf53d1e..d7584f34 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -15,8 +15,7 @@ rootProject.name = "CozyDiscord"
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
+include(":module-ama")
include(":module-log-parser")
include(":module-moderation")
include(":module-role-sync")
-include(":module-user-cleanup")
-include(":module-ama")
diff --git a/src/main/kotlin/org/quiltmc/community/App.kt b/src/main/kotlin/org/quiltmc/community/App.kt
index 9e2b7f99..dad4fc2f 100644
--- a/src/main/kotlin/org/quiltmc/community/App.kt
+++ b/src/main/kotlin/org/quiltmc/community/App.kt
@@ -44,7 +44,6 @@ import org.quiltmc.community.modes.quilt.extensions.messagelog.MessageLogExtensi
import org.quiltmc.community.modes.quilt.extensions.minecraft.MinecraftExtension
import org.quiltmc.community.modes.quilt.extensions.modhostverify.ModHostingVerificationExtension
import org.quiltmc.community.modes.quilt.extensions.settings.SettingsExtension
-import org.quiltmc.community.modes.quilt.extensions.suggestions.SuggestionsExtension
import kotlin.time.Duration.Companion.minutes
val MODE = envOrNull("MODE")?.lowercase() ?: "quilt"
@@ -66,8 +65,6 @@ suspend fun setupDev() = ExtensibleBot(DISCORD_TOKEN) {
database()
extensions {
- add(::SubteamsExtension)
-
extMappings { }
if (GITHUB_TOKEN != null) {
@@ -107,10 +104,8 @@ suspend fun setupQuilt() = ExtensibleBot(DISCORD_TOKEN) {
add(::MessageLogExtension)
add(::MinecraftExtension)
add(::ModHostingVerificationExtension)
- add(::PKExtension)
add(::SettingsExtension)
add(::ShowcaseExtension)
- add(::SuggestionsExtension)
add(::SyncExtension)
add(::UtilityExtension)
diff --git a/src/main/kotlin/org/quiltmc/community/_Utils.kt b/src/main/kotlin/org/quiltmc/community/_Utils.kt
index f2f9334d..13af8bf2 100644
--- a/src/main/kotlin/org/quiltmc/community/_Utils.kt
+++ b/src/main/kotlin/org/quiltmc/community/_Utils.kt
@@ -27,8 +27,8 @@ import dev.kord.core.entity.Message
import dev.kord.core.entity.channel.GuildMessageChannel
import dev.kord.rest.builder.message.EmbedBuilder
import dev.kord.rest.request.RestRequestException
+import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.runBlocking
-import mu.KotlinLogging
import org.koin.dsl.bind
import org.quiltmc.community.database.Database
import org.quiltmc.community.database.collections.*
@@ -108,15 +108,11 @@ suspend fun ExtensibleBotBuilder.database(migrate: Boolean = false) {
loadModule {
single { AmaConfigCollection() } bind AmaConfigCollection::class
single { FilterCollection() } bind FilterCollection::class
- single { FilterEventCollection() } bind FilterEventCollection::class
single { GlobalSettingsCollection() } bind GlobalSettingsCollection::class
single { LinkedMessagesCollection() } bind LinkedMessagesCollection::class
single { MetaCollection() } bind MetaCollection::class
single { OwnedThreadCollection() } bind OwnedThreadCollection::class
single { ServerSettingsCollection() } bind ServerSettingsCollection::class
- single { ServerApplicationCollection() } bind ServerApplicationCollection::class
- single { SuggestionsCollection() } bind SuggestionsCollection::class
- single { TeamCollection() } bind TeamCollection::class
single { UserFlagsCollection() } bind UserFlagsCollection::class
single { TagsCollection() } bind TagsCollection::class
single { WelcomeChannelCollection() } bind WelcomeChannelCollection::class
diff --git a/src/main/kotlin/org/quiltmc/community/database/Migrations.kt b/src/main/kotlin/org/quiltmc/community/database/Migrations.kt
index fd2f23a4..8cf9cd74 100644
--- a/src/main/kotlin/org/quiltmc/community/database/Migrations.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/Migrations.kt
@@ -7,7 +7,7 @@
package org.quiltmc.community.database
import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
import org.quiltmc.community.database.collections.MetaCollection
import org.quiltmc.community.database.entities.Meta
@@ -65,6 +65,7 @@ object Migrations : KordExKoinComponent {
22 -> ::v22
23 -> ::v23
24 -> ::v24
+ 25 -> ::v25
else -> break
}(db.mongo)
diff --git a/src/main/kotlin/org/quiltmc/community/database/collections/FilterEventCollection.kt b/src/main/kotlin/org/quiltmc/community/database/collections/FilterEventCollection.kt
deleted file mode 100644
index 4859c24a..00000000
--- a/src/main/kotlin/org/quiltmc/community/database/collections/FilterEventCollection.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.database.collections
-
-import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
-import dev.kord.core.behavior.GuildBehavior
-import dev.kord.core.behavior.MessageBehavior
-import dev.kord.core.behavior.UserBehavior
-import dev.kord.core.behavior.channel.ChannelBehavior
-import org.koin.core.component.inject
-import org.quiltmc.community.database.Collection
-import org.quiltmc.community.database.Database
-import org.quiltmc.community.database.entities.FilterEntry
-import org.quiltmc.community.database.entities.FilterEvent
-
-class FilterEventCollection : KordExKoinComponent {
- private val database: Database by inject()
- private val col = database.mongo.getCollection(name)
-
- suspend fun add(
- filter: FilterEntry,
-
- guild: GuildBehavior,
- author: UserBehavior,
- channel: ChannelBehavior?,
- message: MessageBehavior?
- ) = add(
- FilterEvent(
- filter = filter._id,
-
- guildId = guild.id,
- authorId = author.id,
- channelId = channel?.id,
- messageId = message?.id
- )
- )
-
- suspend fun add(event: FilterEvent) =
- col.save(event)
-
- companion object : Collection("filter_events")
-}
diff --git a/src/main/kotlin/org/quiltmc/community/database/collections/ServerApplicationCollection.kt b/src/main/kotlin/org/quiltmc/community/database/collections/ServerApplicationCollection.kt
deleted file mode 100644
index 371927dd..00000000
--- a/src/main/kotlin/org/quiltmc/community/database/collections/ServerApplicationCollection.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.database.collections
-
-import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
-import dev.kord.common.entity.Snowflake
-import org.koin.core.component.inject
-import org.litote.kmongo.eq
-import org.quiltmc.community.database.Collection
-import org.quiltmc.community.database.Database
-import org.quiltmc.community.database.entities.ServerApplication
-
-class ServerApplicationCollection : KordExKoinComponent {
- private val database: Database by inject()
- private val col = database.mongo.getCollection(name)
-
- suspend fun save(event: ServerApplication) =
- col.save(event)
-
- suspend fun get(id: Snowflake) =
- col.findOne(ServerApplication::_id eq id)
-
- suspend fun getByMessage(id: Snowflake) =
- col.findOne(ServerApplication::messageId eq id)
-
- fun findByGuild(id: Snowflake) =
- col.find(ServerApplication::guildId eq id)
- .toFlow()
-
- fun findByUser(id: Snowflake) =
- col.find(ServerApplication::userId eq id)
- .toFlow()
-
- companion object : Collection("server_applications")
-}
diff --git a/src/main/kotlin/org/quiltmc/community/database/collections/SuggestionsCollection.kt b/src/main/kotlin/org/quiltmc/community/database/collections/SuggestionsCollection.kt
deleted file mode 100644
index 388d5f62..00000000
--- a/src/main/kotlin/org/quiltmc/community/database/collections/SuggestionsCollection.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.database.collections
-
-import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
-import dev.kord.common.entity.Snowflake
-import dev.kord.core.behavior.MessageBehavior
-import org.bson.conversions.Bson
-import org.koin.core.component.inject
-import org.litote.kmongo.eq
-import org.quiltmc.community.database.Collection
-import org.quiltmc.community.database.Database
-import org.quiltmc.community.database.entities.Suggestion
-
-class SuggestionsCollection : KordExKoinComponent {
- private val database: Database by inject()
- private val col = database.mongo.getCollection(name)
-
- suspend fun get(id: Snowflake) =
- col.findOne(Suggestion::_id eq id)
-
- suspend fun getByMessage(id: Snowflake) =
- col.findOne(Suggestion::message eq id)
-
- suspend fun getByThread(id: Snowflake) =
- col.findOne(Suggestion::thread eq id)
-
- suspend fun getByMessage(message: MessageBehavior) =
- getByMessage(message.id)
-
- suspend fun find(filter: Bson) =
- col.find(filter)
-
- suspend fun set(suggestion: Suggestion) =
- col.save(suggestion)
-
- companion object : Collection("suggestions")
-}
diff --git a/src/main/kotlin/org/quiltmc/community/database/collections/TeamCollection.kt b/src/main/kotlin/org/quiltmc/community/database/collections/TeamCollection.kt
deleted file mode 100644
index 09812f62..00000000
--- a/src/main/kotlin/org/quiltmc/community/database/collections/TeamCollection.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.database.collections
-
-import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
-import dev.kord.common.entity.Snowflake
-import kotlinx.serialization.Serializable
-import org.koin.core.component.inject
-import org.litote.kmongo.eq
-import org.litote.kmongo.graphLookup
-import org.litote.kmongo.limit
-import org.quiltmc.community.database.Collection
-import org.quiltmc.community.database.Database
-import org.quiltmc.community.database.entities.Team
-
-class TeamCollection : KordExKoinComponent {
- private val database: Database by inject()
- private val col = database.mongo.getCollection(name)
-
- suspend fun getAll(): List = col.find().toList()
-
- suspend fun get(id: Snowflake) =
- col.findOne(Team::_id eq id)
-
- suspend fun set(team: Team) =
- col.save(team)
-
- suspend fun delete(id: Snowflake) =
- col.deleteOne(Team::_id eq id)
-
- suspend fun getImmediateChildren(id: Snowflake) =
- col.find(Team::parent eq id)
-
- suspend fun getParents(id: Snowflake) =
- col.aggregate(
- listOf(
- graphLookup(
- name,
- id,
- "parent",
- "_id",
- "parentHierarchy"
- ),
-
- limit(1)
- )
- ).first()?.parentHierarchy?.map { it.parent }.orEmpty()
-
- @Serializable
- data class AggregateResult(val parentHierarchy: List)
-
- companion object : Collection("teams")
-}
diff --git a/src/main/kotlin/org/quiltmc/community/database/entities/FilterEvent.kt b/src/main/kotlin/org/quiltmc/community/database/entities/FilterEvent.kt
deleted file mode 100644
index cc5a3f62..00000000
--- a/src/main/kotlin/org/quiltmc/community/database/entities/FilterEvent.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-@file:UseSerializers(UUIDSerializer::class)
-
-@file:Suppress("DataClassShouldBeImmutable") // Well, yes, but actually no.
-
-package org.quiltmc.community.database.entities
-
-import com.github.jershell.kbson.UUIDSerializer
-import dev.kord.common.entity.Snowflake
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.UseSerializers
-import org.quiltmc.community.database.Entity
-import java.util.*
-
-@Serializable
-@Suppress("ConstructorParameterNaming") // MongoDB calls it that...
-data class FilterEvent(
- override val _id: UUID = UUID.randomUUID(),
- val filter: UUID,
-
- val guildId: Snowflake,
- val authorId: Snowflake,
- val channelId: Snowflake?,
- val messageId: Snowflake?,
-) : Entity
diff --git a/src/main/kotlin/org/quiltmc/community/database/entities/ServerApplication.kt b/src/main/kotlin/org/quiltmc/community/database/entities/ServerApplication.kt
deleted file mode 100644
index 52a8af2d..00000000
--- a/src/main/kotlin/org/quiltmc/community/database/entities/ServerApplication.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-@file:UseSerializers(UUIDSerializer::class)
-
-@file:Suppress("DataClassShouldBeImmutable") // Well, yes, but actually no.
-
-package org.quiltmc.community.database.entities
-
-import com.github.jershell.kbson.UUIDSerializer
-import com.kotlindiscord.kord.extensions.events.extra.models.ApplicationStatus
-import dev.kord.common.entity.Snowflake
-import kotlinx.datetime.Instant
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.UseSerializers
-import org.quiltmc.community.database.Entity
-import java.util.*
-
-@Serializable
-@Suppress("ConstructorParameterNaming") // MongoDB calls it that...
-data class ServerApplication(
- override val _id: Snowflake,
-
- var status: ApplicationStatus?,
- var threadId: Snowflake? = null,
-
- val userId: Snowflake,
- val guildId: Snowflake,
- val messageId: Snowflake,
-
- val messageLink: String? = null,
- var rejectionReason: String? = null,
- var actionedAt: Instant? = null,
-) : Entity
diff --git a/src/main/kotlin/org/quiltmc/community/database/entities/Suggestion.kt b/src/main/kotlin/org/quiltmc/community/database/entities/Suggestion.kt
deleted file mode 100644
index f8d377fe..00000000
--- a/src/main/kotlin/org/quiltmc/community/database/entities/Suggestion.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-@file:Suppress("DataClassShouldBeImmutable") // Well, yes, but actually no.
-
-package org.quiltmc.community.database.entities
-
-import dev.kord.common.entity.Snowflake
-import kotlinx.serialization.Serializable
-import org.quiltmc.community.database.Entity
-import org.quiltmc.community.modes.quilt.extensions.suggestions.SuggestionStatus
-
-@Serializable
-@Suppress("ConstructorParameterNaming") // MongoDB calls it that...
-data class Suggestion(
- override val _id: Snowflake,
-
- var comment: String? = null,
- var status: SuggestionStatus = SuggestionStatus.Open,
- var message: Snowflake? = null,
- var thread: Snowflake? = null,
- var threadButtons: Snowflake? = null,
-
- var text: String,
-
- val owner: Snowflake,
- val ownerAvatar: String?,
- val ownerName: String,
-
- var githubIssue: String? = null,
-
- val positiveVoters: MutableList = mutableListOf(),
- val negativeVoters: MutableList = mutableListOf(),
-
- val isPluralkit: Boolean = false,
-) : Entity {
- val positiveVotes get() = positiveVoters.size
- val negativeVotes get() = negativeVoters.size
- val voteDifference get() = positiveVotes - negativeVotes
-}
diff --git a/src/main/kotlin/org/quiltmc/community/database/entities/Team.kt b/src/main/kotlin/org/quiltmc/community/database/entities/Team.kt
deleted file mode 100644
index 2b094c97..00000000
--- a/src/main/kotlin/org/quiltmc/community/database/entities/Team.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-@file:Suppress("DataClassShouldBeImmutable") // Well, yes, but actually no.
-
-package org.quiltmc.community.database.entities
-
-import dev.kord.common.entity.Snowflake
-import kotlinx.serialization.Serializable
-import org.quiltmc.community.database.Entity
-
-@Serializable
-data class Team(
- override val _id: Snowflake,
- val parent: Snowflake
-) : Entity
diff --git a/src/main/kotlin/org/quiltmc/community/database/entities/UserFlags.kt b/src/main/kotlin/org/quiltmc/community/database/entities/UserFlags.kt
index 036a64e1..c02bcd40 100644
--- a/src/main/kotlin/org/quiltmc/community/database/entities/UserFlags.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/entities/UserFlags.kt
@@ -18,7 +18,6 @@ import org.quiltmc.community.database.collections.UserFlagsCollection
data class UserFlags(
override val _id: Snowflake,
- var hasUsedPK: Boolean = false,
var autoPublish: Boolean = true,
var syncNicks: Boolean = true,
var usePKFronter: Boolean = false,
diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v1.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v1.kt
index e7bd2f51..db1a1331 100644
--- a/src/main/kotlin/org/quiltmc/community/database/migrations/v1.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v1.kt
@@ -8,9 +8,9 @@ package org.quiltmc.community.database.migrations
import org.litote.kmongo.coroutine.CoroutineDatabase
import org.quiltmc.community.database.collections.ServerSettingsCollection
-import org.quiltmc.community.database.collections.SuggestionsCollection
suspend fun v1(db: CoroutineDatabase) {
db.createCollection(ServerSettingsCollection.name)
- db.createCollection(SuggestionsCollection.name)
+
+ // Suggestions no longer exist as a feature, so no migration is necessary.
}
diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v11.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v11.kt
index e3d084e6..707945e8 100644
--- a/src/main/kotlin/org/quiltmc/community/database/migrations/v11.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v11.kt
@@ -7,16 +7,8 @@
package org.quiltmc.community.database.migrations
import org.litote.kmongo.coroutine.CoroutineDatabase
-import org.litote.kmongo.exists
-import org.litote.kmongo.setValue
-import org.quiltmc.community.database.collections.SuggestionsCollection
-import org.quiltmc.community.database.entities.Suggestion
+@Suppress("UnusedParameter")
suspend fun v11(db: CoroutineDatabase) {
- with(db.getCollection(SuggestionsCollection.name)) {
- updateMany(
- Suggestion::githubIssue exists false,
- setValue(Suggestion::githubIssue, null),
- )
- }
+ // Suggestions no longer exist as a feature, so no migration is necessary.
}
diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v19.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v19.kt
index 70461f31..018f5a67 100644
--- a/src/main/kotlin/org/quiltmc/community/database/migrations/v19.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v19.kt
@@ -7,11 +7,8 @@
package org.quiltmc.community.database.migrations
import org.litote.kmongo.coroutine.CoroutineDatabase
-import org.quiltmc.community.database.entities.Suggestion
+@Suppress("UnusedParameter")
suspend fun v19(db: CoroutineDatabase) {
- db.getCollection("suggestions").updateMany(
- "{}",
- "{\$rename: {isTupper: \"isPluralkit\"}}"
- )
+ // Suggestions no longer exist as a feature, so no migration is necessary.
}
diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v2.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v2.kt
index 2668e736..3ceb39de 100644
--- a/src/main/kotlin/org/quiltmc/community/database/migrations/v2.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v2.kt
@@ -7,23 +7,9 @@
package org.quiltmc.community.database.migrations
import org.litote.kmongo.coroutine.CoroutineDatabase
-import org.litote.kmongo.exists
-import org.litote.kmongo.setValue
-import org.quiltmc.community.database.collections.SuggestionsCollection
-import org.quiltmc.community.database.entities.Suggestion
suspend fun v2(db: CoroutineDatabase) {
db.createCollection("collab-server-settings")
- with(db.getCollection(SuggestionsCollection.name)) {
- updateMany(
- Suggestion::thread exists false,
- setValue(Suggestion::thread, null),
- )
-
- updateMany(
- Suggestion::threadButtons exists false,
- setValue(Suggestion::threadButtons, null),
- )
- }
+ // Suggestions no longer exist as a feature, so no migration is necessary.
}
diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v20.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v20.kt
index 549f1e01..5a3c6f68 100644
--- a/src/main/kotlin/org/quiltmc/community/database/migrations/v20.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v20.kt
@@ -7,8 +7,8 @@
package org.quiltmc.community.database.migrations
import org.litote.kmongo.coroutine.CoroutineDatabase
-import org.quiltmc.community.database.collections.ServerApplicationCollection
+@Suppress("UnusedParameter")
suspend fun v20(db: CoroutineDatabase) {
- db.createCollection(ServerApplicationCollection.name)
+ // Server applications no longer exist as a feature, so no migration is necessary.
}
diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v21.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v21.kt
index d61e41f5..436fcd83 100644
--- a/src/main/kotlin/org/quiltmc/community/database/migrations/v21.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v21.kt
@@ -7,37 +7,8 @@
package org.quiltmc.community.database.migrations
import org.litote.kmongo.coroutine.CoroutineDatabase
-import org.litote.kmongo.eq
-import org.litote.kmongo.exists
-import org.litote.kmongo.setValue
-import org.quiltmc.community.database.collections.ServerApplicationCollection
-import org.quiltmc.community.database.collections.ServerSettingsCollection
-import org.quiltmc.community.database.entities.ServerApplication
-import org.quiltmc.community.database.entities.ServerSettings
+@Suppress("UnusedParameter")
suspend fun v21(db: CoroutineDatabase) {
- val settingsCollection = db.getCollection(ServerSettingsCollection.name)
- val appCollection = db.getCollection(ServerApplicationCollection.name)
-
- val entries = appCollection.find(
- ServerApplication::messageLink exists false,
- ).toList()
-
- entries
- .groupBy { it.guildId }
- .forEach { (guildId, applications) ->
- val settings = settingsCollection.findOne(ServerSettings::_id eq guildId)!!
-
- applications.forEach { app ->
- val link = "https://discord.com/channels" +
- "/${settings._id}" +
- "/${settings.applicationLogChannel}" +
- "/${app.messageId}"
-
- appCollection.updateOne(
- ServerApplication::_id eq app._id,
- setValue(ServerApplication::messageLink, link),
- )
- }
- }
+ // Server applications no longer exist as a feature, so no migration is necessary.
}
diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v25.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v25.kt
new file mode 100644
index 00000000..ac5f8bdd
--- /dev/null
+++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v25.kt
@@ -0,0 +1,28 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package org.quiltmc.community.database.migrations
+
+import com.mongodb.client.model.Updates
+import org.litote.kmongo.EMPTY_BSON
+import org.litote.kmongo.coroutine.CoroutineDatabase
+import org.quiltmc.community.database.collections.UserFlagsCollection
+import org.quiltmc.community.database.entities.UserFlags
+
+suspend fun v25(db: CoroutineDatabase) {
+ // Get rid of some collections that refer to removed features.
+ db.dropCollection("filter_events")
+ db.dropCollection("server_applications")
+ db.dropCollection("suggestions")
+ db.dropCollection("teams")
+
+ // Remove old unnecessary data.
+ db.getCollection(UserFlagsCollection.name)
+ .updateMany(
+ EMPTY_BSON,
+ Updates.unset("hasUsedPK")
+ )
+}
diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v3.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v3.kt
index eb112f54..1d0e35d9 100644
--- a/src/main/kotlin/org/quiltmc/community/database/migrations/v3.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v3.kt
@@ -6,45 +6,11 @@
package org.quiltmc.community.database.migrations
-import com.mongodb.client.model.BulkWriteOptions
-import com.mongodb.client.model.ReplaceOneModel
import org.litote.kmongo.coroutine.CoroutineDatabase
-import org.litote.kmongo.eq
-import org.litote.kmongo.replaceOne
-import org.litote.kmongo.replaceUpsert
-import org.quiltmc.community.COMMUNITY_GUILD
import org.quiltmc.community.database.collections.OwnedThreadCollection
-import org.quiltmc.community.database.collections.SuggestionsCollection
-import org.quiltmc.community.database.entities.OwnedThread
-import org.quiltmc.community.database.entities.Suggestion
suspend fun v3(db: CoroutineDatabase) {
db.createCollection(OwnedThreadCollection.name)
- val suggestions = db.getCollection(SuggestionsCollection.name)
- val ownedThreads = db.getCollection(OwnedThreadCollection.name)
-
- val documents = mutableListOf>()
-
- suggestions.find().consumeEach {
- if (it.thread != null) {
- documents.add(
- replaceOne(
- OwnedThread::_id eq it.thread!!,
-
- OwnedThread(
- it.thread!!,
- it.owner,
- COMMUNITY_GUILD
- ),
-
- replaceUpsert()
- )
- )
- }
- }
-
- if (documents.isNotEmpty()) {
- ownedThreads.bulkWrite(requests = documents, BulkWriteOptions().ordered(false))
- }
+ // Suggestions no longer exist as a feature, so no migration is necessary.
}
diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v4.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v4.kt
index 2194ef3f..91015a3b 100644
--- a/src/main/kotlin/org/quiltmc/community/database/migrations/v4.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v4.kt
@@ -7,8 +7,8 @@
package org.quiltmc.community.database.migrations
import org.litote.kmongo.coroutine.CoroutineDatabase
-import org.quiltmc.community.database.collections.TeamCollection
+@Suppress("UnusedParameter")
suspend fun v4(db: CoroutineDatabase) {
- db.createCollection(TeamCollection.name)
+ // Teams no longer exist as a feature, so no migration is necessary.
}
diff --git a/src/main/kotlin/org/quiltmc/community/database/migrations/v6.kt b/src/main/kotlin/org/quiltmc/community/database/migrations/v6.kt
index ae80968d..e00b229c 100644
--- a/src/main/kotlin/org/quiltmc/community/database/migrations/v6.kt
+++ b/src/main/kotlin/org/quiltmc/community/database/migrations/v6.kt
@@ -7,8 +7,8 @@
package org.quiltmc.community.database.migrations
import org.litote.kmongo.coroutine.CoroutineDatabase
-import org.quiltmc.community.database.collections.FilterEventCollection
+@Suppress("UnusedParameter")
suspend fun v6(db: CoroutineDatabase) {
- db.createCollection(FilterEventCollection.name)
+ // Filter event storage no longer exists as a feature, so no migration is necessary.
}
diff --git a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/PKExtension.kt b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/PKExtension.kt
deleted file mode 100644
index ada1d987..00000000
--- a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/PKExtension.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.modes.quilt.extensions
-
-import com.kotlindiscord.kord.extensions.DISCORD_FUCHSIA
-import com.kotlindiscord.kord.extensions.extensions.Extension
-import com.kotlindiscord.kord.extensions.extensions.event
-import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.ProxiedMessageCreateEvent
-import com.kotlindiscord.kord.extensions.utils.getJumpUrl
-import dev.kord.core.behavior.channel.createMessage
-import dev.kord.rest.builder.message.embed
-import org.koin.core.component.inject
-import org.quiltmc.community.database.collections.ServerSettingsCollection
-import org.quiltmc.community.database.collections.UserFlagsCollection
-import org.quiltmc.community.database.entities.UserFlags
-import org.quiltmc.community.userField
-
-class PKExtension : Extension() {
- override val name: String = "pluralkit"
-
- private val userFlags: UserFlagsCollection by inject()
- private val settings: ServerSettingsCollection by inject()
-
- override suspend fun setup() {
- event {
- action {
- val flags = userFlags.get(event.pkMessage.sender) ?: UserFlags(event.pkMessage.sender, false)
-
- if (!flags.hasUsedPK) {
- flags.hasUsedPK = true
- flags.save()
-
- settings.getCommunity()?.getConfiguredLogChannel()?.createMessage {
- embed {
- title = "New PK user"
- color = DISCORD_FUCHSIA
-
- description = "A message has been sent by a PluralKit user for the first time."
-
- if (event.pkMessage.system?.avatarUrl != null) {
- thumbnail {
- url = event.pkMessage.system!!.avatarUrl!!
- }
- }
-
- userField(kord.getUser(event.pkMessage.sender)!!, "Discord Account")
-
- field {
- name = "Channel"
- value = "${event.message.channel.mention} (`${event.message.channelId}`)"
- }
-
- field {
- name = "Message URL"
- value = event.message.getJumpUrl()
- }
-
- field {
- name = "PK Member"
-
- value = if (event.pkMessage.member != null) {
- "Unknown; private member"
- } else {
- "${event.pkMessage.member?.name} (`${event.pkMessage.member?.id}`)"
- }
-
- inline = true
- }
-
- field {
- name = "PK System"
-
- value = if (event.pkMessage.system != null) {
- "Unknown; private system"
- } else {
- (event.pkMessage.system?.name ?: "**No system name**") +
- " (`${event.pkMessage.system?.id}`)"
- }
-
- inline = true
- }
- }
- }
- }
- }
- }
- }
-}
diff --git a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/SubteamsExtension.kt b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/SubteamsExtension.kt
deleted file mode 100644
index 5ae9d3c1..00000000
--- a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/SubteamsExtension.kt
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.modes.quilt.extensions
-
-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.member
-import com.kotlindiscord.kord.extensions.commands.converters.impl.role
-import com.kotlindiscord.kord.extensions.extensions.Extension
-import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand
-import dev.kord.common.entity.Permission
-import dev.kord.core.entity.Member
-import dev.kord.core.entity.Role
-import dev.kord.rest.builder.message.allowedMentions
-import org.koin.core.component.inject
-import org.quiltmc.community.database.collections.TeamCollection
-import org.quiltmc.community.database.entities.Team
-
-class SubteamsExtension : Extension() {
- override val name: String = "subteams"
-
- private val teamColl: TeamCollection by inject()
-
- override suspend fun setup() {
- publicSlashCommand {
- name = "team"
- description = "Manage your teams"
-
- allowInDms = false
-
- publicSubCommand(::TeamArguments) {
- name = "add"
- description = "Add someone to your team or any subteam"
-
- action {
- if (this.member?.asMemberOrNull()?.mayManageRole(arguments.role) == true) {
- arguments.targetUser.addRole(
- arguments.role.id,
- "${this.user.asUserOrNull()?.tag ?: this.user.id} used /team add"
- )
-
- respond {
- content = "Successfully added ${arguments.targetUser.mention} to ${arguments.role.mention}."
-
- allowedMentions { }
- }
- } else {
- respond {
- content = "Your team needs to be above ${arguments.role.mention} in order to add anyone " +
- "to it."
-
- allowedMentions { }
- }
- }
- }
- }
-
- publicSubCommand(::TeamArguments) {
- name = "remove"
- description = "Remove someone from your team or any subteam"
-
- action {
- if (this.member?.asMemberOrNull()?.mayManageRole(arguments.role) == true) {
- arguments.targetUser.removeRole(
- arguments.role.id,
- "${this.user.asUserOrNull()?.tag ?: this.user.id} used /team remove"
- )
- respond {
- content = "Successfully removed ${arguments.targetUser.mention} from " +
- "${arguments.role.mention}."
-
- allowedMentions { }
- }
- } else {
- respond {
- content = "Your team needs to be above ${arguments.role.mention} in order to remove " +
- "anyone from it."
-
- allowedMentions { }
- }
- }
- }
- }
- }
-
- publicSlashCommand {
- name = "manage-teams"
- description = "Change which roles can manage each other"
-
- allowInDms = false
-
- check { hasPermission(Permission.Administrator) }
-
- ephemeralSubCommand(::ManageTeamAllowArguments) {
- name = "allow"
- description = "Allow a role to manage another using /team"
-
- action {
- teamColl.set(
- Team(
- _id = arguments.inferior.id,
- parent = arguments.superior.id
- )
- )
-
- respond {
- content = "${arguments.superior.mention} can now manage ${arguments.inferior.mention}"
-
- allowedMentions { }
- }
- }
- }
-
- ephemeralSubCommand(::ManageTeamDisallowArguments) {
- name = "disallow"
- description = "Prevent a role from being managed by another again"
-
- action {
- teamColl.delete(arguments.role.id)
-
- respond {
- content = "${arguments.role.mention} can no longer be managed using /team."
-
- allowedMentions { }
- }
- }
- }
-
- ephemeralSubCommand {
- name = "list-relationships"
- description = "List all the relationships between roles"
-
- action {
- respond {
- content = teamColl.getAll().joinToString("\n") { "<@&${it._id.value}> is managed by <@&${it.parent.value}>" }
- }
- }
- }
- }
- }
-
- inner class TeamArguments : Arguments() {
- val role by role {
- name = "team"
- description = "Which team to add"
- }
-
- val targetUser by member {
- name = "user"
- description = "Who to add to the team"
- }
- }
-
- inner class ManageTeamAllowArguments : Arguments() {
- val superior by role {
- name = "superior"
- description = "The superior role"
- }
-
- val inferior by role {
- name = "inferior"
- description = "The inferior role"
- }
- }
-
- inner class ManageTeamDisallowArguments : Arguments() {
- val role by role {
- name = "role"
- description = "Role to disallow managing for"
- }
- }
-
- private suspend fun Member.mayManageRole(role: Role): Boolean =
- teamColl.getParents(role.id).any { it in this.roleIds }
-}
diff --git a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/UtilityExtension.kt b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/UtilityExtension.kt
index 1b0a96d5..eb4c153d 100644
--- a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/UtilityExtension.kt
+++ b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/UtilityExtension.kt
@@ -15,6 +15,7 @@ import com.kotlindiscord.kord.extensions.DISCORD_GREEN
import com.kotlindiscord.kord.extensions.DISCORD_RED
import com.kotlindiscord.kord.extensions.DiscordRelayedException
import com.kotlindiscord.kord.extensions.annotations.DoNotChain
+import com.kotlindiscord.kord.extensions.annotations.UnexpectedFunctionBehaviour
import com.kotlindiscord.kord.extensions.checks.channelType
import com.kotlindiscord.kord.extensions.checks.hasPermission
import com.kotlindiscord.kord.extensions.checks.isInThread
@@ -106,6 +107,7 @@ class UtilityExtension : Extension() {
encodeDefaults = false
}
+ @OptIn(UnexpectedFunctionBehaviour::class)
override suspend fun setup() {
if (STATUS_CHANNEL_ID != null) {
event {
diff --git a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/filtering/FilterExtension.kt b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/filtering/FilterExtension.kt
index 83e6bd7e..1f486501 100644
--- a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/filtering/FilterExtension.kt
+++ b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/filtering/FilterExtension.kt
@@ -15,6 +15,7 @@ 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.annotations.DoNotChain
import com.kotlindiscord.kord.extensions.checks.isNotBot
import com.kotlindiscord.kord.extensions.commands.Arguments
import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.enumChoice
@@ -45,7 +46,6 @@ import net.codebox.homoglyph.HomoglyphBuilder
import org.koin.core.component.inject
import org.quiltmc.community.*
import org.quiltmc.community.database.collections.FilterCollection
-import org.quiltmc.community.database.collections.FilterEventCollection
import org.quiltmc.community.database.collections.GlobalSettingsCollection
import org.quiltmc.community.database.collections.ServerSettingsCollection
import org.quiltmc.community.database.entities.FilterEntry
@@ -89,7 +89,6 @@ class FilterExtension : Extension() {
val filters: FilterCollection by inject()
val filterCache: MutableMap = mutableMapOf()
- val filterEvents: FilterEventCollection by inject()
override suspend fun setup() {
reloadFilters()
@@ -733,10 +732,6 @@ class FilterExtension : Extension() {
else -> {} // Nothing
}
- filterEvents.add(
- this, guild, member, null, null
- )
-
guild.getFilterLogChannel()?.createMessage {
if (pingStaff) {
val modRole = when (guild.id) {
@@ -836,6 +831,7 @@ class FilterExtension : Extension() {
}
}
+ @OptIn(DoNotChain::class)
suspend fun FilterEntry.action(message: Message) {
val guild = message.getGuild()
@@ -948,10 +944,6 @@ class FilterExtension : Extension() {
null -> {} // Nothing
}
- filterEvents.add(
- this, message.getGuild(), message.author!!, message.channel, message
- )
-
guild.getFilterLogChannel()?.createMessage {
if (pingStaff) {
val modRole = when (guild.id) {
diff --git a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/messagelog/MessageLogExtension.kt b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/messagelog/MessageLogExtension.kt
index f3aed874..b6d36d84 100644
--- a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/messagelog/MessageLogExtension.kt
+++ b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/messagelog/MessageLogExtension.kt
@@ -32,13 +32,10 @@ import dev.kord.rest.builder.message.embed
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.client.request.forms.*
import io.ktor.utils.io.jvm.javaio.*
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
+import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.toList
-import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.serialization.encodeToString
@@ -498,7 +495,7 @@ class MessageLogExtension : Extension() {
suspend fun send(message: LogMessage) = messageChannel.send(message)
- @OptIn(ExperimentalCoroutinesApi::class)
+ @OptIn(DelicateCoroutinesApi::class)
private suspend fun sendLoop() {
for (logMessage in messageChannel) {
val rotator = rotators[logMessage.guild.id]
diff --git a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/suggestions/SuggestionConverter.kt b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/suggestions/SuggestionConverter.kt
deleted file mode 100644
index 843de987..00000000
--- a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/suggestions/SuggestionConverter.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-@file:OptIn(KordPreview::class)
-
-package org.quiltmc.community.modes.quilt.extensions.suggestions
-
-import com.kotlindiscord.kord.extensions.DiscordRelayedException
-import com.kotlindiscord.kord.extensions.commands.Argument
-import com.kotlindiscord.kord.extensions.commands.CommandContext
-import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter
-import com.kotlindiscord.kord.extensions.commands.converters.Validator
-import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter
-import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType
-import com.kotlindiscord.kord.extensions.parser.StringParser
-import dev.kord.common.annotation.KordPreview
-import dev.kord.common.entity.Snowflake
-import dev.kord.core.entity.interaction.OptionValue
-import dev.kord.core.entity.interaction.StringOptionValue
-import dev.kord.rest.builder.interaction.OptionsBuilder
-import dev.kord.rest.builder.interaction.StringChoiceBuilder
-import org.koin.core.component.inject
-import org.quiltmc.community.database.collections.SuggestionsCollection
-import org.quiltmc.community.database.entities.Suggestion
-
-@Converter(
- names = ["suggestion"],
- types = [ConverterType.SINGLE, ConverterType.OPTIONAL],
-)
-class SuggestionConverter(
- override var validator: Validator = null
-) : SingleConverter() {
- override val signatureTypeString: String = "Suggestion ID"
-
- private val suggestions: SuggestionsCollection by inject()
-
- override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean {
- val arg: String = named ?: parser?.parseNext()?.data ?: return false
-
- try {
- val snowflake = Snowflake(arg)
-
- this.parsed = suggestions.get(snowflake)
- ?: suggestions.getByMessage(snowflake)
- ?: throw DiscordRelayedException("Unknown suggestion ID: $arg")
- } catch (e: NumberFormatException) {
- throw DiscordRelayedException("Unknown suggestion ID: $arg")
- }
-
- return true
- }
-
- override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder =
- StringChoiceBuilder(arg.displayName, arg.description).apply { required = true }
-
- override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean {
- val arg = (option as? StringOptionValue)?.value ?: return false
-
- try {
- val snowflake = Snowflake(arg)
-
- this.parsed = suggestions.get(snowflake)
- ?: suggestions.getByMessage(snowflake)
- ?: throw DiscordRelayedException("Unknown suggestion ID: $arg")
- } catch (e: NumberFormatException) {
- throw DiscordRelayedException("Unknown suggestion ID: $arg")
- }
-
- return true
- }
-}
diff --git a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/suggestions/SuggestionStatus.kt b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/suggestions/SuggestionStatus.kt
deleted file mode 100644
index 191c28b1..00000000
--- a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/suggestions/SuggestionStatus.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package org.quiltmc.community.modes.quilt.extensions.suggestions
-
-import com.kotlindiscord.kord.extensions.*
-import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceEnum
-import dev.kord.common.Color
-import kotlinx.serialization.Serializable
-
-@Serializable
-enum class SuggestionStatus(override val readableName: String, val color: Color) : ChoiceEnum {
- Open("Open", DISCORD_BLURPLE),
-
- Approved("Approved", DISCORD_FUCHSIA),
-
- Denied("Denied", DISCORD_RED),
- Invalid("Invalid", DISCORD_RED),
- Spam("Spam", DISCORD_RED),
-
- Future("Future Concern", DISCORD_YELLOW),
- Stale("Stale", DISCORD_YELLOW),
-
- Duplicate("Duplicate", DISCORD_BLACK),
- Implemented("Implemented", DISCORD_GREEN),
-}
diff --git a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/suggestions/SuggestionsExtension.kt b/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/suggestions/SuggestionsExtension.kt
deleted file mode 100644
index 489a48c2..00000000
--- a/src/main/kotlin/org/quiltmc/community/modes/quilt/extensions/suggestions/SuggestionsExtension.kt
+++ /dev/null
@@ -1,881 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-@file:OptIn(ExperimentalTime::class, KordPreview::class)
-
-@file:Suppress("MagicNumber") // Yep. I'm done.
-
-package org.quiltmc.community.modes.quilt.extensions.suggestions
-
-import com.kotlindiscord.kord.extensions.checks.inTopChannel
-import com.kotlindiscord.kord.extensions.commands.Arguments
-import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.optionalEnumChoice
-import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingString
-import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalString
-import com.kotlindiscord.kord.extensions.events.interfaces.MessageEvent
-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.events.ProxiedMessageCreateEvent
-import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.UnProxiedMessageCreateEvent
-import com.kotlindiscord.kord.extensions.utils.*
-import dev.kord.common.annotation.KordPreview
-import dev.kord.common.entity.ButtonStyle
-import dev.kord.common.entity.MessageType
-import dev.kord.common.entity.Snowflake
-import dev.kord.core.behavior.channel.createMessage
-import dev.kord.core.behavior.edit
-import dev.kord.core.behavior.getChannelOf
-import dev.kord.core.behavior.interaction.response.createEphemeralFollowup
-import dev.kord.core.behavior.reply
-import dev.kord.core.builder.components.emoji
-import dev.kord.core.entity.ReactionEmoji
-import dev.kord.core.entity.channel.GuildMessageChannel
-import dev.kord.core.entity.channel.TextChannel
-import dev.kord.core.entity.channel.thread.ThreadChannel
-import dev.kord.core.entity.interaction.ButtonInteraction
-import dev.kord.core.event.channel.thread.ThreadChannelCreateEvent
-import dev.kord.core.event.interaction.InteractionCreateEvent
-import dev.kord.core.event.message.MessageCreateEvent
-import dev.kord.rest.builder.message.actionRow
-import dev.kord.rest.builder.message.create.MessageCreateBuilder
-import dev.kord.rest.builder.message.embed
-import dev.kord.rest.builder.message.modify.MessageModifyBuilder
-import io.github.evanrupert.excelkt.Sheet
-import io.github.evanrupert.excelkt.workbook
-import io.ktor.client.request.forms.*
-import io.ktor.utils.io.jvm.javaio.*
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.toList
-import org.apache.poi.ss.usermodel.FillPatternType
-import org.apache.poi.ss.usermodel.IndexedColors
-import org.apache.poi.xssf.usermodel.XSSFColor
-import org.koin.core.component.inject
-import org.litote.kmongo.exists
-import org.quiltmc.community.*
-import org.quiltmc.community.database.collections.OwnedThreadCollection
-import org.quiltmc.community.database.collections.SuggestionsCollection
-import org.quiltmc.community.database.entities.OwnedThread
-import org.quiltmc.community.database.entities.Suggestion
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import kotlin.time.Duration.Companion.seconds
-import kotlin.time.ExperimentalTime
-
-private const val ACTION_DOWN = "down"
-private const val ACTION_REMOVE = "remove"
-private const val ACTION_UP = "up"
-
-private const val THREAD_INTRO = "This message is at the top of the thread.\n\n" +
- "If this is your suggestion, **please** use `/thread rename` to change the " +
- "name of the thread! You're also welcome to use the other `/thread` commands to manage " +
- "your suggestion thread as needed. You can edit your suggestion at any time using the `/edit-suggestion`" +
- " command."
-
-private const val COMMENT_SIZE_LIMIT: Long = 800
-private const val SUGGESTION_SIZE_LIMIT: Long = 1000
-private const val THIRTY_SECONDS: Long = 30_000
-
-private const val GITHUB_EMOJI: String = "<:github:864972399111569468>"
-
-private val EMOTE_DOWNVOTE = ReactionEmoji.Unicode("⬇️")
-private val EMOTE_REMOVE = ReactionEmoji.Unicode("\uD83D\uDDD1️")
-private val EMOTE_UPVOTE = ReactionEmoji.Unicode("⬆️")
-
-private val CLEAR_WORDS = arrayOf("clear", "null")
-
-class SuggestionsExtension : Extension() {
- override val name: String = "suggestions"
-
- private val suggestions: SuggestionsCollection by inject()
- private val threads: OwnedThreadCollection by inject()
-
- override suspend fun setup() {
- // region: Events
-
- event {
- check {
- failIfNot {
- event.message.type == MessageType.Default ||
- event.message.type == MessageType.Reply
- }
- }
-
- check { failIf(event.message.content.trim().isEmpty()) }
- check { failIf(event.message.interaction != null) }
- check { failIf(event.message.data.authorId == event.kord.selfId) }
- check { failIf(event.message.author?.isBot == true) }
-
- // TODO: switch back to inChannel once the bug is fixed
- // Currently, due to a lack of support for the pluralkit events, inChannel
- // will always return false.
- check { failIf(event.message.channelId != SUGGESTION_CHANNEL) }
-
- action {
- val id = event.message.id
- val suggestion = Suggestion(
- _id = id,
- text = event.message.content,
-
- owner = event.message.author!!.id,
- ownerAvatar = event.message.author!!.avatar?.cdnUrl?.toUrl(),
- ownerName = event.message.author!!.asMember(event.message.getGuild().id)
- .let { it.globalName ?: it.username },
-
- positiveVoters = mutableListOf(event.message.author!!.id)
- )
-
- if (checkSuggestionLength(suggestion, event)) {
- suggestions.set(suggestion)
- sendSuggestion(suggestion)
- event.message.delete()
- }
- }
- }
-
- event {
- check {
- failIfNot {
- event.message.type == MessageType.Default ||
- event.message.type == MessageType.Reply
- }
- }
-
- check { failIf(event.message.content.trim().isEmpty()) }
- check { failIf(event.message.interaction != null) }
- check { failIf(event.message.data.authorId == event.kord.selfId) }
- check { failIf(event.message.author?.isBot == true) }
-
- // TODO: switch back to inChannel once the bug is fixed
- check { failIf(event.message.channelId != SUGGESTION_CHANNEL) }
-
- action {
- val id = event.message.id
- val suggestion = Suggestion(
- _id = id,
- text = event.message.content,
-
- owner = event.pkMessage.sender,
- ownerAvatar = event.pkMessage.member?.avatarUrl,
- ownerName = event.pkMessage.member?.name ?: event.author.globalName ?: event.author.username,
-
- positiveVoters = mutableListOf(event.pkMessage.sender),
-
- isPluralkit = true
- )
-
- if (checkSuggestionLength(suggestion, event)) {
- suggestions.set(suggestion)
- sendSuggestion(suggestion)
- event.message.delete()
- }
- }
- }
-
- event {
- check { failIfNot(event.message.channelId == SUGGESTION_CHANNEL) }
- check { failIfNot(event.message.type == MessageType.ThreadCreated) }
-
- action {
- event.message.deleteIgnoringNotFound()
- }
- }
-
- event {
- check { failIfNot(event.interaction is ButtonInteraction) }
- check { inTopChannel(SUGGESTION_CHANNEL) }
-
- action {
- val interaction = event.interaction as ButtonInteraction
-
- if ("/" !in interaction.componentId) {
- return@action
- }
-
- val split = interaction.componentId.split('/', limit = 2)
-
- val id = Snowflake(split[0])
- val action = split[1]
-
- val suggestion = suggestions.get(id) ?: return@action
- val response = interaction.ackEphemeral(false)
-
- if (suggestion.status != SuggestionStatus.Open) {
- response.createEphemeralFollowup {
- content = "**Error:** This suggestion isn't open, and votes can't be changed."
- }
-
- return@action
- }
-
- when (action) {
- ACTION_UP -> if (!suggestion.positiveVoters.contains(interaction.user.id)) {
- suggestion.positiveVoters.add(interaction.user.id)
- suggestion.negativeVoters.remove(interaction.user.id)
-
- response.createEphemeralFollowup {
- content = "Vote registered!"
- }
- } else {
- response.createEphemeralFollowup {
- content = "**Error:** You've already upvoted this suggestion."
- }
-
- return@action
- }
-
- ACTION_DOWN -> if (!suggestion.negativeVoters.contains(interaction.user.id)) {
- suggestion.negativeVoters.add(interaction.user.id)
- suggestion.positiveVoters.remove(interaction.user.id)
-
- response.createEphemeralFollowup {
- content = "Vote registered!"
- }
- } else {
- response.createEphemeralFollowup {
- content = "**Error:** You've already downvoted this suggestion."
- }
-
- return@action
- }
-
- ACTION_REMOVE -> if (suggestion.positiveVoters.contains(interaction.user.id)) {
- suggestion.positiveVoters.remove(interaction.user.id)
-
- response.createEphemeralFollowup {
- content = "Vote removed!"
- }
- } else if (suggestion.negativeVoters.contains(interaction.user.id)) {
- suggestion.negativeVoters.remove(interaction.user.id)
-
- response.createEphemeralFollowup {
- content = "Vote removed!"
- }
- } else {
- response.createEphemeralFollowup {
- content = "**Error:** You haven't voted for this suggestion."
- }
-
- return@action
- }
-
- else -> response.createEphemeralFollowup {
- content = "Unknown action: $action"
-
- return@action
- }
- }
-
- suggestions.set(suggestion)
- sendSuggestion(suggestion)
- }
- }
-
- event {
- check { inTopChannel(SUGGESTION_CHANNEL) }
-
- check { failIf(event.channel.ownerId == kord.selfId) }
-
- action {
- event.channel.delete("Suggestion thread not created by Cozy")
-
- event.channel.owner.asUser().dm {
- content = "I've removed your thread - please note that suggestion threads are only " +
- "meant to be created automatically, you shouldn't create your own."
- }
- }
- }
-
- // endregion
-
- // region: Commands
-
- ephemeralSlashCommand {
- name = "suggestion-spreadsheet"
- description = "Download a copy of the suggestions as a spreadsheet."
-
- allowInDms = false
-
- guild(COMMUNITY_GUILD)
-
- check { hasBaseModeratorRole() }
-
- action {
- val suggestions = suggestions.find(Suggestion::_id exists true).toList()
- val outputStream = ByteArrayOutputStream()
-
- val book = workbook {
- sheet("Suggestions") {
- suggestionHeader()
-
- suggestions.forEach { suggestionRow(it) }
- }
- }
-
- book.xssfWorkbook.write(outputStream)
-
- respond {
- content = "Wrote ${suggestions.size} suggestions to an Excel spreadsheet."
-
- addFile(
- "suggestions.xlsx",
-
- ChannelProvider { ByteArrayInputStream(outputStream.toByteArray()).toByteReadChannel() }
- )
- }
- }
- }
-
- ephemeralSlashCommand(::SuggestionEditArguments) {
- name = "edit-suggestion"
- description = "Edit one of your suggestions"
-
- allowInDms = false
-
- guild(COMMUNITY_GUILD)
-
- action {
- if (arguments.suggestion.owner != user.id) {
- respond {
- content = "**Error:** You don't own that suggestion."
- }
-
- return@action
- }
-
- arguments.suggestion.text = arguments.text
-
- suggestions.set(arguments.suggestion)
- sendSuggestion(arguments.suggestion)
-
- respond {
- content = "Suggestion updated."
- }
- }
- }
-
- ephemeralSlashCommand(::SuggestionStateArguments) {
- name = "suggestion"
- description = "Suggestion state change command; \"clear\" to remove comment"
-
- allowInDms = false
-
- guild(COMMUNITY_GUILD)
-
- check { hasBaseModeratorRole() }
-
- action {
- val status = arguments.status
-
- if (status != null) {
- arguments.suggestion.status = status
- }
-
- if (arguments.comment != null) {
- arguments.suggestion.comment = if (arguments.comment!!.lowercase() in CLEAR_WORDS) {
- null
- } else {
- arguments.comment
- }
- }
-
- if (arguments.issue != null) {
- arguments.suggestion.githubIssue = if (arguments.issue!!.lowercase() in CLEAR_WORDS) {
- null
- } else {
- arguments.issue
- }
- }
-
- suggestions.set(arguments.suggestion)
- sendSuggestion(arguments.suggestion)
- sendSuggestionUpdateMessage(arguments.suggestion)
-
- respond {
- content = "Suggestion updated."
- }
- }
- }
-
- // TODO: Searching command?
-// subCommand(::SuggestionSearchArguments) {
-// name = "search"
-// description = "Search through the submitted suggestions"
-//
-// COMMUNITY_MANAGEMENT_ROLES.forEach(::allowRole)
-//
-// action {
-//
-// }
-// }
-
- // endregion
- }
-
- private fun Sheet.suggestionHeader() {
- val headings = listOf("ID", "Status", "Text", "+", "-", "=", "Staff Comment")
-
- val style = createCellStyle {
- setFont(
- createFont {
- color = IndexedColors.WHITE.index
- bold = true
- }
- )
-
- fillPattern = FillPatternType.SOLID_FOREGROUND
- fillForegroundColor = IndexedColors.BLACK.index
- }
-
- row(style) {
- headings.forEach(::cell)
- }
- }
-
- private fun Sheet.suggestionRow(suggestion: Suggestion) {
- val statusStyle = createCellStyle {
- setFont(
- createFont {
- val color = XSSFColor(
- byteArrayOf(
- suggestion.status.color.red.toByte(),
- suggestion.status.color.green.toByte(),
- suggestion.status.color.blue.toByte()
- )
- )
-
- setColor(color)
- }
- )
- }
-
- row {
- cell(suggestion._id.toString())
- cell(suggestion.status.readableName, statusStyle)
- cell(suggestion.text)
- cell(suggestion.positiveVotes)
- cell(suggestion.negativeVotes)
- cell(suggestion.voteDifference)
- cell(suggestion.comment ?: "")
- }
- }
-
- suspend fun checkSuggestionLength(suggestion: Suggestion, event: MessageEvent): Boolean {
- if (suggestion.text.length > SUGGESTION_SIZE_LIMIT) {
- val user = kord.getUser(suggestion.owner)
-
- val resentText = if (suggestion.text.length > 1800) {
- suggestion.text.substring(0, 1797) + "..."
- } else {
- suggestion.text
- }
-
- val errorMessage = "The suggestion you posted was too long (${suggestion.text.length} / " +
- "$SUGGESTION_SIZE_LIMIT characters)\n\n```\n$resentText\n```"
-
- val dm = user?.dm {
- content = errorMessage
- }
-
- if (dm != null) {
- event.message?.delete()
- } else {
- event.message?.reply {
- content = errorMessage
- }?.delete(THIRTY_SECONDS)
-
- event.message?.delete(THIRTY_SECONDS)
- }
-
- return false
- }
- return true
- }
-
- suspend fun sendSuggestion(suggestion: Suggestion) {
- val channel = getChannel()
-
- if (suggestion.message == null) {
- val message = channel.createMessage { suggestion(suggestion) }
-
- val thread = (channel as? TextChannel)?.startPublicThreadWithMessage(
- message.id, suggestion._id.toString()
- ) { reason = "Suggestion thread created" }
-
- if (thread != null) {
- val threadMessage = thread.createMessage {
- suggestion(suggestion, sendEmbed = false)
-
- content = THREAD_INTRO
- }
-
- threadMessage.pin()
-
- thread.addUser(suggestion.owner)
-
- threads.set(
- OwnedThread(
- thread.id,
- suggestion.owner,
- thread.guildId
- )
- )
-
- suggestion.thread = thread.id
- suggestion.threadButtons = threadMessage.id
-
- val modRole = when (thread.guildId) {
- COMMUNITY_GUILD -> thread.guild.getRole(COMMUNITY_MODERATOR_ROLE)
- TOOLCHAIN_GUILD -> thread.guild.getRole(TOOLCHAIN_MODERATOR_ROLE)
-
- else -> return
- }
-
- val managerRole = when (thread.guildId) {
- COMMUNITY_GUILD -> thread.guild.getRole(COMMUNITY_MANAGER_ROLE)
- TOOLCHAIN_GUILD -> thread.guild.getRole(TOOLCHAIN_MANAGER_ROLE)
-
- else -> return
- }
-
- val pingMessage = thread.createMessage {
- content = "Oh right, better get the mods in..."
- }
-
- delay(3.seconds)
-
- pingMessage.edit {
- content = "Oh right, better get the staff in...\n" +
- "Hey, ${modRole.mention} and ${managerRole.mention}! Squirrel!"
- }
-
- delay(3.seconds)
-
- pingMessage.delete("Removing temporary moderator ping message.")
- }
-
- suggestion.message = message.id
-
- suggestions.set(suggestion)
- } else {
- val message = channel.getMessage(suggestion.message!!)
-
- message.edit { suggestion(suggestion) }
-
- if (suggestion.thread != null && suggestion.threadButtons != null) {
- val thread = (channel as? TextChannel)?.activeThreads?.toList()?.firstOrNull {
- it.id == suggestion.thread
- }
-
- val threadMessage = thread?.getMessage(suggestion.threadButtons!!)
-
- threadMessage?.edit {
- suggestion(suggestion, false)
-
- content = THREAD_INTRO
- }
- }
- }
- }
-
- suspend fun sendSuggestionUpdateMessage(suggestion: Suggestion) {
- val user = kord.getUser(suggestion.owner) ?: return
-
- val suggestionMessage = if (suggestion.message != null) {
- kord.getGuildOrNull(COMMUNITY_GUILD)
- ?.getChannelOf(SUGGESTION_CHANNEL)
- ?.getMessageOrNull(suggestion.message!!)
- } else {
- null
- }
-
- user.dm {
- embed {
- color = suggestion.status.color
- title = "Suggestion updated"
-
- description = if (suggestionMessage != null) {
- "[Suggestion ${suggestion._id.value}](${suggestionMessage.getJumpUrl()}) "
- } else {
- "Suggestion ${suggestion._id.value} "
- }
-
- description += "has been updated.\n\n" +
- "**__Suggestion__**\n\n" +
- suggestion.text
-
- description += "\n\n**Status:** ${suggestion.status.readableName}\n"
-
- if (suggestion.githubIssue != null) {
- val issue = suggestion.githubIssue!!
- val (repoName, issueNumber) = issue.split('/')
-
- description += "$GITHUB_EMOJI " +
- "[$repoName#$issueNumber]" +
- "(https://github.com/QuiltMC/$repoName/issues/$issueNumber)\n"
- }
-
- if (suggestion.comment != null) {
- description += "\n" +
- "**__Staff response__**\n\n" +
- suggestion.comment
- }
- }
- }
-
- if (suggestion.thread != null) {
- kord.getChannelOf(suggestion.thread!!)?.createMessage {
- content = "**__Suggestion updated__**\n" +
- "**Status:** ${suggestion.status.readableName}\n"
-
- if (suggestion.githubIssue != null) {
- val issue = suggestion.githubIssue!!
- val (repoName, issueNumber) = issue.split('/')
-
- content += "$GITHUB_EMOJI " +
- "\n"
- }
-
- if (suggestion.comment != null) {
- content += "\n" +
- "**__Staff response__**\n\n" +
- suggestion.comment
- }
- }
- }
- }
-
- suspend fun getChannel() = kord.getChannelOf(SUGGESTION_CHANNEL)!!
-
- fun MessageCreateBuilder.suggestion(suggestion: Suggestion, sendEmbed: Boolean = true) {
- val id = suggestion._id.value
-
- if (sendEmbed) {
- embed {
- author {
- name = suggestion.ownerName
- icon = suggestion.ownerAvatar
- }
-
- description = if (suggestion.isPluralkit) {
- "@${suggestion.ownerName} (<@${suggestion.owner.value}>)\n\n"
- } else {
- "<@${suggestion.owner.value}>\n\n"
- }
-
- description += "${suggestion.text}\n\n"
-
- if (suggestion.githubIssue != null) {
- val issue = suggestion.githubIssue!!
- val (repoName, issueNumber) = issue.split('/')
-
- description += "$GITHUB_EMOJI " +
- "[$repoName#$issueNumber]" +
- "(https://github.com/QuiltMC/$repoName/issues/$issueNumber)\n\n"
- }
-
- if (suggestion.positiveVotes > 0) {
- description += "**Upvotes:** ${suggestion.positiveVotes}\n"
- }
-
- if (suggestion.negativeVotes > 0) {
- description += "**Downvotes:** ${suggestion.negativeVotes}\n"
- }
-
- description += "**Total:** ${suggestion.voteDifference}"
-
- if (suggestion.comment != null) {
- description += "\n\n**__Staff response__\n\n** ${suggestion.comment}"
- }
-
- color = suggestion.status.color
-
- footer {
- text = "Status: ${suggestion.status.readableName} • ID: $id"
- }
- }
- }
-
- if (suggestion.status == SuggestionStatus.Open) {
- actionRow {
- interactionButton(ButtonStyle.Primary, "$id/$ACTION_UP") {
- emoji(EMOTE_UPVOTE)
-
- label = "Upvote"
- }
-
- interactionButton(ButtonStyle.Primary, "$id/$ACTION_DOWN") {
- emoji(EMOTE_DOWNVOTE)
-
- label = "Downvote"
- }
-
- interactionButton(ButtonStyle.Danger, "$id/$ACTION_REMOVE") {
- emoji(EMOTE_REMOVE)
-
- label = "Retract vote"
- }
- }
- }
- }
-
- fun MessageModifyBuilder.suggestion(suggestion: Suggestion, sendEmbed: Boolean = true) {
- val id = suggestion._id.value
-
- if (sendEmbed) {
- embed {
- author {
- name = suggestion.ownerName
- icon = suggestion.ownerAvatar
- }
-
- description = if (suggestion.isPluralkit) {
- "@${suggestion.ownerName} (<@${suggestion.owner.value}>)\n\n"
- } else {
- "<@${suggestion.owner.value}>\n\n"
- }
-
- description += "${suggestion.text}\n\n"
-
- if (suggestion.githubIssue != null) {
- val issue = suggestion.githubIssue!!
- val (repoName, issueNumber) = issue.split('/')
-
- description += "$GITHUB_EMOJI " +
- "[$repoName#$issueNumber]" +
- "(https://github.com/QuiltMC/$repoName/issues/$issueNumber)\n\n"
- }
-
- if (suggestion.positiveVotes > 0) {
- description += "**Upvotes:** ${suggestion.positiveVotes}\n"
- }
-
- if (suggestion.negativeVotes > 0) {
- description += "**Downvotes:** ${suggestion.negativeVotes}\n"
- }
-
- description += "**Total:** ${suggestion.voteDifference}"
-
- if (suggestion.comment != null) {
- description += "\n\n**__Staff response__\n\n** ${suggestion.comment}"
- }
-
- color = suggestion.status.color
-
- footer {
- text = "Status: ${suggestion.status.readableName} • ID: $id"
- }
- }
- }
-
- if (suggestion.status == SuggestionStatus.Open) {
- actionRow {
- interactionButton(ButtonStyle.Primary, "$id/$ACTION_UP") {
- emoji(EMOTE_UPVOTE)
-
- label = "Upvote"
- }
-
- interactionButton(ButtonStyle.Primary, "$id/$ACTION_DOWN") {
- emoji(EMOTE_DOWNVOTE)
-
- label = "Downvote"
- }
-
- interactionButton(ButtonStyle.Danger, "$id/$ACTION_REMOVE") {
- emoji(EMOTE_REMOVE)
-
- label = "Retract vote"
- }
- }
- } else if (suggestion.status != SuggestionStatus.Open) {
- components = mutableListOf()
- }
- }
-
- inner class SuggestionEditArguments : Arguments() {
- val suggestion by suggestion {
- name = "suggestion"
- description = "Suggestion ID to act on"
- }
-
- val text by coalescingString {
- name = "text"
- description = "New suggestion text"
-
- validate {
- if (value.length > SUGGESTION_SIZE_LIMIT) {
- fail("Suggestion text must not be longer than $SUGGESTION_SIZE_LIMIT characters.")
- }
- }
- }
- }
-
-// inner class SuggestionSearchArguments : Arguments() {
-// val status by defaultingEnumChoice(
-// "status",
-// "Status to check for, defaulting to Approved",
-// "Status",
-// SuggestionStatus.Approved
-// )
-//
-// val sentiment by optionalEnumChoice(
-// "sentiment",
-// "How the community voted",
-// "Sentiment"
-// )
-//
-// val user by optionalUser("user", "Suggestion creator")
-// val suggestion by optionalSuggestion("suggestion", "Suggestion ID to search for")
-//
-// val text by optionalCoalescingString("text", "Text to search for in the description")
-// }
-
- inner class SuggestionStateArguments : Arguments() {
- val suggestion by suggestion {
- name = "suggestion"
- description = "Suggestion ID to act on"
- }
-
- val status by optionalEnumChoice {
- name = "status"
- description = "Status to apply"
-
- typeName = "status"
- }
-
- val comment by optionalString {
- name = "comment"
- description = "Comment text to set, 'clear' to remove"
-
- validate {
- if ((value?.length ?: -1) > COMMENT_SIZE_LIMIT) {
- fail("Comment must not be longer than $COMMENT_SIZE_LIMIT characters.")
- }
- }
- }
-
- val issue by optionalString {
- name = "github-issue"
- description = "GitHub issue for this suggestion, 'clear' to remove"
-
- validate {
- value ?: return@validate
-
- failIf(
- "Issue specification must be of the form `repo/123`, without the repo owner - " +
- "For example: `cozy-discord/12`"
- ) { value!!.count { it == '/' } != 1 }
-
- if (passed) {
- failIf(
- "Issue numbers must be integers"
- ) { value!!.split('/').last().toIntOrNull() == null }
- }
- }
- }
- }
-}