From 3161a911cd9fdca8a460fe380f067a195391ce26 Mon Sep 17 00:00:00 2001 From: Octal Date: Sat, 29 Oct 2022 20:50:37 -0500 Subject: [PATCH 001/137] Set up handling for message interaction commands --- .../src/main/kotlin/dev/proxyfox/bot/BotUtil.kt | 16 ++++++++++++++-- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 5 +++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 5dc7af8d..ab7bd6e1 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -9,14 +9,14 @@ package dev.proxyfox.bot import dev.kord.common.Color -import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.* import dev.kord.core.Kord import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.channel.createMessage import dev.kord.core.builder.kord.KordBuilder import dev.kord.core.entity.channel.TextChannel import dev.kord.core.event.gateway.ReadyEvent -import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import dev.kord.core.event.interaction.* import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.event.message.MessageUpdateEvent import dev.kord.core.event.message.ReactionAddEvent @@ -101,6 +101,11 @@ suspend fun login() { } } + kord.registerMessageCommands() + kord.on { + onInteract() + } + var initialized = false kord.on { if (!initialized) { @@ -132,6 +137,13 @@ suspend fun login() { } } +suspend fun Kord.registerMessageCommands() { + createGlobalMessageCommand("Delete Message") {} + createGlobalMessageCommand("Fetch Message Info") {} + createGlobalMessageCommand("Ping Message Author") {} + createGlobalMessageCommand("Edit Message") +} + suspend fun updatePresence() { startTime = Clock.System.now() var count = 0 diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index 1f9163e6..92f789fb 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -17,6 +17,7 @@ import dev.kord.core.cache.data.EmbedData import dev.kord.core.entity.Attachment import dev.kord.core.entity.Embed import dev.kord.core.entity.channel.GuildMessageChannel +import dev.kord.core.event.interaction.* import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.event.message.MessageUpdateEvent import dev.kord.core.event.message.ReactionAddEvent @@ -277,4 +278,8 @@ suspend fun ReactionAddEvent.onReactionAdd() { message.deleteReaction(userId, emoji) } } +} + +fun GlobalMessageCommandInteractionCreateEvent.onInteract() { + } \ No newline at end of file From 37b690902a1d36efd25e1887b8be63dee5b542be Mon Sep 17 00:00:00 2001 From: tibs Date: Tue, 8 Nov 2022 20:14:38 -0500 Subject: [PATCH 002/137] Initial API stuff --- build.gradle.kts | 1 - gradle/libs.versions.toml | 16 ++++++-- modules/api/{server => }/build.gradle.kts | 19 +++------- .../dev/proxyfox/api/server/ServerMain.kt | 17 --------- .../main/kotlin/dev/proxyfox/api/ApiMain.kt | 37 +++++++++++++++++++ .../kotlin/dev/proxyfox/api/models/System.kt | 24 ++++++++++++ .../dev/proxyfox/api/routes/SystemRoutes.kt | 34 +++++++++++++++++ modules/bot/build.gradle.kts | 1 + .../main/kotlin/dev/proxyfox/bot/BotMain.kt | 4 ++ settings.gradle.kts | 3 +- 10 files changed, 119 insertions(+), 37 deletions(-) rename modules/api/{server => }/build.gradle.kts (56%) delete mode 100644 modules/api/server/src/main/kotlin/dev/proxyfox/api/server/ServerMain.kt create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt diff --git a/build.gradle.kts b/build.gradle.kts index 9d194ea4..08992d82 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,7 +46,6 @@ allprojects { repositories { mavenCentral() - maven("https://libraries.minecraft.net/") maven("https://oss.sonatype.org/content/repositories/snapshots") maven("https://jitpack.io") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6c948cf5..60aeac5d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,12 +5,15 @@ kord = "0.8.0-M16" kotlin = "1.7.10" kotlinx_coroutines = "1.6.4" -# Database-specific +# Database postgres = "42.3.3" kjdbc = "0.5.2" gson = "2.9.0" kmongo = "4.6.0" +# API +ktor = "2.1.3" + # Testing testng = "7.6.1" mockk = "1.+" @@ -34,16 +37,23 @@ kmongo_base = { module = "org.litote.kmongo:kmongo", version.ref = "kmongo" } kmongo_coroutine = { module = "org.litote.kmongo:kmongo-coroutine", version.ref = "kmongo" } kmongo_async = { module = "org.litote.kmongo:kmongo-async", version.ref = "kmongo" } +ktor_server = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } +ktor_server_netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } +ktor_content_negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } +ktor_serialization = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } + testng = { module = "org.testng:testng", version.ref = "testng" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } kotlinx_coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx_coroutines" } [bundles] base = ["guava", "logback", "kotlin_stdlib", "kotlinx_coroutines_core", "kord"] -test = ["testng", "kotlinx_coroutines_test", "mockk"] database = ["gson", "kmongo_base", "kmongo_coroutine", "kmongo_async"] +api = ["ktor_server", "ktor_server_netty", "ktor_content_negotiation", "ktor_serialization"] +test = ["testng", "kotlinx_coroutines_test", "mockk"] [plugins] kotlin_jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } -licenser = { id = "org.quiltmc.gradle.licenser", version.ref = "licenser" } \ No newline at end of file +licenser = { id = "org.quiltmc.gradle.licenser", version.ref = "licenser" } +serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } \ No newline at end of file diff --git a/modules/api/server/build.gradle.kts b/modules/api/build.gradle.kts similarity index 56% rename from modules/api/server/build.gradle.kts rename to modules/api/build.gradle.kts index ad0a1888..a2e961b0 100644 --- a/modules/api/server/build.gradle.kts +++ b/modules/api/build.gradle.kts @@ -10,20 +10,11 @@ plugins { application alias(libs.plugins.kotlin.jvm) alias(libs.plugins.shadow) -} - -application { - mainClass.set("dev.proxyfox.api.server.ServerMainKt") + alias(libs.plugins.serialization) } dependencies { - implementation(project(":modules:common")) - implementation(project(":modules:database")) -} - -tasks { - shadowJar { - archiveClassifier.set("shadow") - mergeServiceFiles() - } -} + api(project(":modules:common")) + api(project(":modules:database")) + api(libs.bundles.api) +} \ No newline at end of file diff --git a/modules/api/server/src/main/kotlin/dev/proxyfox/api/server/ServerMain.kt b/modules/api/server/src/main/kotlin/dev/proxyfox/api/server/ServerMain.kt deleted file mode 100644 index 4b23e0a5..00000000 --- a/modules/api/server/src/main/kotlin/dev/proxyfox/api/server/ServerMain.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.api.server - -fun main() = ServerMain.main() - -object ServerMain { - fun main() { - - } -} \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt new file mode 100644 index 00000000..db3b2745 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api + +import dev.proxyfox.api.routes.* +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.routing.* + + +object ApiMain { + fun main() = embeddedServer(Netty, port = Integer.parseInt(System.getenv("PORT"))) { + configureRouting() + configureSerialization() + }.start() + + private fun Application.configureSerialization() { + install(ContentNegotiation) { + json() + } + } + + private fun Application.configureRouting() { + routing { + systemRoutes() + } + } +} \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt new file mode 100644 index 00000000..fbae0fb0 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.models + +import kotlinx.serialization.Serializable + +@Serializable +data class System( + val id: String, + val name: String?, + val description: String?, + val tag: String?, + val pronouns: String?, + val color: String, + val avatarUrl: String?, + val timezone: String?, + val timestamp: String +) \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt new file mode 100644 index 00000000..386f417f --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.routes + +import dev.proxyfox.api.models.* +import dev.proxyfox.database.database +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Route.systemRoutes() { + route("/systems/{id}") { + get { + val system = database.fetchSystemFromId(call.parameters["id"]!!) ?: return@get call.respond("System not found") + call.respond(System( + id = system.id, + name = system.name, + description = system.description, + tag = system.tag, + pronouns = system.pronouns, + color = Integer.toHexString(system.color), + avatarUrl = system.avatarUrl, + timezone = system.timezone, + timestamp = system.timestamp.toString() + )) + } + } +} \ No newline at end of file diff --git a/modules/bot/build.gradle.kts b/modules/bot/build.gradle.kts index b816886c..480abb1a 100644 --- a/modules/bot/build.gradle.kts +++ b/modules/bot/build.gradle.kts @@ -15,6 +15,7 @@ plugins { dependencies { implementation(project(":modules:common")) implementation(project(":modules:database")) + implementation(project(":modules:api")) } application.mainClass.set("dev.proxyfox.bot.BotMainKt") diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt index f4bc1cd0..f2656a6b 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt @@ -8,6 +8,7 @@ package dev.proxyfox.bot +import dev.proxyfox.api.ApiMain import dev.proxyfox.bot.command.Commands import dev.proxyfox.bot.md.parseMarkdown import dev.proxyfox.bot.terminal.TerminalCommands @@ -32,6 +33,9 @@ object BotMain { // Setup database DatabaseMain.main(findUnixValue(args, "--database=")) + // Start API + ApiMain.main() + // Start reading console input TerminalCommands.start() diff --git a/settings.gradle.kts b/settings.gradle.kts index 0f092266..70274b9c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,5 +23,4 @@ include(":modules:bot") include(":modules:common") include(":modules:database") include(":modules:conversion") -include(":modules:api") -include(":modules:api:server") \ No newline at end of file +include(":modules:api") \ No newline at end of file From b9567131870273f19979a8a0274ebb9d1edde8af Mon Sep 17 00:00:00 2001 From: tibs Date: Tue, 8 Nov 2022 20:32:20 -0500 Subject: [PATCH 003/137] Fix funni failure --- modules/api/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/api/build.gradle.kts b/modules/api/build.gradle.kts index a2e961b0..e1d3265b 100644 --- a/modules/api/build.gradle.kts +++ b/modules/api/build.gradle.kts @@ -7,7 +7,6 @@ */ plugins { - application alias(libs.plugins.kotlin.jvm) alias(libs.plugins.shadow) alias(libs.plugins.serialization) From 66bd1243a2e3bdfca86cc8b12caaa47933b4d746 Mon Sep 17 00:00:00 2001 From: tibs Date: Wed, 9 Nov 2022 15:03:20 -0500 Subject: [PATCH 004/137] More API stuff --- .../main/kotlin/dev/proxyfox/api/ApiMain.kt | 6 ++- .../kotlin/dev/proxyfox/api/models/Member.kt | 40 +++++++++++++++++++ .../kotlin/dev/proxyfox/api/models/Switch.kt | 17 ++++++++ .../kotlin/dev/proxyfox/api/models/System.kt | 28 +++++++++++-- .../dev/proxyfox/api/routes/MemberRoutes.kt | 24 +++++++++++ .../dev/proxyfox/api/routes/SwitchRoutes.kt | 24 +++++++++++ .../dev/proxyfox/api/routes/SystemRoutes.kt | 14 +------ 7 files changed, 137 insertions(+), 16 deletions(-) create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt index db3b2745..d0d08aae 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt @@ -8,7 +8,9 @@ package dev.proxyfox.api -import dev.proxyfox.api.routes.* +import dev.proxyfox.api.routes.memberRoutes +import dev.proxyfox.api.routes.switchRoutes +import dev.proxyfox.api.routes.systemRoutes import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* import io.ktor.server.engine.* @@ -32,6 +34,8 @@ object ApiMain { private fun Application.configureRouting() { routing { systemRoutes() + memberRoutes() + switchRoutes() } } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt new file mode 100644 index 00000000..3cda6c26 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt @@ -0,0 +1,40 @@ +package dev.proxyfox.api.models + +import dev.proxyfox.common.fromColor +import dev.proxyfox.database.records.member.MemberRecord +import kotlinx.serialization.Serializable + +@Serializable +data class Member( + val id: String, + val name: String, + val displayName: String?, + val description: String?, + val pronouns: String?, + val color: String?, + val avatarUrl: String?, + val keepProxy: Boolean, + val autoProxy: Boolean, + val messageCount: ULong, + val birthday: String?, + val age: String?, + val role: String? +) { + companion object { + fun fromRecord(member: MemberRecord) = Member( + id = member.id, + name = member.name, + displayName = member.displayName, + description = member.description, + pronouns = member.pronouns, + color = member.color.fromColor(), + avatarUrl = member.avatarUrl, + keepProxy = member.keepProxy, + autoProxy = member.autoProxy, + messageCount = member.messageCount, + birthday = member.birthday.toString(), + age = member.age, + role = member.role + ) + } +} \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt new file mode 100644 index 00000000..80493ca3 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt @@ -0,0 +1,17 @@ +package dev.proxyfox.api.models + +import dev.proxyfox.database.records.system.SystemSwitchRecord + +data class Switch( + val id: String, + val members: List, + val timestamp: String +) { + companion object { + fun fromRecord(record: SystemSwitchRecord) = Switch( + id = record.id, + members = record.memberIds, + timestamp = record.timestamp.toString() + ) + } +} diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt index fbae0fb0..045fc0a6 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt @@ -8,6 +8,10 @@ package dev.proxyfox.api.models +import dev.proxyfox.common.fromColor +import dev.proxyfox.database.records.misc.AutoProxyMode +import dev.proxyfox.database.records.misc.TrustLevel +import dev.proxyfox.database.records.system.SystemRecord import kotlinx.serialization.Serializable @Serializable @@ -17,8 +21,26 @@ data class System( val description: String?, val tag: String?, val pronouns: String?, - val color: String, + val color: String?, val avatarUrl: String?, val timezone: String?, - val timestamp: String -) \ No newline at end of file + val autoProxy: String?, + val autoType: AutoProxyMode, + val trust: Map +) { + companion object { + fun fromRecord(system: SystemRecord) = System( + id = system.id, + name = system.name, + description = system.description, + tag = system.tag, + pronouns = system.pronouns, + color = system.color.fromColor(), + avatarUrl = system.avatarUrl, + timezone = system.timezone, + autoProxy = system.autoProxy, + autoType = system.autoType, + trust = system.trust + ) + } +} \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt new file mode 100644 index 00000000..5a3fc173 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.routes + +import dev.proxyfox.api.models.Member +import dev.proxyfox.database.database +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Route.memberRoutes() { + route("/systems/{id}/members") { + get { + val id = call.parameters["id"] ?: return@get call.respond("System not found") + call.respond(database.fetchMembersFromSystem(id)?.map(Member.Companion::fromRecord) ?: emptyList()) + } + } +} \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt new file mode 100644 index 00000000..f3175ab7 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.routes + +import dev.proxyfox.api.models.Switch +import dev.proxyfox.database.database +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Route.switchRoutes() { + route("/systems/{id}/switches") { + get { + val id = call.parameters["id"] ?: return@get call.respond("System not found") + call.respond(database.fetchSwitchesFromSystem(id)?.map(Switch.Companion::fromRecord) ?: emptyList()) + } + } +} \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt index 386f417f..f640031b 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt @@ -8,7 +8,7 @@ package dev.proxyfox.api.routes -import dev.proxyfox.api.models.* +import dev.proxyfox.api.models.System import dev.proxyfox.database.database import io.ktor.server.application.* import io.ktor.server.response.* @@ -18,17 +18,7 @@ fun Route.systemRoutes() { route("/systems/{id}") { get { val system = database.fetchSystemFromId(call.parameters["id"]!!) ?: return@get call.respond("System not found") - call.respond(System( - id = system.id, - name = system.name, - description = system.description, - tag = system.tag, - pronouns = system.pronouns, - color = Integer.toHexString(system.color), - avatarUrl = system.avatarUrl, - timezone = system.timezone, - timestamp = system.timestamp.toString() - )) + call.respond(System.fromRecord(system)) } } } \ No newline at end of file From 2c8d0e949e58f3b9aafae4bfdd2d9dca608db21b Mon Sep 17 00:00:00 2001 From: tibs Date: Wed, 9 Nov 2022 16:17:25 -0500 Subject: [PATCH 005/137] Messages et al. --- .../main/kotlin/dev/proxyfox/api/ApiMain.kt | 2 + .../kotlin/dev/proxyfox/api/models/Member.kt | 10 +++++ .../kotlin/dev/proxyfox/api/models/Message.kt | 41 +++++++++++++++++++ .../kotlin/dev/proxyfox/api/models/Switch.kt | 10 +++++ .../kotlin/dev/proxyfox/api/models/System.kt | 2 + .../dev/proxyfox/api/routes/MemberRoutes.kt | 6 +++ .../dev/proxyfox/api/routes/MessageRoutes.kt | 25 +++++++++++ 7 files changed, 96 insertions(+) create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt index d0d08aae..d7a2612e 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt @@ -9,6 +9,7 @@ package dev.proxyfox.api import dev.proxyfox.api.routes.memberRoutes +import dev.proxyfox.api.routes.messageRoutes import dev.proxyfox.api.routes.switchRoutes import dev.proxyfox.api.routes.systemRoutes import io.ktor.serialization.kotlinx.json.* @@ -36,6 +37,7 @@ object ApiMain { systemRoutes() memberRoutes() switchRoutes() + messageRoutes() } } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt index 3cda6c26..76be994a 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.models import dev.proxyfox.common.fromColor @@ -16,6 +24,7 @@ data class Member( val keepProxy: Boolean, val autoProxy: Boolean, val messageCount: ULong, + val created: String, val birthday: String?, val age: String?, val role: String? @@ -32,6 +41,7 @@ data class Member( keepProxy = member.keepProxy, autoProxy = member.autoProxy, messageCount = member.messageCount, + created = member.timestamp.toString(), birthday = member.birthday.toString(), age = member.age, role = member.role diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt new file mode 100644 index 00000000..9cfecd6e --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.models + +import dev.proxyfox.database.records.misc.ProxiedMessageRecord +import kotlinx.serialization.Serializable + +@Serializable +data class Message( + val timestamp: String, + val sender: String, + val original: String, + val proxied: String, + val channel: String, + val guild: String, + val thread: String?, + val system: String, + val member: String, + val deleted: Boolean +) { + companion object { + fun fromRecord(record: ProxiedMessageRecord) = Message( + timestamp = record.creationDate.toString(), + sender = record.userId.toString(), + original = record.oldMessageId.toString(), + proxied = record.newMessageId.toString(), + channel = record.channelId.toString(), + guild = record.guildId.toString(), + thread = record.threadId.toString(), + system = record.systemId, + member = record.memberId, + deleted = record.deleted + ) + } +} \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt index 80493ca3..a2fada44 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt @@ -1,7 +1,17 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.models import dev.proxyfox.database.records.system.SystemSwitchRecord +import kotlinx.serialization.Serializable +@Serializable data class Switch( val id: String, val members: List, diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt index 045fc0a6..ad10feed 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt @@ -24,6 +24,7 @@ data class System( val color: String?, val avatarUrl: String?, val timezone: String?, + val created: String, val autoProxy: String?, val autoType: AutoProxyMode, val trust: Map @@ -38,6 +39,7 @@ data class System( color = system.color.fromColor(), avatarUrl = system.avatarUrl, timezone = system.timezone, + created = system.timestamp.toString(), autoProxy = system.autoProxy, autoType = system.autoType, trust = system.trust diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt index 5a3fc173..cbfa6ec3 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt @@ -20,5 +20,11 @@ fun Route.memberRoutes() { val id = call.parameters["id"] ?: return@get call.respond("System not found") call.respond(database.fetchMembersFromSystem(id)?.map(Member.Companion::fromRecord) ?: emptyList()) } + + get("/{member}") { + val id = call.parameters["id"] ?: return@get call.respond("System not found") + val member = database.fetchMemberFromSystem(id, call.parameters["member"]!!) ?: return@get call.respond("Member not found") + call.respond(Member.fromRecord(member)) + } } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt new file mode 100644 index 00000000..634edad4 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.routes + +import dev.kord.common.entity.Snowflake +import dev.proxyfox.api.models.Message +import dev.proxyfox.database.database +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Route.messageRoutes() { + route("/messages/{id}") { + get { + val message = database.fetchMessage(Snowflake(call.parameters["id"]!!)) ?: return@get call.respond("Message not found") + call.respond(Message.fromRecord(message)) + } + } +} \ No newline at end of file From a973e26c55b6a887cca35691f4ffa3cc97df3d16 Mon Sep 17 00:00:00 2001 From: tibs Date: Wed, 9 Nov 2022 17:38:16 -0500 Subject: [PATCH 006/137] Guild settings and API version --- .../main/kotlin/dev/proxyfox/api/ApiMain.kt | 12 ++++---- .../api/models/MemberGuildSettings.kt | 29 +++++++++++++++++++ .../dev/proxyfox/api/routes/MemberRoutes.kt | 19 +++++++++--- 3 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt index d7a2612e..44f148a2 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt @@ -19,8 +19,8 @@ import io.ktor.server.netty.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.routing.* - object ApiMain { + private const val version = "1" fun main() = embeddedServer(Netty, port = Integer.parseInt(System.getenv("PORT"))) { configureRouting() configureSerialization() @@ -34,10 +34,12 @@ object ApiMain { private fun Application.configureRouting() { routing { - systemRoutes() - memberRoutes() - switchRoutes() - messageRoutes() + route("/v${version}") { + systemRoutes() + memberRoutes() + switchRoutes() + messageRoutes() + } } } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt new file mode 100644 index 00000000..ae690cad --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.models + +import dev.proxyfox.database.records.member.MemberServerSettingsRecord +import kotlinx.serialization.Serializable + +@Serializable +data class MemberGuildSettings( + val displayName: String?, + val avatarUrl: String?, + val autoProxy: Boolean, + val proxyEnabled: Boolean +) { + companion object { + fun fromRecord(record: MemberServerSettingsRecord) = MemberGuildSettings( + displayName = record.nickname, + avatarUrl = record.avatarUrl, + autoProxy = record.autoProxy, + proxyEnabled = record.proxyEnabled + ) + } +} diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt index cbfa6ec3..8a21abe0 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt @@ -8,7 +8,9 @@ package dev.proxyfox.api.routes +import dev.kord.common.entity.Snowflake import dev.proxyfox.api.models.Member +import dev.proxyfox.api.models.MemberGuildSettings import dev.proxyfox.database.database import io.ktor.server.application.* import io.ktor.server.response.* @@ -21,10 +23,19 @@ fun Route.memberRoutes() { call.respond(database.fetchMembersFromSystem(id)?.map(Member.Companion::fromRecord) ?: emptyList()) } - get("/{member}") { - val id = call.parameters["id"] ?: return@get call.respond("System not found") - val member = database.fetchMemberFromSystem(id, call.parameters["member"]!!) ?: return@get call.respond("Member not found") - call.respond(Member.fromRecord(member)) + route("/{member}") { + get { + val id = call.parameters["id"] ?: return@get call.respond("System not found") + val member = database.fetchMemberFromSystem(id, call.parameters["member"]!!) ?: return@get call.respond("Member not found") + call.respond(Member.fromRecord(member)) + } + + get("/guilds/{guild}") { + val id = call.parameters["id"] ?: return@get call.respond("System not found") + val member = database.fetchMemberFromSystem(id, call.parameters["member"]!!) ?: return@get call.respond("Member not found") + val guildSettings = database.fetchMemberServerSettingsFromSystemAndMember(Snowflake(call.parameters["guild"]!!).value, id, member.id) ?: return@get call.respond("Guild not found") + call.respond(MemberGuildSettings.fromRecord(guildSettings)) + } } } } \ No newline at end of file From dca836f089e8bb0e8dc715ace8f71ac029f95f67 Mon Sep 17 00:00:00 2001 From: tibs Date: Wed, 9 Nov 2022 19:12:34 -0500 Subject: [PATCH 007/137] Misc API stuff --- .../kotlin/dev/proxyfox/api/models/Member.kt | 28 +++++++++++-------- .../api/models/MemberGuildSettings.kt | 16 +++++------ .../dev/proxyfox/api/models/ProxyTag.kt | 19 +++++++++++++ .../kotlin/dev/proxyfox/api/models/System.kt | 12 ++++---- .../api/models/SystemGuildSettings.kt | 24 ++++++++++++++++ .../dev/proxyfox/api/routes/SwitchRoutes.kt | 8 ++++++ .../dev/proxyfox/api/routes/SystemRoutes.kt | 8 ++++++ 7 files changed, 89 insertions(+), 26 deletions(-) create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt index 76be994a..99e86bc6 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt @@ -9,42 +9,46 @@ package dev.proxyfox.api.models import dev.proxyfox.common.fromColor +import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberRecord +import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable @Serializable data class Member( val id: String, val name: String, - val displayName: String?, + val display_name: String?, val description: String?, val pronouns: String?, val color: String?, - val avatarUrl: String?, - val keepProxy: Boolean, - val autoProxy: Boolean, - val messageCount: ULong, + val avatar_url: String?, + val keep_proxy: Boolean, + val auto_proxy: Boolean, + val message_count: ULong, val created: String, val birthday: String?, val age: String?, - val role: String? + val role: String?, + val proxy_tags : List ) { companion object { fun fromRecord(member: MemberRecord) = Member( id = member.id, name = member.name, - displayName = member.displayName, + display_name = member.displayName, description = member.description, pronouns = member.pronouns, color = member.color.fromColor(), - avatarUrl = member.avatarUrl, - keepProxy = member.keepProxy, - autoProxy = member.autoProxy, - messageCount = member.messageCount, + avatar_url = member.avatarUrl, + keep_proxy = member.keepProxy, + auto_proxy = member.autoProxy, + message_count = member.messageCount, created = member.timestamp.toString(), birthday = member.birthday.toString(), age = member.age, - role = member.role + role = member.role, + proxy_tags = runBlocking { database.fetchProxiesFromSystemAndMember(member.systemId, member.id)?.map(ProxyTag::fromRecord) ?: emptyList() } ) } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt index ae690cad..0fafae86 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt @@ -13,17 +13,17 @@ import kotlinx.serialization.Serializable @Serializable data class MemberGuildSettings( - val displayName: String?, - val avatarUrl: String?, - val autoProxy: Boolean, - val proxyEnabled: Boolean + val display_name: String?, + val avatar_url: String?, + val auto_proxy: Boolean, + val proxy_enabled: Boolean ) { companion object { fun fromRecord(record: MemberServerSettingsRecord) = MemberGuildSettings( - displayName = record.nickname, - avatarUrl = record.avatarUrl, - autoProxy = record.autoProxy, - proxyEnabled = record.proxyEnabled + display_name = record.nickname, + avatar_url = record.avatarUrl, + auto_proxy = record.autoProxy, + proxy_enabled = record.proxyEnabled ) } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt new file mode 100644 index 00000000..1613fca9 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.models + +import dev.proxyfox.database.records.member.MemberProxyTagRecord +import kotlinx.serialization.Serializable + +@Serializable +data class ProxyTag(val prefix: String?, val suffix: String?) { + companion object { + fun fromRecord(record: MemberProxyTagRecord) = ProxyTag(record.prefix, record.suffix) + } +} \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt index ad10feed..0ac41b4a 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt @@ -22,11 +22,11 @@ data class System( val tag: String?, val pronouns: String?, val color: String?, - val avatarUrl: String?, + val avatar_url: String?, val timezone: String?, val created: String, - val autoProxy: String?, - val autoType: AutoProxyMode, + val auto_proxy: String?, + val auto_type: AutoProxyMode, val trust: Map ) { companion object { @@ -37,11 +37,11 @@ data class System( tag = system.tag, pronouns = system.pronouns, color = system.color.fromColor(), - avatarUrl = system.avatarUrl, + avatar_url = system.avatarUrl, timezone = system.timezone, created = system.timestamp.toString(), - autoProxy = system.autoProxy, - autoType = system.autoType, + auto_proxy = system.autoProxy, + auto_type = system.autoType, trust = system.trust ) } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt new file mode 100644 index 00000000..b36e4e9b --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.models + +import dev.proxyfox.database.records.misc.AutoProxyMode +import dev.proxyfox.database.records.system.SystemServerSettingsRecord +import kotlinx.serialization.Serializable + +@Serializable +data class SystemGuildSettings(val proxy_enabled: Boolean, val auto_proxy: String?, val auto_proxy_mode: AutoProxyMode) { + companion object { + fun fromRecord(record: SystemServerSettingsRecord) = SystemGuildSettings( + proxy_enabled = record.proxyEnabled, + auto_proxy = record.autoProxy, + auto_proxy_mode = record.autoProxyMode + ) + } +} diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt index f3175ab7..dce5843d 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt @@ -8,6 +8,7 @@ package dev.proxyfox.api.routes +import dev.proxyfox.api.models.Member import dev.proxyfox.api.models.Switch import dev.proxyfox.database.database import io.ktor.server.application.* @@ -21,4 +22,11 @@ fun Route.switchRoutes() { call.respond(database.fetchSwitchesFromSystem(id)?.map(Switch.Companion::fromRecord) ?: emptyList()) } } + + route("/systems/{id}/fronters") { + get { + val id = call.parameters["id"] ?: return@get call.respond("System not found") + call.respond(database.fetchFrontingMembersFromSystem(id)?.map(Member.Companion::fromRecord) ?: emptyList()) + } + } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt index f640031b..d6d2d86e 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt @@ -8,7 +8,9 @@ package dev.proxyfox.api.routes +import dev.kord.common.entity.Snowflake import dev.proxyfox.api.models.System +import dev.proxyfox.api.models.SystemGuildSettings import dev.proxyfox.database.database import io.ktor.server.application.* import io.ktor.server.response.* @@ -20,5 +22,11 @@ fun Route.systemRoutes() { val system = database.fetchSystemFromId(call.parameters["id"]!!) ?: return@get call.respond("System not found") call.respond(System.fromRecord(system)) } + + get("/guilds/{guild}") { + val id = call.parameters["id"] ?: return@get call.respond("System not found") + val settings = database.getOrCreateServerSettingsFromSystem(Snowflake(call.parameters["guild"]!!).value, id) + call.respond(SystemGuildSettings.fromRecord(settings)) + } } } \ No newline at end of file From 0be4f9eb045c16baac37ddda6fe5f761d2eb2066 Mon Sep 17 00:00:00 2001 From: tibs Date: Thu, 10 Nov 2022 17:53:03 -0500 Subject: [PATCH 008/137] Initial API authentication --- .../main/kotlin/dev/proxyfox/api/ApiMain.kt | 4 +-- .../kotlin/dev/proxyfox/api/Authentication.kt | 21 ++++++++++++++++ .../dev/proxyfox/api/routes/MemberRoutes.kt | 2 ++ .../dev/proxyfox/api/routes/MessageRoutes.kt | 8 +++--- .../dev/proxyfox/api/routes/SwitchRoutes.kt | 2 ++ .../dev/proxyfox/api/routes/SystemRoutes.kt | 2 ++ .../proxyfox/bot/command/SystemCommands.kt | 22 ++++++++++++++++ .../kotlin/dev/proxyfox/database/Database.kt | 4 +++ .../dev/proxyfox/database/DatabaseUtil.kt | 5 ++++ .../dev/proxyfox/database/JsonDatabase.kt | 13 ++++++++++ .../dev/proxyfox/database/MongoDatabase.kt | 9 +++++++ .../dev/proxyfox/database/NopDatabase.kt | 4 +++ .../dev/proxyfox/database/ProxyDatabase.kt | 8 ++++++ .../database/records/misc/TokenRecord.kt | 25 +++++++++++++++++++ .../database/records/system/SystemRecord.kt | 1 + 15 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt create mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt index 44f148a2..886033e5 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt @@ -23,10 +23,10 @@ object ApiMain { private const val version = "1" fun main() = embeddedServer(Netty, port = Integer.parseInt(System.getenv("PORT"))) { configureRouting() - configureSerialization() + configurePlugins() }.start() - private fun Application.configureSerialization() { + private fun Application.configurePlugins() { install(ContentNegotiation) { json() } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt new file mode 100644 index 00000000..80a4f57c --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api + +import dev.proxyfox.database.database +import io.ktor.server.application.* +import io.ktor.server.response.* + +val AuthenticationPlugin = createRouteScopedPlugin(name = "AuthenticationPlugin") { + onCall { call -> + val token = call.request.headers["Authorization"] ?: return@onCall call.respond("401 Unauthorized") + val userToken = database.getOrCreateTokenFromSystem(call.parameters["id"]!!).token + if (token != userToken) return@onCall call.respond("403 Forbidden") + } +} diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt index 8a21abe0..8d663b47 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt @@ -9,6 +9,7 @@ package dev.proxyfox.api.routes import dev.kord.common.entity.Snowflake +import dev.proxyfox.api.AuthenticationPlugin import dev.proxyfox.api.models.Member import dev.proxyfox.api.models.MemberGuildSettings import dev.proxyfox.database.database @@ -18,6 +19,7 @@ import io.ktor.server.routing.* fun Route.memberRoutes() { route("/systems/{id}/members") { + install(AuthenticationPlugin) get { val id = call.parameters["id"] ?: return@get call.respond("System not found") call.respond(database.fetchMembersFromSystem(id)?.map(Member.Companion::fromRecord) ?: emptyList()) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt index 634edad4..94703368 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt @@ -16,10 +16,8 @@ import io.ktor.server.response.* import io.ktor.server.routing.* fun Route.messageRoutes() { - route("/messages/{id}") { - get { - val message = database.fetchMessage(Snowflake(call.parameters["id"]!!)) ?: return@get call.respond("Message not found") - call.respond(Message.fromRecord(message)) - } + get("/messages/{id}") { + val message = database.fetchMessage(Snowflake(call.parameters["id"]!!)) ?: return@get call.respond("Message not found") + call.respond(Message.fromRecord(message)) } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt index dce5843d..6c2b1b28 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt @@ -8,6 +8,7 @@ package dev.proxyfox.api.routes +import dev.proxyfox.api.AuthenticationPlugin import dev.proxyfox.api.models.Member import dev.proxyfox.api.models.Switch import dev.proxyfox.database.database @@ -17,6 +18,7 @@ import io.ktor.server.routing.* fun Route.switchRoutes() { route("/systems/{id}/switches") { + install(AuthenticationPlugin) get { val id = call.parameters["id"] ?: return@get call.respond("System not found") call.respond(database.fetchSwitchesFromSystem(id)?.map(Switch.Companion::fromRecord) ?: emptyList()) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt index d6d2d86e..2d49cca7 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt @@ -9,6 +9,7 @@ package dev.proxyfox.api.routes import dev.kord.common.entity.Snowflake +import dev.proxyfox.api.AuthenticationPlugin import dev.proxyfox.api.models.System import dev.proxyfox.api.models.SystemGuildSettings import dev.proxyfox.database.database @@ -18,6 +19,7 @@ import io.ktor.server.routing.* fun Route.systemRoutes() { route("/systems/{id}") { + install(AuthenticationPlugin) get { val system = database.fetchSystemFromId(call.parameters["id"]!!) ?: return@get call.respond("System not found") call.respond(System.fromRecord(system)) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index bccc1ac3..8cea5e8b 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -26,6 +26,7 @@ import dev.proxyfox.common.printStep import dev.proxyfox.common.toColor import dev.proxyfox.database.database import dev.proxyfox.database.etc.exporter.Exporter +import dev.proxyfox.database.generateToken /** * Commands for accessing and changing system settings @@ -83,6 +84,10 @@ object SystemCommands { unixLiteral(arrayOf("by-message-count", "bmc"), ::listByMessage) unixLiteral(arrayOf("verbose", "v"), ::listVerbose) }) + + registerCommand(literal(arrayOf("token", "t"), ::token) { + literal(arrayOf("refresh", "r"), ::tokenRefresh) + }) } private suspend fun empty(ctx: MessageHolder): String { @@ -312,6 +317,23 @@ object SystemCommands { return system.tag ?: "System tag not set." } + private suspend fun token(ctx: MessageHolder): String { + val system = database.fetchSystemFromUser(ctx.message.author) + ?: return "System does not exist. Create one using `pf>system new`" + ctx.respond("`${database.getOrCreateTokenFromSystem(system.id).token}`", true) + return "Token sent in DMs" + } + + private suspend fun tokenRefresh(ctx: MessageHolder): String { + val system = database.fetchSystemFromUser(ctx.message.author) + ?: return "System does not exist. Create one using `pf>system new`" + val token = database.getOrCreateTokenFromSystem(system.id) + token.token = generateToken() + database.updateToken(token) + ctx.respond("`${token.token}`", true) + return "Token refreshed. New token sent in DMs" + } + private suspend fun delete(ctx: MessageHolder): String { val author = ctx.message.author!! database.fetchSystemFromUser(author) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt index 5ca72308..716488dd 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt @@ -313,6 +313,10 @@ abstract class Database : AutoCloseable { abstract suspend fun fetchMessage(messageId: Snowflake): ProxiedMessageRecord? abstract suspend fun fetchLatestMessage(systemId: String, channelId: Snowflake): ProxiedMessageRecord? + abstract suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord + + abstract suspend fun updateToken(token: TokenRecord) + /** * Allocates a proxy tag * @param systemId The system ID to assign it to diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt index c5eeca3e..df26f719 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt @@ -146,3 +146,8 @@ suspend inline fun Mongo.getOrCreateCollection(): MongoCollect } return getCollection() } + +fun generateToken(): String { + val alphabet: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') + return List(64) { alphabet.random() }.joinToString("") +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt index 153cca98..2c8b4494 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt @@ -62,6 +62,8 @@ import kotlin.time.Duration * "auto", "memid", * // the autoproxy type * "autoType": "autoproxy type", + * // system access token + * "token": "token", * "members": { * "memid": { * // here for redundancy @@ -458,6 +460,14 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { return messages.firstOrNull { it.channelId == channelId.value && it.systemId == systemId } } + override suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord { + return TokenRecord(systems[systemId]?.token ?: generateToken(), systemId) + } + + override suspend fun updateToken(token: TokenRecord) { + systems[token.systemId]?.token = token.token + } + override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { return systems[record.systemId]!!.proxyTags.add(record) } @@ -626,6 +636,7 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { var timestamp: OffsetDateTime? = OffsetDateTime.now(ZoneOffset.UTC), var auto: String? = null, var autoType: AutoProxyMode? = AutoProxyMode.OFF, + var token: String = generateToken(), val members: MutableMap = HashMap(), val serverSettings: MutableMap = HashMap(), @@ -678,6 +689,7 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { autoType?.let { record.autoType = it } + record.token = token record.trust = trust } @@ -694,6 +706,7 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { timestamp = record.timestamp auto = record.autoProxy autoType = record.autoType + token = record.token } fun putMember(member: JsonMemberStruct): JsonMemberStruct { diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index ebe371bf..2662dd99 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -64,6 +64,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { private lateinit var systems: KCollection private lateinit var systemSwitches: KCollection + private lateinit var systemTokens: KCollection private lateinit var systemServers: KCollection private lateinit var systemChannels: KCollection @@ -94,6 +95,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { channels = db.getOrCreateCollection() systems = db.getOrCreateCollection() + systemTokens = db.getOrCreateCollection() systemSwitches = db.getOrCreateCollection() systemServers = db.getOrCreateCollection() @@ -267,6 +269,13 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { ): ProxiedMessageRecord? = messages.find("systemId" eq systemId, "channelId" eq channelId).sort("{'creationDate':-1}").limit(1).awaitFirstOrNull() + override suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord = + systemTokens.findFirstOrNull("systemId" eq systemId) ?: TokenRecord(generateToken(), systemId) + + override suspend fun updateToken(token: TokenRecord) { + systemTokens.replaceOneById(token._id, token, upsert()).awaitFirst() + } + override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { memberProxies.insertOne(record).awaitFirst() return true diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt index bf4e056b..15a7b602 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt @@ -108,6 +108,10 @@ class NopDatabase : Database() { channelId: Snowflake ): ProxiedMessageRecord? = null + override suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord = fail("Cannot store token for $systemId.") + + override suspend fun updateToken(token: TokenRecord) = fail("Cannot store token for ${token.systemId}.") + override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean = false override suspend fun createSwitch(systemId: String, memberId: List, timestamp: Instant?): SystemSwitchRecord? = null diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt index f74dd2b6..2a62b67e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt @@ -149,6 +149,14 @@ open class ProxyDatabase(protected val proxy: T) : Database() { return proxy.fetchLatestMessage(systemId, channelId) } + override suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord { + return proxy.getOrCreateTokenFromSystem(systemId) + } + + override suspend fun updateToken(token: TokenRecord) { + return proxy.updateToken(token) + } + override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { return proxy.createProxyTag(record) } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt new file mode 100644 index 00000000..0238c670 --- /dev/null +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.database.records.misc + +import dev.proxyfox.database.records.MongoRecord +import org.bson.types.ObjectId + +class TokenRecord : MongoRecord { + override var _id: ObjectId = ObjectId() + var token: String = "" + var systemId: String = "" + + constructor() + + constructor(token: String, systemId: String) { + this.token = token + this.systemId = systemId + } +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index 9da2d237..32518eb3 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -35,6 +35,7 @@ class SystemRecord : MongoRecord { var avatarUrl: String? = null var timezone: String? = null var timestamp: OffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC) + var token: String = generateToken() /** The ID of the member that's currently being auto-proxied. */ var autoProxy: PkId? = null From fffe373b9c113fb7cd6691849847c9ee9a5d223a Mon Sep 17 00:00:00 2001 From: tibs Date: Thu, 10 Nov 2022 20:11:00 -0500 Subject: [PATCH 009/137] Fix casing --- .../kotlin/dev/proxyfox/api/models/Member.kt | 31 ++++++++++++------- .../api/models/MemberGuildSettings.kt | 21 ++++++++----- .../kotlin/dev/proxyfox/api/models/System.kt | 16 ++++++---- .../api/models/SystemGuildSettings.kt | 16 +++++++--- .../dev/proxyfox/api/routes/SwitchRoutes.kt | 1 + 5 files changed, 55 insertions(+), 30 deletions(-) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt index 99e86bc6..d8284ba4 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt @@ -12,43 +12,50 @@ import dev.proxyfox.common.fromColor import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberRecord import kotlinx.coroutines.runBlocking +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class Member( val id: String, val name: String, - val display_name: String?, + @SerialName("display_name") + val displayName: String?, val description: String?, val pronouns: String?, val color: String?, - val avatar_url: String?, - val keep_proxy: Boolean, - val auto_proxy: Boolean, - val message_count: ULong, + @SerialName("avatar_url") + val avatarUrl: String?, + @SerialName("keep_proxy") + val keepProxy: Boolean, + @SerialName("auto_proxy") + val autoProxy: Boolean, + @SerialName("message_count") + val messageCount: ULong, val created: String, val birthday: String?, val age: String?, val role: String?, - val proxy_tags : List + @SerialName("proxy_tags") + val proxyTags : List ) { companion object { fun fromRecord(member: MemberRecord) = Member( id = member.id, name = member.name, - display_name = member.displayName, + displayName = member.displayName, description = member.description, pronouns = member.pronouns, color = member.color.fromColor(), - avatar_url = member.avatarUrl, - keep_proxy = member.keepProxy, - auto_proxy = member.autoProxy, - message_count = member.messageCount, + avatarUrl = member.avatarUrl, + keepProxy = member.keepProxy, + autoProxy = member.autoProxy, + messageCount = member.messageCount, created = member.timestamp.toString(), birthday = member.birthday.toString(), age = member.age, role = member.role, - proxy_tags = runBlocking { database.fetchProxiesFromSystemAndMember(member.systemId, member.id)?.map(ProxyTag::fromRecord) ?: emptyList() } + proxyTags = runBlocking { database.fetchProxiesFromSystemAndMember(member.systemId, member.id)?.map(ProxyTag::fromRecord) ?: emptyList() } ) } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt index 0fafae86..f09d37b2 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt @@ -9,21 +9,26 @@ package dev.proxyfox.api.models import dev.proxyfox.database.records.member.MemberServerSettingsRecord +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class MemberGuildSettings( - val display_name: String?, - val avatar_url: String?, - val auto_proxy: Boolean, - val proxy_enabled: Boolean + @SerialName("display_name") + val displayName: String?, + @SerialName("avatar_url") + val avatarUrl: String?, + @SerialName("auto_proxy") + val autoProxy: Boolean, + @SerialName("proxy_enabled") + val proxyEnabled: Boolean ) { companion object { fun fromRecord(record: MemberServerSettingsRecord) = MemberGuildSettings( - display_name = record.nickname, - avatar_url = record.avatarUrl, - auto_proxy = record.autoProxy, - proxy_enabled = record.proxyEnabled + displayName = record.nickname, + avatarUrl = record.avatarUrl, + autoProxy = record.autoProxy, + proxyEnabled = record.proxyEnabled ) } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt index 0ac41b4a..7272e0a8 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt @@ -12,6 +12,7 @@ import dev.proxyfox.common.fromColor import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.TrustLevel import dev.proxyfox.database.records.system.SystemRecord +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable @@ -22,11 +23,14 @@ data class System( val tag: String?, val pronouns: String?, val color: String?, - val avatar_url: String?, + @SerialName("avatar_url") + val avatarUrl: String?, val timezone: String?, val created: String, - val auto_proxy: String?, - val auto_type: AutoProxyMode, + @SerialName("autoProxy") + val autoProxy: String?, + @SerialName("autoType") + val autoType: AutoProxyMode, val trust: Map ) { companion object { @@ -37,11 +41,11 @@ data class System( tag = system.tag, pronouns = system.pronouns, color = system.color.fromColor(), - avatar_url = system.avatarUrl, + avatarUrl = system.avatarUrl, timezone = system.timezone, created = system.timestamp.toString(), - auto_proxy = system.autoProxy, - auto_type = system.autoType, + autoProxy = system.autoProxy, + autoType = system.autoType, trust = system.trust ) } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt index b36e4e9b..08bf1d84 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt @@ -10,15 +10,23 @@ package dev.proxyfox.api.models import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.system.SystemServerSettingsRecord +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class SystemGuildSettings(val proxy_enabled: Boolean, val auto_proxy: String?, val auto_proxy_mode: AutoProxyMode) { +data class SystemGuildSettings( + @SerialName("proxy_enabled") + val proxyEnabled: Boolean, + @SerialName("auto_proxy") + val autoProxy: String?, + @SerialName("auto_proxy_mode") + val autoProxyMode: AutoProxyMode +) { companion object { fun fromRecord(record: SystemServerSettingsRecord) = SystemGuildSettings( - proxy_enabled = record.proxyEnabled, - auto_proxy = record.autoProxy, - auto_proxy_mode = record.autoProxyMode + proxyEnabled = record.proxyEnabled, + autoProxy = record.autoProxy, + autoProxyMode = record.autoProxyMode ) } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt index 6c2b1b28..f15b317f 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt @@ -26,6 +26,7 @@ fun Route.switchRoutes() { } route("/systems/{id}/fronters") { + install(AuthenticationPlugin) get { val id = call.parameters["id"] ?: return@get call.respond("System not found") call.respond(database.fetchFrontingMembersFromSystem(id)?.map(Member.Companion::fromRecord) ?: emptyList()) From 5adaa3987b1af66a8fa5eac0669e5256aff8c0b5 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 14 Nov 2022 23:56:25 -0600 Subject: [PATCH 010/137] Start move to proxyfox-command + start working on slash commands --- build.gradle.kts | 1 + gradle/libs.versions.toml | 4 +- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 27 +- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 34 +- .../dev/proxyfox/bot/command/Commands.kt | 32 + .../proxyfox/bot/command/MemberCommands.kt | 1461 ++++++++++++----- .../bot/command/context/DiscordContext.kt | 50 + .../command/context/DiscordMessageContext.kt | 86 + .../context/InteractionCommandContext.kt | 129 ++ .../bot/command/node/AttachmentNode.kt | 29 + 10 files changed, 1430 insertions(+), 423 deletions(-) create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt diff --git a/build.gradle.kts b/build.gradle.kts index 9d194ea4..bba16222 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,6 +49,7 @@ allprojects { maven("https://libraries.minecraft.net/") maven("https://oss.sonatype.org/content/repositories/snapshots") maven("https://jitpack.io") + maven("https://maven.proxyfox.dev") } dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6c948cf5..e0cd20f8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ logback = "1.2.11" kord = "0.8.0-M16" kotlin = "1.7.10" kotlinx_coroutines = "1.6.4" +proxyfox_command = "1.1" # Database-specific postgres = "42.3.3" @@ -23,6 +24,7 @@ licenser = "1.1.2" guava = { module = "com.google.guava:guava", version.ref = "guava" } kord = { module = "dev.kord:kord-core", version.ref = "kord" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } +proxyfox_command = { module = "dev.proxyfox:proxyfox-command", version.ref = "proxyfox_command" } kotlin_stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlinx_coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx_coroutines" } @@ -39,7 +41,7 @@ mockk = { module = "io.mockk:mockk", version.ref = "mockk" } kotlinx_coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx_coroutines" } [bundles] -base = ["guava", "logback", "kotlin_stdlib", "kotlinx_coroutines_core", "kord"] +base = ["guava", "logback", "kotlin_stdlib", "kotlinx_coroutines_core", "kord", "proxyfox_command"] test = ["testng", "kotlinx_coroutines_test", "mockk"] database = ["gson", "kmongo_base", "kmongo_coroutine", "kmongo_async"] diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 6756c64b..b710d561 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -30,9 +30,17 @@ import dev.kord.core.on import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent import dev.kord.gateway.builder.Shards +import dev.kord.rest.builder.interaction.BaseInputChatBuilder +import dev.kord.rest.builder.interaction.string +import dev.kord.rest.builder.interaction.subCommand import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException +import dev.proxyfox.bot.command.Commands +import dev.proxyfox.bot.command.MemberCommands.registerMemberCommands +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.command.node.CommandNode +import dev.proxyfox.command.node.builtin.LiteralNode import dev.proxyfox.common.* import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberRecord @@ -113,10 +121,18 @@ suspend fun login() { } } - kord.registerMessageCommands() + printStep("Registering slash commands", 2) + + kord.registerApplicationCommands() kord.on { onInteract() } + kord.on { + onInteract() + } + kord.on { + onInteract() + } var initialized = false kord.on { @@ -149,11 +165,12 @@ suspend fun login() { } } -suspend fun Kord.registerMessageCommands() { - createGlobalMessageCommand("Delete Message") {} - createGlobalMessageCommand("Fetch Message Info") {} - createGlobalMessageCommand("Ping Message Author") {} +suspend fun Kord.registerApplicationCommands() { + createGlobalMessageCommand("Delete Message") + createGlobalMessageCommand("Fetch Message Info") + createGlobalMessageCommand("Ping Message Author") createGlobalMessageCommand("Edit Message") + registerMemberCommands() } suspend fun updatePresence() { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index 23c6e99f..940a2876 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -8,10 +8,7 @@ package dev.proxyfox.bot -import dev.kord.common.entity.MessageType -import dev.kord.common.entity.Permission -import dev.kord.common.entity.Permissions -import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.* import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.createMessage import dev.kord.core.cache.data.AttachmentData @@ -20,10 +17,16 @@ import dev.kord.core.entity.Attachment import dev.kord.core.entity.Embed import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.* +import dev.kord.core.entity.interaction.SubCommand import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.event.message.MessageUpdateEvent import dev.kord.core.event.message.ReactionAddEvent import dev.kord.rest.builder.message.create.embed +import dev.proxyfox.bot.command.Commands +import dev.proxyfox.bot.command.MemberCommands +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.context.DiscordMessageContext +import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.string.parser.parseString import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil @@ -34,6 +37,7 @@ import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord +import org.litote.kmongo.json import org.slf4j.LoggerFactory val prefixRegex = Regex("^(?:(<@!?${kord.selfId}>)|pf[>;!:])\\s*", RegexOption.IGNORE_CASE) @@ -61,8 +65,12 @@ suspend fun MessageCreateEvent.onMessageCreate() { if (contentWithoutRegex.isBlank() && matcher.start(1) >= 0) { channel.createMessage("Hi, I'm ProxyFox! My prefix is `pf>`.") } else { + // Currently running both parsers for testing. TODO: Remove legacy parser // Run the command - val output = parseString(contentWithoutRegex, message) ?: return + val output = parseString(contentWithoutRegex, message) ?: run { + Commands.parser.parse(DiscordMessageContext(message, contentWithoutRegex) as DiscordContext) + return + } // Send output message if exists if (output.isNotBlank()) channel.createMessage(output) @@ -287,6 +295,18 @@ suspend fun ReactionAddEvent.onReactionAdd() { } } -fun GlobalMessageCommandInteractionCreateEvent.onInteract() { +suspend fun GlobalMessageCommandInteractionCreateEvent.onInteract() { + +} + +suspend fun ChatInputCommandInteractionCreateEvent.onInteract() { + when (interaction.invokedCommandName) { + "member" -> { + val command = interaction.command as? SubCommand ?: return + MemberCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } + } + else -> { -} \ No newline at end of file + } + } +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 17b3920a..396808a3 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -8,6 +8,9 @@ package dev.proxyfox.bot.command +import dev.kord.rest.builder.interaction.* +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.command.CommandParser import dev.proxyfox.common.printStep /** @@ -16,6 +19,8 @@ import dev.proxyfox.common.printStep * */ object Commands { + val parser = CommandParser>() + suspend fun register() { printStep("Registering commands",1) SystemCommands.register() @@ -23,4 +28,31 @@ object Commands { SwitchCommands.register() MiscCommands.register() } +} + +fun SubCommandBuilder.guild() { + int("server-id", "The ID for the server") { + required = false + } +} +fun SubCommandBuilder.name(name: String = "name", required: Boolean = true) { + string(name, "The $name to use") { + this.required = required + } +} +fun SubCommandBuilder.avatar(name: String = "avatar") { + attachment(name, "The $name to set") { + required = false + } +} +fun SubCommandBuilder.bool(name: String, desc: String) { + boolean(name, desc) { + required = false + } +} +fun SubCommandBuilder.raw() = bool("raw", "Whether to fetch the raw data") +fun SubCommandBuilder.clear() = bool("clear", "Whether to clear the data") +fun SubCommandBuilder.member() = name("member") +fun GlobalChatInputCreateBuilder.access(type: String, name: String, builder: SubCommandBuilder.() -> Unit) { + subCommand(name, "Accesses the $type's $name", builder) } \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 1fd494c7..0edc4874 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -6,118 +6,848 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +@file:Suppress("INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING") + package dev.proxyfox.bot.command import dev.kord.common.entity.ButtonStyle +import dev.kord.common.entity.Snowflake +import dev.kord.core.Kord +import dev.kord.rest.builder.interaction.* +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.context.InteractionCommandContext +import dev.proxyfox.bot.command.context.guild +import dev.proxyfox.bot.command.context.runs +import dev.proxyfox.bot.command.node.attachment +import dev.proxyfox.bot.kord import dev.proxyfox.bot.kordColor import dev.proxyfox.bot.member import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.TimedYesNoPrompt -import dev.proxyfox.bot.string.dsl.greedy -import dev.proxyfox.bot.string.dsl.literal -import dev.proxyfox.bot.string.dsl.string -import dev.proxyfox.bot.string.dsl.unixLiteral -import dev.proxyfox.bot.string.parser.MessageHolder -import dev.proxyfox.bot.string.parser.registerCommand import dev.proxyfox.bot.toKtInstant +import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.* import dev.proxyfox.database.database import dev.proxyfox.database.displayDate +import dev.proxyfox.database.records.member.MemberProxyTagRecord +import dev.proxyfox.database.records.member.MemberRecord +import dev.proxyfox.database.records.member.MemberServerSettingsRecord +import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.tryParseLocalDate +import java.time.LocalDate /** * Commands for accessing and changing system settings * @author Oliver * */ object MemberCommands { - suspend fun register() { - printStep("Registering commands", 2) - registerCommand(literal(arrayOf("member", "m"), ::empty) { - string("member", ::access) { - literal(arrayOf("rename", "name"), ::renameEmpty) { - greedy("name", ::rename) + var interactionExecutors: HashMap Boolean> = hashMapOf() + + fun SubCommandBuilder.runs(action: suspend InteractionCommandContext.() -> Boolean) { + interactionExecutors[name] = action + } + + suspend fun Kord.registerMemberCommands() { + createGlobalChatInputCommand("member", "Manage or create a system member!") { + subCommand("create", "Create a member") { + name() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val name = value.interaction.command.strings["name"]!! + + create(this, system!!, name) } + } + subCommand("delete", "Delete a member") { + member() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false - literal(arrayOf("nickname", "nick", "displayname", "dn"), ::nicknameEmpty) { - unixLiteral("clear", ::nicknameClear) - greedy("name", ::nickname) + delete(this, member!!) } + } + subCommand("fetch", "Fetches the member's card") { + member() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false - literal(arrayOf("servername", "servernick", "sn"), ::servernameEmpty) { - unixLiteral("clear", ::servernameClear) - greedy("name", ::servername) + access(this, system, member!!) } - literal(arrayOf("description", "desc", "d"), ::descriptionEmpty) { - unixLiteral("clear", ::descriptionClear) - unixLiteral("raw", ::descriptionRaw) - greedy("desc", ::description) + } + access("member", "name") { + member() + name(required = false) + raw() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val name = value.interaction.command.strings["name"] + val raw = value.interaction.command.booleans["raw"] ?: false + + rename(this, member!!, name, raw) } + } + access("member", "nickname") { + member() + name(required = false) + raw() + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val name = value.interaction.command.strings["name"] + val raw = value.interaction.command.booleans["raw"] ?: false + val clear = value.interaction.command.booleans["clear"] ?: false - literal(arrayOf("avatar", "pfp"), ::avatar) { - unixLiteral("clear", ::avatarClear) - greedy("avatar", ::avatarLinked) + nickname(this, member!!, name, raw, clear) } + } + access("member", "servernick") { + member() + name() + guild() + raw() + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id + guildId ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val name = value.interaction.command.strings["name"] + val raw = value.interaction.command.booleans["raw"] ?: false + val clear = value.interaction.command.booleans["clear"] ?: false - literal(arrayOf("serveravatar", "serverpfp", "sp", "sa"), ::serverAvatar) { - unixLiteral("clear", ::serverAvatarClear) - greedy("avatar", ::serverAvatarLinked) + servername(this, serverMember!!, name, raw, clear) } + } + access("member", "description") { + member() + raw() + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val desc = value.interaction.command.strings["description"] + val raw = value.interaction.command.booleans["raw"] ?: false + val clear = value.interaction.command.booleans["clear"] ?: false - literal(arrayOf("autoproxy", "ap"), ::apEmpty) { - literal(arrayOf("disable", "off", "false", "0"), ::apDisable) - literal(arrayOf("enable", "on", "true", "1"), ::apEnable) + description(this, member!!, desc, raw, clear) } + } + access("member", "avatar") { + member() + avatar() + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val avatar = value.interaction.command.attachments["avatar"]?.data?.url + val clear = value.interaction.command.booleans["clear"] ?: false - literal(arrayOf("proxy", "p"), ::proxyEmpty) { - literal("remove", ::removeProxyEmpty) { - greedy("proxy", ::removeProxy) + avatar(this, member!!, avatar, clear) + } + } + access("member", "serveravatar") { + member() + avatar() + guild() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id + guildId ?: run { + respondFailure("Command not ran in server.") + return@runs false } - literal("add", ::proxyEmpty) { - greedy("proxy", ::proxy) + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false } - greedy("proxy", ::proxy) + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + + val avatar = value.interaction.command.attachments["avatar"]?.data?.url + val clear = value.interaction.command.booleans["clear"] ?: false + + serverAvatar(this, serverMember!!, avatar, clear) } + } + access("member", "pronouns") { + member() + name("pronouns", required = false) + raw() + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val pro = value.interaction.command.strings["pronouns"] + val raw = value.interaction.command.booleans["raw"] ?: false + val clear = value.interaction.command.booleans["clear"] ?: false - literal("pronouns", ::pronounsEmpty) { - unixLiteral("clear", ::pronounsClear) - unixLiteral("raw", ::pronounsRaw) - greedy("pronouns", ::pronouns) + pronouns(this, member!!, pro, raw, clear) } + } + access("member", "color") { + member() + name("color", required = false) + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val color = value.interaction.command.strings["color"] - literal("color", ::colorEmpty) { - greedy("color", ::color) + color(this, member!!, color?.toColor()) } + } + access("member", "birthday") { + member() + name("birthday", required = false) + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val birthday = value.interaction.command.strings["birthday"] + val clear = value.interaction.command.booleans["clear"] ?: false - literal(arrayOf("birthday", "bd"), ::birthEmpty) { - unixLiteral("clear", ::birthClear) - greedy("birthday", ::birth) + birthday(this, member!!, tryParseLocalDate(birthday)?.first, clear) } + } + subCommand("proxy-add", "Adds a proxy") { + member() + name("prefix", required = false) + name("suffix", required = false) + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val prefix = value.interaction.command.strings["prefix"] + val suffix = value.interaction.command.strings["suffix"] + val proxy = if (prefix == null && suffix == null) null else Pair(prefix, suffix) - literal(arrayOf("delete", "remove", "del"), ::delete) + proxy(this, member!!, proxy) + } + } + subCommand("proxy-delete", "Delete a proxy") { + member() + name("prefix", required = false) + name("suffix", required = false) + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val prefix = value.interaction.command.strings["prefix"] + val suffix = value.interaction.command.strings["suffix"] + val proxy = if (prefix == null && suffix == null) null else Pair(prefix, suffix) + val exists = proxy != null + val proxyTag = if (exists) database.fetchProxyTagFromMessage(getUser(), "${prefix}text$suffix") else null + removeProxy(this, member!!, exists, proxyTag) + } } + access("member", "autoproxy") { + member() + bool("value", "The value to set") + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this as DiscordContext, system)) return@runs false + val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, member)) return@runs false + val value = value.interaction.command.booleans["value"] - literal(arrayOf("delete", "remove", "del"), ::deleteEmpty) { - greedy("member", ::delete) + autoproxy(this, member!!, value) + } } - literal(arrayOf("new", "n", "create"), ::createEmpty) { - greedy("name", ::create) + } + } + + suspend fun register() { + printStep("Registering member commands", 2) + //TODO: Dedupe code + Commands.parser.literal("member", "m") { + runs { + empty(this) } - }) + string("member") { getMem -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + access(this, system, member!!) + } + + literal("remame", "name") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + rename(this, member!!, null, false) + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + rename(this, member!!, null, true) + } + } + greedy("name") { getName -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + rename(this, member!!, name, false) + } + } + } + + literal("nickname", "nick", "displayname", "dn") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + nickname(this, member!!, null, false, false) + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + nickname(this, member!!, null, false, true) + } + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + nickname(this, member!!, null, true, false) + } + } + greedy("name") { getName -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val name = getName() + nickname(this, member!!, name, false, false) + } + } + } + + literal("servername", "servernick", "sn") { + guild { getGuildId -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + servername(this, serverMember!!, null, false, false) + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + servername(this, serverMember!!, null, false, true) + } + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + servername(this, serverMember!!, null, true, false) + } + } + greedy("name") { getName -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + servername(this, serverMember!!, getName(), false, false) + } + } + } + } + + literal("description", "desc", "d") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + description(this, member!!, null, false, false) + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + description(this, member!!, null, true, false) + } + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + description(this, member!!, null, false, true) + } + } + greedy("description") { getDesc -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + description(this, member!!, getDesc(), false, false) + } + } + } + + literal("avatar", "pfp") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + avatar(this, member!!, null, false) + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + avatar(this, member!!, null, true) + } + } + attachment("avatar") { getAvatar -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + avatar(this, member!!, getAvatar().url, false) + } + } + string("avatar") { getAvatar -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + avatar(this, member!!, getAvatar(), false) + } + } + } + + literal("serveravatar", "serverpfp", "sp", "sa") { + guild { getGuildId -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + serverAvatar(this, serverMember!!, null, false) + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + serverAvatar(this, serverMember!!, null, true) + } + } + attachment("avatar") { getAvatar -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + serverAvatar(this, serverMember!!, getAvatar().url, false) + } + } + greedy("avatar") { getAvatar -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + serverAvatar(this, serverMember!!, getAvatar(), false) + } + } + } + } + + literal("autoproxy", "ap") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + autoproxy(this, member!!, null) + } + // TODO: BooleanNode + literal("disable", "off", "false", "0") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + autoproxy(this, member!!, false) + } + } + literal("enable", "on", "true", "1") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + autoproxy(this, member!!, true) + } + } + } + + literal("proxy", "p") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + proxy(this, member!!, null) + } + + literal("remove", "delete") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + removeProxy(this, member!!, false, null) + } + greedy("proxy") { getProxy -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + extractProxyFromTag(this, getProxy()) ?: return@runs false + val proxy = database.fetchProxyTagFromMessage(getUser(), getProxy()) + proxy ?: run { + respondFailure("Proxy tag doesn't exist in this member.") + return@runs false + } + if (proxy.memberId != member!!.id) { + respondFailure("Proxy tag doesn't exist in this member.") + return@runs false + } + removeProxy(this, member, false, proxy) + } + } + } + + literal("add", "create") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + proxy(this, member!!, null) + } + + greedy("proxy") { getProxy -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false + proxy(this, member!!, proxy) + } + } + } + + greedy("proxy") { getProxy -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false + proxy(this, member!!, proxy) + } + } + } + + literal("pronouns") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + pronouns(this, member!!, null, false, false) + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + pronouns(this, member!!, null, false, true) + } + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + pronouns(this, member!!, null, true, false) + } + } + greedy("pronouns") { getPronouns -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + pronouns(this, member!!, getPronouns(), false, false) + } + } + } + + literal("color", "c") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + color(this, member!!, null) + } + greedy("color") { getColor -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + color(this, member!!, getColor().toColor()) + } + } + } + + literal("birthday","bday","birth","bd") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + birthday(this, member!!, null, false) + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + birthday(this, member!!, null, true) + } + } + greedy("birthday") { getBirthday -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + birthday(this, member!!, tryParseLocalDate(getBirthday())?.first, false) + } + } + } + literal("delete", "remove", "del") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + delete(this, member!!) + } + } + } + literal("delete", "remove", "del") { + runs { + delete(this, null) + } + greedy("member") { getMem -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + delete(this, member!!) + } + } + } + literal("create", "add", "new", "c") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + create(this, system!!, null) + } + greedy("member") { getMem -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + create(this, system!!, getMem()) + } + } + } + } + } + + suspend fun checkSystem(ctx: DiscordContext, system: SystemRecord?): Boolean { + system ?: run { + ctx.respondFailure("System does not exist. Create one using a slash command or `pf>system new`") + return false + } + return true } - private fun empty(ctx: MessageHolder): String { - return "Make sure to provide a member command!" + suspend fun checkMember(ctx: DiscordContext, member: MemberRecord?): Boolean { + member ?: run { + ctx.respondFailure("Member does not exist. Create one using a slash command or `pf>member new`") + return false + } + return true } - private suspend fun access(ctx: MessageHolder): String { - val guild = ctx.message.getGuildOrNull() - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" + suspend fun empty(ctx: DiscordContext): Boolean { + ctx.respondWarning("Make sure to provide a member command!") + return false + } + + suspend fun access(ctx: DiscordContext, system: SystemRecord, member: MemberRecord): Boolean { + val guild = ctx.getGuild() val settings = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) - ctx.respond { + ctx.respondEmbed { val systemName = system.name ?: system.id author { name = member.displayName?.let { "$it (${member.name})\u2007•\u2007$systemName" } ?: "${member.name}\u2007•\u2007$systemName" @@ -175,425 +905,338 @@ object MemberCommands { } timestamp = member.timestamp.toKtInstant() } - return "" + return true } - private suspend fun renameEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - return "Member's name is `${member.name}`" - } + suspend fun rename(ctx: DiscordContext, member: MemberRecord, name: String?, raw: Boolean): Boolean { + name ?: run { + if (raw) + ctx.respondPlain("`${member.name}`") + else ctx.respondSuccess("Member's name is `${member.name}`!") + + return true + } - private suspend fun rename(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.name = ctx.params["name"]!![0] + member.name = name database.updateMember(member) - return "Updated member's name!" - } - private suspend fun nicknameEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - return if (member.displayName != null) - "Member's display name is `${member.displayName}`" - else "Member doesn't have a display name." - } + ctx.respondSuccess("Member's name is now `$name`!") - private suspend fun nickname(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.displayName = ctx.params["name"]!![0] - database.updateMember(member) - return "Member displayname updated!" + return true } - private suspend fun nicknameClear(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.displayName = null + suspend fun nickname(ctx: DiscordContext, member: MemberRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { + if (clear) { + member.displayName = null + database.updateMember(member) + + ctx.respondSuccess("Member display name cleared!") + + return true + } + + name ?: run { + member.displayName ?: run { + ctx.respondWarning("Member doesn't have a display name.") + return true + } + + if (raw) + ctx.respondPlain("`${member.displayName}`") + else ctx.respondSuccess("Member's display name is `${member.displayName}`!") + + return true + } + + member.displayName = name database.updateMember(member) - return "Member displayname cleared!" - } - private suspend fun servernameEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(ctx.message.getGuild(), system.id, member.id)!! - return if (serverMember.nickname != null) - "Member's server nickname is `${serverMember.nickname}`" - else "Member doesn't have a server nickname" - } + ctx.respondSuccess("Member's display name is now `$name`!") - private suspend fun servername(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(ctx.message.getGuild(), system.id, member.id)!! - serverMember.nickname = ctx.params["name"]!![0] - database.updateMemberServerSettings(serverMember) - return "Member's server nickname updated!" + return true } - private suspend fun servernameClear(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(ctx.message.getGuild(), system.id, member.id)!! - serverMember.nickname = null + suspend fun servername(ctx: DiscordContext, serverMember: MemberServerSettingsRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { + if (clear) { + serverMember.nickname = null + database.updateMemberServerSettings(serverMember) + + ctx.respondSuccess("Member's server name cleared!") + return true + } + + name ?: run { + serverMember.nickname ?: run { + ctx.respondWarning("Member doesn't have a server nickname.") + + return true + } + + if (raw) + ctx.respondPlain("`${serverMember.nickname}`") + else ctx.respondSuccess("Member's server nickname is `${serverMember.nickname}`!") + + return true + } + + serverMember.nickname = name database.updateMemberServerSettings(serverMember) - return "Member's server nickname removed!" - } - private suspend fun descriptionEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - return if (member.description != null) - "Member's description is ${member.description}" - else "Member doesn't have a description" - } + ctx.respondSuccess("Member's server nickname is now $name!") - private suspend fun descriptionRaw(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - return member.description?.let { "```md\n$it```" } ?: "There's no description set." + return true } - private suspend fun description(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.description = ctx.params["desc"]!![0] - database.updateMember(member) - return "Member description updated!" - } + suspend fun description(ctx: DiscordContext, member: MemberRecord, description: String?, raw: Boolean, clear: Boolean): Boolean { + if (clear) { + member.description = null + database.updateMember(member) + ctx.respondSuccess("Member's description cleared!") + return true + } - private suspend fun descriptionClear(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.description = null - database.updateMember(member) - return "Member description cleared!" - } + description ?: run { + member.description ?: run { + ctx.respondWarning("Member has no description set") + return true + } - private suspend fun avatarLinked(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.avatarUrl = ctx.params["avatar"]!![0] - database.updateMember(member) - return "Member avatar updated!" - } + if (raw) + ctx.respondPlain("```md\n${member.description}```") + else ctx.respondSuccess("Member's description is ${member.description}") + + return true + } - private suspend fun avatar(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - val attachments = ctx.message.attachments - if (attachments.isEmpty()) - return if (member.avatarUrl != null) - member.avatarUrl!! - else "Member doesn't have an avatar" - member.avatarUrl = attachments.first().url + member.description = description database.updateMember(member) - return "Member avatar updated!" + ctx.respondSuccess("Member description updated!") + + return true } - private suspend fun avatarClear(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.avatarUrl = null + suspend fun avatar(ctx: DiscordContext, member: MemberRecord, avatar: String?, clear: Boolean): Boolean { + if (clear) { + member.avatarUrl = null + database.updateMember(member) + ctx.respondSuccess("Member's avatar cleared!") + return true + } + + avatar ?: run { + member.avatarUrl ?: run { + ctx.respondWarning("Member doesn't have an avatar set.") + return true + } + + ctx.respondEmbed { + image = member.avatarUrl + } + return true + } + + member.avatarUrl = avatar database.updateMember(member) - return "Member avatar cleared!" - } + ctx.respondSuccess("Member's avatar updated!") - private suspend fun serverAvatarLinked(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(ctx.message.getGuild(), system.id, member.id)!! - serverMember.avatarUrl = ctx.params["avatar"]!![0] - database.updateMemberServerSettings(serverMember) - return "Member server avatar updated!" + return true } - private suspend fun serverAvatar(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(ctx.message.getGuild(), system.id, member.id)!! - val attachments = ctx.message.attachments - if (attachments.isEmpty()) - return if (serverMember.avatarUrl != null) - serverMember.avatarUrl!! - else "Member doesn't have a server avatar" - serverMember.avatarUrl = attachments.first().url - database.updateMemberServerSettings(serverMember) - return "Member server avatar updated!" - } + suspend fun serverAvatar(ctx: DiscordContext, serverMember: MemberServerSettingsRecord, avatar: String?, clear: Boolean): Boolean { + if (clear) { + serverMember.avatarUrl = null + database.updateMemberServerSettings(serverMember) + ctx.respondSuccess("Member's server avatar cleared!") + return true + } - private suspend fun serverAvatarClear(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(ctx.message.getGuild(), system.id, member.id)!! - serverMember.avatarUrl = null + avatar ?: run { + serverMember.avatarUrl ?: run { + ctx.respondWarning("Member doesn't have a server avatar set.") + return true + } + + ctx.respondEmbed { + image = serverMember.avatarUrl + } + return true + } + + serverMember.avatarUrl = avatar database.updateMemberServerSettings(serverMember) - return "Member server avatar cleared!" - } + ctx.respondSuccess("Member's server avatar updated!") - private suspend fun removeProxyEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - return "Please provide a proxy tag to remove" + return true } - private suspend fun removeProxy(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - val proxy = ctx.params["proxy"]!![0] - val proxyTag = database.fetchProxyTagFromMessage(ctx.message.author, proxy) - ?: return "Proxy tag doesn't exist in this member" - if (proxyTag.memberId != member.id) return "Proxy tag doens't exist in this member" - database.dropProxyTag(proxyTag) - return "Proxy removed." - } + suspend fun removeProxy(ctx: DiscordContext, member: MemberRecord, exists: Boolean, proxy: MemberProxyTagRecord?): Boolean { + if (!exists) { + ctx.respondWarning("Please provide a proxy tag to remove.") + return true + } - private suspend fun apEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - return "AutoProxy for ${member.showDisplayName()} is set to ${if (member.autoProxy) "on" else "off"}" - } + proxy ?: run { + ctx.respondFailure("Proxy tag doesn't exist in this member.") + return false + } - private suspend fun apEnable(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - database.updateMember(member.apply { autoProxy = true }) - return "Enabled front & latch autproxy for ${member.showDisplayName()}." - } + if (proxy.memberId != member.id) { + ctx.respondFailure("Proxy tag doesn't exist in this member.") + return false + } + + database.dropProxyTag(proxy) + ctx.respondSuccess("Proxy removed!") - private suspend fun apDisable(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - database.updateMember(member.apply { autoProxy = false }) - return "Disabled front & latch autproxy for ${member.showDisplayName()}." + return true } - private suspend fun proxyEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - ctx.respond { - member(member, ctx.message.getGuildOrNull()?.id?.value ?: 0UL) - title = "${member.name}'s proxy tags" - description = database.fetchProxiesFromSystemAndMember(system.id, member.id).run { - if (isNullOrEmpty()) - "${member.name} has no tags set." - else - joinToString( - separator = "\n", - limit = 20, - truncated = "\n\u2026" - ) { "``$it``" } - } + suspend fun autoproxy(ctx: DiscordContext, member: MemberRecord, enabled: Boolean?): Boolean { + enabled ?: run { + ctx.respondSuccess("AutoProxy for ${member.showDisplayName()} is set to ${if (member.autoProxy) "on" else "off"}!") + return true } - return "" + database.updateMember(member.apply { autoProxy = enabled }) + ctx.respondSuccess("${if (enabled) "Enabled" else "Disabled"} front & latch autproxy for ${member.showDisplayName()}!") + return true } - private suspend fun proxy(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - val proxy = ctx.params["proxy"]!![0] - if (!proxy.contains("text")) return "Given proxy tag does not contain `text`" + suspend fun extractProxyFromTag(ctx: DiscordContext, proxy: String): Pair? { + if (!proxy.contains("text")) { + ctx.respondFailure("Given proxy tag does not contain `text`.") + return null + } val prefix = proxy.substring(0, proxy.indexOf("text")) val suffix = proxy.substring(4 + prefix.length, proxy.length) - if (prefix.isEmpty() && suffix.isEmpty()) return "Proxy tag must contain either a prefix or a suffix" - database.createProxyTag(system.id, member.id, prefix, suffix) - ?: return "Proxy tag already exists in this system" - return "Proxy tag created!" + if (prefix.isEmpty() && suffix.isEmpty()) { + ctx.respondFailure("Proxy tag must contain either a prefix or a suffix.") + return null + } + return Pair(prefix, suffix) } - private suspend fun pronounsEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - return if (member.pronouns == null) - "Member does not have pronouns set" - else "Member's pronouns are ${member.pronouns}" - } + suspend fun proxy(ctx: DiscordContext, member: MemberRecord, proxy: Pair?): Boolean { + proxy ?: run { + ctx.respondEmbed { + member(member, ctx.getGuild()?.id?.value ?: 0UL) + title = "${member.name}'s proxy tags" + description = database.fetchProxiesFromSystemAndMember(member.systemId, member.id).run { + if (isNullOrEmpty()) + "${member.name} has no tags set." + else + joinToString( + separator = "\n", + limit = 20, + truncated = "\n\u2026" + ) { "``$it``" } + } + } - private suspend fun pronounsRaw(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - return if (member.pronouns == null) - "Member does not have pronouns set" - else "`${member.pronouns}`" - } + return true + } - private suspend fun pronouns(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.pronouns = ctx.params["pronouns"]!![0] - database.updateMember(member) - return "Member's pronouns updated!" + database.createProxyTag(member.systemId, member.id, proxy.first, proxy.second) ?: run { + ctx.respondFailure("Proxy tag already exists in this system.") + return false + } + ctx.respondSuccess("Proxy tag `${proxy.first}text${proxy.second}` created!") + return true } - private suspend fun pronounsClear(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.pronouns = null - database.updateMember(member) - return "Member's pronouns cleared!" - } + suspend fun pronouns(ctx: DiscordContext, member: MemberRecord, pronouns: String?, raw: Boolean, clear: Boolean): Boolean { + if (clear) { + member.pronouns = null + database.updateMember(member) + ctx.respondSuccess("Member's pronouns cleared!") + return true + } - private suspend fun colorEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - return "Member's color is `${member.color.fromColor()}`" + pronouns ?: run { + member.pronouns ?: run { + ctx.respondWarning("Member has no pronouns set") + return true + } - } + if (raw) + ctx.respondPlain("`${member.pronouns}`") + else ctx.respondSuccess("Member's pronouns are ${member.pronouns}") + + return true + } - private suspend fun color(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.color = ctx.params["color"]!![0].toColor() + member.pronouns = pronouns database.updateMember(member) - return "Member's color updated!" + ctx.respondSuccess("Member's pronouns are now $pronouns!") + return true } - private suspend fun birthEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - return if (member.birthday == null) - "Member does not have a birthday reigstered" - else "Member's birthday is ${member.birthday}" - } + suspend fun color(ctx: DiscordContext, member: MemberRecord, color: Int?): Boolean { + color ?: run { + ctx.respondSuccess("Member's color is `${member.color.fromColor()}`") + return true + } - private suspend fun birth(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.birthday = tryParseLocalDate(ctx.params["birthday"]!![0])?.first + member.color = color database.updateMember(member) - return "Member's birthday updated!" + ctx.respondSuccess("Member's color is now `${color.fromColor()}!") + return true } - private suspend fun birthClear(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - member.birthday = null + suspend fun birthday(ctx: DiscordContext, member: MemberRecord, birthday: LocalDate?, clear: Boolean): Boolean { + if (clear) { + member.birthday = null + database.updateMember(member) + ctx.respondSuccess("Member's birthday cleared!") + return true + } + + birthday ?: run { + member.birthday ?: run { + ctx.respondWarning("Member does not have a birthday.") + return true + } + ctx.respondSuccess("Member's birthday is ${member.birthday}!") + return true + } + + member.birthday = birthday database.updateMember(member) - return "Member's birthday cleared!" + ctx.respondSuccess("Member's birthday is now $birthday!") + return true } - private suspend fun delete(ctx: MessageHolder): String { - val author = ctx.message.author!! - val system = database.fetchSystemFromUser(author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" + suspend fun delete(ctx: DiscordContext, member: MemberRecord?): Boolean { + member ?: run { + ctx.respondFailure("Make sure to provide the name of the member to delete!") + return false + } TimedYesNoPrompt.build( - runner = author.id, - channel = ctx.message.channel, + runner = ctx.getUser()!!.id, + channel = ctx.getChannel(), message = "Are you sure you want to delete member `${member.asString()}`?\n" + "Their data will be lost forever (A long time!)", yes = Button("Delete Member", Button.wastebasket, ButtonStyle.Danger) { - database.dropMember(system.id, member.id) + database.dropMember(member.systemId, member.id) content = "Member deleted" }, ) - return "" + return true } - private suspend fun deleteEmpty(ctx: MessageHolder): String { - database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return "Make sure to tell me which member you want to delete!" - } - - private suspend fun createEmpty(ctx: MessageHolder): String { - database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return "Make sure to provide a name for the new member!" - } + suspend fun create(ctx: DiscordContext, system: SystemRecord, name: String?, ): Boolean { + name ?: run { + ctx.respondFailure("Make sure to provide a name for the new member!") + return false + } - private suspend fun create(ctx: MessageHolder): String { - val message = ctx.message - val author = message.author!! - val system = database.fetchSystemFromUser(author) - ?: return "System does not exist. Create one using `pf>system new`" - val name = ctx.params["name"]!![0] val member = database.fetchMemberFromSystemAndName(system.id, name, false) if (member != null) { TimedYesNoPrompt.build( - runner = author.id, - channel = ctx.message.channel, + runner = ctx.getUser()!!.id, + channel = ctx.getChannel(), message = "You already have a member named \"${member.name}\" (`${member.id}`)." + "\nDo you want to create another member with the same name?", yes = "Create $name" to { @@ -607,14 +1250,12 @@ object MemberCommands { ) } else { val newMember = database.createMember(system.id, name) - ctx.respond( - if (newMember != null) { - "Member created with ID `${newMember.id}`." - } else { - "Something went wrong while creating your member. Try again?" - } - ) + newMember ?: run { + ctx.respondFailure("Something went wrong while creating your member. Try again?") + return false + } + ctx.respondSuccess("Member created with ID `${newMember.id}`.") } - return "" + return true } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt new file mode 100644 index 00000000..f305bb2a --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -0,0 +1,50 @@ +package dev.proxyfox.bot.command.context + +import dev.kord.common.entity.Permission +import dev.kord.common.entity.Snowflake +import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.entity.Attachment +import dev.kord.core.entity.Guild +import dev.kord.core.entity.Member +import dev.kord.core.entity.User +import dev.kord.rest.NamedFile +import dev.kord.rest.builder.message.EmbedBuilder +import dev.proxyfox.bot.kord +import dev.proxyfox.command.CommandContext +import dev.proxyfox.command.NodeActionParam +import dev.proxyfox.command.NodeHolder +import dev.proxyfox.command.node.CommandNode +import dev.proxyfox.command.node.builtin.int + +abstract class DiscordContext(override val value: T) : CommandContext() { + abstract fun getAttachment(): Attachment? + abstract suspend fun getChannel(private: Boolean = false): MessageChannelBehavior + abstract suspend fun getGuild(): Guild? + abstract suspend fun getUser(): User? + abstract suspend fun getMember(): Member? + abstract suspend fun respondFiles(text: String? = null, vararg files: NamedFile): T + abstract suspend fun respondEmbed(private: Boolean = false, text: String? = null, embed: suspend EmbedBuilder.() -> Unit): T + abstract suspend fun tryDeleteTrigger(reason: String? = null) + + suspend fun hasRequired(permission: Permission): Boolean { + val author = getMember() ?: return false + return author.getPermissions().contains(permission) + } +} + +// Get a DiscordContext. +fun > CommandNode.runs(action: suspend DiscordContext.() -> Boolean) { + executes(action as suspend CommandContext.() -> Boolean) +} + +suspend fun> CommandNode.guild(action: NodeActionParam) { + action { + val ctx = this as? DiscordContext ?: return@action null + ctx.getGuild()?.id + } + int("server") { + action { + Snowflake(it()) + } + } +} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt new file mode 100644 index 00000000..c5da0c89 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -0,0 +1,86 @@ +package dev.proxyfox.bot.command.context + +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.entity.* +import dev.kord.rest.NamedFile +import dev.kord.rest.builder.message.EmbedBuilder +import dev.proxyfox.bot.prompts.TimedYesNoPrompt +import dev.proxyfox.command.CommandContext +import dev.proxyfox.common.applyAsync +import kotlin.jvm.optionals.getOrNull + +class DiscordMessageContext(message: Message, override val command: String): DiscordContext(message) { + @OptIn(ExperimentalStdlibApi::class) + override fun getAttachment(): Attachment? { + return value.attachments.stream().findFirst().getOrNull() + } + + override suspend fun getChannel(private: Boolean): MessageChannelBehavior { + return if (private) + value.author?.getDmChannelOrNull() + ?: value.channel + else value.channel + } + + override suspend fun getGuild(): Guild? { + return value.getGuildOrNull() + } + + override suspend fun getUser(): User? { + return value.author + } + + override suspend fun getMember(): Member? { + return value.getAuthorAsMember() + } + + override suspend fun respondFiles(text: String?, vararg files: NamedFile): Message { + return getChannel(true).createMessage { + this.files.addAll(files) + } + } + + override suspend fun respondEmbed( + private: Boolean, + text: String?, + embed: suspend EmbedBuilder.() -> Unit + ): Message { + return getChannel(private).createMessage { + content = text + + embeds.add(EmbedBuilder().applyAsync(embed)) + } + } + + override suspend fun tryDeleteTrigger(reason: String?) { + if (value.getGuildOrNull() != null) value.delete(reason) + } + + override suspend fun respondPlain(text: String, private: Boolean): Message { + return getChannel(private).createMessage(text) + } + + override suspend fun respondSuccess(text: String, private: Boolean): Message { + return getChannel(private).createMessage("✅ $text") + } + + override suspend fun respondWarning(text: String, private: Boolean): Message { + return getChannel(private).createMessage("⚠️ $text") + } + + override suspend fun respondFailure(text: String, private: Boolean): Message { + return getChannel(private).createMessage("❌ $text") + } + + override suspend fun timedYesNoPrompt( + text: String, + yesAction: Pair.() -> Boolean>, + noAction: Pair.() -> Boolean>, + timeoutAction: suspend CommandContext.() -> Boolean, + private: Boolean + ) { + // TODO: Move TimedYesNoPrompt here + } + +} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt new file mode 100644 index 00000000..b9fc3828 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -0,0 +1,129 @@ +package dev.proxyfox.bot.command.context + +import dev.kord.core.behavior.channel.GuildChannelBehavior +import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.entity.Attachment +import dev.kord.core.entity.Guild +import dev.kord.core.entity.Member +import dev.kord.core.entity.User +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.rest.NamedFile +import dev.kord.rest.builder.message.EmbedBuilder +import dev.kord.rest.builder.message.create.embed +import dev.proxyfox.command.CommandContext +import kotlin.jvm.optionals.getOrNull + +class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : + DiscordContext(value) { + override val command: String = "" + + @OptIn(ExperimentalStdlibApi::class) + override fun getAttachment(): Attachment? { + return value.interaction.command.attachments.values.stream().findFirst().getOrNull() + } + + override suspend fun respondFailure(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { + if (private) + value.interaction.respondEphemeral { + content = "❌ $text" + } + else value.interaction.respondPublic { + content = "❌️ $text" + } + return value + } + + override suspend fun respondPlain(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { + if (private) + value.interaction.respondEphemeral { + content = text + } + else value.interaction.respondPublic { + content = text + } + return value + } + + override suspend fun respondSuccess(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { + if (private) + value.interaction.respondEphemeral { + content = "✅ $text" + } + else value.interaction.respondPublic { + content = "✅️ $text" + } + return value + } + + override suspend fun respondWarning(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { + if (private) + value.interaction.respondEphemeral { + content = "⚠️ $text" + } + else value.interaction.respondPublic { + content = "⚠️ $text" + } + return value + } + + override suspend fun timedYesNoPrompt( + text: String, + yesAction: Pair.() -> Boolean>, + noAction: Pair.() -> Boolean>, + timeoutAction: suspend CommandContext.() -> Boolean, + private: Boolean + ) { + } + + override suspend fun getChannel(private: Boolean): MessageChannelBehavior { + return if (private) + value.interaction.user.getDmChannelOrNull() + ?: value.interaction.channel + else value.interaction.channel + } + + override suspend fun getGuild(): Guild? { + return (value.interaction.channel as? GuildChannelBehavior)?.getGuildOrNull() + } + + override suspend fun getUser(): User { + return value.interaction.user + } + + override suspend fun getMember(): Member? { + return getGuild()?.getMemberOrNull(getUser().id) + } + + override suspend fun respondFiles(text: String?, vararg files: NamedFile): ChatInputCommandInteractionCreateEvent { + getChannel(true).createMessage { + this.files.addAll(files) + } + return value + } + + override suspend fun respondEmbed( + private: Boolean, + text: String?, + embed: suspend EmbedBuilder.() -> Unit + ): ChatInputCommandInteractionCreateEvent { + if (private) + value.interaction.respondEphemeral { + embed { + embed() + } + } + else value.interaction.respondPublic { + embed { + embed() + } + } + return value + } + + override suspend fun tryDeleteTrigger(reason: String?) { + + } +} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt new file mode 100644 index 00000000..58bb4f5c --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt @@ -0,0 +1,29 @@ +package dev.proxyfox.bot.command.node + +import dev.kord.core.entity.Attachment +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.command.NodeActionParam +import dev.proxyfox.command.NodeHolder +import dev.proxyfox.command.node.CommandNode +import dev.proxyfox.command.node.Priority + +class AttachmentNode>(override val name: String) : CommandNode() { + override val priority: Priority = Priority.SEMI_STATIC + override fun parse(str: String, ctx: C): Int { + val attachment = ctx.getAttachment() ?: return -1 + ctx[name] = attachment + return 0 + } +} + +suspend fun > NodeHolder.attachment( + name: String, + action: NodeActionParam +): CommandNode { + val node = AttachmentNode(name) + node.action { + this[name] ?: throw NullPointerException("Parameter $name for $command is null!") + } + addNode(node) + return node +} \ No newline at end of file From 36dbc571575568d7200ffbd15ba8a4e0723a356a Mon Sep 17 00:00:00 2001 From: Octal Date: Tue, 15 Nov 2022 00:00:14 -0600 Subject: [PATCH 011/137] Apply licenses --- .../kotlin/dev/proxyfox/bot/command/MemberCommands.kt | 2 -- .../dev/proxyfox/bot/command/context/DiscordContext.kt | 8 ++++++++ .../proxyfox/bot/command/context/DiscordMessageContext.kt | 8 ++++++++ .../bot/command/context/InteractionCommandContext.kt | 8 ++++++++ .../dev/proxyfox/bot/command/node/AttachmentNode.kt | 8 ++++++++ 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 0edc4874..f145d457 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -6,8 +6,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -@file:Suppress("INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING") - package dev.proxyfox.bot.command import dev.kord.common.entity.ButtonStyle diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index f305bb2a..ff86854d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.context import dev.kord.common.entity.Permission diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index c5da0c89..85769f62 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.context import dev.kord.core.behavior.channel.createMessage diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index b9fc3828..8cb56032 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.context import dev.kord.core.behavior.channel.GuildChannelBehavior diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt index 58bb4f5c..8ff50c63 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.node import dev.kord.core.entity.Attachment From ad0f08ded1e9468bb9142ff7ef95e63b9fa6537b Mon Sep 17 00:00:00 2001 From: Octal Date: Tue, 15 Nov 2022 17:33:45 -0600 Subject: [PATCH 012/137] Move SystemCommands and SwitchCommands fully to the new system + start work on MiscCommands --- gradle/libs.versions.toml | 2 +- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 13 +- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 10 + .../dev/proxyfox/bot/command/Commands.kt | 29 +- .../proxyfox/bot/command/MemberCommands.kt | 913 +++++++++--------- .../dev/proxyfox/bot/command/MiscCommands.kt | 335 ++++--- .../proxyfox/bot/command/SwitchCommands.kt | 221 +++-- .../proxyfox/bot/command/SystemCommands.kt | 705 ++++++++++---- .../bot/command/context/DiscordContext.kt | 16 + .../bot/command/node/AttachmentNode.kt | 2 +- .../kotlin/dev/proxyfox/bot/prompts/Pager.kt | 1 + .../proxyfox/bot/prompts/TimedYesNoPrompt.kt | 1 + .../main/kotlin/dev/proxyfox/common/Util.kt | 6 + 13 files changed, 1419 insertions(+), 835 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e0cd20f8..cc57ebb6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ logback = "1.2.11" kord = "0.8.0-M16" kotlin = "1.7.10" kotlinx_coroutines = "1.6.4" -proxyfox_command = "1.1" +proxyfox_command = "1.2" # Database-specific postgres = "42.3.3" diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index b710d561..484a70ee 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -38,6 +38,7 @@ import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException import dev.proxyfox.bot.command.Commands import dev.proxyfox.bot.command.MemberCommands.registerMemberCommands +import dev.proxyfox.bot.command.SystemCommands.registerSystemCommands import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.LiteralNode @@ -127,9 +128,6 @@ suspend fun login() { kord.on { onInteract() } - kord.on { - onInteract() - } kord.on { onInteract() } @@ -171,6 +169,7 @@ suspend fun Kord.registerApplicationCommands() { createGlobalMessageCommand("Ping Message Author") createGlobalMessageCommand("Edit Message") registerMemberCommands() + registerSystemCommands() } suspend fun updatePresence() { @@ -242,6 +241,14 @@ fun findUnixValue(args: Array, key: String): String? { } return null } +fun hasUnixValue(args: Array, key: String): Boolean { + for (i in args.indices) { + if (args[i].startsWith(key)) { + return true + } + } + return true +} fun OffsetDateTime.toKtInstant() = Instant.fromEpochSeconds(epochSeconds = toEpochSecond(), nanosecondAdjustment = nano) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index 940a2876..734cb357 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -24,6 +24,8 @@ import dev.kord.core.event.message.ReactionAddEvent import dev.kord.rest.builder.message.create.embed import dev.proxyfox.bot.command.Commands import dev.proxyfox.bot.command.MemberCommands +import dev.proxyfox.bot.command.SwitchCommands +import dev.proxyfox.bot.command.SystemCommands import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.DiscordMessageContext import dev.proxyfox.bot.command.context.InteractionCommandContext @@ -305,6 +307,14 @@ suspend fun ChatInputCommandInteractionCreateEvent.onInteract() { val command = interaction.command as? SubCommand ?: return MemberCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } } + "system" -> { + val command = interaction.command as? SubCommand ?: return + SystemCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } + } + "switch" -> { + val command = interaction.command as? SubCommand ?: return + SwitchCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } + } else -> { } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 396808a3..86381048 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -12,6 +12,9 @@ import dev.kord.rest.builder.interaction.* import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.command.CommandParser import dev.proxyfox.common.printStep +import dev.proxyfox.database.records.member.MemberRecord +import dev.proxyfox.database.records.system.SystemRecord +import dev.proxyfox.database.records.system.SystemSwitchRecord /** * General utilities relating to commands @@ -55,4 +58,28 @@ fun SubCommandBuilder.clear() = bool("clear", "Whether to clear the data") fun SubCommandBuilder.member() = name("member") fun GlobalChatInputCreateBuilder.access(type: String, name: String, builder: SubCommandBuilder.() -> Unit) { subCommand(name, "Accesses the $type's $name", builder) -} \ No newline at end of file +} + +suspend fun checkSystem(ctx: DiscordContext, system: SystemRecord?): Boolean { + system ?: run { + ctx.respondFailure("System does not exist. Create one using a slash command or `pf>system new`") + return false + } + return true +} + +suspend fun checkMember(ctx: DiscordContext, member: MemberRecord?): Boolean { + member ?: run { + ctx.respondFailure("Member does not exist. Create one using a slash command or `pf>member new`") + return false + } + return true +} + +suspend fun checkSwitch(ctx: DiscordContext, switch: SystemSwitchRecord?): Boolean { + switch ?: run { + ctx.respondFailure("Looks like you haven't registered any switches yet. Create one using a slash command or `pf>switch`") + return false + } + return true +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index f145d457..5dcc060c 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -23,6 +23,10 @@ import dev.proxyfox.bot.member import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.TimedYesNoPrompt import dev.proxyfox.bot.toKtInstant +import dev.proxyfox.command.NodeActionParam +import dev.proxyfox.command.NodeHolder +import dev.proxyfox.command.ParamGetter +import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.* import dev.proxyfox.database.database @@ -51,7 +55,7 @@ object MemberCommands { name() runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val name = value.interaction.command.strings["name"]!! create(this, system!!, name) @@ -61,7 +65,7 @@ object MemberCommands { member() runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -72,7 +76,7 @@ object MemberCommands { member() runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -85,7 +89,7 @@ object MemberCommands { raw() runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val name = value.interaction.command.strings["name"] @@ -101,7 +105,7 @@ object MemberCommands { clear() runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val name = value.interaction.command.strings["name"] @@ -119,7 +123,7 @@ object MemberCommands { clear() runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id @@ -143,9 +147,10 @@ object MemberCommands { member() raw() clear() + name("description", required = false) runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val desc = value.interaction.command.strings["description"] @@ -161,7 +166,7 @@ object MemberCommands { clear() runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val avatar = value.interaction.command.attachments["avatar"]?.data?.url @@ -176,7 +181,7 @@ object MemberCommands { guild() runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id @@ -203,7 +208,7 @@ object MemberCommands { clear() runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val pro = value.interaction.command.strings["pronouns"] @@ -218,7 +223,7 @@ object MemberCommands { name("color", required = false) runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val color = value.interaction.command.strings["color"] @@ -232,7 +237,7 @@ object MemberCommands { clear() runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val birthday = value.interaction.command.strings["birthday"] @@ -247,7 +252,7 @@ object MemberCommands { name("suffix", required = false) runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val prefix = value.interaction.command.strings["prefix"] @@ -263,7 +268,7 @@ object MemberCommands { name("suffix", required = false) runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val prefix = value.interaction.command.strings["prefix"] @@ -279,7 +284,7 @@ object MemberCommands { bool("value", "The value to set") runs { val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this as DiscordContext, system)) return@runs false + if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val value = value.interaction.command.booleans["value"] @@ -289,507 +294,512 @@ object MemberCommands { } } } + @JvmName("registerMemberCommands_nonext") + suspend fun > registerMemberCommands(node: CommandNode, getMem: suspend DiscordContext.() -> String) { + node.registerMemberCommands(getMem) + } + suspend fun > CommandNode.registerMemberCommands(getMem: suspend DiscordContext.() -> String) { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + access(this, system, member!!) + } - suspend fun register() { - printStep("Registering member commands", 2) - //TODO: Dedupe code - Commands.parser.literal("member", "m") { + literal("remame", "name") { runs { - empty(this) + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + rename(this, member!!, null, false) + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + rename(this, member!!, null, true) + } + } + greedy("name") { getName -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + rename(this, member!!, name, false) + } } + } - string("member") { getMem -> + literal("nickname", "nick", "displayname", "dn") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + nickname(this, member!!, null, false, false) + } + unixLiteral("clear", "remove") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - access(this, system, member!!) + nickname(this, member!!, null, false, true) } + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + nickname(this, member!!, null, true, false) + } + } + greedy("name") { getName -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val name = getName() + nickname(this, member!!, name, false, false) + } + } + } - literal("remame", "name") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - rename(this, member!!, null, false) - } - unixLiteral("raw") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - rename(this, member!!, null, true) - } + literal("servername", "servernick", "sn") { + guild { getGuildId -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false } - greedy("name") { getName -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - rename(this, member!!, name, false) - } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + servername(this, serverMember!!, null, false, false) } - - literal("nickname", "nick", "displayname", "dn") { + unixLiteral("clear", "remove") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - nickname(this, member!!, null, false, false) - } - unixLiteral("clear", "remove") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - nickname(this, member!!, null, false, true) + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false } - } - unixLiteral("raw") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - nickname(this, member!!, null, true, false) - } - } - greedy("name") { getName -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val name = getName() - nickname(this, member!!, name, false, false) + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + servername(this, serverMember!!, null, false, true) } } - - literal("servername", "servernick", "sn") { - guild { getGuildId -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuild(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) - servername(this, serverMember!!, null, false, false) - } - unixLiteral("clear", "remove") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuild(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) - servername(this, serverMember!!, null, false, true) - } - } - unixLiteral("raw") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuild(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) - servername(this, serverMember!!, null, true, false) - } - } - greedy("name") { getName -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuild(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) - servername(this, serverMember!!, getName(), false, false) - } - } - } - } - - literal("description", "desc", "d") { + unixLiteral("raw") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - description(this, member!!, null, false, false) - } - unixLiteral("raw") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - description(this, member!!, null, true, false) - } - } - unixLiteral("clear", "remove") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - description(this, member!!, null, false, true) + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false } - } - greedy("description") { getDesc -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - description(this, member!!, getDesc(), false, false) + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + servername(this, serverMember!!, null, true, false) } } - - literal("avatar", "pfp") { + greedy("name") { getName -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - avatar(this, member!!, null, false) - } - unixLiteral("clear", "remove") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - avatar(this, member!!, null, true) - } - } - attachment("avatar") { getAvatar -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - avatar(this, member!!, getAvatar().url, false) + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false } - } - string("avatar") { getAvatar -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - avatar(this, member!!, getAvatar(), false) + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + servername(this, serverMember!!, getName(), false, false) } } + } + } - literal("serveravatar", "serverpfp", "sp", "sa") { - guild { getGuildId -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuild(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) - serverAvatar(this, serverMember!!, null, false) - } - unixLiteral("clear", "remove") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuild(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) - serverAvatar(this, serverMember!!, null, true) - } - } - attachment("avatar") { getAvatar -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuild(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) - serverAvatar(this, serverMember!!, getAvatar().url, false) - } - } - greedy("avatar") { getAvatar -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuild(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) - serverAvatar(this, serverMember!!, getAvatar(), false) - } - } - } + literal("description", "desc", "d") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + description(this, member!!, null, false, false) + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + description(this, member!!, null, true, false) } + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + description(this, member!!, null, false, true) + } + } + greedy("description") { getDesc -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + description(this, member!!, getDesc(), false, false) + } + } + } - literal("autoproxy", "ap") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - autoproxy(this, member!!, null) - } - // TODO: BooleanNode - literal("disable", "off", "false", "0") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - autoproxy(this, member!!, false) - } + literal("avatar", "pfp") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + avatar(this, member!!, null, false) + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + avatar(this, member!!, null, true) + } + } + attachment("avatar") { getAvatar -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + avatar(this, member!!, getAvatar().url, false) + } + } + string("avatar") { getAvatar -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + avatar(this, member!!, getAvatar(), false) + } + } + } + + literal("serveravatar", "serverpfp", "sp", "sa") { + guild { getGuildId -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false } - literal("enable", "on", "true", "1") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - autoproxy(this, member!!, true) - } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + serverAvatar(this, serverMember!!, null, false) } - - literal("proxy", "p") { + unixLiteral("clear", "remove") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - proxy(this, member!!, null) - } - - literal("remove", "delete") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - removeProxy(this, member!!, false, null) - } - greedy("proxy") { getProxy -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - extractProxyFromTag(this, getProxy()) ?: return@runs false - val proxy = database.fetchProxyTagFromMessage(getUser(), getProxy()) - proxy ?: run { - respondFailure("Proxy tag doesn't exist in this member.") - return@runs false - } - if (proxy.memberId != member!!.id) { - respondFailure("Proxy tag doesn't exist in this member.") - return@runs false - } - removeProxy(this, member, false, proxy) - } - } - } - - literal("add", "create") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - proxy(this, member!!, null) + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false } - - greedy("proxy") { getProxy -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false - proxy(this, member!!, proxy) - } - } - } - - greedy("proxy") { getProxy -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false - proxy(this, member!!, proxy) + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + serverAvatar(this, serverMember!!, null, true) } } - - literal("pronouns") { + attachment("avatar") { getAvatar -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - pronouns(this, member!!, null, false, false) - } - unixLiteral("clear", "remove") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - pronouns(this, member!!, null, false, true) - } - } - unixLiteral("raw") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - pronouns(this, member!!, null, true, false) + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false } - } - greedy("pronouns") { getPronouns -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - pronouns(this, member!!, getPronouns(), false, false) + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + serverAvatar(this, serverMember!!, getAvatar().url, false) } } - - literal("color", "c") { + greedy("avatar") { getAvatar -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - color(this, member!!, null) - } - greedy("color") { getColor -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - color(this, member!!, getColor().toColor()) + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + serverAvatar(this, serverMember!!, getAvatar(), false) } } + } + } - literal("birthday","bday","birth","bd") { + literal("autoproxy", "ap") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + autoproxy(this, member!!, null) + } + // TODO: BooleanNode + literal("disable", "off", "false", "0") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + autoproxy(this, member!!, false) + } + } + literal("enable", "on", "true", "1") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + autoproxy(this, member!!, true) + } + } + } + + literal("proxy", "p") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + proxy(this, member!!, null) + } + + literal("remove", "rem", "delete", "del") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + removeProxy(this, member!!, false, null) + } + greedy("proxy") { getProxy -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - birthday(this, member!!, null, false) - } - unixLiteral("clear", "remove") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - birthday(this, member!!, null, true) + extractProxyFromTag(this, getProxy()) ?: return@runs false + val proxy = database.fetchProxyTagFromMessage(getUser(), getProxy()) + proxy ?: run { + respondFailure("Proxy tag doesn't exist in this member.") + return@runs false } - } - greedy("birthday") { getBirthday -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) - if (!checkMember(this, member)) return@runs false - birthday(this, member!!, tryParseLocalDate(getBirthday())?.first, false) + if (proxy.memberId != member!!.id) { + respondFailure("Proxy tag doesn't exist in this member.") + return@runs false } + removeProxy(this, member, false, proxy) } } - literal("delete", "remove", "del") { + } + + literal("add", "create") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + proxy(this, member!!, null) + } + + greedy("proxy") { getProxy -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - delete(this, member!!) + val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false + proxy(this, member!!, proxy) } } } + + greedy("proxy") { getProxy -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false + proxy(this, member!!, proxy) + } + } + } + + literal("pronouns") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + pronouns(this, member!!, null, false, false) + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + pronouns(this, member!!, null, false, true) + } + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + pronouns(this, member!!, null, true, false) + } + } + greedy("pronouns") { getPronouns -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + pronouns(this, member!!, getPronouns(), false, false) + } + } + } + + literal("color", "colour", "c") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + color(this, member!!, null) + } + greedy("color") { getColor -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + color(this, member!!, getColor().toColor()) + } + } + } + + literal("birthday","bday","birth","bd") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + birthday(this, member!!, null, false) + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + birthday(this, member!!, null, true) + } + } + greedy("birthday") { getBirthday -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + birthday(this, member!!, tryParseLocalDate(getBirthday())?.first, false) + } + } + } + literal("delete", "remove", "del", "rem") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + delete(this, member!!) + } + } + } + + suspend fun register() { + printStep("Registering member commands", 2) + //TODO: Dedupe code + Commands.parser.literal("member", "m") { + runs { + empty(this) + } + + string("member", ::registerMemberCommands) literal("delete", "remove", "del") { runs { delete(this, null) @@ -821,28 +831,12 @@ object MemberCommands { } } - suspend fun checkSystem(ctx: DiscordContext, system: SystemRecord?): Boolean { - system ?: run { - ctx.respondFailure("System does not exist. Create one using a slash command or `pf>system new`") - return false - } - return true - } - - suspend fun checkMember(ctx: DiscordContext, member: MemberRecord?): Boolean { - member ?: run { - ctx.respondFailure("Member does not exist. Create one using a slash command or `pf>member new`") - return false - } - return true - } - - suspend fun empty(ctx: DiscordContext): Boolean { + suspend fun empty(ctx: DiscordContext): Boolean { ctx.respondWarning("Make sure to provide a member command!") return false } - suspend fun access(ctx: DiscordContext, system: SystemRecord, member: MemberRecord): Boolean { + suspend fun access(ctx: DiscordContext, system: SystemRecord, member: MemberRecord): Boolean { val guild = ctx.getGuild() val settings = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) ctx.respondEmbed { @@ -906,7 +900,7 @@ object MemberCommands { return true } - suspend fun rename(ctx: DiscordContext, member: MemberRecord, name: String?, raw: Boolean): Boolean { + suspend fun rename(ctx: DiscordContext, member: MemberRecord, name: String?, raw: Boolean): Boolean { name ?: run { if (raw) ctx.respondPlain("`${member.name}`") @@ -923,7 +917,7 @@ object MemberCommands { return true } - suspend fun nickname(ctx: DiscordContext, member: MemberRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { + suspend fun nickname(ctx: DiscordContext, member: MemberRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { if (clear) { member.displayName = null database.updateMember(member) @@ -954,7 +948,7 @@ object MemberCommands { return true } - suspend fun servername(ctx: DiscordContext, serverMember: MemberServerSettingsRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { + suspend fun servername(ctx: DiscordContext, serverMember: MemberServerSettingsRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { if (clear) { serverMember.nickname = null database.updateMemberServerSettings(serverMember) @@ -985,7 +979,7 @@ object MemberCommands { return true } - suspend fun description(ctx: DiscordContext, member: MemberRecord, description: String?, raw: Boolean, clear: Boolean): Boolean { + suspend fun description(ctx: DiscordContext, member: MemberRecord, description: String?, raw: Boolean, clear: Boolean): Boolean { if (clear) { member.description = null database.updateMember(member) @@ -1013,7 +1007,7 @@ object MemberCommands { return true } - suspend fun avatar(ctx: DiscordContext, member: MemberRecord, avatar: String?, clear: Boolean): Boolean { + suspend fun avatar(ctx: DiscordContext, member: MemberRecord, avatar: String?, clear: Boolean): Boolean { if (clear) { member.avatarUrl = null database.updateMember(member) @@ -1029,6 +1023,7 @@ object MemberCommands { ctx.respondEmbed { image = member.avatarUrl + color = member.color.kordColor() } return true } @@ -1040,7 +1035,7 @@ object MemberCommands { return true } - suspend fun serverAvatar(ctx: DiscordContext, serverMember: MemberServerSettingsRecord, avatar: String?, clear: Boolean): Boolean { + suspend fun serverAvatar(ctx: DiscordContext, serverMember: MemberServerSettingsRecord, avatar: String?, clear: Boolean): Boolean { if (clear) { serverMember.avatarUrl = null database.updateMemberServerSettings(serverMember) @@ -1067,7 +1062,7 @@ object MemberCommands { return true } - suspend fun removeProxy(ctx: DiscordContext, member: MemberRecord, exists: Boolean, proxy: MemberProxyTagRecord?): Boolean { + suspend fun removeProxy(ctx: DiscordContext, member: MemberRecord, exists: Boolean, proxy: MemberProxyTagRecord?): Boolean { if (!exists) { ctx.respondWarning("Please provide a proxy tag to remove.") return true @@ -1089,7 +1084,7 @@ object MemberCommands { return true } - suspend fun autoproxy(ctx: DiscordContext, member: MemberRecord, enabled: Boolean?): Boolean { + suspend fun autoproxy(ctx: DiscordContext, member: MemberRecord, enabled: Boolean?): Boolean { enabled ?: run { ctx.respondSuccess("AutoProxy for ${member.showDisplayName()} is set to ${if (member.autoProxy) "on" else "off"}!") return true @@ -1100,7 +1095,7 @@ object MemberCommands { return true } - suspend fun extractProxyFromTag(ctx: DiscordContext, proxy: String): Pair? { + suspend fun extractProxyFromTag(ctx: DiscordContext, proxy: String): Pair? { if (!proxy.contains("text")) { ctx.respondFailure("Given proxy tag does not contain `text`.") return null @@ -1114,7 +1109,7 @@ object MemberCommands { return Pair(prefix, suffix) } - suspend fun proxy(ctx: DiscordContext, member: MemberRecord, proxy: Pair?): Boolean { + suspend fun proxy(ctx: DiscordContext, member: MemberRecord, proxy: Pair?): Boolean { proxy ?: run { ctx.respondEmbed { member(member, ctx.getGuild()?.id?.value ?: 0UL) @@ -1138,11 +1133,11 @@ object MemberCommands { ctx.respondFailure("Proxy tag already exists in this system.") return false } - ctx.respondSuccess("Proxy tag `${proxy.first}text${proxy.second}` created!") + ctx.respondSuccess("Proxy tag `${proxy.first ?: ""}text${proxy.second ?: ""}` created!") return true } - suspend fun pronouns(ctx: DiscordContext, member: MemberRecord, pronouns: String?, raw: Boolean, clear: Boolean): Boolean { + suspend fun pronouns(ctx: DiscordContext, member: MemberRecord, pronouns: String?, raw: Boolean, clear: Boolean): Boolean { if (clear) { member.pronouns = null database.updateMember(member) @@ -1169,7 +1164,7 @@ object MemberCommands { return true } - suspend fun color(ctx: DiscordContext, member: MemberRecord, color: Int?): Boolean { + suspend fun color(ctx: DiscordContext, member: MemberRecord, color: Int?): Boolean { color ?: run { ctx.respondSuccess("Member's color is `${member.color.fromColor()}`") return true @@ -1181,7 +1176,7 @@ object MemberCommands { return true } - suspend fun birthday(ctx: DiscordContext, member: MemberRecord, birthday: LocalDate?, clear: Boolean): Boolean { + suspend fun birthday(ctx: DiscordContext, member: MemberRecord, birthday: LocalDate?, clear: Boolean): Boolean { if (clear) { member.birthday = null database.updateMember(member) @@ -1204,7 +1199,7 @@ object MemberCommands { return true } - suspend fun delete(ctx: DiscordContext, member: MemberRecord?): Boolean { + suspend fun delete(ctx: DiscordContext, member: MemberRecord?): Boolean { member ?: run { ctx.respondFailure("Make sure to provide the name of the member to delete!") return false @@ -1224,7 +1219,7 @@ object MemberCommands { return true } - suspend fun create(ctx: DiscordContext, system: SystemRecord, name: String?, ): Boolean { + suspend fun create(ctx: DiscordContext, system: SystemRecord, name: String?, ): Boolean { name ?: run { ctx.respondFailure("Make sure to provide a name for the new member!") return false diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 369e0a0e..56b8c5be 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -10,11 +10,17 @@ package dev.proxyfox.bot.command import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake +import dev.kord.core.Kord import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior +import dev.kord.core.entity.Guild import dev.kord.core.entity.Message import dev.kord.rest.NamedFile import dev.proxyfox.bot.* +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.context.guild +import dev.proxyfox.bot.command.context.runs +import dev.proxyfox.bot.command.node.attachment import dev.proxyfox.bot.string.dsl.greedy import dev.proxyfox.bot.string.dsl.literal import dev.proxyfox.bot.string.dsl.string @@ -23,15 +29,19 @@ import dev.proxyfox.bot.string.parser.MessageHolder import dev.proxyfox.bot.string.parser.registerCommand import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil +import dev.proxyfox.command.node.builtin.greedy +import dev.proxyfox.command.node.builtin.literal import dev.proxyfox.common.* import dev.proxyfox.database.database import dev.proxyfox.database.displayDate import dev.proxyfox.database.etc.exporter.Exporter import dev.proxyfox.database.etc.importer.ImporterException import dev.proxyfox.database.etc.importer.import +import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.ProxiedMessageRecord import dev.proxyfox.database.records.system.SystemRecord +import dev.proxyfox.database.records.system.SystemServerSettingsRecord import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull @@ -47,28 +57,159 @@ import kotlin.math.floor object MiscCommands { private val roleMatcher = Regex("\\d+") + suspend fun Kord.registerMiscCommands() { + createGlobalChatInputCommand("info", "Fetches info about the bot") { + + } + createGlobalChatInputCommand("moderation", "Moderator-only commands") { + + } + createGlobalChatInputCommand("misc", "Other commands that don't fit in a category") { + + } + } + suspend fun register() { printStep("Registering misc commands", 2) - registerCommand(literal("import", ::importEmpty) { - greedy("url", ::import) - }) + Commands.parser.literal("import") { + runs { + import(this, null) + } + attachment("file") { getFile -> + runs { + import(this, URL(getFile().url)) + } + } + greedy("file") { getFile -> + runs { + import(this, URL(getFile())) + } + } + } //TODO: export --full - registerCommand(literal("export", ::export)) - registerCommand(literal("time", ::time)) - registerCommand(literal("help", ::help)) - registerCommand(literal("explain", ::explain)) - registerCommand(literal("invite", ::invite)) - registerCommand(literal("source", ::source)) - registerCommand(literal(arrayOf("proxy", "p"), ::serverProxyEmpty) { - literal(arrayOf("off", "disable"), ::serverProxyOff) - literal(arrayOf("on", "enable"), ::serverProxyOn) - }) - registerCommand(literal(arrayOf("autoproxy", "ap"), ::proxyEmpty) { - literal(arrayOf("off", "disable"), ::proxyOff) - literal(arrayOf("latch", "l"), ::proxyLatch) - literal(arrayOf("front", "f"), ::proxyFront) - greedy("member", MiscCommands::proxyMember) - }) + Commands.parser.literal("export") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + export(this, system!!) + } + } + Commands.parser.literal("time") { + runs { + time(this) + } + } + Commands.parser.literal("help") { + runs { + respondSuccess(getHelp()) + true + } + } + Commands.parser.literal("explain") { + runs { + respondSuccess(getExplain()) + true + } + } + Commands.parser.literal("invite") { + runs { + respondSuccess(getInvite()) + true + } + } + Commands.parser.literal("source") { + runs { + respondSuccess(getSource()) + true + } + } + Commands.parser.literal("proxy", "p") { + guild { getGuildId -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + serverProxy(this, system, serverSystem, null) + } + literal("on", "enable") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + serverProxy(this, system, serverSystem, true) + } + } + literal("off", "disable") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + serverProxy(this, system, serverSystem, false) + } + } + } + } + Commands.parser.literal("autoproxy", "ap") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + proxy(this, system!!, null, null) + } + literal("off", "disable", "o") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + proxy(this, system!!, AutoProxyMode.OFF, null) + } + } + literal("latch", "l") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + proxy(this, system!!, AutoProxyMode.LATCH, null) + } + } + literal("front", "f") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + proxy(this, system!!, AutoProxyMode.FRONT, null) + } + } + greedy("member") { getMem -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + proxy(this, system, AutoProxyMode.MEMBER, member) + } + } + } registerCommand(literal(arrayOf("serverautoproxy", "sap"), ::serverAutoProxyEmpty) { literal(arrayOf("off", "disable"), ::serverAutoProxyOff) @@ -116,20 +257,34 @@ object MiscCommands { } }) - registerCommand(literal("debug", ::debug)) + Commands.parser.literal("debug") { + runs { + debug(this) + } + } - registerCommand(literal("fox", ::getFox)) + Commands.parser.literal("fox") { + runs { + getFox(this) + } + } } - private suspend fun getFox(ctx: MessageHolder): String { - return FoxFetch.fetch() + private suspend fun getFox(ctx: DiscordContext): Boolean { + ctx.respondEmbed { + val fox = FoxFetch.fetch() + title = "**Link**" + url = fox + image = fox + } + return true } - private suspend fun debug(ctx: MessageHolder): String { - val shardid = ctx.message.getGuildOrNull()?.id?.value?.toShard() ?: 0 - ctx.respond { + private suspend fun debug(ctx: DiscordContext): Boolean { + val shardid = ctx.getGuild()?.id?.value?.toShard() ?: 0 + ctx.respondEmbed { title = "ProxyFox Debug" - val gatewayPing = ctx.message.kord.gateway.gateways[shardid]!!.ping.value!! + val gatewayPing = kord.gateway.gateways[shardid]!!.ping.value!! field { inline = true name = "Shard ID" @@ -195,101 +350,66 @@ object MiscCommands { throw DebugException() } - private suspend fun importEmpty(ctx: MessageHolder): String { - return try { - if (ctx.message.attachments.isEmpty()) return "Please attach a file or link to import" - val attach = URL(ctx.message.attachments.toList()[0].url) - val importer = withContext(Dispatchers.IO) { - attach.openStream().reader().use { import(it, ctx.message.author) } - } - "File imported. created ${importer.createdMembers} member(s), updated ${importer.updatedMembers} member(s)" - } catch (exception: ImporterException) { - "Failed to import file: ${exception.message}" + private suspend fun import(ctx: DiscordContext, url: URL?): Boolean { + url ?: run { + ctx.respondFailure("Please provide a file to import") + return false } - } - private suspend fun import(ctx: MessageHolder): String { return try { - val attach = URL(ctx.params["url"]!![0]) val importer = withContext(Dispatchers.IO) { - attach.openStream().reader().use { import(it, ctx.message.author) } + url.openStream().reader().use { import(it, ctx.getUser()) } } - "File imported. created ${importer.createdMembers} member(s), updated ${importer.updatedMembers} member(s)" + ctx.respondSuccess("File imported. created ${importer.createdMembers} member(s), updated ${importer.updatedMembers} member(s)") + true } catch (exception: ImporterException) { - "Failed to import file: ${exception.message}" + ctx.respondFailure("Failed to import file: ${exception.message}") + false } } - private suspend fun export(ctx: MessageHolder): String { - database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val export = Exporter.export(ctx.message.author!!.id.value) - ctx.sendFiles(NamedFile("system.json", export.byteInputStream())) - return "Check your DMs~" + private suspend fun export(ctx: DiscordContext, system: SystemRecord): Boolean { + val export = Exporter.export(ctx.getUser()!!.id.value) + ctx.respondFiles(null, NamedFile("system.json", export.byteInputStream())) + ctx.respondSuccess("Check your DMs~") + return true } - private fun time(ctx: MessageHolder): String { + private suspend fun time(ctx: DiscordContext): Boolean { val date = System.currentTimeMillis() / 1000 - return "It is currently " + ctx.respondSuccess("It is currently ") + return true } - private fun help(ctx: MessageHolder): String = + private fun getHelp(): String = """To view commands for ProxyFox, visit For quick setup: - pf>system new name - pf>member new John Doe - pf>member "John Doe" proxy j:text""" - private fun explain(ctx: MessageHolder): String = + private fun getExplain(): String = """ProxyFox is modern Discord bot designed to help systems communicate. It uses discord's webhooks to generate "pseudo-users" which different members of the system can use. Someone will likely be willing to explain further if need be.""" - private fun invite(ctx: MessageHolder): String = - """Use to invite ProxyFox to your server! + private fun getInvite(): String = + """Use to invite ProxyFox to your server! To get support, head on over to https://discord.gg/q3yF8ay9V7""" - private fun source(ctx: MessageHolder): String = + private fun getSource(): String = "Source code for ProxyFox is available at https://github.com/The-ProxyFox-Group/ProxyFox!" - private suspend fun proxyEmpty(ctx: MessageHolder): String { - database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return "Please provide whether you want autoproxy set to `off`, `latch`, `front`, or a member" - } - - private suspend fun proxyLatch(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.autoType = AutoProxyMode.LATCH - database.updateSystem(system) - return "Autoproxy mode is now set to `latch`" - } - - private suspend fun proxyFront(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.autoType = AutoProxyMode.FRONT - database.updateSystem(system) - return "Autoproxy mode is now set to `front`" - } - - private suspend fun proxyMember(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - system.autoType = AutoProxyMode.MEMBER - system.autoProxy = member.id - database.updateSystem(system) - return "Autoproxy mode is now set to ${member.name}" - } + private suspend fun proxy(ctx: DiscordContext, system: SystemRecord, mode: AutoProxyMode?, member: MemberRecord?): Boolean { + mode ?: run { + ctx.respondSuccess("Autoproxy is set to ${system.autoType.name}") + return true + } - private suspend fun proxyOff(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.autoType = AutoProxyMode.OFF + system.autoType = mode + val response = if (member != null) {system.autoProxy = member.id; "Now autoproxying as ${member.showDisplayName()}"} else "Autoproxy mode is now set to ${mode.name}" database.updateSystem(system) - return "Autoproxy disabled" + ctx.respondSuccess(response) + return true } private suspend fun serverAutoProxyEmpty(ctx: MessageHolder): String { @@ -346,29 +466,16 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return "Autoproxy disabled for this server." } - private suspend fun serverProxyEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val systemServer = database.getOrCreateServerSettingsFromSystem(ctx.message.getGuild(), system.id) - return "Proxy for this server is currently ${if (systemServer.proxyEnabled) "enabled" else "disabled"}." - } - - private suspend fun serverProxyOn(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val systemServer = database.getOrCreateServerSettingsFromSystem(ctx.message.getGuild(), system.id) - systemServer.proxyEnabled = true - database.updateSystemServerSettings(systemServer) - return "Proxy for this server has been enabled" - } + private suspend fun serverProxy(ctx: DiscordContext, system: SystemRecord, systemServer: SystemServerSettingsRecord, enabled: Boolean?): Boolean { + enabled ?: run { + ctx.respondSuccess("Proxy for this server is currently ${if (systemServer.proxyEnabled) "enabled" else "disabled"}.") + return false + } - private suspend fun serverProxyOff(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val systemServer = database.getOrCreateServerSettingsFromSystem(ctx.message.getGuild(), system.id) - systemServer.proxyEnabled = false + systemServer.proxyEnabled = enabled database.updateSystemServerSettings(systemServer) - return "Proxy for this server has been disabled" + ctx.respondSuccess("Proxy for this server has been ${if (enabled) "enabled" else "disabled"}") + return true } private suspend fun roleEmpty(ctx: MessageHolder): String { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 95a7cc5b..0ae2a156 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -9,6 +9,13 @@ package dev.proxyfox.bot.command import dev.kord.common.entity.ButtonStyle +import dev.kord.core.Kord +import dev.kord.rest.builder.interaction.SubCommandBuilder +import dev.kord.rest.builder.interaction.string +import dev.kord.rest.builder.interaction.subCommand +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.context.InteractionCommandContext +import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.parseDuration import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.Pager @@ -18,52 +25,162 @@ import dev.proxyfox.bot.string.dsl.literal import dev.proxyfox.bot.string.dsl.stringList import dev.proxyfox.bot.string.parser.MessageHolder import dev.proxyfox.bot.string.parser.registerCommand +import dev.proxyfox.command.node.builtin.greedy +import dev.proxyfox.command.node.builtin.literal +import dev.proxyfox.command.node.builtin.stringList import dev.proxyfox.common.printStep +import dev.proxyfox.common.trimEach import dev.proxyfox.database.database +import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemSwitchRecord import java.time.Instant object SwitchCommands { + var interactionExecutors: HashMap Boolean> = hashMapOf() + + fun SubCommandBuilder.runs(action: suspend InteractionCommandContext.() -> Boolean) { + interactionExecutors[name] = action + } + + suspend fun Kord.registerSwitchCommands() { + createGlobalChatInputCommand("switch", "Create or manage switches!") { + subCommand("create", "Create a switch") { + string("members", "The members to use, comma separated") { + required = true + } + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val members = value.interaction.command.strings["members"]!!.split(",").toTypedArray() + members.trimEach() + switch(this, system!!, members) + } + } + subCommand("out", "Marks that no-one's fronting") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + out(this, system!!) + } + } + subCommand("delete", "Deletes the latest switch") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val switch = database.fetchLatestSwitch(system!!.id) + if (!checkSwitch(this, switch)) return@runs false + val oldSwitch = database.fetchSecondLatestSwitch(system.id) + delete(this, system, switch!!, oldSwitch) + } + } + subCommand("move", "Moves the latest switch") { + name("time") + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val switch = database.fetchLatestSwitch(system!!.id) + if (!checkSwitch(this, switch)) return@runs false + val oldSwitch = database.fetchSecondLatestSwitch(system.id) + val time = value.interaction.command.strings["time"]!! + move(this, system, switch!!, oldSwitch, time) + } + } + subCommand("list", "Lists your switches") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + list(this, system!!) + } + } + } + } + suspend fun register() { printStep("Registering switch commands", 2) - registerCommand(literal(arrayOf("switch", "sw"), ::empty) { - literal(arrayOf("out", "o"), ::out) - literal(arrayOf("move", "mv", "m"), ::moveEmpty) { - greedy("time", ::move) + Commands.parser.literal("switch", "sw") { + runs { + respondFailure("Please provide a switch subcommand.") + false + } + literal("out", "o") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + out(this, system!!) + } } - literal(arrayOf("delete", "del", "remove"), ::delete) - literal(arrayOf("list", "l"), ::list) - stringList("members", ::switch) - }) + literal("delete", "del", "remove", "rem") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val switch = database.fetchLatestSwitch(system!!.id) + if (!checkSwitch(this, switch)) return@runs false + val oldSwitch = database.fetchSecondLatestSwitch(system.id) + delete(this, system, switch!!, oldSwitch) + } + } + literal("move","mv","m") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val switch = database.fetchLatestSwitch(system!!.id) + if (!checkSwitch(this, switch)) return@runs false + val oldSwitch = database.fetchSecondLatestSwitch(system.id) + move(this, system, switch!!, oldSwitch, null) + } + greedy("time") { getTime -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val switch = database.fetchLatestSwitch(system!!.id) + if (!checkSwitch(this, switch)) return@runs false + val oldSwitch = database.fetchSecondLatestSwitch(system.id) + move(this, system, switch!!, oldSwitch, getTime()) + } + } + } + literal("list", "l") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + list(this, system!!) + } + } + stringList("members") { getMembers -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + switch(this, system!!, getMembers().toTypedArray()) + } + } + } } - private suspend fun empty(ctx: MessageHolder): String = "Make sure to provide a switch command!" - - private suspend fun out(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" + private suspend fun out(ctx: DiscordContext, system: SystemRecord): Boolean { database.createSwitch(system.id, listOf()) - return "Switch registered." + ctx.respondSuccess("Switch registered. Take care!") + return true } - private suspend fun moveEmpty(ctx: MessageHolder): String = "Please provide a time to move the switch back" - private suspend fun move(ctx: MessageHolder): String { - val author = ctx.message.author!! - val system = database.fetchSystemFromUser(author) - ?: return "System does not exist. Create one using `pf>system new`" - val switch = database.fetchLatestSwitch(system.id) - ?: return "It looks like you haven't registered any switches yet" + private suspend fun move(ctx: DiscordContext, system: SystemRecord, switch: SystemSwitchRecord, oldSwitch: SystemSwitchRecord?, time: String?): Boolean { + time ?: run { + ctx.respondFailure("Please provide a time to move the switch back") + return false + } + val oldSwitch = database.fetchSecondLatestSwitch(system.id) - val either = ctx.params["time"]!![0].parseDuration() + val either = time.parseDuration() either.right?.let { - return it + ctx.respondFailure(it) + return false } val nowMinus = Instant.now().minusMillis(either.left!!.inWholeMilliseconds) if (oldSwitch != null && oldSwitch.timestamp > nowMinus) { - return "It looks like you're trying to break the space-time continuum..\n" + - "The provided time is set before the previous switch" + ctx.respondFailure("It looks like you're trying to break the space-time continuum..\n" + + "The provided time is set before the previous switch") + return false } val members = switch.memberIds.map { @@ -71,8 +188,8 @@ object SwitchCommands { }.joinToString(", ") TimedYesNoPrompt.build( - runner = author.id, - channel = ctx.message.channel, + runner = ctx.getUser()!!.id, + channel = ctx.getChannel(), message = "Are you sure you want to move the switch $members back to ?", yes = Button("Move switch", Button.move, ButtonStyle.Primary) { switch.timestamp = nowMinus @@ -81,27 +198,18 @@ object SwitchCommands { } ) - return "" + return true } - private suspend fun delete(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val switch = database.fetchLatestSwitch(system.id) - ?: return "No switches registered" - - val switchBefore = database.fetchSecondLatestSwitch(system.id)?.let { - "The next latest switch is ${it.membersAsString()} ()." - } ?: "There is no previous switch." - + private suspend fun delete(ctx: DiscordContext, system: SystemRecord, switch: SystemSwitchRecord, oldSwitch: SystemSwitchRecord?): Boolean { val epoch = switch.timestamp.epochSecond TimedYesNoPrompt.build( - runner = ctx.message.author!!.id, - channel = ctx.message.channel, + runner = ctx.getUser()!!.id, + channel = ctx.getChannel(), message = """ Are you sure you want to delete the latest switch (${switch.membersAsString()}, )? - $switchBefore + $oldSwitch The data will be lost forever (A long time!) """.trimIndent(), yes = Button("Delete switch", Button.wastebasket, ButtonStyle.Danger) { @@ -110,37 +218,36 @@ object SwitchCommands { }, ) - return "" + return true } - private suspend fun list(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" + private suspend fun list(ctx: DiscordContext, system: SystemRecord): Boolean { // We know the system exists here, will be non-null val switches = database.fetchSortedSwitchesFromSystem(system.id)!! - Pager.build(ctx.message.author!!.id, ctx.message.channel, switches, 20, { + Pager.build(ctx.getUser()!!.id, ctx.getChannel(), switches, 20, { title = "[$it] Front history of ${system.showName}" }, { it.membersAsString("**", "**") + " ()\n" }) - return "" + return true } - private suspend fun switch(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val members = ArrayList() + private suspend fun switch(ctx: DiscordContext, system: SystemRecord, members: Array): Boolean { + val membersOut = ArrayList() var memberString = "" - ctx.params["members"]!!.forEach { - val member = database.findMember(system.id, it) ?: return "Couldn't find member `$it`, do they exist?" - members += member.id + members.forEach { + val member = database.findMember(system.id, it) ?: run { + ctx.respondFailure("Couldn't find member `$it`, do they exist?") + return false + } + membersOut += member.id memberString += "`${member.showDisplayName()}`, " } memberString = memberString.substring(0, memberString.length - 2) - database.createSwitch(system.id, members) - + database.createSwitch(system.id, membersOut) - return "Switch registered, current fronters: $memberString" + ctx.respondSuccess("Switch registered! Current fronters: $memberString") + return true } private suspend fun SystemSwitchRecord.membersAsString(prefix: String = "", postfix: String = ""): String { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index bccc1ac3..fb0e6839 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -9,87 +9,388 @@ package dev.proxyfox.bot.command import dev.kord.common.entity.ButtonStyle +import dev.kord.core.Kord import dev.kord.rest.NamedFile +import dev.kord.rest.builder.interaction.SubCommandBuilder +import dev.kord.rest.builder.interaction.subCommand +import dev.proxyfox.bot.command.MemberCommands.runs +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.context.InteractionCommandContext +import dev.proxyfox.bot.command.context.runs +import dev.proxyfox.bot.command.context.system +import dev.proxyfox.bot.command.node.attachment +import dev.proxyfox.bot.hasUnixValue import dev.proxyfox.bot.kordColor import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.Pager import dev.proxyfox.bot.prompts.TimedYesNoPrompt -import dev.proxyfox.bot.string.dsl.greedy -import dev.proxyfox.bot.string.dsl.literal -import dev.proxyfox.bot.string.dsl.unixLiteral -import dev.proxyfox.bot.string.parser.MessageHolder -import dev.proxyfox.bot.string.parser.registerCommand import dev.proxyfox.bot.system import dev.proxyfox.bot.toKtInstant +import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.fromColor import dev.proxyfox.common.printStep import dev.proxyfox.common.toColor import dev.proxyfox.database.database import dev.proxyfox.database.etc.exporter.Exporter +import dev.proxyfox.database.records.member.MemberRecord +import dev.proxyfox.database.records.system.SystemRecord /** * Commands for accessing and changing system settings * @author Oliver * */ object SystemCommands { - suspend fun register() { - printStep("Registering system commands", 2) - registerCommand(literal(arrayOf("system", "s"), ::empty) { - literal(arrayOf("new", "n", "create", "add"), ::createEmpty) { - greedy("name", ::create) - } + var interactionExecutors: HashMap Boolean> = hashMapOf() - literal(arrayOf("name", "rename"), ::accessName) { - greedy("name", ::rename) - } + fun SubCommandBuilder.runs(action: suspend InteractionCommandContext.() -> Boolean) { + interactionExecutors[name] = action + } - literal(arrayOf("list", "l"), ::list) { - unixLiteral(arrayOf("by-message-count", "bmc"), ::listByMessage) - unixLiteral(arrayOf("verbose", "v"), ::listVerbose) + suspend fun Kord.registerSystemCommands() { + createGlobalChatInputCommand("system", "Manage or create a system!") { + subCommand("fetch", "Fetch your system card!") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + access(this, system!!) + } } - - literal(arrayOf("color", "colour"), ::colorEmpty) { - greedy("color", ::color) + subCommand("create", "Create a system") { + name(required = false) + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val name = value.interaction.command.strings["name"] + create(this, name) + } } + subCommand("delete", "Delete the system") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false - literal(arrayOf("pronouns", "p"), ::pronounsEmpty) { - unixLiteral("raw", ::pronounsRaw) - greedy("pronouns", ::pronouns) + delete(this, system!!) + } } - - literal(arrayOf("description", "desc", "d"), ::descriptionEmpty) { - unixLiteral("raw", ::descriptionRaw) - greedy("desc", ::description) + access("system", "name") { + name(required = false) + raw() + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val name = value.interaction.command.strings["name"] + val raw = value.interaction.command.booleans["raw"] ?: false + val clear = value.interaction.command.booleans["clear"] ?: false + name(this, system!!, name, raw, clear) + } } - - literal(arrayOf("avatar", "pfp"), ::avatarEmpty) { - unixLiteral("raw", ::avatarRaw) - unixLiteral("clear", ::avatarClear) - unixLiteral("delete", ::avatarClear) - greedy("avatar", ::avatar) + subCommand("list", "List your system members") { + bool("by-message", "Whether to sort by message count") + bool("verbose", "Whether to display information verbosely") + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val byMessage = value.interaction.command.booleans["by-message"] ?: false + val verbose = value.interaction.command.booleans["verbose"] ?: false + list(this, system!!, byMessage, verbose) + } } - - literal("tag", ::tagEmpty) { - unixLiteral("raw", ::tagRaw) - unixLiteral("clear", ::tagClear) - unixLiteral("delete", ::tagClear) - greedy("tag", ::tag) + access("system", "color") { + name("color", required = false) + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val color = value.interaction.command.strings["color"] + + color(this, system!!, color?.toColor()) + } } + access("system", "pronouns") { + name("pronouns", required = false) + raw() + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val pronouns = value.interaction.command.strings["pronouns"] + val raw = value.interaction.command.booleans["raw"] ?: false + val clear = value.interaction.command.booleans["clear"] ?: false + pronouns(this, system!!, pronouns, raw, clear) + } + } + access("system", "description") { + name("description", required = false) + raw() + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val desc = value.interaction.command.strings["description"] + val raw = value.interaction.command.booleans["raw"] ?: false + val clear = value.interaction.command.booleans["clear"] ?: false + + description(this, system!!, desc, raw, clear) + } + } + access("system", "avatar") { + avatar() + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val avatar = value.interaction.command.attachments["avatar"]?.data?.url + val clear = value.interaction.command.booleans["clear"] ?: false + + avatar(this, system!!, avatar, clear) + } + } + access("system", "tag") { + name("tag", required = false) + raw() + clear() + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val tag = value.interaction.command.strings["tag"] + val raw = value.interaction.command.booleans["raw"] ?: false + val clear = value.interaction.command.booleans["clear"] ?: false + + tag(this, system!!, tag, raw, clear) + } + } + } + } - literal(arrayOf("delete", "del", "remove"), ::delete) - }) - - registerCommand(literal(arrayOf("list", "l"), ::list) { - unixLiteral(arrayOf("by-message-count", "bmc"), ::listByMessage) - unixLiteral(arrayOf("verbose", "v"), ::listVerbose) - }) + suspend fun register() { + printStep("Registering system commands", 2) + Commands.parser.literal("list", "l") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + list(this, system!!, false, false) + } + unix("params") { getParams -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val params = getParams().toTypedArray() + val byMessage = hasUnixValue(params, "by-message-count") || hasUnixValue(params, "bmc") + val verbose = hasUnixValue(params, "verbose") || hasUnixValue(params, "v") + list(this, system!!, byMessage, verbose) + } + } + } + Commands.parser.literal("system", "sys", "s") { + literal("new", "n", "create", "add") { + runs { + create(this, null) + } + greedy("name") { getName -> + runs { + create(this, getName()) + } + } + } + literal("delete", "del", "remove", "rem") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + delete(this, system!!) + } + } + system { getSys -> + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + access(this, system!!) + } + literal("name", "rename") { + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + name(this, system!!, null, false, false) + } + unixLiteral("raw") { + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + name(this, system!!, null, true, false) + } + } + unixLiteral("clear", "remove") { + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + name(this, system!!, null, false, true) + } + } + greedy("name") { getName -> + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + name(this, system!!, getName(), false, false) + } + } + } + literal("list", "l") { + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + list(this, system!!, false, false) + } + unix("params") { getParams -> + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + val params = getParams().toTypedArray() + val byMessage = hasUnixValue(params, "by-message-count") || hasUnixValue(params, "bmc") + val verbose = hasUnixValue(params, "verbose") || hasUnixValue(params, "v") + list(this, system!!, byMessage, verbose) + } + } + } + literal("color", "colour", "c") { + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + color(this, system!!, null) + } + greedy("color") { getColor -> + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + color(this, system!!, getColor().toColor()) + } + } + } + literal("pronouns", "p") { + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + pronouns(this, system!!, null, false, false) + } + unixLiteral("raw") { + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + pronouns(this, system!!, null, true, false) + } + } + unixLiteral("clear", "remove") { + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + pronouns(this, system!!, null, false, true) + } + } + greedy("pronouns") { getPronouns -> + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + pronouns(this, system!!, getPronouns(), false, false) + } + } + } + literal("description", "desc", "d") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + description(this, system!!, null, false, false) + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + description(this, system!!, null, true, false) + } + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + description(this, system!!, null, false, true) + } + } + greedy("description") { getDesc -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + description(this, system!!, getDesc(), false, false) + } + } + } + literal("avatar", "pfp") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + avatar(this, system!!, null, false) + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + avatar(this, system!!, null, true) + } + } + attachment("avatar") { getAvatar -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + avatar(this, system!!, getAvatar().url, false) + } + } + string("avatar") { getAvatar -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + avatar(this, system!!, getAvatar(), false) + } + } + } + literal("tag", "t") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + tag(this, system!!, null, false, false) + } + unixLiteral("raw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + tag(this, system!!, null, true, false) + } + } + unixLiteral("clear", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + tag(this, system!!, null, false, true) + } + } + greedy("description") { getTag -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + tag(this, system!!, getTag(), false, false) + } + } + } + } + } +// registerCommand(literal(arrayOf("system", "s"), ::empty) { +// literal(arrayOf("delete", "del", "remove"), ::delete) +// }) +// +// registerCommand(literal(arrayOf("list", "l"), ::list) { +// unixLiteral(arrayOf("by-message-count", "bmc"), ::listByMessage) +// unixLiteral(arrayOf("verbose", "v"), ::listVerbose) +// }) } - private suspend fun empty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val members = database.fetchTotalMembersFromUser(ctx.message.author) - ctx.respond { + private suspend fun access(ctx: DiscordContext, system: SystemRecord): Boolean { + val members = database.fetchTotalMembersFromUser(ctx.getUser()) + ctx.respondEmbed { title = system.name ?: system.id color = system.color.kordColor() system.avatarUrl?.let { @@ -125,48 +426,65 @@ object SystemCommands { } timestamp = system.timestamp.toKtInstant() } - return "" - } - - private suspend fun createEmpty(ctx: MessageHolder): String { - database.getOrCreateSystem(ctx.message.author!!) - return "System created! See `pf>help` for how to set up your system further!" + return true } - private suspend fun create(ctx: MessageHolder): String { - val system = database.getOrCreateSystem(ctx.message.author!!) - system.name = ctx.params["name"]!![0] + private suspend fun create(ctx: DiscordContext, name: String?): Boolean { + val system = database.getOrCreateSystem(ctx.getUser()!!) + system.name = name database.updateSystem(system) - return "System created with name ${system.name}! See `pf>help` for how to set up your system further!" + val add = if (name != null) "with name ${name}" else "" + ctx.respondSuccess("System created $add! See `pf>help` or `/pf-help` for how to set up your system further.") + return true } - private suspend fun renameEmpty(ctx: MessageHolder): String { - database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return "Make sure to provide me with a name to update your system!" - } + private suspend fun name(ctx: DiscordContext, system: SystemRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { + if (clear) { + system.name = null + database.updateSystem(system) + ctx.respondSuccess("System name cleared!") + } - private suspend fun rename(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.name = ctx.params["name"]!![0] + name ?: run { + system.name ?: run { + ctx.respondFailure("System doesn't have a name set.") + return false + } + + if (raw) + ctx.respondPlain("`${system.name}`") + else ctx.respondSuccess("System's name is ${system.name}") + } + + system.name = name database.updateSystem(system) - return "System name updated to ${system.name}!" + ctx.respondSuccess("System name updated to ${system.name}!") + return true } - private suspend fun accessName(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return "System's name is ${system.name}" - } + private suspend fun list(ctx: DiscordContext, system: SystemRecord, byMessage: Boolean, verbose: Boolean): Boolean { + // TODO: List by message + + if (verbose) { + ctx.respondEmbed { + system(system, nameTransformer = { "Members of $it" }) + val proxies = database.fetchProxiesFromSystem(system.id) + for (m in database.fetchMembersFromSystem(system.id)!!) { + val memberProxies = proxies?.filter { it.memberId == m.id } + field { + name = "${m.asString()} [`${m.id}`]" + value = if (memberProxies.isNullOrEmpty()) "*No proxy tags set.*" else memberProxies.joinToString("\uFEFF``\n``\uFEFF", "``\uFEFF", "\uFEFF``") + inline = true + } + } + } + return true + } - private suspend fun list(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" val proxies = database.fetchProxiesFromSystem(system.id)!! Pager.build( - ctx.message.author!!.id, - ctx.message.channel, + ctx.getUser()!!.id, + ctx.getChannel(), database.fetchMembersFromSystem(system.id)!!.map { m -> m to proxies.filter { it.memberId == m.id } }, 20, { page -> system(system, nameTransformer = { "[$page] Members of $it" }) }, @@ -175,160 +493,149 @@ object SystemCommands { "`${it.first.id}`\u2007•\u2007**${it.first.name}**${str}\n" }, ) - return "" + return true } - private suspend fun listByMessage(ctx: MessageHolder): String { - // TODO: Make it sort by message count - return list(ctx) + suspend fun color(ctx: DiscordContext, system: SystemRecord, color: Int?): Boolean { + color ?: run { + ctx.respondSuccess("Member's color is `${system.color.fromColor()}`") + return true + } + + system.color = color + database.updateSystem(system) + ctx.respondSuccess("Member's color is now `${color.fromColor()}!") + return true } - private suspend fun listVerbose(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - ctx.respond { - system(system, nameTransformer = { "Members of $it" }) - val proxies = database.fetchProxiesFromSystem(system.id) - for (m in database.fetchMembersFromSystem(system.id)!!) { - val memberProxies = proxies?.filter { it.memberId == m.id } - field { - name = "${m.asString()} [`${m.id}`]" - value = if (memberProxies.isNullOrEmpty()) "*No proxy tags set.*" else memberProxies.joinToString("\uFEFF``\n``\uFEFF", "``\uFEFF", "\uFEFF``") - inline = true - } - } + private suspend fun pronouns(ctx: DiscordContext, system: SystemRecord, pronouns: String?, raw: Boolean, clear: Boolean): Boolean { + if (clear) { + system.pronouns = null + database.updateSystem(system) + ctx.respondSuccess("System pronouns cleared!") + return true } - return "" - } - private suspend fun colorEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return system.color.fromColor()?.let { "System's color is `$it` " } ?: "There's no color set." + pronouns ?: run { + system.pronouns ?: run { + ctx.respondFailure("System doesn't have pronouns set") + return false + } - } + if (raw) { + ctx.respondPlain("`${system.pronouns}`") + return true + } - private suspend fun color(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.color = ctx.params["color"]!![0].toColor() - database.updateSystem(system) - return "Member's color updated!" - } + ctx.respondSuccess("System's pronouns are ${system.pronouns}") + return true + } - private suspend fun pronouns(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.pronouns = ctx.params["pronouns"]!![0] + system.pronouns = pronouns database.updateSystem(system) - return "Pronouns updated!" + ctx.respondSuccess("System pronouns updated to $pronouns!") + return true } - private suspend fun pronounsRaw(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return system.pronouns?.let { "``$it``" } ?: "There's no pronouns set." - } + suspend fun description(ctx: DiscordContext, system: SystemRecord, description: String?, raw: Boolean, clear: Boolean): Boolean { + if (clear) { + system.description = null + database.updateSystem(system) + ctx.respondSuccess("System's description cleared!") + return true + } - private suspend fun pronounsEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return system.pronouns?.let { "System's pronouns are set to $it" } ?: "There's no pronouns set." - } + description ?: run { + system.description ?: run { + ctx.respondWarning("System has no description set") + return true + } + + if (raw) + ctx.respondPlain("```md\n${system.description}```") + else ctx.respondSuccess("System's description is ${system.description}") - private suspend fun description(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.description = ctx.params["desc"]!![0] + return true + } + + system.description = description database.updateSystem(system) - return "Description updated!" - } + ctx.respondSuccess("System description updated!") - private suspend fun descriptionRaw(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return system.description?.let { "```md\n$it```" } ?: "There's no description set." + return true } - private suspend fun descriptionEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return system.description ?: "Description not set." - } + suspend fun avatar(ctx: DiscordContext, system: SystemRecord, avatar: String?, clear: Boolean): Boolean { + if (clear) { + system.avatarUrl = null + database.updateSystem(system) + ctx.respondSuccess("System's avatar cleared!") + return true + } - private suspend fun avatar(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.avatarUrl = ctx.params["avatar"]!![0] - database.updateSystem(system) - return "System avatar updated!" - } + avatar ?: run { + system.avatarUrl ?: run { + ctx.respondWarning("Member doesn't have an avatar set.") + return true + } + + ctx.respondEmbed { + image = system.avatarUrl + color = system.color.kordColor() + } + return true + } - private suspend fun avatarClear(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.avatarUrl = null + system.avatarUrl = avatar database.updateSystem(system) - return "System avatar cleared!" - } + ctx.respondSuccess("Member's avatar updated!") - private suspend fun avatarRaw(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return "`${system.avatarUrl}`" + return true } - private suspend fun avatarEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return system.avatarUrl ?: "System avatar not set." - } + private suspend fun tag(ctx: DiscordContext, system: SystemRecord, tag: String?, raw: Boolean, clear: Boolean): Boolean { + if (clear) { + system.tag = null + database.updateSystem(system) + ctx.respondSuccess("System tag cleared!") + return true + } - private suspend fun tag(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.tag = ctx.params["tag"]!![0] - database.updateSystem(system) - return "System tag updated!" - } + tag ?: run { + system.tag ?: run { + ctx.respondFailure("System doesn't have a tag set.") + return false + } - private suspend fun tagClear(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - system.tag = null - database.updateSystem(system) - return "System tag cleared!" - } + if (raw) { + ctx.respondPlain("`${system.tag}`") + return true + } - private suspend fun tagRaw(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return "`${system.tag}`" - } + ctx.respondSuccess("System's tag is ${system.tag}") - private suspend fun tagEmpty(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return system.tag ?: "System tag not set." - } + return true + } - private suspend fun delete(ctx: MessageHolder): String { - val author = ctx.message.author!! - database.fetchSystemFromUser(author) - ?: return "System does not exist. Create one using `pf>system new`" + system.tag = tag + database.updateSystem(system) + ctx.respondSuccess("System tag updated to $tag!") + return true + } + private suspend fun delete(ctx: DiscordContext, system: SystemRecord): Boolean { TimedYesNoPrompt.build( - runner = author.id, - channel = ctx.message.channel, + runner = ctx.getUser()!!.id, + channel = ctx.getChannel(), message = "Are you sure you want to delete your system?\n" + "The data will be lost forever (A long time!)", yes = Button("Delete system", Button.wastebasket, ButtonStyle.Danger) { - val export = Exporter.export(author.id.value) - ctx.sendFiles(NamedFile("system.json", export.byteInputStream())) - database.dropSystem(author) + val export = Exporter.export(ctx.getUser()!!.id.value) + ctx.respondFiles(null, NamedFile("system.json", export.byteInputStream())) + database.dropSystem(ctx.getUser()!!) content = "System deleted." }, ) - return "" + return true } } \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index ff86854d..5fe3d5d5 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -23,6 +23,9 @@ import dev.proxyfox.command.NodeActionParam import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.int +import dev.proxyfox.command.node.builtin.string +import dev.proxyfox.database.database +import dev.proxyfox.database.records.system.SystemRecord abstract class DiscordContext(override val value: T) : CommandContext() { abstract fun getAttachment(): Attachment? @@ -55,4 +58,17 @@ suspend fun> CommandNode.guild(action: NodeActionP Snowflake(it()) } } +} + +suspend fun> CommandNode.system(action: NodeActionParam) { + action { + val ctx = this as? DiscordContext ?: return@action null + database.fetchSystemFromUser(ctx.getUser()) + } + // TODO: Check trust + string("sysid") { + action { + null + } + } } \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt index 8ff50c63..8d232063 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt @@ -24,7 +24,7 @@ class AttachmentNode>(override val name: String) : Comma } } -suspend fun > NodeHolder.attachment( +suspend fun > NodeHolder.attachment( name: String, action: NodeActionParam ): CommandNode { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt index c8b91cca..4f34020d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt @@ -42,6 +42,7 @@ import kotlin.time.Duration.Companion.minutes // Created 2022-15-10T08:37:40 +// TODO: Move to DiscordContext /** * @author Ampflower * @since ${version} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt index 32be2676..01920b83 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt @@ -26,6 +26,7 @@ import dev.proxyfox.bot.prompts.Button.Companion.multiply import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes +// TODO: Move to DiscordContext /** * @author Ampflower * @since ${version} diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt index 1dca6acf..91ada221 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt @@ -82,3 +82,9 @@ fun getRamUsage(): Long = getMaxRam() - getFreeRam() fun getRamUsagePercentage(): Double = (getRamUsage().toDouble() / getMaxRam().toDouble()) * 100 fun getThreadCount() = threadMXBean.threadCount + +fun Array.trimEach() { + forEachIndexed { i, s -> + this[i] = s.trim() + } +} \ No newline at end of file From 767ebe5e5f9ed19e3f46e6228e466073edad9c84 Mon Sep 17 00:00:00 2001 From: Octal Date: Tue, 15 Nov 2022 18:08:04 -0600 Subject: [PATCH 013/137] Misc fixes --- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt | 2 ++ .../main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt | 3 +-- .../kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt | 2 ++ .../dev/proxyfox/bot/command/context/DiscordMessageContext.kt | 4 ++++ .../proxyfox/bot/command/context/InteractionCommandContext.kt | 4 ++++ 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 484a70ee..db219ed0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -38,6 +38,7 @@ import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException import dev.proxyfox.bot.command.Commands import dev.proxyfox.bot.command.MemberCommands.registerMemberCommands +import dev.proxyfox.bot.command.SwitchCommands.registerSwitchCommands import dev.proxyfox.bot.command.SystemCommands.registerSystemCommands import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.command.node.CommandNode @@ -170,6 +171,7 @@ suspend fun Kord.registerApplicationCommands() { createGlobalMessageCommand("Edit Message") registerMemberCommands() registerSystemCommands() + registerSwitchCommands() } suspend fun updatePresence() { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 0ae2a156..6587ee70 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -208,8 +208,7 @@ object SwitchCommands { runner = ctx.getUser()!!.id, channel = ctx.getChannel(), message = """ - Are you sure you want to delete the latest switch (${switch.membersAsString()}, )? - $oldSwitch + Are you sure you want to delete the latest switch (${switch.membersAsString()}, )? ${if (oldSwitch != null) "\nThe previous switch would be at " else ""} The data will be lost forever (A long time!) """.trimIndent(), yes = Button("Delete switch", Button.wastebasket, ButtonStyle.Danger) { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index 5fe3d5d5..facf409a 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -41,6 +41,8 @@ abstract class DiscordContext(override val value: T) : CommandContext() { val author = getMember() ?: return false return author.getPermissions().contains(permission) } + + abstract suspend fun respondPager() } // Get a DiscordContext. diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index 85769f62..bc86d38a 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -65,6 +65,10 @@ class DiscordMessageContext(message: Message, override val command: String): Dis if (value.getGuildOrNull() != null) value.delete(reason) } + override suspend fun respondPager() { + TODO("Not yet implemented") + } + override suspend fun respondPlain(text: String, private: Boolean): Message { return getChannel(private).createMessage(text) } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index 8cb56032..b66756e0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -134,4 +134,8 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : override suspend fun tryDeleteTrigger(reason: String?) { } + + override suspend fun respondPager() { + TODO("Not yet implemented") + } } \ No newline at end of file From a7243f93c2ec35566b853c4586d0e4b2d5ad5e7b Mon Sep 17 00:00:00 2001 From: Octal Date: Wed, 16 Nov 2022 17:37:24 -0600 Subject: [PATCH 014/137] Finish slash command support --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 4 +- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 24 +- .../dev/proxyfox/bot/command/Commands.kt | 8 +- .../dev/proxyfox/bot/command/MiscCommands.kt | 918 ++++++++++++------ .../proxyfox/bot/command/SystemCommands.kt | 2 +- .../bot/command/context/DiscordContext.kt | 10 +- .../command/context/DiscordMessageContext.kt | 21 + .../context/InteractionCommandContext.kt | 27 +- 8 files changed, 679 insertions(+), 335 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index db219ed0..31800fbe 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -38,6 +38,7 @@ import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException import dev.proxyfox.bot.command.Commands import dev.proxyfox.bot.command.MemberCommands.registerMemberCommands +import dev.proxyfox.bot.command.MiscCommands.registerMiscCommands import dev.proxyfox.bot.command.SwitchCommands.registerSwitchCommands import dev.proxyfox.bot.command.SystemCommands.registerSystemCommands import dev.proxyfox.bot.command.context.DiscordContext @@ -172,6 +173,7 @@ suspend fun Kord.registerApplicationCommands() { registerMemberCommands() registerSystemCommands() registerSwitchCommands() + registerMiscCommands() } suspend fun updatePresence() { @@ -196,7 +198,7 @@ suspend fun updatePresence() { else -> throw IllegalStateException("Count is not 0, 1, or 2!") } kord.editPresence { - watching("for pf>help! $append") + watching("for /info help! $append") } delay(120000) } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index 734cb357..fcaa34c9 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -22,10 +22,7 @@ import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.event.message.MessageUpdateEvent import dev.kord.core.event.message.ReactionAddEvent import dev.kord.rest.builder.message.create.embed -import dev.proxyfox.bot.command.Commands -import dev.proxyfox.bot.command.MemberCommands -import dev.proxyfox.bot.command.SwitchCommands -import dev.proxyfox.bot.command.SystemCommands +import dev.proxyfox.bot.command.* import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.DiscordMessageContext import dev.proxyfox.bot.command.context.InteractionCommandContext @@ -65,17 +62,10 @@ suspend fun MessageCreateEvent.onMessageCreate() { val contentWithoutRegex = content.substring(matcher.end()) if (contentWithoutRegex.isBlank() && matcher.start(1) >= 0) { - channel.createMessage("Hi, I'm ProxyFox! My prefix is `pf>`.") + channel.createMessage("Hi, I'm ProxyFox! My prefix is `pf>`. I also support slash commands!") } else { - // Currently running both parsers for testing. TODO: Remove legacy parser // Run the command - val output = parseString(contentWithoutRegex, message) ?: run { - Commands.parser.parse(DiscordMessageContext(message, contentWithoutRegex) as DiscordContext) - return - } - // Send output message if exists - if (output.isNotBlank()) - channel.createMessage(output) + Commands.parser.parse(DiscordMessageContext(message, contentWithoutRegex) as DiscordContext) } } else if (channel is GuildMessageChannel && channel.selfHasPermissions(Permissions(Permission.ManageWebhooks, Permission.ManageMessages))) { val guild = channel.getGuild() @@ -316,7 +306,13 @@ suspend fun ChatInputCommandInteractionCreateEvent.onInteract() { SwitchCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } } else -> { - + val command = interaction.command as? SubCommand ?: return + when(command.rootName) { + "info" -> MiscCommands.infoInteractionExecutors + "moderation" -> MiscCommands.moderationInteractionExecutors + "misc" -> MiscCommands.miscInteractionExecutors + else -> return + }[command.name]?.let { it(InteractionCommandContext(this)) } } } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 86381048..eb004a24 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -60,17 +60,17 @@ fun GlobalChatInputCreateBuilder.access(type: String, name: String, builder: Sub subCommand(name, "Accesses the $type's $name", builder) } -suspend fun checkSystem(ctx: DiscordContext, system: SystemRecord?): Boolean { +suspend fun checkSystem(ctx: DiscordContext, system: SystemRecord?, private: Boolean = false): Boolean { system ?: run { - ctx.respondFailure("System does not exist. Create one using a slash command or `pf>system new`") + ctx.respondFailure("System does not exist. Create one using a slash command or `pf>system new`", private) return false } return true } -suspend fun checkMember(ctx: DiscordContext, member: MemberRecord?): Boolean { +suspend fun checkMember(ctx: DiscordContext, member: MemberRecord?, private: Boolean = false): Boolean { member ?: run { - ctx.respondFailure("Member does not exist. Create one using a slash command or `pf>member new`") + ctx.respondFailure("Member does not exist. Create one using a slash command or `pf>member new`", private) return false } return true diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 56b8c5be..477be7d2 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -14,10 +14,13 @@ import dev.kord.core.Kord import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior import dev.kord.core.entity.Guild +import dev.kord.core.entity.Member import dev.kord.core.entity.Message import dev.kord.rest.NamedFile +import dev.kord.rest.builder.interaction.* import dev.proxyfox.bot.* import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.guild import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.command.node.attachment @@ -29,8 +32,7 @@ import dev.proxyfox.bot.string.parser.MessageHolder import dev.proxyfox.bot.string.parser.registerCommand import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil -import dev.proxyfox.command.node.builtin.greedy -import dev.proxyfox.command.node.builtin.literal +import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.* import dev.proxyfox.database.database import dev.proxyfox.database.displayDate @@ -40,6 +42,7 @@ import dev.proxyfox.database.etc.importer.import import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.ProxiedMessageRecord +import dev.proxyfox.database.records.misc.ServerSettingsRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord import kotlinx.coroutines.Dispatchers @@ -56,16 +59,197 @@ import kotlin.math.floor * */ object MiscCommands { private val roleMatcher = Regex("\\d+") + var infoInteractionExecutors: HashMap Boolean> = hashMapOf() + var moderationInteractionExecutors: HashMap Boolean> = hashMapOf() + var miscInteractionExecutors: HashMap Boolean> = hashMapOf() + + fun SubCommandBuilder.runs(rootName: String, action: suspend InteractionCommandContext.() -> Boolean) { + when (rootName) { + "info" -> infoInteractionExecutors + "moderation" -> moderationInteractionExecutors + "misc" -> miscInteractionExecutors + else -> return + }[name] = action + } suspend fun Kord.registerMiscCommands() { createGlobalChatInputCommand("info", "Fetches info about the bot") { - + subCommand("debug", "Fetch debug information about the bot") { + runs("info") { + debug(this) + } + } + subCommand("help", "Get help information") { + runs("info") { + respondSuccess(getHelp()) + true + } + } + subCommand("about", "Get about information") { + runs("info") { + respondSuccess(getExplain()) + true + } + } + subCommand("source", "Get the source code") { + runs("info") { + respondSuccess(getSource()) + true + } + } + subCommand("invite", "Get the bot invite and the support server invite") { + runs("info") { + respondSuccess(getSource()) + true + } + } } createGlobalChatInputCommand("moderation", "Moderator-only commands") { - + subCommand("role", "Access the role required for proxying") { + role("role", "The role required for proxying") { + required = false + } + clear() + runs("moderation") { + val role = value.interaction.command.roles["role"] + val clear = value.interaction.command.booleans["clear"] ?: false + role(this, role?.id?.value?.toString(), clear) + } + } + subCommand("mod-delay", "The amount of time to delay proxying for moderation bots") { + name("delay") + runs("moderation") { + val delay = value.interaction.command.strings["delay"] + val guild = getGuild() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + delay(this, database.getOrCreateServerSettings(guild), delay) + } + } + subCommand("channel-proxy", "Toggle proxying for a specific channel") { + channel("channel", "The channel to set") { + required = true + } + bool("value", "The value to set") + runs("moderation") { + val channel = value.interaction.command.channels["channel"]!!.id.value.toString() + val enabled = value.interaction.command.booleans["value"] + channelProxy(this, channel, enabled) + } + } } createGlobalChatInputCommand("misc", "Other commands that don't fit in a category") { - + subCommand("fox", "Gets a random fox picture") { + runs("misc") { + getFox(this) + } + } + subCommand("import", "Import a system") { + attachment("import", "The file to import") { + required = true + } + runs("misc") { + import(this, URL(value.interaction.command.attachments["import"]!!.url)) + } + } + subCommand("export", "Export your system") { + runs("misc") { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + export(this) + } + } + subCommand("time", "Displays the current time") { + runs("misc") { + time(this) + } + } + subCommand("autoproxy", "Changes the autoproxy type") { + name("value") + runs("misc") { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val type: AutoProxyMode? = when(value.interaction.command.strings["value"]) { + null -> null + "off", "disable" -> AutoProxyMode.OFF + "latch", "l" -> AutoProxyMode.LATCH + "front", "f" -> AutoProxyMode.FRONT + else -> AutoProxyMode.MEMBER + } + val member = if (type == AutoProxyMode.MEMBER) { + val mem = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, mem)) return@runs false + mem + } else null + proxy(this, system!!, type, member) + } + } + subCommand("proxy", "Toggles proxying for this server") { + bool("value", "the value to set") + guild() + runs("misc") { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val enabled = value.interaction.command.booleans["value"] + val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id + guildId ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + serverProxy(this, serverSystem, enabled) + } + } + subCommand("serverautoproxy", "Changes the autoproxy type for the server") { + name("value") + guild() + runs("misc") { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val type: AutoProxyMode? = when(value.interaction.command.strings["value"]) { + null -> null + "off", "disable" -> AutoProxyMode.OFF + "latch", "l" -> AutoProxyMode.LATCH + "front", "f" -> AutoProxyMode.FRONT + else -> AutoProxyMode.MEMBER + } + val member = if (type == AutoProxyMode.MEMBER) { + val mem = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + if (!checkMember(this, mem)) return@runs false + mem + } else null + val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id + guildId ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + serverAutoProxy(this, serverSystem, type, member) + } + } + subCommand("edit", "Edit a message proxied by ProxyFox") { + string("content", "The content to replace with") { + required = true + } + int("message", "The message ID to edit") { + required = false + } + runs("misc") { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + val message = value.interaction.command.integers["message"]?.toULong()?.let { Snowflake(it) } + editMessage(this, system!!, message, value.interaction.command.strings["content"]!!) + } + } } } @@ -91,7 +275,7 @@ object MiscCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - export(this, system!!) + export(this) } } Commands.parser.literal("time") { @@ -137,7 +321,7 @@ object MiscCommands { return@runs false } val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) - serverProxy(this, system, serverSystem, null) + serverProxy(this, serverSystem, null) } literal("on", "enable") { runs { @@ -152,7 +336,7 @@ object MiscCommands { return@runs false } val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) - serverProxy(this, system, serverSystem, true) + serverProxy(this, serverSystem, true) } } literal("off", "disable") { @@ -168,7 +352,7 @@ object MiscCommands { return@runs false } val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) - serverProxy(this, system, serverSystem, false) + serverProxy(this, serverSystem, false) } } } @@ -210,52 +394,267 @@ object MiscCommands { } } } + Commands.parser.literal("serverautoproxy", "sap") { + guild { getGuildId -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + serverAutoProxy(this, serverSystem, null, null) + } + literal("off", "disable", "o") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + serverAutoProxy(this, serverSystem, AutoProxyMode.OFF, null) + } + } + literal("on", "enable", "fallback", "fb") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + serverAutoProxy(this, serverSystem, AutoProxyMode.FALLBACK, null) + } + } + literal("latch", "l") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + serverAutoProxy(this, serverSystem, AutoProxyMode.LATCH, null) + } + } + literal("front", "f") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + serverAutoProxy(this, serverSystem, AutoProxyMode.FRONT, null) + } + } + greedy("member") { getMem -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member)) return@runs false + val guildId = getGuildId() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + val guild = kord.getGuild(guildId) ?: run { + respondFailure("Cannot find server. Am I in it?") + return@runs false + } + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) + serverAutoProxy(this, serverSystem, AutoProxyMode.MEMBER, member) + } + } + } + } + Commands.parser.literal("role") { + runs { + role(this, null, false) + } + unixLiteral("clear", "remove") { + runs { + role(this, null, true) + } + } + greedy("role") { getRole -> + runs { + role(this, getRole(), false) + } + } + } - registerCommand(literal(arrayOf("serverautoproxy", "sap"), ::serverAutoProxyEmpty) { - literal(arrayOf("off", "disable"), ::serverAutoProxyOff) - literal(arrayOf("latch", "l"), ::serverAutoProxyLatch) - literal(arrayOf("front", "f"), ::serverAutoProxyFront) - literal(arrayOf("on", "enable", "fallback", "fb"), ::serverAutoProxyFallback) - greedy("member", MiscCommands::serverAutoProxyMember) - }) - - registerCommand(literal("role", ::roleEmpty) { - unixLiteral("clear", ::roleClear) - greedy("role", ::role) - }) + Commands.parser.literal("moddelay") { + runs { + val guild = getGuild() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + delay(this, database.getOrCreateServerSettings(guild), null) + } + greedy("delay") { getDelay -> + runs { + val guild = getGuild() ?: run { + respondFailure("Command not ran in server.") + return@runs false + } + delay(this, database.getOrCreateServerSettings(guild), getDelay()) + } + } + } - registerCommand(literal("moddelay", ::delayEmpty) { - greedy("delay", ::delay) - }) + Commands.parser.literal("delete", "del") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + deleteMessage(this, system!!, null) + } + int("message") { getMessage -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + deleteMessage(this, system!!, Snowflake(getMessage())) + } + } + } - registerCommand(literal(arrayOf("delete", "del"), ::deleteMessage) { - greedy("message", ::deleteMessage) - }) + Commands.parser.literal("reproxy", "rp") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + reproxyMessage(this, system!!, null, null) + } + int("message") { getMessage -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + reproxyMessage(this, system!!, Snowflake(getMessage()), null) + } + greedy("member") { getMem -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member, true)) return@runs false + reproxyMessage(this, system, Snowflake(getMessage()), member!!) + } + } + } + greedy("member") { getMem -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + val member = database.findMember(system!!.id, getMem()) + if (!checkMember(this, member, true)) return@runs false + reproxyMessage(this, system, null, member!!) + } + } + } - registerCommand(literal(arrayOf("reproxy", "rp"), ::reproxyMessage) { - greedy("member", ::reproxyMessage) - }) + Commands.parser.literal("info", "i") { + runs { + fetchMessageInfo(this, null) + } + int("message") { getMessage -> + runs { + fetchMessageInfo(this, Snowflake(getMessage())) + } + } + } - registerCommand(literal(arrayOf("info", "i"), ::fetchMessageInfo) { - greedy("message", MiscCommands::fetchMessageInfo) - }) + Commands.parser.literal("ping", "p") { + runs { + pingMessageAuthor(this, null) + } + int("message") { getMessage -> + runs { + pingMessageAuthor(this, Snowflake(getMessage())) + } + } + } - registerCommand(literal(arrayOf("ping", "p"), ::pingMessageAuthor) { - greedy("message", ::pingMessageAuthor) - }) + Commands.parser.literal("edit", "e") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + editMessage(this, system!!, null, null) + } + int("message") { getMessage -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + editMessage(this, system!!, Snowflake(getMessage()), null) + } - registerCommand(literal(arrayOf("edit", "e"), ::editMessage) { - greedy("content", ::editMessage) - }) + greedy("content") { getContent -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + editMessage(this, system!!, Snowflake(getMessage()), getContent()) + } + } + } + greedy("content") { getContent -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + editMessage(this, system!!, null, getContent()) + } + } + } - registerCommand(literal(arrayOf("channel", "c"), ::channelEmpty) { - literal(arrayOf("proxy", "p"), ::channelProxy) { - string("channel", ::channelProxy) { - literal(arrayOf("on", "enable"), ::channelProxyEnable) - literal(arrayOf("of", "disable"), ::channelProxyDisable) + Commands.parser.literal("channel", "c") { + runs { + respondFiles("Please provide a channel subcommand") + false + } + literal("proxy", "p") { + runs { + channelProxy(this, null, null) + } + string("channel") { getChannelMention -> + runs { + channelProxy(this, getChannelMention(), null) + } + literal("on", "true", "enable", "1") { + runs { + channelProxy(this, getChannelMention(), true) + } + } + literal("off", "false", "disable", "0") { + runs { + channelProxy(this, getChannelMention(), false) + } + } } } - }) + } Commands.parser.literal("debug") { runs { @@ -368,7 +767,7 @@ object MiscCommands { } } - private suspend fun export(ctx: DiscordContext, system: SystemRecord): Boolean { + private suspend fun export(ctx: DiscordContext): Boolean { val export = Exporter.export(ctx.getUser()!!.id.value) ctx.respondFiles(null, NamedFile("system.json", export.byteInputStream())) ctx.respondSuccess("Check your DMs~") @@ -381,6 +780,7 @@ object MiscCommands { return true } + // TODO: Provide better help private fun getHelp(): String = """To view commands for ProxyFox, visit For quick setup: @@ -401,7 +801,8 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" private suspend fun proxy(ctx: DiscordContext, system: SystemRecord, mode: AutoProxyMode?, member: MemberRecord?): Boolean { mode ?: run { - ctx.respondSuccess("Autoproxy is set to ${system.autoType.name}") + val currMember = system.autoProxy?.let { database.fetchMemberFromSystem(system.id, it) } + ctx.respondSuccess("Autoproxy is set to ${currMember?.showDisplayName() ?: system.autoType.name}") return true } @@ -412,61 +813,21 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun serverAutoProxyEmpty(ctx: MessageHolder): String { - database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - return "Please provide whether you want autoproxy set to `off`, `latch`, `front`, or a member" - } - - private suspend fun serverAutoProxyLatch(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val systemServer = database.getOrCreateServerSettingsFromSystem(ctx.message.getGuild(), system.id) - systemServer.autoProxyMode = AutoProxyMode.LATCH - database.updateSystemServerSettings(systemServer) - return "Autoproxy mode for this server is now set to `latch`" - } - - private suspend fun serverAutoProxyFront(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val systemServer = database.getOrCreateServerSettingsFromSystem(ctx.message.getGuild(), system.id) - systemServer.autoProxyMode = AutoProxyMode.FRONT - database.updateSystemServerSettings(systemServer) - return "Autoproxy mode for this server is now set to `front`" - } - - private suspend fun serverAutoProxyFallback(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val systemServer = database.getOrCreateServerSettingsFromSystem(ctx.message.getGuild(), system.id) - systemServer.autoProxyMode = AutoProxyMode.FALLBACK - database.updateSystemServerSettings(systemServer) - return "Autoproxy for this server is now using your global settings." - } - - private suspend fun serverAutoProxyMember(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val member = database.findMember(system.id, ctx.params["member"]!![0]) - ?: return "Member does not exist. Create one using `pf>member new`" - val systemServer = database.getOrCreateServerSettingsFromSystem(ctx.message.getGuild(), system.id) - systemServer.autoProxyMode = AutoProxyMode.MEMBER - systemServer.autoProxy = member.id - database.updateSystemServerSettings(systemServer) - return "Autoproxy mode for this server is now set to ${member.name}" - } + private suspend fun serverAutoProxy(ctx: DiscordContext, systemServer: SystemServerSettingsRecord, mode: AutoProxyMode?, member: MemberRecord?): Boolean { + mode ?: run { + val currMember = systemServer.autoProxy?.let { database.fetchMemberFromSystem(systemServer.systemId, it) } + ctx.respondSuccess("Autoproxy is set to ${currMember?.showDisplayName() ?: systemServer.autoProxyMode.name}") + return true + } - private suspend fun serverAutoProxyOff(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - val systemServer = database.getOrCreateServerSettingsFromSystem(ctx.message.getGuild(), system.id) - systemServer.autoProxyMode = AutoProxyMode.OFF + systemServer.autoProxyMode = mode + val response = if (member != null) {systemServer.autoProxy = member.id; "Now autoproxying as ${member.showDisplayName()}"} else "Autoproxy mode is now set to ${mode.name}" database.updateSystemServerSettings(systemServer) - return "Autoproxy disabled for this server." + ctx.respondSuccess(response) + return true } - private suspend fun serverProxy(ctx: DiscordContext, system: SystemRecord, systemServer: SystemServerSettingsRecord, enabled: Boolean?): Boolean { + private suspend fun serverProxy(ctx: DiscordContext, systemServer: SystemServerSettingsRecord, enabled: Boolean?): Boolean { enabled ?: run { ctx.respondSuccess("Proxy for this server is currently ${if (systemServer.proxyEnabled) "enabled" else "disabled"}.") return false @@ -478,189 +839,157 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun roleEmpty(ctx: MessageHolder): String { - val server = database.getOrCreateServerSettings(ctx.message.getGuild()) - if (server.proxyRole == 0UL) return "There is no proxy role set." - return "Current role is <@&${server.proxyRole}>" - } - - private suspend fun role(ctx: MessageHolder): String { - if (!ctx.hasRequired(Permission.ManageGuild)) - return "You do not have the proper permissions to run this command" - val server = database.getOrCreateServerSettings(ctx.message.getGuild()) - val roleRaw = ctx.params["role"]!![0] + private suspend fun role(ctx: DiscordContext, roleRaw: String?, clear: Boolean): Boolean { + val server = database.getOrCreateServerSettings(ctx.getGuild() ?: run { + ctx.respondFailure("You are not in a server.") + return false + }) + if (!ctx.hasRequired(Permission.ManageGuild)) { + ctx.respondFailure("You do not have the proper permissions to run this command.") + return false + } + if (clear) { + server.proxyRole = 0UL + database.updateServerSettings(server) + ctx.respondSuccess("Role cleared!") + } + roleRaw ?: run { + if (server.proxyRole == 0UL) { + ctx.respondFailure("There is no proxy role set.") + return false + } + ctx.respondSuccess("Current role is <@&${server.proxyRole}>") + return true + } val role = roleMatcher.find(roleRaw)?.value?.toULong() - ?: ctx.message.getGuild().roles.filter { it.name == roleRaw }.firstOrNull()?.id?.value - ?: return "Please provide a role to set" + ?: ctx.getGuild()!!.roles.filter { it.name == roleRaw }.firstOrNull()?.id?.value + ?: run { + ctx.respondFailure("Please provide a role to set.") + return false + } server.proxyRole = role database.updateServerSettings(server) - return "Role updated!" - } - - private suspend fun roleClear(ctx: MessageHolder): String { - if (!ctx.hasRequired(Permission.ManageGuild)) - return "You do not have the proper permissions to run this command" - val server = database.getOrCreateServerSettings(ctx.message.getGuild()) - server.proxyRole = 0UL - database.updateServerSettings(server) - return "Role removed!" + ctx.respondSuccess("Role updated!") + return true } - private suspend fun delayEmpty(ctx: MessageHolder): String { - val server = database.getOrCreateServerSettings(ctx.message.getGuild()) - return if (server.moderationDelay <= 0) { - "There is no moderation delay present." - } else { - "Current moderation delay is ${server.moderationDelay}ms" + private suspend fun delay(ctx: DiscordContext, server: ServerSettingsRecord, delayStr: String?): Boolean { + if (!ctx.hasRequired(Permission.ManageGuild)) { + ctx.respondFailure("You do not have the proper permissions to run this command") + return false + } + delayStr ?: run { + if (server.moderationDelay <= 0) { + ctx.respondFailure("There is no moderation delay present.") + return false + } + ctx.respondSuccess("Current moderation delay is ${server.moderationDelay}ms") + return true + } + val delay = delayStr.parseDuration() + delay.right?.let { + ctx.respondFailure(it) + return false } - } - - private suspend fun delay(ctx: MessageHolder): String { - if (!ctx.hasRequired(Permission.ManageGuild)) - return "You do not have the proper permissions to run this command" - val server = database.getOrCreateServerSettings(ctx.message.getGuild()) - val delay = ctx.params["delay"]!![0].parseDuration() - delay.right?.let { return it } var millis = delay.left!!.inWholeMilliseconds if (millis > 30000L) { millis = 30000L } server.moderationDelay = millis.toShort() database.updateServerSettings(server) - return "Moderation delay set to ${millis}ms" - } - - private suspend fun getMessageFromContext(system: SystemRecord, ctx: MessageHolder): Pair { - val messageIdString = ctx.params["message"]?.get(0) - val messageSnowflake: Snowflake? = messageIdString?.let { Snowflake(it) } - val channelId = ctx.message.channelId - val channel = ctx.message.channel.fetchChannelOrNull() - var message = if (messageSnowflake != null) - channel?.getMessage(messageSnowflake) - else ctx.message.referencedMessage - - val databaseMessage = if (message != null) - database.fetchMessage(message.id) - else { - val m = database.fetchLatestMessage(system.id, channelId) - message = m?.newMessageId?.let { Snowflake(it) }?.let { nullOn404 { channel?.getMessage(it) } } - m - } - - return message to databaseMessage - } - private suspend fun getSystemlessMessage(ctx: MessageHolder): Pair { - val messageIdString = ctx.params["message"]?.get(0) - val messageSnowflake: Snowflake? = messageIdString?.let { Snowflake(it) } - val channel = ctx.message.channel.fetchChannelOrNull() - val message = if (messageSnowflake != null) - channel?.getMessage(messageSnowflake) - else ctx.message.referencedMessage - if (message == null) return null to null - val databaseMessage = database.fetchMessage(message.id) - return message to databaseMessage + ctx.respondSuccess("Moderation delay set to ${millis}ms!") + return true } - private suspend fun deleteMessage(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - if (system == null) { - ctx.respond("System does not exist. Create one using `pf>system new`", true) - return "" - } - val messages = getMessageFromContext(system, ctx) - val message = messages.first - if (message == null) { - ctx.respond("Unable to find message to delete.", true) - return "" + private suspend fun deleteMessage(ctx: DiscordContext, system: SystemRecord, message: Snowflake?): Boolean { + val messages = ctx.getDatabaseMessage(system, message) + val discordMessage = messages.first + discordMessage ?: run { + ctx.respondFailure("Unable to find message to delete.", true) + return false } val databaseMessage = messages.second - if (databaseMessage == null) { - ctx.respond("This message is either too old or wasn't proxied by ProxyFox", true) - return "" + databaseMessage ?: run { + ctx.respondFailure("This message is either too old or wasn't proxied by ProxyFox", true) + return false } if (databaseMessage.systemId != system.id) { - ctx.respond("You weren't the original creator of this message.", true) - return "" + ctx.respondFailure("You weren't the original creator of this message.", true) + return false } - message.delete("User requested message deletion.") - ctx.message.delete("User requested message deletion") + discordMessage.delete("User requested message deletion.") + ctx.tryDeleteTrigger("User requested message deletion") databaseMessage.deleted = true database.updateMessage(databaseMessage) - return "" + ctx.optionalSuccess("Message deleted.") + return true } - private suspend fun reproxyMessage(ctx: MessageHolder): String { - val guild = ctx.message.getGuildOrNull() ?: return "Run this in a server." - val system = database.fetchSystemFromUser(ctx.message.author) - if (system == null) { - ctx.respond("System does not exist. Create one using `pf>system new`", true) - return "" + private suspend fun reproxyMessage(ctx: DiscordContext, system: SystemRecord, message: Snowflake?, member: MemberRecord?): Boolean { + member ?: run { + ctx.respondFailure("Please provide the member to reproxy as.") + return false } - val messages = getMessageFromContext(system, ctx) - val message = messages.first - if (message == null) { - ctx.respond("Unable to find message to delete.", true) - return "" + + val messages = ctx.getDatabaseMessage(system, message) + val discordMessage = messages.first + discordMessage ?: run { + ctx.respondFailure("Unable to find message to delete.", true) + return false } val databaseMessage = messages.second - if (databaseMessage == null) { - ctx.respond("Targeted message is either too old or wasn't proxied by ProxyFox", true) - return "" + databaseMessage ?: run { + ctx.respondFailure("This message is either too old or wasn't proxied by ProxyFox", true) + return false } if (databaseMessage.systemId != system.id) { - ctx.respond("You weren't the original creator of the targeted message.", true) - return "" - } - val member = database.findMember(system.id, ctx.params["member"]?.get(0)!!) - if (member == null) { - ctx.respond("Couldn't find member to proxy as", true) - return "" + ctx.respondFailure("You weren't the original creator of this message.", true) + return false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(ctx.getGuild(), system.id, member.id) - val guildMessage = GuildMessage(message, guild, message.channel.asChannelOf(), ctx.message.author!!) + val guildMessage = GuildMessage(discordMessage, ctx.getGuild()!!, discordMessage.channel.asChannelOf(), ctx.getUser()!!) - WebhookUtil.prepareMessage(guildMessage, message.content, system, member, null, serverMember)?.send(true) + WebhookUtil.prepareMessage(guildMessage, discordMessage.content, system, member, null, serverMember)?.send(true) ?: throw AssertionError("Message could not be reproxied. Is the contents empty?") databaseMessage.deleted = true database.updateMessage(databaseMessage) - ctx.message.delete("User requested message deletion") - - return "" + ctx.tryDeleteTrigger("User requested message deletion") + ctx.optionalSuccess("Message reproxied.") + return true } - private suspend fun fetchMessageInfo(ctx: MessageHolder): String { - val messages = getSystemlessMessage(ctx) + private suspend fun fetchMessageInfo(ctx: DiscordContext, message: Snowflake?): Boolean { + val messages = ctx.getDatabaseMessage(null, message) val discordMessage = messages.first - if (discordMessage == null) { - ctx.respond("Unable to find message to fetch info of", true) - return "" + discordMessage ?: run { + ctx.respondFailure("Unable to find message to delete.", true) + return false } - val databaseMessage = messages.second - if (databaseMessage == null) { - ctx.respond("Targeted message is either too old or wasn't proxied by ProxyFox", true) - return "" + databaseMessage ?: run { + ctx.respondFailure("This message is either too old or wasn't proxied by ProxyFox", true) + return false } val system = database.fetchSystemFromId(databaseMessage.systemId) if (system == null) { - ctx.respond("Targeted message's system has since been deleted.", true) - return "" + ctx.respondFailure("Targeted message's system has since been deleted.", true) + return false } val member = database.fetchMemberFromSystem(databaseMessage.systemId, databaseMessage.memberId) if (member == null) { - ctx.respond("Targeted message's member has since been deleted.", true) - return "" + ctx.respondFailure("Targeted message's member has since been deleted.", true) + return false } val guild = discordMessage.getGuild() val settings = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) - ctx.respond(dm=true) { + ctx.respondEmbed(true) { val systemName = system.name ?: system.id author { name = member.displayName?.let { "$it (${member.name})\u2007•\u2007$systemName" } ?: "${member.name}\u2007•\u2007$systemName" @@ -700,112 +1029,87 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" timestamp = system.timestamp.toKtInstant() } - ctx.message.delete("User requested message deletion") - - return "" + ctx.tryDeleteTrigger("User requested message deletion") + ctx.optionalSuccess("Info fetched.") + return true } - private suspend fun editMessage(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - if (system == null) { - ctx.respond("System does not exist. Create one using `pf>system new`", true) - return "" - } - val messages = getMessageFromContext(system, ctx) - val message = messages.first - if (message == null) { - ctx.respond("Unable to find message to edit.", true) - return "" + private suspend fun editMessage(ctx: DiscordContext, system: SystemRecord, message: Snowflake?, content: String?): Boolean { + val messages = ctx.getDatabaseMessage(system, message) + val discordMessage = messages.first + discordMessage ?: run { + ctx.respondFailure("Unable to find message to delete.", true) + return false } - val channel = message.getChannel() val databaseMessage = messages.second - if (databaseMessage == null) { - ctx.respond("Targeted message is either too old or wasn't proxied by ProxyFox", true) - return "" - } - if (databaseMessage.systemId != system.id) { - ctx.respond("You weren't the original creator of the targeted message.", true) - return "" + databaseMessage ?: run { + ctx.respondFailure("This message is either too old or wasn't proxied by ProxyFox", true) + return false } - val content = ctx.params["content"]?.get(0) - if (content == null) { - ctx.respond( + content ?: run { + ctx.respondFailure( "Please provide message content to edit with.\n" + "To delete the message, run `pf>delete`", true ) - return "" + return false } - - val webhook = WebhookUtil.createOrFetchWebhookFromCache(channel) - webhook.edit(message.id, if (channel is ThreadChannelBehavior) channel.id else null) { + val channel = ctx.getChannel() + val webhook = WebhookUtil.createOrFetchWebhookFromCache(channel.fetchChannel()) + webhook.edit(discordMessage.id, if (channel is ThreadChannelBehavior) channel.id else null) { this.content = content } - ctx.message.delete("User requested message deletion") - - return "" + ctx.tryDeleteTrigger("User requested message deletion") + ctx.optionalSuccess("Edited message") + return true } - private suspend fun pingMessageAuthor(ctx: MessageHolder): String { - val messages = getSystemlessMessage(ctx) + private suspend fun pingMessageAuthor(ctx: DiscordContext, message: Snowflake?): Boolean { + val messages = ctx.getDatabaseMessage(null, message) val discordMessage = messages.first if (discordMessage == null) { - ctx.respond("Targeted message doesn't exist.", true) - return "" + ctx.respondFailure("Targeted message doesn't exist.", true) + return false } val databaseMessage = messages.second if (databaseMessage == null) { - ctx.respond("Targeted message is either too old or wasn't proxied by ProxyFox") - return "" + ctx.respondFailure("Targeted message is either too old or wasn't proxied by ProxyFox") + return false } - ctx.message.delete("User requested message deletion") + ctx.tryDeleteTrigger("User requested message deletion") // TODO: Add a jump to message embed - ctx.respond("Psst.. ${databaseMessage.memberName} (<@${databaseMessage.userId}>)$ellipsis You were pinged by <@${ctx.message.author!!.id}>") - return "" - } - - private suspend fun channelEmpty(ctx: MessageHolder): String { - return "Please provide a channel command" - } - - private suspend fun channelProxy(ctx: MessageHolder): String { - val channel = ctx.params["channel"]?.get(0) - ?: ctx.message.channelId.value.toString() - val channelId = channel.toULongOrNull() - ?: channel.substring(2, channel.length-1).toULongOrNull() - ?: return "Provided string is not a valid channel" - val channelSettings = database.getOrCreateChannel(ctx.message.getGuild().id.value, channelId) - return "Proxying is currently ${if (channelSettings.proxyEnabled) "enabled" else "disabled"} for <#$channelId>." - } - - private suspend fun channelProxyEnable(ctx: MessageHolder): String { - if (!ctx.hasRequired(Permission.ManageChannels)) - return "You do not have the proper permissions to run this command" - val channel = ctx.params["channel"]?.get(0) - ?: ctx.message.channelId.value.toString() - val channelId = channel.toULongOrNull() - ?: channel.substring(2, channel.length - 1).toULongOrNull() - ?: return "Provided string is not a valid channel" - val channelSettings = database.getOrCreateChannel(ctx.message.getGuild().id.value, channelId) - if (channelSettings.proxyEnabled) return "Proxying is already enabled for <#$channelId>" - channelSettings.proxyEnabled = true - database.updateChannel(channelSettings) - return "Proxying is now enabled for <#$channelId>" + ctx.getChannel().createMessage("Psst.. ${databaseMessage.memberName} (<@${databaseMessage.userId}>)$ellipsis You were pinged by <@${ctx.getUser()!!.id}>") + ctx.optionalSuccess("Author pinged.") + return true } - private suspend fun channelProxyDisable(ctx: MessageHolder): String { - if (!ctx.hasRequired(Permission.ManageChannels)) - return "You do not have the proper permissions to run this command" - val channel = ctx.params["channel"]?.get(0) - ?: ctx.message.channelId.value.toString() + private suspend fun channelProxy(ctx: DiscordContext, channel: String?, value: Boolean?): Boolean { + if (!ctx.hasRequired(Permission.ManageChannels)) { + ctx.respondFailure("You do not have the proper permissions to run this command") + return false + } + ctx.getGuild() ?: run { + ctx.respondFailure("You need to run this command in a server.") + } + channel ?: run { + ctx.respondFailure("Please provide a channel to change") + return false + } val channelId = channel.toULongOrNull() ?: channel.substring(2, channel.length - 1).toULongOrNull() - ?: return "Provided string is not a valid channel" - val channelSettings = database.getOrCreateChannel(ctx.message.getGuild().id.value, channelId) - if (!channelSettings.proxyEnabled) return "Proxying is already disabled for <#$channelId>" - channelSettings.proxyEnabled = false + ?: run { + ctx.respondFailure("Provided string is not a valid channel") + return false + } + val channelSettings = database.getOrCreateChannel(ctx.getChannel().id.value, channelId) + value ?: run { + ctx.respondSuccess("Proxying is currently ${if (channelSettings.proxyEnabled) "enabled" else "disabled"} for <#$channelId>.") + return true + } + channelSettings.proxyEnabled = value database.updateChannel(channelSettings) - return "Proxying is now disabled for <#$channelId>" + ctx.respondSuccess("Proxying is now ${if (value) "enabled" else "disabled"} for <#$channelId>") + return true } } \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index fb0e6839..0b0d79d5 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -434,7 +434,7 @@ object SystemCommands { system.name = name database.updateSystem(system) val add = if (name != null) "with name ${name}" else "" - ctx.respondSuccess("System created $add! See `pf>help` or `/pf-help` for how to set up your system further.") + ctx.respondSuccess("System created $add! See `pf>help` or `/info help` for how to set up your system further.") return true } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index facf409a..ef0d5bc9 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -11,10 +11,7 @@ package dev.proxyfox.bot.command.context import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.MessageChannelBehavior -import dev.kord.core.entity.Attachment -import dev.kord.core.entity.Guild -import dev.kord.core.entity.Member -import dev.kord.core.entity.User +import dev.kord.core.entity.* import dev.kord.rest.NamedFile import dev.kord.rest.builder.message.EmbedBuilder import dev.proxyfox.bot.kord @@ -25,6 +22,7 @@ import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.int import dev.proxyfox.command.node.builtin.string import dev.proxyfox.database.database +import dev.proxyfox.database.records.misc.ProxiedMessageRecord import dev.proxyfox.database.records.system.SystemRecord abstract class DiscordContext(override val value: T) : CommandContext() { @@ -37,12 +35,16 @@ abstract class DiscordContext(override val value: T) : CommandContext() { abstract suspend fun respondEmbed(private: Boolean = false, text: String? = null, embed: suspend EmbedBuilder.() -> Unit): T abstract suspend fun tryDeleteTrigger(reason: String? = null) + abstract suspend fun optionalSuccess(text: String): T + suspend fun hasRequired(permission: Permission): Boolean { val author = getMember() ?: return false return author.getPermissions().contains(permission) } abstract suspend fun respondPager() + + abstract suspend fun getDatabaseMessage(system: SystemRecord?, messageId: Snowflake?): Pair } // Get a DiscordContext. diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index bc86d38a..71a19ec1 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -8,6 +8,7 @@ package dev.proxyfox.bot.command.context +import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.entity.* @@ -16,6 +17,9 @@ import dev.kord.rest.builder.message.EmbedBuilder import dev.proxyfox.bot.prompts.TimedYesNoPrompt import dev.proxyfox.command.CommandContext import dev.proxyfox.common.applyAsync +import dev.proxyfox.database.database +import dev.proxyfox.database.records.misc.ProxiedMessageRecord +import dev.proxyfox.database.records.system.SystemRecord import kotlin.jvm.optionals.getOrNull class DiscordMessageContext(message: Message, override val command: String): DiscordContext(message) { @@ -65,10 +69,27 @@ class DiscordMessageContext(message: Message, override val command: String): Dis if (value.getGuildOrNull() != null) value.delete(reason) } + override suspend fun optionalSuccess(text: String): Message { + return value + } + override suspend fun respondPager() { TODO("Not yet implemented") } + override suspend fun getDatabaseMessage(system: SystemRecord?, messageId: Snowflake?): Pair { + val databaseMessage = if (messageId != null) { + database.fetchMessage(messageId) + } else if (value.referencedMessage != null) { + database.fetchMessage(value.referencedMessage!!.id) + } else if (system != null) { + database.fetchLatestMessage(system.id, getChannel().id) + } else null + databaseMessage ?: return null to null + val message = getChannel().getMessageOrNull(Snowflake(databaseMessage.newMessageId)) + return message to databaseMessage + } + override suspend fun respondPlain(text: String, private: Boolean): Message { return getChannel(private).createMessage(text) } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index b66756e0..85fe87a5 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -8,20 +8,21 @@ package dev.proxyfox.bot.command.context +import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.GuildChannelBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic -import dev.kord.core.entity.Attachment -import dev.kord.core.entity.Guild -import dev.kord.core.entity.Member -import dev.kord.core.entity.User +import dev.kord.core.entity.* import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.NamedFile import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed import dev.proxyfox.command.CommandContext +import dev.proxyfox.database.database +import dev.proxyfox.database.records.misc.ProxiedMessageRecord +import dev.proxyfox.database.records.system.SystemRecord import kotlin.jvm.optionals.getOrNull class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : @@ -132,10 +133,28 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : } override suspend fun tryDeleteTrigger(reason: String?) { + } + override suspend fun optionalSuccess(text: String): ChatInputCommandInteractionCreateEvent { + respondSuccess(text, true) + return value } override suspend fun respondPager() { TODO("Not yet implemented") } + + override suspend fun getDatabaseMessage( + system: SystemRecord?, + messageId: Snowflake? + ): Pair { + val databaseMessage = if (messageId != null) { + database.fetchMessage(messageId) + } else if (system != null) { + database.fetchLatestMessage(system.id, getChannel().id) + } else null + databaseMessage ?: return null to null + val message = getChannel().getMessageOrNull(Snowflake(databaseMessage.newMessageId)) + return message to databaseMessage + } } \ No newline at end of file From 5b6aa64123f506d45f3a83e38758d6e1a84d7279 Mon Sep 17 00:00:00 2001 From: Octal Date: Wed, 16 Nov 2022 17:47:35 -0600 Subject: [PATCH 015/137] Remove dev.proxyfox.bot.string package as it's unused now --- .../dev/proxyfox/bot/string/dsl/CommandDsl.kt | 146 ------------------ .../proxyfox/bot/string/node/GreedyNode.kt | 27 ---- .../proxyfox/bot/string/node/LiteralNode.kt | 47 ------ .../dev/proxyfox/bot/string/node/Node.kt | 19 --- .../dev/proxyfox/bot/string/node/NodeType.kt | 15 -- .../bot/string/node/StringListNode.kt | 75 --------- .../proxyfox/bot/string/node/StringNode.kt | 78 ---------- .../dev/proxyfox/bot/string/node/UnixNode.kt | 65 -------- .../bot/string/parser/MessageHolder.kt | 46 ------ .../bot/string/parser/StringParser.kt | 49 ------ 10 files changed, 567 deletions(-) delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/string/dsl/CommandDsl.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/GreedyNode.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/LiteralNode.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/Node.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/NodeType.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/StringListNode.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/StringNode.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/UnixNode.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/string/parser/MessageHolder.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/string/parser/StringParser.kt diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/dsl/CommandDsl.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/dsl/CommandDsl.kt deleted file mode 100644 index d0f5720c..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/dsl/CommandDsl.kt +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.string.dsl - -import dev.proxyfox.bot.string.node.* -import dev.proxyfox.bot.string.parser.MessageHolder -import dev.proxyfox.common.applyAsync - -suspend fun literal( - name: String, - executor: suspend MessageHolder.() -> String, - action: suspend Node.() -> Unit -): LiteralNode = LiteralNode(arrayOf(name), executor).applyAsync(action) - -fun literal( - name: String, - executor: suspend MessageHolder.() -> String -): LiteralNode = LiteralNode(arrayOf(name), executor) - -suspend fun Node.literal( - name: String, - executor: suspend MessageHolder.() -> String, - action: suspend Node.() -> Unit -): LiteralNode { - val node = LiteralNode(arrayOf(name), executor).applyAsync(action) - addSubNode(node) - return node -} - -fun Node.literal( - name: String, - executor: suspend suspend MessageHolder.() -> String -): LiteralNode { - val node = LiteralNode(arrayOf(name), executor) - addSubNode(node) - return node -} - -suspend fun literal( - names: Array, - executor: suspend MessageHolder.() -> String, - action: suspend Node.() -> Unit -): LiteralNode = LiteralNode(names, executor).applyAsync(action) - -fun literal( - names: Array, - executor: suspend MessageHolder.() -> String -): LiteralNode = LiteralNode(names, executor) - -suspend fun Node.literal( - names: Array, - executor: suspend MessageHolder.() -> String, - action: suspend Node.() -> Unit -): LiteralNode { - val node = LiteralNode(names, executor).applyAsync(action) - addSubNode(node) - return node -} - -fun Node.literal( - names: Array, - executor: suspend suspend MessageHolder.() -> String -): LiteralNode { - val node = LiteralNode(names, executor) - addSubNode(node) - return node -} - -suspend fun Node.unix( - name: String, - executor: suspend MessageHolder.() -> String, - action: suspend Node.() -> Unit -): UnixNode { - val node = UnixNode(name, executor).applyAsync(action) - addSubNode(node) - return node -} -suspend fun Node.unix( - name: String, - executor: suspend MessageHolder.() -> String -): UnixNode { - val node = UnixNode(name, executor) - addSubNode(node) - return node -} - -fun Node.unixLiteral( - name: Array, - executor: suspend MessageHolder.() -> String -): LiteralNode { - val node = LiteralNode(name.flatMap { listOf("-$it", "--$it") }.toTypedArray(), executor) - addSubNode(node) - return node -} - -fun Node.unixLiteral( - name: String, - executor: suspend MessageHolder.() -> String -): LiteralNode { - val node = LiteralNode(arrayOf("-$name", "--$name"), executor) - addSubNode(node) - return node -} - -fun Node.greedy( - name: String, - executor: suspend MessageHolder.() -> String -): GreedyNode { - val node = GreedyNode(name, executor) - addSubNode(node) - return node -} - -fun Node.stringList( - name: String, - executor: suspend MessageHolder.() -> String -): StringListNode { - val node = StringListNode(name, executor) - addSubNode(node) - return node -} - -suspend fun Node.string( - name: String, - executor: suspend MessageHolder.() -> String, - action: suspend Node.() -> Unit -): StringNode { - val node = StringNode(name, executor).applyAsync(action) - addSubNode(node) - return node -} - -fun Node.string( - name: String, - executor: suspend MessageHolder.() -> String -): StringNode { - val node = StringNode(name, executor) - addSubNode(node) - return node -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/GreedyNode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/GreedyNode.kt deleted file mode 100644 index f8295f91..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/GreedyNode.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.string.node - -import dev.proxyfox.bot.string.parser.MessageHolder - -class GreedyNode(val name: String, val executor: suspend MessageHolder.() -> String) : Node { - override val type: NodeType = NodeType.GREEDY - - override fun parse(string: String, holder: MessageHolder): Int { - if (string.isEmpty()) return 0 - holder.params[name] = arrayOf(string) - return string.length - } - - override fun getSubNodes(): Array = arrayOf() - - override fun addSubNode(node: Node) = Unit - - override suspend fun execute(holder: MessageHolder) = holder.executor() -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/LiteralNode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/LiteralNode.kt deleted file mode 100644 index 6007f33f..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/LiteralNode.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.string.node - -import dev.proxyfox.bot.string.parser.MessageHolder - -class LiteralNode(private val literals: Array, val executor: suspend MessageHolder.() -> String) : Node { - override val type: NodeType = NodeType.LITERAL - - private val literalNodes: ArrayList = ArrayList() - private val stringNodes: ArrayList = ArrayList() - private val greedyNodes: ArrayList = ArrayList() - - override fun parse(string: String, holder: MessageHolder): Int { - for (literal in literals) { - if (string.length < literal.length) continue - if (string.length == literal.length && string.lowercase() == literal.lowercase()) - return literal.length - if (string.lowercase().startsWith(literal.lowercase() + " ")) - return literal.length - } - return 0 - } - - override fun getSubNodes(): Array { - val literalArray: Array = literalNodes.toTypedArray() - val stringArray: Array = stringNodes.toTypedArray() - val greedyArray: Array = greedyNodes.toTypedArray() - return literalArray + stringArray + greedyArray - } - - override fun addSubNode(node: Node) { - when (node.type) { - NodeType.LITERAL -> literalNodes.add(node) - NodeType.VARIABLE -> stringNodes.add(node) - NodeType.GREEDY -> greedyNodes.add(node) - } - } - - override suspend fun execute(holder: MessageHolder) = holder.executor() -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/Node.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/Node.kt deleted file mode 100644 index 43403558..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/Node.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.string.node - -import dev.proxyfox.bot.string.parser.MessageHolder - -interface Node { - val type: NodeType - fun parse(string: String, holder: MessageHolder): Int - fun getSubNodes(): Array - fun addSubNode(node: Node) - suspend fun execute(holder: MessageHolder): String -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/NodeType.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/NodeType.kt deleted file mode 100644 index 020b9e65..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/NodeType.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.string.node - -enum class NodeType { - LITERAL, - VARIABLE, - GREEDY -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/StringListNode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/StringListNode.kt deleted file mode 100644 index 87868526..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/StringListNode.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.string.node - -import dev.proxyfox.bot.string.parser.MessageHolder - -class StringListNode(val name: String, val executor: suspend MessageHolder.() -> String) : Node { - override val type: NodeType = NodeType.GREEDY - - override fun parse(string: String, holder: MessageHolder): Int { - if (string.isEmpty()) return 0 - var i = 0 - val arr = ArrayList() - while (i < string.length) { - when (string[i]) { - '"' -> { - var out = "" - for (j in string.substring(1).indices) { - if (string[j] == '"') { - arr.add(out) - out = "" - i += j + 1 - continue - } - out += string[j].toString() - } - arr.add(out) - } - - '\'' -> { - var out = "" - for (j in string.substring(1).indices) { - if (string[j] == '\'') { - arr.add(out) - out = "" - i += j + 1 - continue - } - out += string[j].toString() - } - arr.add(out) - } - - else -> { - var out = "" - for (j in string.indices) { - if (string[j] == ' ') { - arr.add(out) - out = "" - i += j - continue - } - out += string[j].toString() - } - arr.add(out) - } - } - i = string.length - } - holder.params[name] = arr.toTypedArray() - return string.length - } - - override fun getSubNodes(): Array = arrayOf() - - override fun addSubNode(node: Node) = Unit - - override suspend fun execute(holder: MessageHolder): String = holder.executor() -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/StringNode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/StringNode.kt deleted file mode 100644 index 29a25a85..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/StringNode.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.string.node - -import dev.proxyfox.bot.string.parser.MessageHolder - -class StringNode(val name: String, val executor: suspend MessageHolder.() -> String) : Node { - override val type: NodeType = NodeType.VARIABLE - - private val literalNodes: ArrayList = ArrayList() - private val stringNodes: ArrayList = ArrayList() - private val greedyNodes: ArrayList = ArrayList() - - override fun parse(string: String, holder: MessageHolder): Int { - if (string.isEmpty()) return 0 - when (string[0]) { - '"' -> { - var out = "" - for (i in string.substring(1).indices) { - if (string[i+1] == '"') { - holder.params[name] = arrayOf(out) - return i + 2 - } - out += string[i+1].toString() - } - holder.params[name] = arrayOf(out) - } - - '\'' -> { - var out = "" - for (i in string.substring(1).indices) { - if (string[i+1] == '\'') { - holder.params[name] = arrayOf(out) - return i + 2 - } - out += string[i+1].toString() - } - holder.params[name] = arrayOf(out) - } - - else -> { - var out = "" - for (i in string.indices) { - if (string[i] == ' ') { - holder.params[name] = arrayOf(out) - return i - } - out += string[i].toString() - } - holder.params[name] = arrayOf(out) - } - } - return string.length - } - - override fun getSubNodes(): Array { - val literalArray: Array = literalNodes.toTypedArray() - val stringArray: Array = stringNodes.toTypedArray() - val greedyArray: Array = greedyNodes.toTypedArray() - return literalArray + stringArray + greedyArray - } - - override fun addSubNode(node: Node) { - when (node.type) { - NodeType.LITERAL -> literalNodes.add(node) - NodeType.VARIABLE -> stringNodes.add(node) - NodeType.GREEDY -> greedyNodes.add(node) - } - } - - override suspend fun execute(holder: MessageHolder): String = holder.executor() -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/UnixNode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/UnixNode.kt deleted file mode 100644 index 6458a594..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/node/UnixNode.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.string.node - -import dev.proxyfox.bot.string.parser.MessageHolder - -class UnixNode(val name: String, val executor: suspend MessageHolder.() -> String) : Node { - override val type: NodeType = NodeType.VARIABLE - - private val literalNodes: ArrayList = ArrayList() - private val stringNodes: ArrayList = ArrayList() - private val greedyNodes: ArrayList = ArrayList() - - override fun parse(string: String, holder: MessageHolder): Int { - if (string.isEmpty()) return 0 - var idx = 0 - val arr = ArrayList() - while (idx < string.length) { - while (idx < string.length) { - if (string[idx] != ' ') break - idx++ - } - var substr = string.substring(idx) - val start = - if (substr.startsWith("--")) "--" - else if (substr.startsWith("-")) "-" - else break - idx += start.length - substr = string.substring(idx) - var out = "" - for (i in substr) { - if (i == ' ') - break - out += i - } - arr.add(out) - idx += out.length - } - holder.params[name] = arr.toTypedArray() - return idx - } - - override fun getSubNodes(): Array { - val literalArray: Array = literalNodes.toTypedArray() - val stringArray: Array = stringNodes.toTypedArray() - val greedyArray: Array = greedyNodes.toTypedArray() - return literalArray + stringArray + greedyArray - } - - override fun addSubNode(node: Node) { - when (node.type) { - NodeType.LITERAL -> literalNodes.add(node) - NodeType.VARIABLE -> stringNodes.add(node) - NodeType.GREEDY -> greedyNodes.add(node) - } - } - - override suspend fun execute(holder: MessageHolder): String = holder.executor() -} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/parser/MessageHolder.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/parser/MessageHolder.kt deleted file mode 100644 index 40db8645..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/parser/MessageHolder.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.string.parser - -import dev.kord.common.entity.Permission -import dev.kord.core.behavior.channel.createMessage -import dev.kord.core.entity.Message -import dev.kord.rest.NamedFile -import dev.kord.rest.builder.message.EmbedBuilder -import dev.proxyfox.common.applyAsync - -data class MessageHolder( - val message: Message, - val params: HashMap> -) { - // TODO: Check if can send in channels - suspend fun respond(msg: String = "", dm: Boolean = false, embed: (suspend EmbedBuilder.() -> Unit)? = null): Message { - val channel = if (dm) - message.author?.getDmChannelOrNull() - ?: message.channel - else message.channel - - return channel.createMessage { - if (msg.isNotBlank()) content = msg - // TODO: an `embedAsync` helper function - if (embed != null) embeds.add(EmbedBuilder().applyAsync(embed)) - } - } - - suspend fun sendFiles(vararg files: NamedFile) { - message.author!!.getDmChannel().createMessage { - this.files.addAll(files) - } - } - - suspend fun hasRequired(permission: Permission): Boolean { - val author = message.getAuthorAsMember() ?: return false - return author.getPermissions().contains(permission) - } -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/parser/StringParser.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/parser/StringParser.kt deleted file mode 100644 index 4981c1d2..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/string/parser/StringParser.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.string.parser - -import dev.kord.core.entity.Message -import dev.proxyfox.bot.string.node.LiteralNode -import dev.proxyfox.bot.string.node.Node -import dev.proxyfox.common.logger - -val nodes: ArrayList = ArrayList() - -fun registerCommand(node: LiteralNode) { - nodes.add(node) -} - -suspend fun parseString(input: String, message: Message): String? { - for (node in nodes) { - val str = tryExecuteNode(input, node, MessageHolder(message, HashMap())) - if (str != null) return str - } - return null -} - -suspend fun tryExecuteNode(input: String, node: Node, holder: MessageHolder): String? { - // Parse out the command node - val idx = node.parse(input, holder) - // Check if returned index is greater than the string length or zero - if (idx == 0) return null - if (idx >= input.length) return execute(node, holder) - // Get a substring with the index - val subStr = input.substring(idx).trim() - // Loop through sub nodes and try to execute - for (subNode in node.getSubNodes()) { - val str = tryExecuteNode(subStr, subNode, holder) - if (str != null) return str - } - // Try to run the executor - return execute(node, holder) -} - -suspend fun execute(node: Node, holder: MessageHolder): String { - return node.execute(holder) -} \ No newline at end of file From 80befbec4d9b0e8b20a699000332674e1197c31b Mon Sep 17 00:00:00 2001 From: Octal Date: Wed, 16 Nov 2022 17:55:02 -0600 Subject: [PATCH 016/137] Fix build --- .../main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt | 6 ------ .../main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt | 5 ----- 2 files changed, 11 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 477be7d2..dd3758b0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -24,12 +24,6 @@ import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.guild import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.command.node.attachment -import dev.proxyfox.bot.string.dsl.greedy -import dev.proxyfox.bot.string.dsl.literal -import dev.proxyfox.bot.string.dsl.string -import dev.proxyfox.bot.string.dsl.unixLiteral -import dev.proxyfox.bot.string.parser.MessageHolder -import dev.proxyfox.bot.string.parser.registerCommand import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil import dev.proxyfox.command.node.builtin.* diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 6587ee70..e7e0736e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -20,11 +20,6 @@ import dev.proxyfox.bot.parseDuration import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.Pager import dev.proxyfox.bot.prompts.TimedYesNoPrompt -import dev.proxyfox.bot.string.dsl.greedy -import dev.proxyfox.bot.string.dsl.literal -import dev.proxyfox.bot.string.dsl.stringList -import dev.proxyfox.bot.string.parser.MessageHolder -import dev.proxyfox.bot.string.parser.registerCommand import dev.proxyfox.command.node.builtin.greedy import dev.proxyfox.command.node.builtin.literal import dev.proxyfox.command.node.builtin.stringList From 4670604cbae45ca7ff43776513df5161165071e8 Mon Sep 17 00:00:00 2001 From: Octal Date: Wed, 16 Nov 2022 18:01:27 -0600 Subject: [PATCH 017/137] Fix fix build build --- modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index fcaa34c9..a95c49fa 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -26,7 +26,6 @@ import dev.proxyfox.bot.command.* import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.DiscordMessageContext import dev.proxyfox.bot.command.context.InteractionCommandContext -import dev.proxyfox.bot.string.parser.parseString import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil import dev.proxyfox.common.ellipsis From 0fa816c3f8ce406ee40a73c5f37702fb93365a12 Mon Sep 17 00:00:00 2001 From: Octal Date: Sun, 20 Nov 2022 17:57:23 -0600 Subject: [PATCH 018/137] Add initial group implementation to database, add TODO for future refactor --- .../kotlin/dev/proxyfox/database/Database.kt | 15 ++++++++++ .../dev/proxyfox/database/JsonDatabase.kt | 9 ++++++ .../dev/proxyfox/database/MongoDatabase.kt | 16 +++++++++++ .../dev/proxyfox/database/NopDatabase.kt | 8 ++++++ .../dev/proxyfox/database/ProxyDatabase.kt | 9 ++++++ .../database/records/group/GroupRecord.kt | 28 +++++++++++++++++++ .../database/records/group/TagMode.kt | 7 +++++ 7 files changed, 92 insertions(+) create mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt create mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt index 5ca72308..e5547057 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt @@ -13,6 +13,7 @@ import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.ChannelBehavior import dev.proxyfox.database.records.DatabaseException +import dev.proxyfox.database.records.group.GroupRecord import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.member.MemberServerSettingsRecord @@ -35,6 +36,10 @@ import kotlin.time.Duration * @author Ampflower **/ // Suppression since unused warnings aren't useful for an API. +/* +* TODO: Move methods that require an id for an object in the database to use that object. +* ex: systemId: String, memberName: String -> system: SystemRecord, memberName: String +* */ @Suppress("unused") abstract class Database : AutoCloseable { abstract suspend fun setup(): Database @@ -465,6 +470,16 @@ abstract class Database : AutoCloseable { * */ abstract suspend fun fetchMemberFromSystemAndName(systemId: String, memberName: String, caseSensitive: Boolean = true): MemberRecord? + /** + * Gets the groups a member is a part of + * */ + abstract suspend fun fetchGroupsFromMember(member: MemberRecord): List + + /** + * Gets the members that are a part of a group + * */ + abstract suspend fun fetchMembersFromGroup(group: GroupRecord): List + /** * Gets a member by system ID and either member ID or name. * */ diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt index 153cca98..badbf2c3 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt @@ -17,6 +17,7 @@ import dev.proxyfox.common.ellipsis import dev.proxyfox.common.fromColor import dev.proxyfox.common.logger import dev.proxyfox.common.toColor +import dev.proxyfox.database.records.group.GroupRecord import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.member.MemberServerSettingsRecord @@ -510,6 +511,14 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { return systems[systemId]?.membersByName?.get(memberName)?.view() } + override suspend fun fetchGroupsFromMember(member: MemberRecord): List { + TODO("Not yet implemented") + } + + override suspend fun fetchMembersFromGroup(group: GroupRecord): List { + TODO("Not yet implemented") + } + override suspend fun export(other: Database) { val memberLookup = HashMap() logger.info("Migrating {} systems$ellipsis", systems.size) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index ebe371bf..89ea310f 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -14,6 +14,7 @@ import com.mongodb.reactivestreams.client.MongoCollection import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.ChannelBehavior import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.group.GroupRecord import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.member.MemberServerSettingsRecord @@ -73,6 +74,8 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { private lateinit var memberServers: KCollection + private lateinit var groups: KCollection + override suspend fun setup(): MongoDatabase { val connectionString = System.getenv("PROXYFOX_MONGO") kmongo = @@ -104,6 +107,8 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberServers = db.getOrCreateCollection() + groups = db.getOrCreateCollection() + return this } @@ -332,6 +337,17 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { return search.awaitFirstOrNull() } + override suspend fun fetchGroupsFromMember(member: MemberRecord): List { + return groups.find( + "systemId" eq member.systemId, + "members" eq member.id + ).toList() + } + + override suspend fun fetchMembersFromGroup(group: GroupRecord): List { + TODO("Not yet implemented") + } + override suspend fun export(other: Database) { TODO("Not yet implemented") } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt index bf4e056b..d376fb6b 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt @@ -10,6 +10,7 @@ package dev.proxyfox.database import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.ChannelBehavior +import dev.proxyfox.database.records.group.GroupRecord import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.member.MemberServerSettingsRecord @@ -127,6 +128,13 @@ class NopDatabase : Database() { override suspend fun fetchTotalMembersFromSystem(systemId: String): Int? = null override suspend fun fetchMemberFromSystemAndName(systemId: String, memberName: String, caseSensitive: Boolean): MemberRecord? = null + override suspend fun fetchGroupsFromMember(member: MemberRecord): List { + return emptyList() + } + + override suspend fun fetchMembersFromGroup(group: GroupRecord): List { + return emptyList() + } override suspend fun export(other: Database) {} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt index f74dd2b6..f153c11e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt @@ -10,6 +10,7 @@ package dev.proxyfox.database import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.ChannelBehavior +import dev.proxyfox.database.records.group.GroupRecord import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.member.MemberServerSettingsRecord @@ -193,6 +194,14 @@ open class ProxyDatabase(protected val proxy: T) : Database() { return proxy.fetchMemberFromSystemAndName(systemId, memberName) } + override suspend fun fetchGroupsFromMember(member: MemberRecord): List { + return proxy.fetchGroupsFromMember(member) + } + + override suspend fun fetchMembersFromGroup(group: GroupRecord): List { + return proxy.fetchMembersFromGroup(group) + } + override suspend fun export(other: Database) { proxy.export(other) } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt new file mode 100644 index 00000000..e5bce424 --- /dev/null +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt @@ -0,0 +1,28 @@ +package dev.proxyfox.database.records.group + +import dev.proxyfox.database.PkId +import dev.proxyfox.database.records.MongoRecord +import org.bson.types.ObjectId +import java.time.OffsetDateTime +import java.time.ZoneOffset + +class GroupRecord() : MongoRecord { + constructor(id: PkId, systemId: PkId, name: String) : this() { + this.id = id + this.systemId = systemId + this.name = name + } + + override var _id: ObjectId = ObjectId() + + var id: PkId = "" + var systemId: PkId = "" + var members: ArrayList = arrayListOf() + var name: String = "" + var description: String? = null + var color: Int = -1 + var avatarUrl: String? = null + var timestamp: OffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC) + var tag: String? = null + var tagMode: TagMode = TagMode.HIDDEN +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt new file mode 100644 index 00000000..f6280827 --- /dev/null +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt @@ -0,0 +1,7 @@ +package dev.proxyfox.database.records.group + +enum class TagMode { + HIDDEN, + BEFORE, + AFTER +} \ No newline at end of file From 7f013a0b7d69458bd782202068229ba0f94f39d0 Mon Sep 17 00:00:00 2001 From: Octal Date: Sun, 20 Nov 2022 17:58:57 -0600 Subject: [PATCH 019/137] Apply licenses to new files --- .../dev/proxyfox/database/records/group/GroupRecord.kt | 8 ++++++++ .../kotlin/dev/proxyfox/database/records/group/TagMode.kt | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt index e5bce424..71235aba 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.database.records.group import dev.proxyfox.database.PkId diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt index f6280827..10906586 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.database.records.group enum class TagMode { From 1b2f51ed1e601b3aa141e6d01e41f1246e94205c Mon Sep 17 00:00:00 2001 From: tibs Date: Sun, 20 Nov 2022 22:30:28 -0500 Subject: [PATCH 020/137] aaaaaa --- gradle/libs.versions.toml | 10 +- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 3 - .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 5 +- .../proxyfox/bot/command/MemberCommands.kt | 6 +- .../dev/proxyfox/bot/command/MiscCommands.kt | 5 +- .../proxyfox/bot/command/SwitchCommands.kt | 14 +- .../proxyfox/bot/command/SystemCommands.kt | 3 +- .../kotlin/dev/proxyfox/common/FoxFetch.kt | 6 +- modules/database/build.gradle.kts | 1 + .../kotlin/dev/proxyfox/database/Database.kt | 2 +- .../dev/proxyfox/database/JsonDatabase.kt | 66 +++---- .../dev/proxyfox/database/MongoDatabase.kt | 36 ++-- .../dev/proxyfox/database/NopDatabase.kt | 2 +- .../dev/proxyfox/database/ProxyDatabase.kt | 2 +- .../kotlin/dev/proxyfox/database/TimeUtil.kt | 42 ++--- .../database/etc/importer/Importer.kt | 31 ++-- .../etc/importer/PluralKitImporter.kt | 25 +-- .../etc/importer/TupperBoxImporter.kt | 9 +- .../database/etc/jackson/InstantAdaptor.kt | 58 ------ .../proxyfox/database/etc/types/PkTypes.kt | 170 ++++++++++-------- .../records/member/MemberProxyTagRecord.kt | 15 +- .../database/records/member/MemberRecord.kt | 15 +- .../member/MemberServerSettingsRecord.kt | 4 + .../records/misc/ChannelSettingsRecord.kt | 13 +- .../records/misc/ProxiedMessageRecord.kt | 11 +- .../records/misc/ServerSettingsRecord.kt | 10 +- .../database/records/misc/TokenRecord.kt | 6 +- .../database/records/misc/UserRecord.kt | 4 + .../system/SystemChannelSettingsRecord.kt | 13 +- .../database/records/system/SystemRecord.kt | 30 +++- .../system/SystemServerSettingsRecord.kt | 10 +- .../records/system/SystemSwitchRecord.kt | 19 +- 32 files changed, 324 insertions(+), 322 deletions(-) delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/jackson/InstantAdaptor.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 60aeac5d..6bdcae4f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ postgres = "42.3.3" kjdbc = "0.5.2" gson = "2.9.0" kmongo = "4.6.0" +kotlinx_datetime = "0.4.0" # API ktor = "2.1.3" @@ -33,9 +34,10 @@ kotlinx_coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c gson = { module = "com.google.code.gson:gson", version.ref = "gson" } kjdbc = { module = "com.vladsch.kotlin-jdbc:kotlin-jdbc", version.ref = "kjdbc" } postgres = { module = "org.postgresql:postgresql", version.ref = "postgres" } -kmongo_base = { module = "org.litote.kmongo:kmongo", version.ref = "kmongo" } -kmongo_coroutine = { module = "org.litote.kmongo:kmongo-coroutine", version.ref = "kmongo" } -kmongo_async = { module = "org.litote.kmongo:kmongo-async", version.ref = "kmongo" } +kmongo_base = { module = "org.litote.kmongo:kmongo-serialization", version.ref = "kmongo" } +kmongo_coroutine = { module = "org.litote.kmongo:kmongo-coroutine-serialization", version.ref = "kmongo" } +kmongo_async = { module = "org.litote.kmongo:kmongo-async-serialization", version.ref = "kmongo" } +kotlinx_datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx_datetime" } ktor_server = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } ktor_server_netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } @@ -48,7 +50,7 @@ kotlinx_coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t [bundles] base = ["guava", "logback", "kotlin_stdlib", "kotlinx_coroutines_core", "kord"] -database = ["gson", "kmongo_base", "kmongo_coroutine", "kmongo_async"] +database = ["gson", "kmongo_base", "kmongo_coroutine", "kmongo_async", "kotlinx_datetime"] api = ["ktor_server", "ktor_server_netty", "ktor_content_negotiation", "ktor_serialization"] test = ["testng", "kotlinx_coroutines_test", "mockk"] diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index fe66a3e9..69485ebb 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -48,7 +48,6 @@ import kotlinx.coroutines.flow.fold import kotlinx.datetime.Clock import kotlinx.datetime.Instant import java.lang.Integer.min -import java.time.OffsetDateTime import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -216,8 +215,6 @@ fun findUnixValue(args: Array, key: String): String? { return null } -fun OffsetDateTime.toKtInstant() = Instant.fromEpochSeconds(epochSeconds = toEpochSecond(), nanosecondAdjustment = nano) - fun Int.kordColor() = if (this < 0) null else Color(this) suspend fun EmbedBuilder.member(record: MemberRecord, serverId: ULong) { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index e80ab399..69b0afb5 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -33,6 +33,7 @@ import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord +import kotlinx.datetime.toJavaLocalDate import org.slf4j.LoggerFactory val prefixRegex = Regex("^(?:(<@!?${kord.selfId}>)|pf[>;!:])\\s*", RegexOption.IGNORE_CASE) @@ -271,14 +272,14 @@ suspend fun ReactionAddEvent.onReactionAdd() { member.birthday?.let { field { name = "Birthday" - value = it.displayDate() + value = it.toJavaLocalDate().displayDate() inline = true } } footer { text = "Member ID \u2009• \u2009${member.id}\u2007|\u2007System ID \u2009• \u2009${system.id}\u2007|\u2007Created " } - timestamp = system.timestamp.toKtInstant() + timestamp = system.timestamp } } message.deleteReaction(userId, emoji) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 1fd494c7..f631c9fb 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -19,11 +19,11 @@ import dev.proxyfox.bot.string.dsl.string import dev.proxyfox.bot.string.dsl.unixLiteral import dev.proxyfox.bot.string.parser.MessageHolder import dev.proxyfox.bot.string.parser.registerCommand -import dev.proxyfox.bot.toKtInstant import dev.proxyfox.common.* import dev.proxyfox.database.database import dev.proxyfox.database.displayDate import dev.proxyfox.database.tryParseLocalDate +import kotlinx.datetime.toJavaLocalDate /** * Commands for accessing and changing system settings @@ -147,7 +147,7 @@ object MemberCommands { member.birthday?.let { field { name = "Birthday" - value = it.displayDate() + value = it.toJavaLocalDate().displayDate() inline = true } } @@ -173,7 +173,7 @@ object MemberCommands { footer { text = "Member ID \u2009• \u2009${member.id}\u2007|\u2007System ID \u2009• \u2009${system.id}\u2007|\u2007Created " } - timestamp = member.timestamp.toKtInstant() + timestamp = member.timestamp } return "" } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 369e0a0e..20d17138 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext import kotlinx.datetime.Clock +import kotlinx.datetime.toJavaLocalDate import java.net.URL import kotlin.math.floor @@ -583,14 +584,14 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" member.birthday?.let { field { name = "Birthday" - value = it.displayDate() + value = it.toJavaLocalDate().displayDate() inline = true } } footer { text = "Member ID \u2009• \u2009${member.id}\u2007|\u2007System ID \u2009• \u2009${system.id}\u2007|\u2007Created " } - timestamp = system.timestamp.toKtInstant() + timestamp = system.timestamp } ctx.message.delete("User requested message deletion") diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 95a7cc5b..6e47fae6 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -21,7 +21,9 @@ import dev.proxyfox.bot.string.parser.registerCommand import dev.proxyfox.common.printStep import dev.proxyfox.database.database import dev.proxyfox.database.records.system.SystemSwitchRecord -import java.time.Instant +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.minus object SwitchCommands { suspend fun register() { @@ -60,7 +62,7 @@ object SwitchCommands { return it } - val nowMinus = Instant.now().minusMillis(either.left!!.inWholeMilliseconds) + val nowMinus = Clock.System.now().minus(either.left!!.inWholeMilliseconds, DateTimeUnit.MILLISECOND) if (oldSwitch != null && oldSwitch.timestamp > nowMinus) { return "It looks like you're trying to break the space-time continuum..\n" + "The provided time is set before the previous switch" @@ -73,7 +75,7 @@ object SwitchCommands { TimedYesNoPrompt.build( runner = author.id, channel = ctx.message.channel, - message = "Are you sure you want to move the switch $members back to ?", + message = "Are you sure you want to move the switch $members back to ?", yes = Button("Move switch", Button.move, ButtonStyle.Primary) { switch.timestamp = nowMinus database.updateSwitch(switch) @@ -91,10 +93,10 @@ object SwitchCommands { ?: return "No switches registered" val switchBefore = database.fetchSecondLatestSwitch(system.id)?.let { - "The next latest switch is ${it.membersAsString()} ()." + "The next latest switch is ${it.membersAsString()} ()." } ?: "There is no previous switch." - val epoch = switch.timestamp.epochSecond + val epoch = switch.timestamp.epochSeconds TimedYesNoPrompt.build( runner = ctx.message.author!!.id, @@ -121,7 +123,7 @@ object SwitchCommands { Pager.build(ctx.message.author!!.id, ctx.message.channel, switches, 20, { title = "[$it] Front history of ${system.showName}" - }, { it.membersAsString("**", "**") + " ()\n" }) + }, { it.membersAsString("**", "**") + " ()\n" }) return "" } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 8cea5e8b..1cd86dfe 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -20,7 +20,6 @@ import dev.proxyfox.bot.string.dsl.unixLiteral import dev.proxyfox.bot.string.parser.MessageHolder import dev.proxyfox.bot.string.parser.registerCommand import dev.proxyfox.bot.system -import dev.proxyfox.bot.toKtInstant import dev.proxyfox.common.fromColor import dev.proxyfox.common.printStep import dev.proxyfox.common.toColor @@ -128,7 +127,7 @@ object SystemCommands { footer { text = "ID \u2009• \u2009${system.id}\u2007|\u2007Created " } - timestamp = system.timestamp.toKtInstant() + timestamp = system.timestamp } return "" } diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt index 16c9ab10..bb5fa98a 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt @@ -8,8 +8,10 @@ package dev.proxyfox.common -import com.google.gson.* import kotlinx.coroutines.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import java.net.* object FoxFetch { @@ -17,6 +19,6 @@ object FoxFetch { private val url = URL("https://api.tinyfox.dev/img?animal=fox&json") suspend fun fetch() = withContext(Dispatchers.IO) { - baseUrl + url.openStream().reader().use { JsonParser.parseReader(it).asJsonObject["loc"].asString } + baseUrl + Json.parseToJsonElement(url.readText()).jsonObject["loc"]!!.jsonPrimitive.content } } diff --git a/modules/database/build.gradle.kts b/modules/database/build.gradle.kts index e557907a..c9a49514 100644 --- a/modules/database/build.gradle.kts +++ b/modules/database/build.gradle.kts @@ -8,6 +8,7 @@ plugins { alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.serialization) } dependencies { diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt index 716488dd..052bc3f9 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt @@ -21,8 +21,8 @@ import dev.proxyfox.database.records.system.SystemChannelSettingsRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord import dev.proxyfox.database.records.system.SystemSwitchRecord +import kotlinx.datetime.Instant import org.jetbrains.annotations.TestOnly -import java.time.Instant import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import kotlin.time.Duration diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt index 2c8b4494..5367c539 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt @@ -8,9 +8,6 @@ package dev.proxyfox.database -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import com.google.gson.reflect.TypeToken import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.ChannelBehavior import dev.proxyfox.common.ellipsis @@ -25,12 +22,13 @@ import dev.proxyfox.database.records.system.SystemChannelSettingsRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord import dev.proxyfox.database.records.system.SystemSwitchRecord +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.serialization.* +import kotlinx.serialization.json.Json import org.jetbrains.annotations.TestOnly import java.io.File -import java.time.Instant -import java.time.LocalDate -import java.time.OffsetDateTime -import java.time.ZoneOffset import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration @@ -158,7 +156,12 @@ import kotlin.time.Duration * @author Ampflower, Ram * @since ${version} **/ -class JsonDatabase(val file: File = File("systems.json")) : Database() { +@Serializable +class JsonDatabase(@Transient val file: File = File("systems.json")) : Database() { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + private val schema = 1 + private lateinit var systems: MutableMap private lateinit var servers: MutableMap private lateinit var channels: MutableMap @@ -177,12 +180,11 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { users = ConcurrentHashMap() messageMap = ConcurrentHashMap() if (file.exists()) { - val db = file.reader().use(JsonParser::parseReader) - if (db != null && db.isJsonObject) { - read(db.asJsonObject) + val data = file.reader().readText() + val db = Json.decodeFromString(data) + read(db) - return this - } + return this } init() @@ -190,15 +192,12 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { } @TestOnly - fun read(dbObject: JsonObject) { - if (!dbObject.has("schema")) { - throw IllegalStateException("JSON Database missing schema.") - } - if (dbObject["schema"].asInt == 1) { - systems = gson.fromJson(dbObject.getAsJsonObject("systems"), systemMapToken.type) ?: ConcurrentHashMap() - servers = gson.fromJson(dbObject.getAsJsonObject("servers"), serverMapToken.type) ?: ConcurrentHashMap() - channels = gson.fromJson(dbObject.getAsJsonObject("channels"), channelMapToken.type) ?: ConcurrentHashMap() - messages = gson.fromJson(dbObject.getAsJsonArray("messages"), messageSetToken.type) ?: HashSet() + fun read(db: JsonDatabase) { + if (db.schema == 1) { + systems = db.systems + servers = db.servers + channels = db.channels + messages = db.messages for ((_, system) in systems) { system.init() } @@ -213,7 +212,7 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { users[account] = system } } - OffsetDateTime.now().offset.totalSeconds + Clock.System.now().epochSeconds } @TestOnly @@ -613,15 +612,10 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { override fun close() { if (dropped) return - val obj = JsonObject() - obj.addProperty("schema", 1) - obj.add("systems", gson.toJsonTree(systems)) - obj.add("servers", gson.toJsonTree(servers)) - obj.add("channels", gson.toJsonTree(channels)) - obj.add("messages", gson.toJsonTree(messages)) - file.writer().use { gson.toJson(obj, it) } + file.writeText(Json.encodeToString(this)) } + @Serializable data class JsonSystemStruct( val id: String, /** The user must have their snowflake bound to `system` to be included here. */ @@ -633,7 +627,7 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { var color: Int = -1, var avatarUrl: String? = null, var timezone: String? = null, - var timestamp: OffsetDateTime? = OffsetDateTime.now(ZoneOffset.UTC), + var timestamp: Instant? = Clock.System.now(), var auto: String? = null, var autoType: AutoProxyMode? = AutoProxyMode.OFF, var token: String = generateToken(), @@ -726,6 +720,7 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { } } + @Serializable data class JsonMemberStruct( val id: String, var systemId: String, @@ -740,7 +735,7 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { var avatarUrl: String? = null, var keepProxy: Boolean = false, var messageCount: ULong = 0UL, - var timestamp: OffsetDateTime? = OffsetDateTime.now(ZoneOffset.UTC), + var timestamp: Instant? = Clock.System.now(), val serverSettings: MutableMap = HashMap() ) { @@ -793,11 +788,4 @@ class JsonDatabase(val file: File = File("systems.json")) : Database() { timestamp = record.timestamp } } - - companion object { - private val systemMapToken = object : TypeToken>() {} - private val serverMapToken = object : TypeToken>() {} - private val channelMapToken = object : TypeToken>() {} - private val messageSetToken = object : TypeToken>() {} - } } \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index 2662dd99..643da8d9 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -18,13 +18,12 @@ import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.member.MemberServerSettingsRecord import dev.proxyfox.database.records.misc.* -import dev.proxyfox.database.records.system.SystemChannelSettingsRecord -import dev.proxyfox.database.records.system.SystemRecord -import dev.proxyfox.database.records.system.SystemServerSettingsRecord -import dev.proxyfox.database.records.system.SystemSwitchRecord +import dev.proxyfox.database.records.system.* import kotlinx.coroutines.reactive.awaitFirst import kotlinx.coroutines.reactive.awaitFirstOrElse import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.datetime.Instant +import kotlinx.serialization.json.JsonObject import org.bson.conversions.Bson import org.litote.kmongo.and import org.litote.kmongo.coroutine.toList @@ -33,7 +32,6 @@ import org.litote.kmongo.path import org.litote.kmongo.reactivestreams.* import org.litote.kmongo.util.KMongoUtil import org.slf4j.LoggerFactory -import java.time.Instant import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.TimeUnit import kotlin.reflect.KProperty0 @@ -106,13 +104,15 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberServers = db.getOrCreateCollection() + ping() + return this } @OptIn(ExperimentalTime::class) override suspend fun ping(): Duration { return measureTime { - db.runCommand("{ping: 1}").awaitFirst() + db.runCommand("{ping: 1}").awaitFirst() } } @@ -196,7 +196,11 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberProxies.deleteMany(filter).awaitFirst() memberServers.deleteMany(filter).awaitFirst() members.deleteMany(filter).awaitFirst() - systems.deleteOneById(system._id).awaitFirst() + if (system is MongoSystemRecord) { + systems.deleteOneById(system._id).awaitFirst() + } else { + throw IllegalStateException("SystemRecord is not a MongoSystemRecord") + } users.deleteMany(filter).awaitFirst() return true } @@ -219,7 +223,11 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { } override suspend fun updateSystem(system: SystemRecord) { - systems.replaceOneById(system._id, system, upsert()).awaitFirst() + if (system is MongoSystemRecord) { + systems.replaceOneById(system._id, system, upsert()).awaitFirst() + } else { + throw IllegalArgumentException("SystemRecord is not a MongoSystemRecord") + } } override suspend fun updateSystemServerSettings(serverSettings: SystemServerSettingsRecord) { @@ -401,7 +409,11 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { override suspend fun updateSystem(system: SystemRecord) { if (witness.add(system)) { - systemQueue += system.replace() + if (system is MongoSystemRecord) { + systemQueue += system.replace() + } else { + throw IllegalArgumentException("SystemRecord is not a MongoSystemRecord") + } } } @@ -508,7 +520,11 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberProxiesQueue += DeleteManyModel(filter) memberServerSettingsQueue += DeleteManyModel(filter) memberQueue += DeleteManyModel(filter) - systemQueue += system.delete() + if (system is MongoSystemRecord) { + systemQueue += system.delete() + } else { + throw IllegalStateException("SystemRecord is not a MongoSystemRecord") + } userQueue += DeleteManyModel(filter) return true } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt index 15a7b602..c8e4726e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt @@ -18,7 +18,7 @@ import dev.proxyfox.database.records.system.SystemChannelSettingsRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord import dev.proxyfox.database.records.system.SystemSwitchRecord -import java.time.Instant +import kotlinx.datetime.Instant import kotlin.time.Duration class NopDatabase : Database() { diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt index 2a62b67e..3ccc6d45 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt @@ -18,7 +18,7 @@ import dev.proxyfox.database.records.system.SystemChannelSettingsRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord import dev.proxyfox.database.records.system.SystemSwitchRecord -import java.time.Instant +import kotlinx.datetime.Instant import kotlin.time.Duration // Created 2022-07-10T23:56:55 diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt index 38baf1e1..ce1cb2ea 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt @@ -8,11 +8,12 @@ package dev.proxyfox.database +import kotlinx.datetime.LocalDate import java.text.ParsePosition -import java.time.Instant -import java.time.LocalDate -import java.time.OffsetDateTime -import java.time.format.* +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeFormatterBuilder +import java.time.format.SignStyle +import java.time.format.TextStyle import java.time.temporal.ChronoField import java.time.temporal.TemporalAccessor import java.util.* @@ -263,7 +264,9 @@ fun tryParseLocalDate(str: String?, preferMonthDay: Boolean = true): Pair 12) - LocalDate.of(year, day, month) to if (preferMonthDay) DDMMuuuu else MMDDuuuu + LocalDate(year, month, day) to if (preferMonthDay) DDMMuuuu else MMDDuuuu else - LocalDate.of(year, month, day) to parser + LocalDate(year, month, day) to parser } -fun String?.tryParseOffsetTimestamp(): OffsetDateTime? = if (this == null) null else try { - OffsetDateTime.parse(this) -} catch (ignored: DateTimeParseException) { - null -} - -// This redirects to tryParseOffsetTimestamp as for some reason, -// it's perfectly valid for OffsetDateTime to omit seconds, but it's -// not fine for Instant to parse an ISO8601 timestamp with missing seconds -fun String?.tryParseInstant(): Instant? = if (this == null) null else tryParseOffsetTimestamp()?.toInstant() - fun shouldPreferMonthDay(timezone: String?) = timezone != null && (mmddTimezones.contains(timezone) || mmddTimezonesStartOf.any(timezone::startsWith)) @@ -297,23 +289,13 @@ fun TemporalAccessor.displayDate(): String = if (get(ChronoField.YEAR) == 1 || g } @OptIn(ExperimentalContracts::class) -fun TemporalAccessor?.pkValid(): Boolean { +fun LocalDate?.pkValid(): Boolean { contract { returns(true) implies (this@pkValid != null) } - return this != null && get(ChronoField.YEAR) in 1..9999 + return this != null && this.year in 1..9999 } -@OptIn(ExperimentalContracts::class) -fun TemporalAccessor?.pkInvalid(): Boolean { - contract { - returns(true) implies (this@pkInvalid != null) - } - return this != null && get(ChronoField.YEAR) !in 1..9999 -} - -fun TemporalAccessor.pkCompatibleIso8601() = if (this is Instant) toString() else DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(this) - private fun TemporalAccessor?.validate(): TemporalAccessor? { if (this == null) return null if (getLong(ChronoField.DAY_OF_MONTH) > 31) return null diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt index 38710cce..7e8cede2 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt @@ -8,16 +8,16 @@ package dev.proxyfox.database.etc.importer -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import com.google.gson.JsonSyntaxException import dev.kord.core.entity.Entity import dev.proxyfox.database.Database import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonObject import java.io.InputStreamReader import java.io.Reader @@ -41,6 +41,8 @@ suspend fun import(string: String, user: Entity?) = import(database, string, use * */ suspend fun import(reader: Reader, user: Entity?) = import(database, reader, user) +private val json1 = Json { ignoreUnknownKeys = true } + /** * Imports a system file from a [String]. Supports both PluralKit and TupperBox formats. * @@ -51,9 +53,9 @@ suspend fun import(reader: Reader, user: Entity?) = import(database, reader, use * */ suspend fun import(database: Database, string: String, user: Entity?): Importer { try { - return import(database, JsonParser.parseString(string), user) - } catch (syntax: JsonSyntaxException) { - throw ImporterException("Not a JSON file", syntax) + return import(database, Json.parseToJsonElement(string), user) + } catch (reason: Throwable) { + throw ImporterException("Not a JSON file $reason", reason) } } @@ -66,11 +68,7 @@ suspend fun import(database: Database, string: String, user: Entity?): Importer * @author Oliver * */ suspend fun import(database: Database, reader: Reader, user: Entity?): Importer { - try { - return import(database, JsonParser.parseReader(reader), user) - } catch (syntax: JsonSyntaxException) { - throw ImporterException("Not a JSON file", syntax) - } + return import(database, reader.readText(), user) } /** @@ -82,13 +80,12 @@ suspend fun import(database: Database, reader: Reader, user: Entity?): Importer * @author Oliver * */ suspend fun import(database: Database, element: JsonElement, user: Entity?): Importer { - if (!element.isJsonObject) throw ImporterException("Not a JSON object") - val map = element.asJsonObject - if (map.size() == 0) throw ImporterException("No data to import.") - if (map.has("type") && map.has("uri") && map.size() == 2) { + val map = element.jsonObject + if (map.isEmpty()) throw ImporterException("No data to import.") + if (map.contains("type") && map.contains("uri") && map.size == 2) { throw ImporterException("Your system file is invalid; try fetching directly from ${map["uri"]}?") } - val importer = if (map.has("tuppers")) TupperBoxImporter() else PluralKitImporter() + val importer = if (map.contains("tuppers")) TupperBoxImporter() else PluralKitImporter() val bulk = database.bulkInserter() importer.import(bulk, map, user!!.id.value) bulk.commit() diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt index 03c20cd3..46a68bd5 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt @@ -8,9 +8,10 @@ package dev.proxyfox.database.etc.importer -import com.google.gson.JsonObject import dev.proxyfox.common.toColor import dev.proxyfox.database.* +import dev.proxyfox.database.etc.types.PkMember +import dev.proxyfox.database.etc.types.PkSystem import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.member.MemberServerSettingsRecord @@ -18,10 +19,12 @@ import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord import dev.proxyfox.database.records.system.SystemSwitchRecord -import dev.proxyfox.database.etc.types.PkMember -import dev.proxyfox.database.etc.types.PkSystem -import java.time.Instant -import java.time.LocalDate +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.toInstant +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.decodeFromJsonElement import java.time.format.DateTimeFormatter import java.util.* @@ -45,7 +48,9 @@ open class PluralKitImporter protected constructor( constructor() : this(false, false) override suspend fun import(database: Database, json: JsonObject, userId: ULong) { - import(database, gson.fromJson(json, PkSystem::class.java), userId) + // Do lenient decoding + val decoder = Json { isLenient = true; coerceInputValues = true; ignoreUnknownKeys = true } + import(database, decoder.decodeFromJsonElement(json), userId) } suspend fun import(database: Database, pkSystem: PkSystem, userId: ULong) { @@ -102,7 +107,7 @@ open class PluralKitImporter protected constructor( trust?.let(system.trust::putAll) } pkSystem.accounts?.let(system.users::addAll) - pkSystem.created.tryParseOffsetTimestamp()?.let { system.timestamp = it } + system.timestamp = pkSystem.created?.toInstant() ?: system.timestamp } database.updateSystem(system) @@ -178,7 +183,7 @@ open class PluralKitImporter protected constructor( memberTags.forEach { database.dropProxyTag(it) } } if (directAllocation) { - pkMember.created.tryParseOffsetTimestamp()?.let { member.timestamp = it } + member.timestamp = pkMember.created?.let { Instant.parse(it) } ?: member.timestamp } val memberServerSettings = HashMap() @@ -242,7 +247,7 @@ open class PluralKitImporter protected constructor( id = existingIds.firstFreeRaw() val switchMap = TreeMap>(Instant::compareTo) for (switch in switches) { - val timestamp = switch.timestamp.tryParseInstant() ?: continue + val timestamp = switch.timestamp?.let { Instant.parse(it) } ?: continue switchMap.computeIfAbsent(timestamp) { LinkedHashSet() }.addAll(switch.members?.filterNotNull() ?: emptyList()) } var lastMember: LinkedHashSet? = null @@ -305,7 +310,7 @@ open class PluralKitImporter protected constructor( if (otherCount > expectedCount) { for (pkMember in ambiguousBirthdays) { // Not null assertion as it was already parsed successfully once. - birthdays.computeIfPresent(pkMember) { _, (date, _) -> LocalDate.of(date.year, date.dayOfMonth, date.monthValue) to otherFormat } + birthdays.computeIfPresent(pkMember) { _, (date, _) -> LocalDate(date.year, date.monthNumber, date.dayOfMonth) to otherFormat } } } } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt index c8da2b2d..825009aa 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt @@ -8,14 +8,15 @@ package dev.proxyfox.database.etc.importer -import com.google.gson.JsonObject import dev.proxyfox.database.Database -import dev.proxyfox.database.gson import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.validate import dev.proxyfox.database.etc.types.TbSystem +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.decodeFromJsonElement /** * [Importer] to import a JSON with a TupperBox format @@ -26,7 +27,9 @@ class TupperBoxImporter : Importer { private var proxies = HashMap() override suspend fun import(database: Database, json: JsonObject, userId: ULong) { - val tbSystem = gson.fromJson(json, TbSystem::class.java) + // Do lenient decoding + val decoder = Json { isLenient = true; coerceInputValues = true; ignoreUnknownKeys = true } + val tbSystem = decoder.decodeFromJsonElement(json) system = database.getOrCreateSystem(userId) tbSystem.tuppers?.let { tbMembers -> diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/jackson/InstantAdaptor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/jackson/InstantAdaptor.kt deleted file mode 100644 index c0a17f74..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/jackson/InstantAdaptor.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.jackson - -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.core.JsonToken -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider -import de.undercouch.bson4jackson.BsonGenerator -import java.time.Instant -import java.util.* - -// Created 2022-09-10T19:50:39 - -/** - * @author Ampflower - * @since ${version} - **/ -object InstantDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Instant? { - return when (val obj = p.embeddedObject) { - is Long -> Instant.ofEpochSecond(obj / 1000000, obj.mod(1000000L) * 1000L) - is Date -> obj.toInstant() - is String -> Instant.parse(obj) - else -> when (p.currentToken) { - JsonToken.VALUE_STRING -> Instant.parse(p.valueAsString) - JsonToken.VALUE_NUMBER_INT -> p.valueAsLong.let { Instant.ofEpochSecond(it / 100000, (it / 1000L).mod(1000000L)) } - else -> { - p.skipChildren() - null - } - } - } - } -} - -object InstantSerializer : JsonSerializer() { - override fun serialize(value: Instant?, gen: JsonGenerator, serializers: SerializerProvider) { - if (value == null) { - gen.writeNull() - return - } - if (gen is BsonGenerator) { - gen.writeNumber((value.epochSecond * 1000000) + (value.nano / 1000)) - return - } - gen.writeString(value.toString()) - } -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt index 971f48f1..c6a85ff2 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt @@ -8,22 +8,24 @@ package dev.proxyfox.database.etc.types -import com.google.gson.annotations.SerializedName import dev.proxyfox.common.fromColorForExport import dev.proxyfox.database.* +import dev.proxyfox.database.pkValid import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.TrustLevel import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemSwitchRecord -import dev.proxyfox.database.etc.gson.NullValueProcessor -import dev.proxyfox.database.etc.gson.UnexpectedValueProcessor -import java.time.LocalDate -import java.time.OffsetDateTime +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import java.time.format.DateTimeFormatter @JvmRecord +@Serializable data class PkSystem( val id: String? = null, val name: String? = null, @@ -49,19 +51,20 @@ data class PkSystem( // Required for PFv1 database imports @Deprecated("PFv1 database imports only") - @SerializedName("auto_bool") + @SerialName("auto_bool") val autoBool: Boolean? = null, @Deprecated("PFv1 database imports only") val auto: String? = null, @Deprecated("PFv1 database imports only") - @SerializedName("server_proxy") + @SerialName("server_proxy") val serverProxyEnabled: Map? = null, // Ignored @Deprecated("PFv1 database imports only") - val subsystems: Void? = null, + @Transient + val subsystems: Unit? = null, // Required for PK to accept the export. // ProxyFox however will accept any export that vaguely matches PK's. @@ -69,19 +72,29 @@ data class PkSystem( // The following are ignored for as we don't support these yet, // at least at this location. - val tz: Void? = null, - val timezone: Void? = null, - - val description_privacy: Void? = null, - val pronoun_privacy: Void? = null, - val member_list_privacy: Void? = null, - val group_list_privacy: Void? = null, - val front_privacy: Void? = null, - val front_history_privacy: Void? = null, + @Transient + val tz: Unit? = null, + @Transient + val timezone: Unit? = null, + + @Transient + val description_privacy: Unit? = null, + @Transient + val pronoun_privacy: Unit? = null, + @Transient + val member_list_privacy: Unit? = null, + @Transient + val group_list_privacy: Unit? = null, + @Transient + val front_privacy: Unit? = null, + @Transient + val front_history_privacy: Unit? = null, // The following are ignored entirely. We don't use these. - val uuid: Void? = null, - val version: Void? = null, + @Transient + val uuid: Unit? = null, + @Transient + val version: Unit? = null, ) { constructor( record: SystemRecord, @@ -97,7 +110,7 @@ data class PkSystem( pronouns = record.pronouns, color = record.color.fromColorForExport(), avatar_url = record.avatarUrl, - created = record.timestamp.pkCompatibleIso8601(), + created = record.timestamp.toString(), config = PkConfig( timezone = record.timezone, ), @@ -114,6 +127,7 @@ data class PkSystem( } @JvmRecord +@Serializable data class PkMember( val id: String? = null, val name: String? = null, @@ -148,25 +162,33 @@ data class PkMember( // Required for PFv1 database imports @Deprecated("PFv1 database imports only") - @SerializedName("server_nick") + @SerialName("server_nick") val serverNicknames: Map? = null, @Deprecated("PFv1 database imports only") - @SerializedName("server_avatar") + @SerialName("server_avatar") val serverAvatars: Map? = null, // The following are ignored for as we don't support these yet, // at least at this location. - val visibility: Void? = null, - val name_privacy: Void? = null, - val description_privacy: Void? = null, - val birthday_privacy: Void? = null, - val pronoun_privacy: Void? = null, - val avatar_privacy: Void? = null, - val metadata_privacy: Void? = null, + @Transient + val visibility: Unit? = null, + @Transient + val name_privacy: Unit? = null, + @Transient + val description_privacy: Unit? = null, + @Transient + val birthday_privacy: Unit? = null, + @Transient + val pronoun_privacy: Unit? = null, + @Transient + val avatar_privacy: Unit? = null, + @Transient + val metadata_privacy: Unit? = null, // The following are ignored. We don't use these. - val uuid: Void? = null, + @Transient + val uuid: Unit? = null, ) { constructor(record: MemberRecord, proxyTags: Set?) : this( id = record.id, @@ -177,12 +199,12 @@ data class PkMember( color = record.color.fromColorForExport(), keep_proxy = record.keepProxy, message_count = record.messageCount.toLong(), - birthday = record.birthday?.run { if (pkValid()) toString() else "0004-${monthValue.paddedString(2)}-${dayOfMonth.paddedString(2)}" }, - created = record.timestamp.pkCompatibleIso8601(), + birthday = record.birthday?.run { if (pkValid()) toString() else "0004-${monthNumber.paddedString(2)}-${dayOfMonth.paddedString(2)}" }, + created = record.timestamp.toString(), proxy_tags = proxyTags, avatar_url = record.avatarUrl, proxyfox = PfMemberExtension( - birthday = record.birthday.run { if (pkInvalid()) toString() else null }, + birthday = record.birthday.run { if (!pkValid()) toString() else null }, age = record.age, role = record.role, autoProxy = record.autoProxy, @@ -196,6 +218,7 @@ data class PkMember( } @JvmRecord +@Serializable data class PkGroup( val id: String? = null, val name: String? = null, @@ -204,34 +227,38 @@ data class PkGroup( val icon: String? = null, val banner: String? = null, val color: String? = null, - val created: OffsetDateTime? = null, + val created: Instant? = null, val members: List? = null, val privacy: PkGroupPrivacy? = null, // The following are ignored. We don't use these. - val uuid: Void? = null, + @Transient + val uuid: Unit? = null, ) @JvmRecord +@Serializable data class PkSwitch( - val timestamp: String?, - val members: List?, + val timestamp: String? = null, + val members: List? = null, // Ignored for PFv1 database imports @Deprecated("PFv1 database imports only") - val id: Void? = null, + @Transient + val id: Unit? = null, ) { constructor(record: SystemSwitchRecord) : this( - timestamp = record.timestamp.pkCompatibleIso8601(), + timestamp = record.timestamp.toString(), members = record.memberIds, ) } @JvmRecord +@Serializable data class PkProxy( - val prefix: String?, - val suffix: String? + val prefix: String? = null, + val suffix: String? = null ) { constructor(record: MemberProxyTagRecord) : this( prefix = record.prefix, @@ -239,15 +266,15 @@ data class PkProxy( ) } -@UnexpectedValueProcessor(NullValueProcessor::class) @JvmRecord +@Serializable data class PkSystemPrivacy( - val description_privacy: PkPrivacyEnum?, - val pronoun_privacy: PkPrivacyEnum?, - val member_list_privacy: PkPrivacyEnum?, - val group_list_privacy: PkPrivacyEnum?, - val front_privacy: PkPrivacyEnum?, - val front_history_privacy: PkPrivacyEnum?, + val description_privacy: PkPrivacyEnum? = null, + val pronoun_privacy: PkPrivacyEnum? = null, + val member_list_privacy: PkPrivacyEnum? = null, + val group_list_privacy: PkPrivacyEnum? = null, + val front_privacy: PkPrivacyEnum? = null, + val front_history_privacy: PkPrivacyEnum? = null, ) { constructor(privacy: PkPrivacyEnum) : this( description_privacy = privacy, @@ -259,16 +286,16 @@ data class PkSystemPrivacy( ) } -@UnexpectedValueProcessor(NullValueProcessor::class) @JvmRecord +@Serializable data class PkMemberPrivacy( - val visibility: PkPrivacyEnum?, - val name_privacy: PkPrivacyEnum?, - val description_privacy: PkPrivacyEnum?, - val birthday_privacy: PkPrivacyEnum?, - val pronoun_privacy: PkPrivacyEnum?, - val avatar_privacy: PkPrivacyEnum?, - val metadata_privacy: PkPrivacyEnum?, + val visibility: PkPrivacyEnum? = null, + val name_privacy: PkPrivacyEnum? = null, + val description_privacy: PkPrivacyEnum? = null, + val birthday_privacy: PkPrivacyEnum? = null, + val pronoun_privacy: PkPrivacyEnum? = null, + val avatar_privacy: PkPrivacyEnum? = null, + val metadata_privacy: PkPrivacyEnum? = null, ) { constructor(privacy: PkPrivacyEnum) : this( visibility = privacy, @@ -281,15 +308,15 @@ data class PkMemberPrivacy( ) } -@UnexpectedValueProcessor(NullValueProcessor::class) @JvmRecord +@Serializable data class PkGroupPrivacy( - val name_privacy: PkPrivacyEnum?, - val description_privacy: PkPrivacyEnum?, - val icon_privacy: PkPrivacyEnum?, - val list_privacy: PkPrivacyEnum?, - val metadata_privacy: PkPrivacyEnum?, - val visibility: PkPrivacyEnum?, + val name_privacy: PkPrivacyEnum? = null, + val description_privacy: PkPrivacyEnum? = null, + val icon_privacy: PkPrivacyEnum? = null, + val list_privacy: PkPrivacyEnum? = null, + val metadata_privacy: PkPrivacyEnum? = null, + val visibility: PkPrivacyEnum? = null, ) { constructor(privacy: PkPrivacyEnum) : this( name_privacy = privacy, @@ -302,6 +329,7 @@ data class PkGroupPrivacy( } @JvmRecord +@Serializable data class PkConfig( val timezone: String? = null, val pings_enabled: Boolean? = true, @@ -315,18 +343,20 @@ data class PkConfig( ) @JvmRecord +@Serializable data class PfSystemExtension( - val trust: Map?, - val autoType: AutoProxyMode?, - val autoProxy: String?, + val trust: Map? = null, + val autoType: AutoProxyMode? = null, + val autoProxy: String? = null, ) @JvmRecord +@Serializable data class PfMemberExtension( - val birthday: String?, - val age: String?, - val role: String?, - val autoProxy: Boolean?, + val birthday: String? = null, + val age: String? = null, + val role: String? = null, + val autoProxy: Boolean? = null, ) @Suppress("EnumEntryName") diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt index 5801dbcf..e09da12e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt @@ -12,6 +12,8 @@ import com.google.gson.annotations.Expose import com.google.gson.annotations.SerializedName import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId // Created 2022-09-04T15:17:43 @@ -21,23 +23,18 @@ import org.bson.types.ObjectId * * @author Ampflower **/ -class MemberProxyTagRecord : MongoRecord { +@Serializable +class MemberProxyTagRecord(): MongoRecord { + @Contextual override var _id: ObjectId = ObjectId() - // GSON-specific annotation for JSON database - @Expose(serialize = false, deserialize = false) var systemId: PkId = "" - - // GSON-specific annotation for JSON database - @SerializedName(value = "memberId", alternate = ["member"]) var memberId: PkId = "" var prefix: String? = null var suffix: String? = null - constructor() - - constructor(systemId: PkId, memberId: PkId, prefix: String?, suffix: String?) { + constructor(systemId: PkId, memberId: PkId, prefix: String?, suffix: String?): this() { this.systemId = systemId this.memberId = memberId this.prefix = if (prefix == "") null else prefix diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt index 83ae4673..55edc633 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt @@ -8,12 +8,15 @@ package dev.proxyfox.database.records.member -import dev.proxyfox.database.* +import dev.proxyfox.database.PkId +import dev.proxyfox.database.database import dev.proxyfox.database.records.MongoRecord +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId -import java.time.LocalDate -import java.time.OffsetDateTime -import java.time.ZoneOffset // Created 2022-09-04T14:12:07 @@ -22,6 +25,7 @@ import java.time.ZoneOffset * * @author Ampflower **/ +@Serializable class MemberRecord() : MongoRecord { constructor(id: PkId, systemId: PkId, name: String) : this() { this.id = id @@ -29,6 +33,7 @@ class MemberRecord() : MongoRecord { this.name = name } + @Contextual override var _id: ObjectId = ObjectId() var id: PkId = "" @@ -44,7 +49,7 @@ class MemberRecord() : MongoRecord { var keepProxy: Boolean = false var autoProxy: Boolean = true var messageCount: ULong = 0UL - var timestamp: OffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC) + var timestamp: Instant = Clock.System.now() var birthday: LocalDate? = null var age: String? = null var role: String? = null diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt index 250a6f2f..19ea39a4 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt @@ -10,6 +10,8 @@ package dev.proxyfox.database.records.member import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId // Created 2022-09-04T14:16:19 @@ -19,7 +21,9 @@ import org.bson.types.ObjectId * * @author Ampflower **/ +@Serializable class MemberServerSettingsRecord : MongoRecord { + @Contextual override var _id: ObjectId = ObjectId() var serverId: ULong = 0UL var systemId: PkId = "" diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt index a41c4e17..c5d9f44f 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt @@ -9,20 +9,19 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.records.MongoRecord +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId -class ChannelSettingsRecord : MongoRecord { +@Serializable +class ChannelSettingsRecord() : MongoRecord { + @Contextual override var _id: ObjectId = ObjectId() var serverId: ULong = 0UL var channelId: ULong = 0UL var proxyEnabled: Boolean = true - constructor() - - constructor( - serverId: ULong, - channelId: ULong, - ) { + constructor(serverId: ULong, channelId: ULong) : this() { this.serverId = serverId this.channelId = channelId } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt index 298a1b72..9af75b71 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt @@ -8,15 +8,18 @@ package dev.proxyfox.database.records.misc -import dev.proxyfox.database.* +import dev.proxyfox.database.PkId import dev.proxyfox.database.records.MongoRecord +import kotlinx.datetime.Clock +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId -import java.time.OffsetDateTime -import java.time.ZoneOffset +@Serializable class ProxiedMessageRecord : MongoRecord { + @Contextual override var _id: ObjectId = ObjectId() - var creationDate = OffsetDateTime.now(ZoneOffset.UTC) + var creationDate = Clock.System.now() var memberName: String = "" var userId: ULong = 0UL var oldMessageId: ULong = 0UL diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt index fd702d8e..78314773 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt @@ -9,6 +9,8 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.records.MongoRecord +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId // Created 2022-10-04T21:06:30 @@ -17,15 +19,15 @@ import org.bson.types.ObjectId * @author Ampflower * @since ${version} **/ -class ServerSettingsRecord : MongoRecord { +@Serializable +class ServerSettingsRecord() : MongoRecord { + @Contextual override var _id: ObjectId = ObjectId() var serverId: ULong = 0UL var proxyRole: ULong = 0UL var moderationDelay: Short = 250 - constructor() - - constructor(serverId: ULong) { + constructor(serverId: ULong) : this() { this.serverId = serverId } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt index 0238c670..6702036c 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt @@ -9,15 +9,17 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.records.MongoRecord +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId +@Serializable class TokenRecord : MongoRecord { + @Contextual override var _id: ObjectId = ObjectId() var token: String = "" var systemId: String = "" - constructor() - constructor(token: String, systemId: String) { this.token = token this.systemId = systemId diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt index f65a7d88..73343e79 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt @@ -10,9 +10,13 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId +@Serializable class UserRecord : MongoRecord { + @Contextual override var _id: ObjectId = ObjectId() var id: ULong = 0UL var systemId: PkId? = null diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt index 8069b6bd..91b673c8 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt @@ -10,21 +10,20 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId -class SystemChannelSettingsRecord : MongoRecord { +@Serializable +class SystemChannelSettingsRecord() : MongoRecord { + @Contextual override var _id: ObjectId = ObjectId() var serverId: ULong = 0UL var channelId: ULong = 0UL var systemId: PkId = "" var proxyEnabled: Boolean = true - constructor() - - constructor( - channelId: ULong, - systemId: PkId, - ) { + constructor(channelId: ULong, systemId: PkId) : this() { this.channelId = channelId this.systemId = systemId } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index 32518eb3..ed74e1d2 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -8,13 +8,18 @@ package dev.proxyfox.database.records.system -import dev.proxyfox.database.* +import dev.proxyfox.database.JsonDatabase +import dev.proxyfox.database.PkId +import dev.proxyfox.database.generateToken import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.TrustLevel +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId -import java.time.OffsetDateTime -import java.time.ZoneOffset // Created 2022-09-04T14:07:21 @@ -23,8 +28,8 @@ import java.time.ZoneOffset * * @author Ampflower **/ -class SystemRecord : MongoRecord { - override var _id: ObjectId = ObjectId() +@Serializable +open class SystemRecord { var id: PkId = "" var users: ArrayList = ArrayList() var name: String? = null @@ -34,7 +39,7 @@ class SystemRecord : MongoRecord { var color: Int = -1 var avatarUrl: String? = null var timezone: String? = null - var timestamp: OffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC) + var timestamp: Instant = Clock.System.now() var token: String = generateToken() /** The ID of the member that's currently being auto-proxied. */ @@ -44,3 +49,16 @@ class SystemRecord : MongoRecord { val showName get() = name?.let { "$it [`$id`]" } ?: "`$id`" } + +class MongoSystemRecord : SystemRecord(), MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() +} + +class JsonSystemStruct : SystemRecord() { + val members: MutableMap = HashMap() + val serverSettings: MutableMap = HashMap() + val channelSettings: MutableMap = HashMap() + val proxyTags: MutableSet = HashSet() + val switches: MutableMap = HashMap() +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt index 2f640be4..745b48dd 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt @@ -11,6 +11,8 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord import dev.proxyfox.database.records.misc.AutoProxyMode +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId // Created 2022-09-04T15:13:09 @@ -21,7 +23,9 @@ import org.bson.types.ObjectId * @author Ampflower * @since ${version} **/ -class SystemServerSettingsRecord : MongoRecord { +@Serializable +class SystemServerSettingsRecord() : MongoRecord { + @Contextual override var _id: ObjectId = ObjectId() var serverId: ULong = 0UL var systemId: PkId = "" @@ -31,9 +35,7 @@ class SystemServerSettingsRecord : MongoRecord { var autoProxy: PkId? = null var autoProxyMode: AutoProxyMode = AutoProxyMode.FALLBACK - constructor() - - constructor(serverId: ULong, systemId: PkId) { + constructor(serverId: ULong, systemId: PkId) : this() { this.serverId = serverId this.systemId = systemId } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt index 670c6fd0..b8f477c4 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt @@ -8,14 +8,14 @@ package dev.proxyfox.database.records.system -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.annotation.JsonSerialize import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.etc.jackson.InstantDeserializer -import dev.proxyfox.database.etc.jackson.InstantSerializer +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import org.bson.types.ObjectId -import java.time.Instant +import java.time.Duration // Created 2022-09-04T15:18:49 @@ -24,25 +24,24 @@ import java.time.Instant * * @author Ampflower **/ +@Serializable class SystemSwitchRecord : MongoRecord { + @Contextual override var _id: ObjectId = ObjectId() var systemId: PkId var id: PkId var memberIds: List - @JsonDeserialize(using = InstantDeserializer::class) - @JsonSerialize(using = InstantSerializer::class) var timestamp: Instant set(inst) { - field = inst.minusNanos(inst.nano.mod(1000).toLong()) + field = Instant.fromEpochSeconds(inst.epochSeconds) } - @Suppress("ConvertSecondaryConstructorToPrimary") constructor(systemId: PkId = "", id: PkId = "", memberIds: List = ArrayList(), timestamp: Instant? = null) { this.systemId = systemId this.id = id this.memberIds = memberIds - this.timestamp = timestamp ?: Instant.now() + this.timestamp = timestamp ?: Clock.System.now() } override fun equals(other: Any?): Boolean { From 5b6e20f213ef6b8a3e7f642cdf4ea207b77ba5e6 Mon Sep 17 00:00:00 2001 From: tibs Date: Sun, 20 Nov 2022 22:59:20 -0500 Subject: [PATCH 021/137] totally fixed --- .../dev/proxyfox/database/MongoDatabase.kt | 20 +++++--------- .../database/records/system/SystemRecord.kt | 26 +++++++++++++------ 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index 643da8d9..9aa2f93e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -60,7 +60,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { private lateinit var servers: KCollection private lateinit var channels: KCollection - private lateinit var systems: KCollection + private lateinit var systems: KCollection private lateinit var systemSwitches: KCollection private lateinit var systemTokens: KCollection @@ -182,7 +182,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { system.users.add(userId) user.systemId = system.id updateUser(user) - this.systems.insertOne(system).awaitFirst() + this.systems.insertOne(MongoSystemRecord(system)).awaitFirst() system } } @@ -378,7 +378,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { private val channelSettingsQueue = ConcurrentLinkedQueue>() private val memberQueue = ConcurrentLinkedQueue>() private val memberServerSettingsQueue = ConcurrentLinkedQueue>() - private val systemQueue = ConcurrentLinkedQueue>() + private val systemQueue = ConcurrentLinkedQueue>() private val systemServerSettingsQueue = ConcurrentLinkedQueue>() private val systemChannelSettingsQueue = ConcurrentLinkedQueue>() private val userQueue = ConcurrentLinkedQueue>() @@ -409,11 +409,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { override suspend fun updateSystem(system: SystemRecord) { if (witness.add(system)) { - if (system is MongoSystemRecord) { - systemQueue += system.replace() - } else { - throw IllegalArgumentException("SystemRecord is not a MongoSystemRecord") - } + systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).replace() } } @@ -450,7 +446,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { } override suspend fun createSystem(system: SystemRecord) { - if (witness.add(system)) systemQueue += system.create() + systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).create() } override suspend fun createSystemServerSettings(serverSettings: SystemServerSettingsRecord) { @@ -520,11 +516,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberProxiesQueue += DeleteManyModel(filter) memberServerSettingsQueue += DeleteManyModel(filter) memberQueue += DeleteManyModel(filter) - if (system is MongoSystemRecord) { - systemQueue += system.delete() - } else { - throw IllegalStateException("SystemRecord is not a MongoSystemRecord") - } + systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).delete() userQueue += DeleteManyModel(filter) return true } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index ed74e1d2..389cc5b1 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -50,15 +50,25 @@ open class SystemRecord { val showName get() = name?.let { "$it [`$id`]" } ?: "`$id`" } -class MongoSystemRecord : SystemRecord(), MongoRecord { +@Serializable +class MongoSystemRecord() : SystemRecord(), MongoRecord { @Contextual override var _id: ObjectId = ObjectId() -} -class JsonSystemStruct : SystemRecord() { - val members: MutableMap = HashMap() - val serverSettings: MutableMap = HashMap() - val channelSettings: MutableMap = HashMap() - val proxyTags: MutableSet = HashSet() - val switches: MutableMap = HashMap() + constructor(system: SystemRecord) : this() { + id = system.id + users = system.users + name = system.name + description = system.description + tag = system.tag + pronouns = system.pronouns + color = system.color + avatarUrl = system.avatarUrl + timezone = system.timezone + timestamp = system.timestamp + token = system.token + autoProxy = system.autoProxy + autoType = system.autoType + trust = system.trust + } } From 1e938020d60e99303c41095758a7a2f06c63df58 Mon Sep 17 00:00:00 2001 From: tibs Date: Mon, 21 Nov 2022 11:59:27 -0500 Subject: [PATCH 022/137] Add docker builds --- .github/workflows/docker.yml | 53 +++++++++++++++++++++++++++++++++++ .github/workflows/gradle.yml | 27 ++++++++++++------ .github/workflows/publish.yml | 17 ++++++++--- Dockerfile | 9 ++++++ docker-compose.yml | 35 +++++++++++++++++++++++ 5 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/docker.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..9da8cb9c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,53 @@ +name: Build +on: + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: Persist Gradle cache + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Setup JDK 17 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17 + + - name: Build + run: ./gradlew build -x check -x test --no-daemon + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: Artifacts + path: | + build/libs/ + module/**/build/libs/ + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - run: docker build -t ghcr.io/the-proxyfox-group/proxyfox:latest . + - run: docker push ghcr.io/the-proxyfox-group/proxyfox:latest diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2e172a55..b66a80e0 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,20 +1,25 @@ name: Build -on: [pull_request, push] +on: [ pull_request, push ] jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest + services: mongo: image: mongo ports: - 27017:27017 + steps: - - name: checkout repository + - name: Checkout repository uses: actions/checkout@v3 - - name: validate gradle wrapper + + - name: Validate Gradle wrapper uses: gradle/wrapper-validation-action@v1 - - uses: actions/cache@v3 + + - name: Persist Gradle cache + uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -22,21 +27,25 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle- - - name: setup jdk 17 + + - name: Setup JDK 17 uses: actions/setup-java@v3 with: distribution: temurin java-version: 17 - - name: build + + - name: Build run: ./gradlew build --no-daemon - - name: capture build artifacts + + - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: Artifacts path: | build/libs/ module/**/build/libs/ - - name: capture test reports on failure + + - name: Capture test reports on failure uses: actions/upload-artifact@v3 if: failure() with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index dd11f65a..cc0c9d44 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,10 +8,16 @@ on: jobs: build: runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v3 - - uses: gradle/wrapper-validation-action@v1 - - uses: actions/cache@v3 + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: Persist Gradle cache + uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -19,14 +25,17 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle- + - uses: actions/setup-java@v3 with: distribution: temurin java-version: 17 + - name: Build and publish with Gradle run: ./gradlew poolRelease --no-daemon + - name: Upload build artifacts uses: AButler/upload-release-assets@v2.0 with: files: 'build/pool/*' - repo-token: ${{secrets.GITHUB_TOKEN}} \ No newline at end of file + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a5d6fa48 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM openjdk:17-jdk-slim + +COPY modules/bot/build/libs/proxyfox-*.jar /usr/local/lib/ProxyFox.jar + +RUN mkdir /bot + +WORKDIR /bot + +ENTRYPOINT ["java", "-Xms2G", "-Xmx2G", "-jar", "/usr/local/lib/ProxyFox.jar"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..eda015aa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +version: "3" + +networks: + proxyfox: + external: false + +services: + proxyfox: + image: ghcr.io/the-proxyfox-group/proxyfox:latest + container_name: proxyfox + restart: always + environment: + - PROXYFOX_KEY= + - PROXYFOX_MONGO=mongodb://:@mongo + networks: + - proxyfox + depends_on: + - mongo + + mongo: + image: mongo:latest + container_name: proxyfox_mongo + restart: always + environment: + - MONGO_INITDB_ROOT_USERNAME= + - MONGO_INITDB_ROOT_PASSWORD= + networks: + - proxyfox + volumes: + - proxyfox-mongo:/data/db + ports: + - 27017:27017 + +volumes: + proxyfox-mongo: From 7a8989c73dd04277b96e62c5934223f4c05c76c3 Mon Sep 17 00:00:00 2001 From: tibs Date: Mon, 21 Nov 2022 12:16:02 -0500 Subject: [PATCH 023/137] Fix name, publish with hash --- .github/workflows/docker.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9da8cb9c..a70e0d08 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,4 +1,4 @@ -name: Build +name: Docker Build and Publish on: push: branches: @@ -49,5 +49,6 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - run: docker build -t ghcr.io/the-proxyfox-group/proxyfox:latest . + - run: docker build -t ghcr.io/the-proxyfox-group/proxyfox:latest -t ghcr.io/the-proxyfox-group/proxyfox:${GITHUB_SHA} . - run: docker push ghcr.io/the-proxyfox-group/proxyfox:latest + - run: docker push ghcr.io/the-proxyfox-group/proxyfox:${GITHUB_SHA} From c737366474e68827bcadc0712e1184c5049befea Mon Sep 17 00:00:00 2001 From: tibs Date: Mon, 21 Nov 2022 21:35:39 -0500 Subject: [PATCH 024/137] Bump proxyfox-command --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cc57ebb6..ede4719e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ logback = "1.2.11" kord = "0.8.0-M16" kotlin = "1.7.10" kotlinx_coroutines = "1.6.4" -proxyfox_command = "1.2" +proxyfox_command = "1.3" # Database-specific postgres = "42.3.3" From b8f2e6eca34eac4eafb0f0d34ef6818c15e58f36 Mon Sep 17 00:00:00 2001 From: tibs Date: Sat, 17 Dec 2022 15:19:49 -0500 Subject: [PATCH 025/137] Pain --- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 269 +++++++++++------- gradlew.bat | 15 +- .../dev/proxyfox/database/MongoDatabase.kt | 229 ++++++++++----- .../proxyfox/database/records/MongoRecord.kt | 4 +- .../dev/proxyfox/database/records/Record.kt | 5 + .../records/member/MemberProxyTagRecord.kt | 22 +- .../database/records/member/MemberRecord.kt | 34 ++- .../member/MemberServerSettingsRecord.kt | 35 ++- .../records/misc/ChannelSettingsRecord.kt | 21 +- .../records/misc/ProxiedMessageRecord.kt | 31 +- .../records/misc/ServerSettingsRecord.kt | 23 +- .../database/records/misc/TokenRecord.kt | 22 +- .../database/records/misc/UserRecord.kt | 22 +- .../system/SystemChannelSettingsRecord.kt | 22 +- .../database/records/system/SystemRecord.kt | 41 +-- .../system/SystemServerSettingsRecord.kt | 25 +- .../records/system/SystemSwitchRecord.kt | 24 +- 19 files changed, 596 insertions(+), 251 deletions(-) create mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/records/Record.kt diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36900 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

yHlr~Qj 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 GTKgBj+ffbx diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fce..f398c33c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882e..65dcd68d 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# 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"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + 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. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +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 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# 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. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd32..93e3f59f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index 9aa2f93e..1561ed50 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -14,9 +14,8 @@ import com.mongodb.reactivestreams.client.MongoCollection import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.ChannelBehavior import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.member.MemberProxyTagRecord -import dev.proxyfox.database.records.member.MemberRecord -import dev.proxyfox.database.records.member.MemberServerSettingsRecord +import dev.proxyfox.database.records.Record +import dev.proxyfox.database.records.member.* import dev.proxyfox.database.records.misc.* import dev.proxyfox.database.records.system.* import kotlinx.coroutines.reactive.awaitFirst @@ -53,24 +52,24 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { private lateinit var kmongo: MongoClient private lateinit var db: Mongo - private lateinit var users: KCollection + private lateinit var users: KCollection - private lateinit var messages: KCollection + private lateinit var messages: KCollection - private lateinit var servers: KCollection - private lateinit var channels: KCollection + private lateinit var servers: KCollection + private lateinit var channels: KCollection private lateinit var systems: KCollection - private lateinit var systemSwitches: KCollection - private lateinit var systemTokens: KCollection + private lateinit var systemSwitches: KCollection + private lateinit var systemTokens: KCollection - private lateinit var systemServers: KCollection - private lateinit var systemChannels: KCollection + private lateinit var systemServers: KCollection + private lateinit var systemChannels: KCollection - private lateinit var members: KCollection - private lateinit var memberProxies: KCollection + private lateinit var members: KCollection + private lateinit var memberProxies: KCollection - private lateinit var memberServers: KCollection + private lateinit var memberServers: KCollection override suspend fun setup(): MongoDatabase { val connectionString = System.getenv("PROXYFOX_MONGO") @@ -159,7 +158,14 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { servers.findFirstOrNull("serverId" eq serverId) ?: ServerSettingsRecord(serverId) override suspend fun updateServerSettings(serverSettings: ServerSettingsRecord) { - servers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() + if (serverSettings is MongoServerSettingsRecord) { + servers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() + } else { + val id = servers.findFirstOrNull("serverId" eq serverSettings.serverId)?._id + if (id != null) { + servers.replaceOneById(id, serverSettings.toMongo(), upsert()).awaitFirst() + } + } } override suspend fun getOrCreateChannelSettingsFromSystem(channelId: ULong, systemId: String): SystemChannelSettingsRecord = @@ -171,7 +177,14 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { ?: ChannelSettingsRecord(serverId, channelId) override suspend fun updateChannel(channel: ChannelSettingsRecord) { - channels.replaceOneById(channel._id, channel, upsert()).awaitFirst() + if (channel is MongoChannelSettingsRecord) { + channels.replaceOneById(channel._id, channel, upsert()).awaitFirst() + } else { + val id = channels.findFirstOrNull("channelId" eq channel.channelId)?._id + if (id != null) { + channels.replaceOneById(id, channel.toMongo(), upsert()).awaitFirst() + } + } } override suspend fun getOrCreateSystem(userId: ULong, id: String?): SystemRecord { @@ -199,7 +212,10 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { if (system is MongoSystemRecord) { systems.deleteOneById(system._id).awaitFirst() } else { - throw IllegalStateException("SystemRecord is not a MongoSystemRecord") + val id = systems.findFirstOrNull("systemId" eq system.id)?._id + if (id != null) { + systems.deleteOneById(id).awaitFirst() + } } users.deleteMany(filter).awaitFirst() return true @@ -210,36 +226,81 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { val filter = and("systemId" eq systemId, "memberId" eq memberId) memberProxies.deleteMany(filter).awaitFirst() memberServers.deleteMany(filter).awaitFirst() - members.deleteOneById(member._id).awaitFirst() + if (member is MongoMemberRecord) { + members.deleteOneById(member._id).awaitFirst() + } else { + val id = members.findFirstOrNull("memberId" eq member.id)?._id + if (id != null) { + members.deleteOneById(id).awaitFirst() + } + } return true } override suspend fun updateMember(member: MemberRecord) { - members.replaceOneById(member._id, member, upsert()).awaitFirst() + if (member is MongoMemberRecord) { + members.replaceOneById(member._id, member, upsert()).awaitFirst() + } else { + val id = members.findFirstOrNull("id" eq member.id)?._id + if (id != null) { + members.replaceOneById(id, member.toMongo(), upsert()).awaitFirst() + } + } } override suspend fun updateMemberServerSettings(serverSettings: MemberServerSettingsRecord) { - memberServers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() + if (serverSettings is MongoMemberServerSettingsRecord) { + memberServers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() + } else { + val id = memberServers.findFirstOrNull("memberId" eq serverSettings.memberId, "serverId" eq serverSettings.serverId)?._id + if (id != null) { + memberServers.replaceOneById(id, serverSettings.toMongo(), upsert()).awaitFirst() + } + } } override suspend fun updateSystem(system: SystemRecord) { if (system is MongoSystemRecord) { systems.replaceOneById(system._id, system, upsert()).awaitFirst() } else { - throw IllegalArgumentException("SystemRecord is not a MongoSystemRecord") + val id = systems.findFirstOrNull("systemId" eq system.id)?._id + if (id != null) { + systems.replaceOneById(id, system.toMongo(), upsert()).awaitFirst() + } } } override suspend fun updateSystemServerSettings(serverSettings: SystemServerSettingsRecord) { - systemServers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() + if (serverSettings is MongoSystemServerSettingsRecord) { + systemServers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() + } else { + val id = systemServers.findFirstOrNull("systemId" eq serverSettings.systemId, "serverId" eq serverSettings.serverId)?._id + if (id != null) { + systemServers.replaceOneById(id, serverSettings.toMongo(), upsert()).awaitFirst() + } + } } override suspend fun updateSystemChannelSettings(channelSettings: SystemChannelSettingsRecord) { - systemChannels.replaceOneById(channelSettings._id, channelSettings, upsert()).awaitFirst() + if (channelSettings is MongoSystemChannelSettingsRecord) { + systemChannels.replaceOneById(channelSettings._id, channelSettings, upsert()).awaitFirst() + } else { + val id = systemChannels.findFirstOrNull("systemId" eq channelSettings.systemId, "serverId" eq channelSettings.serverId)?._id + if (id != null) { + systemChannels.replaceOneById(id, channelSettings.toMongo(), upsert()).awaitFirst() + } + } } override suspend fun updateUser(user: UserRecord) { - users.replaceOneById(user._id, user, upsert()).awaitFirst() + if (user is MongoUserRecord) { + users.replaceOneById(user._id, user, upsert()).awaitFirst() + } else { + val id = users.findFirstOrNull("userId" eq user.id)?._id + if (id != null) { + users.replaceOneById(id, user.toMongo(), upsert()).awaitFirst() + } + } } override suspend fun createMessage( @@ -261,11 +322,18 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { message.channelId = channel.id.value message.memberId = memberId message.systemId = systemId - messages.insertOne(message).awaitFirst() + messages.insertOne(message.toMongo()).awaitFirst() } override suspend fun updateMessage(message: ProxiedMessageRecord) { - messages.replaceOneById(message._id, message, upsert()).awaitFirst() + if (message is MongoProxiedMessageRecord) { + messages.replaceOneById(message._id, message, upsert()).awaitFirst() + } else { + val id = messages.findFirstOrNull("oldMessageId" eq message.oldMessageId)?._id + if (id != null) { + messages.replaceOneById(id, message.toMongo(), upsert()).awaitFirst() + } + } } override suspend fun fetchMessage(messageId: Snowflake): ProxiedMessageRecord? = @@ -281,11 +349,18 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { systemTokens.findFirstOrNull("systemId" eq systemId) ?: TokenRecord(generateToken(), systemId) override suspend fun updateToken(token: TokenRecord) { - systemTokens.replaceOneById(token._id, token, upsert()).awaitFirst() + if (token is MongoTokenRecord) { + systemTokens.replaceOneById(token._id, token, upsert()).awaitFirst() + } else { + val id = systemTokens.findFirstOrNull("token" eq token.token)?._id + if (id != null) { + systemTokens.replaceOneById(id, token.toMongo(), upsert()).awaitFirst() + } + } } override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { - memberProxies.insertOne(record).awaitFirst() + memberProxies.insertOne(record.toMongo()).awaitFirst() return true } @@ -298,16 +373,30 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { switch.systemId = systemId switch.memberIds = memberId timestamp?.let { switch.timestamp = it } - systemSwitches.insertOne(switch).awaitFirst() + systemSwitches.insertOne(switch.toMongo()).awaitFirst() return switch } override suspend fun dropSwitch(switch: SystemSwitchRecord) { - systemSwitches.deleteOneById(switch._id).awaitFirst() + if (switch is MongoSystemSwitchRecord) { + systemSwitches.deleteOneById(switch._id).awaitFirst() + } else { + val id = systemSwitches.findFirstOrNull("id" eq switch.id)?._id + if (id != null) { + systemSwitches.deleteOneById(id).awaitFirst() + } + } } override suspend fun updateSwitch(switch: SystemSwitchRecord) { - systemSwitches.replaceOneById(switch._id, switch, upsert()).awaitFirst() + if (switch is MongoSystemSwitchRecord) { + systemSwitches.replaceOneById(switch._id, switch.toMongo(), upsert()).awaitFirst() + } else { + val id = systemSwitches.findFirstOrNull("id" eq switch.id)?._id + if (id != null) { + systemSwitches.replaceOneById(id, switch.toMongo(), upsert()).awaitFirst() + } + } } override suspend fun fetchSwitchesFromSystem(systemId: String): List = @@ -374,17 +463,17 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { private class BulkInserter(mongo: MongoDatabase) : ProxyDatabase(mongo) { private val logger = LoggerFactory.getLogger(BulkInserter::class.java) - private val serverSettingsQueue = ConcurrentLinkedQueue>() - private val channelSettingsQueue = ConcurrentLinkedQueue>() - private val memberQueue = ConcurrentLinkedQueue>() - private val memberServerSettingsQueue = ConcurrentLinkedQueue>() + private val serverSettingsQueue = ConcurrentLinkedQueue>() + private val channelSettingsQueue = ConcurrentLinkedQueue>() + private val memberQueue = ConcurrentLinkedQueue>() + private val memberServerSettingsQueue = ConcurrentLinkedQueue>() private val systemQueue = ConcurrentLinkedQueue>() - private val systemServerSettingsQueue = ConcurrentLinkedQueue>() - private val systemChannelSettingsQueue = ConcurrentLinkedQueue>() - private val userQueue = ConcurrentLinkedQueue>() - private val proxiedMessageQueue = ConcurrentLinkedQueue>() - private val systemSwitchQueue = ConcurrentLinkedQueue>() - private val memberProxiesQueue = ConcurrentLinkedQueue>() + private val systemServerSettingsQueue = ConcurrentLinkedQueue>() + private val systemChannelSettingsQueue = ConcurrentLinkedQueue>() + private val userQueue = ConcurrentLinkedQueue>() + private val proxiedMessageQueue = ConcurrentLinkedQueue>() + private val systemSwitchQueue = ConcurrentLinkedQueue>() + private val memberProxiesQueue = ConcurrentLinkedQueue>() private val witness = HashSet() override suspend fun getDatabaseName(): String { @@ -392,96 +481,98 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { } override suspend fun updateServerSettings(serverSettings: ServerSettingsRecord) { - if (witness.add(serverSettings)) serverSettingsQueue += serverSettings.replace() + if (witness.add(serverSettings)) + serverSettingsQueue += (if (serverSettings is MongoServerSettingsRecord) serverSettings else MongoServerSettingsRecord(serverSettings)).replace() } override suspend fun updateChannel(channel: ChannelSettingsRecord) { - if (witness.add(channel)) channelSettingsQueue += channel.replace() + if (witness.add(channel)) channelSettingsQueue += channel.toMongo().replace() } override suspend fun updateMember(member: MemberRecord) { - if (witness.add(member)) memberQueue += member.replace() + if (witness.add(member)) memberQueue += member.toMongo().replace() } override suspend fun updateMemberServerSettings(serverSettings: MemberServerSettingsRecord) { - if (witness.add(serverSettings)) memberServerSettingsQueue += serverSettings.replace() + if (witness.add(serverSettings)) memberServerSettingsQueue += serverSettings.toMongo().replace() } override suspend fun updateSystem(system: SystemRecord) { - if (witness.add(system)) { - systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).replace() - } + if (witness.add(system)) + systemQueue += system.toMongo().replace() } override suspend fun updateSystemServerSettings(serverSettings: SystemServerSettingsRecord) { - if (witness.add(serverSettings)) systemServerSettingsQueue += serverSettings.replace() + if (witness.add(serverSettings)) systemServerSettingsQueue += serverSettings.toMongo().replace() } override suspend fun updateSystemChannelSettings(channelSettings: SystemChannelSettingsRecord) { - if (witness.add(channelSettings)) systemChannelSettingsQueue += channelSettings.replace() + if (witness.add(channelSettings)) { + systemChannelSettingsQueue += channelSettings.toMongo().replace() + } } override suspend fun updateUser(user: UserRecord) { - if (witness.add(user)) userQueue += user.replace() + if (witness.add(user)) userQueue += user.toMongo().replace() } override suspend fun updateMessage(message: ProxiedMessageRecord) { - if (witness.add(message)) proxiedMessageQueue += message.replace() + if (witness.add(message)) proxiedMessageQueue += message.toMongo().replace() } override suspend fun createServerSettings(serverSettings: ServerSettingsRecord) { - if (witness.add(serverSettings)) serverSettingsQueue += serverSettings.create() + if (witness.add(serverSettings)) serverSettingsQueue += serverSettings.toMongo().create() } override suspend fun createChannel(channel: ChannelSettingsRecord) { - if (witness.add(channel)) channelSettingsQueue += channel.create() + if (witness.add(channel)) channelSettingsQueue += channel.toMongo().create() } override suspend fun createMember(member: MemberRecord) { - if (witness.add(member)) memberQueue += member.create() + if (witness.add(member)) memberQueue += member.toMongo().create() } override suspend fun createMemberServerSettings(serverSettings: MemberServerSettingsRecord) { - if (witness.add(serverSettings)) memberServerSettingsQueue += serverSettings.create() + if (witness.add(serverSettings)) memberServerSettingsQueue += serverSettings.toMongo().create() } override suspend fun createSystem(system: SystemRecord) { - systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).create() + systemQueue += system.toMongo().create() } override suspend fun createSystemServerSettings(serverSettings: SystemServerSettingsRecord) { - if (witness.add(serverSettings)) systemServerSettingsQueue += serverSettings.create() + if (witness.add(serverSettings)) systemServerSettingsQueue += serverSettings.toMongo().create() } override suspend fun createSystemChannelSettings(channelSettings: SystemChannelSettingsRecord) { - if (witness.add(channelSettings)) systemChannelSettingsQueue += channelSettings.create() + if (witness.add(channelSettings)) systemChannelSettingsQueue += channelSettings.toMongo().create() } override suspend fun createUser(user: UserRecord) { - if (witness.add(user)) userQueue += user.create() + if (witness.add(user)) userQueue += user.toMongo().create() } override suspend fun createMessage(message: ProxiedMessageRecord) { - if (witness.add(message)) proxiedMessageQueue += message.create() + if (witness.add(message)) proxiedMessageQueue += message.toMongo().create() } override suspend fun updateSwitch(switch: SystemSwitchRecord) { - if (witness.add(switch)) systemSwitchQueue += switch.replace() + if (witness.add(switch)) systemSwitchQueue += switch.toMongo().replace() } override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { - if (witness.add(record)) memberProxiesQueue += record.create() + if (witness.add(record)) memberProxiesQueue += record.toMongo().create() return true } override suspend fun createSwitch(systemId: String, memberId: List, timestamp: Instant?): SystemSwitchRecord { val record = SystemSwitchRecord(systemId, "", memberId, timestamp) - systemSwitchQueue += record.create() + systemSwitchQueue += record.toMongo().create() return record } override suspend fun createSwitch(switch: SystemSwitchRecord) { - if (witness.add(switch)) systemSwitchQueue += switch.create() + if (witness.add(switch)) systemSwitchQueue += switch.toMongo().create() } override suspend fun updateTrustLevel(systemId: String, trustee: ULong, level: TrustLevel): Boolean { @@ -499,12 +590,12 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { val filter = KMongoUtil.toBson("{systemId:'$systemId',memberId:'$memberId'}") memberProxiesQueue += DeleteManyModel(filter) memberServerSettingsQueue += DeleteManyModel(filter) - memberQueue += member.delete() + memberQueue += member.toMongo().delete() return true } override suspend fun dropSwitch(switch: SystemSwitchRecord) { - systemSwitchQueue += switch.delete() + systemSwitchQueue += switch.toMongo().delete() } override suspend fun dropSystem(userId: ULong): Boolean { @@ -516,13 +607,13 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberProxiesQueue += DeleteManyModel(filter) memberServerSettingsQueue += DeleteManyModel(filter) memberQueue += DeleteManyModel(filter) - systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).delete() + systemQueue += system.toMongo().delete() userQueue += DeleteManyModel(filter) return true } override suspend fun dropProxyTag(proxyTag: MemberProxyTagRecord) { - memberProxiesQueue += proxyTag.delete() + memberProxiesQueue += proxyTag.toMongo().delete() } override suspend fun commit() { diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt index 3177cc90..6dc56acc 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt @@ -16,6 +16,6 @@ import org.bson.types.ObjectId * @author Ampflower * @since ${version} **/ -interface MongoRecord { +interface MongoRecord : Record { val _id: ObjectId -} \ No newline at end of file +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/Record.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/Record.kt new file mode 100644 index 00000000..2b42b672 --- /dev/null +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/Record.kt @@ -0,0 +1,5 @@ +package dev.proxyfox.database.records + +interface Record { + fun toMongo(): MongoRecord +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt index e09da12e..5b4dc397 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt @@ -12,6 +12,7 @@ import com.google.gson.annotations.Expose import com.google.gson.annotations.SerializedName import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @@ -24,10 +25,7 @@ import org.bson.types.ObjectId * @author Ampflower **/ @Serializable -class MemberProxyTagRecord(): MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - +open class MemberProxyTagRecord() : Record { var systemId: PkId = "" var memberId: PkId = "" @@ -78,4 +76,20 @@ class MemberProxyTagRecord(): MongoRecord { } override fun toString() = "${prefix ?: ""}text${suffix ?: ""}" + + override fun toMongo() = MongoMemberProxyTagRecord(this) +} + +class MongoMemberProxyTagRecord : MemberProxyTagRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor(record: MemberProxyTagRecord) { + this.systemId = record.systemId + this.memberId = record.memberId + this.prefix = record.prefix + this.suffix = record.suffix + } + + override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt index 55edc633..694ab891 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt @@ -11,6 +11,7 @@ package dev.proxyfox.database.records.member import dev.proxyfox.database.PkId import dev.proxyfox.database.database import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate @@ -26,16 +27,13 @@ import org.bson.types.ObjectId * @author Ampflower **/ @Serializable -class MemberRecord() : MongoRecord { +open class MemberRecord() : Record { constructor(id: PkId, systemId: PkId, name: String) : this() { this.id = id this.systemId = systemId this.name = name } - @Contextual - override var _id: ObjectId = ObjectId() - var id: PkId = "" var systemId: PkId = "" var name: String = "" @@ -60,4 +58,32 @@ class MemberRecord() : MongoRecord { suspend fun serverName(serverId: ULong) = if (serverId == 0UL) null else database.fetchMemberServerSettingsFromSystemAndMember(serverId, systemId, id)?.nickname + + override fun toMongo() = MongoMemberRecord(this) +} + +@Serializable +class MongoMemberRecord : MemberRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor(record: MemberRecord) { + this.id = record.id + this.systemId = record.systemId + this.name = record.name + this.displayName = record.displayName + this.description = record.description + this.pronouns = record.pronouns + this.color = record.color + this.avatarUrl = record.avatarUrl + this.keepProxy = record.keepProxy + this.autoProxy = record.autoProxy + this.messageCount = record.messageCount + this.timestamp = record.timestamp + this.birthday = record.birthday + this.age = record.age + this.role = record.role + } + + override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt index 19ea39a4..ef5facb3 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt @@ -10,6 +10,7 @@ package dev.proxyfox.database.records.member import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @@ -22,9 +23,7 @@ import org.bson.types.ObjectId * @author Ampflower **/ @Serializable -class MemberServerSettingsRecord : MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() +open class MemberServerSettingsRecord() : Record { var serverId: ULong = 0UL var systemId: PkId = "" var memberId: PkId = "" @@ -35,15 +34,29 @@ class MemberServerSettingsRecord : MongoRecord { var autoProxy: Boolean = false var proxyEnabled: Boolean = true - constructor() - - constructor( - serverId: ULong, - systemId: PkId, - memberId: PkId, - ) { + constructor(serverId: ULong, systemId: PkId, memberId: PkId) : this() { this.serverId = serverId this.systemId = systemId this.memberId = memberId } -} \ No newline at end of file + + override fun toMongo() = MongoMemberServerSettingsRecord(this) +} + +@Serializable +class MongoMemberServerSettingsRecord : MemberServerSettingsRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor(record: MemberServerSettingsRecord) { + this.serverId = record.serverId + this.systemId = record.systemId + this.memberId = record.memberId + this.avatarUrl = record.avatarUrl + this.nickname = record.nickname + this.autoProxy = record.autoProxy + this.proxyEnabled = record.proxyEnabled + } + + override fun toMongo() = this +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt index c5d9f44f..709ffec4 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt @@ -9,14 +9,13 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @Serializable -class ChannelSettingsRecord() : MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() +open class ChannelSettingsRecord() : Record { var serverId: ULong = 0UL var channelId: ULong = 0UL var proxyEnabled: Boolean = true @@ -29,4 +28,20 @@ class ChannelSettingsRecord() : MongoRecord { fun writeTo(other: ChannelSettingsRecord) { other.proxyEnabled = proxyEnabled } + + override fun toMongo() = MongoChannelSettingsRecord(this) +} + +@Serializable +class MongoChannelSettingsRecord : ChannelSettingsRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor (record: ChannelSettingsRecord) { + this.serverId = record.serverId + this.channelId = record.channelId + this.proxyEnabled = record.proxyEnabled + } + + override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt index 9af75b71..f3f84302 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt @@ -10,15 +10,14 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.PkId import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import kotlinx.datetime.Clock import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @Serializable -class ProxiedMessageRecord : MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() +open class ProxiedMessageRecord : Record { var creationDate = Clock.System.now() var memberName: String = "" var userId: ULong = 0UL @@ -30,4 +29,28 @@ class ProxiedMessageRecord : MongoRecord { var memberId: PkId = "" var systemId: PkId = "" var deleted = false -} \ No newline at end of file + + override fun toMongo() = MongoProxiedMessageRecord(this) +} + +@Serializable +class MongoProxiedMessageRecord : ProxiedMessageRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor(record: ProxiedMessageRecord) { + this.creationDate = record.creationDate + this.memberName = record.memberName + this.userId = record.userId + this.oldMessageId = record.oldMessageId + this.newMessageId = record.newMessageId + this.guildId = record.guildId + this.channelId = record.channelId + this.threadId = record.threadId + this.memberId = record.memberId + this.systemId = record.systemId + this.deleted = record.deleted + } + + override fun toMongo() = this +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt index 78314773..4d3a9b3d 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt @@ -9,6 +9,7 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @@ -20,9 +21,7 @@ import org.bson.types.ObjectId * @since ${version} **/ @Serializable -class ServerSettingsRecord() : MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() +open class ServerSettingsRecord() : Record { var serverId: ULong = 0UL var proxyRole: ULong = 0UL var moderationDelay: Short = 250 @@ -34,4 +33,20 @@ class ServerSettingsRecord() : MongoRecord { fun writeTo(other: ServerSettingsRecord) { other.proxyRole = proxyRole } -} \ No newline at end of file + + override fun toMongo() = MongoServerSettingsRecord(this) +} + +@Serializable +class MongoServerSettingsRecord : ServerSettingsRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor(record: ServerSettingsRecord) { + this.serverId = record.serverId + this.proxyRole = record.proxyRole + this.moderationDelay = record.moderationDelay + } + + override fun toMongo() = this +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt index 6702036c..4b294dea 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt @@ -9,19 +9,33 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @Serializable -class TokenRecord : MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() +open class TokenRecord() : Record { var token: String = "" var systemId: String = "" - constructor(token: String, systemId: String) { + constructor(token: String, systemId: String) : this() { this.token = token this.systemId = systemId } + + override fun toMongo() = MongoTokenRecord(this) +} + +@Serializable +class MongoTokenRecord : TokenRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor(record: TokenRecord) { + this.token = record.token + this.systemId = record.systemId + } + + override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt index 73343e79..91d394e4 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt @@ -10,14 +10,28 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @Serializable -class UserRecord : MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() +open class UserRecord : Record { var id: ULong = 0UL var systemId: PkId? = null -} \ No newline at end of file + + override fun toMongo() = MongoUserRecord(this) +} + +@Serializable +class MongoUserRecord : UserRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor(record: UserRecord) { + this.id = record.id + this.systemId = record.systemId + } + + override fun toMongo() = this +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt index 91b673c8..1916a5c0 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt @@ -10,14 +10,13 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @Serializable -class SystemChannelSettingsRecord() : MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() +open class SystemChannelSettingsRecord() : Record { var serverId: ULong = 0UL var channelId: ULong = 0UL var systemId: PkId = "" @@ -31,4 +30,21 @@ class SystemChannelSettingsRecord() : MongoRecord { fun writeTo(other: SystemChannelSettingsRecord) { other.proxyEnabled = proxyEnabled } + + override fun toMongo() = MongoSystemChannelSettingsRecord(this) +} + +@Serializable +class MongoSystemChannelSettingsRecord: SystemChannelSettingsRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor(record: SystemChannelSettingsRecord) { + serverId = record.serverId + channelId = record.channelId + systemId = record.systemId + proxyEnabled = record.proxyEnabled + } + + override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index 389cc5b1..07f761c7 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -8,11 +8,10 @@ package dev.proxyfox.database.records.system -import dev.proxyfox.database.JsonDatabase import dev.proxyfox.database.PkId import dev.proxyfox.database.generateToken import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.member.MemberProxyTagRecord +import dev.proxyfox.database.records.Record import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.TrustLevel import kotlinx.datetime.Clock @@ -29,7 +28,7 @@ import org.bson.types.ObjectId * @author Ampflower **/ @Serializable -open class SystemRecord { +open class SystemRecord : Record { var id: PkId = "" var users: ArrayList = ArrayList() var name: String? = null @@ -48,27 +47,31 @@ open class SystemRecord { var trust: HashMap = HashMap() val showName get() = name?.let { "$it [`$id`]" } ?: "`$id`" + + override fun toMongo() = MongoSystemRecord(this) } @Serializable -class MongoSystemRecord() : SystemRecord(), MongoRecord { +class MongoSystemRecord : SystemRecord, MongoRecord { @Contextual override var _id: ObjectId = ObjectId() - constructor(system: SystemRecord) : this() { - id = system.id - users = system.users - name = system.name - description = system.description - tag = system.tag - pronouns = system.pronouns - color = system.color - avatarUrl = system.avatarUrl - timezone = system.timezone - timestamp = system.timestamp - token = system.token - autoProxy = system.autoProxy - autoType = system.autoType - trust = system.trust + constructor(record: SystemRecord) { + id = record.id + users = record.users + name = record.name + description = record.description + tag = record.tag + pronouns = record.pronouns + color = record.color + avatarUrl = record.avatarUrl + timezone = record.timezone + timestamp = record.timestamp + token = record.token + autoProxy = record.autoProxy + autoType = record.autoType + trust = record.trust } + + override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt index 745b48dd..bfe20ac2 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt @@ -10,6 +10,7 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import dev.proxyfox.database.records.misc.AutoProxyMode import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable @@ -24,9 +25,7 @@ import org.bson.types.ObjectId * @since ${version} **/ @Serializable -class SystemServerSettingsRecord() : MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() +open class SystemServerSettingsRecord() : Record { var serverId: ULong = 0UL var systemId: PkId = "" var proxyEnabled: Boolean = true @@ -45,4 +44,22 @@ class SystemServerSettingsRecord() : MongoRecord { other.autoProxy = autoProxy other.autoProxyMode = autoProxyMode } -} \ No newline at end of file + + override fun toMongo() = MongoSystemServerSettingsRecord(this) +} + +@Serializable +class MongoSystemServerSettingsRecord : SystemServerSettingsRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor(record: SystemServerSettingsRecord) { + this.serverId = record.serverId + this.systemId = record.systemId + this.proxyEnabled = record.proxyEnabled + this.autoProxy = record.autoProxy + this.autoProxyMode = record.autoProxyMode + } + + override fun toMongo() = this +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt index b8f477c4..31ff9ee1 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt @@ -10,6 +10,7 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord +import dev.proxyfox.database.records.Record import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.Contextual @@ -25,9 +26,7 @@ import java.time.Duration * @author Ampflower **/ @Serializable -class SystemSwitchRecord : MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() +open class SystemSwitchRecord : Record { var systemId: PkId var id: PkId var memberIds: List @@ -61,4 +60,21 @@ class SystemSwitchRecord : MongoRecord { override fun toString(): String { return "Switch{systemId=$systemId, memberIds=$memberIds, timestamp=$timestamp}" } -} \ No newline at end of file + + override fun toMongo() = MongoSystemSwitchRecord(this) +} + +@Serializable +class MongoSystemSwitchRecord : SystemSwitchRecord, MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + + constructor(record: SystemSwitchRecord) { + this.systemId = record.systemId + this.id = record.id + this.memberIds = record.memberIds + this.timestamp = record.timestamp + } + + override fun toMongo() = this +} From a7b33dce16cc29e42a9f08cc81a6641544cbb185 Mon Sep 17 00:00:00 2001 From: tibs Date: Sat, 17 Dec 2022 15:22:55 -0500 Subject: [PATCH 026/137] cursed --- .../src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index 1561ed50..ccd60ac6 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -381,7 +381,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { if (switch is MongoSystemSwitchRecord) { systemSwitches.deleteOneById(switch._id).awaitFirst() } else { - val id = systemSwitches.findFirstOrNull("id" eq switch.id)?._id + val id = systemSwitches.findFirstOrNull("id" eq switch.id, "systemId" eq switch.systemId)?._id if (id != null) { systemSwitches.deleteOneById(id).awaitFirst() } @@ -392,7 +392,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { if (switch is MongoSystemSwitchRecord) { systemSwitches.replaceOneById(switch._id, switch.toMongo(), upsert()).awaitFirst() } else { - val id = systemSwitches.findFirstOrNull("id" eq switch.id)?._id + val id = systemSwitches.findFirstOrNull("id" eq switch.id, "systemId" eq switch.systemId)?._id if (id != null) { systemSwitches.replaceOneById(id, switch.toMongo(), upsert()).awaitFirst() } From eb5f784130f686e50d722ba562400e7a6d5263e0 Mon Sep 17 00:00:00 2001 From: tibs Date: Sat, 17 Dec 2022 15:47:30 -0500 Subject: [PATCH 027/137] Revert "cursed" This reverts commit a7b33dce16cc29e42a9f08cc81a6641544cbb185. --- .../src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index ccd60ac6..1561ed50 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -381,7 +381,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { if (switch is MongoSystemSwitchRecord) { systemSwitches.deleteOneById(switch._id).awaitFirst() } else { - val id = systemSwitches.findFirstOrNull("id" eq switch.id, "systemId" eq switch.systemId)?._id + val id = systemSwitches.findFirstOrNull("id" eq switch.id)?._id if (id != null) { systemSwitches.deleteOneById(id).awaitFirst() } @@ -392,7 +392,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { if (switch is MongoSystemSwitchRecord) { systemSwitches.replaceOneById(switch._id, switch.toMongo(), upsert()).awaitFirst() } else { - val id = systemSwitches.findFirstOrNull("id" eq switch.id, "systemId" eq switch.systemId)?._id + val id = systemSwitches.findFirstOrNull("id" eq switch.id)?._id if (id != null) { systemSwitches.replaceOneById(id, switch.toMongo(), upsert()).awaitFirst() } From 64ebfcbd5b37d9cfa9946a383276cd2da53bb722 Mon Sep 17 00:00:00 2001 From: tibs Date: Sat, 17 Dec 2022 15:47:35 -0500 Subject: [PATCH 028/137] Revert "Pain" This reverts commit b8f2e6eca34eac4eafb0f0d34ef6818c15e58f36. --- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 269 +++++++----------- gradlew.bat | 15 +- .../dev/proxyfox/database/MongoDatabase.kt | 229 +++++---------- .../proxyfox/database/records/MongoRecord.kt | 4 +- .../dev/proxyfox/database/records/Record.kt | 5 - .../records/member/MemberProxyTagRecord.kt | 22 +- .../database/records/member/MemberRecord.kt | 34 +-- .../member/MemberServerSettingsRecord.kt | 35 +-- .../records/misc/ChannelSettingsRecord.kt | 21 +- .../records/misc/ProxiedMessageRecord.kt | 31 +- .../records/misc/ServerSettingsRecord.kt | 23 +- .../database/records/misc/TokenRecord.kt | 22 +- .../database/records/misc/UserRecord.kt | 22 +- .../system/SystemChannelSettingsRecord.kt | 22 +- .../database/records/system/SystemRecord.kt | 41 ++- .../system/SystemServerSettingsRecord.kt | 25 +- .../records/system/SystemSwitchRecord.kt | 24 +- 19 files changed, 251 insertions(+), 596 deletions(-) delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/records/Record.kt diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 35022 zcmY(JV?(A5xV4*X+qN;;wr$(ym2KO$J=w-o6Q;UzlQFqwKl}ak?%!}~oyS^{Ac*8; zh-QXwZF^4Yut>h``a_(MU|_#sz`#V)mi$T5NH3^>3e7!r0!_>>r|)?YmKbU>w3vD# z+xXyAnhfx^_WGpw_;OU35_JnyJxJTkechWP|00E6er64vrLE!^^HGR-RtB!-d{KP) zE#nm|yGjW@qX&7w^AM#?_i#V&xDVX)onHQ?0f0}~A%>SJ323qi_ zUW`-V&I%*7n^c=Qw>x~9I^J|gWMN33y3~i?&6N0$Ie8MCEi*wjr_1;druf($Jr;<= z16yD)wdSS&GJ39dF)J&gh>q4ev!sNPP!$wn!qc%a!REZ?DPT14#~;gBqYkPMA67ep z*yw3I_G+zm+dteG-Dzm(J{(y0y4n{QJ^l%NgDga7b&Q1?>_7`p0TwOdTad> zD$c+J)ihS1d%b-R1hNq_ZfQndv$=+CHwdaxP-5bc^V}|R)VV?sQ zG`MpON9^Y5sB&G@uWp8}YHprga>ERzXU9BnKh^Ve94m5f(oQ#Xr}q_owr7v3CY-az z+)VtLTWqS*nAQmYq*{+?7}0yH??dfumg4P|baz-_|G*zVa+qfC&9GJh*E<{0L~!JB zC?O)kPApy>p+iKk6NR|Z$(C9kfy)Ql&w6~(s^>nu&_xXUom17|NQJ zC!W#J`GShp z{)gR21Y#3FrI5xcJFz4~Y=Mo`#nr7e&&QLS!6V0^xW_}UrI5erSoP7xqV8g1sghvh zN-O20s{OXLL^}_k7@xYAN6%4T*3|WEN+;B5BHDZl~&} z^&cC!{>r83p4b2)mRfEWLm}E^u?J%nc?d{&FfdqHu>Up+SYc?xc1hZlzbNqAU0o9M z-<9H-q7yggm|Trc4LY0bHl^f8v1D<1vB{h1U~xP6c3#2b!QWjUck^@MBM!dY(m5WX zb3~Lmo?t$q7wwmQjM2^Q_O$W>O#bt0-o8Qir~EzMzUSqKq9AA&d@2ZOHv9@udx%hf z-A@kH{;21S$B+;d*YzRX2~QxO164DaRw#DAKbOVhkeu4XAhsBFxIA$d+RtTN1e}Dy zx#+CB_7Gn@YtTtE%{MZn^diIEQaRlrXZu#7g8au$c^~LkBW(i4ZT_*&mv7{-hO~uW z44Hw8d}>LR4X<18({b)2_E@eWLrkeXyuYkZ<_bZaDHizEyx;YY`4}K~keO(YJ>td> z@uT)orpYAEP7|Ga@BHk@2nN#|(0yyO7y$WIR0_^|;wn|HjQ1Vbr?{6FZIeh4n_(S$ zTkBJy{rWXRcX|@I=r#ixi#p}4xM39y{W4x#{$lLWwoi|@P{UI!37}Y22a*ZO}b((VF*`8paErO^WCTp%N z<>FN$pHBV+K8IX9p2Is6LJ}3&!_{Kncsy70KWeG#EZUoORe|!(^O}=NJ6_7o(DDOH zW9Ug28!xAm3HH&NtiRisRH{FCw96|_s%;`v`gN_(v~VoDV*I^t8ytiBA>=gx)7(}) z#l({u(KeWVjO}at0n5{~plTc`GD0_w)GhzVT^sy{s_Vj=YfjDjaXQU}RPuvdqJ{e3 z8I^kn%`FmyFMyM&p$|qO&G&Otxe9IgpO5e1ZE7+srpdb?A-_6Zfkr1ZSu&eHYN|AY zN?Uj%RL;~%!Irg)-2wts;VR0l=}%^XN{`mw$X-V^kqOIMPR zw+INRO)}`8{ZJkr@DrAif%1aH-(HSr54jVK%aMrk0PF9En zH%MNT!mPugh>L{*x{ijH)TKet#zMAshp#goVhm!_p0~i|d=b zKX7*^*a-1xuCQu`L9M{HiekBiSQ0yn`J$*EPfRJ5xty~Qm)yRw2Dbcz`oGhg0uX|1lABxTc^AgGQH#C~UWis6c^j@uoY% z5%W9q98fvVAT}DuiIJ>>vg{baVd$R_*It34ZyL{HL7T6j=ZXD zKGVCZcj{bZlHWA0wSDWvXs~uqKy|(%$5&z#$PrDdK2o&w5ts!UVaKN#7Ztt9Z`11g}{ zcd{hS(ApwuI{YHb3KQC~^mFnZ@0!Up62{`MAJ3d9HmhzD@kf^LL)2q)w%}XS*^~qS%%ns#qGIN=NbuLV#TR|pEGSRY(K;zUkUVM%e zd!=*>X#socMI;hG0N&8IDlSeAmvLz`KGE`M(?pj3nCq&ZQ1SginfsILm|eS zH@kIU+X7XJ-5G53@UV6*F_ZZ1hYCDC`*%TSH$F^~9sBIS6jh4C@9r~Uiy^MeGcH4g z?Kv`etoI%EL8;x-skig=DTOOurPqz}J`I$goshX~=SFDnq6`?7Z3u|C3if z-*`tqVlp!`ZkoQHn$!ajh*^DsADebD$yGPh2$f#y#BXWtF865&F`QwbsdD4=7O=$n zT=AhV>SpHUA$I}?!opy)s2EuKlWR(B{ASlW&pm68z_fhD?mXOEG`|*EE z8mqiOCkRh)+dW$P$&~q@%j&Djt3?&!hj6mpwNG&0&BO1N-jNMx9wt3F;sc>59P`X- zMVw!hBqY&r#{O5n=Rzd$eb<>an8LGvr?NvZ^y% z6U#A93?#Ue|GpZ|F98zK1+GjremNb1@6@cz z7V_ywkBWBAo1>I1)h&AV6h5MC_rVk-cUbkht>BYOwEBVkIp>4fUpez)BPtm14(Z#fEq|jjBK#7&zc4OF1<&#B8gHm3f~};t!6o*nbFq z3B@xY|0V_RD$!hrO8|zNzpW823?jnPp~tz8_>(T?O9T2ahz_ zec%rwzyE!9tR9p&hZzsOlF1 z1;Kz9-<+FbPv@}5xU;}3FJtCpVG#x&Lh&khYWz)?k-B@_E&+TC4M`La=?JOu`Rm%N zWamCs)eN`k)X;cwYcN9j3Anl}F&B`^p`!WCf8FIki?6h*HvytD0Nr8Ike3=J;yH0A zV+P5P8*ixF?qoy>YJQ-LAN{~DK=$ur#VVcTvGbd-zd_7Jt+|elsV|mkHc`5t%(NembP<$4=Gb1pKp5sg^O!rh**7qbcT&jeu;haDMQQE7iCS#+w6MCo znvrj`4uwQG2YaQluyN&~X;}bvxNl1qvXbgMzX+CEYX(pFTdGn=f=F(%kpGOi*`XBK zc873Gx75)Ar>HH*zo-dBMAQTdDZ{X3A31^gaSO!Ki^V@NR(plHRkt{Br8OU19Oh(M zbQK+PpsuC;XfnHm&>(36OT8cS)qs~W&NXI_mHZZ}=6c+9WVw(4{T?72(>Ai}A$JRO zDcD>=fBm(wgNJSH+;pO2NE^Jh7-*qv*$nj(^}JQKZX?NOO$Cc)aypmxVd)EDb$DtC zuuS3NuWXpkV!wJ7{5N`H5-;Om9KiD7ZHs1pnT^Na1IdWE?zfaaIK}8Cb~jrrx#q|L zQYtpP=ej12rIGe@j|H?Ok^hxMJ5@eZCnB2lh6o&0>7Sv#b)l=m1?FQfIX=ehys%Cb z%@F|bhsvi3!eMvT2opkg8j^c7Ms@f8eV^lD>Ops2(Eom?{v%#l8q6Aqev&V~B<1G4 zV`{27?tR11a0?|gKMIgy--}ugV_BBujMG~EJX_Pbd;}Au{Ril2Fn3vRV!)?Q6{-w} zbokVSg(mz8Y0>HN%{PEBKf11;PIgPxsBG*_)0jaWfF?p&l|Q;_Y!H^kKLqJTE-+Sd z_)HK{&Ep6ArOptwU!9HRY?&vYr{`*=yu7dJshy+i$z`oj+m$-mW$M8+zpLp<8J9Gb z!Z4lLKY9je{sD@eWgY~`snUNL>_KL6d83>Vj~fv10*XQriS&=ZAR9=l#FF$WBKkGR z`%>T->GNH5Fkb%2&*=*Ji23cy&a(0(APAAx*5Q@K=58Ho=&A$x0bD_+uDOPX-b6Hw zcvZX*9iHZ#&petTj)g8s;>2$OGE{aUaE--kz35JQ(tvw47OidBaeJX%jUj&V_!h-! zXK()YA4(-Ti<@YVyfZi$K1=1|Nvip>%@6NkTIP4gy^%%r$Mytj2z$uI*j($Fzz5~j zLCD6s^fD+nkKCC_TaXA+;c%SN5^owz4i)!xv1EHnZH+p;qht4o)|=}2d8(w5%An$; z!^7V+aiEd0X?E!Vv7oO(3YVT0&P3h?<+2^`lZlrHGxP=TEfMM9W~EKX*T89_9p+QP zi(`^lNA;t{5zE^>t?mi3AgkmdZ|Bfsc!-AyZ)ie((nhyyub||=OOdNL=pJ7SYQ|EG z-Gj@b#{+M0^OcPJbLAYims2u9t!>FA*z~=|4DbNqE1&B*pKq}b&Nf-u91rELq(<4E z!s%s{#9ddly6Oq;_xZ%H=hxmZFbUQ-{ng5tcGlJ0B-G>A^IH@zH=S{RDTJ{JDaW&) z-4CzTTdM7+IalL;(k613=lJR2aUiOo`IgJ!k+bKSt1-wRp0!a_S@?$7L0FMUE$P6c z1Za~xY`p4m{G?v!+TBPriv0eP!PfgnL*3VvEEe^EMffiwqfp##<#UL7Ko9y;V3GA~ z6I3t^s?SIPRXfsIFTTOHE!&lZ$Tj#$W0__-MYcD@Mi}fB>tAq32+sH%G!=4ANaLLL zET>Z1Rx844r6FtCF@yzNC4)x33V)^-;^poN@n4;5>qz6Wk zH1`8L-x!w%1NV|+Kl-MY$%&AOITrdB?mFEsUPT(%SA;$T`Nfbb%-k^>LP3H z@V%U>P^u|el)68Y zHRfPclv6g}53DhQBoxm_l%H|`5&{>5RZI{AyIXAV1*s)OB6zz7$&OAi$H?VN{1su6 zPr@WsK{-K`uNUXf`=|^z-7%g}b@F330#|bnnE9k?7V=0>XBUmaVXfyEO%Y0XTW?^t z?4+G!q<;dmt;?*z*wod9rM4S>iSlL71;;^=s^IR>E)ZYtM`%5OC4q@}^8$a)EdDx9 zQ#EE99N3izLyE{XzoEZT_LePFIFo^G)rUQO+(X&&3Xp*n~#pW5rDe*%X$V{*^!4s3IYyJvIFM!qv zl}{<`8bba7n}-Iuz{K;XL1t^jXk!TcVfb$HktTU5c<5dIF~4|D8vVuH#|83xr%hMs z?g!K-mER8;P9UOiXeuSYAxWn1ATmaNOZlv+q^#M6DMP`;KPsFJ{0yifhkjB36I>vK zgOnXlEh0PBk-^ST=V?>an#`_GY?jC(oM;=p?p^g@zCRNq5UqA|#8SkQ`>7Ah2iv!F1;=MSG_PjzE9Z@Ihk0{-CiM3(Nu|DR6MCsw1By)R$53g5 z#m^3N8fF;Z*7_=Hr-Ay~0=H~>f#@9mXu`@iaSds<-7JE>BOk!&@`3ImsZR_dc8>^O#aza>KF7OPJNFbBpU5oQa=xTw~Kg5qa`qDG5KVr;V zvd%Jb9y*iFOlpZgKfPB*<5G718R?Z1^ZpIAO_{Z2_zdgE^i*AjF25CL9Z}K~{}*1^ zCsqMe0xd+_(M{1ZzNNAeJE`5AH)e;WKn6k9(%|&do@&8Z!h$Rb##hJ^Z*>6ow|j)U zA9#dDd~zs#@&LmBlBTqe3;edj)H--16}R4;Iyf*eCTuV;`u}_=>@=ls_<#@QB-R&9 zL3`C&sat6bd66W447mcE&Il?Q9AyBh2)e{RSX_H5^0m|WE-{tTfk#!UR4h>y4vj0k zQhr)9_?VKn-_6?jkF*1xSLhm(1RfBp}!&W62uV{8+sIp^h(gXNbNw;NmE8IFLE*VeMV&tjeq3Dx7ySe(L!VuACxIEUqWVk3Eo5-ULbj0C!@Z#i2M1Uf$(|=WR$t2vLIm$kD|q+s&H&prb@UFUX*7CDW3j4iT&QwM;?T)`FVr zAoBOGzNR$$P+F!LGOwb9?YEqG^CLJb%N?gSu38#&M_^*#ivy3uri&3KI_G!iE?|}= zbU-;6+JsP#q)4<2uHL0&zxvm##w$;@ZqMZ*KxtT1p9zbdL_nfFr|M8uon)yQto?rO22a!{f)QsCJr5#CP%*YhG?2B^GG|4jGNjDN`v7jb<+0c*G1csqlK zwUNL+{l(bT9D;p}i0(oraA54VH;5(B2om-Y8wR-eC^6Z@F(gN-qRkZ3U1Fg&cts`b z*lC`q4!tO?EU@W}U$|818*Y(Sd=#ro6-?yoh?DZXT!xC%*dkefu`K?Ey@N;2)nZKm zWRszUd2Di8OoaVc*#u1?vse@vjSJGE3?~x_K0B#7+0<(pv?U^_=_NDB!E>vj)oY&K zU<@$YTr|;9pg8fll%FS* z$9!@7sPV^BRX#m>)njt7dzagyjHD$1?aH5uljSyD(qHcS2YT=QyB^FtnBIS z+4=Gab_OLJtsgl24Zgj*K2Hnvj!Ld3CB*EPmtJhnrG}VZ>Quikp*j`I=&fZMh8%)GX+z@gc?v?uzt*1tXSgn`q$APMC@hR2J&L~=;A9-S{ zu^m}+$E(|N8uZjPO2?jtRjc2DxbJn+dFMiif2iY?SD)JZ_Vr=umGD0aP)kBD-rW3f^0sdjmVw3&&0ZM#eGu|RmLzDDl6TbtXzLw3HSusL zciNsdFQ=E1jh=(|Ff00G&nqm4h|wo>&OesTO>4-`+=xM~Wp+0sD0)yT$H7fnvAm^c z2&}ecDki1fAmA4U#rPX;dmRbPj8yuP^N!3aotbk*sipoyd_rVJ1_S7Ch zq&?lb`Bkcx<$~;yrMIzcFJ7*+yMl?S1FE!&1Ng@9Ul3da2lBL64Djim&#&Nm-tZji zv_+KKGHw-=B)HO8-q5+R_OZvifAEdP;oEZMCRqDqYgA>J@Fod?);UE}BX}+@gPgsi z(^y~)7klb_q;e(0T<2%`dNtBv^;I1mQPe(eHyJA7c*0@z1;qm`c9PjNPo~;>D`uv$ z-vGw9#926x=z;YzLIzeGh8EbmX5zZ#5H83^YO|Kan*tk+Gb^Xvt4 z24bnYu-)i5RAdm~MH7(qYQ(1?A@7PN{lXQ7Ph4I;N?Tg^UUG=r^K?M@#wPMJ$<4_m z8I7&m9d=Zux-P?edKB@Pcgus2hW1LpF^+s9dW=XAoOP`aBHxf}FL#{9C0}ZVCoTd@Qscs~AwyA% zj&Wsh+!?kwBXwGNf{ttoeNW{X*X8mqw2FmmwEy6nZHiFf@%~%$Q5Wi56q=A!rZG%3 ztP~-q`HHQ`zjJB<1wmjj4Q z3n`=rbbJFay|Mm%wN5goeOplx!?DTJb8u$?(T9(UiLp7Nlahr)mKR(i=aIE>TwF4S z_^CKHNdLIV@GH`htoY?1wmk7JV*kT=S*t->@Pgz?T{6(wihJ`nBOP1O;@5)r=kEK! z^Sk20=V?jQxB3y`6H^FAr_`PPWP-drOzy;Z0K1%uFa>QSI=qbCqTJUlUb-vlmi*dy zj)4VqQn5pLdV-7x*RLSOZL~07@Zf@DG+fqa*^l02ma0ALgLDlC>QH#=MKxM%-6cIt z@WE*6?;(6XU{ZL|DjaAaRPFyk$krd0w~TsycKg7+8uxi5b#w7y zv!6u5nO68I0n|(mb!Aol_utq$>3N%PCR@u)Z5!V!vlZrJ9=*CSRxK5QljrMW@Ww{TK8JD2=pW2QKzZJL;Ipv&^+&dW*v}{*1 zSUzz-yK%XYM+8n8D!*HqqTM4Lc_-gI;eE7Rm!`_Tsd3LA9k5(^){8_@3QECWKC&h zCr@|mbxH@a?XoFck%y&nlL4g-@8)YcrGgjwG#%lq86u8o*|@sgwzrco{#xoL?kwCI z@w!7&z(9>{i$)%o8Ga@{#l*J}JvqVh4lHv;*LsU6F9{CVB##$(Wxgwd6y#E>Va-_arru~T^%DM0)SC}t=>%lJyH+;qKTSZHpLz?X%Wvr?H)0zy>%QPY(d&NOjBWY* z!SAuVhR-(dr(=O^vNf2cG^gWs?zx2CbWD9?xS(57MrT>>X}N(zZg#v#+wXXMt=Qt9 zHN4_l3L{lm0?}+x+pcM$iofbj5V#jd6W}||@3)SEPS0ppm=N{>keQg`9{PIR zX1NU};MSM|;cb{3)b={V);NP^*yVIJKQcQEp4>zcN3-h5moc59y zDtyQyVE~>TUaiI8I997TTcecMbun!xS8O*~s>BHw-pj>hnZrc+w<%zM5Of1yI8r{e zVteCRr6{dzqb|0o?GavZd34-H#bC=a5kHjC7Am#>CazJJfzyI7G`A{8PJt{x3jN3JZT(?OwH)DNXS<$3g9xJJe}mS&YG!ux)&++&B|Sh zZF711Zn8<8kus5sZs|RthJ7-I>&ECTyT6sIW;xg$lyy@+(I@lrbzH;*JYR>8NWmfpc zndd}Z7MjyZm(}f5ZF+q{wZti%EWL7arC9&9TkrQ>$VDJ)sSZaLQ%kjm2Kly>;%o5!S(7tXZ-*hlmEM zS!2UZ$Ey_eXDc0Z`)sdxqa6BW3i7;kXuosy_fDBd41q|)X`ku#o^>8u8RcdJq8t6a z+TyaUg^0!8G(dH=(|e0p5~V4TKQ*$v((Us0Jo@s#aW{WUaAz|q_IPF1B>Lg^A8DTP zUzrcz@B=z6pQ(POCcVhh`SL;$=nPN%d&j$qErsw*W#m$V(-JZ)Klvj$K+(@oB~JjN z(pb$>LYNYQWT1bcgH#!$+FlKtx;j@pdU|AZ^Y`Ok<}OVN;=c_zaH?7cn;}&N3=KbV zB@9P#Xa3+%?$;r_PwqD%z)YZ4Bfw0e))PcMf&r?TAS=7DF_ii-rk`5N__87}yg?IZJ;Aw%*omusSz3X32H#`< z{>9TsEX~1&Wbq@2qjvGN9)-kCB9|~+t69|%`^3Tvj|s9ZqG`VulKH~8egD3?BOGFB zI15O#3Dm*ORw>xrMSbe3nt^Lu$ucyNhfW|iQkNpu{+PGd3HSv-FW!+|K9?JAXSMl& zGwAL7K80_G90}p*Rx-iN^Y!>qd}>)urBhxWnI0bIp|F@+U+Url-VsRi#h;TwI91FX z=C>{_yyYNqPwc@N|ypzNQ7+oK4-KMcR&hx<(fw^s%CI|+S&gknxmwmJy^$_&m4`vP!{ z`xS}YLS%SA>JT^Ls_>R& z%Kd~Is;s8;H`Pmcx^dD7A4+y5=rP6do0KQ^JJ*5h<7(qjba$4Uz3?3|&htK)?&aue zDLTuLXsR1AQsWVrEd*xi^OF;Way8Jtg7^ylBnvBh76grOvM1xkD>kwZ#h8hjf$9(4 z5JkoLi2(DJ0IMoW@m&~>PopJch55RIh};Q3)QuBoRXRgnAgz$`ymDjs0l4EXRP8~V4a&p%-U<(H-UIN=o?l>H4#tha`*Nd``l?S%`?`+yAIv< zaD+y^u1o!Dbe?OqOh(@J?^e}8x@1(_ie-FTNO9jAbD3+d?!f+8<Idi}L_YObnei1w_ z%6Vp(8SI*>cT2f*=tNw^nod!}pxrxwnN~)jcE?OXi;oCds^ZgBf9M3g66ysV6E3qj zD&)!q&x@J6%QPdZIT(>~gdnbFfBUI0l9M}aMezuf(U4^NDwXwT%>fZl1iepidXMqU z5`Fzvef`wpw~U|W(ec9OY3A8wwci%uec4)x_%AMae~-tQ8o9{?;2_|PSycWDLBh6n zbq?m?%YO;-pX5Kdi8i2CqQ5iqZ|fVsWOr>|I}$|{%&36z zumlqfOq>Y}jP(D3&aWB*fSe35j{<#4?pKybi!3ZUVhDOBwBBDTUs)-uhk1guB}sj( ztj_iIl~_ZEhK$ZqtPDs+$%Zw(u5~A`wXMKaCu1Cay*J_Kc?Ife@u9s*mYw(AAE$-> zng4j7`}vhWpNGvQ+Oz-Rm;W%JoY!4ZNU7Axt%PT zu12AZaBQ105f_GeaxQ8#A|Lj1X!gjnhm)aPmp3u-t`=;=u3xWm1M-~cgBs6(VE>^U za8JJI78*igZ&NCF1~5ndiqeA~Ao@k$s1vxMZJ~^dUEPzlO!*O=QY$5M=SQsL7z5>l zyJlqSCbl_uiT8=V?b1OwBdG~?$+j`b2%r4MA5=W-nmvpV?G0vuUy&NnF{hBpi+GoE zLUD=e_mFE-Gv|=m?vX#dCVh61$dwOmSC@K%wB=StanX3o1~?hQ2u~$~(?kc-8^n}a znCL4Y0&*UIkgF6;e2V@-t9!cLb$#RxisHQa`C=#oFn@|WNO1ig7~28fVv91F90U3i)`7JUGYECJD=%M|GT{tFB=nuk}v)Yc{Fy)-)hPJ zSz^B@r;(q3Ao6h-d6v_`-H_6fqrq*>q-u4v#4zQ$-SSt8M1W_{;iF8clmmI=*;J7= zy|AO!5>Sn?t)KGL-tXL1s(?ZGH~sn0`}B2$;x{UTC+ zt$l}NA}#3lr>v1uHcMNV@!n}(#r|&W1Hc=Z*MBQ6SLka&`PDWatgpa;En7hejv7|h zBf1Pee9*qr4ME@LUT5pUH_d73O}*lU++=t07mmT|S10+cRLaK?&1RxRq4gY-me`70 zARoFXk8A3AeG4SJc_M7od{4Du!NZ{5GUjBa79U*MXd!F^JL;c=^XKhSIfI_>k1{fDe49P5NnAuUZ98$_|~)A3~OZ$+4;WtuH=92N+& z=4k85L+euotP<`#=H@EAlF(`5!D^_f`%#skcLZU;$U1R^h_c2dF=x8)39~_Wa?SSNfH~sIe?@qW#m*(1apk%K zjN@u4BcJIDa-d%M#_kz*J?j6AdET;*1BO}q*Bajfc1cU$22`Up>k<2nTi_t0^@XXb z!ZK z9IYToj^*N!N3dj7)1yP_rh>r}zgV=O@f5}Ukb~aSa#@kjP=4dQJ*jc|g@W(qH0jR= z+koyN#JyYG0?DcJ*@x^GBmlp-A^J{k`b1aYe5@=U5rC9JsmJ|OvrKR0l_P+FUGmGp z2sI4C<9PA@iVsM~RtXs~-viWKR2DoC*fVo@Ly1PW@l43U119 za+rmTrwJCCSVkV?)gML+;5e`nX)al347Q`kMy2{mEU*`j!jFca0MNwTH=<4q5Oevz z=FO-!fh`iF^s)=%;1vsrJu_wQ_OGJD1W~ zN89e%V0ZpSx`eC=U>nRyJ2!ioV(;tx_ z0k81pZJ1R!za3r2<~gcFdhqgCq@53987jvYmy^*_ohLPPD^mxB`6ivpbTrf^M*!BN z=8AoG)KH5Y`u&#{A620XeK%C84$mMxa#?j9QdXth;bu5KkojM1Cm)p0!p}Z#*>Dg4 zEBrzug2zhibn?XtQ*!iWD>rdFB|C?~i1KV8R?Up(eO)(mnT1a0bn;xXplHA8{G(hT zkO;ZFNJas2o8nG^5FxBeg)hJU5 zEU4C>cM8)D;O#HqEf}0$L@0BXeYirCJD!m&7^J|yixs4r8OWm|(0w}p5G2d{e9I`B zU^)8;{0dnRPT$dG|2}Dq%oU`2T6DMQ`2|%rvFcY)s&;A&+%k?P$0fU+p6|E5MhrnkB+8-t^Z@8R=|5C?~e)EG#;i8W+j@g8fF(0~euF=cv=^V^W&#KQG0XSUR+2V`9#FIs=@+d$Q)hv!-E&TO=#7`J6Ht%F(OG+}j$F`W7qLATqzZ7@_2+NT$sK#QX;( zEre^&v(sKXE#Q4BeXBZ-|1i>=hG&LJGNX2NodosFbjTW*#1ub$ofrDG~tPY zgl6;Pc+Ce_nfG(ea%MRB!qBLiaZjJZd71hNw?+|e)*(KZtsAO^mD%ZOGiPJ@Ynlob z>BQ}t=(9y|Vcy3ESJ#|*(C*$7Aab4bVuyYAbM4ReK)$MQBfnRT-c`)PSjF;TD1KH+ z+2P&qkzpp)7))wZ{p|1{dTSH$7yN;8^?v6C#pAQQ*nnF;5=#c(iItG2pp2Xv6h5J? zK}^Hm^fH{{U|4Yf< z;)h-X|1)jsc=#;pY!nyGHc>5^^UiJNoFvpUU}2G+fA zY{^l57)_9>phz1^s?kMORPsMi?Ki%@b$$s@rzl_5`l;?U%TrW8FzHklk#;UIrGIIB ze_h5|rG;P%;nDcK%E^3`*X|O0a*gw|<(I_1 zjZ81K4b{;riuTQeIVA3RX%n;J6*G+NP{(>1U(Pf`GU1F{C0DOH%S(-zJf0BYpA4GvS;qPdnqm+)!s=OYv@ zzG*}X%SwUVQ=mumb?6+EhtO{%W~0l2%mIn#;G$qpI$N5d^`>Q`1Ub%L?Xq{BviBIH zvds%FKJ*tB#fd&CQz4}XPCK83i6oa}FeIyDUvPmyasWyIIJ2(_3O?Z=DyEaP+>NU4 zpI2Y=OQ%m%I~L5Y5j*L@QeP{p55nqkht*P@_W*T zFw_Yik*HK3(=M~v7;f$-1O<0>^4~*2nIth`l4|WGK>L>Ryo$^^3ffPhLdG}Mg-J!( zSkp96hf4K}8~4Qig-0;OJs>0&lpx*?ud2;pYy0<`UYL_2Lc5U~(}Fk6rBV zhA}gqs#G-b&-zUF^jGk=Pr1iQ7l(ZB;Qpwn>hgxxv-vQMt{DBu>Vf%xs9f#7vFpPZ zk_orG27?2h$qU~1FVIJ>N5z#8?LpDsJCT;50LS}X0hv7LnhI>+Kn{l=P~RU>mh`vm zAe2>PWf->pjLFe1@rg9>r;v<~ZR;VgC`4T$3mla5$T<`J4_Dt5omtc^n~rVUwr$(C z)3Kc|wr$(CZL_0}(XpMIbH*L#-v7L>v7hE%HCN4=Rr%~#>ty)Q2i5bTmK>bDHK&&# zE(QIF+dz7(f*1s$>?4r%)>d8T_QJ@HhV4IeYM zOVDU~aP_BtoV2C2hOex@53IlsSTBcJf1hamKX7Mb?EmU|;P-!`tNTfKvO=|A4O>0n z9+SRE3w`st{VUMQ@5J?{FQ|F2RrGGy1$)qY!}oFKvoy%RHn9=leFy#&4ESuo1;S1C!d=IqLgWna1UnCfn3qH zeN$qFRONo5TnwPuRk2hEtJ5Gy3@N}gPJWs~eae1_V53PV0<1zs2KUu#{l$WQ43o)_ zVGSLki!mb0BqKt_U=p8Xz$X9*%eZVtB+p1@2Mp&xazB4*(JpFFDZ##9(!}Vw1cfq4 zlIok`9YWG@i7`%6DVS&RfOz_(^m9JRgPhZII4cAKUPlzS%Oq(MLWBaK#)dTd;SPHt z_9&Ybj6st3`D>8j=c7bTn0)aEYV+@4(kBel^S(h@fJnuoyXgrazY*|)!HEY^_pJ<+oq#-vC;*ov@jjQC3BDw zoOHe^=N&fMR}{4BOgw;xqSd4bFfYJz5{z2{JhnK&sSHAwQhzYrdbAU_6kPdRZSIkP z_ZHfp181Ym{iRxkjN0wSIiCEUGjjq(F-EqygO}=BmSN^hJMzyFeTg;I#akrzQV#Yc zh-B(~pPHVlrj?$9?(e+!I29%Y7(OZ>gAWQ47ZUXeq(U{-{R;p*tj4Tg%Lpu)@H$bz zCN2^y=NwZTIsI_t)&v(-Kdc7#&vm0;?vn`E*7^q@FoYe&cj2maA<#3z|73x_W{#X_ zfM$JFl@ok0XLaP>3``IMV&~HxHXE-%q%V?(yUH>jbYmFb(f7O&2Ecu6zCnrg9)la6X06HGjjM zAcmlx2l-`NmGM`1|C9Vinvegc+>;Eiu#=X&QIfK*V4Dd0IuM~N`6>|Vf2el>h@@)= zti&5^KunUY0*Vmgm_@25>Otp zd%PK7%nIYYWKHD*iQsdXm=Li99`Z#foVIBL0L9C2z;UWI#Ol*3_$tfxBiq#`Y@?Dw zRF_;;EL$7ZbI-{DQIN2ErQbNsJ^t0Xd{VM!3u6C3uEvJhQ_>uOewYFRwL9@-js4)e3o4G$RA5pFE zfC(!%UU}N^EW1AgZzV|<(q^w0Rt9$1^mt@QoT)~i!{ZvD4X)3cUk52yk+HB28!7w+79`(@vPSv<@9kn##{YP9ap zn*p3bB#9GWM5Xfmszx|ALSn-nd+`ZGep8n?_^pBaW=SmW8;t%|eZ#ePKZqfm2P}Rf z!4p`eH_h_EF_YInZSzevJZZ{HxhB+^F~<{^w1|7%Cu`4{$)# z4Z}Ib5^ozONB63POBWFQcH^g|2gTSAaK5$0#Mno>xGJ)9enWkLLFJp4&p(#uEWmV) zfI?m9nIA=2cSIv450a%8x*Fs|lavLgDjL1`C5#|~qd+ahie)Me%KUhx1l z0Ub|8Hl7d5Tn9>3Ap~v~FSbnks0cIx72k+VN)*Ja5t#lvJ{Yz!GP4Dr(DN5_4XD&4 zp&HpZ2%Drb_=ez27Cs@^FJ_eA=HI{mfA(GoNaCX$0qsYnjQd02Q~noupLhe2WV(b1 zcm|-HV14J(y&fKDGK1T|B8~dT+rWZC(iE?!@2`rq*n|_+aLHJ_3$9X?q5MV7Tv&7| zrm@Y8zjB$+NJqE9<|sh<<8s~eZgIHuS3;r0VH&nI0&A?yZr?!?oBJvi>>Lx~&^twDgWhr$a;3{wcX z!JW%H-eY0r#~D1)41k&b@&t1~fT`Zc@O&iG_vH$%tACqg8G>Oh_4Lb~P#A9qlpFH& zP9D}#Ngf~v>8mpaX@P0nJR<5R&)4_yaB99MV zYP%_sDAI$RigzX-O$zZ2(MgR2;7f+)B(uoi+HQp7V=$^H@)}@gzKq!Cs_4rfcI_XJ z|AN7lAF?^&b6hT-zDQ@HHxh}nifN0}(dI5{%WG`L-L@9En9d0-Gqh?oGCxz^PPa

yHlr~Qj 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 GTKgBj+ffbx delta 36900 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 '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` # 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"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum +MAX_FD="maximum" warn () { echo "$*" -} >&2 +} die () { echo echo "$*" echo exit 1 -} >&2 +} # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -121,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java + JAVACMD="$JAVA_HOME/jre/sh/java" else - JAVACMD=$JAVA_HOME/bin/java + JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -132,7 +98,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD=java + 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. Please set the JAVA_HOME variable in your environment to match the @@ -140,105 +106,80 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -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 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi fi -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi # For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg + i=`expr $i + 1` done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac 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. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f..107acd32 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%"=="" @echo off +@if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,8 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused +if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd +if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index 1561ed50..9aa2f93e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -14,8 +14,9 @@ import com.mongodb.reactivestreams.client.MongoCollection import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.ChannelBehavior import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record -import dev.proxyfox.database.records.member.* +import dev.proxyfox.database.records.member.MemberProxyTagRecord +import dev.proxyfox.database.records.member.MemberRecord +import dev.proxyfox.database.records.member.MemberServerSettingsRecord import dev.proxyfox.database.records.misc.* import dev.proxyfox.database.records.system.* import kotlinx.coroutines.reactive.awaitFirst @@ -52,24 +53,24 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { private lateinit var kmongo: MongoClient private lateinit var db: Mongo - private lateinit var users: KCollection + private lateinit var users: KCollection - private lateinit var messages: KCollection + private lateinit var messages: KCollection - private lateinit var servers: KCollection - private lateinit var channels: KCollection + private lateinit var servers: KCollection + private lateinit var channels: KCollection private lateinit var systems: KCollection - private lateinit var systemSwitches: KCollection - private lateinit var systemTokens: KCollection + private lateinit var systemSwitches: KCollection + private lateinit var systemTokens: KCollection - private lateinit var systemServers: KCollection - private lateinit var systemChannels: KCollection + private lateinit var systemServers: KCollection + private lateinit var systemChannels: KCollection - private lateinit var members: KCollection - private lateinit var memberProxies: KCollection + private lateinit var members: KCollection + private lateinit var memberProxies: KCollection - private lateinit var memberServers: KCollection + private lateinit var memberServers: KCollection override suspend fun setup(): MongoDatabase { val connectionString = System.getenv("PROXYFOX_MONGO") @@ -158,14 +159,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { servers.findFirstOrNull("serverId" eq serverId) ?: ServerSettingsRecord(serverId) override suspend fun updateServerSettings(serverSettings: ServerSettingsRecord) { - if (serverSettings is MongoServerSettingsRecord) { - servers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() - } else { - val id = servers.findFirstOrNull("serverId" eq serverSettings.serverId)?._id - if (id != null) { - servers.replaceOneById(id, serverSettings.toMongo(), upsert()).awaitFirst() - } - } + servers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() } override suspend fun getOrCreateChannelSettingsFromSystem(channelId: ULong, systemId: String): SystemChannelSettingsRecord = @@ -177,14 +171,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { ?: ChannelSettingsRecord(serverId, channelId) override suspend fun updateChannel(channel: ChannelSettingsRecord) { - if (channel is MongoChannelSettingsRecord) { - channels.replaceOneById(channel._id, channel, upsert()).awaitFirst() - } else { - val id = channels.findFirstOrNull("channelId" eq channel.channelId)?._id - if (id != null) { - channels.replaceOneById(id, channel.toMongo(), upsert()).awaitFirst() - } - } + channels.replaceOneById(channel._id, channel, upsert()).awaitFirst() } override suspend fun getOrCreateSystem(userId: ULong, id: String?): SystemRecord { @@ -212,10 +199,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { if (system is MongoSystemRecord) { systems.deleteOneById(system._id).awaitFirst() } else { - val id = systems.findFirstOrNull("systemId" eq system.id)?._id - if (id != null) { - systems.deleteOneById(id).awaitFirst() - } + throw IllegalStateException("SystemRecord is not a MongoSystemRecord") } users.deleteMany(filter).awaitFirst() return true @@ -226,81 +210,36 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { val filter = and("systemId" eq systemId, "memberId" eq memberId) memberProxies.deleteMany(filter).awaitFirst() memberServers.deleteMany(filter).awaitFirst() - if (member is MongoMemberRecord) { - members.deleteOneById(member._id).awaitFirst() - } else { - val id = members.findFirstOrNull("memberId" eq member.id)?._id - if (id != null) { - members.deleteOneById(id).awaitFirst() - } - } + members.deleteOneById(member._id).awaitFirst() return true } override suspend fun updateMember(member: MemberRecord) { - if (member is MongoMemberRecord) { - members.replaceOneById(member._id, member, upsert()).awaitFirst() - } else { - val id = members.findFirstOrNull("id" eq member.id)?._id - if (id != null) { - members.replaceOneById(id, member.toMongo(), upsert()).awaitFirst() - } - } + members.replaceOneById(member._id, member, upsert()).awaitFirst() } override suspend fun updateMemberServerSettings(serverSettings: MemberServerSettingsRecord) { - if (serverSettings is MongoMemberServerSettingsRecord) { - memberServers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() - } else { - val id = memberServers.findFirstOrNull("memberId" eq serverSettings.memberId, "serverId" eq serverSettings.serverId)?._id - if (id != null) { - memberServers.replaceOneById(id, serverSettings.toMongo(), upsert()).awaitFirst() - } - } + memberServers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() } override suspend fun updateSystem(system: SystemRecord) { if (system is MongoSystemRecord) { systems.replaceOneById(system._id, system, upsert()).awaitFirst() } else { - val id = systems.findFirstOrNull("systemId" eq system.id)?._id - if (id != null) { - systems.replaceOneById(id, system.toMongo(), upsert()).awaitFirst() - } + throw IllegalArgumentException("SystemRecord is not a MongoSystemRecord") } } override suspend fun updateSystemServerSettings(serverSettings: SystemServerSettingsRecord) { - if (serverSettings is MongoSystemServerSettingsRecord) { - systemServers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() - } else { - val id = systemServers.findFirstOrNull("systemId" eq serverSettings.systemId, "serverId" eq serverSettings.serverId)?._id - if (id != null) { - systemServers.replaceOneById(id, serverSettings.toMongo(), upsert()).awaitFirst() - } - } + systemServers.replaceOneById(serverSettings._id, serverSettings, upsert()).awaitFirst() } override suspend fun updateSystemChannelSettings(channelSettings: SystemChannelSettingsRecord) { - if (channelSettings is MongoSystemChannelSettingsRecord) { - systemChannels.replaceOneById(channelSettings._id, channelSettings, upsert()).awaitFirst() - } else { - val id = systemChannels.findFirstOrNull("systemId" eq channelSettings.systemId, "serverId" eq channelSettings.serverId)?._id - if (id != null) { - systemChannels.replaceOneById(id, channelSettings.toMongo(), upsert()).awaitFirst() - } - } + systemChannels.replaceOneById(channelSettings._id, channelSettings, upsert()).awaitFirst() } override suspend fun updateUser(user: UserRecord) { - if (user is MongoUserRecord) { - users.replaceOneById(user._id, user, upsert()).awaitFirst() - } else { - val id = users.findFirstOrNull("userId" eq user.id)?._id - if (id != null) { - users.replaceOneById(id, user.toMongo(), upsert()).awaitFirst() - } - } + users.replaceOneById(user._id, user, upsert()).awaitFirst() } override suspend fun createMessage( @@ -322,18 +261,11 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { message.channelId = channel.id.value message.memberId = memberId message.systemId = systemId - messages.insertOne(message.toMongo()).awaitFirst() + messages.insertOne(message).awaitFirst() } override suspend fun updateMessage(message: ProxiedMessageRecord) { - if (message is MongoProxiedMessageRecord) { - messages.replaceOneById(message._id, message, upsert()).awaitFirst() - } else { - val id = messages.findFirstOrNull("oldMessageId" eq message.oldMessageId)?._id - if (id != null) { - messages.replaceOneById(id, message.toMongo(), upsert()).awaitFirst() - } - } + messages.replaceOneById(message._id, message, upsert()).awaitFirst() } override suspend fun fetchMessage(messageId: Snowflake): ProxiedMessageRecord? = @@ -349,18 +281,11 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { systemTokens.findFirstOrNull("systemId" eq systemId) ?: TokenRecord(generateToken(), systemId) override suspend fun updateToken(token: TokenRecord) { - if (token is MongoTokenRecord) { - systemTokens.replaceOneById(token._id, token, upsert()).awaitFirst() - } else { - val id = systemTokens.findFirstOrNull("token" eq token.token)?._id - if (id != null) { - systemTokens.replaceOneById(id, token.toMongo(), upsert()).awaitFirst() - } - } + systemTokens.replaceOneById(token._id, token, upsert()).awaitFirst() } override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { - memberProxies.insertOne(record.toMongo()).awaitFirst() + memberProxies.insertOne(record).awaitFirst() return true } @@ -373,30 +298,16 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { switch.systemId = systemId switch.memberIds = memberId timestamp?.let { switch.timestamp = it } - systemSwitches.insertOne(switch.toMongo()).awaitFirst() + systemSwitches.insertOne(switch).awaitFirst() return switch } override suspend fun dropSwitch(switch: SystemSwitchRecord) { - if (switch is MongoSystemSwitchRecord) { - systemSwitches.deleteOneById(switch._id).awaitFirst() - } else { - val id = systemSwitches.findFirstOrNull("id" eq switch.id)?._id - if (id != null) { - systemSwitches.deleteOneById(id).awaitFirst() - } - } + systemSwitches.deleteOneById(switch._id).awaitFirst() } override suspend fun updateSwitch(switch: SystemSwitchRecord) { - if (switch is MongoSystemSwitchRecord) { - systemSwitches.replaceOneById(switch._id, switch.toMongo(), upsert()).awaitFirst() - } else { - val id = systemSwitches.findFirstOrNull("id" eq switch.id)?._id - if (id != null) { - systemSwitches.replaceOneById(id, switch.toMongo(), upsert()).awaitFirst() - } - } + systemSwitches.replaceOneById(switch._id, switch, upsert()).awaitFirst() } override suspend fun fetchSwitchesFromSystem(systemId: String): List = @@ -463,17 +374,17 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { private class BulkInserter(mongo: MongoDatabase) : ProxyDatabase(mongo) { private val logger = LoggerFactory.getLogger(BulkInserter::class.java) - private val serverSettingsQueue = ConcurrentLinkedQueue>() - private val channelSettingsQueue = ConcurrentLinkedQueue>() - private val memberQueue = ConcurrentLinkedQueue>() - private val memberServerSettingsQueue = ConcurrentLinkedQueue>() + private val serverSettingsQueue = ConcurrentLinkedQueue>() + private val channelSettingsQueue = ConcurrentLinkedQueue>() + private val memberQueue = ConcurrentLinkedQueue>() + private val memberServerSettingsQueue = ConcurrentLinkedQueue>() private val systemQueue = ConcurrentLinkedQueue>() - private val systemServerSettingsQueue = ConcurrentLinkedQueue>() - private val systemChannelSettingsQueue = ConcurrentLinkedQueue>() - private val userQueue = ConcurrentLinkedQueue>() - private val proxiedMessageQueue = ConcurrentLinkedQueue>() - private val systemSwitchQueue = ConcurrentLinkedQueue>() - private val memberProxiesQueue = ConcurrentLinkedQueue>() + private val systemServerSettingsQueue = ConcurrentLinkedQueue>() + private val systemChannelSettingsQueue = ConcurrentLinkedQueue>() + private val userQueue = ConcurrentLinkedQueue>() + private val proxiedMessageQueue = ConcurrentLinkedQueue>() + private val systemSwitchQueue = ConcurrentLinkedQueue>() + private val memberProxiesQueue = ConcurrentLinkedQueue>() private val witness = HashSet() override suspend fun getDatabaseName(): String { @@ -481,98 +392,96 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { } override suspend fun updateServerSettings(serverSettings: ServerSettingsRecord) { - if (witness.add(serverSettings)) - serverSettingsQueue += (if (serverSettings is MongoServerSettingsRecord) serverSettings else MongoServerSettingsRecord(serverSettings)).replace() + if (witness.add(serverSettings)) serverSettingsQueue += serverSettings.replace() } override suspend fun updateChannel(channel: ChannelSettingsRecord) { - if (witness.add(channel)) channelSettingsQueue += channel.toMongo().replace() + if (witness.add(channel)) channelSettingsQueue += channel.replace() } override suspend fun updateMember(member: MemberRecord) { - if (witness.add(member)) memberQueue += member.toMongo().replace() + if (witness.add(member)) memberQueue += member.replace() } override suspend fun updateMemberServerSettings(serverSettings: MemberServerSettingsRecord) { - if (witness.add(serverSettings)) memberServerSettingsQueue += serverSettings.toMongo().replace() + if (witness.add(serverSettings)) memberServerSettingsQueue += serverSettings.replace() } override suspend fun updateSystem(system: SystemRecord) { - if (witness.add(system)) - systemQueue += system.toMongo().replace() + if (witness.add(system)) { + systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).replace() + } } override suspend fun updateSystemServerSettings(serverSettings: SystemServerSettingsRecord) { - if (witness.add(serverSettings)) systemServerSettingsQueue += serverSettings.toMongo().replace() + if (witness.add(serverSettings)) systemServerSettingsQueue += serverSettings.replace() } override suspend fun updateSystemChannelSettings(channelSettings: SystemChannelSettingsRecord) { - if (witness.add(channelSettings)) { - systemChannelSettingsQueue += channelSettings.toMongo().replace() - } + if (witness.add(channelSettings)) systemChannelSettingsQueue += channelSettings.replace() } override suspend fun updateUser(user: UserRecord) { - if (witness.add(user)) userQueue += user.toMongo().replace() + if (witness.add(user)) userQueue += user.replace() } override suspend fun updateMessage(message: ProxiedMessageRecord) { - if (witness.add(message)) proxiedMessageQueue += message.toMongo().replace() + if (witness.add(message)) proxiedMessageQueue += message.replace() } override suspend fun createServerSettings(serverSettings: ServerSettingsRecord) { - if (witness.add(serverSettings)) serverSettingsQueue += serverSettings.toMongo().create() + if (witness.add(serverSettings)) serverSettingsQueue += serverSettings.create() } override suspend fun createChannel(channel: ChannelSettingsRecord) { - if (witness.add(channel)) channelSettingsQueue += channel.toMongo().create() + if (witness.add(channel)) channelSettingsQueue += channel.create() } override suspend fun createMember(member: MemberRecord) { - if (witness.add(member)) memberQueue += member.toMongo().create() + if (witness.add(member)) memberQueue += member.create() } override suspend fun createMemberServerSettings(serverSettings: MemberServerSettingsRecord) { - if (witness.add(serverSettings)) memberServerSettingsQueue += serverSettings.toMongo().create() + if (witness.add(serverSettings)) memberServerSettingsQueue += serverSettings.create() } override suspend fun createSystem(system: SystemRecord) { - systemQueue += system.toMongo().create() + systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).create() } override suspend fun createSystemServerSettings(serverSettings: SystemServerSettingsRecord) { - if (witness.add(serverSettings)) systemServerSettingsQueue += serverSettings.toMongo().create() + if (witness.add(serverSettings)) systemServerSettingsQueue += serverSettings.create() } override suspend fun createSystemChannelSettings(channelSettings: SystemChannelSettingsRecord) { - if (witness.add(channelSettings)) systemChannelSettingsQueue += channelSettings.toMongo().create() + if (witness.add(channelSettings)) systemChannelSettingsQueue += channelSettings.create() } override suspend fun createUser(user: UserRecord) { - if (witness.add(user)) userQueue += user.toMongo().create() + if (witness.add(user)) userQueue += user.create() } override suspend fun createMessage(message: ProxiedMessageRecord) { - if (witness.add(message)) proxiedMessageQueue += message.toMongo().create() + if (witness.add(message)) proxiedMessageQueue += message.create() } override suspend fun updateSwitch(switch: SystemSwitchRecord) { - if (witness.add(switch)) systemSwitchQueue += switch.toMongo().replace() + if (witness.add(switch)) systemSwitchQueue += switch.replace() } override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { - if (witness.add(record)) memberProxiesQueue += record.toMongo().create() + if (witness.add(record)) memberProxiesQueue += record.create() return true } override suspend fun createSwitch(systemId: String, memberId: List, timestamp: Instant?): SystemSwitchRecord { val record = SystemSwitchRecord(systemId, "", memberId, timestamp) - systemSwitchQueue += record.toMongo().create() + systemSwitchQueue += record.create() return record } override suspend fun createSwitch(switch: SystemSwitchRecord) { - if (witness.add(switch)) systemSwitchQueue += switch.toMongo().create() + if (witness.add(switch)) systemSwitchQueue += switch.create() } override suspend fun updateTrustLevel(systemId: String, trustee: ULong, level: TrustLevel): Boolean { @@ -590,12 +499,12 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { val filter = KMongoUtil.toBson("{systemId:'$systemId',memberId:'$memberId'}") memberProxiesQueue += DeleteManyModel(filter) memberServerSettingsQueue += DeleteManyModel(filter) - memberQueue += member.toMongo().delete() + memberQueue += member.delete() return true } override suspend fun dropSwitch(switch: SystemSwitchRecord) { - systemSwitchQueue += switch.toMongo().delete() + systemSwitchQueue += switch.delete() } override suspend fun dropSystem(userId: ULong): Boolean { @@ -607,13 +516,13 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberProxiesQueue += DeleteManyModel(filter) memberServerSettingsQueue += DeleteManyModel(filter) memberQueue += DeleteManyModel(filter) - systemQueue += system.toMongo().delete() + systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).delete() userQueue += DeleteManyModel(filter) return true } override suspend fun dropProxyTag(proxyTag: MemberProxyTagRecord) { - memberProxiesQueue += proxyTag.toMongo().delete() + memberProxiesQueue += proxyTag.delete() } override suspend fun commit() { diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt index 6dc56acc..3177cc90 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt @@ -16,6 +16,6 @@ import org.bson.types.ObjectId * @author Ampflower * @since ${version} **/ -interface MongoRecord : Record { +interface MongoRecord { val _id: ObjectId -} +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/Record.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/Record.kt deleted file mode 100644 index 2b42b672..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/Record.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.proxyfox.database.records - -interface Record { - fun toMongo(): MongoRecord -} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt index 5b4dc397..e09da12e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt @@ -12,7 +12,6 @@ import com.google.gson.annotations.Expose import com.google.gson.annotations.SerializedName import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @@ -25,7 +24,10 @@ import org.bson.types.ObjectId * @author Ampflower **/ @Serializable -open class MemberProxyTagRecord() : Record { +class MemberProxyTagRecord(): MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() + var systemId: PkId = "" var memberId: PkId = "" @@ -76,20 +78,4 @@ open class MemberProxyTagRecord() : Record { } override fun toString() = "${prefix ?: ""}text${suffix ?: ""}" - - override fun toMongo() = MongoMemberProxyTagRecord(this) -} - -class MongoMemberProxyTagRecord : MemberProxyTagRecord, MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - - constructor(record: MemberProxyTagRecord) { - this.systemId = record.systemId - this.memberId = record.memberId - this.prefix = record.prefix - this.suffix = record.suffix - } - - override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt index 694ab891..55edc633 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt @@ -11,7 +11,6 @@ package dev.proxyfox.database.records.member import dev.proxyfox.database.PkId import dev.proxyfox.database.database import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate @@ -27,13 +26,16 @@ import org.bson.types.ObjectId * @author Ampflower **/ @Serializable -open class MemberRecord() : Record { +class MemberRecord() : MongoRecord { constructor(id: PkId, systemId: PkId, name: String) : this() { this.id = id this.systemId = systemId this.name = name } + @Contextual + override var _id: ObjectId = ObjectId() + var id: PkId = "" var systemId: PkId = "" var name: String = "" @@ -58,32 +60,4 @@ open class MemberRecord() : Record { suspend fun serverName(serverId: ULong) = if (serverId == 0UL) null else database.fetchMemberServerSettingsFromSystemAndMember(serverId, systemId, id)?.nickname - - override fun toMongo() = MongoMemberRecord(this) -} - -@Serializable -class MongoMemberRecord : MemberRecord, MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - - constructor(record: MemberRecord) { - this.id = record.id - this.systemId = record.systemId - this.name = record.name - this.displayName = record.displayName - this.description = record.description - this.pronouns = record.pronouns - this.color = record.color - this.avatarUrl = record.avatarUrl - this.keepProxy = record.keepProxy - this.autoProxy = record.autoProxy - this.messageCount = record.messageCount - this.timestamp = record.timestamp - this.birthday = record.birthday - this.age = record.age - this.role = record.role - } - - override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt index ef5facb3..19ea39a4 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt @@ -10,7 +10,6 @@ package dev.proxyfox.database.records.member import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @@ -23,7 +22,9 @@ import org.bson.types.ObjectId * @author Ampflower **/ @Serializable -open class MemberServerSettingsRecord() : Record { +class MemberServerSettingsRecord : MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() var serverId: ULong = 0UL var systemId: PkId = "" var memberId: PkId = "" @@ -34,29 +35,15 @@ open class MemberServerSettingsRecord() : Record { var autoProxy: Boolean = false var proxyEnabled: Boolean = true - constructor(serverId: ULong, systemId: PkId, memberId: PkId) : this() { + constructor() + + constructor( + serverId: ULong, + systemId: PkId, + memberId: PkId, + ) { this.serverId = serverId this.systemId = systemId this.memberId = memberId } - - override fun toMongo() = MongoMemberServerSettingsRecord(this) -} - -@Serializable -class MongoMemberServerSettingsRecord : MemberServerSettingsRecord, MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - - constructor(record: MemberServerSettingsRecord) { - this.serverId = record.serverId - this.systemId = record.systemId - this.memberId = record.memberId - this.avatarUrl = record.avatarUrl - this.nickname = record.nickname - this.autoProxy = record.autoProxy - this.proxyEnabled = record.proxyEnabled - } - - override fun toMongo() = this -} +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt index 709ffec4..c5d9f44f 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt @@ -9,13 +9,14 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @Serializable -open class ChannelSettingsRecord() : Record { +class ChannelSettingsRecord() : MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() var serverId: ULong = 0UL var channelId: ULong = 0UL var proxyEnabled: Boolean = true @@ -28,20 +29,4 @@ open class ChannelSettingsRecord() : Record { fun writeTo(other: ChannelSettingsRecord) { other.proxyEnabled = proxyEnabled } - - override fun toMongo() = MongoChannelSettingsRecord(this) -} - -@Serializable -class MongoChannelSettingsRecord : ChannelSettingsRecord, MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - - constructor (record: ChannelSettingsRecord) { - this.serverId = record.serverId - this.channelId = record.channelId - this.proxyEnabled = record.proxyEnabled - } - - override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt index f3f84302..9af75b71 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt @@ -10,14 +10,15 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.PkId import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import kotlinx.datetime.Clock import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @Serializable -open class ProxiedMessageRecord : Record { +class ProxiedMessageRecord : MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() var creationDate = Clock.System.now() var memberName: String = "" var userId: ULong = 0UL @@ -29,28 +30,4 @@ open class ProxiedMessageRecord : Record { var memberId: PkId = "" var systemId: PkId = "" var deleted = false - - override fun toMongo() = MongoProxiedMessageRecord(this) -} - -@Serializable -class MongoProxiedMessageRecord : ProxiedMessageRecord, MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - - constructor(record: ProxiedMessageRecord) { - this.creationDate = record.creationDate - this.memberName = record.memberName - this.userId = record.userId - this.oldMessageId = record.oldMessageId - this.newMessageId = record.newMessageId - this.guildId = record.guildId - this.channelId = record.channelId - this.threadId = record.threadId - this.memberId = record.memberId - this.systemId = record.systemId - this.deleted = record.deleted - } - - override fun toMongo() = this -} +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt index 4d3a9b3d..78314773 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt @@ -9,7 +9,6 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @@ -21,7 +20,9 @@ import org.bson.types.ObjectId * @since ${version} **/ @Serializable -open class ServerSettingsRecord() : Record { +class ServerSettingsRecord() : MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() var serverId: ULong = 0UL var proxyRole: ULong = 0UL var moderationDelay: Short = 250 @@ -33,20 +34,4 @@ open class ServerSettingsRecord() : Record { fun writeTo(other: ServerSettingsRecord) { other.proxyRole = proxyRole } - - override fun toMongo() = MongoServerSettingsRecord(this) -} - -@Serializable -class MongoServerSettingsRecord : ServerSettingsRecord, MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - - constructor(record: ServerSettingsRecord) { - this.serverId = record.serverId - this.proxyRole = record.proxyRole - this.moderationDelay = record.moderationDelay - } - - override fun toMongo() = this -} +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt index 4b294dea..6702036c 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt @@ -9,33 +9,19 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @Serializable -open class TokenRecord() : Record { +class TokenRecord : MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() var token: String = "" var systemId: String = "" - constructor(token: String, systemId: String) : this() { + constructor(token: String, systemId: String) { this.token = token this.systemId = systemId } - - override fun toMongo() = MongoTokenRecord(this) -} - -@Serializable -class MongoTokenRecord : TokenRecord, MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - - constructor(record: TokenRecord) { - this.token = record.token - this.systemId = record.systemId - } - - override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt index 91d394e4..73343e79 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt @@ -10,28 +10,14 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @Serializable -open class UserRecord : Record { - var id: ULong = 0UL - var systemId: PkId? = null - - override fun toMongo() = MongoUserRecord(this) -} - -@Serializable -class MongoUserRecord : UserRecord, MongoRecord { +class UserRecord : MongoRecord { @Contextual override var _id: ObjectId = ObjectId() - - constructor(record: UserRecord) { - this.id = record.id - this.systemId = record.systemId - } - - override fun toMongo() = this -} + var id: ULong = 0UL + var systemId: PkId? = null +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt index 1916a5c0..91b673c8 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt @@ -10,13 +10,14 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @Serializable -open class SystemChannelSettingsRecord() : Record { +class SystemChannelSettingsRecord() : MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() var serverId: ULong = 0UL var channelId: ULong = 0UL var systemId: PkId = "" @@ -30,21 +31,4 @@ open class SystemChannelSettingsRecord() : Record { fun writeTo(other: SystemChannelSettingsRecord) { other.proxyEnabled = proxyEnabled } - - override fun toMongo() = MongoSystemChannelSettingsRecord(this) -} - -@Serializable -class MongoSystemChannelSettingsRecord: SystemChannelSettingsRecord, MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - - constructor(record: SystemChannelSettingsRecord) { - serverId = record.serverId - channelId = record.channelId - systemId = record.systemId - proxyEnabled = record.proxyEnabled - } - - override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index 07f761c7..389cc5b1 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -8,10 +8,11 @@ package dev.proxyfox.database.records.system +import dev.proxyfox.database.JsonDatabase import dev.proxyfox.database.PkId import dev.proxyfox.database.generateToken import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record +import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.TrustLevel import kotlinx.datetime.Clock @@ -28,7 +29,7 @@ import org.bson.types.ObjectId * @author Ampflower **/ @Serializable -open class SystemRecord : Record { +open class SystemRecord { var id: PkId = "" var users: ArrayList = ArrayList() var name: String? = null @@ -47,31 +48,27 @@ open class SystemRecord : Record { var trust: HashMap = HashMap() val showName get() = name?.let { "$it [`$id`]" } ?: "`$id`" - - override fun toMongo() = MongoSystemRecord(this) } @Serializable -class MongoSystemRecord : SystemRecord, MongoRecord { +class MongoSystemRecord() : SystemRecord(), MongoRecord { @Contextual override var _id: ObjectId = ObjectId() - constructor(record: SystemRecord) { - id = record.id - users = record.users - name = record.name - description = record.description - tag = record.tag - pronouns = record.pronouns - color = record.color - avatarUrl = record.avatarUrl - timezone = record.timezone - timestamp = record.timestamp - token = record.token - autoProxy = record.autoProxy - autoType = record.autoType - trust = record.trust + constructor(system: SystemRecord) : this() { + id = system.id + users = system.users + name = system.name + description = system.description + tag = system.tag + pronouns = system.pronouns + color = system.color + avatarUrl = system.avatarUrl + timezone = system.timezone + timestamp = system.timestamp + token = system.token + autoProxy = system.autoProxy + autoType = system.autoType + trust = system.trust } - - override fun toMongo() = this } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt index bfe20ac2..745b48dd 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt @@ -10,7 +10,6 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import dev.proxyfox.database.records.misc.AutoProxyMode import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable @@ -25,7 +24,9 @@ import org.bson.types.ObjectId * @since ${version} **/ @Serializable -open class SystemServerSettingsRecord() : Record { +class SystemServerSettingsRecord() : MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() var serverId: ULong = 0UL var systemId: PkId = "" var proxyEnabled: Boolean = true @@ -44,22 +45,4 @@ open class SystemServerSettingsRecord() : Record { other.autoProxy = autoProxy other.autoProxyMode = autoProxyMode } - - override fun toMongo() = MongoSystemServerSettingsRecord(this) -} - -@Serializable -class MongoSystemServerSettingsRecord : SystemServerSettingsRecord, MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - - constructor(record: SystemServerSettingsRecord) { - this.serverId = record.serverId - this.systemId = record.systemId - this.proxyEnabled = record.proxyEnabled - this.autoProxy = record.autoProxy - this.autoProxyMode = record.autoProxyMode - } - - override fun toMongo() = this -} +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt index 31ff9ee1..b8f477c4 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt @@ -10,7 +10,6 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.* import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.Record import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.Contextual @@ -26,7 +25,9 @@ import java.time.Duration * @author Ampflower **/ @Serializable -open class SystemSwitchRecord : Record { +class SystemSwitchRecord : MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() var systemId: PkId var id: PkId var memberIds: List @@ -60,21 +61,4 @@ open class SystemSwitchRecord : Record { override fun toString(): String { return "Switch{systemId=$systemId, memberIds=$memberIds, timestamp=$timestamp}" } - - override fun toMongo() = MongoSystemSwitchRecord(this) -} - -@Serializable -class MongoSystemSwitchRecord : SystemSwitchRecord, MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() - - constructor(record: SystemSwitchRecord) { - this.systemId = record.systemId - this.id = record.id - this.memberIds = record.memberIds - this.timestamp = record.timestamp - } - - override fun toMongo() = this -} +} \ No newline at end of file From fe584ec1ab43564dd2d50a74bfd96d4951bcc634 Mon Sep 17 00:00:00 2001 From: tibs Date: Sat, 17 Dec 2022 15:47:35 -0500 Subject: [PATCH 029/137] Revert "totally fixed" This reverts commit 5b6e20f213ef6b8a3e7f642cdf4ea207b77ba5e6. --- .../dev/proxyfox/database/MongoDatabase.kt | 20 +++++++++----- .../database/records/system/SystemRecord.kt | 26 ++++++------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index 9aa2f93e..643da8d9 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -60,7 +60,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { private lateinit var servers: KCollection private lateinit var channels: KCollection - private lateinit var systems: KCollection + private lateinit var systems: KCollection private lateinit var systemSwitches: KCollection private lateinit var systemTokens: KCollection @@ -182,7 +182,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { system.users.add(userId) user.systemId = system.id updateUser(user) - this.systems.insertOne(MongoSystemRecord(system)).awaitFirst() + this.systems.insertOne(system).awaitFirst() system } } @@ -378,7 +378,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { private val channelSettingsQueue = ConcurrentLinkedQueue>() private val memberQueue = ConcurrentLinkedQueue>() private val memberServerSettingsQueue = ConcurrentLinkedQueue>() - private val systemQueue = ConcurrentLinkedQueue>() + private val systemQueue = ConcurrentLinkedQueue>() private val systemServerSettingsQueue = ConcurrentLinkedQueue>() private val systemChannelSettingsQueue = ConcurrentLinkedQueue>() private val userQueue = ConcurrentLinkedQueue>() @@ -409,7 +409,11 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { override suspend fun updateSystem(system: SystemRecord) { if (witness.add(system)) { - systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).replace() + if (system is MongoSystemRecord) { + systemQueue += system.replace() + } else { + throw IllegalArgumentException("SystemRecord is not a MongoSystemRecord") + } } } @@ -446,7 +450,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { } override suspend fun createSystem(system: SystemRecord) { - systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).create() + if (witness.add(system)) systemQueue += system.create() } override suspend fun createSystemServerSettings(serverSettings: SystemServerSettingsRecord) { @@ -516,7 +520,11 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberProxiesQueue += DeleteManyModel(filter) memberServerSettingsQueue += DeleteManyModel(filter) memberQueue += DeleteManyModel(filter) - systemQueue += (if (system is MongoSystemRecord) system else MongoSystemRecord(system)).delete() + if (system is MongoSystemRecord) { + systemQueue += system.delete() + } else { + throw IllegalStateException("SystemRecord is not a MongoSystemRecord") + } userQueue += DeleteManyModel(filter) return true } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index 389cc5b1..ed74e1d2 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -50,25 +50,15 @@ open class SystemRecord { val showName get() = name?.let { "$it [`$id`]" } ?: "`$id`" } -@Serializable -class MongoSystemRecord() : SystemRecord(), MongoRecord { +class MongoSystemRecord : SystemRecord(), MongoRecord { @Contextual override var _id: ObjectId = ObjectId() +} - constructor(system: SystemRecord) : this() { - id = system.id - users = system.users - name = system.name - description = system.description - tag = system.tag - pronouns = system.pronouns - color = system.color - avatarUrl = system.avatarUrl - timezone = system.timezone - timestamp = system.timestamp - token = system.token - autoProxy = system.autoProxy - autoType = system.autoType - trust = system.trust - } +class JsonSystemStruct : SystemRecord() { + val members: MutableMap = HashMap() + val serverSettings: MutableMap = HashMap() + val channelSettings: MutableMap = HashMap() + val proxyTags: MutableSet = HashSet() + val switches: MutableMap = HashMap() } From ccea77b1b23c24c3ead83e616700ae68821d8527 Mon Sep 17 00:00:00 2001 From: tibs Date: Wed, 21 Dec 2022 11:52:21 -0500 Subject: [PATCH 030/137] Finish database refactor --- .gitignore | 1 - .../dev/proxyfox/conversion/ConversionMain.kt | 8 - .../dev/proxyfox/database/DatabaseMain.kt | 4 +- .../dev/proxyfox/database/DatabaseUtil.kt | 3 +- .../dev/proxyfox/database/InMemoryDatabase.kt | 298 +++++++ .../dev/proxyfox/database/JsonDatabase.kt | 791 ------------------ .../dev/proxyfox/database/MongoDatabase.kt | 26 +- .../database/records/system/SystemRecord.kt | 19 +- .../dev/proxyfox/importer/ImporterTest.kt | 2 +- 9 files changed, 309 insertions(+), 843 deletions(-) create mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt diff --git a/.gitignore b/.gitignore index 42d56c63..6a041a5a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ build/ out/ proxyfox.db.properties -systems.json .env \ No newline at end of file diff --git a/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt b/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt index 7068e75c..aa5278e0 100644 --- a/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt +++ b/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt @@ -11,7 +11,6 @@ package dev.proxyfox.conversion import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import dev.proxyfox.database.Database -import dev.proxyfox.database.JsonDatabase import dev.proxyfox.database.databaseFromString import dev.proxyfox.database.gson import dev.proxyfox.database.records.misc.ServerSettingsRecord @@ -55,13 +54,6 @@ suspend fun main(args: Array) { val systems = if (file.isFile) file else file.resolve("systems.json") val roles = (if (file.isFile) file.parentFile else file).resolve("roles.json") - // NIO is used here as it has a much stronger guarantee that ./systems.json will match correctly. - if (outputDatabase is JsonDatabase && Path("./systems.json") == systems.toPath()) { - logger.warn("JSON database selected as output while trying to import legacy database from current directory.") - logger.warn("Select either a different database or directory before importing.") - exit(2) - } - if (!systems.exists() || !roles.exists()) exit(1, "No data to import at $file; does `systems.json` or `roles.json` exist?") logger.info("Converting legacy database... this may take a while.") diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseMain.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseMain.kt index eeb84723..510ff5cb 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseMain.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseMain.kt @@ -20,8 +20,8 @@ object DatabaseMain { database = try { databaseFromString(db) } catch (err: Throwable) { - printStep("Database setup failed. Falling back to JSON", 2) - JsonDatabase() + printStep("Database setup failed. Falling back to in-memory database", 2) + InMemoryDatabase() }.setup() printStep("Registering shutdown hook for database", 2) // Allows the database to shut down & save correctly. diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt index df26f719..6f040909 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt @@ -126,8 +126,7 @@ fun Collection.firstFree(): String { fun databaseFromString(db: String?) = when (db) { "nop" -> NopDatabase() - "json" -> JsonDatabase() - "postgres" -> TODO("Postgres db isn't implemented yet!") + "in-memory" -> InMemoryDatabase() "mongo", null -> MongoDatabase() else -> throw IllegalArgumentException("Unknown database $db") } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt new file mode 100644 index 00000000..352599f6 --- /dev/null +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.database + +import dev.kord.common.entity.Snowflake +import dev.kord.core.behavior.channel.ChannelBehavior +import dev.proxyfox.database.records.member.MemberProxyTagRecord +import dev.proxyfox.database.records.member.MemberRecord +import dev.proxyfox.database.records.member.MemberServerSettingsRecord +import dev.proxyfox.database.records.misc.* +import dev.proxyfox.database.records.system.SystemChannelSettingsRecord +import dev.proxyfox.database.records.system.SystemRecord +import dev.proxyfox.database.records.system.SystemServerSettingsRecord +import dev.proxyfox.database.records.system.SystemSwitchRecord +import kotlinx.datetime.Instant +import kotlin.time.Duration + +class InMemoryDatabase : Database() { + private var users = HashMap() + + private var messages = ArrayList() + + private var servers = HashMap() + private var channels = HashMap>() + + private var systems = HashMap() + private var systemSwitches = HashMap>() + private var systemTokens = HashMap() + + private var systemServers = HashMap>() + private var systemChannels = HashMap>() + + private var members = HashMap>() + private var memberProxies = HashMap>() + + private var memberServers = HashMap>() + + + override suspend fun setup() = this + override suspend fun ping(): Duration { + return Duration.ZERO + } + + override suspend fun getDatabaseName() = "In-Memory Database" + + override suspend fun fetchUser(userId: ULong): UserRecord? = users[userId] + + override suspend fun getOrCreateUser(userId: ULong): UserRecord { + if (!users.containsKey(userId)) { + users[userId] = UserRecord() + } + + return users[userId]!! + } + + override suspend fun fetchSystemFromId(systemId: String): SystemRecord? = systems[systemId] + + override suspend fun fetchMembersFromSystem(systemId: String): List? = members[systemId]?.values?.toList() + + override suspend fun fetchMemberFromSystem(systemId: String, memberId: String): MemberRecord? = fetchMembersFromSystem(systemId)?.find { it.id == memberId } + + override suspend fun fetchProxiesFromSystem(systemId: String): List? { + val members = fetchMembersFromSystem(systemId) + return members?.mapNotNull { member -> memberProxies[member.id] }?.flatten() + } + + override suspend fun fetchProxiesFromSystemAndMember(systemId: String, memberId: String): List? { + val member = fetchMemberFromSystem(systemId, memberId) + return memberProxies[member?.id] + } + + override suspend fun fetchMemberServerSettingsFromSystemAndMember( + serverId: ULong, + systemId: String, + memberId: String + ): MemberServerSettingsRecord? = memberServers[fetchMemberFromSystem(systemId, memberId)?.id]?.get(serverId) + + override suspend fun getOrCreateServerSettingsFromSystem(serverId: ULong, systemId: String): SystemServerSettingsRecord { + if (!systemServers.containsKey(systemId)) { + systemServers[systemId] = HashMap() + } + + if (!systemServers[systemId]!!.containsKey(serverId)) { + systemServers[systemId]!![serverId] = SystemServerSettingsRecord() + } + + return systemServers[systemId]!![serverId]!! + } + + override suspend fun getOrCreateServerSettings(serverId: ULong): ServerSettingsRecord { + if (!servers.containsKey(serverId)) { + servers[serverId] = ServerSettingsRecord(serverId) + } + + return servers[serverId]!! + } + + override suspend fun updateServerSettings(serverSettings: ServerSettingsRecord) { + servers[serverSettings.serverId] = serverSettings + } + + override suspend fun getOrCreateChannelSettingsFromSystem(channelId: ULong, systemId: String): SystemChannelSettingsRecord { + if (!systemChannels.containsKey(systemId)) { + systemChannels[systemId] = HashMap() + } + + if (!systemChannels[systemId]!!.containsKey(channelId)) { + systemChannels[systemId]!![channelId] = SystemChannelSettingsRecord() + } + + return systemChannels[systemId]!![channelId]!! + } + + override suspend fun getOrCreateChannel(serverId: ULong, channelId: ULong): ChannelSettingsRecord { + getOrCreateServerSettings(serverId) + if (!channels.containsKey(serverId)) { + channels[serverId] = HashMap() + } + + if (!channels[serverId]!!.containsKey(channelId)) { + channels[serverId]!![channelId] = ChannelSettingsRecord(serverId, channelId) + } + + return channels[serverId]!![channelId]!! + } + + override suspend fun updateChannel(channel: ChannelSettingsRecord) { + getOrCreateChannel(channel.serverId, channel.channelId) + channels[channel.serverId]!![channel.channelId] = channel + } + + override suspend fun getOrCreateSystem(userId: ULong, id: String?): SystemRecord { + if (users.containsKey(userId) && users[userId]!!.systemId != null && systems.containsKey(users[userId]!!.systemId)) { + return systems[users[userId]!!.systemId]!! + } + + if (id != null && systems.containsKey(id)) { + return systems[id]!! + } + + val system = SystemRecord() + systems[system.id] = system + systemSwitches[system.id] = ArrayList() + systemServers[system.id] = HashMap() + systemChannels[system.id] = HashMap() + members[system.id] = HashMap() + + val user = getOrCreateUser(userId) + user.systemId = system.id + + return system + } + + override suspend fun dropSystem(userId: ULong): Boolean { + val system = fetchSystemFromUser(userId) ?: return false + systems.remove(system.id) + return true + } + + override suspend fun dropMember(systemId: String, memberId: String): Boolean { + val member = members[systemId]?.remove(memberId) + return member != null + } + + override suspend fun updateMember(member: MemberRecord) { + members[member.systemId]?.set(member.id, member) + } + + override suspend fun updateMemberServerSettings(serverSettings: MemberServerSettingsRecord) { + memberServers[serverSettings.memberId]?.set(serverSettings.serverId, serverSettings) + } + + override suspend fun updateSystem(system: SystemRecord) { + systems[system.id] = system + } + + override suspend fun updateSystemServerSettings(serverSettings: SystemServerSettingsRecord) { + systemServers[serverSettings.systemId]?.set(serverSettings.serverId, serverSettings) + } + override suspend fun updateSystemChannelSettings(channelSettings: SystemChannelSettingsRecord) { + systemChannels[channelSettings.systemId]?.set(channelSettings.channelId, channelSettings) + } + + override suspend fun updateUser(user: UserRecord) { + users[user.id] = user + } + + override suspend fun createMessage( + userId: Snowflake, + oldMessageId: Snowflake, + newMessageId: Snowflake, + channelBehavior: ChannelBehavior, + memberId: String, + systemId: String, + memberName: String + ) { + val message = ProxiedMessageRecord() + message.userId = userId.value + message.oldMessageId = oldMessageId.value + message.newMessageId = newMessageId.value + message.channelId = channelBehavior.id.value + message.memberId = memberId + message.systemId = systemId + message.memberName = memberName + messages.add(message) + } + + override suspend fun updateMessage(message: ProxiedMessageRecord) { + val idx = messages.indexOfFirst { it.oldMessageId == message.oldMessageId } + messages[idx] = message + } + + override suspend fun fetchMessage(messageId: Snowflake): ProxiedMessageRecord? { + return messages.find { it.oldMessageId == messageId.value || it.newMessageId == messageId.value } + } + + override suspend fun fetchLatestMessage( + systemId: String, + channelId: Snowflake + ): ProxiedMessageRecord? { + return messages.findLast { it.systemId == systemId && it.channelId == channelId.value } + } + + override suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord { + if (!systemTokens.containsKey(systemId)) systemTokens[systemId] = TokenRecord(generateToken(), systemId) + return systemTokens[systemId]!! + } + + override suspend fun updateToken(token: TokenRecord) { + systemTokens[token.systemId] = token + } + + override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { + memberProxies[record.memberId]?.add(record) + return true + } + + override suspend fun createSwitch(systemId: String, memberId: List, timestamp: Instant?): SystemSwitchRecord? { + val switches = fetchSwitchesFromSystem(systemId) + if (switches == null) { + systemSwitches[systemId] = ArrayList() + } + val id = ((switches!!.maxOfOrNull { it.id.fromPkString() } ?: 0) + 1).toPkString() + val switch = SystemSwitchRecord(systemId, id, memberId, timestamp) + systemSwitches[systemId]!!.add(switch) + return switch + } + override suspend fun dropSwitch(switch: SystemSwitchRecord) { + systemSwitches[switch.systemId]?.remove(switch) + } + override suspend fun updateSwitch(switch: SystemSwitchRecord) { + systemSwitches[switch.systemId]?.add(switch) + } + + override suspend fun fetchSwitchesFromSystem(systemId: String): List? = systemSwitches[systemId] + + override suspend fun dropProxyTag(proxyTag: MemberProxyTagRecord) { + memberProxies[proxyTag.memberId]?.remove(proxyTag) + } + + override suspend fun updateTrustLevel(systemId: String, trustee: ULong, level: TrustLevel): Boolean { + systems[systemId]?.trust?.set(trustee, level) + return true + } + + override suspend fun fetchTrustLevel(systemId: String, trustee: ULong): TrustLevel { + return systems[systemId]?.trust?.get(trustee) ?: TrustLevel.NONE + } + + override suspend fun fetchTotalSystems(): Int = systems.size + + override suspend fun fetchTotalMembersFromSystem(systemId: String): Int? = members[systemId]?.size + + override suspend fun fetchMemberFromSystemAndName(systemId: String, memberName: String, caseSensitive: Boolean): MemberRecord? { + return members[systemId]?.values?.find { if (caseSensitive) it.name == memberName else it.name.lowercase() == memberName.lowercase() } + } + + override suspend fun export(other: Database) { + TODO("Not yet implemented") + } + + @Deprecated("Not for regular use.", level = DeprecationLevel.ERROR) + override suspend fun drop() { + TODO("Not yet implemented") + } + + override suspend fun firstFreeSystemId(id: String?): String { + return "aaaaa" // my mental state + } + + override fun close() {} +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt deleted file mode 100644 index 5367c539..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/JsonDatabase.kt +++ /dev/null @@ -1,791 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database - -import dev.kord.common.entity.Snowflake -import dev.kord.core.behavior.channel.ChannelBehavior -import dev.proxyfox.common.ellipsis -import dev.proxyfox.common.fromColor -import dev.proxyfox.common.logger -import dev.proxyfox.common.toColor -import dev.proxyfox.database.records.member.MemberProxyTagRecord -import dev.proxyfox.database.records.member.MemberRecord -import dev.proxyfox.database.records.member.MemberServerSettingsRecord -import dev.proxyfox.database.records.misc.* -import dev.proxyfox.database.records.system.SystemChannelSettingsRecord -import dev.proxyfox.database.records.system.SystemRecord -import dev.proxyfox.database.records.system.SystemServerSettingsRecord -import dev.proxyfox.database.records.system.SystemSwitchRecord -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import kotlinx.datetime.LocalDate -import kotlinx.serialization.* -import kotlinx.serialization.json.Json -import org.jetbrains.annotations.TestOnly -import java.io.File -import java.util.concurrent.ConcurrentHashMap -import kotlin.time.Duration - -// Created 2022-26-05T19:47:37 - -/** - * JSON flat file database. Warning, all mutations *will* immediately take effect and are inherently unsafe. - * - * ## Expected File Structure - * ```json5 - * { - * "systems": { - * "sysid": { - * // here for redundancy purposes - * "id": "sysid", - * // list of user snowflakes of users that own the system (have it set as 'system' in their user object) - * "accounts": [], - * // name of the system - * "name": "name", - * // description that the user set - * "description": "description", - * // tag that shows when proxying to identify systems - * "tag": "tag", - * // default avatar for when a member doesn't have an avatar set - * "avatarUrl": "avatar url", - * // timestamp of creation - * "timestamp": "timestamp", - * // the member to autoproxy - * "auto", "memid", - * // the autoproxy type - * "autoType": "autoproxy type", - * // system access token - * "token": "token", - * "members": { - * "memid": { - * // here for redundancy - * "id": "memid", - * // name of the member, can be used to index - * "name": "member name", - * // display namme of the member, shown when proxying - * "displayName": "display name", - * // description that the member set - * "description": "member description", - * // birth date of the member - * "birthday": "member birthday" - * // age of the member - * "age": "0", - * // role of the member - * "role": "role", - * // pronouns of the member - * "pronouns": "pronouns", - * // member's avatar - * "avatarUrl": "avatar url", - * // whether to keep proxy tags - * "keepProxy": false, - * // the amount of messages sent by the member - * "messageCount": 0, - * // timestamp of when the member was created - * "timestamp": "timestamp", - * // per server settings - * "serverSettings": { - * "server snowflake": { - * // display name - * "displayName": "display name", - * // avatar - * "avatarUrl": "avatar URL", - * // whether to proxy or not - * "proxy": true - * } - * } - * } - * }, - * // per server - * "serverSettings": { - * "server snowflake": { - * // autoproxy type - * "autoType": "autoproxy type", - * // whether or not to proxy - * "proxyEnabled": true - * } - * }, - * // array of proxy tags (each prefix/suffix pair will be unique) - * "proxyTags": [ - * { - * "prefix": "prefix" - * "suffix": "suffix", - * "member": "memid" - * } - * ], - * // array of switches - * "switches": { - * "switch": { - * // here for redundancy - * "id": "switch" - * // timestamp of the switch - * "timestamp": "timestamp", - * // array of fronting members - * "members": [], - * } - * } - * } - * }, - * "users": { - * "user snowflake": { - * "system": "sysid", - * // appended to when systems grant trust - * "trusted": { - * "sysid": "LEVEL" - * } - * } - * }, - * "servers": { - * "server snowflake": { - * "channels": { - * // false if disabled - * "channel snowflake": true - * }, - * // role to enable proxying - * "role": "role snowflake" - * } - * } - * } - * ``` - * - * @author Ampflower, Ram - * @since ${version} - **/ -@Serializable -class JsonDatabase(@Transient val file: File = File("systems.json")) : Database() { - @OptIn(ExperimentalSerializationApi::class) - @EncodeDefault - private val schema = 1 - - private lateinit var systems: MutableMap - private lateinit var servers: MutableMap - private lateinit var channels: MutableMap - private lateinit var messages: MutableSet - - @Transient - private lateinit var users: MutableMap - - @Transient - private lateinit var messageMap: MutableMap - - @Volatile - private var dropped: Boolean = false - - override suspend fun setup(): JsonDatabase { - users = ConcurrentHashMap() - messageMap = ConcurrentHashMap() - if (file.exists()) { - val data = file.reader().readText() - val db = Json.decodeFromString(data) - read(db) - - return this - } - init() - - return this - } - - @TestOnly - fun read(db: JsonDatabase) { - if (db.schema == 1) { - systems = db.systems - servers = db.servers - channels = db.channels - messages = db.messages - for ((_, system) in systems) { - system.init() - } - for (message in messages) { - messageMap[message.oldMessageId] = message - messageMap[message.newMessageId] = message - } - } - - for (system in systems.values) { - for (account in system.accounts) { - users[account] = system - } - } - Clock.System.now().epochSeconds - } - - @TestOnly - fun init() { - systems = ConcurrentHashMap() - servers = ConcurrentHashMap() - channels = ConcurrentHashMap() - messages = HashSet() - } - - override suspend fun ping(): Duration { - return Duration.ZERO - } - - override suspend fun getDatabaseName() = "JSON" - - @Deprecated(level = DeprecationLevel.ERROR, message = "Non-native method") - override suspend fun fetchUser(userId: ULong): UserRecord? { - return users[userId]?.let { UserRecord().apply { id = userId; systemId = it.id } } - } - - @Deprecated(level = DeprecationLevel.ERROR, message = "Non-native method") - override suspend fun fetchSystemFromId(systemId: String): SystemRecord? { - return systems[systemId]?.view() - } - - @Deprecated(level = DeprecationLevel.ERROR, message = "Non-native method") - override suspend fun fetchMembersFromSystem(systemId: String): List? { - return systems[systemId]?.members?.values?.map(JsonMemberStruct::view) - } - - @Deprecated(level = DeprecationLevel.ERROR, message = "Non-native method") - override suspend fun fetchMemberFromSystem(systemId: String, memberId: String): MemberRecord? { - return systems[systemId]?.members?.get(memberId)?.view() - } - - @Deprecated(level = DeprecationLevel.ERROR, message = "Non-native method") - override suspend fun fetchFrontingMembersFromSystem(systemId: String): List? { - val system = systems[systemId] ?: return null - return fetchLatestSwitch(systemId)?.memberIds?.mapNotNull { system.members[it]?.view() } - } - - @Deprecated(level = DeprecationLevel.ERROR, message = "Non-native method") - override suspend fun fetchProxiesFromSystem(systemId: String): List? { - return systems[systemId]?.proxyTags?.toList() - } - - @Deprecated(level = DeprecationLevel.ERROR, message = "Non-native method") - override suspend fun fetchProxiesFromSystemAndMember(systemId: String, memberId: String): List? { - return systems[systemId]?.proxyTags?.filter { it.memberId == memberId } - } - - override suspend fun fetchMemberFromMessage(userId: ULong, message: String): MemberRecord? { - val system = users[userId] ?: return null - return system.members[fetchProxyTagFromMessage(userId, message)?.memberId]?.view() - } - - override suspend fun fetchProxyTagFromMessage(userId: ULong, message: String): MemberProxyTagRecord? { - return users[userId]?.proxyTags?.find { it.test(message) } - } - - override suspend fun fetchMemberServerSettingsFromSystemAndMember( - serverId: ULong, - systemId: String, - memberId: String - ): MemberServerSettingsRecord? { - return systems[systemId]?.members?.get(memberId)?.serverSettings?.run { - get(serverId) ?: MemberServerSettingsRecord().apply { - this.serverId = serverId - this.systemId = systemId - this.memberId = memberId - } - } - } - - override suspend fun getOrCreateServerSettingsFromSystem(serverId: ULong, systemId: String): SystemServerSettingsRecord { - return systems[systemId]?.serverSettings?.let { - it[serverId] ?: SystemServerSettingsRecord().apply { - this.serverId = serverId - this.systemId = systemId - } - } ?: fail("No such system $systemId") - } - - override suspend fun getOrCreateServerSettings(serverId: ULong): ServerSettingsRecord { - return servers[serverId] ?: ServerSettingsRecord().apply { - this.serverId = serverId - } - } - - override suspend fun updateServerSettings(serverSettings: ServerSettingsRecord) { - servers[serverSettings.serverId] = serverSettings - } - - override suspend fun getOrCreateChannelSettingsFromSystem(channelId: ULong, systemId: String): SystemChannelSettingsRecord { - return systems[systemId]?.channelSettings?.let { - it[channelId] ?: SystemChannelSettingsRecord().apply { - this.channelId = channelId - this.systemId = systemId - } - } ?: fail("No such system $systemId") - } - - override suspend fun getOrCreateChannel(serverId: ULong, channelId: ULong): ChannelSettingsRecord { - return channels[channelId] ?: ChannelSettingsRecord().apply { - this.serverId = serverId - this.channelId = channelId - } - } - - override suspend fun updateChannel(channel: ChannelSettingsRecord) { - channels[channel.channelId] = channel - } - - @Deprecated(level = DeprecationLevel.ERROR, message = "Non-native method") - override suspend fun getOrCreateSystem(userId: ULong, id: String?): SystemRecord { - return fetchSystemFromUser(userId) ?: run { - val sysId = if (!id.isValidPkString() || systems.containsKey(id)) systems.keys.firstFree() else id - val struct = JsonSystemStruct(sysId) - struct.accounts.add(userId) - initSystem(struct).view() - } - } - - private fun initSystem(struct: JsonSystemStruct): JsonSystemStruct { - for (user in struct.accounts) users[user] = struct - systems[struct.id] = struct - struct.init() - return struct - } - - override suspend fun containsSystem(systemId: String): Boolean { - return systems.containsKey(systemId) - } - - override suspend fun dropSystem(userId: ULong): Boolean { - val system = users[userId] ?: return false - - system.accounts.remove(userId) - users.remove(userId, system) - - if (system.accounts.isEmpty()) { - systems.remove(system.id) - } - - return true - } - - @Deprecated(level = DeprecationLevel.ERROR, message = "Non-native method") - override suspend fun getOrCreateMember(systemId: String, name: String, id: String?): MemberRecord? { - return fetchMemberFromSystemAndName(systemId, name) ?: run { - val system = systems[systemId] ?: return null - val memId = if (!id.isValidPkString() || system.members.containsKey(id)) system.members.keys.firstFree() else id - system.putMember(JsonMemberStruct(id = memId, systemId = systemId, name = name)).view() - } - } - - override suspend fun containsMember(systemId: String, memberId: String): Boolean { - return systems[systemId]?.members?.containsKey(memberId) ?: false - } - - override suspend fun dropMember(systemId: String, memberId: String): Boolean { - return systems[systemId]?.removeMember(memberId) ?: false - } - - override suspend fun updateMember(member: MemberRecord) { - systems[member.systemId]!!.run { - members[member.id]?.from(member, this) ?: run { putMember(JsonMemberStruct(member)) } - } - } - - override suspend fun updateMemberServerSettings(serverSettings: MemberServerSettingsRecord) { - systems[serverSettings.systemId]!! - .members[serverSettings.memberId]!! - .serverSettings[serverSettings.serverId] = serverSettings - } - - override suspend fun updateSystem(system: SystemRecord) { - systems[system.id]?.from(system) ?: initSystem(JsonSystemStruct(system)) - } - - override suspend fun updateSystemServerSettings(serverSettings: SystemServerSettingsRecord) { - systems[serverSettings.systemId]!! - .serverSettings[serverSettings.serverId] = serverSettings - } - - override suspend fun updateSystemChannelSettings(channelSettings: SystemChannelSettingsRecord) { - systems[channelSettings.systemId]!! - .channelSettings[channelSettings.channelId] = channelSettings - } - - @Deprecated(level = DeprecationLevel.ERROR, message = "Non-native method") - override suspend fun updateUser(user: UserRecord) { - if (user.systemId == null) { - dropSystem(user.id) - } else { - val system = systems[user.systemId] ?: throw IllegalArgumentException("No such system ${user.systemId}") - users[user.id] = system - system.accounts.add(user.id) - } - } - - override suspend fun createMessage( - userId: Snowflake, - oldMessageId: Snowflake, - newMessageId: Snowflake, - channelBehavior: ChannelBehavior, - memberId: String, - systemId: String, - memberName: String - ) { - val message = ProxiedMessageRecord() - message.memberName = memberName - message.oldMessageId = oldMessageId.value - message.newMessageId = newMessageId.value - val channel = channelBehavior.fetchChannel() - message.channelId = channel.id.value - message.guildId = channel.data.guildId.value?.value ?: 0UL - message.memberId = memberId - message.systemId = systemId - messages.add(message) - messageMap[oldMessageId.value] = message - messageMap[newMessageId.value] = message - } - - override suspend fun updateMessage(message: ProxiedMessageRecord) { - messageMap[message.oldMessageId]?.let { old -> - messageMap.remove(old.newMessageId) - messages.remove(message) - } - messageMap[message.oldMessageId] = message - messages.add(message) - } - - override suspend fun fetchMessage(messageId: Snowflake): ProxiedMessageRecord? { - return messageMap[messageId.value] - } - - override suspend fun fetchLatestMessage( - systemId: String, - channelId: Snowflake - ): ProxiedMessageRecord? { - // TODO: Better caching logic - return messages.firstOrNull { it.channelId == channelId.value && it.systemId == systemId } - } - - override suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord { - return TokenRecord(systems[systemId]?.token ?: generateToken(), systemId) - } - - override suspend fun updateToken(token: TokenRecord) { - systems[token.systemId]?.token = token.token - } - - override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { - return systems[record.systemId]!!.proxyTags.add(record) - } - - override suspend fun createSwitch(systemId: String, memberId: List, timestamp: Instant?): SystemSwitchRecord? { - val system = systems[systemId] ?: return null - val switch = SystemSwitchRecord() - val id = ((system.switches.keys.maxOfOrNull { it.fromPkString() } ?: 0) + 1).toPkString() - switch.id = id - switch.systemId = systemId - switch.memberIds = memberId - timestamp?.let { switch.timestamp = it } - system.switches[id] = switch - return switch - } - - override suspend fun dropSwitch(switch: SystemSwitchRecord) { - systems[switch.systemId]?.run { switches.remove(switch.id) } - } - - override suspend fun updateSwitch(switch: SystemSwitchRecord) { - systems[switch.systemId]?.run { switches[switch.id] = switch } - } - - override suspend fun fetchSwitchesFromSystem(systemId: String): List? { - return systems[systemId]?.switches?.values?.toList() - } - - override suspend fun dropProxyTag(proxyTag: MemberProxyTagRecord) { - systems[proxyTag.systemId]!!.proxyTags.remove(proxyTag) - } - - override suspend fun updateTrustLevel(systemId: String, trustee: ULong, level: TrustLevel): Boolean { - val system = systems[systemId] ?: return false - system.trust[trustee] = level - return true - } - - override suspend fun fetchTrustLevel(systemId: String, trustee: ULong): TrustLevel { - return systems[systemId]?.trust?.get(trustee) ?: TrustLevel.NONE - } - - override suspend fun fetchTotalSystems() = systems.size - - override suspend fun fetchTotalMembersFromSystem(systemId: String): Int? { - return systems[systemId]?.members?.size - } - - override suspend fun fetchMemberFromSystemAndName(systemId: String, memberName: String, caseSensitive: Boolean): MemberRecord? { - return systems[systemId]?.membersByName?.get(memberName)?.view() - } - - override suspend fun export(other: Database) { - val memberLookup = HashMap() - logger.info("Migrating {} systems$ellipsis", systems.size) - for ((sid, system) in systems) { - memberLookup.clear() - logger.info("Migrating {}: {}", sid, system.name) - val newSystem = other.getOrCreateSystem(system.accounts[0], sid) - system.writeTo(newSystem) - other.updateSystem(newSystem) - - val nsid = newSystem.id - - logger.info("Migrating {} members$ellipsis", system.members.size) - for ((mid, member) in system.members) { - logger.info("Migrating {}: {}", mid, member.name) - val newMember = other.getOrCreateMember(sid, member.name, mid) - if (newMember == null) { - logger.warn("Unable to import {}: {} didn't return a member for {}/{} ({}/???)?", member.name, other, sid, mid, nsid) - } else { - memberLookup[mid] = newMember.id - member.writeTo(newMember) - other.updateMember(newMember) - } - } - - for ((id, serverSettings) in system.serverSettings) { - val newSettings = other.getOrCreateServerSettingsFromSystem(id, nsid) - serverSettings.writeTo(newSettings, memberLookup[serverSettings.autoProxy]) - other.updateSystemServerSettings(newSettings) - logger.info("Written server settings for {}", id) - } - - for ((id, channelSettings) in system.channelSettings) { - val newSettings = other.getOrCreateChannelSettingsFromSystem(id, nsid) - channelSettings.writeTo(newSettings) - updateSystemChannelSettings(newSettings) - logger.info("Written channel settings for {}", id) - } - - for (proxyTag in system.proxyTags) { - val member = memberLookup[proxyTag.memberId] - if (member == null) { - logger.warn("Couldn't write proxy tag {}text{} for {}/{} ({}/???)", proxyTag.prefix, proxyTag.suffix, sid, proxyTag.memberId, nsid) - } else { - other.createProxyTag(nsid, member, proxyTag.prefix, proxyTag.suffix) - logger.info("Written proxy tag {}text{} for {}", proxyTag.prefix, proxyTag.suffix, member) - } - } - - for ((id, switch) in system.switches) { - val newSwitch = other.createSwitch(nsid, switch.memberIds.mapNotNull(memberLookup::get), switch.timestamp) - if (newSwitch == null) { - logger.warn("Couldn't write switch {}/{} to {}", sid, id, nsid) - } else { - logger.info("Written switch {}/{} to {}/{}", sid, id, nsid, newSwitch.id) - } - } - } - logger.info("Migrating server settings$ellipsis") - for ((sid, server) in servers) { - val newSettings = other.getOrCreateServerSettings(sid) - server.writeTo(newSettings) - other.updateServerSettings(newSettings) - logger.info("Written server settings for {}", sid) - } - } - - @Deprecated("Not for regular use.", level = DeprecationLevel.ERROR) - override suspend fun drop() { - dropped = true - - systems.clear() - servers.clear() - channels.clear() - messages.clear() - users.clear() - messageMap.clear() - - if (file.exists()) { - file.delete() - } - } - - override suspend fun firstFreeSystemId(id: String?): String { - return if (!id.isValidPkString() || systems.containsKey(id)) systems.keys.firstFree() else id - } - - override suspend fun firstFreeMemberId(systemId: String, id: String?): String { - return systems[systemId]?.let { system -> if (!id.isValidPkString() || system.members.containsKey(id)) system.members.keys.firstFree() else id } ?: fail("No such system") - } - - override fun close() { - if (dropped) return - file.writeText(Json.encodeToString(this)) - } - - @Serializable - data class JsonSystemStruct( - val id: String, - /** The user must have their snowflake bound to `system` to be included here. */ - val accounts: ArrayList = ArrayList(), - var name: String? = null, - var description: String? = null, - var tag: String? = null, - var pronouns: String? = null, - var color: Int = -1, - var avatarUrl: String? = null, - var timezone: String? = null, - var timestamp: Instant? = Clock.System.now(), - var auto: String? = null, - var autoType: AutoProxyMode? = AutoProxyMode.OFF, - var token: String = generateToken(), - - val members: MutableMap = HashMap(), - val serverSettings: MutableMap = HashMap(), - val channelSettings: MutableMap = HashMap(), - val proxyTags: MutableSet = HashSet(), - val switches: MutableMap = HashMap(), - val trust: HashMap = HashMap() - ) { - constructor(record: SystemRecord) : this(record.id) { - from(record) - } - - /** - * Cached lookup of name to member. - * */ - @Transient - lateinit var membersByName: HashMap - - fun init() { - val membersByName = HashMap() - for ((_, member) in members) { - val old = membersByName.put(member.name, member) - member.systemId = id - if (old != null) logger.warn("Member {} collided with {} from {}", member, old, id) - } - this.membersByName = membersByName - for (proxyTag in proxyTags) { - proxyTag.systemId = id - } - } - - fun view(): SystemRecord { - val record = SystemRecord() - record.id = id - writeTo(record) - return record - } - - fun writeTo(record: SystemRecord) { - record.users.addAll(accounts) - record.name = name - record.description = description - record.tag = tag - record.pronouns = pronouns - record.color = color - record.avatarUrl = avatarUrl - record.timezone = timezone - timestamp?.let { record.timestamp = it } - record.autoProxy = auto - autoType?.let { - record.autoType = it - } - record.token = token - record.trust = trust - } - - fun from(record: SystemRecord) { - accounts.clear() - accounts.addAll(record.users) - name = record.name - description = record.description - tag = record.tag - pronouns = record.pronouns - color = record.color - avatarUrl = record.avatarUrl - timezone = record.timezone - timestamp = record.timestamp - auto = record.autoProxy - autoType = record.autoType - token = record.token - } - - fun putMember(member: JsonMemberStruct): JsonMemberStruct { - assert(member.systemId == id) { "systemId != id" } - members[member.id] = member - membersByName[member.name] = member - return member - } - - fun removeMember(member: String): Boolean { - val struct = members.remove(member) ?: return false - membersByName.remove(struct.name) - - proxyTags.removeIf { it.memberId == member } - - return true - } - } - - @Serializable - data class JsonMemberStruct( - val id: String, - var systemId: String, - var name: String, - var displayName: String? = null, - var description: String? = null, - var birthday: LocalDate? = null, - var age: String? = null, - var role: String? = null, - var pronouns: String? = null, - var color: String? = null, - var avatarUrl: String? = null, - var keepProxy: Boolean = false, - var messageCount: ULong = 0UL, - var timestamp: Instant? = Clock.System.now(), - - val serverSettings: MutableMap = HashMap() - ) { - - constructor(record: MemberRecord) : this(record.id, record.systemId, record.name) { - from(record) - } - - fun view(): MemberRecord { - val record = MemberRecord() - record.id = id - record.systemId = systemId - writeTo(record) - return record - } - - fun writeTo(record: MemberRecord) { - record.name = name - record.displayName = displayName - record.description = description - record.birthday = birthday - record.age = age - record.role = role - record.pronouns = pronouns - record.color = color.toColor() - record.avatarUrl = avatarUrl - record.keepProxy = keepProxy - record.messageCount = messageCount - timestamp?.let { record.timestamp = it } - } - - fun JsonSystemStruct.from(record: MemberRecord) = from(record, this) - - fun from(record: MemberRecord, system: JsonSystemStruct? = null) { - if (name != record.name && system != null) { - system.membersByName.remove(name) - system.membersByName[record.name] = this - name = record.name - } - displayName = record.displayName - description = record.description - birthday = record.birthday - age = record.age - role = record.role - pronouns = record.pronouns - color = record.color.fromColor() - avatarUrl = record.avatarUrl - keepProxy = record.keepProxy - messageCount = record.messageCount - timestamp = record.timestamp - } - } -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index 643da8d9..6179f782 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -196,11 +196,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberProxies.deleteMany(filter).awaitFirst() memberServers.deleteMany(filter).awaitFirst() members.deleteMany(filter).awaitFirst() - if (system is MongoSystemRecord) { - systems.deleteOneById(system._id).awaitFirst() - } else { - throw IllegalStateException("SystemRecord is not a MongoSystemRecord") - } + systems.deleteOneById(system._id).awaitFirst() users.deleteMany(filter).awaitFirst() return true } @@ -223,11 +219,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { } override suspend fun updateSystem(system: SystemRecord) { - if (system is MongoSystemRecord) { - systems.replaceOneById(system._id, system, upsert()).awaitFirst() - } else { - throw IllegalArgumentException("SystemRecord is not a MongoSystemRecord") - } + systems.replaceOneById(system._id, system, upsert()).awaitFirst() } override suspend fun updateSystemServerSettings(serverSettings: SystemServerSettingsRecord) { @@ -408,13 +400,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { } override suspend fun updateSystem(system: SystemRecord) { - if (witness.add(system)) { - if (system is MongoSystemRecord) { - systemQueue += system.replace() - } else { - throw IllegalArgumentException("SystemRecord is not a MongoSystemRecord") - } - } + if (witness.add(system)) systemQueue += system.replace() } override suspend fun updateSystemServerSettings(serverSettings: SystemServerSettingsRecord) { @@ -520,11 +506,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberProxiesQueue += DeleteManyModel(filter) memberServerSettingsQueue += DeleteManyModel(filter) memberQueue += DeleteManyModel(filter) - if (system is MongoSystemRecord) { - systemQueue += system.delete() - } else { - throw IllegalStateException("SystemRecord is not a MongoSystemRecord") - } + systemQueue += system.delete() userQueue += DeleteManyModel(filter) return true } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index ed74e1d2..63c5eb1e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -8,11 +8,9 @@ package dev.proxyfox.database.records.system -import dev.proxyfox.database.JsonDatabase import dev.proxyfox.database.PkId import dev.proxyfox.database.generateToken import dev.proxyfox.database.records.MongoRecord -import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.TrustLevel import kotlinx.datetime.Clock @@ -29,7 +27,9 @@ import org.bson.types.ObjectId * @author Ampflower **/ @Serializable -open class SystemRecord { +open class SystemRecord : MongoRecord { + @Contextual + override var _id: ObjectId = ObjectId() var id: PkId = "" var users: ArrayList = ArrayList() var name: String? = null @@ -49,16 +49,3 @@ open class SystemRecord { val showName get() = name?.let { "$it [`$id`]" } ?: "`$id`" } - -class MongoSystemRecord : SystemRecord(), MongoRecord { - @Contextual - override var _id: ObjectId = ObjectId() -} - -class JsonSystemStruct : SystemRecord() { - val members: MutableMap = HashMap() - val serverSettings: MutableMap = HashMap() - val channelSettings: MutableMap = HashMap() - val proxyTags: MutableSet = HashSet() - val switches: MutableMap = HashMap() -} diff --git a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt index 7b5ac3d4..4ed10121 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt @@ -15,7 +15,7 @@ import dev.proxyfox.database.DatabaseTestUtil.entity import dev.proxyfox.database.DatabaseTestUtil.instantEpoch import dev.proxyfox.database.DatabaseTestUtil.instantLastMicroOfEpochDay import dev.proxyfox.database.DatabaseTestUtil.seeded -import dev.proxyfox.database.JsonDatabase +import dev.proxyfox.database.InMemoryDatabase import dev.proxyfox.database.MongoDatabase import dev.proxyfox.database.etc.importer.* import kotlinx.coroutines.ExperimentalCoroutinesApi From b6036a736b0392cdbded0f241223dc48fd7fef47 Mon Sep 17 00:00:00 2001 From: Octal Date: Sun, 25 Dec 2022 14:51:12 -0600 Subject: [PATCH 031/137] Update versions of things --- build.gradle.kts | 4 ++-- gradle/libs.versions.toml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index bba16222..7286ec1f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,8 +40,8 @@ allprojects { } java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_18 + targetCompatibility = JavaVersion.VERSION_18 } repositories { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ede4719e..1675bdc2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] guava = "31.1-jre" logback = "1.2.11" -kord = "0.8.0-M16" -kotlin = "1.7.10" +kord = "0.8.0-M17" +kotlin = "1.7.21" kotlinx_coroutines = "1.6.4" -proxyfox_command = "1.3" +proxyfox_command = "1.5" # Database-specific postgres = "42.3.3" From 2a1c44431ba5f374ab5af6349f8782f0fac8406f Mon Sep 17 00:00:00 2001 From: Octal Date: Sun, 25 Dec 2022 14:58:59 -0600 Subject: [PATCH 032/137] Use Java 18 --- .github/workflows/docker.yml | 4 ++-- .github/workflows/gradle.yml | 4 ++-- .github/workflows/publish.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a70e0d08..524b0f36 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -25,11 +25,11 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - - name: Setup JDK 17 + - name: Setup JDK 18 uses: actions/setup-java@v3 with: distribution: temurin - java-version: 17 + java-version: 18 - name: Build run: ./gradlew build -x check -x test --no-daemon diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index b66a80e0..8200feb8 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -28,11 +28,11 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - - name: Setup JDK 17 + - name: Setup JDK 18 uses: actions/setup-java@v3 with: distribution: temurin - java-version: 17 + java-version: 18 - name: Build run: ./gradlew build --no-daemon diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cc0c9d44..b50de4ad 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: temurin - java-version: 17 + java-version: 18 - name: Build and publish with Gradle run: ./gradlew poolRelease --no-daemon From c8714b3a92cc395faf17d0b0b3f05edc99267f7d Mon Sep 17 00:00:00 2001 From: Octal Date: Sun, 25 Dec 2022 15:12:48 -0600 Subject: [PATCH 033/137] Replace all references of JsonDatabase with InMemoryDatabase --- .../kotlin/dev/proxyfox/database/InMemoryDatabase.kt | 2 +- .../test/kotlin/dev/proxyfox/database/DatabaseTest.kt | 5 ++--- .../kotlin/dev/proxyfox/database/DatabaseTestUtil.kt | 7 ++----- .../test/kotlin/dev/proxyfox/exporter/ExporterTest.kt | 11 +++++------ .../test/kotlin/dev/proxyfox/importer/ImporterTest.kt | 2 +- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index 352599f6..9bf12d74 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -47,7 +47,7 @@ class InMemoryDatabase : Database() { return Duration.ZERO } - override suspend fun getDatabaseName() = "In-Memory Database" + override suspend fun getDatabaseName() = "In-Memory" override suspend fun fetchUser(userId: ULong): UserRecord? = users[userId] diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTest.kt index 653af46f..6d9e4236 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTest.kt @@ -15,8 +15,7 @@ import dev.proxyfox.database.DatabaseTestUtil.seeded import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest -import org.testng.Assert.assertNotNull -import org.testng.Assert.assertNull +import org.testng.Assert.* import org.testng.annotations.* import java.nio.file.Files @@ -176,7 +175,7 @@ class DatabaseTest @Factory(dataProvider = "constructorParameters") constructor( @DataProvider @JvmStatic fun constructorParameters() = arrayOf( - arrayOf("JSON", { JsonDatabase(test.resolve("systems-${System.nanoTime()}.json").toFile()) }), + arrayOf("InMemory", { InMemoryDatabase() }), arrayOf("MongoDB", { MongoDatabase("TestFoxy-" + System.nanoTime()) }), ) diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt index f5b3fd06..5ffd6764 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt @@ -12,10 +12,9 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.entity.Entity import io.mockk.every import io.mockk.mockk +import kotlinx.datetime.toKotlinInstant import org.testng.annotations.DataProvider import java.time.Instant -import java.time.OffsetDateTime -import java.time.ZoneOffset import java.util.* import java.util.concurrent.TimeUnit import java.util.stream.IntStream @@ -38,10 +37,8 @@ object DatabaseTestUtil { private val rng = Random() const val offsetDateTimeEpochString = "1970-01-01T00:00:00Z" - val offsetDateTimeEpoch = OffsetDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)!! - val offsetDateTimeLastMicroOfEpochDay = OffsetDateTime.of(1970, 1, 1, 23, 59, 59, 999_999_000, ZoneOffset.UTC)!! - val instantEpoch = Instant.EPOCH!! + val instantEpoch = Instant.EPOCH!!.toKotlinInstant() val instantLastMicroOfEpochDay = Instant.ofEpochSecond(TimeUnit.DAYS.toSeconds(1) - 1L, 999_999_000L)!! inline fun entity(ret: ULong): T { diff --git a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt index 351365a4..ee7a69c1 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt @@ -9,14 +9,13 @@ package dev.proxyfox.exporter import dev.proxyfox.database.DatabaseTestUtil.instantEpoch -import dev.proxyfox.database.DatabaseTestUtil.offsetDateTimeEpoch import dev.proxyfox.database.DatabaseTestUtil.offsetDateTimeEpochString -import dev.proxyfox.database.records.member.MemberRecord -import dev.proxyfox.database.records.system.SystemRecord -import dev.proxyfox.database.records.system.SystemSwitchRecord import dev.proxyfox.database.etc.types.PkMember import dev.proxyfox.database.etc.types.PkSwitch import dev.proxyfox.database.etc.types.PkSystem +import dev.proxyfox.database.records.member.MemberRecord +import dev.proxyfox.database.records.system.SystemRecord +import dev.proxyfox.database.records.system.SystemSwitchRecord import org.testng.Assert import org.testng.annotations.Test @@ -30,7 +29,7 @@ class ExporterTest { @Test fun `Exporter(System) - retain seconds`() { val system = PkSystem(SystemRecord().apply { - timestamp = offsetDateTimeEpoch + timestamp = instantEpoch }) Assert.assertEquals(system.created, offsetDateTimeEpochString) } @@ -38,7 +37,7 @@ class ExporterTest { @Test fun `Exporter(Member) - retain seconds`() { val member = PkMember(MemberRecord().apply { - timestamp = offsetDateTimeEpoch + timestamp = instantEpoch }, null) Assert.assertEquals(member.created, offsetDateTimeEpochString) } diff --git a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt index 4ed10121..b8af1fc7 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt @@ -189,7 +189,7 @@ constructor(private val name: String, databaseFactory: () -> Database) { @DataProvider @JvmStatic fun constructorParameters() = arrayOf( - arrayOf("JSON", { JsonDatabase(test.resolve("systems-${System.nanoTime()}.json").toFile()) }), + arrayOf("InMemory", { InMemoryDatabase() }), arrayOf("MongoDB", { MongoDatabase("TestFoxy-" + System.nanoTime()) }), ) From b706da38806a751111605f5e8c4147a4d88cdec4 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sat, 31 Dec 2022 22:41:00 -0600 Subject: [PATCH 034/137] Update Gradle to 7.6 --- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 257 ++++++++++++++--------- 3 files changed, 154 insertions(+), 105 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fce..070cb702 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882e..1b6c7873 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # 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"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + 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. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# 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. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" From dbb972670e151fcc3e9c68e18ae3a09ea7fb0a7d Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sat, 31 Dec 2022 22:44:13 -0600 Subject: [PATCH 035/137] Provide immediate reason for inability to proxy if fixable by end user. --- .../src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt index 2ab1b9dd..5dac012f 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt @@ -102,7 +102,7 @@ data class ProxyContext( val id = if (channel is ThreadChannelBehavior) channel.parentId.value else channel.id.value WebhookCache -= id } - throw RuntimeException("Failed to proxy your message.", e) + throw RuntimeException("Failed to proxy your message: $e", e) } if (newMessage.content != messageContent && messageContent.isNotBlank()) webhook.edit(newMessage.id, threadId) { From 201642a4ecddc4c9c3d206fa9d36ca428e2c537f Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 1 Jan 2023 23:30:01 -0600 Subject: [PATCH 036/137] Add far better logging of exceptions. May make it worse for looking at the stacktrace on mobile tho. --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index f8c6f755..523f0b3e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -33,7 +33,6 @@ import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent import dev.kord.gateway.builder.Shards import dev.kord.rest.builder.message.EmbedBuilder -import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException import dev.proxyfox.common.* import dev.proxyfox.database.database @@ -49,6 +48,7 @@ import kotlinx.datetime.Clock import kotlinx.datetime.Instant import java.lang.Integer.min import java.time.OffsetDateTime +import java.util.* import java.util.concurrent.Executors import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind @@ -61,6 +61,11 @@ const val UPLOAD_LIMIT = 1024 * 1024 * 8 val scheduler = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()) +private val idUrl = System.getenv("PROXYFOX_KEY").let { it.substring(0, it.indexOf('.')) } + +private val webhook = Regex("https?://(?:[^./]\\.)?discord(?:app)?\\.com/api/(v\\d+/)?webhooks/\\d+/\\S+") +private val token = Regex("$idUrl[a-z0-9=/+_-]*\\.[a-z0-9=/+_-]+\\.[a-z0-9=/+_-]", RegexOption.IGNORE_CASE) + lateinit var scope: CoroutineScope lateinit var kord: Kord lateinit var http: HttpClient @@ -185,32 +190,30 @@ suspend fun updatePresence() { suspend fun handleError(err: Throwable, message: MessageBehavior) { // Catch any errors and log them val timestamp = System.currentTimeMillis() - logger.warn(timestamp.toString()) - logger.warn(err.stackTraceToString()) - val reason = err.message + // Let the logger unwind the stacktrace. + logger.warn(timestamp.toString(), err) + // Do not leak webhook URL nor token in output. + // Note: The token here is a generic regex that only matches by the bot's + // ID and will make no attempt to verify it's the real one, purely for guarding the + val reason = err.message?.replace(webhook, "[WEBHOOK]")?.replace(token, "[TOKEN]") var cause = "" err.stackTrace.forEach { - if (it.toString().startsWith("dev.proxyfox")) + if (it.className.startsWith("dev.proxyfox")) cause += " at $it\n" } message.channel.createMessage( "An unexpected error occurred.\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" ) - if (err is DebugException) return + // if (err is DebugException) return if (errorChannel == null && errorChannelId != null) errorChannel = kord.getChannel(errorChannelId) as TextChannel if (errorChannel != null) { - cause = "" - err.stackTrace.forEach { - if (cause.length > 2000) return@forEach - cause += "at $it\n\n" - } + // Prevent the log channel from also showing tokens, should it be public in any manner. + cause = err.stackTraceToString().replace(webhook, "[WEBHOOK]").replace(token, "[TOKEN]") + errorChannel!!.createMessage { content = "`$timestamp`" - embed { - title = "${err.javaClass.name}: $reason" - description = "```\n$cause```" - } + addFile("exception.log", cause.byteInputStream()) } } } From 181e27cd3385b5726af5002a45586f3c95f4e09f Mon Sep 17 00:00:00 2001 From: Octal Date: Wed, 4 Jan 2023 23:55:31 -0600 Subject: [PATCH 037/137] Initial pluralkit sync setup --- gradle/libs.versions.toml | 7 ++++- modules/bot/build.gradle.kts | 1 + modules/sync/build.gradle.kts | 28 +++++++++++++++++++ .../main/kotlin/dev/proxyfox/sync/PkSync.kt | 25 +++++++++++++++++ settings.gradle.kts | 3 +- 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 modules/sync/build.gradle.kts create mode 100644 modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1675bdc2..d22c36a6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,10 @@ logback = "1.2.11" kord = "0.8.0-M17" kotlin = "1.7.21" kotlinx_coroutines = "1.6.4" + +# ProxyFox Libraries proxyfox_command = "1.5" +pluralkt = "1.2" # Database-specific postgres = "42.3.3" @@ -24,7 +27,9 @@ licenser = "1.1.2" guava = { module = "com.google.guava:guava", version.ref = "guava" } kord = { module = "dev.kord:kord-core", version.ref = "kord" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } + proxyfox_command = { module = "dev.proxyfox:proxyfox-command", version.ref = "proxyfox_command" } +pluralkt = { module = "dev.proxyfox:pluralkt", version.ref = "pluralkt" } kotlin_stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlinx_coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx_coroutines" } @@ -41,7 +46,7 @@ mockk = { module = "io.mockk:mockk", version.ref = "mockk" } kotlinx_coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx_coroutines" } [bundles] -base = ["guava", "logback", "kotlin_stdlib", "kotlinx_coroutines_core", "kord", "proxyfox_command"] +base = ["guava", "logback", "kotlin_stdlib", "kotlinx_coroutines_core", "kord", "proxyfox_command", "pluralkt"] test = ["testng", "kotlinx_coroutines_test", "mockk"] database = ["gson", "kmongo_base", "kmongo_coroutine", "kmongo_async"] diff --git a/modules/bot/build.gradle.kts b/modules/bot/build.gradle.kts index b816886c..1528f48f 100644 --- a/modules/bot/build.gradle.kts +++ b/modules/bot/build.gradle.kts @@ -15,6 +15,7 @@ plugins { dependencies { implementation(project(":modules:common")) implementation(project(":modules:database")) + implementation(project(":modules:sync")) } application.mainClass.set("dev.proxyfox.bot.BotMainKt") diff --git a/modules/sync/build.gradle.kts b/modules/sync/build.gradle.kts new file mode 100644 index 00000000..b816886c --- /dev/null +++ b/modules/sync/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022, The ProxyFox Group + * + * This Source Code 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 { + application + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.shadow) +} + +dependencies { + implementation(project(":modules:common")) + implementation(project(":modules:database")) +} + +application.mainClass.set("dev.proxyfox.bot.BotMainKt") + +tasks { + shadowJar { + archiveBaseName.set("proxyfox") + archiveClassifier.set("") + mergeServiceFiles() + } +} \ No newline at end of file diff --git a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt new file mode 100644 index 00000000..a51a0314 --- /dev/null +++ b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt @@ -0,0 +1,25 @@ +package dev.proxyfox.sync + +import dev.proxyfox.database.records.system.SystemRecord +import dev.proxyfox.pluralkt.PluralKt +import dev.proxyfox.pluralkt.types.PkSystem + +object PkSync { + fun pull(token: String, system: SystemRecord) { + PluralKt.System.getMe(token) { + if (isSuccess()) { + getSuccess().getMembers(token) + } + } + } + + private fun PkSystem.getMembers(token: String) { + PluralKt.Member.getMembers(id, token) { + if (isSuccess()) { + for (member in getSuccess()) { + //... + } + } + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 0f092266..f3ea08b7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,4 +24,5 @@ include(":modules:common") include(":modules:database") include(":modules:conversion") include(":modules:api") -include(":modules:api:server") \ No newline at end of file +include(":modules:api:server") +include(":modules:sync") \ No newline at end of file From 4d0142ba240e204fb2a58b94835e0b5bf7754725 Mon Sep 17 00:00:00 2001 From: Octal Date: Wed, 4 Jan 2023 23:58:53 -0600 Subject: [PATCH 038/137] Apply&update licenses --- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt | 2 +- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt | 2 +- .../src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt | 2 +- .../bot/src/main/kotlin/dev/proxyfox/bot/SchedulerUtil.kt | 2 +- .../src/main/kotlin/dev/proxyfox/bot/command/Commands.kt | 2 +- .../kotlin/dev/proxyfox/bot/command/MemberCommands.kt | 2 +- .../main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt | 2 +- .../kotlin/dev/proxyfox/bot/command/SwitchCommands.kt | 2 +- .../kotlin/dev/proxyfox/bot/command/SystemCommands.kt | 2 +- .../dev/proxyfox/bot/command/context/DiscordContext.kt | 2 +- .../proxyfox/bot/command/context/DiscordMessageContext.kt | 2 +- .../bot/command/context/InteractionCommandContext.kt | 2 +- .../dev/proxyfox/bot/command/node/AttachmentNode.kt | 2 +- .../bot/src/main/kotlin/dev/proxyfox/bot/md/Markdown.kt | 2 +- .../src/main/kotlin/dev/proxyfox/bot/prompts/Button.kt | 2 +- .../bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt | 2 +- .../main/kotlin/dev/proxyfox/bot/prompts/TimedPrompt.kt | 2 +- .../kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt | 2 +- .../kotlin/dev/proxyfox/bot/terminal/TerminalCommands.kt | 2 +- .../main/kotlin/dev/proxyfox/bot/webhook/GuildMessage.kt | 2 +- .../main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt | 2 +- .../main/kotlin/dev/proxyfox/bot/webhook/WebhookCache.kt | 2 +- .../main/kotlin/dev/proxyfox/bot/webhook/WebhookHolder.kt | 2 +- .../main/kotlin/dev/proxyfox/bot/webhook/WebhookUtil.kt | 2 +- modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt | 8 ++++++++ 25 files changed, 32 insertions(+), 24 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt index f4bc1cd0..0f91305d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 177995e8..00fed93b 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index a95c49fa..461106d8 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/SchedulerUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/SchedulerUtil.kt index 2003e502..45581f2d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/SchedulerUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/SchedulerUtil.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index eb004a24..0dad1c22 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 5dcc060c..f0c21c6e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index dd3758b0..9136d574 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index e7e0736e..abe7c973 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 0b0d79d5..0e7f0ce4 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index ef0d5bc9..161cece8 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index 71a19ec1..4cfea5fa 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index 85fe87a5..3ae48628 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt index 8d232063..74ec055b 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/md/Markdown.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/md/Markdown.kt index d0458b96..1ed9a820 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/md/Markdown.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/md/Markdown.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Button.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Button.kt index 984054b1..abbf4a6b 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Button.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Button.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt index 4f34020d..640b95fb 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedPrompt.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedPrompt.kt index f470af31..63120962 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedPrompt.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedPrompt.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt index 01920b83..58ab357d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalCommands.kt index 5378fd54..dfd433d6 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalCommands.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/GuildMessage.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/GuildMessage.kt index 7404c7e1..fe24f125 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/GuildMessage.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/GuildMessage.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt index 5dac012f..e7e150f9 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookCache.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookCache.kt index 331815dd..109d9f2f 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookCache.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookCache.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookHolder.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookHolder.kt index bb71fcf9..b75f258e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookHolder.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookHolder.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookUtil.kt index 59d070f3..9b9bc112 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookUtil.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt index a51a0314..0ad09f28 100644 --- a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt +++ b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.sync import dev.proxyfox.database.records.system.SystemRecord From 49f7cdca1faaea77cd46fc5c0c13af57b68f1e6c Mon Sep 17 00:00:00 2001 From: Ampflower Date: Fri, 6 Jan 2023 01:52:41 -0600 Subject: [PATCH 039/137] Initial use of Quilt Loader on ProxyFox --- .gitignore | 1 + build.gradle.kts | 3 + modules/patch/build.gradle.kts | 35 ++++ .../proxyfox/patch/BudgetLibClassifier.java | 113 +++++++++++++ .../main/java/dev/proxyfox/patch/FakeKnot.kt | 22 +++ .../main/java/dev/proxyfox/patch/Library.kt | 22 +++ .../dev/proxyfox/patch/ProxyFoxLibrary.kt | 56 +++++++ .../dev/proxyfox/patch/ProxyFoxProvider.kt | 149 ++++++++++++++++++ .../org.quiltmc.loader.impl.game.GameProvider | 1 + settings.gradle.kts | 3 +- 10 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 modules/patch/build.gradle.kts create mode 100644 modules/patch/src/main/java/dev/proxyfox/patch/BudgetLibClassifier.java create mode 100644 modules/patch/src/main/java/dev/proxyfox/patch/FakeKnot.kt create mode 100644 modules/patch/src/main/java/dev/proxyfox/patch/Library.kt create mode 100644 modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxLibrary.kt create mode 100644 modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxProvider.kt create mode 100644 modules/patch/src/main/resources/META-INF/services/org.quiltmc.loader.impl.game.GameProvider diff --git a/.gitignore b/.gitignore index 42d56c63..7555e099 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .idea/ build/ out/ +mods/ proxyfox.db.properties systems.json .env \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 9d194ea4..04d37206 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -48,6 +48,9 @@ allprojects { mavenCentral() maven("https://libraries.minecraft.net/") maven("https://oss.sonatype.org/content/repositories/snapshots") + maven("https://maven.quiltmc.org/repository/release/") + maven("https://maven.quiltmc.org/repository/snapshot/") + maven("https://maven.fabricmc.net/") maven("https://jitpack.io") } diff --git a/modules/patch/build.gradle.kts b/modules/patch/build.gradle.kts new file mode 100644 index 00000000..aa5f272d --- /dev/null +++ b/modules/patch/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 { + alias(libs.plugins.kotlin.jvm) +} + +dependencies { + // This provides a patched Quilt Loader that can run in this environment. + // Version is pinned, avoiding having the API suddenly break due to an + // update that happens to effect what we depend on. + implementation("org.quiltmc:quilt-loader:0.18.1-beta.27-20230105.235638-8") + + // Loader's dependencies; will not be required when QuiltMC/quilt-loader#190 + // is merged upstream. + implementation("net.fabricmc:sponge-mixin:0.11.4+mixin.0.8.5") + implementation("net.fabricmc:tiny-mappings-parser:0.3.0+build.17") + implementation("net.fabricmc:tiny-remapper:0.8.6") + implementation("net.fabricmc:access-widener:2.1.0") + implementation("org.quiltmc:quilt-json5:1.0.2") + implementation("org.quiltmc:quilt-config:1.0.0-beta.6") + implementation("org.ow2.asm:asm:9.4") + implementation("org.ow2.asm:asm-analysis:9.4") + implementation("org.ow2.asm:asm-commons:9.4") + implementation("org.ow2.asm:asm-tree:9.4") + implementation("org.ow2.asm:asm-util:9.4") + + // Bot module to avoid shadowing in Quilt Loader into ProxyFox. + implementation(project(":modules:bot")) +} \ No newline at end of file diff --git a/modules/patch/src/main/java/dev/proxyfox/patch/BudgetLibClassifier.java b/modules/patch/src/main/java/dev/proxyfox/patch/BudgetLibClassifier.java new file mode 100644 index 00000000..dba974c6 --- /dev/null +++ b/modules/patch/src/main/java/dev/proxyfox/patch/BudgetLibClassifier.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.patch;// Created 2023-03-01T01:42:32 + +import net.fabricmc.api.EnvType; +import org.quiltmc.loader.impl.util.log.Log; +import org.quiltmc.loader.impl.util.log.LogCategory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.zip.ZipFile; + +/** + * @author Ampflower + * @since 2.0.8 + **/ +public class BudgetLibClassifier & Library> { + private final EnumMap origins; + private final Set unmatched; + private final Class clazz; + + public BudgetLibClassifier(Class clazz) { + this.clazz = clazz; + this.origins = new EnumMap<>(clazz); + this.unmatched = new HashSet<>(); + } + + public void process(final Collection paths, final EnvType envType) throws IOException { + final List values; + if (envType != null) { + final var tmp = new ArrayList(); + for (var value : clazz.getEnumConstants()) { + if (value.envType() == null || value.envType() == envType) { + tmp.add(value); + } + } + + values = List.copyOf(tmp); + } else { + values = List.of(clazz.getEnumConstants()); + } + + for (final var path : paths) { + boolean found = false; + if (Files.isDirectory(path)) { + for (final var lib : values) { + for (final var lpath : lib.paths()) + if (Files.exists(path.resolve(lpath))) { + found = true; + final var old = origins.putIfAbsent(lib, path); + if (old != null) { + Log.warn(LogCategory.GENERAL, "Found %s for %s but %s is already present!", path, lib, old); + } + } + } + } else if (Files.isRegularFile(path)) { + try (final var zipfile = new ZipFile(path.toFile())) { + for (final var lib : values) { + for (final var lpath : lib.paths()) + if (zipfile.getEntry(lpath) != null) { + found = true; + final var old = origins.putIfAbsent(lib, path); + if (old != null) { + Log.warn(LogCategory.GENERAL, "Found %s for %s but %s is already present!", path, lib, old); + } + } + } + } + } + if (!found) { + unmatched.add(path); + } + } + } + + public Path getOrigin(T library) { + return origins.get(library); + } + + public Set getUnmatched() { + return unmatched; + } + + public String getClassName(T library) { + if (!origins.containsKey(library)) { + return null; + } + + final var path = library.path(); + + if (path.endsWith(".class")) { + return path.substring(0, path.length() - 6).replace('/', '.'); + } + + return null; + } + + public boolean is(Path path, T library) { + return origins.get(library).equals(path); + } + + public boolean has(T library) { + return origins.containsKey(library); + } +} diff --git a/modules/patch/src/main/java/dev/proxyfox/patch/FakeKnot.kt b/modules/patch/src/main/java/dev/proxyfox/patch/FakeKnot.kt new file mode 100644 index 00000000..e5fe23a9 --- /dev/null +++ b/modules/patch/src/main/java/dev/proxyfox/patch/FakeKnot.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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:JvmName("FakeKnot") + +package dev.proxyfox.patch + +import org.quiltmc.loader.impl.launch.knot.KnotServer + +// Created 2023-05-01T20:11:34 + +/** + * @author Ampflower + * @since 2.0.8 + **/ + +fun main(args: Array) = KnotServer.main(args) \ No newline at end of file diff --git a/modules/patch/src/main/java/dev/proxyfox/patch/Library.kt b/modules/patch/src/main/java/dev/proxyfox/patch/Library.kt new file mode 100644 index 00000000..7527651a --- /dev/null +++ b/modules/patch/src/main/java/dev/proxyfox/patch/Library.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.patch + +import net.fabricmc.api.EnvType + +// Created 2023-03-01T01:43:20 +/** + * @author Ampflower + * @since 2.0.8 + */ +interface Library { + fun envType(): EnvType? + fun paths(): List + fun path(): String? = paths()[0] +} \ No newline at end of file diff --git a/modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxLibrary.kt b/modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxLibrary.kt new file mode 100644 index 00000000..db2d6458 --- /dev/null +++ b/modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxLibrary.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.patch + +import net.fabricmc.api.EnvType + +// Created 2023-03-01T02:42:57 +/** + * @author Ampflower + * @since 2.0.8 + */ +enum class ProxyFoxLibrary : Library { + PROXY_FOX("dev/proxyfox/bot/BotMainKt.class"), + QUILT_LIBRARIES( + "org/quiltmc/loader/impl/launch/knot/Knot.class", + "net/fabricmc/loader/launch/server/FabricServerLauncher.class", + "org/quiltmc/json5/JsonReader.class", + "org/quiltmc/config/api/Config.class", + "org/objectweb/asm/util/ASMifier.class", + "org/objectweb/asm/tree/ClassNode.class", + "org/objectweb/asm/commons/JSRInlinerAdapter.class", + "org/objectweb/asm/tree/analysis/Frame.class", + "org/objectweb/asm/Opcodes.class", + "net/fabricmc/accesswidener/AccessWidener.class", + "org/spongepowered/asm/mixin/Debug.class", + "net/fabricmc/tinyremapper/AsmRemapper.class", + "net/fabricmc/mapping/tree/TinyTree.class" + ); + + private val envType: EnvType? + private val paths: List + + constructor(envType: EnvType, vararg paths: String) { + this.envType = envType + this.paths = java.util.List.of(*paths) + } + + constructor(vararg paths: String) { + envType = null + this.paths = java.util.List.of(*paths) + } + + override fun envType(): EnvType? { + return envType + } + + override fun paths(): List { + return paths + } +} \ No newline at end of file diff --git a/modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxProvider.kt b/modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxProvider.kt new file mode 100644 index 00000000..b5baa6b5 --- /dev/null +++ b/modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxProvider.kt @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.patch + +import org.quiltmc.loader.api.Version +import org.quiltmc.loader.impl.FormattedException +import org.quiltmc.loader.impl.QuiltLoaderImpl +import org.quiltmc.loader.impl.entrypoint.GameTransformer +import org.quiltmc.loader.impl.game.GameProvider +import org.quiltmc.loader.impl.game.GameProvider.BuiltinMod +import org.quiltmc.loader.impl.launch.common.QuiltLauncher +import org.quiltmc.loader.impl.metadata.qmj.V1ModMetadataBuilder +import org.quiltmc.loader.impl.util.Arguments +import org.quiltmc.loader.impl.util.ExceptionUtil +import org.quiltmc.loader.impl.util.SystemProperties +import org.quiltmc.loader.impl.util.log.ConsoleLogHandler +import org.quiltmc.loader.impl.util.log.Log +import java.io.File +import java.io.IOException +import java.lang.reflect.InvocationTargetException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.notExists +import kotlin.io.path.writeText + +// Created 2023-05-01T19:51:55 + +/** + * Partial copy of the Minecraft [GameProvider] adapted for use with ProxyFox. + * + * @author Ampflower + * @since 2.0.8 + **/ +class ProxyFoxProvider : GameProvider { + private val TRANSFORMER = GameTransformer() + + private var arguments: Arguments? = null + private lateinit var entrypoint: String + private lateinit var appJar: Path + private lateinit var miscJars: Set + + override fun getGameId() = "proxyfox" + + override fun getGameName() = "ProxyFox" + + override fun getRawGameVersion() = "0.0.0" + + override fun getNormalizedGameVersion() = "0.0.0" + + override fun getBuiltinMods() = setOf(BuiltinMod(listOf(appJar), V1ModMetadataBuilder().apply { + id = gameId + group = "builtin" + version = Version.of(normalizedGameVersion) + name = gameName + }.build())) + + override fun getEntrypoint() = entrypoint + + override fun getLaunchDirectory() = Path.of(".") + + override fun isObfuscated() = false + + override fun requiresUrlClassLoader() = false + + override fun isEnabled() = true + + override fun locateGame(launcher: QuiltLauncher, args: Array): Boolean { + this.arguments = Arguments().apply { parse(args) } + + try { + val jar = System.getProperty(SystemProperties.GAME_JAR_PATH) + val lookupPaths: List + + if (jar != null) { + val path = Paths.get(jar).toAbsolutePath().normalize() + + if (path.notExists()) { + throw RuntimeException("ProxyFox jar $path configured through ${SystemProperties.GAME_JAR_PATH} system property doesn't exist!") + } + + lookupPaths = path + launcher.classPath + } else { + lookupPaths = launcher.classPath + } + + val classifier = BudgetLibClassifier(ProxyFoxLibrary::class.java) + classifier.process(lookupPaths, launcher.environmentType) + + appJar = classifier.getOrigin(ProxyFoxLibrary.PROXY_FOX) ?: return false + + miscJars = classifier.unmatched + + entrypoint = classifier.getClassName(ProxyFoxLibrary.PROXY_FOX) + } catch (e: IOException) { + throw ExceptionUtil.wrap(e) + } + + QuiltLoaderImpl.INSTANCE.objectShare.put("fabric-loader:inputGameJar", appJar) + + return true + } + + override fun initialize(launcher: QuiltLauncher?) { + // Ideally, use SLF4J, but we currently don't test for that. + Log.init(ConsoleLogHandler(), true) + + val path = Files.createTempFile("Hacky-Classpath", ".txt").toAbsolutePath() + path.writeText("$appJar${File.pathSeparator}${miscJars.joinToString(File.pathSeparator)}") + + // This is honestly stupidly hacky. + // There's no other workaround available that I can see, so + // you will have to deal with this. + System.setProperty(SystemProperties.REMAP_CLASSPATH_FILE, path.toString()) + + TRANSFORMER.locateEntrypoints(launcher, appJar) + } + + override fun getEntrypointTransformer() = TRANSFORMER + + override fun unlockClassPath(launcher: QuiltLauncher) { + launcher.addToClassPath(appJar) + for (path in miscJars) { + launcher.addToClassPath(path) + } + } + + override fun launch(loader: ClassLoader) { + try { + val c = loader.loadClass(entrypoint) + val m = c.getMethod("main", Array::class.java) + m.invoke(null, arguments!!.toArray() as Any) + } catch (e: InvocationTargetException) { + throw FormattedException("ProxyFox has crashed", e.cause) + } catch (e: ReflectiveOperationException) { + throw FormattedException("Failed to start ProxyFox", e) + } + } + + override fun getArguments() = arguments + + override fun getLaunchArguments(sanitize: Boolean): Array = arguments?.toArray() ?: emptyArray() +} \ No newline at end of file diff --git a/modules/patch/src/main/resources/META-INF/services/org.quiltmc.loader.impl.game.GameProvider b/modules/patch/src/main/resources/META-INF/services/org.quiltmc.loader.impl.game.GameProvider new file mode 100644 index 00000000..cba7238e --- /dev/null +++ b/modules/patch/src/main/resources/META-INF/services/org.quiltmc.loader.impl.game.GameProvider @@ -0,0 +1 @@ +dev.proxyfox.patch.ProxyFoxProvider \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 0f092266..be8b3d1d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,4 +24,5 @@ include(":modules:common") include(":modules:database") include(":modules:conversion") include(":modules:api") -include(":modules:api:server") \ No newline at end of file +include(":modules:api:server") +include(":modules:patch") \ No newline at end of file From 759fb18f26a69e5da6afac80a899004a3e5965eb Mon Sep 17 00:00:00 2001 From: tibs Date: Sat, 7 Jan 2023 10:54:50 -0500 Subject: [PATCH 040/137] Most tests are fixed --- .../dev/proxyfox/database/DatabaseUtil.kt | 6 +-- .../dev/proxyfox/database/InMemoryDatabase.kt | 54 ++++++++++++++----- .../kotlin/dev/proxyfox/database/TimeUtil.kt | 2 +- .../database/etc/exporter/Exporter.kt | 2 +- .../database/etc/gson/InstantAdaptor.kt | 8 +-- .../database/etc/gson/LocalDateAdaptor.kt | 8 +-- .../etc/gson/OffsetDateTimeAdaptor.kt | 34 ------------ .../database/etc/gson/RecordAdapter.kt | 2 +- .../database/etc/importer/Importer.kt | 5 +- .../etc/importer/TupperBoxImporter.kt | 2 +- .../proxyfox/database/etc/types/PkTypes.kt | 1 - .../proxyfox/database/etc/types/TbTypes.kt | 4 ++ .../records/member/MemberProxyTagRecord.kt | 4 +- .../member/MemberServerSettingsRecord.kt | 2 +- .../system/SystemChannelSettingsRecord.kt | 2 +- .../system/SystemServerSettingsRecord.kt | 2 +- .../records/system/SystemSwitchRecord.kt | 3 +- .../dev/proxyfox/database/DatabaseTestUtil.kt | 7 ++- .../dev/proxyfox/database/TimeUtilTest.kt | 8 +-- .../dev/proxyfox/importer/ImporterTest.kt | 26 ++++----- 20 files changed, 87 insertions(+), 95 deletions(-) delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/OffsetDateTimeAdaptor.kt diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt index 6f040909..ef0e657b 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt @@ -13,13 +13,12 @@ import com.mongodb.reactivestreams.client.MongoCollection import dev.proxyfox.database.etc.gson.* import dev.proxyfox.database.etc.importer.ImporterException import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate import org.bson.types.ObjectId import org.litote.kmongo.coroutine.toList import org.litote.kmongo.reactivestreams.getCollection import org.litote.kmongo.util.KMongoUtil -import java.time.Instant -import java.time.LocalDate -import java.time.OffsetDateTime import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -30,7 +29,6 @@ typealias PkId = String const val pkIdBound = 11881376 val gson = GsonBuilder() - .registerTypeAdapter(OffsetDateTime::class.java, OffsetDateTimeAdaptor) .registerTypeAdapter(LocalDate::class.java, LocalDateAdaptor) .registerTypeAdapter(ObjectId::class.java, ObjectIdNullifier) .registerTypeAdapter(Instant::class.java, InstantAdaptor) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index 9bf12d74..fcace74e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -22,27 +22,42 @@ import kotlinx.datetime.Instant import kotlin.time.Duration class InMemoryDatabase : Database() { - private var users = HashMap() + private lateinit var users: HashMap - private var messages = ArrayList() + private lateinit var messages: ArrayList - private var servers = HashMap() - private var channels = HashMap>() + private lateinit var servers: HashMap + private lateinit var channels: HashMap> - private var systems = HashMap() - private var systemSwitches = HashMap>() - private var systemTokens = HashMap() + private lateinit var systems: HashMap + private lateinit var systemSwitches: HashMap> + private lateinit var systemTokens: HashMap - private var systemServers = HashMap>() - private var systemChannels = HashMap>() + private lateinit var systemServers: HashMap> + private lateinit var systemChannels: HashMap> - private var members = HashMap>() - private var memberProxies = HashMap>() + private lateinit var members: HashMap> + private lateinit var memberProxies: HashMap> - private var memberServers = HashMap>() + private lateinit var memberServers: HashMap> - override suspend fun setup() = this + override suspend fun setup(): InMemoryDatabase { + users = HashMap() + messages = ArrayList() + servers = HashMap() + channels = HashMap() + systems = HashMap() + systemSwitches = HashMap() + systemTokens = HashMap() + systemServers = HashMap() + systemChannels = HashMap() + members = HashMap() + memberProxies = HashMap() + memberServers = HashMap() + + return this + } override suspend fun ping(): Duration { return Duration.ZERO } @@ -287,7 +302,18 @@ class InMemoryDatabase : Database() { @Deprecated("Not for regular use.", level = DeprecationLevel.ERROR) override suspend fun drop() { - TODO("Not yet implemented") + users.clear() + messages.clear() + servers.clear() + channels.clear() + systems.clear() + systemSwitches.clear() + systemTokens.clear() + systemServers.clear() + systemChannels.clear() + members.clear() + memberProxies.clear() + memberServers.clear() } override suspend fun firstFreeSystemId(id: String?): String { diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt index ce1cb2ea..feaf1fb9 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt @@ -274,7 +274,7 @@ fun tryParseLocalDate(str: String?, preferMonthDay: Boolean = true): Pair 12) - LocalDate(year, month, day) to if (preferMonthDay) DDMMuuuu else MMDDuuuu + LocalDate(year, day, month) to if (preferMonthDay) DDMMuuuu else MMDDuuuu else LocalDate(year, month, day) to parser } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt index 2e7ecb43..1e72187a 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt @@ -10,11 +10,11 @@ package dev.proxyfox.database.etc.exporter import dev.proxyfox.database.Database import dev.proxyfox.database.database -import dev.proxyfox.database.gson import dev.proxyfox.database.etc.types.PkMember import dev.proxyfox.database.etc.types.PkProxy import dev.proxyfox.database.etc.types.PkSwitch import dev.proxyfox.database.etc.types.PkSystem +import dev.proxyfox.database.gson object Exporter { suspend inline fun export(userId: ULong) = export(database, userId) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/InstantAdaptor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/InstantAdaptor.kt index da199fbb..220cc296 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/InstantAdaptor.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/InstantAdaptor.kt @@ -10,8 +10,10 @@ package dev.proxyfox.database.etc.gson import com.google.gson.* import dev.proxyfox.database.sanitise +import kotlinx.datetime.Instant +import kotlinx.datetime.toJavaInstant +import kotlinx.datetime.toKotlinInstant import java.lang.reflect.Type -import java.time.Instant import java.time.format.DateTimeFormatter object InstantAdaptor : JsonSerializer, JsonDeserializer { @@ -19,7 +21,7 @@ object InstantAdaptor : JsonSerializer, JsonDeserializer { return if (src == null) JsonNull.INSTANCE else - JsonPrimitive(DateTimeFormatter.ISO_INSTANT.format(src)) + JsonPrimitive(DateTimeFormatter.ISO_INSTANT.format(src.toJavaInstant())) } override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Instant? { @@ -27,7 +29,7 @@ object InstantAdaptor : JsonSerializer, JsonDeserializer { if (isNullOrBlank()) { null } else { - DateTimeFormatter.ISO_INSTANT.parse(this, Instant::from) + DateTimeFormatter.ISO_INSTANT.parse(this, java.time.Instant::from).toKotlinInstant() } } } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/LocalDateAdaptor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/LocalDateAdaptor.kt index 0afd6f02..6932dc7a 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/LocalDateAdaptor.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/LocalDateAdaptor.kt @@ -10,8 +10,10 @@ package dev.proxyfox.database.etc.gson import com.google.gson.* import dev.proxyfox.database.sanitise +import kotlinx.datetime.LocalDate +import kotlinx.datetime.toJavaLocalDate +import kotlinx.datetime.toKotlinLocalDate import java.lang.reflect.Type -import java.time.LocalDate import java.time.format.DateTimeFormatter import java.time.temporal.TemporalQueries @@ -26,7 +28,7 @@ object LocalDateAdaptor : JsonSerializer, JsonDeserializer return if (src == null) { JsonNull.INSTANCE } else { - JsonPrimitive(DateTimeFormatter.ISO_DATE.format(src)) + JsonPrimitive(DateTimeFormatter.ISO_DATE.format(src.toJavaLocalDate())) } } @@ -35,7 +37,7 @@ object LocalDateAdaptor : JsonSerializer, JsonDeserializer if (isNullOrBlank()) { null } else { - DateTimeFormatter.ISO_DATE.parse(this, TemporalQueries.localDate()) + DateTimeFormatter.ISO_DATE.parse(this, TemporalQueries.localDate()).toKotlinLocalDate() } } } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/OffsetDateTimeAdaptor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/OffsetDateTimeAdaptor.kt deleted file mode 100644 index c71029b6..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/OffsetDateTimeAdaptor.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import com.google.gson.* -import dev.proxyfox.database.sanitise -import java.lang.reflect.Type -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter - -object OffsetDateTimeAdaptor : JsonSerializer, JsonDeserializer { - override fun serialize(src: OffsetDateTime?, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - return if (src == null) - JsonNull.INSTANCE - else - JsonPrimitive(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(src)) - } - - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): OffsetDateTime? { - return json.asString.sanitise().run { - if (isNullOrBlank()) { - null - } else { - DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(this, OffsetDateTime::from) - } - } - } -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapter.kt index 5a29cc41..bc23fdca 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapter.kt @@ -17,8 +17,8 @@ import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter -import dev.proxyfox.database.mapArray import dev.proxyfox.database.etc.importer.ImporterException +import dev.proxyfox.database.mapArray import org.slf4j.LoggerFactory import java.lang.reflect.RecordComponent import java.lang.reflect.Type diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt index 7e8cede2..e02d8964 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt @@ -41,8 +41,6 @@ suspend fun import(string: String, user: Entity?) = import(database, string, use * */ suspend fun import(reader: Reader, user: Entity?) = import(database, reader, user) -private val json1 = Json { ignoreUnknownKeys = true } - /** * Imports a system file from a [String]. Supports both PluralKit and TupperBox formats. * @@ -53,6 +51,7 @@ private val json1 = Json { ignoreUnknownKeys = true } * */ suspend fun import(database: Database, string: String, user: Entity?): Importer { try { + println(string) return import(database, Json.parseToJsonElement(string), user) } catch (reason: Throwable) { throw ImporterException("Not a JSON file $reason", reason) @@ -80,7 +79,7 @@ suspend fun import(database: Database, reader: Reader, user: Entity?): Importer * @author Oliver * */ suspend fun import(database: Database, element: JsonElement, user: Entity?): Importer { - val map = element.jsonObject + val map = element.jsonObject if (map.isEmpty()) throw ImporterException("No data to import.") if (map.contains("type") && map.contains("uri") && map.size == 2) { throw ImporterException("Your system file is invalid; try fetching directly from ${map["uri"]}?") diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt index 825009aa..882d3bca 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt @@ -9,11 +9,11 @@ package dev.proxyfox.database.etc.importer import dev.proxyfox.database.Database +import dev.proxyfox.database.etc.types.TbSystem import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.validate -import dev.proxyfox.database.etc.types.TbSystem import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.decodeFromJsonElement diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt index c6a85ff2..aaed3f34 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt @@ -10,7 +10,6 @@ package dev.proxyfox.database.etc.types import dev.proxyfox.common.fromColorForExport import dev.proxyfox.database.* -import dev.proxyfox.database.pkValid import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/TbTypes.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/TbTypes.kt index 99d9fe0e..4d4d8799 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/TbTypes.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/TbTypes.kt @@ -11,6 +11,7 @@ package dev.proxyfox.database.etc.types import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.sanitise import dev.proxyfox.database.tryParseLocalDate +import kotlinx.serialization.Serializable // Created 2022-29-09T22:20:20 @@ -18,11 +19,13 @@ import dev.proxyfox.database.tryParseLocalDate * @author Ampflower * @since ${version} **/ +@Serializable class TbSystem { var tuppers: List? = null var groups: List? = null } +@Serializable class TbMember { var id: Int = 0 var name: String = "" @@ -46,6 +49,7 @@ class TbMember { } } +@Serializable class TbGroup { var id: Int = 0 var name: String = "" diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt index e09da12e..c88eb6cd 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt @@ -8,9 +8,7 @@ package dev.proxyfox.database.records.member -import com.google.gson.annotations.Expose -import com.google.gson.annotations.SerializedName -import dev.proxyfox.database.* +import dev.proxyfox.database.PkId import dev.proxyfox.database.records.MongoRecord import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt index 19ea39a4..c9d3a4a7 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt @@ -8,7 +8,7 @@ package dev.proxyfox.database.records.member -import dev.proxyfox.database.* +import dev.proxyfox.database.PkId import dev.proxyfox.database.records.MongoRecord import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt index 91b673c8..b77b0c8f 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt @@ -8,7 +8,7 @@ package dev.proxyfox.database.records.system -import dev.proxyfox.database.* +import dev.proxyfox.database.PkId import dev.proxyfox.database.records.MongoRecord import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt index 745b48dd..063c0a96 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt @@ -8,7 +8,7 @@ package dev.proxyfox.database.records.system -import dev.proxyfox.database.* +import dev.proxyfox.database.PkId import dev.proxyfox.database.records.MongoRecord import dev.proxyfox.database.records.misc.AutoProxyMode import kotlinx.serialization.Contextual diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt index b8f477c4..079c308a 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt @@ -8,14 +8,13 @@ package dev.proxyfox.database.records.system -import dev.proxyfox.database.* +import dev.proxyfox.database.PkId import dev.proxyfox.database.records.MongoRecord import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId -import java.time.Duration // Created 2022-09-04T15:18:49 diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt index 5ffd6764..86756ce2 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt @@ -12,9 +12,8 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.entity.Entity import io.mockk.every import io.mockk.mockk -import kotlinx.datetime.toKotlinInstant +import kotlinx.datetime.Instant import org.testng.annotations.DataProvider -import java.time.Instant import java.util.* import java.util.concurrent.TimeUnit import java.util.stream.IntStream @@ -38,8 +37,8 @@ object DatabaseTestUtil { const val offsetDateTimeEpochString = "1970-01-01T00:00:00Z" - val instantEpoch = Instant.EPOCH!!.toKotlinInstant() - val instantLastMicroOfEpochDay = Instant.ofEpochSecond(TimeUnit.DAYS.toSeconds(1) - 1L, 999_999_000L)!! + val instantEpoch = Instant.fromEpochSeconds(0L) + val instantLastMicroOfEpochDay = Instant.fromEpochSeconds(TimeUnit.DAYS.toSeconds(1) - 1L) inline fun entity(ret: ULong): T { return mockk { diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/TimeUtilTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/TimeUtilTest.kt index 18a50580..8f43cea4 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/TimeUtilTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/TimeUtilTest.kt @@ -8,10 +8,10 @@ package dev.proxyfox.database +import kotlinx.datetime.LocalDate import org.testng.Assert.assertEquals import org.testng.annotations.DataProvider import org.testng.annotations.Test -import java.time.LocalDate // Created 2022-02-10T23:23:07 @@ -37,9 +37,9 @@ class TimeUtilTest { } companion object { - val dec25 = LocalDate.of(1, 12, 25)!! - val jan01 = LocalDate.of(65535, 1, 1)!! - val jan01neg = LocalDate.of(-65536, 1, 1)!! + val dec25 = LocalDate(1, 12, 25) + val jan01 = LocalDate(65535, 1, 1) + val jan01neg = LocalDate(-65536, 1, 1) @JvmStatic @DataProvider diff --git a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt index b8af1fc7..6c7a0f75 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt @@ -20,13 +20,13 @@ import dev.proxyfox.database.MongoDatabase import dev.proxyfox.database.etc.importer.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import kotlinx.datetime.LocalDate import org.slf4j.LoggerFactory import org.testng.Assert.* import org.testng.annotations.* import java.io.Reader import java.net.URL import java.nio.file.Files -import java.time.LocalDate // Created 2022-29-09T22:17:51 @@ -99,12 +99,12 @@ constructor(private val name: String, databaseFactory: () -> Database) { import(database, it, user) } - assertEquals(database.fetchMemberFromUserAndName(user, "Azalea")!!.birthday, LocalDate.of(1, 12, 25)) - assertEquals(database.fetchMemberFromUserAndName(user, "Berry")!!.birthday, LocalDate.of(1, 1, 2)) - assertEquals(database.fetchMemberFromUserAndName(user, "Cherry")!!.birthday, LocalDate.of(1, 4, 10)) - assertEquals(database.fetchMemberFromUserAndName(user, "Hibiscus")!!.birthday, LocalDate.of(1990, 7, 4)) - assertEquals(database.fetchMemberFromUserAndName(user, "Zinnia")!!.birthday, LocalDate.of(2000, 2, 4)) - assertEquals(database.fetchMemberFromUserAndName(user, "Ivy")!!.birthday, LocalDate.of(1995, 8, 24)) + assertEquals(database.fetchMemberFromUserAndName(user, "Azalea")!!.birthday, LocalDate(1, 12, 25)) + assertEquals(database.fetchMemberFromUserAndName(user, "Berry")!!.birthday, LocalDate(1, 1, 2)) + assertEquals(database.fetchMemberFromUserAndName(user, "Cherry")!!.birthday, LocalDate(1, 4, 10)) + assertEquals(database.fetchMemberFromUserAndName(user, "Hibiscus")!!.birthday, LocalDate(1990, 7, 4)) + assertEquals(database.fetchMemberFromUserAndName(user, "Zinnia")!!.birthday, LocalDate(2000, 2, 4)) + assertEquals(database.fetchMemberFromUserAndName(user, "Ivy")!!.birthday, LocalDate(1995, 8, 24)) } @Test @@ -114,12 +114,12 @@ constructor(private val name: String, databaseFactory: () -> Database) { import(database, it, user) } - assertEquals(database.fetchMemberFromUserAndName(user, "Azalea")!!.birthday, LocalDate.of(1, 12, 25)) - assertEquals(database.fetchMemberFromUserAndName(user, "Berry")!!.birthday, LocalDate.of(1, 2, 1)) - assertEquals(database.fetchMemberFromUserAndName(user, "Cherry")!!.birthday, LocalDate.of(1, 10, 4)) - assertEquals(database.fetchMemberFromUserAndName(user, "Hibiscus")!!.birthday, LocalDate.of(1990, 4, 7)) - assertEquals(database.fetchMemberFromUserAndName(user, "Zinnia")!!.birthday, LocalDate.of(2000, 2, 4)) - assertEquals(database.fetchMemberFromUserAndName(user, "Ivy")!!.birthday, LocalDate.of(1995, 8, 24)) + assertEquals(database.fetchMemberFromUserAndName(user, "Azalea")!!.birthday, LocalDate(1, 12, 25)) + assertEquals(database.fetchMemberFromUserAndName(user, "Berry")!!.birthday, LocalDate(1, 2, 1)) + assertEquals(database.fetchMemberFromUserAndName(user, "Cherry")!!.birthday, LocalDate(1, 10, 4)) + assertEquals(database.fetchMemberFromUserAndName(user, "Hibiscus")!!.birthday, LocalDate(1990, 4, 7)) + assertEquals(database.fetchMemberFromUserAndName(user, "Zinnia")!!.birthday, LocalDate(2000, 2, 4)) + assertEquals(database.fetchMemberFromUserAndName(user, "Ivy")!!.birthday, LocalDate(1995, 8, 24)) } @Test From 331d44cdf9fab04f457e6cb015511d60df1d6913 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sat, 7 Jan 2023 11:03:17 -0600 Subject: [PATCH 041/137] Fix case sensitivity in PluralKitImporter The test expected casing to be sensitive, but the logic expected the casing to be insensitive, which only showed through because InMemoryDatabase properly implemented case (in)sensitivity. --- .../dev/proxyfox/database/etc/importer/PluralKitImporter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt index 46a68bd5..dcb95720 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt @@ -130,7 +130,7 @@ open class PluralKitImporter protected constructor( val member = run { if (!fresh) { val record = pkMember.id?.let { database.fetchMemberFromSystem(system.id, it) } - ?: database.fetchMemberFromSystemAndName(system.id, memberName, caseSensitive = false) + ?: database.fetchMemberFromSystemAndName(system.id, memberName, caseSensitive = true) if (record != null && seenMemberIds.add(record.id)) { assert(record.name == memberName) { "$record did not match $pkMember" } freshMember = false From 25b2682e81e5f51dfef99436afbaef3eb4e6dc1b Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sat, 7 Jan 2023 11:04:01 -0600 Subject: [PATCH 042/137] Fix birthday day/month flipping logic in PluralKitImporter --- .../dev/proxyfox/database/etc/importer/PluralKitImporter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt index dcb95720..1e0497c3 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt @@ -310,7 +310,7 @@ open class PluralKitImporter protected constructor( if (otherCount > expectedCount) { for (pkMember in ambiguousBirthdays) { // Not null assertion as it was already parsed successfully once. - birthdays.computeIfPresent(pkMember) { _, (date, _) -> LocalDate(date.year, date.monthNumber, date.dayOfMonth) to otherFormat } + birthdays.computeIfPresent(pkMember) { _, (date, _) -> LocalDate(date.year, date.dayOfMonth, date.monthNumber) to otherFormat } } } } From f60f555f43a5fd713778439da9a24e8d45de37cf Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sat, 7 Jan 2023 11:05:22 -0600 Subject: [PATCH 043/137] Use ConcurrentHashMap as the backing maps for InMemoryDatabase --- .../dev/proxyfox/database/InMemoryDatabase.kt | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index fcace74e..05ac68d1 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -19,42 +19,43 @@ import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord import dev.proxyfox.database.records.system.SystemSwitchRecord import kotlinx.datetime.Instant +import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration class InMemoryDatabase : Database() { - private lateinit var users: HashMap + private lateinit var users: MutableMap private lateinit var messages: ArrayList - private lateinit var servers: HashMap - private lateinit var channels: HashMap> + private lateinit var servers: MutableMap + private lateinit var channels: MutableMap> - private lateinit var systems: HashMap - private lateinit var systemSwitches: HashMap> - private lateinit var systemTokens: HashMap + private lateinit var systems: MutableMap + private lateinit var systemSwitches: MutableMap> + private lateinit var systemTokens: MutableMap - private lateinit var systemServers: HashMap> - private lateinit var systemChannels: HashMap> + private lateinit var systemServers: MutableMap> + private lateinit var systemChannels: MutableMap> - private lateinit var members: HashMap> - private lateinit var memberProxies: HashMap> + private lateinit var members: MutableMap> + private lateinit var memberProxies: MutableMap> - private lateinit var memberServers: HashMap> + private lateinit var memberServers: MutableMap> override suspend fun setup(): InMemoryDatabase { - users = HashMap() + users = ConcurrentHashMap() messages = ArrayList() - servers = HashMap() - channels = HashMap() - systems = HashMap() - systemSwitches = HashMap() - systemTokens = HashMap() - systemServers = HashMap() - systemChannels = HashMap() - members = HashMap() - memberProxies = HashMap() - memberServers = HashMap() + servers = ConcurrentHashMap() + channels = ConcurrentHashMap() + systems = ConcurrentHashMap() + systemSwitches = ConcurrentHashMap() + systemTokens = ConcurrentHashMap() + systemServers = ConcurrentHashMap() + systemChannels = ConcurrentHashMap() + members = ConcurrentHashMap() + memberProxies = ConcurrentHashMap() + memberServers = ConcurrentHashMap() return this } From 5a5fba766372358dd4b1e95230faf1ce767b2d9d Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sat, 7 Jan 2023 11:06:58 -0600 Subject: [PATCH 044/137] Fix logic errors & mem leaks in InMemoryDatabase Now correctly drops systems and will try case-sensitive search before case-insensitive search, as it'd probably make more sense to exactly match before falling back to insensitive searches. --- .../dev/proxyfox/database/InMemoryDatabase.kt | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index 05ac68d1..f6708e7e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -152,30 +152,40 @@ class InMemoryDatabase : Database() { } override suspend fun getOrCreateSystem(userId: ULong, id: String?): SystemRecord { - if (users.containsKey(userId) && users[userId]!!.systemId != null && systems.containsKey(users[userId]!!.systemId)) { - return systems[users[userId]!!.systemId]!! - } + return fetchSystemFromUser(userId) ?: run { + val system = SystemRecord() - if (id != null && systems.containsKey(id)) { - return systems[id]!! - } + system.id = if (!id.isValidPkString() || systems.containsKey(id)) systems.keys.firstFree() else id + system.users.add(userId) - val system = SystemRecord() - systems[system.id] = system - systemSwitches[system.id] = ArrayList() - systemServers[system.id] = HashMap() - systemChannels[system.id] = HashMap() - members[system.id] = HashMap() + systems[system.id] = system + systemSwitches[system.id] = ArrayList() + systemServers[system.id] = HashMap() + systemChannels[system.id] = HashMap() + members[system.id] = HashMap() - val user = getOrCreateUser(userId) - user.systemId = system.id + val user = getOrCreateUser(userId) + user.systemId = system.id - return system + system + } } override suspend fun dropSystem(userId: ULong): Boolean { - val system = fetchSystemFromUser(userId) ?: return false + val user = users[userId] ?: return false + + val system = systems[user.systemId] ?: return false + assert(user.systemId == system.id) { "User $userId's system ID ${user.systemId} does not match ${system.id}" } systems.remove(system.id) + systemSwitches.remove(system.id) + systemServers.remove(system.id) + systemChannels.remove(system.id) + members.remove(system.id) + + for (systemUserId in system.users) { + users.remove(systemUserId) + } + return true } @@ -257,7 +267,7 @@ class InMemoryDatabase : Database() { return true } - override suspend fun createSwitch(systemId: String, memberId: List, timestamp: Instant?): SystemSwitchRecord? { + override suspend fun createSwitch(systemId: String, memberId: List, timestamp: Instant?): SystemSwitchRecord { val switches = fetchSwitchesFromSystem(systemId) if (switches == null) { systemSwitches[systemId] = ArrayList() @@ -267,6 +277,7 @@ class InMemoryDatabase : Database() { systemSwitches[systemId]!!.add(switch) return switch } + override suspend fun dropSwitch(switch: SystemSwitchRecord) { systemSwitches[switch.systemId]?.remove(switch) } @@ -294,7 +305,14 @@ class InMemoryDatabase : Database() { override suspend fun fetchTotalMembersFromSystem(systemId: String): Int? = members[systemId]?.size override suspend fun fetchMemberFromSystemAndName(systemId: String, memberName: String, caseSensitive: Boolean): MemberRecord? { - return members[systemId]?.values?.find { if (caseSensitive) it.name == memberName else it.name.lowercase() == memberName.lowercase() } + return members[systemId]?.values?.run { + find { it.name == memberName } + ?: if (caseSensitive) { + null + } else { + find { it.name.lowercase() == memberName.lowercase() } + } + } } override suspend fun export(other: Database) { From 7d34dff5014a462b7ee7936a2650c6349eed5fa1 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sat, 7 Jan 2023 11:08:33 -0600 Subject: [PATCH 045/137] Harden ImporterTest to logic errors and its own assertions Now more thoroughly tests system data returned from the importer to check that the ID and user information is correct, being the only fully functioning system creation test. --- .../dev/proxyfox/importer/ImporterTest.kt | 77 +++++++++++++------ 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt index 6c7a0f75..6fcc6b22 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt @@ -18,6 +18,7 @@ import dev.proxyfox.database.DatabaseTestUtil.seeded import dev.proxyfox.database.InMemoryDatabase import dev.proxyfox.database.MongoDatabase import dev.proxyfox.database.etc.importer.* +import dev.proxyfox.database.isValidPkString import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlinx.datetime.LocalDate @@ -55,41 +56,67 @@ constructor(private val name: String, databaseFactory: () -> Database) { @Test(dataProvider = "passImporters") fun `Importer - expect pass`(url: URL) = runTest { val user = entity(prng.nextLong().toULong()) - assertNull(database.fetchSystemFromUser(user), "$user already has system bound?") + try { + database.fetchSystemFromUser(user).let { + assertNull(it, "${user.id} already has system bound at `${it?.id}`?") + } - val importer1 = import(database, url.readText(), user) - assertEquals(importer1.updatedMembers, 0, "Somehow updated existing member") + val importer1 = import(database, url.readText(), user) + assertEquals(importer1.updatedMembers, 0, "Somehow updated existing member") - assertNotNull(database.fetchMemberFromUserAndName(user, "Azalea"), "No such Azalea for $user") + val system = importer1.system - if (!url.file.contains("Tupperbox")) { - assertEquals("| flwr", database.fetchSystemFromUser(user)?.tag, "Tag didn't get imported correctly.") - } + assertTrue(system.users.contains(user.id.value), "System not owned by ${user.id} despite being allocated") + assertTrue(system.id.isValidPkString(), "`${system.id}` is not a valid PK ID") - extraResource("PluralKit-v1-Case-Sensitivity-Test.json") { - val pkImporter = import(database, it, user) - assertEquals(pkImporter.createdMembers, 1, "`azalea` was not counted.") - } + database.fetchMemberFromUserAndName(user, "Azalea").let { + assertNotNull(it, "No such Azalea for $user") + assertTrue(it!!.id.isValidPkString(), "`${it.id}` is not a valid PK ID") + } + + if (!url.file.contains("Tupperbox")) { + assertEquals("| flwr", database.fetchSystemFromUser(user)?.tag, "Tag didn't get imported correctly.") + } + + extraResource("PluralKit-v1-Case-Sensitivity-Test.json") { + val pkImporter = import(database, it, user) + assertEquals(pkImporter.createdMembers, 1, "`azalea` was not counted.") + } - assertEquals(database.fetchMemberFromUserAndName(user, "azalea")?.name, "azalea") + assertEquals(database.fetchMemberFromUserAndName(user, "azalea")?.name, "azalea") - database.dropSystem(user) - database.getOrCreateSystem(user) + database.dropSystem(user) + database.fetchSystemFromUser(user).let { + assertNull(it, "${user.id} still has system bound at `${it?.id}` after explicit drop") + } - val importer2 = import(database, url.readText(), user) - assertEquals(importer2.updatedMembers, 0, "Somehow updated existing member") - assertEquals(importer2.createdMembers, importer1.createdMembers, "Unexpected behaviour change") + database.getOrCreateSystem(user) - database.dropSystem(user) - val id = database.getOrCreateSystem(user).id - database.getOrCreateMember(id, "Azalea") + val importer2 = import(database, url.readText(), user) + assertEquals(importer2.updatedMembers, 0, "Somehow updated existing member") + assertEquals(importer2.createdMembers, importer1.createdMembers, "Unexpected behaviour change") - val importer3 = import(database, url.readText(), user) - assertEquals(importer3.updatedMembers, 1, "Updated more than Azalea") - assertEquals(importer3.createdMembers, importer1.createdMembers - 1, "Unexpected behaviour change") + database.dropSystem(user) + database.fetchSystemFromUser(user).let { + assertNull(it, "${user.id} still has system bound at `${it?.id}` after explicit drop") + } - // Somehow the ID manages to get reused in some implementations - database.dropSystem(user) + val id = database.getOrCreateSystem(user).id + assertTrue(id.isValidPkString(), "`$id` is not a valid PK ID") + + database.getOrCreateMember(id, "Azalea") + + val importer3 = import(database, url.readText(), user) + assertEquals(importer3.updatedMembers, 1, "Updated more than Azalea") + assertEquals(importer3.createdMembers, importer1.createdMembers - 1, "Unexpected behaviour change") + + } finally { + // Somehow the ID manages to get reused in some implementations + database.dropSystem(user) + database.fetchSystemFromUser(user).let { + assertNull(it, "${user.id} still has system bound at `${it?.id}` after explicit drop") + } + } } @Test From 4432d1a7206234e56e92f6839b681a9ca1c6e726 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sat, 7 Jan 2023 23:59:10 -0600 Subject: [PATCH 046/137] Add PolyIgnorePrimitive Note: It still creates the object.. because it's too late to intercept the object creation to ignore it. Complain to Jetbrains. --- .../ktx/serializaton/PolyIgnorePrimitive.kt | 31 +++++++++++++++++++ .../proxyfox/database/etc/types/PkTypes.kt | 10 ++++++ 2 files changed, 41 insertions(+) create mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/PolyIgnorePrimitive.kt diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/PolyIgnorePrimitive.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/PolyIgnorePrimitive.kt new file mode 100644 index 00000000..e6dbf875 --- /dev/null +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/PolyIgnorePrimitive.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.database.etc.ktx.serializaton + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonTransformingSerializer + +// Created 2023-07-01T23:33:13 + +/** + * @author Ampflower + * @since ${version} + **/ +open class PolyIgnorePrimitive(tSerializer: KSerializer) : JsonTransformingSerializer(tSerializer) { + override fun transformDeserialize(element: JsonElement): JsonElement { + if (element !is JsonObject) { + // Null isn't valid here. Deal with it and complain to Jetbrains. + return JsonObject(mapOf()) + } + + return element + } +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt index aaed3f34..6aba7055 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt @@ -10,6 +10,7 @@ package dev.proxyfox.database.etc.types import dev.proxyfox.common.fromColorForExport import dev.proxyfox.database.* +import dev.proxyfox.database.etc.ktx.serializaton.PolyIgnorePrimitive import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode @@ -37,6 +38,7 @@ data class PkSystem( val created: String? = null, val webhook_url: String? = null, + @Serializable(PkSystemPrivacyIgnorePrimitive::class) val privacy: PkSystemPrivacy? = null, val config: PkConfig? = null, @@ -153,6 +155,7 @@ data class PkMember( val proxy_tags: Set? = emptySet(), // Some data structures from here will need to be flattened in. + @Serializable(PkMemberPrivacyIgnorePrimitive::class) val privacy: PkMemberPrivacy? = null, // ProxyFox-specific extensions. @@ -229,6 +232,7 @@ data class PkGroup( val created: Instant? = null, val members: List? = null, + @Serializable(PkGroupPrivacyIgnorePrimitive::class) val privacy: PkGroupPrivacy? = null, // The following are ignored. We don't use these. @@ -285,6 +289,8 @@ data class PkSystemPrivacy( ) } +object PkSystemPrivacyIgnorePrimitive : PolyIgnorePrimitive(PkSystemPrivacy.serializer()) + @JvmRecord @Serializable data class PkMemberPrivacy( @@ -307,6 +313,8 @@ data class PkMemberPrivacy( ) } +object PkMemberPrivacyIgnorePrimitive : PolyIgnorePrimitive(PkMemberPrivacy.serializer()) + @JvmRecord @Serializable data class PkGroupPrivacy( @@ -327,6 +335,8 @@ data class PkGroupPrivacy( ) } +object PkGroupPrivacyIgnorePrimitive : PolyIgnorePrimitive(PkGroupPrivacy.serializer()) + @JvmRecord @Serializable data class PkConfig( From 70cae0a27c2e40450c562feae978ea5abba12497 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 00:23:26 -0600 Subject: [PATCH 047/137] Fix Instant parsing All current tests should now be functional. --- .../main/kotlin/dev/proxyfox/database/TimeUtil.kt | 12 ++++++++++++ .../database/etc/importer/PluralKitImporter.kt | 2 +- .../dev/proxyfox/database/etc/types/PkTypes.kt | 3 +-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt index feaf1fb9..dd06fa33 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt @@ -8,6 +8,7 @@ package dev.proxyfox.database +import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import java.text.ParsePosition import java.time.format.DateTimeFormatter @@ -279,6 +280,17 @@ fun tryParseLocalDate(str: String?, preferMonthDay: Boolean = true): Pair>(Instant::compareTo) for (switch in switches) { - val timestamp = switch.timestamp?.let { Instant.parse(it) } ?: continue + val timestamp = switch.timestamp.tryParseInstant() ?: continue switchMap.computeIfAbsent(timestamp) { LinkedHashSet() }.addAll(switch.members?.filterNotNull() ?: emptyList()) } var lastMember: LinkedHashSet? = null diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt index 6aba7055..df9664b5 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt @@ -17,7 +17,6 @@ import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.TrustLevel import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemSwitchRecord -import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -229,7 +228,7 @@ data class PkGroup( val icon: String? = null, val banner: String? = null, val color: String? = null, - val created: Instant? = null, + val created: String? = null, val members: List? = null, @Serializable(PkGroupPrivacyIgnorePrimitive::class) From 8cef09dda97b30cf514f7f657ed91c0e80e0453b Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 01:58:23 -0600 Subject: [PATCH 048/137] Fix broken merge from dev/2.1 --- .../proxyfox/bot/command/MemberCommands.kt | 41 +++++++++--------- .../proxyfox/bot/command/SwitchCommands.kt | 2 +- .../proxyfox/bot/command/SystemCommands.kt | 42 ++++++++++++------- .../dev/proxyfox/database/InMemoryDatabase.kt | 11 +++++ 4 files changed, 57 insertions(+), 39 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 3451bbc4..f25c5f65 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -22,9 +22,6 @@ import dev.proxyfox.bot.kordColor import dev.proxyfox.bot.member import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.TimedYesNoPrompt -import dev.proxyfox.command.NodeActionParam -import dev.proxyfox.command.NodeHolder -import dev.proxyfox.command.ParamGetter import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.* @@ -35,8 +32,8 @@ import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.member.MemberServerSettingsRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.tryParseLocalDate +import kotlinx.datetime.LocalDate import kotlinx.datetime.toJavaLocalDate -import java.time.LocalDate /** * Commands for accessing and changing system settings @@ -131,7 +128,7 @@ object MemberCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -189,7 +186,7 @@ object MemberCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -384,11 +381,11 @@ object MemberCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) servername(this, serverMember!!, null, false, false) } unixLiteral("clear", "remove") { @@ -401,11 +398,11 @@ object MemberCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) servername(this, serverMember!!, null, false, true) } } @@ -419,11 +416,11 @@ object MemberCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) servername(this, serverMember!!, null, true, false) } } @@ -437,11 +434,11 @@ object MemberCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) servername(this, serverMember!!, getName(), false, false) } } @@ -533,11 +530,11 @@ object MemberCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) serverAvatar(this, serverMember!!, null, false) } unixLiteral("clear", "remove") { @@ -550,11 +547,11 @@ object MemberCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) serverAvatar(this, serverMember!!, null, true) } } @@ -568,11 +565,11 @@ object MemberCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) serverAvatar(this, serverMember!!, getAvatar().url, false) } } @@ -586,11 +583,11 @@ object MemberCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system!!.id, member!!.id) + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) serverAvatar(this, serverMember!!, getAvatar(), false) } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index b1d9a4a4..f961b724 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -205,7 +205,7 @@ object SwitchCommands { runner = ctx.getUser()!!.id, channel = ctx.getChannel(), message = """ - Are you sure you want to delete the latest switch (${switch.membersAsString()}, )? ${if (oldSwitch != null) "\nThe previous switch would be at " else ""} + Are you sure you want to delete the latest switch (${switch.membersAsString()}, )? ${if (oldSwitch != null) "\nThe previous switch would be at " else ""} The data will be lost forever (A long time!) """.trimIndent(), yes = Button("Delete switch", Button.wastebasket, ButtonStyle.Danger) { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 3ba0ba9a..f701b957 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -13,7 +13,6 @@ import dev.kord.core.Kord import dev.kord.rest.NamedFile import dev.kord.rest.builder.interaction.SubCommandBuilder import dev.kord.rest.builder.interaction.subCommand -import dev.proxyfox.bot.command.MemberCommands.runs import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.runs @@ -32,8 +31,9 @@ import dev.proxyfox.common.toColor import dev.proxyfox.database.database import dev.proxyfox.database.etc.exporter.Exporter import dev.proxyfox.database.generateToken -import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord +import io.ktor.client.request.forms.* +import io.ktor.utils.io.jvm.javaio.* /** * Commands for accessing and changing system settings @@ -387,9 +387,20 @@ object SystemCommands { // unixLiteral(arrayOf("verbose", "v"), ::listVerbose) // }) - registerCommand(literal(arrayOf("token", "t"), ::token) { - literal(arrayOf("refresh", "r"), ::tokenRefresh) - }) + Commands.parser.literal("token", "t") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + token(this, system!!) + } + literal("refresh", "r") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + tokenRefresh(this, system!!) + } + } + } } private suspend fun access(ctx: DiscordContext, system: SystemRecord): Boolean { @@ -626,21 +637,20 @@ object SystemCommands { ctx.respondSuccess("System tag updated to $tag!") return true } - private suspend fun token(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" - ctx.respond("`${database.getOrCreateTokenFromSystem(system.id).token}`", true) - return "Token sent in DMs" + + private suspend fun token(ctx: DiscordContext, system: SystemRecord): Boolean { + ctx.respondSuccess("`${database.getOrCreateTokenFromSystem(system.id).token}`", true) + ctx.respondSuccess("Token sent in DMs") + return true } - private suspend fun tokenRefresh(ctx: MessageHolder): String { - val system = database.fetchSystemFromUser(ctx.message.author) - ?: return "System does not exist. Create one using `pf>system new`" + private suspend fun tokenRefresh(ctx: DiscordContext, system: SystemRecord): Boolean { val token = database.getOrCreateTokenFromSystem(system.id) token.token = generateToken() database.updateToken(token) - ctx.respond("`${token.token}`", true) - return "Token refreshed. New token sent in DMs" + ctx.respondSuccess("`${token.token}`", true) + ctx.respondSuccess("Token refreshed. New token sent in DMs") + return true } private suspend fun delete(ctx: DiscordContext, system: SystemRecord): Boolean { @@ -651,7 +661,7 @@ object SystemCommands { "The data will be lost forever (A long time!)", yes = Button("Delete system", Button.wastebasket, ButtonStyle.Danger) { val export = Exporter.export(ctx.getUser()!!.id.value) - ctx.respondFiles(null, NamedFile("system.json", export.byteInputStream())) + ctx.respondFiles(null, NamedFile("system.json", ChannelProvider { export.byteInputStream().toByteReadChannel() })) database.dropSystem(ctx.getUser()!!) content = "System deleted." }, diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index f6708e7e..8d3ac913 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -10,6 +10,7 @@ package dev.proxyfox.database import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.ChannelBehavior +import dev.proxyfox.database.records.group.GroupRecord import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.member.MemberServerSettingsRecord @@ -42,6 +43,7 @@ class InMemoryDatabase : Database() { private lateinit var memberServers: MutableMap> + private lateinit var groups: MutableMap> override suspend fun setup(): InMemoryDatabase { users = ConcurrentHashMap() @@ -56,6 +58,7 @@ class InMemoryDatabase : Database() { members = ConcurrentHashMap() memberProxies = ConcurrentHashMap() memberServers = ConcurrentHashMap() + groups = ConcurrentHashMap() return this } @@ -315,6 +318,14 @@ class InMemoryDatabase : Database() { } } + override suspend fun fetchGroupsFromMember(member: MemberRecord): List { + return groups[member.systemId]?.values?.filter { it.members.contains(member.id) } ?: emptyList() + } + + override suspend fun fetchMembersFromGroup(group: GroupRecord): List { + return members[group.systemId]?.let { group.members.mapNotNull(it::get) } ?: emptyList() + } + override suspend fun export(other: Database) { TODO("Not yet implemented") } From 12bed62bb2471c73c02ef75c55e0fdcc4098c5aa Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 02:02:02 -0600 Subject: [PATCH 049/137] Default to port 8080 on ApiMain --- modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt index 886033e5..08d37099 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt @@ -21,7 +21,7 @@ import io.ktor.server.routing.* object ApiMain { private const val version = "1" - fun main() = embeddedServer(Netty, port = Integer.parseInt(System.getenv("PORT"))) { + fun main() = embeddedServer(Netty, port = System.getenv("PORT")?.toIntOrNull() ?: 8080) { configureRouting() configurePlugins() }.start() From f606126192735c916c2b143f9e3ad55af21717ce Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 04:26:44 -0600 Subject: [PATCH 050/137] Add export test to Time Resolution --- .../dev/proxyfox/database/etc/exporter/Exporter.kt | 13 +++++++++---- .../dev/proxyfox/database/DatabaseTestUtil.kt | 3 +++ .../kotlin/dev/proxyfox/importer/ImporterTest.kt | 10 ++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt index 2e7ecb43..d8b0e32f 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt @@ -10,25 +10,30 @@ package dev.proxyfox.database.etc.exporter import dev.proxyfox.database.Database import dev.proxyfox.database.database -import dev.proxyfox.database.gson import dev.proxyfox.database.etc.types.PkMember import dev.proxyfox.database.etc.types.PkProxy import dev.proxyfox.database.etc.types.PkSwitch import dev.proxyfox.database.etc.types.PkSystem +import dev.proxyfox.database.gson +import org.jetbrains.annotations.TestOnly object Exporter { suspend inline fun export(userId: ULong) = export(database, userId) suspend fun export(database: Database, userId: ULong): String { - val system = database.fetchSystemFromUser(userId) ?: return "" + return exportToPkObject(database, userId)?.let { gson.toJson(it) } ?: "" + } + + @TestOnly + suspend fun exportToPkObject(database: Database, userId: ULong): PkSystem? { + val system = database.fetchSystemFromUser(userId) ?: return null - val pkSystem = PkSystem( + return PkSystem( system, members = database.fetchMembersFromSystem(system.id)?.map { PkMember(it, database.fetchProxiesFromSystemAndMember(system.id, it.id)?.mapTo(HashSet(), ::PkProxy)) }, switches = database.fetchSwitchesFromSystem(system.id)?.map(::PkSwitch), ) - return gson.toJson(pkSystem) } } \ No newline at end of file diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt index f5b3fd06..78aa0ce4 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt @@ -44,6 +44,9 @@ object DatabaseTestUtil { val instantEpoch = Instant.EPOCH!! val instantLastMicroOfEpochDay = Instant.ofEpochSecond(TimeUnit.DAYS.toSeconds(1) - 1L, 999_999_000L)!! + val stringEpoch = "1970-01-01T00:00:00Z" + val stringLastMicroOfEpochDay = "1970-01-01T23:59:59.999999Z" + inline fun entity(ret: ULong): T { return mockk { every { id } returns Snowflake(ret) diff --git a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt index 7b5ac3d4..b6f51457 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt @@ -15,8 +15,11 @@ import dev.proxyfox.database.DatabaseTestUtil.entity import dev.proxyfox.database.DatabaseTestUtil.instantEpoch import dev.proxyfox.database.DatabaseTestUtil.instantLastMicroOfEpochDay import dev.proxyfox.database.DatabaseTestUtil.seeded +import dev.proxyfox.database.DatabaseTestUtil.stringEpoch +import dev.proxyfox.database.DatabaseTestUtil.stringLastMicroOfEpochDay import dev.proxyfox.database.JsonDatabase import dev.proxyfox.database.MongoDatabase +import dev.proxyfox.database.etc.exporter.Exporter import dev.proxyfox.database.etc.importer.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -134,6 +137,13 @@ constructor(private val name: String, databaseFactory: () -> Database) { val sorted = switches!!.sortedBy { it.timestamp } assertEquals(sorted[0].timestamp, instantEpoch) assertEquals(sorted[1].timestamp, instantLastMicroOfEpochDay) + + val system = Exporter.exportToPkObject(database, user.id.value)!! + + val sortedExport = system.switches!!.sortedBy { it.timestamp } + + assertEquals(sortedExport[0].timestamp, stringEpoch) + assertEquals(sortedExport[1].timestamp, stringLastMicroOfEpochDay) } @Test From 27954dba6f3fcb68ad6c46337a812732e5afc403 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 04:33:46 -0600 Subject: [PATCH 051/137] Restore microsecond resolution of switches --- .../proxyfox/database/records/system/SystemSwitchRecord.kt | 4 +++- .../src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt index 079c308a..8538e84a 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt @@ -11,7 +11,9 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.PkId import dev.proxyfox.database.records.MongoRecord import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.Instant +import kotlinx.datetime.minus import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.bson.types.ObjectId @@ -33,7 +35,7 @@ class SystemSwitchRecord : MongoRecord { var timestamp: Instant set(inst) { - field = Instant.fromEpochSeconds(inst.epochSeconds) + field = inst.minus(inst.nanosecondsOfSecond % 1000, DateTimeUnit.NANOSECOND) } constructor(systemId: PkId = "", id: PkId = "", memberIds: List = ArrayList(), timestamp: Instant? = null) { diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt index dcab12ff..487a50e0 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt @@ -38,7 +38,7 @@ object DatabaseTestUtil { const val offsetDateTimeEpochString = "1970-01-01T00:00:00Z" val instantEpoch = Instant.fromEpochSeconds(0L) - val instantLastMicroOfEpochDay = Instant.fromEpochSeconds(TimeUnit.DAYS.toSeconds(1) - 1L) + val instantLastMicroOfEpochDay = Instant.fromEpochSeconds(TimeUnit.DAYS.toSeconds(1) - 1L, 999_999_000L) val stringEpoch = "1970-01-01T00:00:00Z" val stringLastMicroOfEpochDay = "1970-01-01T23:59:59.999999Z" From 02abe431586fec6f42ba070747a82b80042a2d6d Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 04:36:33 -0600 Subject: [PATCH 052/137] Add explicit microsecond resolution test --- .../test/kotlin/dev/proxyfox/exporter/ExporterTest.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt index ee7a69c1..80f38e1f 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt @@ -9,7 +9,9 @@ package dev.proxyfox.exporter import dev.proxyfox.database.DatabaseTestUtil.instantEpoch +import dev.proxyfox.database.DatabaseTestUtil.instantLastMicroOfEpochDay import dev.proxyfox.database.DatabaseTestUtil.offsetDateTimeEpochString +import dev.proxyfox.database.DatabaseTestUtil.stringLastMicroOfEpochDay import dev.proxyfox.database.etc.types.PkMember import dev.proxyfox.database.etc.types.PkSwitch import dev.proxyfox.database.etc.types.PkSystem @@ -49,4 +51,12 @@ class ExporterTest { }) Assert.assertEquals(switch.timestamp, offsetDateTimeEpochString) } + + @Test + fun `Exporter(Switch) - retain microseconds`() { + val switch = PkSwitch(SystemSwitchRecord().apply { + timestamp = instantLastMicroOfEpochDay + }) + Assert.assertEquals(switch.timestamp, stringLastMicroOfEpochDay) + } } \ No newline at end of file From 987d5474587ea93161ff25a9ad7ccf50791522cd Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 04:37:14 -0600 Subject: [PATCH 053/137] Remove redundant epoch string --- .../test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt | 2 -- .../src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt index 487a50e0..30e2c206 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt @@ -35,8 +35,6 @@ object DatabaseTestUtil { private val seed = System.getenv("TEST_SEED")?.toLongOrNull() private val rng = Random() - const val offsetDateTimeEpochString = "1970-01-01T00:00:00Z" - val instantEpoch = Instant.fromEpochSeconds(0L) val instantLastMicroOfEpochDay = Instant.fromEpochSeconds(TimeUnit.DAYS.toSeconds(1) - 1L, 999_999_000L) diff --git a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt index 80f38e1f..e476b64d 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt @@ -10,7 +10,7 @@ package dev.proxyfox.exporter import dev.proxyfox.database.DatabaseTestUtil.instantEpoch import dev.proxyfox.database.DatabaseTestUtil.instantLastMicroOfEpochDay -import dev.proxyfox.database.DatabaseTestUtil.offsetDateTimeEpochString +import dev.proxyfox.database.DatabaseTestUtil.stringEpoch import dev.proxyfox.database.DatabaseTestUtil.stringLastMicroOfEpochDay import dev.proxyfox.database.etc.types.PkMember import dev.proxyfox.database.etc.types.PkSwitch @@ -33,7 +33,7 @@ class ExporterTest { val system = PkSystem(SystemRecord().apply { timestamp = instantEpoch }) - Assert.assertEquals(system.created, offsetDateTimeEpochString) + Assert.assertEquals(system.created, stringEpoch) } @Test @@ -41,7 +41,7 @@ class ExporterTest { val member = PkMember(MemberRecord().apply { timestamp = instantEpoch }, null) - Assert.assertEquals(member.created, offsetDateTimeEpochString) + Assert.assertEquals(member.created, stringEpoch) } @Test @@ -49,7 +49,7 @@ class ExporterTest { val switch = PkSwitch(SystemSwitchRecord().apply { timestamp = instantEpoch }) - Assert.assertEquals(switch.timestamp, offsetDateTimeEpochString) + Assert.assertEquals(switch.timestamp, stringEpoch) } @Test From 5472cce79be33c892ec34906f168484b5e25fa86 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 04:39:23 -0600 Subject: [PATCH 054/137] Add nanosecond truncation test --- .../kotlin/dev/proxyfox/database/DatabaseTestUtil.kt | 1 + .../test/kotlin/dev/proxyfox/exporter/ExporterTest.kt | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt index 30e2c206..5ffe5652 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt @@ -37,6 +37,7 @@ object DatabaseTestUtil { val instantEpoch = Instant.fromEpochSeconds(0L) val instantLastMicroOfEpochDay = Instant.fromEpochSeconds(TimeUnit.DAYS.toSeconds(1) - 1L, 999_999_000L) + val instantLastNanoOfEpochDay = Instant.fromEpochSeconds(TimeUnit.DAYS.toSeconds(1) - 1L, 999_999_999L) val stringEpoch = "1970-01-01T00:00:00Z" val stringLastMicroOfEpochDay = "1970-01-01T23:59:59.999999Z" diff --git a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt index e476b64d..69e3d94d 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt @@ -10,6 +10,7 @@ package dev.proxyfox.exporter import dev.proxyfox.database.DatabaseTestUtil.instantEpoch import dev.proxyfox.database.DatabaseTestUtil.instantLastMicroOfEpochDay +import dev.proxyfox.database.DatabaseTestUtil.instantLastNanoOfEpochDay import dev.proxyfox.database.DatabaseTestUtil.stringEpoch import dev.proxyfox.database.DatabaseTestUtil.stringLastMicroOfEpochDay import dev.proxyfox.database.etc.types.PkMember @@ -59,4 +60,12 @@ class ExporterTest { }) Assert.assertEquals(switch.timestamp, stringLastMicroOfEpochDay) } + + @Test + fun `Exporter(Switch) - truncate nanoseconds to microseconds`() { + val switch = PkSwitch(SystemSwitchRecord().apply { + timestamp = instantLastNanoOfEpochDay + }) + Assert.assertEquals(switch.timestamp, stringLastMicroOfEpochDay) + } } \ No newline at end of file From c4bad7e6422c1ae8e3fe408e3d17b4ba5d5b6cbf Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 04:44:31 -0600 Subject: [PATCH 055/137] Use Base64-encoded 24 bytes of secure random for generateToken --- .../src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt index ef0e657b..32b607e7 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt @@ -19,6 +19,8 @@ import org.bson.types.ObjectId import org.litote.kmongo.coroutine.toList import org.litote.kmongo.reactivestreams.getCollection import org.litote.kmongo.util.KMongoUtil +import java.security.SecureRandom +import java.util.* import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -26,6 +28,7 @@ import kotlin.contracts.contract typealias PkId = String +private val secureRandom = SecureRandom() const val pkIdBound = 11881376 val gson = GsonBuilder() @@ -145,6 +148,7 @@ suspend inline fun Mongo.getOrCreateCollection(): MongoCollect } fun generateToken(): String { - val alphabet: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') - return List(64) { alphabet.random() }.joinToString("") + val buffer = ByteArray(24) + secureRandom.nextBytes(buffer) + return Base64.getUrlEncoder().encodeToString(buffer) } From 947f6e33e5a23e0cf98afed977199b8f6dbb46ba Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 04:48:27 -0600 Subject: [PATCH 056/137] Add micros & nanos test to ExporterTest --- .../dev/proxyfox/database/DatabaseTestUtil.kt | 1 + .../dev/proxyfox/exporter/ExporterTest.kt | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt index 5ffe5652..4775c7c5 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt @@ -41,6 +41,7 @@ object DatabaseTestUtil { val stringEpoch = "1970-01-01T00:00:00Z" val stringLastMicroOfEpochDay = "1970-01-01T23:59:59.999999Z" + val stringLastNanoOfEpochDay = "1970-01-01T23:59:59.999999999Z" inline fun entity(ret: ULong): T { return mockk { diff --git a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt index 69e3d94d..b26fbb2a 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt @@ -13,6 +13,7 @@ import dev.proxyfox.database.DatabaseTestUtil.instantLastMicroOfEpochDay import dev.proxyfox.database.DatabaseTestUtil.instantLastNanoOfEpochDay import dev.proxyfox.database.DatabaseTestUtil.stringEpoch import dev.proxyfox.database.DatabaseTestUtil.stringLastMicroOfEpochDay +import dev.proxyfox.database.DatabaseTestUtil.stringLastNanoOfEpochDay import dev.proxyfox.database.etc.types.PkMember import dev.proxyfox.database.etc.types.PkSwitch import dev.proxyfox.database.etc.types.PkSystem @@ -37,6 +38,23 @@ class ExporterTest { Assert.assertEquals(system.created, stringEpoch) } + + @Test + fun `Exporter(System) - retain microseconds`() { + val system = PkSystem(SystemRecord().apply { + timestamp = instantLastMicroOfEpochDay + }, null) + Assert.assertEquals(system.created, stringLastMicroOfEpochDay) + } + + @Test + fun `Exporter(System) - retain nanoseconds`() { + val system = PkSystem(SystemRecord().apply { + timestamp = instantLastNanoOfEpochDay + }, null) + Assert.assertEquals(system.created, stringLastNanoOfEpochDay) + } + @Test fun `Exporter(Member) - retain seconds`() { val member = PkMember(MemberRecord().apply { @@ -45,6 +63,22 @@ class ExporterTest { Assert.assertEquals(member.created, stringEpoch) } + @Test + fun `Exporter(Member) - retain microseconds`() { + val member = PkMember(MemberRecord().apply { + timestamp = instantLastMicroOfEpochDay + }, null) + Assert.assertEquals(member.created, stringLastMicroOfEpochDay) + } + + @Test + fun `Exporter(Member) - retain nanoseconds`() { + val member = PkMember(MemberRecord().apply { + timestamp = instantLastNanoOfEpochDay + }, null) + Assert.assertEquals(member.created, stringLastNanoOfEpochDay) + } + @Test fun `Exporter(Switch) - retain seconds`() { val switch = PkSwitch(SystemSwitchRecord().apply { From f6bdc62d65d960634551e06099e0b791739ef673 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 22:35:26 -0600 Subject: [PATCH 057/137] Fix Mongo record serialization to match production --- .../InstantLongMicrosecondSerializer.kt | 59 +++++++++++++++++ .../InstantLongMillisecondSerializer.kt | 64 +++++++++++++++++++ .../LocalDateLongMillisecondSerializer.kt | 61 ++++++++++++++++++ .../database/records/member/MemberRecord.kt | 6 ++ .../database/records/system/SystemRecord.kt | 3 + .../records/system/SystemSwitchRecord.kt | 2 + 6 files changed, 195 insertions(+) create mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/InstantLongMicrosecondSerializer.kt create mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/InstantLongMillisecondSerializer.kt create mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/LocalDateLongMillisecondSerializer.kt diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/InstantLongMicrosecondSerializer.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/InstantLongMicrosecondSerializer.kt new file mode 100644 index 00000000..0f75a2e9 --- /dev/null +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/InstantLongMicrosecondSerializer.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.database.etc.ktx.serializaton + +import com.github.jershell.kbson.BsonFlexibleDecoder +import kotlinx.datetime.Instant +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.bson.BsonType + +// Created 2023-08-01T21:50:05 + +/** + * [Instant] serializer with the resolution of microseconds, used for + * preserving the timestamps of switches from PluralKit imports, and to + * not mangle the exports to where importing into PluralKit doubles + * most of the history. We do have a deduplicator for on-import for in case, + * but we shouldn't be mangling the data to begin with. + * + * @author Ampflower + * @since 2.1 + **/ +object InstantLongMicrosecondSerializer : KSerializer { + private const val microsecondReference = 1_000_000L + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("Instant", PrimitiveKind.LONG) + + /** + * Bypass the decoder API in the case of BSON. + * */ + override fun deserialize(decoder: Decoder): Instant { + val long = if (decoder is BsonFlexibleDecoder && decoder.reader.currentBsonType == BsonType.DATE_TIME) { + // This evidently should've been millisecond, but we've been + // storing the microsecond precision, so this must be done. + decoder.reader.readDateTime() + } else { + decoder.decodeLong() + } + return Instant.fromEpochSeconds(long / microsecondReference, long.mod(microsecondReference) * 1000L) + } + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeLong(value.epochMicroseconds) + } + + val Instant.epochMicroseconds + get() = epochSeconds * microsecondReference + (nanosecondsOfSecond / 1000L) +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/InstantLongMillisecondSerializer.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/InstantLongMillisecondSerializer.kt new file mode 100644 index 00000000..fc92ceae --- /dev/null +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/InstantLongMillisecondSerializer.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.database.etc.ktx.serializaton + +import com.github.jershell.kbson.BsonEncoder +import com.github.jershell.kbson.BsonFlexibleDecoder +import kotlinx.datetime.Instant +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.bson.BsonType + +// Created 2023-08-01T21:50:05 + +/** + * [Instant] serializer with the resolution of milliseconds, used for + * storing non-critical timestamps such as when a system was created. + * + * Non-critical, as it doesn't risk mangling PluralKit data if reimported + * from our exports. + * + * @author Ampflower + * @since 2.1 + **/ +object InstantLongMillisecondSerializer : KSerializer { + private const val microsecondReference = 1_000_000L + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("Instant", PrimitiveKind.LONG) + + /** + * Bypass the decoder API in the case of BSON. + * */ + override fun deserialize(decoder: Decoder): Instant { + val long = if (decoder is BsonFlexibleDecoder && decoder.reader.currentBsonType == BsonType.DATE_TIME) { + // This evidently should've been millisecond, but we've been + // storing the microsecond precision, so this must be done. + decoder.reader.readDateTime() + } else { + decoder.decodeLong() + } + return Instant.fromEpochSeconds(long / 1000L, long.mod(1000L) * microsecondReference) + } + + override fun serialize(encoder: Encoder, value: Instant) { + if (encoder is BsonEncoder) { + encoder.encodeDateTime(value.epochMilliseconds) + } else { + encoder.encodeLong(value.epochMilliseconds) + } + } + + val Instant.epochMilliseconds + get() = epochSeconds * 1000L + (nanosecondsOfSecond / microsecondReference) +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/LocalDateLongMillisecondSerializer.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/LocalDateLongMillisecondSerializer.kt new file mode 100644 index 00000000..b0129d28 --- /dev/null +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/ktx/serializaton/LocalDateLongMillisecondSerializer.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.database.etc.ktx.serializaton + +import com.github.jershell.kbson.BsonEncoder +import com.github.jershell.kbson.BsonFlexibleDecoder +import kotlinx.datetime.LocalDate +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.bson.BsonType +import java.util.concurrent.TimeUnit + +// Created 2023-08-01T21:50:05 + +/** + * [LocalDate] serializer with the resolution of milliseconds, used for + * retrieving and storing timestamps from the Mongo database. + * + * @author Ampflower + * @since 2.1 + **/ +object LocalDateLongMillisecondSerializer : KSerializer { + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) + + /** + * Bypass the decoder API in the case of BSON. + * */ + override fun deserialize(decoder: Decoder): LocalDate { + val long = if (decoder is BsonFlexibleDecoder && decoder.reader.currentBsonType == BsonType.DATE_TIME) { + // This evidently should've been millisecond, but we've been + // storing the microsecond precision, so this must be done. + decoder.reader.readDateTime() + } else { + decoder.decodeLong() + } + return LocalDate.fromEpochDays((long / TimeUnit.DAYS.toMillis(1)).toInt()) + } + + override fun serialize(encoder: Encoder, value: LocalDate) { + if (encoder is BsonEncoder) { + encoder.encodeDateTime(value.epochMilliseconds) + } else { + encoder.encodeLong(value.epochMilliseconds) + } + } + + val LocalDate.epochMilliseconds + get() = toEpochDays() * TimeUnit.DAYS.toMillis(1L) +} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt index 55edc633..bf780e28 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt @@ -10,6 +10,8 @@ package dev.proxyfox.database.records.member import dev.proxyfox.database.PkId import dev.proxyfox.database.database +import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMillisecondSerializer +import dev.proxyfox.database.etc.ktx.serializaton.LocalDateLongMillisecondSerializer import dev.proxyfox.database.records.MongoRecord import kotlinx.datetime.Clock import kotlinx.datetime.Instant @@ -49,7 +51,11 @@ class MemberRecord() : MongoRecord { var keepProxy: Boolean = false var autoProxy: Boolean = true var messageCount: ULong = 0UL + + @Serializable(InstantLongMillisecondSerializer::class) var timestamp: Instant = Clock.System.now() + + @Serializable(LocalDateLongMillisecondSerializer::class) var birthday: LocalDate? = null var age: String? = null var role: String? = null diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index 63c5eb1e..cc26e361 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -9,6 +9,7 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.PkId +import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMillisecondSerializer import dev.proxyfox.database.generateToken import dev.proxyfox.database.records.MongoRecord import dev.proxyfox.database.records.misc.AutoProxyMode @@ -39,6 +40,8 @@ open class SystemRecord : MongoRecord { var color: Int = -1 var avatarUrl: String? = null var timezone: String? = null + + @Serializable(InstantLongMillisecondSerializer::class) var timestamp: Instant = Clock.System.now() var token: String = generateToken() diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt index 8538e84a..8cbec403 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt @@ -9,6 +9,7 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.PkId +import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMicrosecondSerializer import dev.proxyfox.database.records.MongoRecord import kotlinx.datetime.Clock import kotlinx.datetime.DateTimeUnit @@ -33,6 +34,7 @@ class SystemSwitchRecord : MongoRecord { var id: PkId var memberIds: List + @Serializable(InstantLongMicrosecondSerializer::class) var timestamp: Instant set(inst) { field = inst.minus(inst.nanosecondsOfSecond % 1000, DateTimeUnit.NANOSECOND) From fe36748d2abdff8d3a3c1806f0c78bd5781cb385 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 22:37:30 -0600 Subject: [PATCH 058/137] Drop GSON in favour of Kotlin Serialization --- gradle/libs.versions.toml | 4 +- modules/common/build.gradle.kts | 7 +- .../dev/proxyfox/database/DatabaseUtil.kt | 14 --- .../database/etc/exporter/Exporter.kt | 5 +- .../database/etc/gson/InstantAdaptor.kt | 36 ------ .../database/etc/gson/LocalDateAdaptor.kt | 44 ------- .../database/etc/gson/NullValueProcessor.kt | 29 ----- .../database/etc/gson/ObjectIdNullifier.kt | 23 ---- .../database/etc/gson/RecordAdapter.kt | 119 ------------------ .../database/etc/gson/RecordAdapterFactory.kt | 24 ---- .../database/etc/gson/ULongAdaptor.kt | 22 ---- .../etc/gson/UnexpectedValueProcessor.kt | 23 ---- .../database/etc/gson/ValueProcessor.kt | 25 ---- .../proxyfox/database/etc/gson/VoidAdaptor.kt | 24 ---- 14 files changed, 7 insertions(+), 392 deletions(-) delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/InstantAdaptor.kt delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/LocalDateAdaptor.kt delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/NullValueProcessor.kt delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ObjectIdNullifier.kt delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapter.kt delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapterFactory.kt delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ULongAdaptor.kt delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/UnexpectedValueProcessor.kt delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ValueProcessor.kt delete mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/VoidAdaptor.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5c25abb2..ca5a5bec 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,6 @@ pluralkt = "1.2" # Database postgres = "42.3.3" kjdbc = "0.5.2" -gson = "2.9.0" kmongo = "4.6.0" kotlinx_datetime = "0.4.0" @@ -38,7 +37,6 @@ pluralkt = { module = "dev.proxyfox:pluralkt", version.ref = "pluralkt" } kotlin_stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlinx_coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx_coroutines" } -gson = { module = "com.google.code.gson:gson", version.ref = "gson" } kjdbc = { module = "com.vladsch.kotlin-jdbc:kotlin-jdbc", version.ref = "kjdbc" } postgres = { module = "org.postgresql:postgresql", version.ref = "postgres" } kmongo_base = { module = "org.litote.kmongo:kmongo-serialization", version.ref = "kmongo" } @@ -57,7 +55,7 @@ kotlinx_coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t [bundles] base = ["guava", "logback", "kotlin_stdlib", "kotlinx_coroutines_core", "kord", "proxyfox_command", "pluralkt"] -database = ["gson", "kmongo_base", "kmongo_coroutine", "kmongo_async", "kotlinx_datetime"] +database = ["kmongo_base", "kmongo_coroutine", "kmongo_async", "kotlinx_datetime"] api = ["ktor_server", "ktor_server_netty", "ktor_content_negotiation", "ktor_serialization"] test = ["testng", "kotlinx_coroutines_test", "mockk"] diff --git a/modules/common/build.gradle.kts b/modules/common/build.gradle.kts index 7c226cd7..938e56e2 100644 --- a/modules/common/build.gradle.kts +++ b/modules/common/build.gradle.kts @@ -7,7 +7,7 @@ */ import java.io.ByteArrayOutputStream -import java.nio.charset.* +import java.nio.charset.Charset plugins { alias(libs.plugins.kotlin.jvm) @@ -15,7 +15,6 @@ plugins { dependencies { api(libs.bundles.base) - api(libs.gson) api(kotlin("stdlib")) } @@ -27,12 +26,12 @@ tasks.withType { } } -fun getCommitHash(): String? { +fun getCommitHash(): String { val stdout = ByteArrayOutputStream() exec { commandLine("git", "log", "-n", "1", "--pretty=format:\"%h\"", "--encoding=UTF-8") standardOutput = stdout } val str = stdout.toString(Charset.defaultCharset()) - return str.substring(1, str.length-1) + return str.substring(1, str.length - 1) } \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt index 32b607e7..96953e03 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt @@ -8,14 +8,9 @@ package dev.proxyfox.database -import com.google.gson.* import com.mongodb.reactivestreams.client.MongoCollection -import dev.proxyfox.database.etc.gson.* import dev.proxyfox.database.etc.importer.ImporterException import kotlinx.coroutines.reactive.awaitFirst -import kotlinx.datetime.Instant -import kotlinx.datetime.LocalDate -import org.bson.types.ObjectId import org.litote.kmongo.coroutine.toList import org.litote.kmongo.reactivestreams.getCollection import org.litote.kmongo.util.KMongoUtil @@ -31,15 +26,6 @@ typealias PkId = String private val secureRandom = SecureRandom() const val pkIdBound = 11881376 -val gson = GsonBuilder() - .registerTypeAdapter(LocalDate::class.java, LocalDateAdaptor) - .registerTypeAdapter(ObjectId::class.java, ObjectIdNullifier) - .registerTypeAdapter(Instant::class.java, InstantAdaptor) - .registerTypeAdapter(ULong::class.java, ULongAdaptor) - .registerTypeAdapter(Void::class.java, VoidAdaptor) - .registerTypeAdapterFactory(RecordAdapterFactory) - .create()!! - fun String.sanitise(): String { return replace("\u0000", "").trim() } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt index d8b0e32f..9ac4f7a9 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt @@ -14,14 +14,15 @@ import dev.proxyfox.database.etc.types.PkMember import dev.proxyfox.database.etc.types.PkProxy import dev.proxyfox.database.etc.types.PkSwitch import dev.proxyfox.database.etc.types.PkSystem -import dev.proxyfox.database.gson +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.jetbrains.annotations.TestOnly object Exporter { suspend inline fun export(userId: ULong) = export(database, userId) suspend fun export(database: Database, userId: ULong): String { - return exportToPkObject(database, userId)?.let { gson.toJson(it) } ?: "" + return exportToPkObject(database, userId)?.let { Json.Default.encodeToString(it) } ?: "" } @TestOnly diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/InstantAdaptor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/InstantAdaptor.kt deleted file mode 100644 index 220cc296..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/InstantAdaptor.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import com.google.gson.* -import dev.proxyfox.database.sanitise -import kotlinx.datetime.Instant -import kotlinx.datetime.toJavaInstant -import kotlinx.datetime.toKotlinInstant -import java.lang.reflect.Type -import java.time.format.DateTimeFormatter - -object InstantAdaptor : JsonSerializer, JsonDeserializer { - override fun serialize(src: Instant?, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - return if (src == null) - JsonNull.INSTANCE - else - JsonPrimitive(DateTimeFormatter.ISO_INSTANT.format(src.toJavaInstant())) - } - - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Instant? { - return json.asString.sanitise().run { - if (isNullOrBlank()) { - null - } else { - DateTimeFormatter.ISO_INSTANT.parse(this, java.time.Instant::from).toKotlinInstant() - } - } - } -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/LocalDateAdaptor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/LocalDateAdaptor.kt deleted file mode 100644 index 6932dc7a..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/LocalDateAdaptor.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import com.google.gson.* -import dev.proxyfox.database.sanitise -import kotlinx.datetime.LocalDate -import kotlinx.datetime.toJavaLocalDate -import kotlinx.datetime.toKotlinLocalDate -import java.lang.reflect.Type -import java.time.format.DateTimeFormatter -import java.time.temporal.TemporalQueries - -// Created 2022-01-10T20:32:28 - -/** - * @author Ampflower - * @since ${version} - **/ -object LocalDateAdaptor : JsonSerializer, JsonDeserializer { - override fun serialize(src: LocalDate?, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - return if (src == null) { - JsonNull.INSTANCE - } else { - JsonPrimitive(DateTimeFormatter.ISO_DATE.format(src.toJavaLocalDate())) - } - } - - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): LocalDate? { - return json.asString.sanitise().run { - if (isNullOrBlank()) { - null - } else { - DateTimeFormatter.ISO_DATE.parse(this, TemporalQueries.localDate()).toKotlinLocalDate() - } - } - } -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/NullValueProcessor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/NullValueProcessor.kt deleted file mode 100644 index c974e570..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/NullValueProcessor.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import com.google.gson.stream.JsonReader - -// Created 2022-01-10T17:04:01 - -/** - * @author Ampflower - * @since ${version} - **/ -object NullValueProcessor : ValueProcessor { - override fun ifArray(reader: JsonReader) = reader.skip() - override fun ifBoolean(reader: JsonReader) = reader.skip() - override fun ifString(reader: JsonReader) = reader.skip() - override fun ifNumber(reader: JsonReader) = reader.skip() - - private fun JsonReader.skip(): Any? { - skipValue() - return null - } -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ObjectIdNullifier.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ObjectIdNullifier.kt deleted file mode 100644 index bb79d417..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ObjectIdNullifier.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import com.google.gson.* -import org.bson.types.ObjectId -import java.lang.reflect.Type - -object ObjectIdNullifier : JsonSerializer, JsonDeserializer { - override fun serialize(src: ObjectId?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { - return JsonNull.INSTANCE - } - - override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): ObjectId { - return ObjectId() - } -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapter.kt deleted file mode 100644 index bc23fdca..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapter.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import com.google.gson.Gson -import com.google.gson.JsonElement -import com.google.gson.TypeAdapter -import com.google.gson.annotations.SerializedName -import com.google.gson.internal.`$Gson$Types` -import com.google.gson.reflect.TypeToken -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken -import com.google.gson.stream.JsonWriter -import dev.proxyfox.database.etc.importer.ImporterException -import dev.proxyfox.database.mapArray -import org.slf4j.LoggerFactory -import java.lang.reflect.RecordComponent -import java.lang.reflect.Type -import kotlin.reflect.full.primaryConstructor - -class RecordAdapter(private val gson: Gson, private val type: Type, private val rawType: Class) : TypeAdapter() { - - private val componentMap = HashMap() - private val serialisedName = HashMap() - - init { - assert(Record::class.java.isAssignableFrom(rawType)) { "Invalid class $rawType ($type)" } - for (component in rawType.recordComponents) { - componentMap[component.name] = component - - rawType.getDeclaredField(component.name).getAnnotation(SerializedName::class.java)?.let { - componentMap[it.value] = component - serialisedName[component] = it.value - for (alt in it.alternate) componentMap[alt] = component - } - } - } - - override fun write(out: JsonWriter, value: T) { - out.beginObject() - for (component in value.javaClass.recordComponents!!) { - out.name(serialisedName[component] ?: component.name) - component.accessor.invoke(value)?.also { - gson.getAdapter(it.javaClass).write(out, it) - } ?: out.nullValue() - } - out.endObject() - } - - override fun read(reader: JsonReader): T { - val list = ArrayList() - val map = HashMap() - val generic = gson.getAdapter(JsonElement::class.java) - - try { - if (reader.peek() != JsonToken.BEGIN_OBJECT) { - @Suppress("UNCHECKED_CAST") - val primitiveProcessor = rawType.getAnnotation(UnexpectedValueProcessor::class.java) as? UnexpectedValueProcessor - ?: throw ImporterException("Unable to import $rawType @ $reader as token is ${reader.peek()}") - - val obj = primitiveProcessor.value.objectInstance ?: primitiveProcessor.value.primaryConstructor?.call() - ?: throw ImporterException("Unable to import $rawType @ $reader as token is ${reader.peek()} and $primitiveProcessor returned an unconstructable class ${primitiveProcessor.value}") - return when (reader.peek()) { - JsonToken.BEGIN_ARRAY -> obj.ifArray(reader) - JsonToken.STRING -> obj.ifString(reader) - JsonToken.BOOLEAN -> obj.ifBoolean(reader) - JsonToken.NUMBER -> obj.ifNumber(reader) - else -> throw ImporterException("Unable to import $rawType @ $reader as token is ${reader.peek()}") - } - } else { - reader.beginObject() - - while (reader.peek() == JsonToken.NAME) { - val name = reader.nextName() - val component = componentMap[name] - val path = reader.path - - if (component == null) { - val output = generic.read(reader) - if (output != null && !output.isJsonNull && !(output.isJsonArray && output.asJsonArray.isEmpty) && !(output.isJsonObject && output.asJsonObject.size() == 0)) { - val location = reader.toString() - list.add(ImporterException("Bad entry at $path: $name -> $output @ $location")) - } - } else try { - map[name] = gson.getAdapter(TypeToken.get(`$Gson$Types`.resolve(type, rawType, componentMap[name]!!.genericType))).read(reader) - } catch (e: Exception) { - throw ImporterException("Unexpected exception processing $component @ $path - ${reader.path}", e) - } - } - - reader.endObject() - } - } catch (e: Throwable) { - list.forEach(e::addSuppressed) - throw e - } - - if (list.isNotEmpty()) { - val e = ImporterException("Errors encountered around ${reader.previousPath} - ${reader.path}") - list.forEach(e::addSuppressed) - logger.warn("Record reader traces", e) - } - - - return rawType - .getDeclaredConstructor(*rawType.recordComponents.mapArray(RecordComponent::getType)) - .newInstance(*rawType.recordComponents.mapArray { map[it.name] }) - } - - companion object { - private val logger = LoggerFactory.getLogger(RecordAdapter::class.java) - } -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapterFactory.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapterFactory.kt deleted file mode 100644 index db3a0de0..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/RecordAdapterFactory.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import com.google.gson.Gson -import com.google.gson.TypeAdapter -import com.google.gson.TypeAdapterFactory -import com.google.gson.reflect.TypeToken - -object RecordAdapterFactory : TypeAdapterFactory { - @Suppress("UNCHECKED_CAST") - override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - if (Record::class.java.isAssignableFrom(type.rawType)) { - return RecordAdapter(gson, type.type, type.rawType as Class) as TypeAdapter - } - return null - } -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ULongAdaptor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ULongAdaptor.kt deleted file mode 100644 index 8382fd31..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ULongAdaptor.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import com.google.gson.* -import java.lang.reflect.Type - -object ULongAdaptor : JsonSerializer, JsonDeserializer { - override fun serialize(src: ULong?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { - return if (src == null) JsonNull.INSTANCE else JsonPrimitive(src.toLong()) - } - - override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): ULong { - return json.asLong.toULong() - } -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/UnexpectedValueProcessor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/UnexpectedValueProcessor.kt deleted file mode 100644 index 03e9c772..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/UnexpectedValueProcessor.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import kotlin.reflect.KClass - -// Created 2022-01-10T03:36:52 - -/** - * @author Ampflower - * @since ${version} - **/ -@Target(AnnotationTarget.FIELD, AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) -@Retention(AnnotationRetention.RUNTIME) -annotation class UnexpectedValueProcessor( - val value: KClass> -) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ValueProcessor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ValueProcessor.kt deleted file mode 100644 index 9c45d2d3..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/ValueProcessor.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import com.google.gson.stream.JsonReader -import dev.proxyfox.database.unsupported - -// Created 2022-01-10T03:41:13 - -/** - * @author Ampflower - * @since ${version} - **/ -interface ValueProcessor { - fun ifArray(reader: JsonReader): T = unsupported() - fun ifBoolean(reader: JsonReader): T = unsupported() - fun ifString(reader: JsonReader): T = unsupported() - fun ifNumber(reader: JsonReader): T = unsupported() -} \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/VoidAdaptor.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/VoidAdaptor.kt deleted file mode 100644 index 6a79ce0f..00000000 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/gson/VoidAdaptor.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2022, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.database.etc.gson - -import com.google.gson.TypeAdapter -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonWriter - -object VoidAdaptor : TypeAdapter() { - override fun write(out: JsonWriter, value: Void?) { - out.nullValue() - } - - override fun read(input: JsonReader): Void? { - input.skipValue() - return null - } -} \ No newline at end of file From 60d5b18c9ae873a7b13aa60e14658f7352fa67af Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 22:41:08 -0600 Subject: [PATCH 059/137] Force asynchronous registration of application commands --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 57016ff5..71af23a8 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -30,33 +30,27 @@ import dev.kord.core.on import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent import dev.kord.gateway.builder.Shards -import dev.kord.rest.builder.interaction.BaseInputChatBuilder -import dev.kord.rest.builder.interaction.string -import dev.kord.rest.builder.interaction.subCommand import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.request.KtorRequestException -import dev.proxyfox.bot.command.Commands import dev.proxyfox.bot.command.MemberCommands.registerMemberCommands import dev.proxyfox.bot.command.MiscCommands.registerMiscCommands import dev.proxyfox.bot.command.SwitchCommands.registerSwitchCommands import dev.proxyfox.bot.command.SystemCommands.registerSystemCommands -import dev.proxyfox.bot.command.context.DiscordContext -import dev.proxyfox.command.node.CommandNode -import dev.proxyfox.command.node.builtin.LiteralNode import dev.proxyfox.common.* import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord import io.ktor.client.* import io.ktor.client.engine.cio.* +import io.ktor.client.request.forms.* import io.ktor.http.* +import io.ktor.utils.io.jvm.javaio.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.count import kotlinx.coroutines.flow.fold import kotlinx.datetime.Clock import kotlinx.datetime.Instant import java.lang.Integer.min -import java.time.OffsetDateTime import java.util.* import java.util.concurrent.Executors import kotlin.contracts.ExperimentalContracts @@ -138,9 +132,15 @@ suspend fun login() { } } - printStep("Registering slash commands", 2) + // TODO: Figure out why registerApplicationCommands is so abysmally slow + // For now, launch asynchronously so it's not the main point of hanging + // when starting the bot. + scope.launch { + printStep("Registering slash commands", 2) + kord.registerApplicationCommands() + printStep("Finished registering slash commands", 2) + } - kord.registerApplicationCommands() kord.on { onInteract() } @@ -243,7 +243,7 @@ suspend fun handleError(err: Throwable, message: MessageBehavior) { errorChannel!!.createMessage { content = "`$timestamp`" - addFile("exception.log", cause.byteInputStream()) + addFile("exception.log", ChannelProvider { cause.byteInputStream().toByteReadChannel() }) } } } From 1c5d45b614a7a03745fe9508fb6caabeade903cb Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 22:57:24 -0600 Subject: [PATCH 060/137] Convert ConversionMain.kt to Kotlin Serialization I honestly forgot this existed. --- .../dev/proxyfox/conversion/ConversionMain.kt | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt b/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt index aa5278e0..4668c7b1 100644 --- a/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt +++ b/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt @@ -8,21 +8,21 @@ package dev.proxyfox.conversion -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken import dev.proxyfox.database.Database import dev.proxyfox.database.databaseFromString -import dev.proxyfox.database.gson -import dev.proxyfox.database.records.misc.ServerSettingsRecord import dev.proxyfox.database.etc.importer.PluralKitImporter import dev.proxyfox.database.etc.types.PkSystem +import dev.proxyfox.database.records.misc.ServerSettingsRecord +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream import org.slf4j.LoggerFactory import java.io.File -import kotlin.io.path.Path import kotlin.system.exitProcess private val logger = LoggerFactory.getLogger("Converter") +@OptIn(ExperimentalSerializationApi::class) suspend fun main(args: Array) { var from: String? = null var to: String? = null @@ -62,12 +62,8 @@ suspend fun main(args: Array) { if (systems.exists()) { logger.info("Importing systems.json...") - JsonReader(systems.reader()).use { - val sysAdaptor = gson.getAdapter(PkSystem::class.java) - it.beginObject() - while (it.peek() != JsonToken.END_OBJECT) { - val id = it.nextName().toULong() - val obj = sysAdaptor.read(it) as PkSystem + systems.inputStream().use { input -> + Json.decodeFromStream>(input).forEach { (id, obj) -> try { output.bulk { val pki = object : PluralKitImporter(directAllocation = true, ignoreUnfinished = true) {} @@ -81,24 +77,19 @@ suspend fun main(args: Array) { throw Exception("Failed with $id -> $obj", e) } } - it.endObject() } } if (roles.exists()) { logger.info("Importing roles.json...") output.bulk { - JsonReader(roles.reader()).use { - it.beginObject() - while (it.peek() != JsonToken.END_OBJECT) { - val id = it.nextName().toULong() - val role = it.nextLong().toULong() + roles.inputStream().use { input -> + Json.decodeFromStream>(input).forEach { (id, role) -> createServerSettings(ServerSettingsRecord().apply { serverId = id proxyRole = role }) } - it.endObject() } } } From 71dc8e1f9d21fda685ed9d4aafbca83115445c78 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sun, 8 Jan 2023 23:00:51 -0600 Subject: [PATCH 061/137] Remove inapplicable tests for GSON. --- .../dev/proxyfox/database/DatabaseUtilTest.kt | 75 ------------------- 1 file changed, 75 deletions(-) diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseUtilTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseUtilTest.kt index 3e3f24ec..f05cf627 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseUtilTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseUtilTest.kt @@ -8,7 +8,6 @@ package dev.proxyfox.database -import com.google.gson.reflect.TypeToken import dev.proxyfox.database.DatabaseTestUtil.pkIdStream import org.testng.Assert.* import org.testng.annotations.DataProvider @@ -59,66 +58,6 @@ class DatabaseUtilTest { assertEquals(list.firstFree(), expected) } - @Test - fun `RecordAdapter(GenericStore) - expect list`() { - val record = readJson>>("""{"value":["a","b","c"]}""") - assertEquals(record.value, listOf("a", "b", "c")) - } - - @Test - fun `RecordAdapter(GenericStore) - expect array`() { - val record = readJson>>("""{"value":["a","b","c"]}""") - assertEquals(record.value, arrayOf("a", "b", "c")) - } - - @Test - fun `RecordAdapter(GenericStore) - expect map A`() { - val record = readJson>>("""{"value":{"integer":123,"string":"Hi!","integerMap":{"a":1,"b":2,"c":3}}}""") - assertEquals( - record.value, mapOf( - "integer" to 123.0, - "string" to "Hi!", - "integerMap" to mapOf("a" to 1.0, "b" to 2.0, "c" to 3.0) - ) - ) - } - - @Test - fun `RecordAdapter(GenericStore) - expect map B`() { - val record = readJson>>("""{"value":{"integer":123,"string":"Hi!","integerMap":{"1":"a","2":"b","3":"c"}}}""") - assertEquals( - record.value, mapOf( - "integer" to 123.0, - "string" to "Hi!", - "integerMap" to mapOf("1" to "a", "2" to "b", "3" to "c") - ) - ) - } - - @Test - fun `RecordAdapter(GenericStore) - expect ComplexStore`() { - val record = readJson>("""{"value":{"integer":123,"string":"Hi!","integerMap":{"1":"a","2":"b","3":"c"}}}""") - assertEquals( - record.value, ComplexStore( - integer = 123, - string = "Hi!", - integerMap = mapOf(1 to "a", 2 to "b", 3 to "c") - ) - ) - } - - @Test - fun `RecordAdapter(ComplexStore) - expect working`() { - val record = readJson("""{"integer":123,"string":"Hi!","integerMap":{"1":"a","2":"b","3":"c"}}""") - assertEquals( - record, ComplexStore( - integer = 123, - string = "Hi!", - integerMap = mapOf(1 to "a", 2 to "b", 3 to "c") - ) - ) - } - @DataProvider fun knownFirstFrees() = arrayOf>( arrayOf(listOf("aaaaa", "aaaab", "aaaac"), "aaaad"), @@ -141,18 +80,4 @@ class DatabaseUtilTest { @DataProvider fun randomIds(): Iterator> = pkIdStream(100).mapToObj { arrayOf(it.toPkString(), it) }.iterator() - - private inline fun readJson(str: String): T { - return gson.fromJson(str, object : TypeToken() {}.type) - } - - @JvmRecord - data class GenericStore(val value: T) - - @JvmRecord - data class ComplexStore( - val integer: Int, - val string: String, - val integerMap: Map - ) } \ No newline at end of file From f5922c2b95accd48e20e049481a29ed101a5ace1 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Tue, 10 Jan 2023 01:36:51 -0600 Subject: [PATCH 062/137] Undo DebugException being passed through to the error channel --- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 523f0b3e..82a62d82 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -204,7 +204,7 @@ suspend fun handleError(err: Throwable, message: MessageBehavior) { message.channel.createMessage( "An unexpected error occurred.\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" ) - // if (err is DebugException) return + if (err is DebugException) return if (errorChannel == null && errorChannelId != null) errorChannel = kord.getChannel(errorChannelId) as TextChannel if (errorChannel != null) { From c2d5da4ac03bbe90e0a28f7c44eae9b58343fb24 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Tue, 10 Jan 2023 01:46:50 -0600 Subject: [PATCH 063/137] Clarify comment. --- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 82a62d82..aaf6b04a 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -193,8 +193,9 @@ suspend fun handleError(err: Throwable, message: MessageBehavior) { // Let the logger unwind the stacktrace. logger.warn(timestamp.toString(), err) // Do not leak webhook URL nor token in output. - // Note: The token here is a generic regex that only matches by the bot's - // ID and will make no attempt to verify it's the real one, purely for guarding the + // Note: The token here is a generic regex that only matches with the bot's + // ID and will make no attempt to verify it's the real one, purely for guarding + // the token from brute forcing of the replace method. val reason = err.message?.replace(webhook, "[WEBHOOK]")?.replace(token, "[TOKEN]") var cause = "" err.stackTrace.forEach { From 7b9f90297ae54eb44c308e622db65576567f6be0 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Tue, 10 Jan 2023 01:51:47 -0600 Subject: [PATCH 064/137] Correct typo in gradle.yml --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2e172a55..5fd1b70e 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -35,7 +35,7 @@ jobs: name: Artifacts path: | build/libs/ - module/**/build/libs/ + modules/**/build/libs/ - name: capture test reports on failure uses: actions/upload-artifact@v3 if: failure() From e148879a24d715c6e4009dc41bee0efd3b4863aa Mon Sep 17 00:00:00 2001 From: Ampflower Date: Tue, 10 Jan 2023 02:57:58 -0600 Subject: [PATCH 065/137] Remove dependency on Kotlin stdlib in Patch --- modules/patch/build.gradle.kts | 10 ++++++ .../dev/proxyfox/patch/ProxyFoxProvider.kt | 36 +++++++++++-------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/modules/patch/build.gradle.kts b/modules/patch/build.gradle.kts index aa5f272d..250bd5ec 100644 --- a/modules/patch/build.gradle.kts +++ b/modules/patch/build.gradle.kts @@ -32,4 +32,14 @@ dependencies { // Bot module to avoid shadowing in Quilt Loader into ProxyFox. implementation(project(":modules:bot")) +} + +tasks { + compileKotlin { + kotlinOptions.freeCompilerArgs += listOf( + "-Xno-call-assertions", + "-Xno-receiver-assertions", + "-Xno-param-assertions" + ) + } } \ No newline at end of file diff --git a/modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxProvider.kt b/modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxProvider.kt index b5baa6b5..90481e6c 100644 --- a/modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxProvider.kt +++ b/modules/patch/src/main/java/dev/proxyfox/patch/ProxyFoxProvider.kt @@ -27,8 +27,11 @@ import java.lang.reflect.InvocationTargetException import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths +import kotlin.collections.List +import kotlin.collections.Set import kotlin.io.path.notExists -import kotlin.io.path.writeText +import java.util.List as JList +import java.util.Set as JSet // Created 2023-05-01T19:51:55 @@ -42,9 +45,9 @@ class ProxyFoxProvider : GameProvider { private val TRANSFORMER = GameTransformer() private var arguments: Arguments? = null - private lateinit var entrypoint: String - private lateinit var appJar: Path - private lateinit var miscJars: Set + private var entrypoint: String? = null + private var appJar: Path? = null + private var miscJars: Set? = null override fun getGameId() = "proxyfox" @@ -54,7 +57,7 @@ class ProxyFoxProvider : GameProvider { override fun getNormalizedGameVersion() = "0.0.0" - override fun getBuiltinMods() = setOf(BuiltinMod(listOf(appJar), V1ModMetadataBuilder().apply { + override fun getBuiltinMods(): Set = JSet.of(BuiltinMod(JList.of(appJar), V1ModMetadataBuilder().apply { id = gameId group = "builtin" version = Version.of(normalizedGameVersion) @@ -76,18 +79,20 @@ class ProxyFoxProvider : GameProvider { try { val jar = System.getProperty(SystemProperties.GAME_JAR_PATH) - val lookupPaths: List - - if (jar != null) { + val lookupPaths: List = if (jar != null) { val path = Paths.get(jar).toAbsolutePath().normalize() if (path.notExists()) { throw RuntimeException("ProxyFox jar $path configured through ${SystemProperties.GAME_JAR_PATH} system property doesn't exist!") } - lookupPaths = path + launcher.classPath + + ArrayList(launcher.classPath.size + 1).apply { + add(path) + addAll(launcher.classPath) + } } else { - lookupPaths = launcher.classPath + launcher.classPath } val classifier = BudgetLibClassifier(ProxyFoxLibrary::class.java) @@ -112,7 +117,9 @@ class ProxyFoxProvider : GameProvider { Log.init(ConsoleLogHandler(), true) val path = Files.createTempFile("Hacky-Classpath", ".txt").toAbsolutePath() - path.writeText("$appJar${File.pathSeparator}${miscJars.joinToString(File.pathSeparator)}") + val builder = StringBuilder("$appJar${File.pathSeparatorChar}") + miscJars?.forEach { builder.append(it).append(File.pathSeparatorChar) } + Files.writeString(path, builder) // This is honestly stupidly hacky. // There's no other workaround available that I can see, so @@ -126,16 +133,15 @@ class ProxyFoxProvider : GameProvider { override fun unlockClassPath(launcher: QuiltLauncher) { launcher.addToClassPath(appJar) - for (path in miscJars) { - launcher.addToClassPath(path) - } + miscJars?.forEach(launcher::addToClassPath) } override fun launch(loader: ClassLoader) { try { val c = loader.loadClass(entrypoint) val m = c.getMethod("main", Array::class.java) - m.invoke(null, arguments!!.toArray() as Any) + val args: Any = arguments?.toArray() ?: throw NullPointerException("arguments") + m.invoke(null, args) } catch (e: InvocationTargetException) { throw FormattedException("ProxyFox has crashed", e.cause) } catch (e: ReflectiveOperationException) { From 9ac901d27f5af76a9256e62843db443d0320f581 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Tue, 10 Jan 2023 03:35:27 -0600 Subject: [PATCH 066/137] Setup shadowJar for Patch to bundle Quilt Loader & dependencies. --- modules/patch/build.gradle.kts | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/modules/patch/build.gradle.kts b/modules/patch/build.gradle.kts index 250bd5ec..94ef53e7 100644 --- a/modules/patch/build.gradle.kts +++ b/modules/patch/build.gradle.kts @@ -8,27 +8,32 @@ plugins { alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.shadow) +} + +configurations { + create("include") } dependencies { // This provides a patched Quilt Loader that can run in this environment. // Version is pinned, avoiding having the API suddenly break due to an // update that happens to effect what we depend on. - implementation("org.quiltmc:quilt-loader:0.18.1-beta.27-20230105.235638-8") + implementation("org.quiltmc:quilt-loader:0.18.1-beta.27-20230105.235638-8")?.let { "include"(it) } // Loader's dependencies; will not be required when QuiltMC/quilt-loader#190 // is merged upstream. - implementation("net.fabricmc:sponge-mixin:0.11.4+mixin.0.8.5") - implementation("net.fabricmc:tiny-mappings-parser:0.3.0+build.17") - implementation("net.fabricmc:tiny-remapper:0.8.6") - implementation("net.fabricmc:access-widener:2.1.0") - implementation("org.quiltmc:quilt-json5:1.0.2") - implementation("org.quiltmc:quilt-config:1.0.0-beta.6") - implementation("org.ow2.asm:asm:9.4") - implementation("org.ow2.asm:asm-analysis:9.4") - implementation("org.ow2.asm:asm-commons:9.4") - implementation("org.ow2.asm:asm-tree:9.4") - implementation("org.ow2.asm:asm-util:9.4") + implementation("net.fabricmc:sponge-mixin:0.11.4+mixin.0.8.5")?.let { "include"(it) } + implementation("net.fabricmc:tiny-mappings-parser:0.3.0+build.17")?.let { "include"(it) } + implementation("net.fabricmc:tiny-remapper:0.8.6")?.let { "include"(it) } + implementation("net.fabricmc:access-widener:2.1.0")?.let { "include"(it) } + implementation("org.quiltmc:quilt-json5:1.0.2")?.let { "include"(it) } + implementation("org.quiltmc:quilt-config:1.0.0-beta.6")?.let { "include"(it) } + implementation("org.ow2.asm:asm:9.4")?.let { "include"(it) } + implementation("org.ow2.asm:asm-analysis:9.4")?.let { "include"(it) } + implementation("org.ow2.asm:asm-commons:9.4")?.let { "include"(it) } + implementation("org.ow2.asm:asm-tree:9.4")?.let { "include"(it) } + implementation("org.ow2.asm:asm-util:9.4")?.let { "include"(it) } // Bot module to avoid shadowing in Quilt Loader into ProxyFox. implementation(project(":modules:bot")) @@ -42,4 +47,7 @@ tasks { "-Xno-param-assertions" ) } + shadowJar { + configurations = listOf(project.configurations["include"]) + } } \ No newline at end of file From d4563e6ea0521259a399ecde914470fc3d6f9cfd Mon Sep 17 00:00:00 2001 From: Ampflower Date: Tue, 10 Jan 2023 03:36:17 -0600 Subject: [PATCH 067/137] Group poolRelease as build for ease of finding it in IntelliJ --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index 04d37206..40485cf8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ tasks { archiveClassifier.set("") } register("poolRelease") { + group = "build" for (proj in subprojects) { val shadowJar = proj.tasks.findByPath("shadowJar") if (shadowJar != null) { From 0721c9a23c99721724a477cbf099274891d013f8 Mon Sep 17 00:00:00 2001 From: Octal Date: Tue, 10 Jan 2023 18:39:34 -0600 Subject: [PATCH 068/137] Decouple tokens from systems --- .../src/main/kotlin/dev/proxyfox/api/Authentication.kt | 3 +-- .../src/main/kotlin/dev/proxyfox/database/Database.kt | 2 +- .../main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt | 8 ++++---- .../main/kotlin/dev/proxyfox/database/MongoDatabase.kt | 7 ++++--- .../src/main/kotlin/dev/proxyfox/database/NopDatabase.kt | 2 +- .../main/kotlin/dev/proxyfox/database/ProxyDatabase.kt | 4 ++-- .../dev/proxyfox/database/records/system/SystemRecord.kt | 2 -- 7 files changed, 13 insertions(+), 15 deletions(-) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt index 80a4f57c..8ff088fe 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt @@ -15,7 +15,6 @@ import io.ktor.server.response.* val AuthenticationPlugin = createRouteScopedPlugin(name = "AuthenticationPlugin") { onCall { call -> val token = call.request.headers["Authorization"] ?: return@onCall call.respond("401 Unauthorized") - val userToken = database.getOrCreateTokenFromSystem(call.parameters["id"]!!).token - if (token != userToken) return@onCall call.respond("403 Forbidden") + database.fetchToken(token) ?: return@onCall call.respond("401 Unauthorized") } } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt index 52991548..0308bce3 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt @@ -318,7 +318,7 @@ abstract class Database : AutoCloseable { abstract suspend fun fetchMessage(messageId: Snowflake): ProxiedMessageRecord? abstract suspend fun fetchLatestMessage(systemId: String, channelId: Snowflake): ProxiedMessageRecord? - abstract suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord + abstract suspend fun fetchToken(token: String): TokenRecord? abstract suspend fun updateToken(token: TokenRecord) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index 8d3ac913..fe37dcf7 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -256,13 +256,13 @@ class InMemoryDatabase : Database() { return messages.findLast { it.systemId == systemId && it.channelId == channelId.value } } - override suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord { - if (!systemTokens.containsKey(systemId)) systemTokens[systemId] = TokenRecord(generateToken(), systemId) - return systemTokens[systemId]!! + override suspend fun fetchToken(token: String): TokenRecord? { + if (!systemTokens.containsKey(token)) return null + return systemTokens[token] } override suspend fun updateToken(token: TokenRecord) { - systemTokens[token.systemId] = token + systemTokens[token.token] = token } override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index b9f23538..f1084e04 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -272,10 +272,11 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { systemId: String, channelId: Snowflake ): ProxiedMessageRecord? = - messages.find("systemId" eq systemId, "channelId" eq channelId).sort("{'creationDate':-1}").limit(1).awaitFirstOrNull() + messages.find("systemId" eq systemId, "channelId" eq channelId).sort("{'creationDate':-1}").limit(1) + .awaitFirstOrNull() - override suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord = - systemTokens.findFirstOrNull("systemId" eq systemId) ?: TokenRecord(generateToken(), systemId) + override suspend fun fetchToken(token: String): TokenRecord? = + systemTokens.findFirstOrNull("token" eq token) override suspend fun updateToken(token: TokenRecord) { systemTokens.replaceOneById(token._id, token, upsert()).awaitFirst() diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt index a1914b3d..879925bc 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt @@ -109,7 +109,7 @@ class NopDatabase : Database() { channelId: Snowflake ): ProxiedMessageRecord? = null - override suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord = fail("Cannot store token for $systemId.") + override suspend fun fetchToken(token: String): TokenRecord? = null override suspend fun updateToken(token: TokenRecord) = fail("Cannot store token for ${token.systemId}.") diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt index 9069c467..bcddd50c 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt @@ -150,8 +150,8 @@ open class ProxyDatabase(protected val proxy: T) : Database() { return proxy.fetchLatestMessage(systemId, channelId) } - override suspend fun getOrCreateTokenFromSystem(systemId: String): TokenRecord { - return proxy.getOrCreateTokenFromSystem(systemId) + override suspend fun fetchToken(token: String): TokenRecord? { + return proxy.fetchToken(token) } override suspend fun updateToken(token: TokenRecord) { diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index cc26e361..e7c4c0c9 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -10,7 +10,6 @@ package dev.proxyfox.database.records.system import dev.proxyfox.database.PkId import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMillisecondSerializer -import dev.proxyfox.database.generateToken import dev.proxyfox.database.records.MongoRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.TrustLevel @@ -43,7 +42,6 @@ open class SystemRecord : MongoRecord { @Serializable(InstantLongMillisecondSerializer::class) var timestamp: Instant = Clock.System.now() - var token: String = generateToken() /** The ID of the member that's currently being auto-proxied. */ var autoProxy: PkId? = null From 20bab0cae66ef1d8306cc427c8ac8fae540e4823 Mon Sep 17 00:00:00 2001 From: Octal Date: Tue, 10 Jan 2023 19:27:30 -0600 Subject: [PATCH 069/137] Move token stuff from SystemCommands to MiscCommands --- .../dev/proxyfox/bot/command/MiscCommands.kt | 18 +++++++-- .../proxyfox/bot/command/SystemCommands.kt | 39 ------------------- 2 files changed, 14 insertions(+), 43 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 1662be84..5d3cbf2e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -13,9 +13,6 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior -import dev.kord.core.entity.Guild -import dev.kord.core.entity.Member -import dev.kord.core.entity.Message import dev.kord.rest.NamedFile import dev.kord.rest.builder.interaction.* import dev.proxyfox.bot.* @@ -35,7 +32,6 @@ import dev.proxyfox.database.etc.importer.ImporterException import dev.proxyfox.database.etc.importer.import import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode -import dev.proxyfox.database.records.misc.ProxiedMessageRecord import dev.proxyfox.database.records.misc.ServerSettingsRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord @@ -662,6 +658,14 @@ object MiscCommands { getFox(this) } } + + Commands.parser.literal("token", "t") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + token(this, system!!) + } + } } private suspend fun getFox(ctx: DiscordContext): Boolean { @@ -674,6 +678,12 @@ object MiscCommands { return true } + private suspend fun token(ctx: DiscordContext, system: SystemRecord): Boolean { + + + return true + } + private suspend fun debug(ctx: DiscordContext): Boolean { val shardid = ctx.getGuild()?.id?.value?.toShard() ?: 0 ctx.respondEmbed { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index f701b957..e3a6761b 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -30,7 +30,6 @@ import dev.proxyfox.common.printStep import dev.proxyfox.common.toColor import dev.proxyfox.database.database import dev.proxyfox.database.etc.exporter.Exporter -import dev.proxyfox.database.generateToken import dev.proxyfox.database.records.system.SystemRecord import io.ktor.client.request.forms.* import io.ktor.utils.io.jvm.javaio.* @@ -378,29 +377,6 @@ object SystemCommands { } } } -// registerCommand(literal(arrayOf("system", "s"), ::empty) { -// literal(arrayOf("delete", "del", "remove"), ::delete) -// }) -// -// registerCommand(literal(arrayOf("list", "l"), ::list) { -// unixLiteral(arrayOf("by-message-count", "bmc"), ::listByMessage) -// unixLiteral(arrayOf("verbose", "v"), ::listVerbose) -// }) - - Commands.parser.literal("token", "t") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - token(this, system!!) - } - literal("refresh", "r") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - tokenRefresh(this, system!!) - } - } - } } private suspend fun access(ctx: DiscordContext, system: SystemRecord): Boolean { @@ -638,21 +614,6 @@ object SystemCommands { return true } - private suspend fun token(ctx: DiscordContext, system: SystemRecord): Boolean { - ctx.respondSuccess("`${database.getOrCreateTokenFromSystem(system.id).token}`", true) - ctx.respondSuccess("Token sent in DMs") - return true - } - - private suspend fun tokenRefresh(ctx: DiscordContext, system: SystemRecord): Boolean { - val token = database.getOrCreateTokenFromSystem(system.id) - token.token = generateToken() - database.updateToken(token) - ctx.respondSuccess("`${token.token}`", true) - ctx.respondSuccess("Token refreshed. New token sent in DMs") - return true - } - private suspend fun delete(ctx: DiscordContext, system: SystemRecord): Boolean { TimedYesNoPrompt.build( runner = ctx.getUser()!!.id, From 00315492b09dcbd819e45f2f7813afb1a6cdda02 Mon Sep 17 00:00:00 2001 From: Octal Date: Sun, 15 Jan 2023 23:39:29 -0600 Subject: [PATCH 070/137] Implement trust levels --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 35 +- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 58 +- .../dev/proxyfox/bot/command/Commands.kt | 7 + .../proxyfox/bot/command/MemberCommands.kt | 678 ++++++++++++++---- .../dev/proxyfox/bot/command/MiscCommands.kt | 82 ++- .../proxyfox/bot/command/SwitchCommands.kt | 113 ++- .../proxyfox/bot/command/SystemCommands.kt | 201 +++++- .../bot/command/context/DiscordContext.kt | 17 +- .../database/records/system/SystemRecord.kt | 26 + 9 files changed, 997 insertions(+), 220 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 71af23a8..ce813e72 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -15,6 +15,7 @@ import dev.kord.core.Kord import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.builder.kord.KordBuilder import dev.kord.core.entity.Member import dev.kord.core.entity.channel.Channel @@ -50,7 +51,7 @@ import kotlinx.coroutines.flow.count import kotlinx.coroutines.flow.fold import kotlinx.datetime.Clock import kotlinx.datetime.Instant -import java.lang.Integer.min +import java.lang.Integer.* import java.util.* import java.util.concurrent.Executors import kotlin.contracts.ExperimentalContracts @@ -217,6 +218,38 @@ suspend fun updatePresence() { } } +suspend fun handleError(err: Throwable, interaction: ChatInputCommandInteractionCreateEvent) { + // Catch any errors and log them + val timestamp = System.currentTimeMillis() + // Let the logger unwind the stacktrace. + logger.warn(timestamp.toString(), err) + // Do not leak webhook URL nor token in output. + // Note: The token here is a generic regex that only matches by the bot's + // ID and will make no attempt to verify it's the real one, purely for guarding the + val reason = err.message?.replace(webhook, "[WEBHOOK]")?.replace(token, "[TOKEN]") + var cause = "" + err.stackTrace.forEach { + if (it.className.startsWith("dev.proxyfox")) + cause += " at $it\n" + } + interaction.interaction.respondEphemeral { + content = + "An unexpected error occurred.\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" + } + // if (err is DebugException) return + if (errorChannel == null && errorChannelId != null) + errorChannel = kord.getChannel(errorChannelId) as TextChannel + if (errorChannel != null) { + // Prevent the log channel from also showing tokens, should it be public in any manner. + cause = err.stackTraceToString().replace(webhook, "[WEBHOOK]").replace(token, "[TOKEN]") + + errorChannel!!.createMessage { + content = "`$timestamp`" + addFile("exception.log", ChannelProvider { cause.byteInputStream().toByteReadChannel() }) + } + } +} + suspend fun handleError(err: Throwable, message: MessageBehavior) { // Catch any errors and log them val timestamp = System.currentTimeMillis() diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index f46695fa..ad1d76b0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -8,7 +8,10 @@ package dev.proxyfox.bot -import dev.kord.common.entity.* +import dev.kord.common.entity.MessageType +import dev.kord.common.entity.Permission +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.createMessage import dev.kord.core.cache.data.AttachmentData @@ -16,8 +19,9 @@ import dev.kord.core.cache.data.EmbedData import dev.kord.core.entity.Attachment import dev.kord.core.entity.Embed import dev.kord.core.entity.channel.GuildMessageChannel -import dev.kord.core.event.interaction.* import dev.kord.core.entity.interaction.SubCommand +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.GlobalMessageCommandInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.event.message.MessageUpdateEvent import dev.kord.core.event.message.ReactionAddEvent @@ -36,7 +40,6 @@ import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord import kotlinx.datetime.toJavaLocalDate -import org.litote.kmongo.json import org.slf4j.LoggerFactory val prefixRegex = Regex("^(?:(<@!?${kord.selfId}>)|pf[>;!:])\\s*", RegexOption.IGNORE_CASE) @@ -292,27 +295,34 @@ suspend fun GlobalMessageCommandInteractionCreateEvent.onInteract() { } suspend fun ChatInputCommandInteractionCreateEvent.onInteract() { - when (interaction.invokedCommandName) { - "member" -> { - val command = interaction.command as? SubCommand ?: return - MemberCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } - } - "system" -> { - val command = interaction.command as? SubCommand ?: return - SystemCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } - } - "switch" -> { - val command = interaction.command as? SubCommand ?: return - SwitchCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } - } - else -> { - val command = interaction.command as? SubCommand ?: return - when(command.rootName) { - "info" -> MiscCommands.infoInteractionExecutors - "moderation" -> MiscCommands.moderationInteractionExecutors - "misc" -> MiscCommands.miscInteractionExecutors - else -> return - }[command.name]?.let { it(InteractionCommandContext(this)) } + try { + when (interaction.invokedCommandName) { + "member" -> { + val command = interaction.command as? SubCommand ?: return + MemberCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } + } + + "system" -> { + val command = interaction.command as? SubCommand ?: return + SystemCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } + } + + "switch" -> { + val command = interaction.command as? SubCommand ?: return + SwitchCommands.interactionExecutors[command.name]?.let { it(InteractionCommandContext(this)) } + } + + else -> { + val command = interaction.command as? SubCommand ?: return + when (command.rootName) { + "info" -> MiscCommands.infoInteractionExecutors + "moderation" -> MiscCommands.moderationInteractionExecutors + "misc" -> MiscCommands.miscInteractionExecutors + else -> return + }[command.name]?.let { it(InteractionCommandContext(this)) } + } } + } catch (err: Throwable) { + handleError(err, this) } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 0dad1c22..7ba836c0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -43,6 +43,13 @@ fun SubCommandBuilder.name(name: String = "name", required: Boolean = true) { this.required = required } } + +fun SubCommandBuilder.system(name: String = "system") { + string(name, "The $name to use") { + required = false + } +} + fun SubCommandBuilder.avatar(name: String = "avatar") { attachment(name, "The $name to set") { required = false diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index f25c5f65..5232d7f2 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -11,7 +11,8 @@ package dev.proxyfox.bot.command import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Snowflake import dev.kord.core.Kord -import dev.kord.rest.builder.interaction.* +import dev.kord.rest.builder.interaction.SubCommandBuilder +import dev.kord.rest.builder.interaction.subCommand import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.guild @@ -22,8 +23,12 @@ import dev.proxyfox.bot.kordColor import dev.proxyfox.bot.member import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.TimedYesNoPrompt +import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.CommandNode -import dev.proxyfox.command.node.builtin.* +import dev.proxyfox.command.node.builtin.greedy +import dev.proxyfox.command.node.builtin.literal +import dev.proxyfox.command.node.builtin.string +import dev.proxyfox.command.node.builtin.unixLiteral import dev.proxyfox.common.* import dev.proxyfox.database.database import dev.proxyfox.database.displayDate @@ -50,8 +55,15 @@ object MemberCommands { createGlobalChatInputCommand("member", "Manage or create a system member!") { subCommand("create", "Create a member") { name() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val name = value.interaction.command.strings["name"]!! @@ -60,19 +72,33 @@ object MemberCommands { } subCommand("delete", "Delete a member") { member() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false - delete(this, member!!) + delete(this, system, member!!) } } subCommand("fetch", "Fetches the member's card") { member() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -84,15 +110,22 @@ object MemberCommands { member() name(required = false) raw() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val name = value.interaction.command.strings["name"] val raw = value.interaction.command.booleans["raw"] ?: false - rename(this, member!!, name, raw) + rename(this, system, member!!, name, raw) } } access("member", "nickname") { @@ -100,8 +133,15 @@ object MemberCommands { name(required = false) raw() clear() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -109,7 +149,7 @@ object MemberCommands { val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - nickname(this, member!!, name, raw, clear) + nickname(this, system, member!!, name, raw, clear) } } access("member", "servernick") { @@ -118,12 +158,20 @@ object MemberCommands { guild() raw() clear() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false - val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id + val guildId = + value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id guildId ?: run { respondFailure("Command not ran in server.") return@runs false @@ -137,7 +185,7 @@ object MemberCommands { val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - servername(this, serverMember!!, name, raw, clear) + servername(this, system, serverMember!!, name, raw, clear) } } access("member", "description") { @@ -145,8 +193,15 @@ object MemberCommands { raw() clear() name("description", required = false) + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -154,34 +209,49 @@ object MemberCommands { val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - description(this, member!!, desc, raw, clear) + description(this, system, member!!, desc, raw, clear) } } access("member", "avatar") { member() avatar() clear() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val avatar = value.interaction.command.attachments["avatar"]?.data?.url val clear = value.interaction.command.booleans["clear"] ?: false - avatar(this, member!!, avatar, clear) + avatar(this, system, member!!, avatar, clear) } } access("member", "serveravatar") { member() avatar() guild() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false - val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id + val guildId = + value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id guildId ?: run { respondFailure("Command not ran in server.") return@runs false @@ -195,7 +265,7 @@ object MemberCommands { val avatar = value.interaction.command.attachments["avatar"]?.data?.url val clear = value.interaction.command.booleans["clear"] ?: false - serverAvatar(this, serverMember!!, avatar, clear) + serverAvatar(this, system, serverMember!!, avatar, clear) } } access("member", "pronouns") { @@ -203,8 +273,15 @@ object MemberCommands { name("pronouns", required = false) raw() clear() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -212,43 +289,64 @@ object MemberCommands { val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - pronouns(this, member!!, pro, raw, clear) + pronouns(this, system, member!!, pro, raw, clear) } } access("member", "color") { member() name("color", required = false) + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val color = value.interaction.command.strings["color"] - color(this, member!!, color?.toColor()) + color(this, system, member!!, color?.toColor()) } } access("member", "birthday") { member() name("birthday", required = false) clear() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val birthday = value.interaction.command.strings["birthday"] val clear = value.interaction.command.booleans["clear"] ?: false - birthday(this, member!!, tryParseLocalDate(birthday)?.first, clear) + birthday(this, system, member!!, tryParseLocalDate(birthday)?.first, clear) } } subCommand("proxy-add", "Adds a proxy") { member() name("prefix", required = false) name("suffix", required = false) + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -256,15 +354,22 @@ object MemberCommands { val suffix = value.interaction.command.strings["suffix"] val proxy = if (prefix == null && suffix == null) null else Pair(prefix, suffix) - proxy(this, member!!, proxy) + proxy(this, system, member!!, proxy) } } subCommand("proxy-delete", "Delete a proxy") { member() name("prefix", required = false) name("suffix", required = false) + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -272,32 +377,40 @@ object MemberCommands { val suffix = value.interaction.command.strings["suffix"] val proxy = if (prefix == null && suffix == null) null else Pair(prefix, suffix) val exists = proxy != null - val proxyTag = if (exists) database.fetchProxyTagFromMessage(getUser(), "${prefix}text$suffix") else null - removeProxy(this, member!!, exists, proxyTag) + val proxyTag = + if (exists) database.fetchProxyTagFromMessage(getUser(), "${prefix}text$suffix") else null + removeProxy(this, system, member!!, exists, proxyTag) } } access("member", "autoproxy") { member() bool("value", "The value to set") + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val value = value.interaction.command.booleans["value"] - autoproxy(this, member!!, value) + autoproxy(this, system, member!!, value) } } } } - @JvmName("registerMemberCommands_nonext") - suspend fun > registerMemberCommands(node: CommandNode, getMem: suspend DiscordContext.() -> String) { - node.registerMemberCommands(getMem) - } - suspend fun > CommandNode.registerMemberCommands(getMem: suspend DiscordContext.() -> String) { + + suspend fun > CommandNode.registerMemberCommands( + getMem: suspend DiscordContext.() -> String, + getSys: suspend DiscordContext.() -> SystemRecord? + ) { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false @@ -306,66 +419,66 @@ object MemberCommands { literal("remame", "name") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - rename(this, member!!, null, false) + rename(this, system, member!!, null, false) } unixLiteral("raw") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - rename(this, member!!, null, true) + rename(this, system, member!!, null, true) } } greedy("name") { getName -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - rename(this, member!!, name, false) + rename(this, system, member!!, name, false) } } } literal("nickname", "nick", "displayname", "dn") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - nickname(this, member!!, null, false, false) + nickname(this, system, member!!, null, false, false) } unixLiteral("clear", "remove") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - nickname(this, member!!, null, false, true) + nickname(this, system, member!!, null, false, true) } } unixLiteral("raw") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - nickname(this, member!!, null, true, false) + nickname(this, system, member!!, null, true, false) } } greedy("name") { getName -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false val name = getName() - nickname(this, member!!, name, false, false) + nickname(this, system, member!!, name, false, false) } } } @@ -373,7 +486,7 @@ object MemberCommands { literal("servername", "servernick", "sn") { guild { getGuildId -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false @@ -386,11 +499,11 @@ object MemberCommands { return@runs false } val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) - servername(this, serverMember!!, null, false, false) + servername(this, system, serverMember!!, null, false, false) } unixLiteral("clear", "remove") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false @@ -403,12 +516,12 @@ object MemberCommands { return@runs false } val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) - servername(this, serverMember!!, null, false, true) + servername(this, system, serverMember!!, null, false, true) } } unixLiteral("raw") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false @@ -421,12 +534,12 @@ object MemberCommands { return@runs false } val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) - servername(this, serverMember!!, null, true, false) + servername(this, system, serverMember!!, null, true, false) } } greedy("name") { getName -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false @@ -439,7 +552,7 @@ object MemberCommands { return@runs false } val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) - servername(this, serverMember!!, getName(), false, false) + servername(this, system, serverMember!!, getName(), false, false) } } } @@ -447,74 +560,74 @@ object MemberCommands { literal("description", "desc", "d") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - description(this, member!!, null, false, false) + description(this, system, member!!, null, false, false) } unixLiteral("raw") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - description(this, member!!, null, true, false) + description(this, system, member!!, null, true, false) } } unixLiteral("clear", "remove") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - description(this, member!!, null, false, true) + description(this, system, member!!, null, false, true) } } greedy("description") { getDesc -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - description(this, member!!, getDesc(), false, false) + description(this, system, member!!, getDesc(), false, false) } } } literal("avatar", "pfp") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - avatar(this, member!!, null, false) + avatar(this, system, member!!, null, false) } unixLiteral("clear", "remove") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - avatar(this, member!!, null, true) + avatar(this, system, member!!, null, true) } } attachment("avatar") { getAvatar -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - avatar(this, member!!, getAvatar().url, false) + avatar(this, system, member!!, getAvatar().url, false) } } string("avatar") { getAvatar -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - avatar(this, member!!, getAvatar(), false) + avatar(this, system, member!!, getAvatar(), false) } } } @@ -522,7 +635,7 @@ object MemberCommands { literal("serveravatar", "serverpfp", "sp", "sa") { guild { getGuildId -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false @@ -535,11 +648,11 @@ object MemberCommands { return@runs false } val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) - serverAvatar(this, serverMember!!, null, false) + serverAvatar(this, system, serverMember!!, null, false) } unixLiteral("clear", "remove") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false @@ -552,12 +665,12 @@ object MemberCommands { return@runs false } val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) - serverAvatar(this, serverMember!!, null, true) + serverAvatar(this, system, serverMember!!, null, true) } } attachment("avatar") { getAvatar -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false @@ -570,12 +683,12 @@ object MemberCommands { return@runs false } val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) - serverAvatar(this, serverMember!!, getAvatar().url, false) + serverAvatar(this, system, serverMember!!, getAvatar().url, false) } } greedy("avatar") { getAvatar -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false @@ -588,7 +701,7 @@ object MemberCommands { return@runs false } val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) - serverAvatar(this, serverMember!!, getAvatar(), false) + serverAvatar(this, system, serverMember!!, getAvatar(), false) } } } @@ -596,53 +709,53 @@ object MemberCommands { literal("autoproxy", "ap") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - autoproxy(this, member!!, null) + autoproxy(this, system, member!!, null) } // TODO: BooleanNode literal("disable", "off", "false", "0") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - autoproxy(this, member!!, false) + autoproxy(this, system, member!!, false) } } literal("enable", "on", "true", "1") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - autoproxy(this, member!!, true) + autoproxy(this, system, member!!, true) } } } literal("proxy", "p") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - proxy(this, member!!, null) + proxy(this, system, member!!, null) } literal("remove", "rem", "delete", "del") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - removeProxy(this, member!!, false, null) + removeProxy(this, system, member!!, false, null) } greedy("proxy") { getProxy -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false @@ -656,170 +769,174 @@ object MemberCommands { respondFailure("Proxy tag doesn't exist in this member.") return@runs false } - removeProxy(this, member, false, proxy) + removeProxy(this, system, member, false, proxy) } } } literal("add", "create") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - proxy(this, member!!, null) + proxy(this, system, member!!, null) } greedy("proxy") { getProxy -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false - proxy(this, member!!, proxy) + proxy(this, system, member!!, proxy) } } } greedy("proxy") { getProxy -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false - proxy(this, member!!, proxy) + proxy(this, system, member!!, proxy) } } } literal("pronouns") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - pronouns(this, member!!, null, false, false) + pronouns(this, system, member!!, null, false, false) } unixLiteral("clear", "remove") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - pronouns(this, member!!, null, false, true) + pronouns(this, system, member!!, null, false, true) } } unixLiteral("raw") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - pronouns(this, member!!, null, true, false) + pronouns(this, system, member!!, null, true, false) } } greedy("pronouns") { getPronouns -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - pronouns(this, member!!, getPronouns(), false, false) + pronouns(this, system, member!!, getPronouns(), false, false) } } } literal("color", "colour", "c") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - color(this, member!!, null) + color(this, system, member!!, null) } greedy("color") { getColor -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - color(this, member!!, getColor().toColor()) + color(this, system, member!!, getColor().toColor()) } } } literal("birthday","bday","birth","bd") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - birthday(this, member!!, null, false) + birthday(this, system, member!!, null, false) } unixLiteral("clear", "remove") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - birthday(this, member!!, null, true) + birthday(this, system, member!!, null, true) } } greedy("birthday") { getBirthday -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - birthday(this, member!!, tryParseLocalDate(getBirthday())?.first, false) + birthday(this, system, member!!, tryParseLocalDate(getBirthday())?.first, false) } } } literal("delete", "remove", "del", "rem") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - delete(this, member!!) + delete(this, system, member!!) } } } - suspend fun register() { - printStep("Registering member commands", 2) - //TODO: Dedupe code - Commands.parser.literal("member", "m") { + suspend fun > NodeHolder.registerBaseMemberCommands(getSys: suspend DiscordContext.() -> SystemRecord?) { + literal("member", "m") { runs { - empty(this) + val system = getSys() + if (!checkSystem(this, system)) return@runs false + empty(this, system!!) } - string("member", ::registerMemberCommands) + string("member") { + registerMemberCommands(it, getSys) + } literal("delete", "remove", "del") { runs { - delete(this, null) + val system = getSys() + if (!checkSystem(this, system)) return@runs false + delete(this, system!!, null) } greedy("member") { getMem -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - delete(this, member!!) + delete(this, system, member!!) } } } literal("create", "add", "new", "c") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false create(this, system!!, null) } greedy("member") { getMem -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false create(this, system!!, getMem()) } @@ -828,18 +945,37 @@ object MemberCommands { } } - suspend fun empty(ctx: DiscordContext): Boolean { + suspend fun register() { + printStep("Registering member commands", 2) + //TODO: Dedupe code + Commands.parser.registerBaseMemberCommands { + database.fetchSystemFromUser(getUser()) + } + } + + suspend fun empty(ctx: DiscordContext, system: SystemRecord): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + ctx.respondWarning("Make sure to provide a member command!") return false } suspend fun access(ctx: DiscordContext, system: SystemRecord, member: MemberRecord): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + val guild = ctx.getGuild() val settings = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) ctx.respondEmbed { val systemName = system.name ?: system.id author { - name = member.displayName?.let { "$it (${member.name})\u2007•\u2007$systemName" } ?: "${member.name}\u2007•\u2007$systemName" + name = member.displayName?.let { "$it (${member.name})\u2007•\u2007$systemName" } + ?: "${member.name}\u2007•\u2007$systemName" icon = member.avatarUrl.ifBlankThenNull() } member.avatarUrl?.let { @@ -890,14 +1026,26 @@ object MemberCommands { } } footer { - text = "Member ID \u2009• \u2009${member.id}\u2007|\u2007System ID \u2009• \u2009${system.id}\u2007|\u2007Created " + text = + "Member ID \u2009• \u2009${member.id}\u2007|\u2007System ID \u2009• \u2009${system.id}\u2007|\u2007Created " } timestamp = member.timestamp } return true } - suspend fun rename(ctx: DiscordContext, member: MemberRecord, name: String?, raw: Boolean): Boolean { + suspend fun rename( + ctx: DiscordContext, + system: SystemRecord, + member: MemberRecord, + name: String?, + raw: Boolean + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + name ?: run { if (raw) ctx.respondPlain("`${member.name}`") @@ -906,6 +1054,11 @@ object MemberCommands { return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.name = name database.updateMember(member) @@ -914,8 +1067,25 @@ object MemberCommands { return true } - suspend fun nickname(ctx: DiscordContext, member: MemberRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { + suspend fun nickname( + ctx: DiscordContext, + system: SystemRecord, + member: MemberRecord, + name: String?, + raw: Boolean, + clear: Boolean + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.displayName = null database.updateMember(member) @@ -937,6 +1107,11 @@ object MemberCommands { return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.displayName = name database.updateMember(member) @@ -945,8 +1120,25 @@ object MemberCommands { return true } - suspend fun servername(ctx: DiscordContext, serverMember: MemberServerSettingsRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { + suspend fun servername( + ctx: DiscordContext, + system: SystemRecord, + serverMember: MemberServerSettingsRecord, + name: String?, + raw: Boolean, + clear: Boolean + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + serverMember.nickname = null database.updateMemberServerSettings(serverMember) @@ -968,6 +1160,11 @@ object MemberCommands { return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + serverMember.nickname = name database.updateMemberServerSettings(serverMember) @@ -976,8 +1173,25 @@ object MemberCommands { return true } - suspend fun description(ctx: DiscordContext, member: MemberRecord, description: String?, raw: Boolean, clear: Boolean): Boolean { + suspend fun description( + ctx: DiscordContext, + system: SystemRecord, + member: MemberRecord, + description: String?, + raw: Boolean, + clear: Boolean + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.description = null database.updateMember(member) ctx.respondSuccess("Member's description cleared!") @@ -997,6 +1211,11 @@ object MemberCommands { return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.description = description database.updateMember(member) ctx.respondSuccess("Member description updated!") @@ -1004,8 +1223,24 @@ object MemberCommands { return true } - suspend fun avatar(ctx: DiscordContext, member: MemberRecord, avatar: String?, clear: Boolean): Boolean { + suspend fun avatar( + ctx: DiscordContext, + system: SystemRecord, + member: MemberRecord, + avatar: String?, + clear: Boolean + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.avatarUrl = null database.updateMember(member) ctx.respondSuccess("Member's avatar cleared!") @@ -1025,6 +1260,11 @@ object MemberCommands { return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.avatarUrl = avatar database.updateMember(member) ctx.respondSuccess("Member's avatar updated!") @@ -1032,8 +1272,24 @@ object MemberCommands { return true } - suspend fun serverAvatar(ctx: DiscordContext, serverMember: MemberServerSettingsRecord, avatar: String?, clear: Boolean): Boolean { + suspend fun serverAvatar( + ctx: DiscordContext, + system: SystemRecord, + serverMember: MemberServerSettingsRecord, + avatar: String?, + clear: Boolean + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + serverMember.avatarUrl = null database.updateMemberServerSettings(serverMember) ctx.respondSuccess("Member's server avatar cleared!") @@ -1052,6 +1308,11 @@ object MemberCommands { return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + serverMember.avatarUrl = avatar database.updateMemberServerSettings(serverMember) ctx.respondSuccess("Member's server avatar updated!") @@ -1059,7 +1320,23 @@ object MemberCommands { return true } - suspend fun removeProxy(ctx: DiscordContext, member: MemberRecord, exists: Boolean, proxy: MemberProxyTagRecord?): Boolean { + suspend fun removeProxy( + ctx: DiscordContext, + system: SystemRecord, + member: MemberRecord, + exists: Boolean, + proxy: MemberProxyTagRecord? + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + if (!exists) { ctx.respondWarning("Please provide a proxy tag to remove.") return true @@ -1081,12 +1358,27 @@ object MemberCommands { return true } - suspend fun autoproxy(ctx: DiscordContext, member: MemberRecord, enabled: Boolean?): Boolean { + suspend fun autoproxy( + ctx: DiscordContext, + system: SystemRecord, + member: MemberRecord, + enabled: Boolean? + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + enabled ?: run { ctx.respondSuccess("AutoProxy for ${member.showDisplayName()} is set to ${if (member.autoProxy) "on" else "off"}!") return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + database.updateMember(member.apply { autoProxy = enabled }) ctx.respondSuccess("${if (enabled) "Enabled" else "Disabled"} front & latch autproxy for ${member.showDisplayName()}!") return true @@ -1106,7 +1398,17 @@ object MemberCommands { return Pair(prefix, suffix) } - suspend fun proxy(ctx: DiscordContext, member: MemberRecord, proxy: Pair?): Boolean { + suspend fun proxy( + ctx: DiscordContext, + system: SystemRecord, + member: MemberRecord, + proxy: Pair? + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + proxy ?: run { ctx.respondEmbed { member(member, ctx.getGuild()?.id?.value ?: 0UL) @@ -1126,6 +1428,11 @@ object MemberCommands { return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + database.createProxyTag(member.systemId, member.id, proxy.first, proxy.second) ?: run { ctx.respondFailure("Proxy tag already exists in this system.") return false @@ -1134,8 +1441,25 @@ object MemberCommands { return true } - suspend fun pronouns(ctx: DiscordContext, member: MemberRecord, pronouns: String?, raw: Boolean, clear: Boolean): Boolean { + suspend fun pronouns( + ctx: DiscordContext, + system: SystemRecord, + member: MemberRecord, + pronouns: String?, + raw: Boolean, + clear: Boolean + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.pronouns = null database.updateMember(member) ctx.respondSuccess("Member's pronouns cleared!") @@ -1155,26 +1479,57 @@ object MemberCommands { return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.pronouns = pronouns database.updateMember(member) ctx.respondSuccess("Member's pronouns are now $pronouns!") return true } - suspend fun color(ctx: DiscordContext, member: MemberRecord, color: Int?): Boolean { + suspend fun color(ctx: DiscordContext, system: SystemRecord, member: MemberRecord, color: Int?): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + color ?: run { ctx.respondSuccess("Member's color is `${member.color.fromColor()}`") return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.color = color database.updateMember(member) ctx.respondSuccess("Member's color is now `${color.fromColor()}!") return true } - suspend fun birthday(ctx: DiscordContext, member: MemberRecord, birthday: LocalDate?, clear: Boolean): Boolean { + suspend fun birthday( + ctx: DiscordContext, + system: SystemRecord, + member: MemberRecord, + birthday: LocalDate?, + clear: Boolean + ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.birthday = null database.updateMember(member) ctx.respondSuccess("Member's birthday cleared!") @@ -1190,13 +1545,27 @@ object MemberCommands { return true } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member.birthday = birthday database.updateMember(member) ctx.respondSuccess("Member's birthday is now $birthday!") return true } - suspend fun delete(ctx: DiscordContext, member: MemberRecord?): Boolean { + suspend fun delete(ctx: DiscordContext, system: SystemRecord, member: MemberRecord?): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + member ?: run { ctx.respondFailure("Make sure to provide the name of the member to delete!") return false @@ -1217,6 +1586,15 @@ object MemberCommands { } suspend fun create(ctx: DiscordContext, system: SystemRecord, name: String?, ): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (!system.canEditMembers(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + name ?: run { ctx.respondFailure("Make sure to provide a name for the new member!") return false diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 5d3cbf2e..2ab31d68 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -8,6 +8,7 @@ package dev.proxyfox.bot.command +import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake import dev.kord.core.Kord @@ -21,6 +22,8 @@ import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.guild import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.command.node.attachment +import dev.proxyfox.bot.prompts.Button +import dev.proxyfox.bot.prompts.TimedYesNoPrompt import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil import dev.proxyfox.command.node.builtin.* @@ -33,6 +36,7 @@ import dev.proxyfox.database.etc.importer.import import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.ServerSettingsRecord +import dev.proxyfox.database.records.misc.TrustLevel import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord import kotlinx.coroutines.Dispatchers @@ -666,6 +670,57 @@ object MiscCommands { token(this, system!!) } } + + Commands.parser.literal("trust") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + respondFailure("Please provide a user to perform this action on") + false + } + int("user") { getId -> + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + trust(this, system!!, getId(), null) + } + literal("none", "remove", "clear") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + trust(this, system!!, getId(), TrustLevel.NONE) + } + } + literal("access", "see", "view") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + trust(this, system!!, getId(), TrustLevel.ACCESS) + } + } + literal("member", "m") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + trust(this, system!!, getId(), TrustLevel.MEMBER) + } + } + literal("switch", "sw") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + trust(this, system!!, getId(), TrustLevel.SWITCH) + } + } + literal("full", "all", "everything") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + trust(this, system!!, getId(), TrustLevel.FULL) + } + } + } + } } private suspend fun getFox(ctx: DiscordContext): Boolean { @@ -678,12 +733,37 @@ object MiscCommands { return true } - private suspend fun token(ctx: DiscordContext, system: SystemRecord): Boolean { + private suspend fun trust( + ctx: DiscordContext, + system: SystemRecord, + user: ULong, + trustLevel: TrustLevel? + ): Boolean { + trustLevel ?: run { + val trust = system.trust[user] ?: TrustLevel.NONE + ctx.respondPlain("User's trust level is currently `${trust.name}`") + return true + } + TimedYesNoPrompt.build( + runner = ctx.getUser()!!.id, + channel = ctx.getChannel(), + message = "Are you sure you want to trust this user with level `${trustLevel.name}`?\nThis can be changed at any time.", + yes = Button("Trust user", Button.check, ButtonStyle.Primary) { + system.trust[user] = trustLevel + database.updateSystem(system) + content = "User trust updated." + } + ) return true } + private suspend fun token(ctx: DiscordContext, system: SystemRecord): Boolean { + ctx.respondWarning("Not yet implemented") + return true + } + private suspend fun debug(ctx: DiscordContext): Boolean { val shardid = ctx.getGuild()?.id?.value?.toShard() ?: 0 ctx.respondEmbed { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index f961b724..7d7bc1ef 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -20,6 +20,7 @@ import dev.proxyfox.bot.parseDuration import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.Pager import dev.proxyfox.bot.prompts.TimedYesNoPrompt +import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.builtin.greedy import dev.proxyfox.command.node.builtin.literal import dev.proxyfox.command.node.builtin.stringList @@ -45,8 +46,15 @@ object SwitchCommands { string("members", "The members to use, comma separated") { required = true } + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val members = value.interaction.command.strings["members"]!!.split(",").toTypedArray() members.trimEach() @@ -54,15 +62,29 @@ object SwitchCommands { } } subCommand("out", "Marks that no-one's fronting") { + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false out(this, system!!) } } subCommand("delete", "Deletes the latest switch") { + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val switch = database.fetchLatestSwitch(system!!.id) if (!checkSwitch(this, switch)) return@runs false @@ -72,8 +94,15 @@ object SwitchCommands { } subCommand("move", "Moves the latest switch") { name("time") + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val switch = database.fetchLatestSwitch(system!!.id) if (!checkSwitch(this, switch)) return@runs false @@ -83,8 +112,15 @@ object SwitchCommands { } } subCommand("list", "Lists your switches") { + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false list(this, system!!) } @@ -92,23 +128,24 @@ object SwitchCommands { } } - suspend fun register() { - printStep("Registering switch commands", 2) - Commands.parser.literal("switch", "sw") { + suspend fun > NodeHolder.registerSwitchCommands(getSys: suspend DiscordContext.() -> SystemRecord?) { + literal("switch", "sw") { runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false respondFailure("Please provide a switch subcommand.") false } literal("out", "o") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false out(this, system!!) } } literal("delete", "del", "remove", "rem") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val switch = database.fetchLatestSwitch(system!!.id) if (!checkSwitch(this, switch)) return@runs false @@ -118,7 +155,7 @@ object SwitchCommands { } literal("move","mv","m") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val switch = database.fetchLatestSwitch(system!!.id) if (!checkSwitch(this, switch)) return@runs false @@ -127,7 +164,7 @@ object SwitchCommands { } greedy("time") { getTime -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false val switch = database.fetchLatestSwitch(system!!.id) if (!checkSwitch(this, switch)) return@runs false @@ -138,14 +175,14 @@ object SwitchCommands { } literal("list", "l") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false list(this, system!!) } } stringList("members") { getMembers -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false switch(this, system!!, getMembers().toTypedArray()) } @@ -153,13 +190,38 @@ object SwitchCommands { } } + suspend fun register() { + printStep("Registering switch commands", 2) + Commands.parser.registerSwitchCommands { + database.fetchSystemFromUser(getUser()) + } + } + private suspend fun out(ctx: DiscordContext, system: SystemRecord): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (!system.canEditSwitches(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + database.createSwitch(system.id, listOf()) ctx.respondSuccess("Switch registered. Take care!") return true } private suspend fun move(ctx: DiscordContext, system: SystemRecord, switch: SystemSwitchRecord, oldSwitch: SystemSwitchRecord?, time: String?): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (!system.canEditSwitches(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + time ?: run { ctx.respondFailure("Please provide a time to move the switch back") return false @@ -199,6 +261,15 @@ object SwitchCommands { } private suspend fun delete(ctx: DiscordContext, system: SystemRecord, switch: SystemSwitchRecord, oldSwitch: SystemSwitchRecord?): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (!system.canEditSwitches(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + val epoch = switch.timestamp.epochSeconds TimedYesNoPrompt.build( @@ -218,6 +289,11 @@ object SwitchCommands { } private suspend fun list(ctx: DiscordContext, system: SystemRecord): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + // We know the system exists here, will be non-null val switches = database.fetchSortedSwitchesFromSystem(system.id)!! @@ -229,6 +305,15 @@ object SwitchCommands { } private suspend fun switch(ctx: DiscordContext, system: SystemRecord, members: Array): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (!system.canEditSwitches(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + val membersOut = ArrayList() var memberString = "" members.forEach { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index e3a6761b..07337694 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -13,6 +13,8 @@ import dev.kord.core.Kord import dev.kord.rest.NamedFile import dev.kord.rest.builder.interaction.SubCommandBuilder import dev.kord.rest.builder.interaction.subCommand +import dev.proxyfox.bot.command.MemberCommands.registerBaseMemberCommands +import dev.proxyfox.bot.command.SwitchCommands.registerSwitchCommands import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.runs @@ -47,9 +49,16 @@ object SystemCommands { suspend fun Kord.registerSystemCommands() { createGlobalChatInputCommand("system", "Manage or create a system!") { - subCommand("fetch", "Fetch your system card!") { + subCommand("fetch", "Fetch a system card!") { + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false access(this, system!!) } @@ -75,8 +84,15 @@ object SystemCommands { name(required = false) raw() clear() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val name = value.interaction.command.strings["name"] val raw = value.interaction.command.booleans["raw"] ?: false @@ -85,10 +101,17 @@ object SystemCommands { } } subCommand("list", "List your system members") { + system() bool("by-message", "Whether to sort by message count") bool("verbose", "Whether to display information verbosely") runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val byMessage = value.interaction.command.booleans["by-message"] ?: false val verbose = value.interaction.command.booleans["verbose"] ?: false @@ -97,8 +120,15 @@ object SystemCommands { } access("system", "color") { name("color", required = false) + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val color = value.interaction.command.strings["color"] @@ -109,8 +139,15 @@ object SystemCommands { name("pronouns", required = false) raw() clear() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val pronouns = value.interaction.command.strings["pronouns"] val raw = value.interaction.command.booleans["raw"] ?: false @@ -119,11 +156,18 @@ object SystemCommands { } } access("system", "description") { + system() name("description", required = false) raw() clear() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val desc = value.interaction.command.strings["description"] val raw = value.interaction.command.booleans["raw"] ?: false @@ -135,8 +179,15 @@ object SystemCommands { access("system", "avatar") { avatar() clear() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val avatar = value.interaction.command.attachments["avatar"]?.data?.url val clear = value.interaction.command.booleans["clear"] ?: false @@ -148,8 +199,15 @@ object SystemCommands { name("tag", required = false) raw() clear() + system() runs { - val system = database.fetchSystemFromUser(getUser()) + val id = value.interaction.command.strings["system"] + val system = + if (id == null) + database.fetchSystemFromUser(getUser()) + else + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) if (!checkSystem(this, system)) return@runs false val tag = value.interaction.command.strings["tag"] val raw = value.interaction.command.booleans["raw"] ?: false @@ -293,27 +351,27 @@ object SystemCommands { } literal("description", "desc", "d") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false description(this, system!!, null, false, false) } unixLiteral("raw") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false description(this, system!!, null, true, false) } } unixLiteral("clear", "remove") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false description(this, system!!, null, false, true) } } greedy("description") { getDesc -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false description(this, system!!, getDesc(), false, false) } @@ -321,27 +379,27 @@ object SystemCommands { } literal("avatar", "pfp") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false avatar(this, system!!, null, false) } unixLiteral("clear", "remove") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false avatar(this, system!!, null, true) } } attachment("avatar") { getAvatar -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false avatar(this, system!!, getAvatar().url, false) } } string("avatar") { getAvatar -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false avatar(this, system!!, getAvatar(), false) } @@ -349,38 +407,45 @@ object SystemCommands { } literal("tag", "t") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false tag(this, system!!, null, false, false) } unixLiteral("raw") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false tag(this, system!!, null, true, false) } } unixLiteral("clear", "remove") { runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false tag(this, system!!, null, false, true) } } greedy("description") { getTag -> runs { - val system = database.fetchSystemFromUser(getUser()) + val system = getSys() if (!checkSystem(this, system)) return@runs false tag(this, system!!, getTag(), false, false) } } } + + registerBaseMemberCommands(getSys) + registerSwitchCommands(getSys) } } } private suspend fun access(ctx: DiscordContext, system: SystemRecord): Boolean { - val members = database.fetchTotalMembersFromUser(ctx.getUser()) + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + val members = database.fetchTotalMembersFromSystem(system.id) ctx.respondEmbed { title = system.name ?: system.id color = system.color.kordColor() @@ -424,13 +489,23 @@ object SystemCommands { val system = database.getOrCreateSystem(ctx.getUser()!!) system.name = name database.updateSystem(system) - val add = if (name != null) "with name ${name}" else "" - ctx.respondSuccess("System created $add! See `pf>help` or `/info help` for how to set up your system further.") + val add = if (name != null) " with name $name" else "" + ctx.respondSuccess("System created$add! See `pf>help` or `/info help` for how to set up your system further.") return true } private suspend fun name(ctx: DiscordContext, system: SystemRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + system.name = null database.updateSystem(system) ctx.respondSuccess("System name cleared!") @@ -447,6 +522,11 @@ object SystemCommands { else ctx.respondSuccess("System's name is ${system.name}") } + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + system.name = name database.updateSystem(system) ctx.respondSuccess("System name updated to ${system.name}!") @@ -454,6 +534,10 @@ object SystemCommands { } private suspend fun list(ctx: DiscordContext, system: SystemRecord, byMessage: Boolean, verbose: Boolean): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } // TODO: List by message if (verbose) { @@ -488,11 +572,21 @@ object SystemCommands { } suspend fun color(ctx: DiscordContext, system: SystemRecord, color: Int?): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + color ?: run { ctx.respondSuccess("Member's color is `${system.color.fromColor()}`") return true } + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + system.color = color database.updateSystem(system) ctx.respondSuccess("Member's color is now `${color.fromColor()}!") @@ -500,7 +594,17 @@ object SystemCommands { } private suspend fun pronouns(ctx: DiscordContext, system: SystemRecord, pronouns: String?, raw: Boolean, clear: Boolean): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + system.pronouns = null database.updateSystem(system) ctx.respondSuccess("System pronouns cleared!") @@ -522,6 +626,11 @@ object SystemCommands { return true } + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + system.pronouns = pronouns database.updateSystem(system) ctx.respondSuccess("System pronouns updated to $pronouns!") @@ -529,7 +638,17 @@ object SystemCommands { } suspend fun description(ctx: DiscordContext, system: SystemRecord, description: String?, raw: Boolean, clear: Boolean): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + system.description = null database.updateSystem(system) ctx.respondSuccess("System's description cleared!") @@ -549,6 +668,11 @@ object SystemCommands { return true } + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + system.description = description database.updateSystem(system) ctx.respondSuccess("System description updated!") @@ -557,7 +681,17 @@ object SystemCommands { } suspend fun avatar(ctx: DiscordContext, system: SystemRecord, avatar: String?, clear: Boolean): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + system.avatarUrl = null database.updateSystem(system) ctx.respondSuccess("System's avatar cleared!") @@ -577,6 +711,11 @@ object SystemCommands { return true } + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + system.avatarUrl = avatar database.updateSystem(system) ctx.respondSuccess("Member's avatar updated!") @@ -585,7 +724,16 @@ object SystemCommands { } private suspend fun tag(ctx: DiscordContext, system: SystemRecord, tag: String?, raw: Boolean, clear: Boolean): Boolean { + if (!system.canAccess(ctx.getUser()!!.id.value)) { + // Force the bot to treat the system as nonexistent + return checkSystem(ctx, null) + } + if (clear) { + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } system.tag = null database.updateSystem(system) ctx.respondSuccess("System tag cleared!") @@ -608,6 +756,11 @@ object SystemCommands { return true } + if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { + ctx.respondFailure("You don't have access to edit this information.") + return false + } + system.tag = tag database.updateSystem(system) ctx.respondSuccess("System tag updated to $tag!") diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index 161cece8..0bb61ca0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -14,10 +14,8 @@ import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.entity.* import dev.kord.rest.NamedFile import dev.kord.rest.builder.message.EmbedBuilder -import dev.proxyfox.bot.kord import dev.proxyfox.command.CommandContext import dev.proxyfox.command.NodeActionParam -import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.int import dev.proxyfox.command.node.builtin.string @@ -52,7 +50,7 @@ fun > CommandNode.runs(action: suspend DiscordCont executes(action as suspend CommandContext.() -> Boolean) } -suspend fun> CommandNode.guild(action: NodeActionParam) { +suspend fun > CommandNode.guild(action: NodeActionParam) { action { val ctx = this as? DiscordContext ?: return@action null ctx.getGuild()?.id @@ -64,15 +62,22 @@ suspend fun> CommandNode.guild(action: NodeActionP } } -suspend fun> CommandNode.system(action: NodeActionParam) { +//suspend fun > CommandNode.id(name: String, action: NodeActionParam) { +// string(name) { +// +// } +//} + +suspend fun > CommandNode.system(action: NodeActionParam) { action { val ctx = this as? DiscordContext ?: return@action null database.fetchSystemFromUser(ctx.getUser()) } - // TODO: Check trust string("sysid") { action { - null + val id = it() + database.fetchSystemFromId(id) + ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) } } } \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index e7c4c0c9..7da35750 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -49,4 +49,30 @@ open class SystemRecord : MongoRecord { var trust: HashMap = HashMap() val showName get() = name?.let { "$it [`$id`]" } ?: "`$id`" + + fun canAccess(user: ULong): Boolean { + if (users.contains(user)) return true + val trust = trust[user] ?: return false + return trust != TrustLevel.NONE + } + + fun canEditSwitches(user: ULong): Boolean { + if (users.contains(user)) return true + val trust = trust[user] ?: return false + if (trust == TrustLevel.SWITCH) return true + return trust == TrustLevel.FULL + } + + fun canEditMembers(user: ULong): Boolean { + if (users.contains(user)) return true + val trust = trust[user] ?: return false + if (trust == TrustLevel.MEMBER) return true + return trust == TrustLevel.FULL + } + + fun hasFullAccess(user: ULong): Boolean { + if (users.contains(user)) return true + val trust = trust[user] ?: return false + return trust == TrustLevel.FULL + } } From 3b95c4abbc9c73d7f548900d2570aaebe219a16a Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 11:28:00 -0600 Subject: [PATCH 071/137] Reduce boilerplate across code --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 42 +--- .../dev/proxyfox/bot/command/Commands.kt | 14 ++ .../proxyfox/bot/command/MemberCommands.kt | 198 ++---------------- .../proxyfox/bot/command/SwitchCommands.kt | 56 +---- .../proxyfox/bot/command/SystemCommands.kt | 102 +-------- .../bot/command/context/DiscordContext.kt | 10 +- 6 files changed, 57 insertions(+), 365 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index ce813e72..d502244e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -15,7 +15,6 @@ import dev.kord.core.Kord import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.createMessage -import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.builder.kord.KordBuilder import dev.kord.core.entity.Member import dev.kord.core.entity.channel.Channel @@ -218,39 +217,12 @@ suspend fun updatePresence() { } } -suspend fun handleError(err: Throwable, interaction: ChatInputCommandInteractionCreateEvent) { - // Catch any errors and log them - val timestamp = System.currentTimeMillis() - // Let the logger unwind the stacktrace. - logger.warn(timestamp.toString(), err) - // Do not leak webhook URL nor token in output. - // Note: The token here is a generic regex that only matches by the bot's - // ID and will make no attempt to verify it's the real one, purely for guarding the - val reason = err.message?.replace(webhook, "[WEBHOOK]")?.replace(token, "[TOKEN]") - var cause = "" - err.stackTrace.forEach { - if (it.className.startsWith("dev.proxyfox")) - cause += " at $it\n" - } - interaction.interaction.respondEphemeral { - content = - "An unexpected error occurred.\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" - } - // if (err is DebugException) return - if (errorChannel == null && errorChannelId != null) - errorChannel = kord.getChannel(errorChannelId) as TextChannel - if (errorChannel != null) { - // Prevent the log channel from also showing tokens, should it be public in any manner. - cause = err.stackTraceToString().replace(webhook, "[WEBHOOK]").replace(token, "[TOKEN]") +suspend fun handleError(err: Throwable, interaction: ChatInputCommandInteractionCreateEvent) = + handleError(err, interaction.interaction.channel) - errorChannel!!.createMessage { - content = "`$timestamp`" - addFile("exception.log", ChannelProvider { cause.byteInputStream().toByteReadChannel() }) - } - } -} +suspend fun handleError(err: Throwable, message: MessageBehavior) = handleError(err, message.channel) -suspend fun handleError(err: Throwable, message: MessageBehavior) { +suspend fun handleError(err: Throwable, channel: MessageChannelBehavior) { // Catch any errors and log them val timestamp = System.currentTimeMillis() // Let the logger unwind the stacktrace. @@ -264,15 +236,15 @@ suspend fun handleError(err: Throwable, message: MessageBehavior) { if (it.className.startsWith("dev.proxyfox")) cause += " at $it\n" } - message.channel.createMessage( + channel.createMessage( "An unexpected error occurred.\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" ) - // if (err is DebugException) return + // Relay to channel if (errorChannel == null && errorChannelId != null) errorChannel = kord.getChannel(errorChannelId) as TextChannel if (errorChannel != null) { // Prevent the log channel from also showing tokens, should it be public in any manner. - cause = err.stackTraceToString().replace(webhook, "[WEBHOOK]").replace(token, "[TOKEN]") + val cause = err.stackTraceToString().replace(webhook, "[WEBHOOK]").replace(token, "[TOKEN]") errorChannel!!.createMessage { content = "`$timestamp`" diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 7ba836c0..4d34d22e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -10,8 +10,10 @@ package dev.proxyfox.bot.command import dev.kord.rest.builder.interaction.* import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.command.CommandParser import dev.proxyfox.common.printStep +import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemSwitchRecord @@ -90,3 +92,15 @@ suspend fun checkSwitch(ctx: DiscordContext, switch: SystemSwitchRecord?) } return true } + +suspend fun InteractionCommandContext.getSystem(): SystemRecord? { + val id = value.interaction.command.strings["system"] + return if (id == null) + database.fetchSystemFromUser(getUser()) + else + (database.fetchSystemFromId(id) ?: database.fetchSystemFromUser(id.toULongOrNull() ?: return null))?.let { + if (!it.canAccess(getUser().id.value)) + return null + return it + } +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 5232d7f2..0880861e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -57,13 +57,7 @@ object MemberCommands { name() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val name = value.interaction.command.strings["name"]!! @@ -74,13 +68,7 @@ object MemberCommands { member() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -92,13 +80,7 @@ object MemberCommands { member() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -112,13 +94,7 @@ object MemberCommands { raw() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -135,13 +111,7 @@ object MemberCommands { clear() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -160,13 +130,7 @@ object MemberCommands { clear() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -195,13 +159,7 @@ object MemberCommands { name("description", required = false) system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -218,13 +176,7 @@ object MemberCommands { clear() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -240,13 +192,7 @@ object MemberCommands { guild() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -275,13 +221,7 @@ object MemberCommands { clear() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -297,13 +237,7 @@ object MemberCommands { name("color", required = false) system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -318,13 +252,7 @@ object MemberCommands { clear() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -340,13 +268,7 @@ object MemberCommands { name("suffix", required = false) system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -363,13 +285,7 @@ object MemberCommands { name("suffix", required = false) system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -387,13 +303,7 @@ object MemberCommands { bool("value", "The value to set") system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false @@ -954,21 +864,11 @@ object MemberCommands { } suspend fun empty(ctx: DiscordContext, system: SystemRecord): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - ctx.respondWarning("Make sure to provide a member command!") return false } suspend fun access(ctx: DiscordContext, system: SystemRecord, member: MemberRecord): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - val guild = ctx.getGuild() val settings = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) ctx.respondEmbed { @@ -1041,11 +941,6 @@ object MemberCommands { name: String?, raw: Boolean ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - name ?: run { if (raw) ctx.respondPlain("`${member.name}`") @@ -1075,11 +970,6 @@ object MemberCommands { raw: Boolean, clear: Boolean ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.canEditMembers(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -1128,11 +1018,6 @@ object MemberCommands { raw: Boolean, clear: Boolean ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.canEditMembers(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -1181,11 +1066,6 @@ object MemberCommands { raw: Boolean, clear: Boolean ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.canEditMembers(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -1230,11 +1110,6 @@ object MemberCommands { avatar: String?, clear: Boolean ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.canEditMembers(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -1279,11 +1154,6 @@ object MemberCommands { avatar: String?, clear: Boolean ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.canEditMembers(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -1327,11 +1197,6 @@ object MemberCommands { exists: Boolean, proxy: MemberProxyTagRecord? ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (!system.canEditMembers(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") return false @@ -1364,11 +1229,6 @@ object MemberCommands { member: MemberRecord, enabled: Boolean? ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - enabled ?: run { ctx.respondSuccess("AutoProxy for ${member.showDisplayName()} is set to ${if (member.autoProxy) "on" else "off"}!") return true @@ -1404,11 +1264,6 @@ object MemberCommands { member: MemberRecord, proxy: Pair? ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - proxy ?: run { ctx.respondEmbed { member(member, ctx.getGuild()?.id?.value ?: 0UL) @@ -1449,11 +1304,6 @@ object MemberCommands { raw: Boolean, clear: Boolean ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.canEditMembers(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -1491,11 +1341,6 @@ object MemberCommands { } suspend fun color(ctx: DiscordContext, system: SystemRecord, member: MemberRecord, color: Int?): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - color ?: run { ctx.respondSuccess("Member's color is `${member.color.fromColor()}`") return true @@ -1519,11 +1364,6 @@ object MemberCommands { birthday: LocalDate?, clear: Boolean ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.canEditMembers(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -1557,10 +1397,6 @@ object MemberCommands { } suspend fun delete(ctx: DiscordContext, system: SystemRecord, member: MemberRecord?): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } if (!system.canEditMembers(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") return false @@ -1586,10 +1422,6 @@ object MemberCommands { } suspend fun create(ctx: DiscordContext, system: SystemRecord, name: String?, ): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } if (!system.canEditMembers(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") return false diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 7d7bc1ef..39120514 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -48,13 +48,7 @@ object SwitchCommands { } system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val members = value.interaction.command.strings["members"]!!.split(",").toTypedArray() members.trimEach() @@ -64,13 +58,7 @@ object SwitchCommands { subCommand("out", "Marks that no-one's fronting") { system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false out(this, system!!) } @@ -78,13 +66,7 @@ object SwitchCommands { subCommand("delete", "Deletes the latest switch") { system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val switch = database.fetchLatestSwitch(system!!.id) if (!checkSwitch(this, switch)) return@runs false @@ -96,13 +78,7 @@ object SwitchCommands { name("time") system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val switch = database.fetchLatestSwitch(system!!.id) if (!checkSwitch(this, switch)) return@runs false @@ -114,13 +90,7 @@ object SwitchCommands { subCommand("list", "Lists your switches") { system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false list(this, system!!) } @@ -198,10 +168,6 @@ object SwitchCommands { } private suspend fun out(ctx: DiscordContext, system: SystemRecord): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } if (!system.canEditSwitches(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") return false @@ -213,10 +179,6 @@ object SwitchCommands { } private suspend fun move(ctx: DiscordContext, system: SystemRecord, switch: SystemSwitchRecord, oldSwitch: SystemSwitchRecord?, time: String?): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } if (!system.canEditSwitches(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") return false @@ -261,10 +223,6 @@ object SwitchCommands { } private suspend fun delete(ctx: DiscordContext, system: SystemRecord, switch: SystemSwitchRecord, oldSwitch: SystemSwitchRecord?): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } if (!system.canEditSwitches(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") return false @@ -305,10 +263,6 @@ object SwitchCommands { } private suspend fun switch(ctx: DiscordContext, system: SystemRecord, members: Array): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } if (!system.canEditSwitches(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") return false diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 07337694..349995b2 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -52,13 +52,7 @@ object SystemCommands { subCommand("fetch", "Fetch a system card!") { system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false access(this, system!!) } @@ -86,13 +80,7 @@ object SystemCommands { clear() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val name = value.interaction.command.strings["name"] val raw = value.interaction.command.booleans["raw"] ?: false @@ -105,13 +93,7 @@ object SystemCommands { bool("by-message", "Whether to sort by message count") bool("verbose", "Whether to display information verbosely") runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val byMessage = value.interaction.command.booleans["by-message"] ?: false val verbose = value.interaction.command.booleans["verbose"] ?: false @@ -122,13 +104,7 @@ object SystemCommands { name("color", required = false) system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val color = value.interaction.command.strings["color"] @@ -141,13 +117,7 @@ object SystemCommands { clear() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val pronouns = value.interaction.command.strings["pronouns"] val raw = value.interaction.command.booleans["raw"] ?: false @@ -161,13 +131,7 @@ object SystemCommands { raw() clear() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val desc = value.interaction.command.strings["description"] val raw = value.interaction.command.booleans["raw"] ?: false @@ -181,13 +145,7 @@ object SystemCommands { clear() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val avatar = value.interaction.command.attachments["avatar"]?.data?.url val clear = value.interaction.command.booleans["clear"] ?: false @@ -201,13 +159,7 @@ object SystemCommands { clear() system() runs { - val id = value.interaction.command.strings["system"] - val system = - if (id == null) - database.fetchSystemFromUser(getUser()) - else - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + val system = getSystem() if (!checkSystem(this, system)) return@runs false val tag = value.interaction.command.strings["tag"] val raw = value.interaction.command.booleans["raw"] ?: false @@ -441,10 +393,6 @@ object SystemCommands { } private suspend fun access(ctx: DiscordContext, system: SystemRecord): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } val members = database.fetchTotalMembersFromSystem(system.id) ctx.respondEmbed { title = system.name ?: system.id @@ -495,11 +443,6 @@ object SystemCommands { } private suspend fun name(ctx: DiscordContext, system: SystemRecord, name: String?, raw: Boolean, clear: Boolean): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -534,10 +477,6 @@ object SystemCommands { } private suspend fun list(ctx: DiscordContext, system: SystemRecord, byMessage: Boolean, verbose: Boolean): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } // TODO: List by message if (verbose) { @@ -572,11 +511,6 @@ object SystemCommands { } suspend fun color(ctx: DiscordContext, system: SystemRecord, color: Int?): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - color ?: run { ctx.respondSuccess("Member's color is `${system.color.fromColor()}`") return true @@ -594,11 +528,6 @@ object SystemCommands { } private suspend fun pronouns(ctx: DiscordContext, system: SystemRecord, pronouns: String?, raw: Boolean, clear: Boolean): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -638,11 +567,6 @@ object SystemCommands { } suspend fun description(ctx: DiscordContext, system: SystemRecord, description: String?, raw: Boolean, clear: Boolean): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -681,11 +605,6 @@ object SystemCommands { } suspend fun avatar(ctx: DiscordContext, system: SystemRecord, avatar: String?, clear: Boolean): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") @@ -724,11 +643,6 @@ object SystemCommands { } private suspend fun tag(ctx: DiscordContext, system: SystemRecord, tag: String?, raw: Boolean, clear: Boolean): Boolean { - if (!system.canAccess(ctx.getUser()!!.id.value)) { - // Force the bot to treat the system as nonexistent - return checkSystem(ctx, null) - } - if (clear) { if (!system.hasFullAccess(ctx.getUser()!!.id.value)) { ctx.respondFailure("You don't have access to edit this information.") diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index 0bb61ca0..db462ec7 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -47,6 +47,7 @@ abstract class DiscordContext(override val value: T) : CommandContext() { // Get a DiscordContext. fun > CommandNode.runs(action: suspend DiscordContext.() -> Boolean) { + @Suppress("UNCHECKED_CAST") executes(action as suspend CommandContext.() -> Boolean) } @@ -76,8 +77,13 @@ suspend fun > CommandNode.system(action: NodeActi string("sysid") { action { val id = it() - database.fetchSystemFromId(id) - ?: database.fetchSystemFromUser(id.toULongOrNull() ?: 0UL) + (database.fetchSystemFromId(id) ?: database.fetchSystemFromUser( + id.toULongOrNull() ?: return@action null + ))?.let { + if (!it.canAccess((this@action as DiscordContext).getUser()!!.id.value)) + return@action null + return@action it + } } } } \ No newline at end of file From 484f4d4edb72ea270e73cfee6ebda0131f7a57b9 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 11:45:02 -0600 Subject: [PATCH 072/137] h --- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index d502244e..baa28939 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -240,6 +240,7 @@ suspend fun handleError(err: Throwable, channel: MessageChannelBehavior) { "An unexpected error occurred.\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" ) // Relay to channel + // if (err is DebugException) return if (errorChannel == null && errorChannelId != null) errorChannel = kord.getChannel(errorChannelId) as TextChannel if (errorChannel != null) { From 756f54531e982dffb7ddd8a3ffd445e77772d2a6 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 12:03:08 -0600 Subject: [PATCH 073/137] Misc code cleanups and deduplication --- .../dev/proxyfox/bot/command/MiscCommands.kt | 58 ++++++------------- .../bot/command/context/DiscordContext.kt | 28 +++++++-- .../command/context/DiscordMessageContext.kt | 10 +--- .../context/InteractionCommandContext.kt | 9 --- 4 files changed, 42 insertions(+), 63 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 2ab31d68..47f83499 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -17,10 +17,7 @@ import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior import dev.kord.rest.NamedFile import dev.kord.rest.builder.interaction.* import dev.proxyfox.bot.* -import dev.proxyfox.bot.command.context.DiscordContext -import dev.proxyfox.bot.command.context.InteractionCommandContext -import dev.proxyfox.bot.command.context.guild -import dev.proxyfox.bot.command.context.runs +import dev.proxyfox.bot.command.context.* import dev.proxyfox.bot.command.node.attachment import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.TimedYesNoPrompt @@ -76,25 +73,25 @@ object MiscCommands { } subCommand("help", "Get help information") { runs("info") { - respondSuccess(getHelp()) + respondSuccess(help) true } } subCommand("about", "Get about information") { runs("info") { - respondSuccess(getExplain()) + respondSuccess(explain) true } } subCommand("source", "Get the source code") { runs("info") { - respondSuccess(getSource()) + respondSuccess(source) true } } subCommand("invite", "Get the bot invite and the support server invite") { runs("info") { - respondSuccess(getSource()) + respondSuccess(invite) true } } @@ -274,33 +271,19 @@ object MiscCommands { } } Commands.parser.literal("time") { - runs { - time(this) - } + runs(::time) } Commands.parser.literal("help") { - runs { - respondSuccess(getHelp()) - true - } + responds(help) } Commands.parser.literal("explain") { - runs { - respondSuccess(getExplain()) - true - } + responds(explain) } Commands.parser.literal("invite") { - runs { - respondSuccess(getInvite()) - true - } + responds(invite) } Commands.parser.literal("source") { - runs { - respondSuccess(getSource()) - true - } + responds(source) } Commands.parser.literal("proxy", "p") { guild { getGuildId -> @@ -625,10 +608,7 @@ object MiscCommands { } Commands.parser.literal("channel", "c") { - runs { - respondFiles("Please provide a channel subcommand") - false - } + responds("Please provide a channel subcommand") literal("proxy", "p") { runs { channelProxy(this, null, null) @@ -652,15 +632,11 @@ object MiscCommands { } Commands.parser.literal("debug") { - runs { - debug(this) - } + runs(::debug) } Commands.parser.literal("fox") { - runs { - getFox(this) - } + runs(::getFox) } Commands.parser.literal("token", "t") { @@ -866,22 +842,22 @@ object MiscCommands { } // TODO: Provide better help - private fun getHelp(): String = + private const val help: String = """To view commands for ProxyFox, visit For quick setup: - pf>system new name - pf>member new John Doe - pf>member "John Doe" proxy j:text""" - private fun getExplain(): String = + private const val explain: String = """ProxyFox is modern Discord bot designed to help systems communicate. It uses discord's webhooks to generate "pseudo-users" which different members of the system can use. Someone will likely be willing to explain further if need be.""" - private fun getInvite(): String = + private val invite: String = """Use to invite ProxyFox to your server! To get support, head on over to https://discord.gg/q3yF8ay9V7""" - private fun getSource(): String = + private const val source: String = "Source code for ProxyFox is available at https://github.com/The-ProxyFox-Group/ProxyFox!" private suspend fun proxy(ctx: DiscordContext, system: SystemRecord, mode: AutoProxyMode?, member: MemberRecord?): Boolean { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index db462ec7..0fb35574 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -11,6 +11,7 @@ package dev.proxyfox.bot.command.context import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.* import dev.kord.rest.NamedFile import dev.kord.rest.builder.message.EmbedBuilder @@ -29,12 +30,21 @@ abstract class DiscordContext(override val value: T) : CommandContext() { abstract suspend fun getGuild(): Guild? abstract suspend fun getUser(): User? abstract suspend fun getMember(): Member? - abstract suspend fun respondFiles(text: String? = null, vararg files: NamedFile): T - abstract suspend fun respondEmbed(private: Boolean = false, text: String? = null, embed: suspend EmbedBuilder.() -> Unit): T + abstract suspend fun respondEmbed( + private: Boolean = false, + text: String? = null, + embed: suspend EmbedBuilder.() -> Unit + ): T + abstract suspend fun tryDeleteTrigger(reason: String? = null) abstract suspend fun optionalSuccess(text: String): T + suspend fun respondFiles(text: String? = null, vararg files: NamedFile): Message = getChannel(true).createMessage { + content = text + this.files.addAll(files) + } + suspend fun hasRequired(permission: Permission): Boolean { val author = getMember() ?: return false return author.getPermissions().contains(permission) @@ -42,7 +52,10 @@ abstract class DiscordContext(override val value: T) : CommandContext() { abstract suspend fun respondPager() - abstract suspend fun getDatabaseMessage(system: SystemRecord?, messageId: Snowflake?): Pair + abstract suspend fun getDatabaseMessage( + system: SystemRecord?, + messageId: Snowflake? + ): Pair } // Get a DiscordContext. @@ -86,4 +99,11 @@ suspend fun > CommandNode.system(action: NodeActi } } } -} \ No newline at end of file +} + +suspend fun > CommandNode.responds(content: String) { + runs { + respondPlain(content) + true + } +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index 4cfea5fa..33a15cee 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -9,12 +9,10 @@ package dev.proxyfox.bot.command.context import dev.kord.common.entity.Snowflake -import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.* -import dev.kord.rest.NamedFile import dev.kord.rest.builder.message.EmbedBuilder -import dev.proxyfox.bot.prompts.TimedYesNoPrompt import dev.proxyfox.command.CommandContext import dev.proxyfox.common.applyAsync import dev.proxyfox.database.database @@ -47,12 +45,6 @@ class DiscordMessageContext(message: Message, override val command: String): Dis return value.getAuthorAsMember() } - override suspend fun respondFiles(text: String?, vararg files: NamedFile): Message { - return getChannel(true).createMessage { - this.files.addAll(files) - } - } - override suspend fun respondEmbed( private: Boolean, text: String?, diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index 3ae48628..aed0d48c 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -11,12 +11,10 @@ package dev.proxyfox.bot.command.context import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.GuildChannelBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior -import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.entity.* import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.rest.NamedFile import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed import dev.proxyfox.command.CommandContext @@ -106,13 +104,6 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : return getGuild()?.getMemberOrNull(getUser().id) } - override suspend fun respondFiles(text: String?, vararg files: NamedFile): ChatInputCommandInteractionCreateEvent { - getChannel(true).createMessage { - this.files.addAll(files) - } - return value - } - override suspend fun respondEmbed( private: Boolean, text: String?, From 405693078381acf0ccd5f3b55a0bac8460655505 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 12:36:23 -0600 Subject: [PATCH 074/137] Clean up code and fix compiler warnings --- gradle/libs.versions.toml | 4 +-- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 2 +- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 3 +- .../proxyfox/bot/command/MemberCommands.kt | 6 ++-- .../dev/proxyfox/bot/command/MiscCommands.kt | 31 ++++++++++++------- .../proxyfox/bot/command/SwitchCommands.kt | 2 -- .../proxyfox/bot/command/SystemCommands.kt | 12 ++++--- .../bot/command/context/DiscordContext.kt | 2 -- .../command/context/DiscordMessageContext.kt | 20 +++--------- .../context/InteractionCommandContext.kt | 19 +++--------- .../dev/proxyfox/bot/webhook/ProxyContext.kt | 15 +++++++-- 11 files changed, 58 insertions(+), 58 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ca5a5bec..cf449086 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,8 +6,8 @@ kotlin = "1.7.21" kotlinx_coroutines = "1.6.4" # ProxyFox Libraries -proxyfox_command = "1.5" -pluralkt = "1.2" +proxyfox_command = "1.6" +pluralkt = "1.3" # Database postgres = "42.3.3" diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index baa28939..5c81e922 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -245,7 +245,7 @@ suspend fun handleError(err: Throwable, channel: MessageChannelBehavior) { errorChannel = kord.getChannel(errorChannelId) as TextChannel if (errorChannel != null) { // Prevent the log channel from also showing tokens, should it be public in any manner. - val cause = err.stackTraceToString().replace(webhook, "[WEBHOOK]").replace(token, "[TOKEN]") + cause = err.stackTraceToString().replace(webhook, "[WEBHOOK]").replace(token, "[TOKEN]") errorChannel!!.createMessage { content = "`$timestamp`" diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index ad1d76b0..77cfa937 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -68,6 +68,7 @@ suspend fun MessageCreateEvent.onMessageCreate() { channel.createMessage("Hi, I'm ProxyFox! My prefix is `pf>`. I also support slash commands!") } else { // Run the command + @Suppress("UNCHECKED_CAST") Commands.parser.parse(DiscordMessageContext(message, contentWithoutRegex) as DiscordContext) } } else if (channel is GuildMessageChannel && channel.selfHasPermissions(Permissions(Permission.ManageWebhooks, Permission.ManageMessages))) { @@ -86,7 +87,7 @@ suspend fun MessageCreateEvent.onMessageCreate() { } suspend fun MessageUpdateEvent.onMessageUpdate() { - val guild = kord.getGuild(new.guildId.value ?: return) ?: return + val guild = kord.getGuildOrNull(new.guildId.value ?: return) ?: return val channel = channel.asChannelOf() val content = new.content.value ?: return val authorRaw = new.author.value ?: return diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 0880861e..08fe2727 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -350,7 +350,7 @@ object MemberCommands { if (!checkSystem(this, system)) return@runs false val member = database.findMember(system!!.id, getMem()) if (!checkMember(this, member)) return@runs false - rename(this, system, member!!, name, false) + rename(this, system, member!!, getName(), false) } } } @@ -816,7 +816,7 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - empty(this, system!!) + empty(this) } string("member") { @@ -863,7 +863,7 @@ object MemberCommands { } } - suspend fun empty(ctx: DiscordContext, system: SystemRecord): Boolean { + suspend fun empty(ctx: DiscordContext): Boolean { ctx.respondWarning("Make sure to provide a member command!") return false } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 47f83499..1e4cd576 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -36,6 +36,8 @@ import dev.proxyfox.database.records.misc.ServerSettingsRecord import dev.proxyfox.database.records.misc.TrustLevel import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord +import io.ktor.client.request.forms.* +import io.ktor.utils.io.jvm.javaio.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull @@ -189,7 +191,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -220,7 +222,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -294,7 +296,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -309,7 +311,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -325,7 +327,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -381,7 +383,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -396,7 +398,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -412,7 +414,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -428,7 +430,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -444,7 +446,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -462,7 +464,7 @@ object MiscCommands { respondFailure("Command not ran in server.") return@runs false } - val guild = kord.getGuild(guildId) ?: run { + val guild = kord.getGuildOrNull(guildId) ?: run { respondFailure("Cannot find server. Am I in it?") return@runs false } @@ -735,6 +737,7 @@ object MiscCommands { return true } + @Suppress("UNUSED_PARAMETER") private suspend fun token(ctx: DiscordContext, system: SystemRecord): Boolean { ctx.respondWarning("Not yet implemented") return true @@ -830,7 +833,11 @@ object MiscCommands { private suspend fun export(ctx: DiscordContext): Boolean { val export = Exporter.export(ctx.getUser()!!.id.value) - ctx.respondFiles(null, NamedFile("system.json", export.byteInputStream())) + val message = ctx.respondFiles( + null, + NamedFile("system.json", ChannelProvider { export.byteInputStream().toByteReadChannel() }) + ) + message.channel.createMessage(message.attachments.elementAt(0).url) ctx.respondSuccess("Check your DMs~") return true } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 39120514..297442fa 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -189,8 +189,6 @@ object SwitchCommands { return false } - val oldSwitch = database.fetchSecondLatestSwitch(system.id) - val either = time.parseDuration() either.right?.let { ctx.respondFailure(it) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 349995b2..04ac9c80 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -71,7 +71,7 @@ object SystemCommands { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - delete(this, system!!) + delete(this) } } access("system", "name") { @@ -205,7 +205,7 @@ object SystemCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - delete(this, system!!) + delete(this) } } system { getSys -> @@ -476,6 +476,7 @@ object SystemCommands { return true } + @Suppress("UNUSED_PARAMETER") private suspend fun list(ctx: DiscordContext, system: SystemRecord, byMessage: Boolean, verbose: Boolean): Boolean { // TODO: List by message @@ -681,7 +682,7 @@ object SystemCommands { return true } - private suspend fun delete(ctx: DiscordContext, system: SystemRecord): Boolean { + private suspend fun delete(ctx: DiscordContext): Boolean { TimedYesNoPrompt.build( runner = ctx.getUser()!!.id, channel = ctx.getChannel(), @@ -689,7 +690,10 @@ object SystemCommands { "The data will be lost forever (A long time!)", yes = Button("Delete system", Button.wastebasket, ButtonStyle.Danger) { val export = Exporter.export(ctx.getUser()!!.id.value) - ctx.respondFiles(null, NamedFile("system.json", ChannelProvider { export.byteInputStream().toByteReadChannel() })) + ctx.respondFiles( + null, + NamedFile("system.json", ChannelProvider { export.byteInputStream().toByteReadChannel() }) + ) database.dropSystem(ctx.getUser()!!) content = "System deleted." }, diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index 0fb35574..ce9737f9 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -50,8 +50,6 @@ abstract class DiscordContext(override val value: T) : CommandContext() { return author.getPermissions().contains(permission) } - abstract suspend fun respondPager() - abstract suspend fun getDatabaseMessage( system: SystemRecord?, messageId: Snowflake? diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index 33a15cee..7b77efed 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -13,7 +13,7 @@ import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.* import dev.kord.rest.builder.message.EmbedBuilder -import dev.proxyfox.command.CommandContext +import dev.proxyfox.command.MenuBuilder import dev.proxyfox.common.applyAsync import dev.proxyfox.database.database import dev.proxyfox.database.records.misc.ProxiedMessageRecord @@ -65,10 +65,6 @@ class DiscordMessageContext(message: Message, override val command: String): Dis return value } - override suspend fun respondPager() { - TODO("Not yet implemented") - } - override suspend fun getDatabaseMessage(system: SystemRecord?, messageId: Snowflake?): Pair { val databaseMessage = if (messageId != null) { database.fetchMessage(messageId) @@ -82,6 +78,10 @@ class DiscordMessageContext(message: Message, override val command: String): Dis return message to databaseMessage } + override suspend fun menu(action: MenuBuilder) { + TODO("Not yet implemented") + } + override suspend fun respondPlain(text: String, private: Boolean): Message { return getChannel(private).createMessage(text) } @@ -98,14 +98,4 @@ class DiscordMessageContext(message: Message, override val command: String): Dis return getChannel(private).createMessage("❌ $text") } - override suspend fun timedYesNoPrompt( - text: String, - yesAction: Pair.() -> Boolean>, - noAction: Pair.() -> Boolean>, - timeoutAction: suspend CommandContext.() -> Boolean, - private: Boolean - ) { - // TODO: Move TimedYesNoPrompt here - } - } \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index aed0d48c..afca8164 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -17,7 +17,7 @@ import dev.kord.core.entity.* import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed -import dev.proxyfox.command.CommandContext +import dev.proxyfox.command.MenuBuilder import dev.proxyfox.database.database import dev.proxyfox.database.records.misc.ProxiedMessageRecord import dev.proxyfox.database.records.system.SystemRecord @@ -27,6 +27,10 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : DiscordContext(value) { override val command: String = "" + override suspend fun menu(action: MenuBuilder) { + TODO("Not yet implemented") + } + @OptIn(ExperimentalStdlibApi::class) override fun getAttachment(): Attachment? { return value.interaction.command.attachments.values.stream().findFirst().getOrNull() @@ -76,15 +80,6 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : return value } - override suspend fun timedYesNoPrompt( - text: String, - yesAction: Pair.() -> Boolean>, - noAction: Pair.() -> Boolean>, - timeoutAction: suspend CommandContext.() -> Boolean, - private: Boolean - ) { - } - override suspend fun getChannel(private: Boolean): MessageChannelBehavior { return if (private) value.interaction.user.getDmChannelOrNull() @@ -131,10 +126,6 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : return value } - override suspend fun respondPager() { - TODO("Not yet implemented") - } - override suspend fun getDatabaseMessage( system: SystemRecord?, messageId: Snowflake? diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt index e7e150f9..a7139040 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt @@ -26,6 +26,7 @@ import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord import io.ktor.client.request.* +import io.ktor.client.request.forms.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.util.* @@ -58,9 +59,19 @@ data class ProxyContext( avatarUrl = resolvedAvatar for (attachment in message.attachments) { val response: HttpResponse = http.get(urlString = attachment.url) { - headers { append(HttpHeaders.UserAgent, "ProxyFox/2.0.0 (+https://github.com/The-ProxyFox-Group/ProxyFox/; +https://proxyfox.dev/)") } + headers { + append( + HttpHeaders.UserAgent, + "ProxyFox/2.0.0 (+https://github.com/The-ProxyFox-Group/ProxyFox/; +https://proxyfox.dev/)" + ) + } } - files.add(NamedFile(attachment.filename, response.content.toInputStream())) + files.add( + NamedFile( + attachment.filename, + ChannelProvider { response.content.toInputStream().toByteReadChannel() } + ) + ) } if (reproxy) { message.embeds.forEach { From 6504d30377f3b6ed7927cec40d37fbee17d3fe82 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 14:58:56 -0600 Subject: [PATCH 075/137] Start implementation of command menus --- gradle/libs.versions.toml | 2 +- .../proxyfox/bot/command/menu/DiscordMenu.kt | 20 +++++++++++++++++++ .../bot/command/menu/DiscordScreen.kt | 13 ++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cf449086..cda656db 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ kotlin = "1.7.21" kotlinx_coroutines = "1.6.4" # ProxyFox Libraries -proxyfox_command = "1.6" +proxyfox_command = "1.7" pluralkt = "1.3" # Database diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt new file mode 100644 index 00000000..469eecf1 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt @@ -0,0 +1,20 @@ +package dev.proxyfox.bot.command.menu + +import dev.kord.core.entity.Message +import dev.proxyfox.command.menu.CommandMenu +import dev.proxyfox.command.menu.CommandScreen +import kotlinx.coroutines.Job + +class DiscordMenu(val message: Message) : CommandMenu() { + private val jobs = arrayListOf() + + override suspend fun close() { + jobs.forEach { + it.cancel() + } + } + + override suspend fun createScreen(name: String): CommandScreen { + return DiscordScreen(name, this) + } +} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt new file mode 100644 index 00000000..99dedc8c --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt @@ -0,0 +1,13 @@ +package dev.proxyfox.bot.command.menu + +import dev.kord.core.entity.Message +import dev.proxyfox.command.menu.CommandScreen + +class DiscordScreen(name: String, private val menu: DiscordMenu) : CommandScreen(name) { + @Suppress("UNUSED") + private val message: Message get() = menu.message + + override suspend fun init() { + + } +} \ No newline at end of file From 42183486ae577b53d4c7eb19ae817e623c024069 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 16:32:43 -0600 Subject: [PATCH 076/137] a --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6a041a5a..33632ad1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ build/ out/ proxyfox.db.properties -.env \ No newline at end of file +.env +.pf-command-lock \ No newline at end of file From a090b2fc5a12970241f1e7fc58a86006f49cfc6c Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 16:37:06 -0600 Subject: [PATCH 077/137] Defer chat input command registry + apply licenses --- .../main/kotlin/dev/proxyfox/api/ApiMain.kt | 2 +- .../kotlin/dev/proxyfox/api/Authentication.kt | 2 +- .../kotlin/dev/proxyfox/api/models/Member.kt | 2 +- .../api/models/MemberGuildSettings.kt | 2 +- .../kotlin/dev/proxyfox/api/models/Message.kt | 2 +- .../dev/proxyfox/api/models/ProxyTag.kt | 2 +- .../kotlin/dev/proxyfox/api/models/Switch.kt | 2 +- .../kotlin/dev/proxyfox/api/models/System.kt | 2 +- .../api/models/SystemGuildSettings.kt | 2 +- .../dev/proxyfox/api/routes/MemberRoutes.kt | 2 +- .../dev/proxyfox/api/routes/MessageRoutes.kt | 2 +- .../dev/proxyfox/api/routes/SwitchRoutes.kt | 2 +- .../dev/proxyfox/api/routes/SystemRoutes.kt | 2 +- .../main/kotlin/dev/proxyfox/bot/BotMain.kt | 5 -- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 42 +++++++++++--- .../dev/proxyfox/bot/command/Commands.kt | 2 +- .../proxyfox/bot/command/MemberCommands.kt | 6 +- .../dev/proxyfox/bot/command/MiscCommands.kt | 9 +-- .../proxyfox/bot/command/SwitchCommands.kt | 6 +- .../proxyfox/bot/command/SystemCommands.kt | 6 +- .../bot/command/context/DiscordContext.kt | 7 +++ .../command/context/DiscordMessageContext.kt | 6 +- .../context/InteractionCommandContext.kt | 8 ++- ...yFoxApplicationCommandModifyStateHolder.kt | 38 ++++++++++++ .../ProxyFoxChatInputCreateBuilderImpl.kt | 58 +++++++++++++++++++ .../proxyfox/bot/command/menu/DiscordMenu.kt | 16 ++++- .../bot/command/menu/DiscordMessageMenu.kt | 36 ++++++++++++ .../bot/command/menu/DiscordScreen.kt | 18 ++++-- .../command/menu/InteractionCommandMenu.kt | 35 +++++++++++ .../kotlin/dev/proxyfox/common/FoxFetch.kt | 2 +- .../main/kotlin/dev/proxyfox/common/Util.kt | 2 +- .../dev/proxyfox/conversion/ConversionMain.kt | 2 +- .../kotlin/dev/proxyfox/database/Database.kt | 2 +- .../dev/proxyfox/database/DatabaseMain.kt | 2 +- .../dev/proxyfox/database/DatabaseUtil.kt | 2 +- .../dev/proxyfox/database/InMemoryDatabase.kt | 2 +- .../dev/proxyfox/database/MongoDatabase.kt | 2 +- .../dev/proxyfox/database/NopDatabase.kt | 2 +- .../dev/proxyfox/database/ProxyDatabase.kt | 2 +- .../kotlin/dev/proxyfox/database/TimeUtil.kt | 2 +- .../database/etc/exporter/Exporter.kt | 2 +- .../database/etc/importer/Importer.kt | 2 +- .../etc/importer/ImporterException.kt | 2 +- .../etc/importer/PluralKitImporter.kt | 2 +- .../etc/importer/TupperBoxImporter.kt | 2 +- .../proxyfox/database/etc/types/PkTypes.kt | 2 +- .../proxyfox/database/etc/types/TbTypes.kt | 2 +- .../database/records/DatabaseException.kt | 2 +- .../proxyfox/database/records/MongoRecord.kt | 2 +- .../database/records/group/GroupRecord.kt | 2 +- .../database/records/group/TagMode.kt | 2 +- .../records/member/MemberProxyTagRecord.kt | 2 +- .../database/records/member/MemberRecord.kt | 2 +- .../member/MemberServerSettingsRecord.kt | 2 +- .../database/records/misc/AutoProxyMode.kt | 2 +- .../records/misc/ChannelSettingsRecord.kt | 2 +- .../records/misc/ProxiedMessageRecord.kt | 2 +- .../records/misc/ServerSettingsRecord.kt | 2 +- .../database/records/misc/TokenRecord.kt | 2 +- .../database/records/misc/TrustLevel.kt | 2 +- .../database/records/misc/UserRecord.kt | 2 +- .../system/SystemChannelSettingsRecord.kt | 2 +- .../database/records/system/SystemRecord.kt | 2 +- .../system/SystemServerSettingsRecord.kt | 2 +- .../records/system/SystemSwitchRecord.kt | 2 +- .../src/test/kotlin/dev/proxyfox/BlackHole.kt | 2 +- .../dev/proxyfox/database/DatabaseTest.kt | 2 +- .../dev/proxyfox/database/DatabaseTestUtil.kt | 2 +- .../dev/proxyfox/database/DatabaseUtilTest.kt | 2 +- .../proxyfox/database/GeneratingIterator.kt | 2 +- .../dev/proxyfox/database/ProxyTagTest.kt | 2 +- .../dev/proxyfox/database/TimeUtilTest.kt | 2 +- .../dev/proxyfox/exporter/ExporterTest.kt | 2 +- .../dev/proxyfox/importer/ImporterTest.kt | 2 +- 74 files changed, 323 insertions(+), 91 deletions(-) create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxApplicationCommandModifyStateHolder.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt index 08d37099..c8b17340 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt index 8ff088fe..72096a60 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt index d8284ba4..321b86f3 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt index f09d37b2..b29456a5 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt index 9cfecd6e..aac287dc 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt index 1613fca9..fc2f71b5 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt index a2fada44..beb5bc06 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt index 7272e0a8..ab90d986 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt index 08bf1d84..fa910824 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt index 8d663b47..09affd0e 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt index 94703368..212f9328 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt index f15b317f..b89876fa 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt index 2d49cca7..26ca2a22 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt index d64a24d3..62c7ff2c 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt @@ -9,8 +9,6 @@ package dev.proxyfox.bot import dev.proxyfox.api.ApiMain -import dev.proxyfox.bot.command.Commands -import dev.proxyfox.bot.md.parseMarkdown import dev.proxyfox.bot.terminal.TerminalCommands import dev.proxyfox.common.printFancy import dev.proxyfox.database.DatabaseMain @@ -27,9 +25,6 @@ object BotMain { printFancy("Initializing ProxyFox") - // Register commands - Commands.register() - // Setup database DatabaseMain.main(findUnixValue(args, "--database=")) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 5c81e922..53813e94 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -30,12 +30,16 @@ import dev.kord.core.on import dev.kord.gateway.Intent import dev.kord.gateway.PrivilegedIntent import dev.kord.gateway.builder.Shards +import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder import dev.kord.rest.builder.message.EmbedBuilder +import dev.kord.rest.json.request.ApplicationCommandCreateRequest import dev.kord.rest.request.KtorRequestException +import dev.proxyfox.bot.command.Commands import dev.proxyfox.bot.command.MemberCommands.registerMemberCommands import dev.proxyfox.bot.command.MiscCommands.registerMiscCommands import dev.proxyfox.bot.command.SwitchCommands.registerSwitchCommands import dev.proxyfox.bot.command.SystemCommands.registerSystemCommands +import dev.proxyfox.bot.command.interaction.ProxyFoxChatInputCreateBuilderImpl import dev.proxyfox.common.* import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberRecord @@ -50,6 +54,7 @@ import kotlinx.coroutines.flow.count import kotlinx.coroutines.flow.fold import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import java.io.File import java.lang.Integer.* import java.util.* import java.util.concurrent.Executors @@ -132,14 +137,9 @@ suspend fun login() { } } - // TODO: Figure out why registerApplicationCommands is so abysmally slow - // For now, launch asynchronously so it's not the main point of hanging - // when starting the bot. - scope.launch { - printStep("Registering slash commands", 2) - kord.registerApplicationCommands() - printStep("Finished registering slash commands", 2) - } + kord.registerApplicationCommands() + + Commands.register() kord.on { onInteract() @@ -180,6 +180,7 @@ suspend fun login() { } suspend fun Kord.registerApplicationCommands() { + printStep("Registering slash commands", 2) createGlobalMessageCommand("Delete Message") createGlobalMessageCommand("Fetch Message Info") createGlobalMessageCommand("Ping Message Author") @@ -188,6 +189,31 @@ suspend fun Kord.registerApplicationCommands() { registerSystemCommands() registerSwitchCommands() registerMiscCommands() + // Only send commands when discord hasn't registered yet + val file = File("./.pf-command-lock") + if (!file.exists()) { + scope.launch { + withContext(Dispatchers.IO) { + file.createNewFile() + } + deferredCommands.forEach { + rest.interaction.createGlobalApplicationCommand( + resources.applicationId, + it + ) + } + } + } +} + +val deferredCommands = arrayListOf() + +fun Kord.deferChatInputCommand( + name: String, + description: String, + builder: GlobalChatInputCreateBuilder.() -> Unit = {} +) { + deferredCommands.add(ProxyFoxChatInputCreateBuilderImpl(name, description).apply(builder).toRequest()) } suspend fun updatePresence() { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 4d34d22e..fe464df8 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -27,7 +27,7 @@ object Commands { val parser = CommandParser>() suspend fun register() { - printStep("Registering commands",1) + printStep("Registering text commands", 2) SystemCommands.register() MemberCommands.register() SwitchCommands.register() diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 08fe2727..eddce5ab 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -18,6 +18,7 @@ import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.guild import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.command.node.attachment +import dev.proxyfox.bot.deferChatInputCommand import dev.proxyfox.bot.kord import dev.proxyfox.bot.kordColor import dev.proxyfox.bot.member @@ -52,7 +53,8 @@ object MemberCommands { } suspend fun Kord.registerMemberCommands() { - createGlobalChatInputCommand("member", "Manage or create a system member!") { + printStep("Registering member commands", 3) + deferChatInputCommand("member", "Manage or create a system member!") { subCommand("create", "Create a member") { name() system() @@ -856,7 +858,7 @@ object MemberCommands { } suspend fun register() { - printStep("Registering member commands", 2) + printStep("Registering member commands", 3) //TODO: Dedupe code Commands.parser.registerBaseMemberCommands { database.fetchSystemFromUser(getUser()) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 1e4cd576..7f76086a 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -67,7 +67,8 @@ object MiscCommands { } suspend fun Kord.registerMiscCommands() { - createGlobalChatInputCommand("info", "Fetches info about the bot") { + printStep("Registering misc commands", 3) + deferChatInputCommand("info", "Fetches info about the bot") { subCommand("debug", "Fetch debug information about the bot") { runs("info") { debug(this) @@ -98,7 +99,7 @@ object MiscCommands { } } } - createGlobalChatInputCommand("moderation", "Moderator-only commands") { + deferChatInputCommand("moderation", "Moderator-only commands") { subCommand("role", "Access the role required for proxying") { role("role", "The role required for proxying") { required = false @@ -133,7 +134,7 @@ object MiscCommands { } } } - createGlobalChatInputCommand("misc", "Other commands that don't fit in a category") { + deferChatInputCommand("misc", "Other commands that don't fit in a category") { subCommand("fox", "Gets a random fox picture") { runs("misc") { getFox(this) @@ -248,7 +249,7 @@ object MiscCommands { } suspend fun register() { - printStep("Registering misc commands", 2) + printStep("Registering misc commands", 3) Commands.parser.literal("import") { runs { import(this, null) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 297442fa..dd7d1800 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -16,6 +16,7 @@ import dev.kord.rest.builder.interaction.subCommand import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.runs +import dev.proxyfox.bot.deferChatInputCommand import dev.proxyfox.bot.parseDuration import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.Pager @@ -41,7 +42,8 @@ object SwitchCommands { } suspend fun Kord.registerSwitchCommands() { - createGlobalChatInputCommand("switch", "Create or manage switches!") { + printStep("Registering switch commands", 3) + deferChatInputCommand("switch", "Create or manage switches!") { subCommand("create", "Create a switch") { string("members", "The members to use, comma separated") { required = true @@ -161,7 +163,7 @@ object SwitchCommands { } suspend fun register() { - printStep("Registering switch commands", 2) + printStep("Registering switch commands", 3) Commands.parser.registerSwitchCommands { database.fetchSystemFromUser(getUser()) } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 04ac9c80..94dcdedb 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -20,6 +20,7 @@ import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.command.context.system import dev.proxyfox.bot.command.node.attachment +import dev.proxyfox.bot.deferChatInputCommand import dev.proxyfox.bot.hasUnixValue import dev.proxyfox.bot.kordColor import dev.proxyfox.bot.prompts.Button @@ -48,7 +49,8 @@ object SystemCommands { } suspend fun Kord.registerSystemCommands() { - createGlobalChatInputCommand("system", "Manage or create a system!") { + printStep("Registering system commands", 3) + deferChatInputCommand("system", "Manage or create a system!") { subCommand("fetch", "Fetch a system card!") { system() runs { @@ -172,7 +174,7 @@ object SystemCommands { } suspend fun register() { - printStep("Registering system commands", 2) + printStep("Registering system commands", 3) Commands.parser.literal("list", "l") { runs { val system = database.fetchSystemFromUser(getUser()) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index ce9737f9..621417f0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -15,8 +15,10 @@ import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.* import dev.kord.rest.NamedFile import dev.kord.rest.builder.message.EmbedBuilder +import dev.proxyfox.bot.command.menu.DiscordMenu import dev.proxyfox.command.CommandContext import dev.proxyfox.command.NodeActionParam +import dev.proxyfox.command.menu.CommandMenu import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.int import dev.proxyfox.command.node.builtin.string @@ -54,6 +56,11 @@ abstract class DiscordContext(override val value: T) : CommandContext() { system: SystemRecord?, messageId: Snowflake? ): Pair + + suspend fun interactionMenu(action: suspend DiscordMenu.() -> Unit) { + @Suppress("UNCHECKED_CAST") + menu(action as CommandMenu.() -> Unit) + } } // Get a DiscordContext. diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index 7b77efed..b3a8f92f 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -13,6 +13,7 @@ import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.* import dev.kord.rest.builder.message.EmbedBuilder +import dev.proxyfox.bot.command.menu.DiscordMessageMenu import dev.proxyfox.command.MenuBuilder import dev.proxyfox.common.applyAsync import dev.proxyfox.database.database @@ -79,7 +80,10 @@ class DiscordMessageContext(message: Message, override val command: String): Dis } override suspend fun menu(action: MenuBuilder) { - TODO("Not yet implemented") + val message = getChannel().createMessage("Thinking...") + val menu = DiscordMessageMenu(message) + menu.action() + menu.init() } override suspend fun respondPlain(text: String, private: Boolean): Message { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index afca8164..964bb02a 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -17,6 +17,7 @@ import dev.kord.core.entity.* import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed +import dev.proxyfox.bot.command.menu.InteractionCommandMenu import dev.proxyfox.command.MenuBuilder import dev.proxyfox.database.database import dev.proxyfox.database.records.misc.ProxiedMessageRecord @@ -28,7 +29,12 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : override val command: String = "" override suspend fun menu(action: MenuBuilder) { - TODO("Not yet implemented") + val message = value.interaction.respondEphemeral { + content = "Thinking..." + } + val menu = InteractionCommandMenu(message) + menu.action() + menu.init() } @OptIn(ExperimentalStdlibApi::class) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxApplicationCommandModifyStateHolder.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxApplicationCommandModifyStateHolder.kt new file mode 100644 index 00000000..50babd4e --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxApplicationCommandModifyStateHolder.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.interaction + +import dev.kord.common.Locale +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.OptionalBoolean +import dev.kord.rest.builder.interaction.OptionsBuilder +import kotlinx.serialization.SerialName + +/** + * The needed class is internal, so we're keeping our on impl + * */ +class ProxyFoxApplicationCommandModifyStateHolder { + + var name: Optional = Optional.Missing() + var nameLocalizations: Optional?> = Optional.Missing() + + var description: Optional = Optional.Missing() + var descriptionLocalizations: Optional?> = Optional.Missing() + + var options: Optional> = Optional.Missing() + + var defaultMemberPermissions: Optional = Optional.Missing() + var dmPermission: OptionalBoolean? = OptionalBoolean.Missing + + + @Deprecated("'defaultPermission' is deprecated in favor of 'defaultMemberPermissions' and 'dmPermission'. Setting 'defaultPermission' to false can be replaced by setting 'defaultMemberPermissions' to empty Permissions and 'dmPermission' to false ('dmPermission' is only available for global commands).") + @SerialName("default_permission") + var defaultPermission: OptionalBoolean = OptionalBoolean.Missing +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt new file mode 100644 index 00000000..b584e793 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.interaction + +import dev.kord.common.Locale +import dev.kord.common.entity.ApplicationCommandType +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.delegate.delegate +import dev.kord.common.entity.optional.mapList +import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder +import dev.kord.rest.builder.interaction.OptionsBuilder +import dev.kord.rest.json.request.ApplicationCommandCreateRequest + +/** + * The needed class is internal, so we're keeping our on impl + * */ +class ProxyFoxChatInputCreateBuilderImpl( + override var name: String, + override var description: String, +) : GlobalChatInputCreateBuilder { + private val state = ProxyFoxApplicationCommandModifyStateHolder() + + override var nameLocalizations: MutableMap? by state::nameLocalizations.delegate() + override var descriptionLocalizations: MutableMap? by state::descriptionLocalizations.delegate() + + override val type: ApplicationCommandType + get() = ApplicationCommandType.ChatInput + + override var options: MutableList? by state::options.delegate() + override var defaultMemberPermissions: Permissions? by state::defaultMemberPermissions.delegate() + override var dmPermission: Boolean? by state::dmPermission.delegate() + + @Deprecated("'defaultPermission' is deprecated in favor of 'defaultMemberPermissions' and 'dmPermission'. Setting 'defaultPermission' to false can be replaced by setting 'defaultMemberPermissions' to empty Permissions and 'dmPermission' to false ('dmPermission' is only available for global commands).") + override var defaultPermission: Boolean? by @Suppress("DEPRECATION") state::defaultPermission.delegate() + + + override fun toRequest(): ApplicationCommandCreateRequest { + return ApplicationCommandCreateRequest( + name, + state.nameLocalizations, + type, + Optional.Value(description), + state.descriptionLocalizations, + state.options.mapList { it.toRequest() }, + state.defaultMemberPermissions, + state.dmPermission, + @Suppress("DEPRECATION") state.defaultPermission, + ) + } + +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt index 469eecf1..c5861478 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt @@ -1,12 +1,20 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.menu -import dev.kord.core.entity.Message +import dev.kord.rest.builder.message.modify.MessageModifyBuilder import dev.proxyfox.command.menu.CommandMenu import dev.proxyfox.command.menu.CommandScreen import kotlinx.coroutines.Job -class DiscordMenu(val message: Message) : CommandMenu() { - private val jobs = arrayListOf() +abstract class DiscordMenu : CommandMenu() { + internal val jobs = arrayListOf() override suspend fun close() { jobs.forEach { @@ -17,4 +25,6 @@ class DiscordMenu(val message: Message) : CommandMenu() { override suspend fun createScreen(name: String): CommandScreen { return DiscordScreen(name, this) } + + abstract suspend fun edit(builder: MessageModifyBuilder.() -> Unit) } \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt new file mode 100644 index 00000000..4eade964 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.menu + +import dev.kord.core.behavior.edit +import dev.kord.core.entity.Message +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import dev.kord.core.on +import dev.kord.rest.builder.message.modify.MessageModifyBuilder +import dev.proxyfox.bot.kord + +class DiscordMessageMenu(val message: Message) : DiscordMenu() { + override suspend fun edit(builder: MessageModifyBuilder.() -> Unit) { + message.edit(builder) + } + + override suspend fun init() { + jobs.add( + kord.on { + interact(this) + } + ) + super.init() + } + + private suspend fun interact(button: ButtonInteractionCreateEvent) { + if (button.interaction.message.id != message.id) return + active!!.click(button.interaction.componentId) + } +} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt index 99dedc8c..8f5c0766 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt @@ -1,13 +1,23 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.menu -import dev.kord.core.entity.Message import dev.proxyfox.command.menu.CommandScreen class DiscordScreen(name: String, private val menu: DiscordMenu) : CommandScreen(name) { - @Suppress("UNUSED") - private val message: Message get() = menu.message + private var initializer: suspend () -> Unit = {} - override suspend fun init() { + fun onInit(action: suspend () -> Unit) { + initializer = action + } + override suspend fun init() { + initializer() } } \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt new file mode 100644 index 00000000..9968693d --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.menu + +import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior +import dev.kord.core.behavior.interaction.response.edit +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import dev.kord.core.on +import dev.kord.rest.builder.message.modify.MessageModifyBuilder +import dev.proxyfox.bot.kord + +class InteractionCommandMenu(val interaction: EphemeralMessageInteractionResponseBehavior) : DiscordMenu() { + override suspend fun edit(builder: MessageModifyBuilder.() -> Unit) { + interaction.edit(builder) + } + + override suspend fun init() { + jobs.add( + kord.on { + interact(this) + } + ) + super.init() + } + + private fun interact(button: ButtonInteractionCreateEvent) { + TODO() + } +} \ No newline at end of file diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt index bb5fa98a..f90289f0 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt index 91ada221..addcc43b 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt b/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt index 4668c7b1..c397742d 100644 --- a/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt +++ b/modules/conversion/src/main/kotlin/dev/proxyfox/conversion/ConversionMain.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt index 0308bce3..ccfab885 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseMain.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseMain.kt index 510ff5cb..d2b59954 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseMain.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseMain.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt index 96953e03..8139de68 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index fe37dcf7..ea6bf3de 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index f1084e04..5e7a24ca 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt index 879925bc..d2c4d880 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt index bcddd50c..2d632d70 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt index dd06fa33..1161af34 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/TimeUtil.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt index 9ac4f7a9..526da783 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt index e02d8964..84020693 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/Importer.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/ImporterException.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/ImporterException.kt index 3de03613..8a0b0d07 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/ImporterException.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/ImporterException.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt index 84ee78f4..381bca84 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt index 882d3bca..9b39ef18 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/TupperBoxImporter.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt index df9664b5..2fdb165d 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/TbTypes.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/TbTypes.kt index 4d4d8799..7b236905 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/TbTypes.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/TbTypes.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/DatabaseException.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/DatabaseException.kt index 35b050f1..961a20d4 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/DatabaseException.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/DatabaseException.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt index 3177cc90..ca1e8af5 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/MongoRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt index 71235aba..f85a12e6 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt index 10906586..02e48bee 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt index c88eb6cd..89bb2ca2 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberProxyTagRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt index bf780e28..bfb5b64f 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt index c9d3a4a7..35e4c0f4 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/member/MemberServerSettingsRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/AutoProxyMode.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/AutoProxyMode.kt index cc56d6c5..b2ffdad9 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/AutoProxyMode.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/AutoProxyMode.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt index c5d9f44f..f5d2c35e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ChannelSettingsRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt index 9af75b71..f4f6df6a 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt index 78314773..fcce2eeb 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt index 6702036c..3149ae97 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TrustLevel.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TrustLevel.kt index 3b33c372..b9d5ec73 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TrustLevel.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TrustLevel.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt index 73343e79..1805bdd2 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/UserRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt index b77b0c8f..979bd2a5 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemChannelSettingsRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index 7da35750..718a5e87 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt index 063c0a96..fb7f246e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemServerSettingsRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt index 8cbec403..894b58a9 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemSwitchRecord.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/test/kotlin/dev/proxyfox/BlackHole.kt b/modules/database/src/test/kotlin/dev/proxyfox/BlackHole.kt index b145a5c9..30558fd0 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/BlackHole.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/BlackHole.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTest.kt index 6d9e4236..8e40ab10 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt index 4775c7c5..5a8e9a5c 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseTestUtil.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseUtilTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseUtilTest.kt index f05cf627..4c2e1d4b 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseUtilTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/DatabaseUtilTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/GeneratingIterator.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/GeneratingIterator.kt index 2aea16c1..01510bfd 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/GeneratingIterator.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/GeneratingIterator.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/ProxyTagTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/ProxyTagTest.kt index 4849f191..83ef3691 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/ProxyTagTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/ProxyTagTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/test/kotlin/dev/proxyfox/database/TimeUtilTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/database/TimeUtilTest.kt index 8f43cea4..d70e16ba 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/database/TimeUtilTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/database/TimeUtilTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt index b26fbb2a..e4f82e43 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/exporter/ExporterTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt index 00745a18..217fbcee 100644 --- a/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt +++ b/modules/database/src/test/kotlin/dev/proxyfox/importer/ImporterTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, The ProxyFox Group + * Copyright (c) 2022-2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this From 7051da8e105820c288c38be6bb3bc63b70f63d29 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 17:47:10 -0600 Subject: [PATCH 078/137] Implement message interaction command handlers --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 6 +- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 147 +++++++++++++++++- .../dev/proxyfox/bot/command/MiscCommands.kt | 2 +- .../proxyfox/bot/command/SystemCommands.kt | 26 +++- 4 files changed, 165 insertions(+), 16 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 53813e94..443c120e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -137,11 +137,15 @@ suspend fun login() { } } + kord.on { + handleModal() + } + kord.registerApplicationCommands() Commands.register() - kord.on { + kord.on { onInteract() } kord.on { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index 77cfa937..55277ae1 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -8,12 +8,13 @@ package dev.proxyfox.bot -import dev.kord.common.entity.MessageType -import dev.kord.common.entity.Permission -import dev.kord.common.entity.Permissions -import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.* import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.cache.data.AttachmentData import dev.kord.core.cache.data.EmbedData import dev.kord.core.entity.Attachment @@ -21,10 +22,12 @@ import dev.kord.core.entity.Embed import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.interaction.SubCommand import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.core.event.interaction.GlobalMessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.ModalSubmitInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.event.message.MessageUpdateEvent import dev.kord.core.event.message.ReactionAddEvent +import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.message.create.embed import dev.proxyfox.bot.command.* import dev.proxyfox.bot.command.context.DiscordContext @@ -281,7 +284,8 @@ suspend fun ReactionAddEvent.onReactionAdd() { } } footer { - text = "Member ID \u2009• \u2009${member.id}\u2007|\u2007System ID \u2009• \u2009${system.id}\u2007|\u2007Created " + text = + "Member ID \u2009• \u2009${member.id}\u2007|\u2007System ID \u2009• \u2009${system.id}\u2007|\u2007Created " } timestamp = system.timestamp } @@ -291,8 +295,137 @@ suspend fun ReactionAddEvent.onReactionAdd() { } } -suspend fun GlobalMessageCommandInteractionCreateEvent.onInteract() { +suspend fun ModalSubmitInteractionCreateEvent.handleModal() { + val channel = interaction.channel + when { + interaction.modalId.startsWith("MessageEdit:") -> { + val webhook = WebhookUtil.createOrFetchWebhookFromCache(channel.fetchChannel()) + val id = Snowflake(interaction.modalId.split(":")[1]) + val content = interaction.textInputs["MessageEdit"]!!.value ?: return let { + interaction.respondEphemeral { + content = "Please provide the content to edit with" + } + } + webhook.edit(id, if (channel is ThreadChannelBehavior) channel.id else null) { + this.content = content + } + interaction.respondEphemeral { + this.content = "message edited." + } + } + } +} + +suspend fun MessageCommandInteractionCreateEvent.onInteract() { + val message = this.interaction.getTargetOrNull() ?: return let { + interaction.respondEphemeral { + content = "Message not found. Can I see it?" + } + } + val databaseMessage = database.fetchMessage(message.id) ?: return let { + interaction.respondEphemeral { + content = "Message not found in database. Did I proxy it?" + } + } + when (interaction.invokedCommandName) { + "Delete Message" -> { + // System needs to be non-null. + val system = database.fetchSystemFromUser(interaction.user) ?: return + if (databaseMessage.systemId == system.id) { + message.delete("User requested message deletion.") + databaseMessage.deleted = true + database.updateMessage(databaseMessage) + interaction.respondEphemeral { + content = "Message deleted." + } + return + } + interaction.respondEphemeral { + content = "You're not the original author of the message" + } + } + + "Fetch Message Info" -> { + val system = database.fetchSystemFromId(databaseMessage.systemId) + ?: return + + val member = database.fetchMemberFromSystem(databaseMessage.systemId, databaseMessage.memberId) + ?: return + + val guild = message.getGuild() + val settings = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) + + val user = kord.getUser(Snowflake(databaseMessage.userId)) + + interaction.respondEphemeral { + content = + "Message by ${member.showDisplayName()} was sent by <@${databaseMessage.userId}> (${user?.tag ?: "Unknown user"})" + embed { + val systemName = system.name ?: system.id + author { + name = member.displayName?.let { "$it (${member.name})\u2007•\u2007$systemName" } + ?: "${member.name}\u2007•\u2007$systemName" + icon = member.avatarUrl + } + member.avatarUrl?.let { + thumbnail { + url = it + } + } + color = member.color.kordColor() + description = member.description + settings?.nickname?.let { + field { + name = "Server Name" + value = "> $it\n*For ${guild?.name}*" + inline = true + } + } + member.pronouns?.let { + field { + name = "Pronouns" + value = it + inline = true + } + } + member.birthday?.let { + field { + name = "Birthday" + value = it.toJavaLocalDate().displayDate() + inline = true + } + } + footer { + text = + "Member ID \u2009• \u2009${member.id}\u2007|\u2007System ID \u2009• \u2009${system.id}\u2007|\u2007Created " + } + timestamp = system.timestamp + } + } + } + "Ping Message Author" -> { + interaction.respondPublic { + content = + "Psst.. ${databaseMessage.memberName} (<@${databaseMessage.userId}>)$ellipsis You were pinged by <@${interaction.user.id}>" + } + } + + "Edit Message" -> { + val system = database.fetchSystemFromUser(interaction.user) ?: return + if (databaseMessage.systemId == system.id) { + interaction.modal("Message Edit Screen", "MessageEdit:${message.id}") { + components.add(ActionRowBuilder().apply { + textInput(TextInputStyle.Paragraph, "MessageEdit", "Message") {} + }) + } + return + } + interaction.respondEphemeral { + content = "You're not the original author of the message" + } + } + } } suspend fun ChatInputCommandInteractionCreateEvent.onInteract() { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 7f76086a..753691b1 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -1107,7 +1107,7 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" val messages = ctx.getDatabaseMessage(system, message) val discordMessage = messages.first discordMessage ?: run { - ctx.respondFailure("Unable to find message to delete.", true) + ctx.respondFailure("Unable to find message to edit.", true) return false } val databaseMessage = messages.second diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 94dcdedb..1764431f 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -478,19 +478,24 @@ object SystemCommands { return true } - @Suppress("UNUSED_PARAMETER") private suspend fun list(ctx: DiscordContext, system: SystemRecord, byMessage: Boolean, verbose: Boolean): Boolean { - // TODO: List by message - if (verbose) { ctx.respondEmbed { system(system, nameTransformer = { "Members of $it" }) val proxies = database.fetchProxiesFromSystem(system.id) - for (m in database.fetchMembersFromSystem(system.id)!!) { + for (m in database.fetchMembersFromSystem(system.id)!!.sortedBy { + if (byMessage) it.messageCount + it.name + }) { val memberProxies = proxies?.filter { it.memberId == m.id } field { name = "${m.asString()} [`${m.id}`]" - value = if (memberProxies.isNullOrEmpty()) "*No proxy tags set.*" else memberProxies.joinToString("\uFEFF``\n``\uFEFF", "``\uFEFF", "\uFEFF``") + value = + if (memberProxies.isNullOrEmpty()) "*No proxy tags set.*" else memberProxies.joinToString( + "\uFEFF``\n``\uFEFF", + "``\uFEFF", + "\uFEFF``" + ) inline = true } } @@ -502,11 +507,18 @@ object SystemCommands { Pager.build( ctx.getUser()!!.id, ctx.getChannel(), - database.fetchMembersFromSystem(system.id)!!.map { m -> m to proxies.filter { it.memberId == m.id } }, + database.fetchMembersFromSystem(system.id)!!.sortedBy { + if (byMessage) it.messageCount + it.name + }.map { m -> m to proxies.filter { it.memberId == m.id } }, 20, { page -> system(system, nameTransformer = { "[$page] Members of $it" }) }, { - val str = if (it.second.isNotEmpty()) it.second.joinToString("\uFEFF``, ``\uFEFF", " (``\uFEFF", "\uFEFF``)") else "" + val str = if (it.second.isNotEmpty()) it.second.joinToString( + "\uFEFF``, ``\uFEFF", + " (``\uFEFF", + "\uFEFF``)" + ) else "" "`${it.first.id}`\u2007•\u2007**${it.first.name}**${str}\n" }, ) From 3f763a4fc4b0b6dd5d71a2edc4a05a98aed3de88 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 18:27:28 -0600 Subject: [PATCH 079/137] Implement a server toggle for system tag enforcement --- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 22 +++++- .../dev/proxyfox/bot/command/MiscCommands.kt | 72 ++++++++++++++++++- .../dev/proxyfox/bot/webhook/ProxyContext.kt | 6 +- .../dev/proxyfox/bot/webhook/WebhookUtil.kt | 2 + .../records/misc/ServerSettingsRecord.kt | 1 + 5 files changed, 97 insertions(+), 6 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index 55277ae1..6e2f74b8 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -174,7 +174,16 @@ private suspend fun handleProxying( database.updateSystem(system) } - WebhookUtil.prepareMessage(message, content, system, member, proxy, memberServer, server.moderationDelay.toLong())?.send() + WebhookUtil.prepareMessage( + message, + content, + system, + member, + proxy, + memberServer, + server.moderationDelay.toLong(), + server.enforceTag + )?.send() } else if (content.startsWith('\\')) { // Doesn't proxy just for this message. if (content.startsWith("\\\\")) { @@ -194,7 +203,16 @@ private suspend fun handleProxying( val memberServer = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) if (memberServer?.proxyEnabled == false) return - WebhookUtil.prepareMessage(message, content, system, member, null, memberServer, server.moderationDelay.toLong())?.send() + WebhookUtil.prepareMessage( + message, + content, + system, + member, + null, + memberServer, + server.moderationDelay.toLong(), + server.enforceTag + )?.send() } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 753691b1..37c82ccc 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -133,6 +133,13 @@ object MiscCommands { channelProxy(this, channel, enabled) } } + subCommand("force-tag", "Toggle the enforcement of a system tag for this server") { + bool("value", "The value to set") + runs("moderation") { + val enabled = value.interaction.command.booleans["value"] + forceTag(this, enabled) + } + } } deferChatInputCommand("misc", "Other commands that don't fit in a category") { subCommand("fox", "Gets a random fox picture") { @@ -510,6 +517,22 @@ object MiscCommands { } } + Commands.parser.literal("forcetag", "requiretag") { + runs { + forceTag(this, null) + } + literal("on", "true", "enable", "1") { + runs { + forceTag(this, true) + } + } + literal("off", "false", "disable", "0") { + runs { + forceTag(this, false) + } + } + } + Commands.parser.literal("delete", "del") { runs { val system = database.fetchSystemFromUser(getUser()) @@ -702,6 +725,29 @@ object MiscCommands { } } + private suspend fun forceTag(ctx: DiscordContext, enabled: Boolean?): Boolean { + val server = database.getOrCreateServerSettings(ctx.getGuild() ?: run { + ctx.respondFailure("You are not in a server.") + return false + }) + + enabled ?: let { + ctx.respondPlain("System tag force is currently ${if (server.enforceTag) "enabled" else "disabled"} for this server.") + return true + } + + if (!ctx.hasRequired(Permission.ManageGuild)) { + ctx.respondFailure("You do not have the proper permissions to run this command.") + return false + } + + server.enforceTag = enabled + database.updateServerSettings(server) + + ctx.respondPlain("System tag force is now ${if (server.enforceTag) "enabled" else "disabled"} for this server.") + return true + } + private suspend fun getFox(ctx: DiscordContext): Boolean { ctx.respondEmbed { val fox = FoxFetch.fetch() @@ -1016,11 +1062,33 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return false } + val server = database.getOrCreateServerSettings(discordMessage.getGuild()) + + val serverSystem = database.getOrCreateServerSettingsFromSystem(databaseMessage.guildId, system.id) + + if (serverSystem.autoProxyMode == AutoProxyMode.LATCH) { + serverSystem.autoProxy = member.id + database.updateSystemServerSettings(serverSystem) + } else if (serverSystem.autoProxyMode == AutoProxyMode.FALLBACK && system.autoType == AutoProxyMode.LATCH) { + system.autoProxy = member.id + database.updateSystem(system) + } + val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(ctx.getGuild(), system.id, member.id) - val guildMessage = GuildMessage(discordMessage, ctx.getGuild()!!, discordMessage.channel.asChannelOf(), ctx.getUser()!!) + val guildMessage = + GuildMessage(discordMessage, ctx.getGuild()!!, discordMessage.channel.asChannelOf(), ctx.getUser()!!) - WebhookUtil.prepareMessage(guildMessage, discordMessage.content, system, member, null, serverMember)?.send(true) + WebhookUtil.prepareMessage( + guildMessage, + discordMessage.content, + system, + member, + null, + serverMember, + server.moderationDelay.toLong(), + server.enforceTag + )?.send(true) ?: throw AssertionError("Message could not be reproxied. Is the contents empty?") databaseMessage.deleted = true diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt index a7139040..95fc17e9 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt @@ -49,20 +49,22 @@ data class ProxyContext( val resolvedUsername: String, val resolvedAvatar: String?, val moderationDelay: Long, + val enforceTag: Boolean ) { @OptIn(InternalAPI::class) suspend fun send(reproxy: Boolean = false) { val newMessage = try { webhook.execute(threadId) { if (messageContent.isNotBlank()) content = messageContent - username = resolvedUsername + " " + (system.tag ?: "") + username = resolvedUsername + " " + (system.tag + ?: if (enforceTag) "| ${message.author.username}#${message.author.discriminator}" else "") avatarUrl = resolvedAvatar for (attachment in message.attachments) { val response: HttpResponse = http.get(urlString = attachment.url) { headers { append( HttpHeaders.UserAgent, - "ProxyFox/2.0.0 (+https://github.com/The-ProxyFox-Group/ProxyFox/; +https://proxyfox.dev/)" + "ProxyFox/2.1 (+https://github.com/The-ProxyFox-Group/ProxyFox/; +https://proxyfox.dev/)" ) } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookUtil.kt index 9b9bc112..84d17ec2 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/WebhookUtil.kt @@ -32,6 +32,7 @@ object WebhookUtil { proxy: MemberProxyTagRecord?, serverMember: MemberServerSettingsRecord?, moderationDelay: Long = 500L, + enforceTag: Boolean = false ): ProxyContext? { var messageContent = content if (!member.keepProxy && proxy != null) @@ -50,6 +51,7 @@ object WebhookUtil { resolvedUsername = serverMember?.nickname ?: member.displayName ?: member.name, resolvedAvatar = serverMember?.avatarUrl ?: member.avatarUrl ?: system.avatarUrl, moderationDelay = max(moderationDelay, 0L), + enforceTag = enforceTag ) } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt index fcce2eeb..9fb33927 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ServerSettingsRecord.kt @@ -26,6 +26,7 @@ class ServerSettingsRecord() : MongoRecord { var serverId: ULong = 0UL var proxyRole: ULong = 0UL var moderationDelay: Short = 250 + var enforceTag: Boolean = false constructor(serverId: ULong) : this() { this.serverId = serverId From bffdda43b8abb345ac61c8014a35da4b718e90a6 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 20:02:24 -0600 Subject: [PATCH 080/137] Work on groups --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 11 +- .../dev/proxyfox/bot/command/Commands.kt | 30 +++ .../dev/proxyfox/bot/command/GroupCommands.kt | 100 +++++++ .../proxyfox/bot/command/MemberCommands.kt | 250 +++++++++--------- .../dev/proxyfox/bot/command/MiscCommands.kt | 78 +++--- .../proxyfox/bot/command/SwitchCommands.kt | 28 +- .../proxyfox/bot/command/SystemCommands.kt | 70 ++--- .../kotlin/dev/proxyfox/database/Database.kt | 68 ++++- .../dev/proxyfox/database/InMemoryDatabase.kt | 22 ++ .../dev/proxyfox/database/MongoDatabase.kt | 41 ++- .../dev/proxyfox/database/NopDatabase.kt | 27 +- .../dev/proxyfox/database/ProxyDatabase.kt | 20 ++ .../database/records/group/GroupRecord.kt | 10 +- .../database/records/group/TagMode.kt | 9 + 14 files changed, 543 insertions(+), 221 deletions(-) create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 443c120e..f252c211 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -35,6 +35,7 @@ import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.json.request.ApplicationCommandCreateRequest import dev.kord.rest.request.KtorRequestException import dev.proxyfox.bot.command.Commands +import dev.proxyfox.bot.command.GroupCommands.registerGroupCommands import dev.proxyfox.bot.command.MemberCommands.registerMemberCommands import dev.proxyfox.bot.command.MiscCommands.registerMiscCommands import dev.proxyfox.bot.command.SwitchCommands.registerSwitchCommands @@ -189,18 +190,16 @@ suspend fun Kord.registerApplicationCommands() { createGlobalMessageCommand("Fetch Message Info") createGlobalMessageCommand("Ping Message Author") createGlobalMessageCommand("Edit Message") - registerMemberCommands() registerSystemCommands() + registerGroupCommands() + registerMemberCommands() registerSwitchCommands() registerMiscCommands() // Only send commands when discord hasn't registered yet val file = File("./.pf-command-lock") if (!file.exists()) { - scope.launch { - withContext(Dispatchers.IO) { - file.createNewFile() - } - deferredCommands.forEach { + deferredCommands.forEach { + scope.launch { rest.interaction.createGlobalApplicationCommand( resources.applicationId, it diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index fe464df8..9d70288f 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -14,9 +14,12 @@ import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.command.CommandParser import dev.proxyfox.common.printStep import dev.proxyfox.database.database +import dev.proxyfox.database.records.group.GroupRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemSwitchRecord +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract /** * General utilities relating to commands @@ -29,6 +32,7 @@ object Commands { suspend fun register() { printStep("Registering text commands", 2) SystemCommands.register() + GroupCommands.register() MemberCommands.register() SwitchCommands.register() MiscCommands.register() @@ -69,7 +73,11 @@ fun GlobalChatInputCreateBuilder.access(type: String, name: String, builder: Sub subCommand(name, "Accesses the $type's $name", builder) } +@OptIn(ExperimentalContracts::class) suspend fun checkSystem(ctx: DiscordContext, system: SystemRecord?, private: Boolean = false): Boolean { + contract { + returns(true) implies (system != null) + } system ?: run { ctx.respondFailure("System does not exist. Create one using a slash command or `pf>system new`", private) return false @@ -77,7 +85,24 @@ suspend fun checkSystem(ctx: DiscordContext, system: SystemRecord?, priva return true } +@OptIn(ExperimentalContracts::class) +suspend fun checkGroup(ctx: DiscordContext, group: GroupRecord?, private: Boolean = false): Boolean { + contract { + returns(true) implies (group != null) + } + group ?: run { + ctx.respondFailure("Group does not exist. Create one using a slash command or `pf>group new`", private) + return false + } + return true +} + +@OptIn(ExperimentalContracts::class) suspend fun checkMember(ctx: DiscordContext, member: MemberRecord?, private: Boolean = false): Boolean { + contract { + returns(true) implies (member != null) + } + member ?: run { ctx.respondFailure("Member does not exist. Create one using a slash command or `pf>member new`", private) return false @@ -85,7 +110,12 @@ suspend fun checkMember(ctx: DiscordContext, member: MemberRecord?, priva return true } +@OptIn(ExperimentalContracts::class) suspend fun checkSwitch(ctx: DiscordContext, switch: SystemSwitchRecord?): Boolean { + contract { + returns(true) implies (switch != null) + } + switch ?: run { ctx.respondFailure("Looks like you haven't registered any switches yet. Create one using a slash command or `pf>switch`") return false diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt new file mode 100644 index 00000000..1fc03733 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt @@ -0,0 +1,100 @@ +package dev.proxyfox.bot.command + +import dev.kord.core.Kord +import dev.kord.rest.builder.interaction.SubCommandBuilder +import dev.kord.rest.builder.interaction.subCommand +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.context.InteractionCommandContext +import dev.proxyfox.bot.command.context.runs +import dev.proxyfox.bot.deferChatInputCommand +import dev.proxyfox.bot.kordColor +import dev.proxyfox.command.NodeHolder +import dev.proxyfox.command.node.builtin.literal +import dev.proxyfox.command.node.builtin.string +import dev.proxyfox.common.printStep +import dev.proxyfox.database.database +import dev.proxyfox.database.records.group.GroupRecord +import dev.proxyfox.database.records.system.SystemRecord + +object GroupCommands { + var interactionExecutors: HashMap Boolean> = hashMapOf() + + fun SubCommandBuilder.runs(action: suspend InteractionCommandContext.() -> Boolean) { + interactionExecutors[name] = action + } + + suspend fun Kord.registerGroupCommands() { + printStep("Registering group commands", 3) + deferChatInputCommand("group", "Manage a group") { + subCommand("access", "View the group") { + name() + system() + runs { + val system = getSystem() + if (!checkSystem(this, system)) return@runs false + val group = database.findGroup(system.id, value.interaction.command.strings["name"]!!) + if (!checkGroup(this, group)) return@runs false + access(this, system, group) + } + } + } + } + + suspend fun register() { + printStep("Registering system commands", 3) + Commands.parser.registerGroupCommands { + database.fetchSystemFromUser(getUser()) + } + } + + suspend fun > NodeHolder.registerGroupCommands(getSys: suspend DiscordContext.() -> SystemRecord?) { + literal("group", "g") { + string("group") { getGroup -> + runs { + val system = getSys() + if (!checkSystem(this, system)) return@runs false + val group = database.findGroup(system.id, getGroup()) + if (!checkGroup(this, group)) return@runs false + + access(this, system, group) + } + } + } + } + + suspend fun access(ctx: DiscordContext, system: SystemRecord, group: GroupRecord): Boolean { + val members = database.fetchMembersFromGroup(group).size + ctx.respondEmbed { + title = group.name + color = group.color.kordColor() + group.avatarUrl?.let { + thumbnail { url = it } + } + group.tag?.let { + field { + name = "Tag" + value = "$it\n**Display Mode:${group.tagMode.getDisplayString()}**" + inline = true + } + } + field { + name = "Members (`${members}`)" + value = "See `pf>group ${group.id} list`" + inline = true + } + group.description?.let { + field { + name = "Description" + value = it + } + } + footer { + text = + "Group ID \u2009• \u2009${group.id}\u2007|\u2007System ID \u2009• \u2009${system.id}\u2007|\u2007Created " + } + timestamp = group.timestamp + } + + return true + } +} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index eddce5ab..4f95618e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -63,7 +63,7 @@ object MemberCommands { if (!checkSystem(this, system)) return@runs false val name = value.interaction.command.strings["name"]!! - create(this, system!!, name) + create(this, system, name) } } subCommand("delete", "Delete a member") { @@ -72,10 +72,10 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false - delete(this, system, member!!) + delete(this, system, member) } } subCommand("fetch", "Fetches the member's card") { @@ -84,10 +84,10 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false - access(this, system, member!!) + access(this, system, member) } } access("member", "name") { @@ -98,12 +98,12 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val name = value.interaction.command.strings["name"] val raw = value.interaction.command.booleans["raw"] ?: false - rename(this, system, member!!, name, raw) + rename(this, system, member, name, raw) } } access("member", "nickname") { @@ -115,13 +115,13 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val name = value.interaction.command.strings["name"] val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - nickname(this, system, member!!, name, raw, clear) + nickname(this, system, member, name, raw, clear) } } access("member", "servernick") { @@ -134,7 +134,7 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id @@ -146,7 +146,8 @@ object MemberCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) val name = value.interaction.command.strings["name"] val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false @@ -163,13 +164,13 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val desc = value.interaction.command.strings["description"] val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - description(this, system, member!!, desc, raw, clear) + description(this, system, member, desc, raw, clear) } } access("member", "avatar") { @@ -180,12 +181,12 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val avatar = value.interaction.command.attachments["avatar"]?.data?.url val clear = value.interaction.command.booleans["clear"] ?: false - avatar(this, system, member!!, avatar, clear) + avatar(this, system, member, avatar, clear) } } access("member", "serveravatar") { @@ -196,7 +197,7 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id @@ -208,7 +209,8 @@ object MemberCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) val avatar = value.interaction.command.attachments["avatar"]?.data?.url val clear = value.interaction.command.booleans["clear"] ?: false @@ -225,13 +227,13 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val pro = value.interaction.command.strings["pronouns"] val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - pronouns(this, system, member!!, pro, raw, clear) + pronouns(this, system, member, pro, raw, clear) } } access("member", "color") { @@ -241,11 +243,11 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val color = value.interaction.command.strings["color"] - color(this, system, member!!, color?.toColor()) + color(this, system, member, color?.toColor()) } } access("member", "birthday") { @@ -256,12 +258,12 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val birthday = value.interaction.command.strings["birthday"] val clear = value.interaction.command.booleans["clear"] ?: false - birthday(this, system, member!!, tryParseLocalDate(birthday)?.first, clear) + birthday(this, system, member, tryParseLocalDate(birthday)?.first, clear) } } subCommand("proxy-add", "Adds a proxy") { @@ -272,13 +274,13 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val prefix = value.interaction.command.strings["prefix"] val suffix = value.interaction.command.strings["suffix"] val proxy = if (prefix == null && suffix == null) null else Pair(prefix, suffix) - proxy(this, system, member!!, proxy) + proxy(this, system, member, proxy) } } subCommand("proxy-delete", "Delete a proxy") { @@ -289,7 +291,7 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val prefix = value.interaction.command.strings["prefix"] val suffix = value.interaction.command.strings["suffix"] @@ -297,7 +299,7 @@ object MemberCommands { val exists = proxy != null val proxyTag = if (exists) database.fetchProxyTagFromMessage(getUser(), "${prefix}text$suffix") else null - removeProxy(this, system, member!!, exists, proxyTag) + removeProxy(this, system, member, exists, proxyTag) } } access("member", "autoproxy") { @@ -307,11 +309,11 @@ object MemberCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val member = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, member)) return@runs false val value = value.interaction.command.booleans["value"] - autoproxy(this, system, member!!, value) + autoproxy(this, system, member, value) } } } @@ -324,35 +326,35 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - access(this, system, member!!) + access(this, system, member) } literal("remame", "name") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - rename(this, system, member!!, null, false) + rename(this, system, member, null, false) } unixLiteral("raw") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - rename(this, system, member!!, null, true) + rename(this, system, member, null, true) } } greedy("name") { getName -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - rename(this, system, member!!, getName(), false) + rename(this, system, member, getName(), false) } } } @@ -361,36 +363,36 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - nickname(this, system, member!!, null, false, false) + nickname(this, system, member, null, false, false) } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - nickname(this, system, member!!, null, false, true) + nickname(this, system, member, null, false, true) } } unixLiteral("raw") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - nickname(this, system, member!!, null, true, false) + nickname(this, system, member, null, true, false) } } greedy("name") { getName -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val name = getName() - nickname(this, system, member!!, name, false, false) + nickname(this, system, member, name, false, false) } } } @@ -400,7 +402,7 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val guildId = getGuildId() ?: run { respondFailure("Command not ran in server.") @@ -410,14 +412,15 @@ object MemberCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) servername(this, system, serverMember!!, null, false, false) } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val guildId = getGuildId() ?: run { respondFailure("Command not ran in server.") @@ -427,7 +430,8 @@ object MemberCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) servername(this, system, serverMember!!, null, false, true) } } @@ -435,7 +439,7 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val guildId = getGuildId() ?: run { respondFailure("Command not ran in server.") @@ -445,7 +449,8 @@ object MemberCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) servername(this, system, serverMember!!, null, true, false) } } @@ -453,7 +458,7 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val guildId = getGuildId() ?: run { respondFailure("Command not ran in server.") @@ -463,7 +468,8 @@ object MemberCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) servername(this, system, serverMember!!, getName(), false, false) } } @@ -474,35 +480,35 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - description(this, system, member!!, null, false, false) + description(this, system, member, null, false, false) } unixLiteral("raw") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - description(this, system, member!!, null, true, false) + description(this, system, member, null, true, false) } } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - description(this, system, member!!, null, false, true) + description(this, system, member, null, false, true) } } greedy("description") { getDesc -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - description(this, system, member!!, getDesc(), false, false) + description(this, system, member, getDesc(), false, false) } } } @@ -511,35 +517,35 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - avatar(this, system, member!!, null, false) + avatar(this, system, member, null, false) } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - avatar(this, system, member!!, null, true) + avatar(this, system, member, null, true) } } attachment("avatar") { getAvatar -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - avatar(this, system, member!!, getAvatar().url, false) + avatar(this, system, member, getAvatar().url, false) } } string("avatar") { getAvatar -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - avatar(this, system, member!!, getAvatar(), false) + avatar(this, system, member, getAvatar(), false) } } } @@ -549,7 +555,7 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val guildId = getGuildId() ?: run { respondFailure("Command not ran in server.") @@ -559,14 +565,15 @@ object MemberCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) serverAvatar(this, system, serverMember!!, null, false) } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val guildId = getGuildId() ?: run { respondFailure("Command not ran in server.") @@ -576,7 +583,8 @@ object MemberCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) serverAvatar(this, system, serverMember!!, null, true) } } @@ -584,7 +592,7 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val guildId = getGuildId() ?: run { respondFailure("Command not ran in server.") @@ -594,7 +602,8 @@ object MemberCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) serverAvatar(this, system, serverMember!!, getAvatar().url, false) } } @@ -602,7 +611,7 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val guildId = getGuildId() ?: run { respondFailure("Command not ran in server.") @@ -612,7 +621,8 @@ object MemberCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverMember = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member!!.id) + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) serverAvatar(this, system, serverMember!!, getAvatar(), false) } } @@ -623,27 +633,27 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - autoproxy(this, system, member!!, null) + autoproxy(this, system, member, null) } // TODO: BooleanNode literal("disable", "off", "false", "0") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - autoproxy(this, system, member!!, false) + autoproxy(this, system, member, false) } } literal("enable", "on", "true", "1") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - autoproxy(this, system, member!!, true) + autoproxy(this, system, member, true) } } } @@ -652,24 +662,24 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - proxy(this, system, member!!, null) + proxy(this, system, member, null) } literal("remove", "rem", "delete", "del") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - removeProxy(this, system, member!!, false, null) + removeProxy(this, system, member, false, null) } greedy("proxy") { getProxy -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false extractProxyFromTag(this, getProxy()) ?: return@runs false val proxy = database.fetchProxyTagFromMessage(getUser(), getProxy()) @@ -677,7 +687,7 @@ object MemberCommands { respondFailure("Proxy tag doesn't exist in this member.") return@runs false } - if (proxy.memberId != member!!.id) { + if (proxy.memberId != member.id) { respondFailure("Proxy tag doesn't exist in this member.") return@runs false } @@ -690,19 +700,19 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - proxy(this, system, member!!, null) + proxy(this, system, member, null) } greedy("proxy") { getProxy -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false - proxy(this, system, member!!, proxy) + proxy(this, system, member, proxy) } } } @@ -711,10 +721,10 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false - proxy(this, system, member!!, proxy) + proxy(this, system, member, proxy) } } } @@ -723,35 +733,35 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - pronouns(this, system, member!!, null, false, false) + pronouns(this, system, member, null, false, false) } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - pronouns(this, system, member!!, null, false, true) + pronouns(this, system, member, null, false, true) } } unixLiteral("raw") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - pronouns(this, system, member!!, null, true, false) + pronouns(this, system, member, null, true, false) } } greedy("pronouns") { getPronouns -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - pronouns(this, system, member!!, getPronouns(), false, false) + pronouns(this, system, member, getPronouns(), false, false) } } } @@ -760,17 +770,17 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - color(this, system, member!!, null) + color(this, system, member, null) } greedy("color") { getColor -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - color(this, system, member!!, getColor().toColor()) + color(this, system, member, getColor().toColor()) } } } @@ -779,26 +789,26 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - birthday(this, system, member!!, null, false) + birthday(this, system, member, null, false) } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - birthday(this, system, member!!, null, true) + birthday(this, system, member, null, true) } } greedy("birthday") { getBirthday -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - birthday(this, system, member!!, tryParseLocalDate(getBirthday())?.first, false) + birthday(this, system, member, tryParseLocalDate(getBirthday())?.first, false) } } } @@ -806,9 +816,9 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - delete(this, system, member!!) + delete(this, system, member) } } } @@ -828,15 +838,15 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - delete(this, system!!, null) + delete(this, system, null) } greedy("member") { getMem -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false - delete(this, system, member!!) + delete(this, system, member) } } } @@ -844,13 +854,13 @@ object MemberCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - create(this, system!!, null) + create(this, system, null) } greedy("member") { getMem -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - create(this, system!!, getMem()) + create(this, system, getMem()) } } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 37c82ccc..cad61f35 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -180,11 +180,11 @@ object MiscCommands { else -> AutoProxyMode.MEMBER } val member = if (type == AutoProxyMode.MEMBER) { - val mem = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val mem = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, mem)) return@runs false mem } else null - proxy(this, system!!, type, member) + proxy(this, system, type, member) } } subCommand("proxy", "Toggles proxying for this server") { @@ -203,7 +203,7 @@ object MiscCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) serverProxy(this, serverSystem, enabled) } } @@ -221,7 +221,7 @@ object MiscCommands { else -> AutoProxyMode.MEMBER } val member = if (type == AutoProxyMode.MEMBER) { - val mem = database.findMember(system!!.id, value.interaction.command.strings["member"]!!) + val mem = database.findMember(system.id, value.interaction.command.strings["member"]!!) if (!checkMember(this, mem)) return@runs false mem } else null @@ -234,7 +234,7 @@ object MiscCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) serverAutoProxy(this, serverSystem, type, member) } } @@ -249,7 +249,7 @@ object MiscCommands { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false val message = value.interaction.command.integers["message"]?.toULong()?.let { Snowflake(it) } - editMessage(this, system!!, message, value.interaction.command.strings["content"]!!) + editMessage(this, system, message, value.interaction.command.strings["content"]!!) } } } @@ -308,7 +308,7 @@ object MiscCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) serverProxy(this, serverSystem, null) } literal("on", "enable") { @@ -323,7 +323,7 @@ object MiscCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) serverProxy(this, serverSystem, true) } } @@ -339,7 +339,7 @@ object MiscCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) serverProxy(this, serverSystem, false) } } @@ -349,34 +349,34 @@ object MiscCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - proxy(this, system!!, null, null) + proxy(this, system, null, null) } literal("off", "disable", "o") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - proxy(this, system!!, AutoProxyMode.OFF, null) + proxy(this, system, AutoProxyMode.OFF, null) } } literal("latch", "l") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - proxy(this, system!!, AutoProxyMode.LATCH, null) + proxy(this, system, AutoProxyMode.LATCH, null) } } literal("front", "f") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - proxy(this, system!!, AutoProxyMode.FRONT, null) + proxy(this, system, AutoProxyMode.FRONT, null) } } greedy("member") { getMem -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false proxy(this, system, AutoProxyMode.MEMBER, member) } @@ -395,7 +395,7 @@ object MiscCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) serverAutoProxy(this, serverSystem, null, null) } literal("off", "disable", "o") { @@ -410,7 +410,7 @@ object MiscCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) serverAutoProxy(this, serverSystem, AutoProxyMode.OFF, null) } } @@ -426,7 +426,7 @@ object MiscCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) serverAutoProxy(this, serverSystem, AutoProxyMode.FALLBACK, null) } } @@ -442,7 +442,7 @@ object MiscCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) serverAutoProxy(this, serverSystem, AutoProxyMode.LATCH, null) } } @@ -458,7 +458,7 @@ object MiscCommands { respondFailure("Cannot find server. Am I in it?") return@runs false } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system!!.id) + val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) serverAutoProxy(this, serverSystem, AutoProxyMode.FRONT, null) } } @@ -466,7 +466,7 @@ object MiscCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member)) return@runs false val guildId = getGuildId() ?: run { respondFailure("Command not ran in server.") @@ -537,13 +537,13 @@ object MiscCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false - deleteMessage(this, system!!, null) + deleteMessage(this, system, null) } int("message") { getMessage -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false - deleteMessage(this, system!!, Snowflake(getMessage())) + deleteMessage(this, system, Snowflake(getMessage())) } } } @@ -552,21 +552,21 @@ object MiscCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false - reproxyMessage(this, system!!, null, null) + reproxyMessage(this, system, null, null) } int("message") { getMessage -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false - reproxyMessage(this, system!!, Snowflake(getMessage()), null) + reproxyMessage(this, system, Snowflake(getMessage()), null) } greedy("member") { getMem -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member, true)) return@runs false - reproxyMessage(this, system, Snowflake(getMessage()), member!!) + reproxyMessage(this, system, Snowflake(getMessage()), member) } } } @@ -574,9 +574,9 @@ object MiscCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false - val member = database.findMember(system!!.id, getMem()) + val member = database.findMember(system.id, getMem()) if (!checkMember(this, member, true)) return@runs false - reproxyMessage(this, system, null, member!!) + reproxyMessage(this, system, null, member) } } } @@ -607,20 +607,20 @@ object MiscCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false - editMessage(this, system!!, null, null) + editMessage(this, system, null, null) } int("message") { getMessage -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false - editMessage(this, system!!, Snowflake(getMessage()), null) + editMessage(this, system, Snowflake(getMessage()), null) } greedy("content") { getContent -> runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false - editMessage(this, system!!, Snowflake(getMessage()), getContent()) + editMessage(this, system, Snowflake(getMessage()), getContent()) } } } @@ -628,7 +628,7 @@ object MiscCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false - editMessage(this, system!!, null, getContent()) + editMessage(this, system, null, getContent()) } } } @@ -669,7 +669,7 @@ object MiscCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - token(this, system!!) + token(this, system) } } @@ -684,41 +684,41 @@ object MiscCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - trust(this, system!!, getId(), null) + trust(this, system, getId(), null) } literal("none", "remove", "clear") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - trust(this, system!!, getId(), TrustLevel.NONE) + trust(this, system, getId(), TrustLevel.NONE) } } literal("access", "see", "view") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - trust(this, system!!, getId(), TrustLevel.ACCESS) + trust(this, system, getId(), TrustLevel.ACCESS) } } literal("member", "m") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - trust(this, system!!, getId(), TrustLevel.MEMBER) + trust(this, system, getId(), TrustLevel.MEMBER) } } literal("switch", "sw") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - trust(this, system!!, getId(), TrustLevel.SWITCH) + trust(this, system, getId(), TrustLevel.SWITCH) } } literal("full", "all", "everything") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - trust(this, system!!, getId(), TrustLevel.FULL) + trust(this, system, getId(), TrustLevel.FULL) } } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index dd7d1800..05467c65 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -54,7 +54,7 @@ object SwitchCommands { if (!checkSystem(this, system)) return@runs false val members = value.interaction.command.strings["members"]!!.split(",").toTypedArray() members.trimEach() - switch(this, system!!, members) + switch(this, system, members) } } subCommand("out", "Marks that no-one's fronting") { @@ -62,7 +62,7 @@ object SwitchCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - out(this, system!!) + out(this, system) } } subCommand("delete", "Deletes the latest switch") { @@ -73,7 +73,7 @@ object SwitchCommands { val switch = database.fetchLatestSwitch(system!!.id) if (!checkSwitch(this, switch)) return@runs false val oldSwitch = database.fetchSecondLatestSwitch(system.id) - delete(this, system, switch!!, oldSwitch) + delete(this, system, switch, oldSwitch) } } subCommand("move", "Moves the latest switch") { @@ -82,11 +82,11 @@ object SwitchCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val switch = database.fetchLatestSwitch(system!!.id) + val switch = database.fetchLatestSwitch(system.id) if (!checkSwitch(this, switch)) return@runs false val oldSwitch = database.fetchSecondLatestSwitch(system.id) val time = value.interaction.command.strings["time"]!! - move(this, system, switch!!, oldSwitch, time) + move(this, system, switch, oldSwitch, time) } } subCommand("list", "Lists your switches") { @@ -94,7 +94,7 @@ object SwitchCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - list(this, system!!) + list(this, system) } } } @@ -112,7 +112,7 @@ object SwitchCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - out(this, system!!) + out(this, system) } } literal("delete", "del", "remove", "rem") { @@ -122,26 +122,26 @@ object SwitchCommands { val switch = database.fetchLatestSwitch(system!!.id) if (!checkSwitch(this, switch)) return@runs false val oldSwitch = database.fetchSecondLatestSwitch(system.id) - delete(this, system, switch!!, oldSwitch) + delete(this, system, switch, oldSwitch) } } literal("move","mv","m") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val switch = database.fetchLatestSwitch(system!!.id) + val switch = database.fetchLatestSwitch(system.id) if (!checkSwitch(this, switch)) return@runs false val oldSwitch = database.fetchSecondLatestSwitch(system.id) - move(this, system, switch!!, oldSwitch, null) + move(this, system, switch, oldSwitch, null) } greedy("time") { getTime -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val switch = database.fetchLatestSwitch(system!!.id) + val switch = database.fetchLatestSwitch(system.id) if (!checkSwitch(this, switch)) return@runs false val oldSwitch = database.fetchSecondLatestSwitch(system.id) - move(this, system, switch!!, oldSwitch, getTime()) + move(this, system, switch, oldSwitch, getTime()) } } } @@ -149,14 +149,14 @@ object SwitchCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - list(this, system!!) + list(this, system) } } stringList("members") { getMembers -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - switch(this, system!!, getMembers().toTypedArray()) + switch(this, system, getMembers().toTypedArray()) } } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 1764431f..aa84187b 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -56,7 +56,7 @@ object SystemCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - access(this, system!!) + access(this, system) } } subCommand("create", "Create a system") { @@ -87,7 +87,7 @@ object SystemCommands { val name = value.interaction.command.strings["name"] val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - name(this, system!!, name, raw, clear) + name(this, system, name, raw, clear) } } subCommand("list", "List your system members") { @@ -99,7 +99,7 @@ object SystemCommands { if (!checkSystem(this, system)) return@runs false val byMessage = value.interaction.command.booleans["by-message"] ?: false val verbose = value.interaction.command.booleans["verbose"] ?: false - list(this, system!!, byMessage, verbose) + list(this, system, byMessage, verbose) } } access("system", "color") { @@ -110,7 +110,7 @@ object SystemCommands { if (!checkSystem(this, system)) return@runs false val color = value.interaction.command.strings["color"] - color(this, system!!, color?.toColor()) + color(this, system, color?.toColor()) } } access("system", "pronouns") { @@ -124,7 +124,7 @@ object SystemCommands { val pronouns = value.interaction.command.strings["pronouns"] val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - pronouns(this, system!!, pronouns, raw, clear) + pronouns(this, system, pronouns, raw, clear) } } access("system", "description") { @@ -139,7 +139,7 @@ object SystemCommands { val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - description(this, system!!, desc, raw, clear) + description(this, system, desc, raw, clear) } } access("system", "avatar") { @@ -152,7 +152,7 @@ object SystemCommands { val avatar = value.interaction.command.attachments["avatar"]?.data?.url val clear = value.interaction.command.booleans["clear"] ?: false - avatar(this, system!!, avatar, clear) + avatar(this, system, avatar, clear) } } access("system", "tag") { @@ -167,7 +167,7 @@ object SystemCommands { val raw = value.interaction.command.booleans["raw"] ?: false val clear = value.interaction.command.booleans["clear"] ?: false - tag(this, system!!, tag, raw, clear) + tag(this, system, tag, raw, clear) } } } @@ -179,7 +179,7 @@ object SystemCommands { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - list(this, system!!, false, false) + list(this, system, false, false) } unix("params") { getParams -> runs { @@ -188,7 +188,7 @@ object SystemCommands { val params = getParams().toTypedArray() val byMessage = hasUnixValue(params, "by-message-count") || hasUnixValue(params, "bmc") val verbose = hasUnixValue(params, "verbose") || hasUnixValue(params, "v") - list(this, system!!, byMessage, verbose) + list(this, system, byMessage, verbose) } } } @@ -214,33 +214,33 @@ object SystemCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - access(this, system!!) + access(this, system) } literal("name", "rename") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - name(this, system!!, null, false, false) + name(this, system, null, false, false) } unixLiteral("raw") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - name(this, system!!, null, true, false) + name(this, system, null, true, false) } } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - name(this, system!!, null, false, true) + name(this, system, null, false, true) } } greedy("name") { getName -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - name(this, system!!, getName(), false, false) + name(this, system, getName(), false, false) } } } @@ -248,7 +248,7 @@ object SystemCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - list(this, system!!, false, false) + list(this, system, false, false) } unix("params") { getParams -> runs { @@ -257,7 +257,7 @@ object SystemCommands { val params = getParams().toTypedArray() val byMessage = hasUnixValue(params, "by-message-count") || hasUnixValue(params, "bmc") val verbose = hasUnixValue(params, "verbose") || hasUnixValue(params, "v") - list(this, system!!, byMessage, verbose) + list(this, system, byMessage, verbose) } } } @@ -265,13 +265,13 @@ object SystemCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - color(this, system!!, null) + color(this, system, null) } greedy("color") { getColor -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - color(this, system!!, getColor().toColor()) + color(this, system, getColor().toColor()) } } } @@ -279,27 +279,27 @@ object SystemCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - pronouns(this, system!!, null, false, false) + pronouns(this, system, null, false, false) } unixLiteral("raw") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - pronouns(this, system!!, null, true, false) + pronouns(this, system, null, true, false) } } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - pronouns(this, system!!, null, false, true) + pronouns(this, system, null, false, true) } } greedy("pronouns") { getPronouns -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - pronouns(this, system!!, getPronouns(), false, false) + pronouns(this, system, getPronouns(), false, false) } } } @@ -307,27 +307,27 @@ object SystemCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - description(this, system!!, null, false, false) + description(this, system, null, false, false) } unixLiteral("raw") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - description(this, system!!, null, true, false) + description(this, system, null, true, false) } } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - description(this, system!!, null, false, true) + description(this, system, null, false, true) } } greedy("description") { getDesc -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - description(this, system!!, getDesc(), false, false) + description(this, system, getDesc(), false, false) } } } @@ -335,27 +335,27 @@ object SystemCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - avatar(this, system!!, null, false) + avatar(this, system, null, false) } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - avatar(this, system!!, null, true) + avatar(this, system, null, true) } } attachment("avatar") { getAvatar -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - avatar(this, system!!, getAvatar().url, false) + avatar(this, system, getAvatar().url, false) } } string("avatar") { getAvatar -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - avatar(this, system!!, getAvatar(), false) + avatar(this, system, getAvatar(), false) } } } @@ -363,27 +363,27 @@ object SystemCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - tag(this, system!!, null, false, false) + tag(this, system, null, false, false) } unixLiteral("raw") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - tag(this, system!!, null, true, false) + tag(this, system, null, true, false) } } unixLiteral("clear", "remove") { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - tag(this, system!!, null, false, true) + tag(this, system, null, false, true) } } greedy("description") { getTag -> runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - tag(this, system!!, getTag(), false, false) + tag(this, system, getTag(), false, false) } } } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt index ccfab885..6ec816f6 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt @@ -463,7 +463,7 @@ abstract class Database : AutoCloseable { suspend inline fun fetchTotalMembersFromUser(user: UserBehavior?) = fetchUser(user)?.systemId?.let { fetchTotalMembersFromSystem(it) } ?: -1 /** - * Gets the total number of members registered in a system by discord ID. + * Gets the total number of members registered in a system. * * Implementation requirements: return an int with the total members registered * */ @@ -484,6 +484,54 @@ abstract class Database : AutoCloseable { * */ abstract suspend fun fetchMembersFromGroup(group: GroupRecord): List + /** + * Fetches a group + * */ + abstract suspend fun fetchGroupFromSystem(system: PkId, groupId: PkId): GroupRecord? + abstract suspend fun fetchGroupsFromSystem(system: PkId): List? + abstract suspend fun fetchGroupFromSystemAndName( + system: PkId, + name: String, + caseSensitive: Boolean = false + ): GroupRecord? + + /** + * Updates a group + * */ + abstract suspend fun updateGroup(group: GroupRecord) + + /** + * Creates a group + * */ + suspend fun createGroup(system: PkId, name: String): GroupRecord? { + return fetchGroupFromSystemAndName(system, name) ?: createGroup(system, name, null) + } + + suspend fun createGroup(group: GroupRecord) = updateGroup(group) + + open suspend fun createGroup(systemId: String, name: String, id: String? = null): GroupRecord? { + fetchSystemFromId(systemId) ?: return null + val group = GroupRecord( + id = firstFreeMemberId(systemId, id), + systemId = systemId, + name = name, + ) + createGroup(group) + return group + } + + /** + * Gets a group by system ID and either group ID or name. + * */ + suspend fun findGroup(system: PkId, group: String): GroupRecord? = + if (group.startsWith("id:")) + fetchGroupFromSystem(system, group.substring(3)) + ?: fetchGroupFromSystemAndName(system, group, false) + else + fetchGroupFromSystemAndName(system, group, true) + ?: fetchGroupFromSystemAndName(system, group, false) + ?: fetchGroupFromSystem(system, group) + /** * Gets a member by system ID and either member ID or name. * */ @@ -564,6 +612,22 @@ abstract class Database : AutoCloseable { } open suspend fun firstFreeMemberId(systemId: String, id: String? = null): String { - return if (isMemberIdReserved(systemId, id)) fetchMembersFromSystem(systemId)?.map(MemberRecord::id)?.firstFree() ?: "aaaaa" else id + return if (isMemberIdReserved(systemId, id)) fetchMembersFromSystem(systemId)?.map(MemberRecord::id) + ?.firstFree() ?: "aaaaa" else id + } + + open suspend fun containsGroup(systemId: String, groupId: String) = fetchGroupFromSystem(systemId, groupId) != null + + @OptIn(ExperimentalContracts::class) + protected suspend fun isGroupIdReserved(systemId: String, groupId: String?): Boolean { + contract { + returns(false) implies (groupId != null) + } + return !systemId.isValidPkString() || !groupId.isValidPkString() || containsGroup(systemId, groupId) + } + + open suspend fun firstFreeGroupId(systemId: String, id: String? = null): String { + return if (isGroupIdReserved(systemId, id)) fetchGroupsFromSystem(systemId)?.map(GroupRecord::id)?.firstFree() + ?: "aaaaa" else id } } \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index ea6bf3de..7fbe5c68 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -326,6 +326,28 @@ class InMemoryDatabase : Database() { return members[group.systemId]?.let { group.members.mapNotNull(it::get) } ?: emptyList() } + override suspend fun fetchGroupFromSystem(system: PkId, groupId: String): GroupRecord? { + return groups[system]?.values?.find { it.id == groupId } + } + + override suspend fun fetchGroupsFromSystem(system: PkId): List? { + return groups[system]?.values?.toList() + } + + override suspend fun fetchGroupFromSystemAndName( + system: PkId, + name: String, + caseSensitive: Boolean + ): GroupRecord? { + return groups[system]?.values?.find { if (caseSensitive) name == it.name else name.lowercase() == it.name.lowercase() } + } + + override suspend fun updateGroup(group: GroupRecord) { + systems[group.systemId] ?: return + groups[group.systemId] ?: groups.set(group.systemId, hashMapOf()) + groups[group.systemId]?.set(group.id, group) + } + override suspend fun export(other: Database) { TODO("Not yet implemented") } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index 5e7a24ca..428f7692 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -355,7 +355,46 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { } override suspend fun fetchMembersFromGroup(group: GroupRecord): List { - TODO("Not yet implemented") + val out = arrayListOf() + group.members.forEach { + out.add(fetchMemberFromSystem(group.systemId, it) ?: return@forEach) + } + return out + } + + override suspend fun fetchGroupFromSystem(system: PkId, groupId: String): GroupRecord? { + return groups.find( + "systemId" eq system, + "id" eq groupId + ).awaitFirstOrNull() + } + + override suspend fun fetchGroupsFromSystem(system: PkId): List? { + if (!containsSystem(system)) return null + return groups.find( + "systemId" eq system, + ).toList() + } + + override suspend fun fetchGroupFromSystemAndName( + system: PkId, + name: String, + caseSensitive: Boolean + ): GroupRecord? { + var search = groups.find( + "systemId" eq system, + "name" eq name + ) + if (!caseSensitive) search = search.collation(Collation.builder().apply { + collationStrength(CollationStrength.SECONDARY) + caseLevel(false) + locale("en_US") + }.build()) + return search.awaitFirstOrNull() + } + + override suspend fun updateGroup(group: GroupRecord) { + groups.replaceOneById(group._id, group, upsert()).awaitFirst() } override suspend fun export(other: Database) { diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt index d2c4d880..ffff89ab 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt @@ -131,7 +131,12 @@ class NopDatabase : Database() { override suspend fun fetchTotalMembersFromSystem(systemId: String): Int? = null - override suspend fun fetchMemberFromSystemAndName(systemId: String, memberName: String, caseSensitive: Boolean): MemberRecord? = null + override suspend fun fetchMemberFromSystemAndName( + systemId: String, + memberName: String, + caseSensitive: Boolean + ): MemberRecord? = null + override suspend fun fetchGroupsFromMember(member: MemberRecord): List { return emptyList() } @@ -140,6 +145,26 @@ class NopDatabase : Database() { return emptyList() } + override suspend fun fetchGroupFromSystem(system: PkId, groupId: String): GroupRecord? { + return null + } + + override suspend fun fetchGroupsFromSystem(system: PkId): List? { + return null + } + + override suspend fun fetchGroupFromSystemAndName( + system: PkId, + name: String, + caseSensitive: Boolean + ): GroupRecord? { + return null + } + + override suspend fun updateGroup(group: GroupRecord) { + + } + override suspend fun export(other: Database) {} @Deprecated("Not for regular use.", level = DeprecationLevel.ERROR) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt index 2d632d70..bad60da2 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt @@ -210,6 +210,26 @@ open class ProxyDatabase(protected val proxy: T) : Database() { return proxy.fetchMembersFromGroup(group) } + override suspend fun fetchGroupFromSystem(system: PkId, groupId: String): GroupRecord? { + return proxy.fetchGroupFromSystem(system, groupId) + } + + override suspend fun fetchGroupsFromSystem(system: PkId): List? { + TODO("Not yet implemented") + } + + override suspend fun fetchGroupFromSystemAndName( + system: PkId, + name: String, + caseSensitive: Boolean + ): GroupRecord? { + return proxy.fetchGroupFromSystemAndName(system, name, caseSensitive) + } + + override suspend fun updateGroup(group: GroupRecord) { + proxy.updateGroup(group) + } + override suspend fun export(other: Database) { proxy.export(other) } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt index f85a12e6..eb0baa60 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/GroupRecord.kt @@ -9,10 +9,12 @@ package dev.proxyfox.database.records.group import dev.proxyfox.database.PkId +import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMillisecondSerializer import dev.proxyfox.database.records.MongoRecord +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable import org.bson.types.ObjectId -import java.time.OffsetDateTime -import java.time.ZoneOffset class GroupRecord() : MongoRecord { constructor(id: PkId, systemId: PkId, name: String) : this() { @@ -30,7 +32,9 @@ class GroupRecord() : MongoRecord { var description: String? = null var color: Int = -1 var avatarUrl: String? = null - var timestamp: OffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC) + + @Serializable(InstantLongMillisecondSerializer::class) + var timestamp: Instant = Clock.System.now() var tag: String? = null var tagMode: TagMode = TagMode.HIDDEN } \ No newline at end of file diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt index 02e48bee..348b04ed 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt @@ -12,4 +12,13 @@ enum class TagMode { HIDDEN, BEFORE, AFTER + ; + + fun getDisplayString(): String { + return when (this) { + HIDDEN -> "None" + BEFORE -> "Before System" + AFTER -> "After System" + } + } } \ No newline at end of file From 9c4e4eabc84cacc66a195d3f77a679c41465fa73 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 16 Jan 2023 20:05:01 -0600 Subject: [PATCH 081/137] Apply licenses --- .../main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt index 1fc03733..d49ce226 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command import dev.kord.core.Kord From 33cd92e97f97fded35f32cc426d2ca7b57f013bb Mon Sep 17 00:00:00 2001 From: tibs Date: Tue, 17 Jan 2023 00:10:06 -0500 Subject: [PATCH 082/137] More API changes, feat: actual tokens --- .../main/kotlin/dev/proxyfox/api/ApiMain.kt | 8 ++-- .../kotlin/dev/proxyfox/api/Authentication.kt | 31 +++++++++++-- .../kotlin/dev/proxyfox/api/models/Token.kt | 18 ++++++++ .../dev/proxyfox/api/routes/MemberRoutes.kt | 5 ++- .../dev/proxyfox/api/routes/SwitchRoutes.kt | 12 +++-- .../dev/proxyfox/api/routes/SystemRoutes.kt | 13 +++--- .../dev/proxyfox/api/routes/TokenRoutes.kt | 15 +++++++ .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 1 + .../kotlin/dev/proxyfox/database/Database.kt | 14 +++++- .../dev/proxyfox/database/DatabaseUtil.kt | 4 +- .../dev/proxyfox/database/InMemoryDatabase.kt | 4 +- .../database/records/group/TagMode.kt | 2 +- .../database/records/misc/TokenRecord.kt | 9 ++-- .../database/records/misc/TokenType.kt | 45 +++++++++++++++++++ 14 files changed, 150 insertions(+), 31 deletions(-) create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt create mode 100644 modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt index c8b17340..72c98a7d 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt @@ -8,10 +8,7 @@ package dev.proxyfox.api -import dev.proxyfox.api.routes.memberRoutes -import dev.proxyfox.api.routes.messageRoutes -import dev.proxyfox.api.routes.switchRoutes -import dev.proxyfox.api.routes.systemRoutes +import dev.proxyfox.api.routes.* import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* import io.ktor.server.engine.* @@ -39,7 +36,8 @@ object ApiMain { memberRoutes() switchRoutes() messageRoutes() + tokenRoutes() } } } -} \ No newline at end of file +} diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt index 72096a60..647d25a8 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt @@ -9,12 +9,37 @@ package dev.proxyfox.api import dev.proxyfox.database.database +import dev.proxyfox.database.records.misc.TokenType +import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.util.* +import io.ktor.util.pipeline.* -val AuthenticationPlugin = createRouteScopedPlugin(name = "AuthenticationPlugin") { +@Suppress("FunctionName") +fun ApiPlugin(name: String, accessFunction: TokenType.() -> Boolean) = createRouteScopedPlugin(name = name) { onCall { call -> - val token = call.request.headers["Authorization"] ?: return@onCall call.respond("401 Unauthorized") - database.fetchToken(token) ?: return@onCall call.respond("401 Unauthorized") + val tokenString = call.request.headers["Authorization"] ?: return@onCall call.respond("401 Unauthorized") + val token = database.fetchToken(tokenString) ?: return@onCall call.respond("401 Unauthorized") + if (token.type.accessFunction()) return@onCall call.respond("401 Unauthorized") + } +} + +val AccessPlugin = ApiPlugin("AccessPlugin", TokenType::canViewApi) + +val EditPlugin = ApiPlugin("EditPlugin", TokenType::canEditApi) + +@KtorDsl +fun Route.getAccess(body: PipelineInterceptor) { + install(AccessPlugin) + get(body) +} + +@KtorDsl +fun Route.getAccess(path: String, body: PipelineInterceptor) { + route(path, HttpMethod.Get) { + install(AccessPlugin) + handle(body) } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt new file mode 100644 index 00000000..2652f579 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt @@ -0,0 +1,18 @@ +package dev.proxyfox.api.models + +import dev.proxyfox.database.records.misc.TokenRecord +import dev.proxyfox.database.records.misc.TokenType +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Token( + val token: String, + @SerialName("system_id") + val systemId: String, + val type: TokenType +) { + companion object { + fun fromRecord(record: TokenRecord) = Token(record.token, record.systemId, record.type) + } +} diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt index 09affd0e..427af381 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt @@ -9,7 +9,7 @@ package dev.proxyfox.api.routes import dev.kord.common.entity.Snowflake -import dev.proxyfox.api.AuthenticationPlugin +import dev.proxyfox.api.AccessPlugin import dev.proxyfox.api.models.Member import dev.proxyfox.api.models.MemberGuildSettings import dev.proxyfox.database.database @@ -19,13 +19,14 @@ import io.ktor.server.routing.* fun Route.memberRoutes() { route("/systems/{id}/members") { - install(AuthenticationPlugin) + install(AccessPlugin) get { val id = call.parameters["id"] ?: return@get call.respond("System not found") call.respond(database.fetchMembersFromSystem(id)?.map(Member.Companion::fromRecord) ?: emptyList()) } route("/{member}") { + install(AccessPlugin) get { val id = call.parameters["id"] ?: return@get call.respond("System not found") val member = database.fetchMemberFromSystem(id, call.parameters["member"]!!) ?: return@get call.respond("Member not found") diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt index b89876fa..868f668b 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt @@ -8,7 +8,7 @@ package dev.proxyfox.api.routes -import dev.proxyfox.api.AuthenticationPlugin +import dev.proxyfox.api.getAccess import dev.proxyfox.api.models.Member import dev.proxyfox.api.models.Switch import dev.proxyfox.database.database @@ -18,17 +18,15 @@ import io.ktor.server.routing.* fun Route.switchRoutes() { route("/systems/{id}/switches") { - install(AuthenticationPlugin) - get { - val id = call.parameters["id"] ?: return@get call.respond("System not found") + getAccess { + val id = call.parameters["id"] ?: return@getAccess call.respond("System not found") call.respond(database.fetchSwitchesFromSystem(id)?.map(Switch.Companion::fromRecord) ?: emptyList()) } } route("/systems/{id}/fronters") { - install(AuthenticationPlugin) - get { - val id = call.parameters["id"] ?: return@get call.respond("System not found") + getAccess { + val id = call.parameters["id"] ?: return@getAccess call.respond("System not found") call.respond(database.fetchFrontingMembersFromSystem(id)?.map(Member.Companion::fromRecord) ?: emptyList()) } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt index 26ca2a22..08902adc 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt @@ -9,7 +9,7 @@ package dev.proxyfox.api.routes import dev.kord.common.entity.Snowflake -import dev.proxyfox.api.AuthenticationPlugin +import dev.proxyfox.api.getAccess import dev.proxyfox.api.models.System import dev.proxyfox.api.models.SystemGuildSettings import dev.proxyfox.database.database @@ -19,16 +19,15 @@ import io.ktor.server.routing.* fun Route.systemRoutes() { route("/systems/{id}") { - install(AuthenticationPlugin) - get { - val system = database.fetchSystemFromId(call.parameters["id"]!!) ?: return@get call.respond("System not found") + getAccess { + val system = database.fetchSystemFromId(call.parameters["id"]!!) ?: return@getAccess call.respond("System not found") call.respond(System.fromRecord(system)) } - get("/guilds/{guild}") { - val id = call.parameters["id"] ?: return@get call.respond("System not found") + getAccess("/guilds/{guild}") { + val id = call.parameters["id"] ?: return@getAccess call.respond("System not found") val settings = database.getOrCreateServerSettingsFromSystem(Snowflake(call.parameters["guild"]!!).value, id) call.respond(SystemGuildSettings.fromRecord(settings)) } } -} \ No newline at end of file +} diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt new file mode 100644 index 00000000..4ac0aa47 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt @@ -0,0 +1,15 @@ +package dev.proxyfox.api.routes + +import dev.proxyfox.api.models.Token +import dev.proxyfox.database.database +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Route.tokenRoutes() { + get("/tokens") { + val tokenString = call.request.headers["Authorization"] ?: return@get call.respond("404: Not Found") + val token = database.fetchToken(tokenString) ?: return@get call.respond("404: Not Found") + call.respond(Token.fromRecord(token)) + } +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index f252c211..3f6c1832 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -198,6 +198,7 @@ suspend fun Kord.registerApplicationCommands() { // Only send commands when discord hasn't registered yet val file = File("./.pf-command-lock") if (!file.exists()) { + file. deferredCommands.forEach { scope.launch { rest.interaction.createGlobalApplicationCommand( diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt index 6ec816f6..36344201 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt @@ -318,9 +318,21 @@ abstract class Database : AutoCloseable { abstract suspend fun fetchMessage(messageId: Snowflake): ProxiedMessageRecord? abstract suspend fun fetchLatestMessage(systemId: String, channelId: Snowflake): ProxiedMessageRecord? + open suspend fun createToken(systemId: String, type: TokenType): TokenRecord { + val token = TokenRecord(generateUniqueToken(), systemId, type) + updateToken(token) + return token + } abstract suspend fun fetchToken(token: String): TokenRecord? - abstract suspend fun updateToken(token: TokenRecord) + open suspend fun containsToken(token: String): Boolean = fetchToken(token) != null + open suspend fun generateUniqueToken(): String { + var token = generateToken() + while (containsToken(token)) { + token = generateToken() + } + return token + } /** * Allocates a proxy tag diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt index 8139de68..cdd8c5b2 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/DatabaseUtil.kt @@ -118,8 +118,10 @@ fun databaseFromString(db: String?) = else -> throw IllegalArgumentException("Unknown database $db") } +@Suppress("NOTHING_TO_INLINE", "UNUSED") inline fun unsupported(message: String = "Not implemented"): Nothing = throw UnsupportedOperationException(message) +@Suppress("UNUSED") inline fun Array.mapArray(action: (T) -> R): Array { return Array(size) { action(this[it]) } } @@ -134,7 +136,7 @@ suspend inline fun Mongo.getOrCreateCollection(): MongoCollect } fun generateToken(): String { - val buffer = ByteArray(24) + val buffer = ByteArray(96) secureRandom.nextBytes(buffer) return Base64.getUrlEncoder().encodeToString(buffer) } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index 7fbe5c68..2d7f79e1 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -266,7 +266,9 @@ class InMemoryDatabase : Database() { } override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { - memberProxies[record.memberId]?.add(record) + systems[record.systemId] ?: return false + memberProxies[record.systemId] ?: memberProxies.set(record.systemId, arrayListOf()) + memberProxies[record.systemId]!!.add(record) return true } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt index 348b04ed..49954f1a 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/group/TagMode.kt @@ -21,4 +21,4 @@ enum class TagMode { AFTER -> "After System" } } -} \ No newline at end of file +} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt index 3149ae97..9d095da4 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt @@ -8,6 +8,7 @@ package dev.proxyfox.database.records.misc +import dev.proxyfox.database.PkId import dev.proxyfox.database.records.MongoRecord import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable @@ -17,11 +18,13 @@ import org.bson.types.ObjectId class TokenRecord : MongoRecord { @Contextual override var _id: ObjectId = ObjectId() - var token: String = "" - var systemId: String = "" + var token: String + var systemId: PkId + var type: TokenType - constructor(token: String, systemId: String) { + constructor(token: String, systemId: PkId, type: TokenType) { this.token = token this.systemId = systemId + this.type = type } } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt new file mode 100644 index 00000000..88ddef05 --- /dev/null +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.database.records.misc + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@Serializable(with = TokenType.Serializer::class) +enum class TokenType(private val actualName: String) { + SYSTEM_TRANSFER("system:transfer"), + API_ACCESS("api:access"), + API_EDIT("api:edit") + ; + + override fun toString(): String { + return actualName + } + + fun canViewApi() = this == API_ACCESS || canEditApi() + fun canEditApi() = this == API_EDIT + + companion object { + fun of(name: String): TokenType? { + for (type in TokenType.values()) { + if (type.toString() == name) return type + } + return null + } + } + class Serializer : KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("tokenType") + override fun serialize(encoder: Encoder, value: TokenType) = encoder.encodeString(value.toString()) + override fun deserialize(decoder: Decoder): TokenType = TokenType.of(decoder.decodeString())!! + } +} From fa5f19fd88a6ef3f61efd056c5edc5e5da9f58a8 Mon Sep 17 00:00:00 2001 From: tibs Date: Tue, 17 Jan 2023 00:27:14 -0500 Subject: [PATCH 083/137] Apply licenses --- .../api/src/main/kotlin/dev/proxyfox/api/models/Token.kt | 8 ++++++++ .../main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt | 8 ++++++++ .../dev/proxyfox/database/records/misc/TokenType.kt | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt index 2652f579..afca1e58 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.models import dev.proxyfox.database.records.misc.TokenRecord diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt index 4ac0aa47..9fa9ae05 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.routes import dev.proxyfox.api.models.Token diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt index 88ddef05..2279dfa0 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023, The ProxyFox Group + * Copyright (c) 2023, The ProxyFox Group * * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this From a85bead6410b40c6dbdffa129a8075b8a1fa9e1a Mon Sep 17 00:00:00 2001 From: Ampflower Date: Fri, 27 Jan 2023 21:38:38 -0600 Subject: [PATCH 084/137] Exporter now handles missing members in a way to not break importer. --- .../database/etc/exporter/Exporter.kt | 34 +++++++++++++++---- .../proxyfox/database/etc/types/PkTypes.kt | 13 +++++-- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt index 2e7ecb43..99948a1d 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/exporter/Exporter.kt @@ -10,24 +10,46 @@ package dev.proxyfox.database.etc.exporter import dev.proxyfox.database.Database import dev.proxyfox.database.database +import dev.proxyfox.database.etc.types.* import dev.proxyfox.database.gson -import dev.proxyfox.database.etc.types.PkMember -import dev.proxyfox.database.etc.types.PkProxy -import dev.proxyfox.database.etc.types.PkSwitch -import dev.proxyfox.database.etc.types.PkSystem +import dev.proxyfox.database.pkCompatibleIso8601 +import dev.proxyfox.database.records.member.MemberRecord +import dev.proxyfox.database.records.system.SystemSwitchRecord object Exporter { suspend inline fun export(userId: ULong) = export(database, userId) suspend fun export(database: Database, userId: ULong): String { val system = database.fetchSystemFromUser(userId) ?: return "" + val members = database.fetchMembersFromSystem(system.id) + val memberIds = members?.mapTo(HashSet(), MemberRecord::id) ?: setOf() + + // Nested function for mapping switches to PkSwitch exports. + fun toPkSwitch(record: SystemSwitchRecord): PkSwitch { + // Note, ArrayList is used here to retain order. + val existing = ArrayList(record.memberIds) + + // If retainAll modifies the list, take the slow route. + if (existing.retainAll(memberIds)) { + return PkSwitch( + timestamp = record.timestamp.pkCompatibleIso8601(), + members = existing.toList(), + + proxyfox = PfSwitchExtension( + allMembers = record.memberIds, + ) + ) + } + + return PkSwitch(record) + } val pkSystem = PkSystem( system, - members = database.fetchMembersFromSystem(system.id)?.map { + members = members?.map { PkMember(it, database.fetchProxiesFromSystemAndMember(system.id, it.id)?.mapTo(HashSet(), ::PkProxy)) }, - switches = database.fetchSwitchesFromSystem(system.id)?.map(::PkSwitch), + switches = database.fetchSwitchesFromSystem(system.id)?.map(::toPkSwitch), ) return gson.toJson(pkSystem) } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt index 971f48f1..1389bacf 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/types/PkTypes.kt @@ -11,14 +11,14 @@ package dev.proxyfox.database.etc.types import com.google.gson.annotations.SerializedName import dev.proxyfox.common.fromColorForExport import dev.proxyfox.database.* +import dev.proxyfox.database.etc.gson.NullValueProcessor +import dev.proxyfox.database.etc.gson.UnexpectedValueProcessor import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.TrustLevel import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemSwitchRecord -import dev.proxyfox.database.etc.gson.NullValueProcessor -import dev.proxyfox.database.etc.gson.UnexpectedValueProcessor import java.time.LocalDate import java.time.OffsetDateTime import java.time.format.DateTimeFormatter @@ -218,6 +218,9 @@ data class PkSwitch( val timestamp: String?, val members: List?, + /** Allows for storing missing member data */ + val proxyfox: PfSwitchExtension? = null, + // Ignored for PFv1 database imports @Deprecated("PFv1 database imports only") val id: Void? = null, @@ -329,6 +332,12 @@ data class PfMemberExtension( val autoProxy: Boolean?, ) +@JvmRecord +data class PfSwitchExtension( + /** Note: It is *not* possible to reimport this. */ + val allMembers: List?, +) + @Suppress("EnumEntryName") enum class PkPrivacyEnum { public, private From 0451b2a42b77fd6891063eced0e024345f66df34 Mon Sep 17 00:00:00 2001 From: Octal Date: Tue, 31 Jan 2023 18:58:10 -0600 Subject: [PATCH 085/137] Fix merge conflicts n stuff --- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index f17d04ed..cf4e01b4 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -198,7 +198,9 @@ suspend fun Kord.registerApplicationCommands() { // Only send commands when discord hasn't registered yet val file = File("./.pf-command-lock") if (!file.exists()) { - file. + withContext(Dispatchers.IO) { + file.createNewFile() + } deferredCommands.forEach { scope.launch { rest.interaction.createGlobalApplicationCommand( From 94464c8dd07cdd22f57328ac5d370699704e17c9 Mon Sep 17 00:00:00 2001 From: Octal Date: Sat, 11 Feb 2023 14:02:15 -0600 Subject: [PATCH 086/137] Migrate to MarKt --- gradle/libs.versions.toml | 6 +- .../main/kotlin/dev/proxyfox/bot/BotMain.kt | 3 + .../kotlin/dev/proxyfox/bot/md/Markdown.kt | 124 ------------------ .../dev/proxyfox/bot/webhook/ProxyContext.kt | 40 +++--- 4 files changed, 27 insertions(+), 146 deletions(-) delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/md/Markdown.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cda656db..f2be4937 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,8 @@ kotlinx_coroutines = "1.6.4" # ProxyFox Libraries proxyfox_command = "1.7" -pluralkt = "1.3" +pluralkt = "1.8" +markt = "1.0" # Database postgres = "42.3.3" @@ -33,6 +34,7 @@ logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } proxyfox_command = { module = "dev.proxyfox:proxyfox-command", version.ref = "proxyfox_command" } pluralkt = { module = "dev.proxyfox:pluralkt", version.ref = "pluralkt" } +markt = { module = "dev.proxyfox:MarKt", version.ref = "markt" } kotlin_stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlinx_coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx_coroutines" } @@ -54,7 +56,7 @@ mockk = { module = "io.mockk:mockk", version.ref = "mockk" } kotlinx_coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx_coroutines" } [bundles] -base = ["guava", "logback", "kotlin_stdlib", "kotlinx_coroutines_core", "kord", "proxyfox_command", "pluralkt"] +base = ["guava", "logback", "kotlin_stdlib", "kotlinx_coroutines_core", "kord", "proxyfox_command", "pluralkt", "markt"] database = ["kmongo_base", "kmongo_coroutine", "kmongo_async", "kotlinx_datetime"] api = ["ktor_server", "ktor_server_netty", "ktor_content_negotiation", "ktor_serialization"] test = ["testng", "kotlinx_coroutines_test", "mockk"] diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt index 62c7ff2c..a3640e66 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt @@ -12,6 +12,7 @@ import dev.proxyfox.api.ApiMain import dev.proxyfox.bot.terminal.TerminalCommands import dev.proxyfox.common.printFancy import dev.proxyfox.database.DatabaseMain +import dev.proxyfox.markt.MarkdownParser suspend fun main(args: Array) = BotMain.main(args) @@ -25,6 +26,8 @@ object BotMain { printFancy("Initializing ProxyFox") + MarkdownParser.addDefaultRules() + // Setup database DatabaseMain.main(findUnixValue(args, "--database=")) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/md/Markdown.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/md/Markdown.kt deleted file mode 100644 index 1ed9a820..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/md/Markdown.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2022-2023, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.md - -interface MarkdownNode { - val length: Int - val trueLength: Int - - override fun toString(): String - fun substring(len: Int): MarkdownNode -} - -class MarkdownString(val string: String) : MarkdownNode { - override val length: Int - get() = string.length - override val trueLength: Int - get() = string.length - - override fun toString(): String = string - override fun substring(len: Int): MarkdownNode { - return MarkdownString(string.substring(0, len.coerceAtMost(string.length))) - } -} - -class BaseMarkdown(val symbol: String) : MarkdownNode { - val values = ArrayList() - - override val length: Int - get() { - var int = 0 - for (value in values) { - int += value.length - } - return int - } - override val trueLength: Int - get() { - var int = symbol.length + symbol.length - for (value in values) { - int += value.length - } - return int - } - - override fun toString(): String { - var out = "" - out += symbol - for (value in values) { - out += value.toString() - } - out += symbol - return out - } - - override fun substring(len: Int): MarkdownNode { - if (trueLength < len) return this - var i = 0 - val out = BaseMarkdown(symbol) - for (value in values) { - if (i + value.length > len) { - out.values.add(value.substring(len - i)) - break - } - out.values.add(value) - i += value.length - } - return out - } -} - -enum class MarkdownSymbols(val symbol: String) { - CODE_MULTILINE("```"), - CODE_DOUBLE("``"), - SPOILER("||"), - BOLD("**"), - STRIKETHROUGH("~~"), - UNDERLINE("__"), - ITALIC_STAR("*"), - ITALIC_UNDER("_"), - CODE("`") -} - -// TODO: Parse out more complex markdowns -fun parseMarkdown(string: String, symbol: String = ""): BaseMarkdown { - val base = BaseMarkdown(symbol) - var idx = 0 - var lastIdx = 0 - while (idx < string.length) { - val substr = string.substring(idx) - for (sym in MarkdownSymbols.values()) { - if (substr.startsWith(sym.symbol)) { - var currIdx = sym.symbol.length - while (currIdx < substr.length) { - val subsubstr = substr.substring(currIdx) - if (subsubstr.startsWith(sym.symbol)) { - base.values.add(MarkdownString(string.substring(lastIdx, idx))) - val md = parseMarkdown( - substr.substring( - sym.symbol.length, - (currIdx).coerceAtMost(substr.length) - ), - sym.symbol - ) - base.values.add(md) - idx += md.trueLength - lastIdx = idx - break - } - currIdx++ - } - break - } - } - idx++ - } - base.values.add(MarkdownString(string.substring(lastIdx, idx.coerceAtMost(string.length)))) - return base -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt index 95fc17e9..6a998c21 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt @@ -17,14 +17,14 @@ import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException import dev.proxyfox.bot.http import dev.proxyfox.bot.kord -import dev.proxyfox.bot.md.BaseMarkdown -import dev.proxyfox.bot.md.MarkdownString -import dev.proxyfox.bot.md.parseMarkdown import dev.proxyfox.common.ellipsis import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord +import dev.proxyfox.markt.MarkdownParser +import dev.proxyfox.markt.RootNode +import dev.proxyfox.markt.StringNode import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.client.statement.* @@ -89,24 +89,24 @@ data class ProxyContext( } } } else message.referencedMessage?.let { ref -> - // Kord's official methods don't return a user if it's a webhook - val user = User(ref.data.author, kord) - val link = "https://discord.com/channels/${ref.getGuild().id}/${ref.channelId}/${ref.id}" - embed { - color = Color(member.color) - author { - name = (ref.getAuthorAsMember()?.displayName ?: user.username) + " ↩️" - icon = user.avatar?.url ?: user.defaultAvatar.url - url = link - } - var msgRef = parseMarkdown(ref.content) - if (msgRef.length > 100) { - // We know it's gonna be a BaseMarkdown so - msgRef = msgRef.substring(100) as BaseMarkdown - msgRef.values.add(MarkdownString(ellipsis)) + // Kord's official methods don't return a user if it's a webhook + val user = User(ref.data.author, kord) + val link = "https://discord.com/channels/${ref.getGuild().id}/${ref.channelId}/${ref.id}" + embed { + color = Color(member.color) + author { + name = (ref.getAuthorAsMember()?.displayName ?: user.username) + " ↩️" + icon = user.avatar?.url ?: user.defaultAvatar.url + url = link + } + var msgRef = MarkdownParser.parse(ref.content) + if (msgRef.length > 100) { + // We should be getting a RootNode returned here. + msgRef = msgRef.truncate(100) as RootNode + msgRef.nodes.add(StringNode(ellipsis)) + } + description = "[**Reply to:**]($link) $msgRef" } - description = "[**Reply to:**]($link) $msgRef" - } } } } catch (e: KtorRequestException) { From 41ae20352504b7f27e80a48a8a9ca5f0e0902de7 Mon Sep 17 00:00:00 2001 From: Octal Date: Sat, 11 Feb 2023 14:03:27 -0600 Subject: [PATCH 087/137] Preliminaries for PK Sync --- .../dev/proxyfox/bot/command/MiscCommands.kt | 20 +++++ .../main/kotlin/dev/proxyfox/common/Util.kt | 21 +++-- .../proxyfox/common/annotations/DontExpose.kt | 5 ++ .../database/records/system/SystemRecord.kt | 4 + .../main/kotlin/dev/proxyfox/sync/PkSync.kt | 78 +++++++++++++++---- 5 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 modules/common/src/main/kotlin/dev/proxyfox/common/annotations/DontExpose.kt diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index cad61f35..9c4267b7 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -723,6 +723,26 @@ object MiscCommands { } } } + + Commands.parser.literal("pluralkit", "pk") { + literal("pull", "get", "download") { + + } + + literal("push", "set", "upload") { + + } + + literal("token") { + literal("clear", "reset", "remove") { + + } + + greedy("token") { getToken -> + + } + } + } } private suspend fun forceTag(ctx: DiscordContext, enabled: Boolean?): Boolean { diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt index addcc43b..c4838420 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt @@ -8,6 +8,7 @@ package dev.proxyfox.common +import org.slf4j.Logger import org.slf4j.LoggerFactory import java.lang.management.* import java.nio.charset.Charset @@ -27,16 +28,20 @@ const val ellipsis = "…" fun printFancy(input: String) { val edges = "*".repeat(input.length + 4) - logger.info(edges) - logger.info("* $input *") - logger.info(edges) + logger..edges.."* $input *"..edges } fun printStep(input: String, step: Int) { - val add = " ".repeat(step) - logger.info(step.toString() + add + input) + logger.." " * step + input } +operator fun Logger.rangeTo(string: String): Logger { + info(string) + return this +} + +operator fun String.times(n: Int) = repeat(n) + fun String?.toColor(): Int { return if (this == null || this == "") -1 else (toUIntOrNull(16)?.toInt() ?: Integer.decode(this)) and 0xFFFFFF } @@ -87,4 +92,8 @@ fun Array.trimEach() { forEachIndexed { i, s -> this[i] = s.trim() } -} \ No newline at end of file +} + +fun Throwable?.throwIfPresent() { + throw this ?: return +} diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/annotations/DontExpose.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/annotations/DontExpose.kt new file mode 100644 index 00000000..31d3e36e --- /dev/null +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/annotations/DontExpose.kt @@ -0,0 +1,5 @@ +package dev.proxyfox.common.annotations + +@Retention(AnnotationRetention.BINARY) +@RequiresOptIn("You shouldn't expose this publicly!") +annotation class DontExpose(val reason: String) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt index 718a5e87..b8109341 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/system/SystemRecord.kt @@ -8,6 +8,7 @@ package dev.proxyfox.database.records.system +import dev.proxyfox.common.annotations.DontExpose import dev.proxyfox.database.PkId import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMillisecondSerializer import dev.proxyfox.database.records.MongoRecord @@ -48,6 +49,9 @@ open class SystemRecord : MongoRecord { var autoType: AutoProxyMode = AutoProxyMode.OFF var trust: HashMap = HashMap() + @DontExpose("PluralKit Tokens grant access to edit systems in PK's API!") + var pkToken: String? = null + val showName get() = name?.let { "$it [`$id`]" } ?: "`$id`" fun canAccess(user: ULong): Boolean { diff --git a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt index 0ad09f28..f56dc3d9 100644 --- a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt +++ b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt @@ -8,26 +8,76 @@ package dev.proxyfox.sync +import com.google.common.collect.HashBiMap +import dev.proxyfox.common.annotations.DontExpose +import dev.proxyfox.common.throwIfPresent +import dev.proxyfox.database.database import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.pluralkt.PluralKt -import dev.proxyfox.pluralkt.types.PkSystem +import dev.proxyfox.pluralkt.Response +import dev.proxyfox.pluralkt.types.PkError +import dev.proxyfox.pluralkt.types.PkMember object PkSync { - fun pull(token: String, system: SystemRecord) { - PluralKt.System.getMe(token) { - if (isSuccess()) { - getSuccess().getMembers(token) - } - } - } + private fun Response.getSuccessOrNull() = if (isSuccess()) getSuccess() else null + + @OptIn(DontExpose::class) + suspend fun pull(system: SystemRecord): PkError? { + val token = system.pkToken ?: return null + + val systemResp = PluralKt.System.getMe(token).await() + systemResp.getException().throwIfPresent() + val pkSystem = systemResp.getSuccessOrNull() ?: return systemResp.getError() + + system.name = pkSystem.name ?: system.name + system.description = pkSystem.description ?: system.description + system.tag = pkSystem.tag ?: system.tag + system.avatarUrl = pkSystem.avatarUrl ?: system.avatarUrl + system.color = pkSystem.color?.color ?: system.color + system.pronouns = pkSystem.pronouns ?: system.pronouns + + val membersResp = PluralKt.Member.getMembers(pkSystem.id, token).await() + membersResp.getException().throwIfPresent() + val pkMembers = membersResp.getSuccessOrNull() ?: return membersResp.getError() - private fun PkSystem.getMembers(token: String) { - PluralKt.Member.getMembers(id, token) { - if (isSuccess()) { - for (member in getSuccess()) { - //... + val memberToIdLookup = HashBiMap.create() + val idToIdLookup = HashBiMap.create() + + for (pkMember in pkMembers) { + val member = database.fetchMemberFromSystem(system.id, pkMember.id) ?: database.getOrCreateMember( + system.id, + pkMember.name + )!! + memberToIdLookup[pkMember] = member.id + idToIdLookup[pkMember.id] = member.id + + member.name = pkMember.name + member.pronouns = pkMember.pronouns + member.avatarUrl = pkMember.avatarUrl + member.color = pkMember.color?.color ?: member.color + member.description = pkMember.description + member.displayName = pkMember.displayName + member.timestamp = pkMember.created ?: member.timestamp + member.keepProxy = pkMember.keepProxy + database.updateMember(member) + + val proxies = database.fetchProxiesFromSystemAndMember(system.id, member.id)!! + + for (pkProxy in pkMember.proxyTags) { + var hasProxy = false + for (proxy in proxies) { + if (proxy.prefix == pkProxy.prefix && proxy.suffix == pkProxy.suffix) { + hasProxy = true + } + } + if (!hasProxy) { + database.createProxyTag(system.id, member.id, pkProxy.prefix, pkProxy.suffix) } } } + + // TODO: groups once implemented + + return null } -} \ No newline at end of file +} From f96963607aba52404973ee0793b3bb26400e0182 Mon Sep 17 00:00:00 2001 From: Octal Date: Sat, 11 Feb 2023 14:22:52 -0600 Subject: [PATCH 088/137] License header for DontExpose --- .../kotlin/dev/proxyfox/common/annotations/DontExpose.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/annotations/DontExpose.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/annotations/DontExpose.kt index 31d3e36e..e835a989 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/annotations/DontExpose.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/annotations/DontExpose.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.common.annotations @Retention(AnnotationRetention.BINARY) From b85ab999a5bae680358d330be9384dd58c7b2ac2 Mon Sep 17 00:00:00 2001 From: Octal Date: Sat, 11 Feb 2023 14:29:40 -0600 Subject: [PATCH 089/137] Bump Quilt Gradle Licenser --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f2be4937..7de75f89 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ mockk = "1.+" # Plugins shadow = "7.1.2" -licenser = "1.1.2" +licenser = "1.2.0" [libraries] guava = { module = "com.google.guava:guava", version.ref = "guava" } From 41814647f29fca887dd47ac3257908d303f1e13e Mon Sep 17 00:00:00 2001 From: Octal Date: Thu, 16 Feb 2023 19:58:43 -0600 Subject: [PATCH 090/137] Fix slash command registration taking eons --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 23 ++++++------------- .../ProxyFoxChatInputCreateBuilderImpl.kt | 2 +- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index cf4e01b4..9eb3824d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -55,7 +55,6 @@ import kotlinx.coroutines.flow.count import kotlinx.coroutines.flow.fold import kotlinx.datetime.Clock import kotlinx.datetime.Instant -import java.io.File import java.lang.Integer.* import java.util.* import java.util.concurrent.Executors @@ -195,26 +194,18 @@ suspend fun Kord.registerApplicationCommands() { registerMemberCommands() registerSwitchCommands() registerMiscCommands() - // Only send commands when discord hasn't registered yet - val file = File("./.pf-command-lock") - if (!file.exists()) { - withContext(Dispatchers.IO) { - file.createNewFile() - } - deferredCommands.forEach { - scope.launch { - rest.interaction.createGlobalApplicationCommand( - resources.applicationId, - it - ) - } - } + + scope.launch { + rest.interaction.createGlobalApplicationCommands( + resources.applicationId, + deferredCommands + ) } } val deferredCommands = arrayListOf() -fun Kord.deferChatInputCommand( +fun deferChatInputCommand( name: String, description: String, builder: GlobalChatInputCreateBuilder.() -> Unit = {} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt index b584e793..9b805df2 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt @@ -19,7 +19,7 @@ import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.json.request.ApplicationCommandCreateRequest /** - * The needed class is internal, so we're keeping our on impl + * The needed class is internal, so we're keeping our own impl * */ class ProxyFoxChatInputCreateBuilderImpl( override var name: String, From 8c61753e3951b0b36fde0fe56c97a6a9179b89a7 Mon Sep 17 00:00:00 2001 From: Octal Date: Thu, 16 Feb 2023 20:30:37 -0600 Subject: [PATCH 091/137] Refactor command registration --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 27 +++++------- .../proxyfox/bot/command/CommandRegistrar.kt | 8 ++++ .../dev/proxyfox/bot/command/Commands.kt | 16 ++++---- .../dev/proxyfox/bot/command/GroupCommands.kt | 12 +++--- .../proxyfox/bot/command/MemberCommands.kt | 16 ++++---- .../dev/proxyfox/bot/command/MiscCommands.kt | 41 +++++++++++++++---- .../proxyfox/bot/command/SwitchCommands.kt | 16 ++++---- .../proxyfox/bot/command/SystemCommands.kt | 12 +++--- 8 files changed, 87 insertions(+), 61 deletions(-) create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 9eb3824d..5460cb77 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -34,12 +34,7 @@ import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.json.request.ApplicationCommandCreateRequest import dev.kord.rest.request.KtorRequestException -import dev.proxyfox.bot.command.Commands -import dev.proxyfox.bot.command.GroupCommands.registerGroupCommands -import dev.proxyfox.bot.command.MemberCommands.registerMemberCommands -import dev.proxyfox.bot.command.MiscCommands.registerMiscCommands -import dev.proxyfox.bot.command.SwitchCommands.registerSwitchCommands -import dev.proxyfox.bot.command.SystemCommands.registerSystemCommands +import dev.proxyfox.bot.command.* import dev.proxyfox.bot.command.interaction.ProxyFoxChatInputCreateBuilderImpl import dev.proxyfox.common.* import dev.proxyfox.database.database @@ -141,9 +136,7 @@ suspend fun login() { handleModal() } - kord.registerApplicationCommands() - - Commands.register() + kord.registerCommands() kord.on { onInteract() @@ -183,17 +176,19 @@ suspend fun login() { } } -suspend fun Kord.registerApplicationCommands() { - printStep("Registering slash commands", 2) +suspend fun Kord.registerCommands() { + printStep("Registering commands", 2) createGlobalMessageCommand("Delete Message") createGlobalMessageCommand("Fetch Message Info") createGlobalMessageCommand("Ping Message Author") createGlobalMessageCommand("Edit Message") - registerSystemCommands() - registerGroupCommands() - registerMemberCommands() - registerSwitchCommands() - registerMiscCommands() + Commands { + +SystemCommands + +MemberCommands + +GroupCommands + +SwitchCommands + +MiscCommands + } scope.launch { rest.interaction.createGlobalApplicationCommands( diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt new file mode 100644 index 00000000..8a903603 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt @@ -0,0 +1,8 @@ +package dev.proxyfox.bot.command + +interface CommandRegistrar { + val displayName: String + + suspend fun registerTextCommands() + suspend fun registerSlashCommands() +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 9d70288f..64930450 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -12,6 +12,7 @@ import dev.kord.rest.builder.interaction.* import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.command.CommandParser +import dev.proxyfox.common.applyAsync import dev.proxyfox.common.printStep import dev.proxyfox.database.database import dev.proxyfox.database.records.group.GroupRecord @@ -29,13 +30,14 @@ import kotlin.contracts.contract object Commands { val parser = CommandParser>() - suspend fun register() { - printStep("Registering text commands", 2) - SystemCommands.register() - GroupCommands.register() - MemberCommands.register() - SwitchCommands.register() - MiscCommands.register() + suspend operator fun invoke(action: suspend Commands.() -> Unit) { + applyAsync(action) + } + + suspend operator fun CommandRegistrar.unaryPlus() { + printStep("Registering $displayName commands", 3) + registerTextCommands() + registerSlashCommands() } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt index d49ce226..06f409da 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt @@ -8,7 +8,6 @@ package dev.proxyfox.bot.command -import dev.kord.core.Kord import dev.kord.rest.builder.interaction.SubCommandBuilder import dev.kord.rest.builder.interaction.subCommand import dev.proxyfox.bot.command.context.DiscordContext @@ -19,20 +18,18 @@ import dev.proxyfox.bot.kordColor import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.builtin.literal import dev.proxyfox.command.node.builtin.string -import dev.proxyfox.common.printStep import dev.proxyfox.database.database import dev.proxyfox.database.records.group.GroupRecord import dev.proxyfox.database.records.system.SystemRecord -object GroupCommands { +object GroupCommands : CommandRegistrar { var interactionExecutors: HashMap Boolean> = hashMapOf() fun SubCommandBuilder.runs(action: suspend InteractionCommandContext.() -> Boolean) { interactionExecutors[name] = action } - suspend fun Kord.registerGroupCommands() { - printStep("Registering group commands", 3) + override suspend fun registerSlashCommands() { deferChatInputCommand("group", "Manage a group") { subCommand("access", "View the group") { name() @@ -48,8 +45,9 @@ object GroupCommands { } } - suspend fun register() { - printStep("Registering system commands", 3) + override val displayName: String = "Group" + + override suspend fun registerTextCommands() { Commands.parser.registerGroupCommands { database.fetchSystemFromUser(getUser()) } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 4f95618e..07241526 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -10,7 +10,6 @@ package dev.proxyfox.bot.command import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Snowflake -import dev.kord.core.Kord import dev.kord.rest.builder.interaction.SubCommandBuilder import dev.kord.rest.builder.interaction.subCommand import dev.proxyfox.bot.command.context.DiscordContext @@ -30,7 +29,10 @@ import dev.proxyfox.command.node.builtin.greedy import dev.proxyfox.command.node.builtin.literal import dev.proxyfox.command.node.builtin.string import dev.proxyfox.command.node.builtin.unixLiteral -import dev.proxyfox.common.* +import dev.proxyfox.common.fromColor +import dev.proxyfox.common.ifBlankThenNull +import dev.proxyfox.common.notBlank +import dev.proxyfox.common.toColor import dev.proxyfox.database.database import dev.proxyfox.database.displayDate import dev.proxyfox.database.records.member.MemberProxyTagRecord @@ -45,15 +47,14 @@ import kotlinx.datetime.toJavaLocalDate * Commands for accessing and changing system settings * @author Oliver * */ -object MemberCommands { +object MemberCommands : CommandRegistrar { var interactionExecutors: HashMap Boolean> = hashMapOf() fun SubCommandBuilder.runs(action: suspend InteractionCommandContext.() -> Boolean) { interactionExecutors[name] = action } - suspend fun Kord.registerMemberCommands() { - printStep("Registering member commands", 3) + override suspend fun registerSlashCommands() { deferChatInputCommand("member", "Manage or create a system member!") { subCommand("create", "Create a member") { name() @@ -867,8 +868,9 @@ object MemberCommands { } } - suspend fun register() { - printStep("Registering member commands", 3) + override val displayName: String = "Member" + + override suspend fun registerTextCommands() { //TODO: Dedupe code Commands.parser.registerBaseMemberCommands { database.fetchSystemFromUser(getUser()) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 9c4267b7..703dd715 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -11,7 +11,6 @@ package dev.proxyfox.bot.command import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake -import dev.kord.core.Kord import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior import dev.kord.rest.NamedFile @@ -25,6 +24,7 @@ import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.* +import dev.proxyfox.common.annotations.DontExpose import dev.proxyfox.database.database import dev.proxyfox.database.displayDate import dev.proxyfox.database.etc.exporter.Exporter @@ -51,7 +51,7 @@ import kotlin.math.floor * Miscellaneous commands * @author Oliver * */ -object MiscCommands { +object MiscCommands : CommandRegistrar { private val roleMatcher = Regex("\\d+") var infoInteractionExecutors: HashMap Boolean> = hashMapOf() var moderationInteractionExecutors: HashMap Boolean> = hashMapOf() @@ -66,8 +66,9 @@ object MiscCommands { }[name] = action } - suspend fun Kord.registerMiscCommands() { - printStep("Registering misc commands", 3) + override val displayName: String = "Misc" + + override suspend fun registerSlashCommands() { deferChatInputCommand("info", "Fetches info about the bot") { subCommand("debug", "Fetch debug information about the bot") { runs("info") { @@ -255,8 +256,7 @@ object MiscCommands { } } - suspend fun register() { - printStep("Registering misc commands", 3) + override suspend fun registerTextCommands() { Commands.parser.literal("import") { runs { import(this, null) @@ -734,17 +734,42 @@ object MiscCommands { } literal("token") { - literal("clear", "reset", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + token(this, system, null, false) + } + literal("clear", "reset", "remove") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + token(this, system, null, true) + } } greedy("token") { getToken -> - + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + token(this, system, getToken(), false) + } } } } } + @OptIn(DontExpose::class) + private suspend fun token( + ctx: DiscordContext, + system: SystemRecord, + token: String?, + clear: Boolean + ): Boolean { + + return true + } + private suspend fun forceTag(ctx: DiscordContext, enabled: Boolean?): Boolean { val server = database.getOrCreateServerSettings(ctx.getGuild() ?: run { ctx.respondFailure("You are not in a server.") diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 05467c65..8baa17b0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -9,7 +9,6 @@ package dev.proxyfox.bot.command import dev.kord.common.entity.ButtonStyle -import dev.kord.core.Kord import dev.kord.rest.builder.interaction.SubCommandBuilder import dev.kord.rest.builder.interaction.string import dev.kord.rest.builder.interaction.subCommand @@ -25,7 +24,6 @@ import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.builtin.greedy import dev.proxyfox.command.node.builtin.literal import dev.proxyfox.command.node.builtin.stringList -import dev.proxyfox.common.printStep import dev.proxyfox.common.trimEach import dev.proxyfox.database.database import dev.proxyfox.database.records.system.SystemRecord @@ -34,15 +32,16 @@ import kotlinx.datetime.Clock import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.minus -object SwitchCommands { +object SwitchCommands : CommandRegistrar { var interactionExecutors: HashMap Boolean> = hashMapOf() fun SubCommandBuilder.runs(action: suspend InteractionCommandContext.() -> Boolean) { interactionExecutors[name] = action } - suspend fun Kord.registerSwitchCommands() { - printStep("Registering switch commands", 3) + override val displayName: String = "Switch" + + override suspend fun registerSlashCommands() { deferChatInputCommand("switch", "Create or manage switches!") { subCommand("create", "Create a switch") { string("members", "The members to use, comma separated") { @@ -70,7 +69,7 @@ object SwitchCommands { runs { val system = getSystem() if (!checkSystem(this, system)) return@runs false - val switch = database.fetchLatestSwitch(system!!.id) + val switch = database.fetchLatestSwitch(system.id) if (!checkSwitch(this, switch)) return@runs false val oldSwitch = database.fetchSecondLatestSwitch(system.id) delete(this, system, switch, oldSwitch) @@ -119,7 +118,7 @@ object SwitchCommands { runs { val system = getSys() if (!checkSystem(this, system)) return@runs false - val switch = database.fetchLatestSwitch(system!!.id) + val switch = database.fetchLatestSwitch(system.id) if (!checkSwitch(this, switch)) return@runs false val oldSwitch = database.fetchSecondLatestSwitch(system.id) delete(this, system, switch, oldSwitch) @@ -162,8 +161,7 @@ object SwitchCommands { } } - suspend fun register() { - printStep("Registering switch commands", 3) + override suspend fun registerTextCommands() { Commands.parser.registerSwitchCommands { database.fetchSystemFromUser(getUser()) } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index aa84187b..db1d0436 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -9,7 +9,6 @@ package dev.proxyfox.bot.command import dev.kord.common.entity.ButtonStyle -import dev.kord.core.Kord import dev.kord.rest.NamedFile import dev.kord.rest.builder.interaction.SubCommandBuilder import dev.kord.rest.builder.interaction.subCommand @@ -29,7 +28,6 @@ import dev.proxyfox.bot.prompts.TimedYesNoPrompt import dev.proxyfox.bot.system import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.fromColor -import dev.proxyfox.common.printStep import dev.proxyfox.common.toColor import dev.proxyfox.database.database import dev.proxyfox.database.etc.exporter.Exporter @@ -41,15 +39,16 @@ import io.ktor.utils.io.jvm.javaio.* * Commands for accessing and changing system settings * @author Oliver * */ -object SystemCommands { +object SystemCommands : CommandRegistrar { var interactionExecutors: HashMap Boolean> = hashMapOf() fun SubCommandBuilder.runs(action: suspend InteractionCommandContext.() -> Boolean) { interactionExecutors[name] = action } - suspend fun Kord.registerSystemCommands() { - printStep("Registering system commands", 3) + override val displayName: String = "System" + + override suspend fun registerSlashCommands() { deferChatInputCommand("system", "Manage or create a system!") { subCommand("fetch", "Fetch a system card!") { system() @@ -173,8 +172,7 @@ object SystemCommands { } } - suspend fun register() { - printStep("Registering system commands", 3) + override suspend fun registerTextCommands() { Commands.parser.literal("list", "l") { runs { val system = database.fetchSystemFromUser(getUser()) From ef57ef92959a959f7a29c15e595612ae02f4a0d6 Mon Sep 17 00:00:00 2001 From: Octal Date: Thu, 16 Feb 2023 21:07:42 -0600 Subject: [PATCH 092/137] Bump MarKt version --- gradle/libs.versions.toml | 2 +- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt | 3 +-- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt | 3 +++ .../src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7de75f89..7d0a974a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ kotlinx_coroutines = "1.6.4" # ProxyFox Libraries proxyfox_command = "1.7" pluralkt = "1.8" -markt = "1.0" +markt = "1.4" # Database postgres = "42.3.3" diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt index a3640e66..514eecb5 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotMain.kt @@ -12,7 +12,6 @@ import dev.proxyfox.api.ApiMain import dev.proxyfox.bot.terminal.TerminalCommands import dev.proxyfox.common.printFancy import dev.proxyfox.database.DatabaseMain -import dev.proxyfox.markt.MarkdownParser suspend fun main(args: Array) = BotMain.main(args) @@ -26,7 +25,7 @@ object BotMain { printFancy("Initializing ProxyFox") - MarkdownParser.addDefaultRules() + markdownParser.addDefaultRules() // Setup database DatabaseMain.main(findUnixValue(args, "--database=")) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 5460cb77..acb88aa2 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -40,6 +40,7 @@ import dev.proxyfox.common.* import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord +import dev.proxyfox.markt.MarkdownParser import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.request.forms.* @@ -82,6 +83,8 @@ val errorChannelId = try { } var errorChannel: TextChannel? = null +val markdownParser = MarkdownParser() + @OptIn(PrivilegedIntent::class) suspend fun login() { printStep("Setting up HTTP client", 1) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt index 6a998c21..f1f013b9 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt @@ -17,12 +17,12 @@ import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException import dev.proxyfox.bot.http import dev.proxyfox.bot.kord +import dev.proxyfox.bot.markdownParser import dev.proxyfox.common.ellipsis import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.system.SystemRecord -import dev.proxyfox.markt.MarkdownParser import dev.proxyfox.markt.RootNode import dev.proxyfox.markt.StringNode import io.ktor.client.request.* @@ -99,7 +99,7 @@ data class ProxyContext( icon = user.avatar?.url ?: user.defaultAvatar.url url = link } - var msgRef = MarkdownParser.parse(ref.content) + var msgRef = markdownParser.parse(ref.content) if (msgRef.length > 100) { // We should be getting a RootNode returned here. msgRef = msgRef.truncate(100) as RootNode From 6b6a44c3f132015bc4e29158a94aefa2053c0b3e Mon Sep 17 00:00:00 2001 From: Octal Date: Thu, 16 Feb 2023 21:10:08 -0600 Subject: [PATCH 093/137] add license header to CommandRegistrar.kt --- .../kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt index 8a903603..c8649e49 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command interface CommandRegistrar { From 7322784f9f7f1012eef56dc3fd1a7d663249db92 Mon Sep 17 00:00:00 2001 From: Octal Date: Thu, 16 Feb 2023 21:48:08 -0600 Subject: [PATCH 094/137] Properly implement menus --- .../dev/proxyfox/bot/command/MiscCommands.kt | 43 +++++++++++++------ .../context/InteractionCommandContext.kt | 9 ++-- .../command/menu/InteractionCommandMenu.kt | 12 +++--- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 703dd715..125935a1 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -254,6 +254,11 @@ object MiscCommands : CommandRegistrar { } } } + deferChatInputCommand("pluralkit", "Commands for PluralKit integration with ProxyFox") { + subCommand("token", "Store you PluralKit token!") { + + } + } } override suspend fun registerTextCommands() { @@ -272,7 +277,6 @@ object MiscCommands : CommandRegistrar { } } } - //TODO: export --full Commands.parser.literal("export") { runs { val system = database.fetchSystemFromUser(getUser()) @@ -497,7 +501,6 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("moddelay") { runs { val guild = getGuild() ?: run { @@ -516,7 +519,6 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("forcetag", "requiretag") { runs { forceTag(this, null) @@ -532,7 +534,6 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("delete", "del") { runs { val system = database.fetchSystemFromUser(getUser()) @@ -547,7 +548,6 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("reproxy", "rp") { runs { val system = database.fetchSystemFromUser(getUser()) @@ -580,7 +580,6 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("info", "i") { runs { fetchMessageInfo(this, null) @@ -591,7 +590,6 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("ping", "p") { runs { pingMessageAuthor(this, null) @@ -602,7 +600,6 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("edit", "e") { runs { val system = database.fetchSystemFromUser(getUser()) @@ -632,7 +629,6 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("channel", "c") { responds("Please provide a channel subcommand") literal("proxy", "p") { @@ -656,15 +652,12 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("debug") { runs(::debug) } - Commands.parser.literal("fox") { runs(::getFox) } - Commands.parser.literal("token", "t") { runs { val system = database.fetchSystemFromUser(getUser()) @@ -672,7 +665,6 @@ object MiscCommands : CommandRegistrar { token(this, system) } } - Commands.parser.literal("trust") { runs { val system = database.fetchSystemFromUser(getUser()) @@ -723,7 +715,6 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("pluralkit", "pk") { literal("pull", "get", "download") { @@ -766,6 +757,30 @@ object MiscCommands : CommandRegistrar { token: String?, clear: Boolean ): Boolean { + if (clear) { + if (system.pkToken == null) { + ctx.respondFailure("You don't have a PluralKit token registered.", true) + return false + } + + system.pkToken = null + database.updateSystem(system) + ctx.respondSuccess("Cleared your PluralKit token!", true) + return true + } + token ?: run { + ctx.respondSuccess("You have a PluralKit token registered.", true) + return true + } + + if (ctx.getGuild() != null && ctx is DiscordMessageContext) { + ctx.respondFailure("Please do not send your PluralKit token in public.\nI advise you reset it immediately and run this command in DMs") + return false + } + + system.pkToken = token + database.updateSystem(system) + ctx.respondSuccess("Successfully updated your PluralKit token", true) return true } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index 964bb02a..08abfce3 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -13,6 +13,7 @@ import dev.kord.core.behavior.channel.GuildChannelBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.behavior.interaction.response.respond import dev.kord.core.entity.* import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.builder.message.EmbedBuilder @@ -29,10 +30,10 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : override val command: String = "" override suspend fun menu(action: MenuBuilder) { - val message = value.interaction.respondEphemeral { + val message = value.interaction.deferEphemeralResponse() + val menu = InteractionCommandMenu(message.respond { content = "Thinking..." - } - val menu = InteractionCommandMenu(message) + }) menu.action() menu.init() } @@ -145,4 +146,4 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : val message = getChannel().getMessageOrNull(Snowflake(databaseMessage.newMessageId)) return message to databaseMessage } -} \ No newline at end of file +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt index 9968693d..056bda88 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt @@ -8,14 +8,14 @@ package dev.proxyfox.bot.command.menu -import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior import dev.kord.core.behavior.interaction.response.edit +import dev.kord.core.entity.interaction.response.EphemeralMessageInteractionResponse import dev.kord.core.event.interaction.ButtonInteractionCreateEvent import dev.kord.core.on import dev.kord.rest.builder.message.modify.MessageModifyBuilder import dev.proxyfox.bot.kord -class InteractionCommandMenu(val interaction: EphemeralMessageInteractionResponseBehavior) : DiscordMenu() { +class InteractionCommandMenu(val interaction: EphemeralMessageInteractionResponse) : DiscordMenu() { override suspend fun edit(builder: MessageModifyBuilder.() -> Unit) { interaction.edit(builder) } @@ -29,7 +29,9 @@ class InteractionCommandMenu(val interaction: EphemeralMessageInteractionRespons super.init() } - private fun interact(button: ButtonInteractionCreateEvent) { - TODO() + private suspend fun interact(button: ButtonInteractionCreateEvent) { + if (button.interaction.message.id != interaction.message.id) return + button.interaction.deferEphemeralMessageUpdate() + active!!.click(button.interaction.componentId) } -} \ No newline at end of file +} From a4f1789427d8d6a7e2879d8b41a10b8f96b6f1bb Mon Sep 17 00:00:00 2001 From: Octal Date: Thu, 16 Feb 2023 22:53:48 -0600 Subject: [PATCH 095/137] Add Kord.onlyIf --- .../proxyfox/bot/command/menu/DiscordMessageMenu.kt | 4 ++-- .../bot/command/menu/InteractionCommandMenu.kt | 6 +++--- .../src/main/kotlin/dev/proxyfox/common/Util.kt | 11 +++++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt index 4eade964..34ddb4c9 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt @@ -11,9 +11,9 @@ package dev.proxyfox.bot.command.menu import dev.kord.core.behavior.edit import dev.kord.core.entity.Message import dev.kord.core.event.interaction.ButtonInteractionCreateEvent -import dev.kord.core.on import dev.kord.rest.builder.message.modify.MessageModifyBuilder import dev.proxyfox.bot.kord +import dev.proxyfox.common.onlyIf class DiscordMessageMenu(val message: Message) : DiscordMenu() { override suspend fun edit(builder: MessageModifyBuilder.() -> Unit) { @@ -22,7 +22,7 @@ class DiscordMessageMenu(val message: Message) : DiscordMenu() { override suspend fun init() { jobs.add( - kord.on { + kord.onlyIf({ interaction.message.id }, message.id) { interact(this) } ) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt index 056bda88..8a53a421 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt @@ -11,9 +11,9 @@ package dev.proxyfox.bot.command.menu import dev.kord.core.behavior.interaction.response.edit import dev.kord.core.entity.interaction.response.EphemeralMessageInteractionResponse import dev.kord.core.event.interaction.ButtonInteractionCreateEvent -import dev.kord.core.on import dev.kord.rest.builder.message.modify.MessageModifyBuilder import dev.proxyfox.bot.kord +import dev.proxyfox.common.onlyIf class InteractionCommandMenu(val interaction: EphemeralMessageInteractionResponse) : DiscordMenu() { override suspend fun edit(builder: MessageModifyBuilder.() -> Unit) { @@ -22,15 +22,15 @@ class InteractionCommandMenu(val interaction: EphemeralMessageInteractionRespons override suspend fun init() { jobs.add( - kord.on { + kord.onlyIf({ interaction.message.id }, interaction.message.id) { interact(this) } ) super.init() } + private suspend fun interact(button: ButtonInteractionCreateEvent) { - if (button.interaction.message.id != interaction.message.id) return button.interaction.deferEphemeralMessageUpdate() active!!.click(button.interaction.componentId) } diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt index c4838420..1b98139b 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt @@ -8,6 +8,9 @@ package dev.proxyfox.common +import dev.kord.core.Kord +import dev.kord.core.event.Event +import dev.kord.core.on import org.slf4j.Logger import org.slf4j.LoggerFactory import java.lang.management.* @@ -97,3 +100,11 @@ fun Array.trimEach() { fun Throwable?.throwIfPresent() { throw this ?: return } + +inline fun Kord.onlyIf( + crossinline getter: E.() -> Any?, + compare: Any?, + crossinline executor: suspend E.() -> Unit +) = on { + if (getter() == compare) executor() +} From b0beff20db087cb5a599491367d3d2c2a07e73c5 Mon Sep 17 00:00:00 2001 From: Octal Date: Thu, 16 Feb 2023 23:04:03 -0600 Subject: [PATCH 096/137] Remove unnecessary parameter --- .../main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt | 2 +- .../main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt index c5861478..2baeb44e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt @@ -23,7 +23,7 @@ abstract class DiscordMenu : CommandMenu() { } override suspend fun createScreen(name: String): CommandScreen { - return DiscordScreen(name, this) + return DiscordScreen(name) } abstract suspend fun edit(builder: MessageModifyBuilder.() -> Unit) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt index 8f5c0766..ecd02e39 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt @@ -10,7 +10,7 @@ package dev.proxyfox.bot.command.menu import dev.proxyfox.command.menu.CommandScreen -class DiscordScreen(name: String, private val menu: DiscordMenu) : CommandScreen(name) { +class DiscordScreen(name: String) : CommandScreen(name) { private var initializer: suspend () -> Unit = {} fun onInit(action: suspend () -> Unit) { From b21947cb6580d5b54436bf5d97447fe24c183e24 Mon Sep 17 00:00:00 2001 From: Octal Date: Thu, 16 Feb 2023 23:13:44 -0600 Subject: [PATCH 097/137] Pass in CommandParser in CommandRegistrar$registerTextCommands --- .../proxyfox/bot/command/CommandRegistrar.kt | 5 +- .../dev/proxyfox/bot/command/Commands.kt | 2 +- .../dev/proxyfox/bot/command/GroupCommands.kt | 5 +- .../proxyfox/bot/command/MemberCommands.kt | 6 +-- .../dev/proxyfox/bot/command/MiscCommands.kt | 51 ++++++++++--------- .../proxyfox/bot/command/SwitchCommands.kt | 5 +- .../proxyfox/bot/command/SystemCommands.kt | 7 +-- 7 files changed, 44 insertions(+), 37 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt index c8649e49..6c831695 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommandRegistrar.kt @@ -8,9 +8,12 @@ package dev.proxyfox.bot.command +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.command.CommandParser + interface CommandRegistrar { val displayName: String - suspend fun registerTextCommands() + suspend fun CommandParser>.registerTextCommands() suspend fun registerSlashCommands() } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 64930450..67e034e1 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -36,7 +36,7 @@ object Commands { suspend operator fun CommandRegistrar.unaryPlus() { printStep("Registering $displayName commands", 3) - registerTextCommands() + parser.registerTextCommands() registerSlashCommands() } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt index 06f409da..daf3b582 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt @@ -15,6 +15,7 @@ import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.deferChatInputCommand import dev.proxyfox.bot.kordColor +import dev.proxyfox.command.CommandParser import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.builtin.literal import dev.proxyfox.command.node.builtin.string @@ -47,8 +48,8 @@ object GroupCommands : CommandRegistrar { override val displayName: String = "Group" - override suspend fun registerTextCommands() { - Commands.parser.registerGroupCommands { + override suspend fun CommandParser>.registerTextCommands() { + registerGroupCommands { database.fetchSystemFromUser(getUser()) } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 07241526..54d011db 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -23,6 +23,7 @@ import dev.proxyfox.bot.kordColor import dev.proxyfox.bot.member import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.TimedYesNoPrompt +import dev.proxyfox.command.CommandParser import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.greedy @@ -870,9 +871,8 @@ object MemberCommands : CommandRegistrar { override val displayName: String = "Member" - override suspend fun registerTextCommands() { - //TODO: Dedupe code - Commands.parser.registerBaseMemberCommands { + override suspend fun CommandParser>.registerTextCommands() { + registerBaseMemberCommands { database.fetchSystemFromUser(getUser()) } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 125935a1..478cef9b 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -22,6 +22,7 @@ import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.TimedYesNoPrompt import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil +import dev.proxyfox.command.CommandParser import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.* import dev.proxyfox.common.annotations.DontExpose @@ -261,8 +262,8 @@ object MiscCommands : CommandRegistrar { } } - override suspend fun registerTextCommands() { - Commands.parser.literal("import") { + override suspend fun CommandParser>.registerTextCommands() { + literal("import") { runs { import(this, null) } @@ -277,29 +278,29 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("export") { + literal("export") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false export(this) } } - Commands.parser.literal("time") { + literal("time") { runs(::time) } - Commands.parser.literal("help") { + literal("help") { responds(help) } - Commands.parser.literal("explain") { + literal("explain") { responds(explain) } - Commands.parser.literal("invite") { + literal("invite") { responds(invite) } - Commands.parser.literal("source") { + literal("source") { responds(source) } - Commands.parser.literal("proxy", "p") { + literal("proxy", "p") { guild { getGuildId -> runs { val system = database.fetchSystemFromUser(getUser()) @@ -349,7 +350,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("autoproxy", "ap") { + literal("autoproxy", "ap") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false @@ -386,7 +387,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("serverautoproxy", "sap") { + literal("serverautoproxy", "sap") { guild { getGuildId -> runs { val system = database.fetchSystemFromUser(getUser()) @@ -486,7 +487,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("role") { + literal("role") { runs { role(this, null, false) } @@ -501,7 +502,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("moddelay") { + literal("moddelay") { runs { val guild = getGuild() ?: run { respondFailure("Command not ran in server.") @@ -519,7 +520,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("forcetag", "requiretag") { + literal("forcetag", "requiretag") { runs { forceTag(this, null) } @@ -534,7 +535,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("delete", "del") { + literal("delete", "del") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false @@ -548,7 +549,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("reproxy", "rp") { + literal("reproxy", "rp") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false @@ -580,7 +581,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("info", "i") { + literal("info", "i") { runs { fetchMessageInfo(this, null) } @@ -590,7 +591,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("ping", "p") { + literal("ping", "p") { runs { pingMessageAuthor(this, null) } @@ -600,7 +601,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("edit", "e") { + literal("edit", "e") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false @@ -629,7 +630,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("channel", "c") { + literal("channel", "c") { responds("Please provide a channel subcommand") literal("proxy", "p") { runs { @@ -652,20 +653,20 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("debug") { + literal("debug") { runs(::debug) } - Commands.parser.literal("fox") { + literal("fox") { runs(::getFox) } - Commands.parser.literal("token", "t") { + literal("token", "t") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false token(this, system) } } - Commands.parser.literal("trust") { + literal("trust") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false @@ -715,7 +716,7 @@ object MiscCommands : CommandRegistrar { } } } - Commands.parser.literal("pluralkit", "pk") { + literal("pluralkit", "pk") { literal("pull", "get", "download") { } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 8baa17b0..3e072a38 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -20,6 +20,7 @@ import dev.proxyfox.bot.parseDuration import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.Pager import dev.proxyfox.bot.prompts.TimedYesNoPrompt +import dev.proxyfox.command.CommandParser import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.builtin.greedy import dev.proxyfox.command.node.builtin.literal @@ -161,8 +162,8 @@ object SwitchCommands : CommandRegistrar { } } - override suspend fun registerTextCommands() { - Commands.parser.registerSwitchCommands { + override suspend fun CommandParser>.registerTextCommands() { + registerSwitchCommands { database.fetchSystemFromUser(getUser()) } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index db1d0436..91e5c5d7 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -26,6 +26,7 @@ import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.Pager import dev.proxyfox.bot.prompts.TimedYesNoPrompt import dev.proxyfox.bot.system +import dev.proxyfox.command.CommandParser import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.fromColor import dev.proxyfox.common.toColor @@ -172,8 +173,8 @@ object SystemCommands : CommandRegistrar { } } - override suspend fun registerTextCommands() { - Commands.parser.literal("list", "l") { + override suspend fun CommandParser>.registerTextCommands() { + literal("list", "l") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false @@ -190,7 +191,7 @@ object SystemCommands : CommandRegistrar { } } } - Commands.parser.literal("system", "sys", "s") { + literal("system", "sys", "s") { literal("new", "n", "create", "add") { runs { create(this, null) From 2aff21576ab52f2845944e5a69f6d3d9d213fea4 Mon Sep 17 00:00:00 2001 From: Octal Date: Fri, 17 Feb 2023 00:00:01 -0600 Subject: [PATCH 098/137] Move TimedYesNoPrompt to DiscordContext --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 11 ++ .../main/kotlin/dev/proxyfox/bot/Emojis.kt | 29 ++++ .../proxyfox/bot/command/MemberCommands.kt | 19 +-- .../dev/proxyfox/bot/command/MiscCommands.kt | 9 +- .../proxyfox/bot/command/SwitchCommands.kt | 21 ++- .../proxyfox/bot/command/SystemCommands.kt | 16 +- .../bot/command/context/DiscordContext.kt | 63 +++++++- .../proxyfox/bot/command/menu/DiscordMenu.kt | 5 +- .../bot/command/menu/DiscordMessageMenu.kt | 6 +- .../command/menu/InteractionCommandMenu.kt | 6 +- .../kotlin/dev/proxyfox/bot/prompts/Button.kt | 37 ----- .../proxyfox/bot/prompts/TimedYesNoPrompt.kt | 153 ------------------ 12 files changed, 133 insertions(+), 242 deletions(-) create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Button.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index acb88aa2..d8b04486 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -54,17 +54,28 @@ import kotlinx.datetime.Instant import java.lang.Integer.* import java.util.* import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.minutes +import kotlin.time.DurationUnit const val UPLOAD_LIMIT = 1024 * 1024 * 8 val scheduler = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()) +suspend fun ScheduledExecutorService.schedule(duration: Duration, action: suspend () -> Unit) { + schedule({ + runBlocking { + action() + } + }, duration.toLong(DurationUnit.SECONDS), TimeUnit.SECONDS) +} + private val idUrl = System.getenv("PROXYFOX_KEY").let { it.substring(0, it.indexOf('.')) } private val webhook = Regex("https?://(?:[^./]\\.)?discord(?:app)?\\.com/api/(v\\d+/)?webhooks/\\d+/\\S+") diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt new file mode 100644 index 00000000..c85e3378 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot + +import dev.kord.common.entity.DiscordPartialEmoji +import dev.kord.common.entity.optional.optional +import dev.kord.core.entity.GuildEmoji +import dev.kord.core.entity.ReactionEmoji + +object Emojis { + val check = "✅".partial + val multiply = "✖".partial + val wastebasket = "🗑".partial + val move = "\uD83D\uDD00".partial + + val ReactionEmoji.Unicode.partial get() = name.partial + + val ReactionEmoji.Custom.partial get() = DiscordPartialEmoji(id = id, name = name, animated = isAnimated.optional()) + + val GuildEmoji.partial get() = DiscordPartialEmoji(id = id, animated = isAnimated.optional()) + + val String.partial get() = DiscordPartialEmoji(name = this) +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 54d011db..0a172c62 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -8,21 +8,15 @@ package dev.proxyfox.bot.command -import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Snowflake import dev.kord.rest.builder.interaction.SubCommandBuilder import dev.kord.rest.builder.interaction.subCommand +import dev.proxyfox.bot.* import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.guild import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.command.node.attachment -import dev.proxyfox.bot.deferChatInputCommand -import dev.proxyfox.bot.kord -import dev.proxyfox.bot.kordColor -import dev.proxyfox.bot.member -import dev.proxyfox.bot.prompts.Button -import dev.proxyfox.bot.prompts.TimedYesNoPrompt import dev.proxyfox.command.CommandParser import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.CommandNode @@ -1421,15 +1415,14 @@ object MemberCommands : CommandRegistrar { return false } - TimedYesNoPrompt.build( - runner = ctx.getUser()!!.id, - channel = ctx.getChannel(), + ctx.timedYesNoPrompt( message = "Are you sure you want to delete member `${member.asString()}`?\n" + "Their data will be lost forever (A long time!)", - yes = Button("Delete Member", Button.wastebasket, ButtonStyle.Danger) { + yes = "Delete Member" to { database.dropMember(member.systemId, member.id) content = "Member deleted" }, + yesEmoji = Emojis.wastebasket ) return true @@ -1448,9 +1441,7 @@ object MemberCommands : CommandRegistrar { val member = database.fetchMemberFromSystemAndName(system.id, name, false) if (member != null) { - TimedYesNoPrompt.build( - runner = ctx.getUser()!!.id, - channel = ctx.getChannel(), + ctx.timedYesNoPrompt( message = "You already have a member named \"${member.name}\" (`${member.id}`)." + "\nDo you want to create another member with the same name?", yes = "Create $name" to { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 478cef9b..544b7c25 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -8,7 +8,6 @@ package dev.proxyfox.bot.command -import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.asChannelOf @@ -18,8 +17,6 @@ import dev.kord.rest.builder.interaction.* import dev.proxyfox.bot.* import dev.proxyfox.bot.command.context.* import dev.proxyfox.bot.command.node.attachment -import dev.proxyfox.bot.prompts.Button -import dev.proxyfox.bot.prompts.TimedYesNoPrompt import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil import dev.proxyfox.command.CommandParser @@ -831,11 +828,9 @@ object MiscCommands : CommandRegistrar { return true } - TimedYesNoPrompt.build( - runner = ctx.getUser()!!.id, - channel = ctx.getChannel(), + ctx.timedYesNoPrompt( message = "Are you sure you want to trust this user with level `${trustLevel.name}`?\nThis can be changed at any time.", - yes = Button("Trust user", Button.check, ButtonStyle.Primary) { + yes = "Trust user" to { system.trust[user] = trustLevel database.updateSystem(system) content = "User trust updated." diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 3e072a38..762b2c80 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -8,18 +8,16 @@ package dev.proxyfox.bot.command -import dev.kord.common.entity.ButtonStyle import dev.kord.rest.builder.interaction.SubCommandBuilder import dev.kord.rest.builder.interaction.string import dev.kord.rest.builder.interaction.subCommand +import dev.proxyfox.bot.Emojis import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.deferChatInputCommand import dev.proxyfox.bot.parseDuration -import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.Pager -import dev.proxyfox.bot.prompts.TimedYesNoPrompt import dev.proxyfox.command.CommandParser import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.builtin.greedy @@ -207,15 +205,14 @@ object SwitchCommands : CommandRegistrar { database.fetchMemberFromSystem(system.id, it)?.showDisplayName() ?: "*Unknown*" }.joinToString(", ") - TimedYesNoPrompt.build( - runner = ctx.getUser()!!.id, - channel = ctx.getChannel(), + ctx.timedYesNoPrompt( message = "Are you sure you want to move the switch $members back to ?", - yes = Button("Move switch", Button.move, ButtonStyle.Primary) { + yes = "Move switch" to { switch.timestamp = nowMinus database.updateSwitch(switch) content = "Switch updated." - } + }, + yesEmoji = Emojis.move ) return true @@ -229,17 +226,17 @@ object SwitchCommands : CommandRegistrar { val epoch = switch.timestamp.epochSeconds - TimedYesNoPrompt.build( - runner = ctx.getUser()!!.id, - channel = ctx.getChannel(), + ctx.timedYesNoPrompt( message = """ Are you sure you want to delete the latest switch (${switch.membersAsString()}, )? ${if (oldSwitch != null) "\nThe previous switch would be at " else ""} The data will be lost forever (A long time!) """.trimIndent(), - yes = Button("Delete switch", Button.wastebasket, ButtonStyle.Danger) { + yes = "Delete switch" to { database.dropSwitch(switch) content = "Switch deleted." }, + yesEmoji = Emojis.wastebasket, + danger = true ) return true diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 91e5c5d7..4c26eea1 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -8,10 +8,10 @@ package dev.proxyfox.bot.command -import dev.kord.common.entity.ButtonStyle import dev.kord.rest.NamedFile import dev.kord.rest.builder.interaction.SubCommandBuilder import dev.kord.rest.builder.interaction.subCommand +import dev.proxyfox.bot.* import dev.proxyfox.bot.command.MemberCommands.registerBaseMemberCommands import dev.proxyfox.bot.command.SwitchCommands.registerSwitchCommands import dev.proxyfox.bot.command.context.DiscordContext @@ -19,13 +19,7 @@ import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.command.context.system import dev.proxyfox.bot.command.node.attachment -import dev.proxyfox.bot.deferChatInputCommand -import dev.proxyfox.bot.hasUnixValue -import dev.proxyfox.bot.kordColor -import dev.proxyfox.bot.prompts.Button import dev.proxyfox.bot.prompts.Pager -import dev.proxyfox.bot.prompts.TimedYesNoPrompt -import dev.proxyfox.bot.system import dev.proxyfox.command.CommandParser import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.fromColor @@ -696,12 +690,10 @@ object SystemCommands : CommandRegistrar { } private suspend fun delete(ctx: DiscordContext): Boolean { - TimedYesNoPrompt.build( - runner = ctx.getUser()!!.id, - channel = ctx.getChannel(), + ctx.timedYesNoPrompt( message = "Are you sure you want to delete your system?\n" + "The data will be lost forever (A long time!)", - yes = Button("Delete system", Button.wastebasket, ButtonStyle.Danger) { + yes = "Delete system" to { val export = Exporter.export(ctx.getUser()!!.id.value) ctx.respondFiles( null, @@ -710,6 +702,8 @@ object SystemCommands : CommandRegistrar { database.dropSystem(ctx.getUser()!!) content = "System deleted." }, + yesEmoji = Emojis.wastebasket, + danger = true ) return true } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index 621417f0..b7aab76d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -8,6 +8,8 @@ package dev.proxyfox.bot.command.context +import dev.kord.common.entity.ButtonStyle +import dev.kord.common.entity.DiscordPartialEmoji import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.MessageChannelBehavior @@ -15,16 +17,23 @@ import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.* import dev.kord.rest.NamedFile import dev.kord.rest.builder.message.EmbedBuilder +import dev.kord.rest.builder.message.modify.MessageModifyBuilder +import dev.kord.rest.builder.message.modify.actionRow +import dev.proxyfox.bot.Emojis import dev.proxyfox.bot.command.menu.DiscordMenu +import dev.proxyfox.bot.command.menu.DiscordScreen +import dev.proxyfox.bot.schedule +import dev.proxyfox.bot.scheduler import dev.proxyfox.command.CommandContext import dev.proxyfox.command.NodeActionParam -import dev.proxyfox.command.menu.CommandMenu import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.int import dev.proxyfox.command.node.builtin.string import dev.proxyfox.database.database import dev.proxyfox.database.records.misc.ProxiedMessageRecord import dev.proxyfox.database.records.system.SystemRecord +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes abstract class DiscordContext(override val value: T) : CommandContext() { abstract fun getAttachment(): Attachment? @@ -58,8 +67,56 @@ abstract class DiscordContext(override val value: T) : CommandContext() { ): Pair suspend fun interactionMenu(action: suspend DiscordMenu.() -> Unit) { - @Suppress("UNCHECKED_CAST") - menu(action as CommandMenu.() -> Unit) + menu { + this as DiscordMenu + action() + } + } + + suspend fun timedYesNoPrompt( + message: String, + yes: Pair Unit>, + no: Pair Unit> = "Cancel" to { content = "Action cancelled." }, + timeout: Duration = 1.minutes, + yesEmoji: DiscordPartialEmoji = Emojis.check, + noEmoji: DiscordPartialEmoji = Emojis.multiply, + timeoutAction: suspend MessageModifyBuilder.() -> Unit = no.second, + danger: Boolean = false + ) { + interactionMenu { + default { + this as DiscordScreen + onInit { + edit { + content = message + actionRow { + interactionButton(if (danger) ButtonStyle.Danger else ButtonStyle.Primary, "yes") { + emoji = yesEmoji + label = yes.first + } + interactionButton(ButtonStyle.Secondary, "no") { + emoji = noEmoji + label = no.first + } + } + } + scheduler.schedule(timeout) { + if (!closed) { + edit { + timeoutAction() + } + close() + } + } + } + button("yes") { + edit(yes.second) + } + button("no") { + edit(no.second) + } + } + } } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt index 2baeb44e..98d78b36 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt @@ -16,15 +16,18 @@ import kotlinx.coroutines.Job abstract class DiscordMenu : CommandMenu() { internal val jobs = arrayListOf() + var closed = false + override suspend fun close() { jobs.forEach { it.cancel() } + closed = true } override suspend fun createScreen(name: String): CommandScreen { return DiscordScreen(name) } - abstract suspend fun edit(builder: MessageModifyBuilder.() -> Unit) + abstract suspend fun edit(builder: suspend MessageModifyBuilder.() -> Unit) } \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt index 34ddb4c9..384e3503 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt @@ -16,8 +16,10 @@ import dev.proxyfox.bot.kord import dev.proxyfox.common.onlyIf class DiscordMessageMenu(val message: Message) : DiscordMenu() { - override suspend fun edit(builder: MessageModifyBuilder.() -> Unit) { - message.edit(builder) + override suspend fun edit(builder: suspend MessageModifyBuilder.() -> Unit) { + message.edit { + builder() + } } override suspend fun init() { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt index 8a53a421..edd22f3d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt @@ -16,8 +16,10 @@ import dev.proxyfox.bot.kord import dev.proxyfox.common.onlyIf class InteractionCommandMenu(val interaction: EphemeralMessageInteractionResponse) : DiscordMenu() { - override suspend fun edit(builder: MessageModifyBuilder.() -> Unit) { - interaction.edit(builder) + override suspend fun edit(builder: suspend MessageModifyBuilder.() -> Unit) { + interaction.edit { + builder() + } } override suspend fun init() { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Button.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Button.kt deleted file mode 100644 index abbf4a6b..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Button.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2022-2023, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.prompts - -import dev.kord.common.entity.ButtonStyle -import dev.kord.common.entity.DiscordPartialEmoji -import dev.kord.common.entity.optional.optional -import dev.kord.core.entity.GuildEmoji -import dev.kord.core.entity.ReactionEmoji -import dev.kord.rest.builder.message.modify.MessageModifyBuilder - -@JvmRecord -data class Button( - val label: String? = "Confirm", - val emoji: DiscordPartialEmoji? = null, - val style: ButtonStyle = ButtonStyle.Secondary, - val action: suspend MessageModifyBuilder.() -> Unit, -) { - companion object { - val check = DiscordPartialEmoji(name = "✅") - val multiply = DiscordPartialEmoji(name = "✖") - val wastebasket = DiscordPartialEmoji(name = "🗑") - val move = DiscordPartialEmoji(name = "\uD83D\uDD00") - - val ReactionEmoji.Unicode.partial get() = DiscordPartialEmoji(name = name) - - val ReactionEmoji.Custom.partial get() = DiscordPartialEmoji(id = id, name = name, animated = isAnimated.optional()) - - val GuildEmoji.partial get() = DiscordPartialEmoji(id = id, animated = isAnimated.optional()) - } -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt deleted file mode 100644 index 58ab357d..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedYesNoPrompt.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2022-2023, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.prompts - -import dev.kord.common.entity.ButtonStyle -import dev.kord.common.entity.Snowflake -import dev.kord.core.behavior.channel.MessageChannelBehavior -import dev.kord.core.behavior.channel.createMessage -import dev.kord.core.behavior.edit -import dev.kord.core.behavior.interaction.response.edit -import dev.kord.core.entity.Message -import dev.kord.core.event.interaction.ButtonInteractionCreateEvent -import dev.kord.core.event.message.ReactionAddEvent -import dev.kord.core.on -import dev.kord.rest.builder.component.ActionRowBuilder -import dev.kord.rest.builder.message.modify.MessageModifyBuilder -import dev.proxyfox.bot.kord -import dev.proxyfox.bot.prompts.Button.Companion.check -import dev.proxyfox.bot.prompts.Button.Companion.multiply -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes - -// TODO: Move to DiscordContext -/** - * @author Ampflower - * @since ${version} - **/ -class TimedYesNoPrompt( - runner: Snowflake, - reference: Message, - timeout: Duration = 1.minutes, - private val yes: suspend MessageModifyBuilder.() -> Unit, - private val no: suspend MessageModifyBuilder.() -> Unit, -) : TimedPrompt( - runner, - reference, - timeout -) { - init { - jobs = listOf( - kord.on(consumer = this::onInteraction), - kord.on(consumer = this::onReaction), - ) - } - - private suspend fun onInteraction(event: ButtonInteractionCreateEvent) = event.run { - if (interaction.message == reference && interaction.user.id == runner) { - when (interaction.componentId) { - "yes" -> { - interaction.deferPublicMessageUpdate().edit { yes(); components = mutableListOf() } - closeInternal() - } - - "no" -> { - interaction.deferPublicMessageUpdate().edit { no(); components = mutableListOf() } - closeInternal() - } - } - } - } - - private suspend fun onReaction(event: ReactionAddEvent) = event.run { - if (message == reference && userId == runner) { - when (emoji.name) { - "✅" -> { - reference.edit { - yes() - components = mutableListOf() - } - closeInternal() - } - - "❌" -> { - reference.edit { no(); components = mutableListOf() } - closeInternal() - } - } - } - } - - override suspend fun close() { - reference.edit { no(); components = mutableListOf() } - closeInternal() - } - - companion object { - suspend fun build( - runner: Snowflake, - channel: MessageChannelBehavior, - timeout: Duration = 1.minutes, - message: String, - yes: Pair Unit>, - no: Pair Unit> = "Cancel" to { content = "Action cancelled." }, - ): TimedYesNoPrompt { - val msg = channel.createMessage { - content = message - components += ActionRowBuilder().apply { - interactionButton(ButtonStyle.Primary, "yes") { - emoji = check - label = yes.first - } - interactionButton(ButtonStyle.Secondary, "no") { - emoji = multiply - label = no.first - } - } - } - return TimedYesNoPrompt( - runner, - msg, - timeout, - yes.second, - no.second, - ) - } - - suspend fun build( - runner: Snowflake, - channel: MessageChannelBehavior, - timeout: Duration = 1.minutes, - message: String, - yes: Button, - no: Button = Button("Cancel", multiply) { content = "Action cancelled." }, - ): TimedYesNoPrompt { - val msg = channel.createMessage { - content = message - components += ActionRowBuilder().apply { - interactionButton(yes.style, "yes") { - emoji = yes.emoji - label = yes.label - } - interactionButton(no.style, "no") { - emoji = no.emoji - label = no.label - } - } - } - return TimedYesNoPrompt( - runner, - msg, - timeout, - yes.action, - no.action, - ) - } - } -} \ No newline at end of file From c6e9046c5b5cba257fe798d1df7dc01ed598ef69 Mon Sep 17 00:00:00 2001 From: Octal Date: Fri, 17 Feb 2023 00:11:26 -0600 Subject: [PATCH 099/137] Add visibility flag for interactionMenu --- .../bot/command/context/DiscordContext.kt | 16 +++++++++----- .../command/context/DiscordMessageContext.kt | 8 +++---- .../context/InteractionCommandContext.kt | 21 ++++++++++--------- .../bot/command/menu/DiscordMessageMenu.kt | 5 +++-- .../command/menu/InteractionCommandMenu.kt | 7 +++++-- 5 files changed, 34 insertions(+), 23 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index b7aab76d..5bd3d76c 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -26,6 +26,7 @@ import dev.proxyfox.bot.schedule import dev.proxyfox.bot.scheduler import dev.proxyfox.command.CommandContext import dev.proxyfox.command.NodeActionParam +import dev.proxyfox.command.menu.CommandMenu import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.int import dev.proxyfox.command.node.builtin.string @@ -66,22 +67,27 @@ abstract class DiscordContext(override val value: T) : CommandContext() { messageId: Snowflake? ): Pair - suspend fun interactionMenu(action: suspend DiscordMenu.() -> Unit) { - menu { - this as DiscordMenu + override suspend fun menu(action: suspend CommandMenu.() -> Unit) { + interactionMenu { action() } } + abstract suspend fun interactionMenu(private: Boolean = false, action: suspend DiscordMenu.() -> Unit) + suspend fun timedYesNoPrompt( message: String, yes: Pair Unit>, - no: Pair Unit> = "Cancel" to { content = "Action cancelled." }, + no: Pair Unit> = "Cancel" to { + content = "Action cancelled." + components = null + }, timeout: Duration = 1.minutes, yesEmoji: DiscordPartialEmoji = Emojis.check, noEmoji: DiscordPartialEmoji = Emojis.multiply, timeoutAction: suspend MessageModifyBuilder.() -> Unit = no.second, - danger: Boolean = false + danger: Boolean = false, + public: Boolean = true ) { interactionMenu { default { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index b3a8f92f..d31127a8 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -13,8 +13,8 @@ import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.* import dev.kord.rest.builder.message.EmbedBuilder +import dev.proxyfox.bot.command.menu.DiscordMenu import dev.proxyfox.bot.command.menu.DiscordMessageMenu -import dev.proxyfox.command.MenuBuilder import dev.proxyfox.common.applyAsync import dev.proxyfox.database.database import dev.proxyfox.database.records.misc.ProxiedMessageRecord @@ -79,9 +79,9 @@ class DiscordMessageContext(message: Message, override val command: String): Dis return message to databaseMessage } - override suspend fun menu(action: MenuBuilder) { - val message = getChannel().createMessage("Thinking...") - val menu = DiscordMessageMenu(message) + override suspend fun interactionMenu(private: Boolean, action: suspend DiscordMenu.() -> Unit) { + val message = getChannel(private).createMessage("Thinking...") + val menu = DiscordMessageMenu(message, getUser()?.id ?: Snowflake(0)) menu.action() menu.init() } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index 08abfce3..6a5f2f5e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -18,8 +18,8 @@ import dev.kord.core.entity.* import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed +import dev.proxyfox.bot.command.menu.DiscordMenu import dev.proxyfox.bot.command.menu.InteractionCommandMenu -import dev.proxyfox.command.MenuBuilder import dev.proxyfox.database.database import dev.proxyfox.database.records.misc.ProxiedMessageRecord import dev.proxyfox.database.records.system.SystemRecord @@ -29,15 +29,6 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : DiscordContext(value) { override val command: String = "" - override suspend fun menu(action: MenuBuilder) { - val message = value.interaction.deferEphemeralResponse() - val menu = InteractionCommandMenu(message.respond { - content = "Thinking..." - }) - menu.action() - menu.init() - } - @OptIn(ExperimentalStdlibApi::class) override fun getAttachment(): Attachment? { return value.interaction.command.attachments.values.stream().findFirst().getOrNull() @@ -146,4 +137,14 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : val message = getChannel().getMessageOrNull(Snowflake(databaseMessage.newMessageId)) return message to databaseMessage } + + override suspend fun interactionMenu(private: Boolean, action: suspend DiscordMenu.() -> Unit) { + val message = + if (private) value.interaction.deferEphemeralResponse() else value.interaction.deferPublicResponse() + val menu = InteractionCommandMenu(message.respond { + content = "Thinking..." + }) + menu.action() + menu.init() + } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt index 384e3503..64fc0cdc 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt @@ -8,6 +8,7 @@ package dev.proxyfox.bot.command.menu +import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.edit import dev.kord.core.entity.Message import dev.kord.core.event.interaction.ButtonInteractionCreateEvent @@ -15,7 +16,7 @@ import dev.kord.rest.builder.message.modify.MessageModifyBuilder import dev.proxyfox.bot.kord import dev.proxyfox.common.onlyIf -class DiscordMessageMenu(val message: Message) : DiscordMenu() { +class DiscordMessageMenu(val message: Message, val userId: Snowflake) : DiscordMenu() { override suspend fun edit(builder: suspend MessageModifyBuilder.() -> Unit) { message.edit { builder() @@ -32,7 +33,7 @@ class DiscordMessageMenu(val message: Message) : DiscordMenu() { } private suspend fun interact(button: ButtonInteractionCreateEvent) { - if (button.interaction.message.id != message.id) return + if (button.interaction.user.id != userId) return active!!.click(button.interaction.componentId) } } \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt index edd22f3d..386638aa 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt @@ -10,12 +10,13 @@ package dev.proxyfox.bot.command.menu import dev.kord.core.behavior.interaction.response.edit import dev.kord.core.entity.interaction.response.EphemeralMessageInteractionResponse +import dev.kord.core.entity.interaction.response.MessageInteractionResponse import dev.kord.core.event.interaction.ButtonInteractionCreateEvent import dev.kord.rest.builder.message.modify.MessageModifyBuilder import dev.proxyfox.bot.kord import dev.proxyfox.common.onlyIf -class InteractionCommandMenu(val interaction: EphemeralMessageInteractionResponse) : DiscordMenu() { +class InteractionCommandMenu(val interaction: MessageInteractionResponse) : DiscordMenu() { override suspend fun edit(builder: suspend MessageModifyBuilder.() -> Unit) { interaction.edit { builder() @@ -33,7 +34,9 @@ class InteractionCommandMenu(val interaction: EphemeralMessageInteractionRespons private suspend fun interact(button: ButtonInteractionCreateEvent) { - button.interaction.deferEphemeralMessageUpdate() + if (interaction is EphemeralMessageInteractionResponse) + button.interaction.deferEphemeralMessageUpdate() + else button.interaction.deferPublicMessageUpdate() active!!.click(button.interaction.componentId) } } From 7bfe0525df7aeb21a5af8db66fbda306d334cccd Mon Sep 17 00:00:00 2001 From: Octal Date: Fri, 17 Feb 2023 20:58:49 -0600 Subject: [PATCH 100/137] Use ProxyFox-Command for terminal input --- .../proxyfox/bot/terminal/TerminalCommands.kt | 21 +++++++++--- .../proxyfox/bot/terminal/TerminalContext.kt | 33 +++++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalContext.kt diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalCommands.kt index dfd433d6..378ca03a 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalCommands.kt @@ -8,7 +8,10 @@ package dev.proxyfox.bot.terminal +import dev.proxyfox.command.CommandParser +import dev.proxyfox.command.node.builtin.literal import dev.proxyfox.common.printStep +import kotlinx.coroutines.runBlocking import kotlin.concurrent.thread import kotlin.system.exitProcess @@ -16,19 +19,27 @@ import kotlin.system.exitProcess * Terminal related functions and variables * @author Oliver * */ - object TerminalCommands { + val parser = CommandParser() + suspend fun start() { printStep("Start reading console input", 1) + parser.literal("exit", "stop", "quit") { + executes { + exitProcess(0) + } + } startThread() } - suspend fun startThread() { + private fun startThread() { printStep("Launching thread", 2) thread { - while (true) { - val input = readln() - if (input.lowercase() == "exit") exitProcess(0) + runBlocking { + while (true) { + val input = readln() + parser.parse(TerminalContext(input)) + } } } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalContext.kt new file mode 100644 index 00000000..cb3806c9 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalContext.kt @@ -0,0 +1,33 @@ +package dev.proxyfox.bot.terminal + +import dev.proxyfox.command.CommandContext +import dev.proxyfox.command.menu.CommandMenu +import dev.proxyfox.common.logger + +class TerminalContext(override val command: String) : CommandContext() { + override val value: String = command + + override suspend fun menu(action: suspend CommandMenu.() -> Unit) { + + } + + override suspend fun respondFailure(text: String, private: Boolean): String { + logger.error(text) + return text + } + + override suspend fun respondPlain(text: String, private: Boolean): String { + logger.info(text) + return text + } + + override suspend fun respondSuccess(text: String, private: Boolean): String { + logger.info(text) + return text + } + + override suspend fun respondWarning(text: String, private: Boolean): String { + logger.warn(text) + return text + } +} \ No newline at end of file From 710c564e3db96aa3e924cd17995067322588beb0 Mon Sep 17 00:00:00 2001 From: Octal Date: Fri, 17 Feb 2023 21:05:16 -0600 Subject: [PATCH 101/137] fix build --- .../kotlin/dev/proxyfox/bot/terminal/TerminalContext.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalContext.kt index cb3806c9..29770d0f 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/terminal/TerminalContext.kt @@ -1,3 +1,11 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.terminal import dev.proxyfox.command.CommandContext From 2aee338f2c2f2bd5dbc3d849c50ed51664fe514e Mon Sep 17 00:00:00 2001 From: Octal Date: Fri, 24 Feb 2023 21:59:26 -0600 Subject: [PATCH 102/137] Add a CommonMessages enum to build common messages + add env-controlled prefix --- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 5 ++-- .../dev/proxyfox/bot/command/Commands.kt | 14 +++++++---- .../proxyfox/bot/command/CommonMessages.kt | 23 +++++++++++++++++++ .../dev/proxyfox/bot/command/GroupCommands.kt | 2 +- .../dev/proxyfox/bot/command/MiscCommands.kt | 6 ++--- .../proxyfox/bot/command/SystemCommands.kt | 7 ++++-- 6 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommonMessages.kt diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index bc76c889..0ddf8a2c 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -19,7 +19,6 @@ import dev.kord.core.cache.data.AttachmentData import dev.kord.core.cache.data.EmbedData import dev.kord.core.entity.Attachment import dev.kord.core.entity.Embed -import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.interaction.SubCommand import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent @@ -46,7 +45,9 @@ import dev.proxyfox.database.records.system.SystemServerSettingsRecord import kotlinx.datetime.toJavaLocalDate import org.slf4j.LoggerFactory -val prefixRegex = Regex("^(?:(<@!?${kord.selfId}>)|pf[>;!:])\\s*", RegexOption.IGNORE_CASE) +val prefix = System.getenv("PROXYFOX_PREFIX") ?: "pf" + +val prefixRegex = Regex("^(?:(<@!?${kord.selfId}>)|$prefix[>;!:])\\s*", RegexOption.IGNORE_CASE) private val logger = LoggerFactory.getLogger("MessageHandler") diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 67e034e1..19676925 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -81,7 +81,7 @@ suspend fun checkSystem(ctx: DiscordContext, system: SystemRecord?, priva returns(true) implies (system != null) } system ?: run { - ctx.respondFailure("System does not exist. Create one using a slash command or `pf>system new`", private) + ctx.respondFailure(CommonMessages.NOT_FOUND("System"), private) return false } return true @@ -93,7 +93,7 @@ suspend fun checkGroup(ctx: DiscordContext, group: GroupRecord?, private: returns(true) implies (group != null) } group ?: run { - ctx.respondFailure("Group does not exist. Create one using a slash command or `pf>group new`", private) + ctx.respondFailure(CommonMessages.NOT_FOUND("Group"), private) return false } return true @@ -106,7 +106,7 @@ suspend fun checkMember(ctx: DiscordContext, member: MemberRecord?, priva } member ?: run { - ctx.respondFailure("Member does not exist. Create one using a slash command or `pf>member new`", private) + ctx.respondFailure(CommonMessages.NOT_FOUND("Member"), private) return false } return true @@ -119,7 +119,13 @@ suspend fun checkSwitch(ctx: DiscordContext, switch: SystemSwitchRecord?) } switch ?: run { - ctx.respondFailure("Looks like you haven't registered any switches yet. Create one using a slash command or `pf>switch`") + ctx.respondFailure( + "Looks like you haven't registered any switches yet. Create one using `/switch create` or ${ + CommonMessages.TEXT_COMMAND( + "switch" + ) + }" + ) return false } return true diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommonMessages.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommonMessages.kt new file mode 100644 index 00000000..9dba9959 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommonMessages.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command + +enum class CommonMessages(val builder: (Array) -> String) { + TEXT_COMMAND({ + "`${dev.proxyfox.bot.prefix}>${it[0]}" + }), + NOT_FOUND({ + "${it[0]} not found. Create one using `/system create` or ${TEXT_COMMAND("system new")}" + }), + NOT_FOUND_WITH_NAME({ + "${it[0]} ${it[1]} not found. Create one using `/member create` or ${TEXT_COMMAND("member new")}" + }) +} + +operator fun CommonMessages.invoke(vararg strings: String): String = builder(strings) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt index daf3b582..db0250af 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/GroupCommands.kt @@ -86,7 +86,7 @@ object GroupCommands : CommandRegistrar { } field { name = "Members (`${members}`)" - value = "See `pf>group ${group.id} list`" + value = "See ${CommonMessages.TEXT_COMMAND("group ${group.id} list")}" inline = true } group.description?.let { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 76fdafe1..2297ca2c 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -955,9 +955,9 @@ object MiscCommands : CommandRegistrar { private const val help: String = """To view commands for ProxyFox, visit For quick setup: -- pf>system new name -- pf>member new John Doe -- pf>member "John Doe" proxy j:text""" +- /system create +- /member create +- /member proxy-add""" private const val explain: String = """ProxyFox is modern Discord bot designed to help systems communicate. diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index e9d6311c..7ca95635 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -56,8 +56,6 @@ object SystemCommands : CommandRegistrar { subCommand("create", "Create a system") { name(required = false) runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false val name = value.interaction.command.strings["name"] create(this, name) } @@ -429,6 +427,11 @@ object SystemCommands : CommandRegistrar { } private suspend fun create(ctx: DiscordContext, name: String?): Boolean { + if (database.fetchSystemFromUser(ctx.getUser()) != null) { + + return false + } + val system = database.getOrCreateSystem(ctx.getUser()!!) system.name = name database.updateSystem(system) From fbaa830edb0b839c38d2cb261d6e128b6b9d2c17 Mon Sep 17 00:00:00 2001 From: Octal Date: Fri, 24 Feb 2023 22:09:29 -0600 Subject: [PATCH 103/137] Misc fixes --- .../proxyfox/bot/command/SystemCommands.kt | 1 + .../bot/command/context/DiscordContext.kt | 26 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 7ca95635..1adc0e56 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -711,6 +711,7 @@ object SystemCommands : CommandRegistrar { ) database.dropSystem(ctx.getUser()!!) content = "System deleted." + components = null }, yesEmoji = Emojis.wastebasket, danger = true diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index 5bd3d76c..0b1c4f23 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -87,9 +87,9 @@ abstract class DiscordContext(override val value: T) : CommandContext() { noEmoji: DiscordPartialEmoji = Emojis.multiply, timeoutAction: suspend MessageModifyBuilder.() -> Unit = no.second, danger: Boolean = false, - public: Boolean = true + private: Boolean = true ) { - interactionMenu { + interactionMenu(private) { default { this as DiscordScreen onInit { @@ -117,9 +117,11 @@ abstract class DiscordContext(override val value: T) : CommandContext() { } button("yes") { edit(yes.second) + close() } button("no") { edit(no.second) + close() } } } @@ -128,14 +130,16 @@ abstract class DiscordContext(override val value: T) : CommandContext() { // Get a DiscordContext. fun > CommandNode.runs(action: suspend DiscordContext.() -> Boolean) { - @Suppress("UNCHECKED_CAST") - executes(action as suspend CommandContext.() -> Boolean) + executes { + if (this !is DiscordContext) return@executes false + action() + } } suspend fun > CommandNode.guild(action: NodeActionParam) { action { - val ctx = this as? DiscordContext ?: return@action null - ctx.getGuild()?.id + if (this !is DiscordContext) return@action null + getGuild()?.id } int("server") { action { @@ -144,16 +148,10 @@ suspend fun > CommandNode.guild(action: NodeActio } } -//suspend fun > CommandNode.id(name: String, action: NodeActionParam) { -// string(name) { -// -// } -//} - suspend fun > CommandNode.system(action: NodeActionParam) { action { - val ctx = this as? DiscordContext ?: return@action null - database.fetchSystemFromUser(ctx.getUser()) + if (this !is DiscordContext) return@action null + database.fetchSystemFromUser(getUser()) } string("sysid") { action { From 672f812675ba9e6f4422dddddabeaa6dc76987dc Mon Sep 17 00:00:00 2001 From: Octal Date: Fri, 24 Feb 2023 22:20:01 -0600 Subject: [PATCH 104/137] Support suppressed errors in error handler --- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index af520675..bbe7fc63 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -272,6 +272,15 @@ suspend fun handleError(err: Throwable, channel: MessageChannelBehavior) { if (it.className.startsWith("dev.proxyfox")) cause += " at $it\n" } + for (suppressed in err.suppressed) { + var supCause = "" + val supReason = suppressed.message?.replace(webhook, "[WEBHOOK]")?.replace(token, "[TOKEN]") + suppressed.stackTrace.forEach { + if (it.className.startsWith("dev.proxyfox")) + supCause += " at $it\n" + } + cause += " Caused by ${suppressed.javaClass.name}: $supReason\n$supCause" + } channel.createMessage( "An unexpected error occurred.\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" ) From 57695170de7105d9186df247766529d7af0a615b Mon Sep 17 00:00:00 2001 From: Octal Date: Fri, 24 Feb 2023 22:35:49 -0600 Subject: [PATCH 105/137] Add the ability to defer responses --- .../dev/proxyfox/bot/command/MiscCommands.kt | 2 + .../bot/command/context/DiscordContext.kt | 2 + .../command/context/DiscordMessageContext.kt | 7 ++- .../context/InteractionCommandContext.kt | 54 +++++++++---------- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 2297ca2c..a9c5d856 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -922,6 +922,8 @@ object MiscCommands : CommandRegistrar { return false } + ctx.deferResponse() + return try { val importer = withContext(Dispatchers.IO) { uri!!.toURL().openStream().reader().use { import(it, ctx.getUser()) } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index 0b1c4f23..5dc6b945 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -48,6 +48,8 @@ abstract class DiscordContext(override val value: T) : CommandContext() { embed: suspend EmbedBuilder.() -> Unit ): T + abstract suspend fun deferResponse(private: Boolean = false) + abstract suspend fun tryDeleteTrigger(reason: String? = null) abstract suspend fun optionalSuccess(text: String): T diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index d31127a8..3faabf32 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -58,6 +58,8 @@ class DiscordMessageContext(message: Message, override val command: String): Dis } } + override suspend fun deferResponse(private: Boolean) = Unit + override suspend fun tryDeleteTrigger(reason: String?) { if (value.getGuildOrNull() != null) value.delete(reason) } @@ -66,7 +68,10 @@ class DiscordMessageContext(message: Message, override val command: String): Dis return value } - override suspend fun getDatabaseMessage(system: SystemRecord?, messageId: Snowflake?): Pair { + override suspend fun getDatabaseMessage( + system: SystemRecord?, + messageId: Snowflake? + ): Pair { val databaseMessage = if (messageId != null) { database.fetchMessage(messageId) } else if (value.referencedMessage != null) { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index 6a5f2f5e..9a3b3749 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -13,11 +13,13 @@ import dev.kord.core.behavior.channel.GuildChannelBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.behavior.interaction.response.DeferredMessageInteractionResponseBehavior import dev.kord.core.behavior.interaction.response.respond import dev.kord.core.entity.* import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed +import dev.kord.rest.builder.message.modify.embed import dev.proxyfox.bot.command.menu.DiscordMenu import dev.proxyfox.bot.command.menu.InteractionCommandMenu import dev.proxyfox.database.database @@ -29,24 +31,23 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : DiscordContext(value) { override val command: String = "" + var deferred: DeferredMessageInteractionResponseBehavior? = null + @OptIn(ExperimentalStdlibApi::class) override fun getAttachment(): Attachment? { return value.interaction.command.attachments.values.stream().findFirst().getOrNull() } override suspend fun respondFailure(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { - if (private) - value.interaction.respondEphemeral { - content = "❌ $text" - } - else value.interaction.respondPublic { - content = "❌️ $text" - } - return value + return respondPlain("❌️ $text", private) } override suspend fun respondPlain(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { - if (private) + if (deferred != null) + deferred!!.respond { + content = text + } + else if (private) value.interaction.respondEphemeral { content = text } @@ -57,25 +58,11 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : } override suspend fun respondSuccess(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { - if (private) - value.interaction.respondEphemeral { - content = "✅ $text" - } - else value.interaction.respondPublic { - content = "✅️ $text" - } - return value + return respondPlain("✅️ $text", private) } override suspend fun respondWarning(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { - if (private) - value.interaction.respondEphemeral { - content = "⚠️ $text" - } - else value.interaction.respondPublic { - content = "⚠️ $text" - } - return value + return respondPlain("⚠️ $text", private) } override suspend fun getChannel(private: Boolean): MessageChannelBehavior { @@ -102,7 +89,13 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : text: String?, embed: suspend EmbedBuilder.() -> Unit ): ChatInputCommandInteractionCreateEvent { - if (private) + if (deferred != null) + deferred!!.respond { + embed { + embed() + } + } + else if (private) value.interaction.respondEphemeral { embed { embed() @@ -116,6 +109,12 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : return value } + override suspend fun deferResponse(private: Boolean) { + deferred = if (private) + value.interaction.deferEphemeralResponse() + else value.interaction.deferPublicResponse() + } + override suspend fun tryDeleteTrigger(reason: String?) { } @@ -140,7 +139,8 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : override suspend fun interactionMenu(private: Boolean, action: suspend DiscordMenu.() -> Unit) { val message = - if (private) value.interaction.deferEphemeralResponse() else value.interaction.deferPublicResponse() + deferred + ?: if (private) value.interaction.deferEphemeralResponse() else value.interaction.deferPublicResponse() val menu = InteractionCommandMenu(message.respond { content = "Thinking..." }) From 88ad2b462fceaabf49b33cbc9525664a25179e0a Mon Sep 17 00:00:00 2001 From: Octal Date: Fri, 24 Feb 2023 22:41:58 -0600 Subject: [PATCH 106/137] Fix default visibility of timed y/n --- .../kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index 5dc6b945..0dd2770e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -89,7 +89,7 @@ abstract class DiscordContext(override val value: T) : CommandContext() { noEmoji: DiscordPartialEmoji = Emojis.multiply, timeoutAction: suspend MessageModifyBuilder.() -> Unit = no.second, danger: Boolean = false, - private: Boolean = true + private: Boolean = false ) { interactionMenu(private) { default { From 0952085e66e518a02c1a9bbec6fa1b280ebadefc Mon Sep 17 00:00:00 2001 From: Octal Date: Fri, 24 Feb 2023 22:43:58 -0600 Subject: [PATCH 107/137] More fixes with timed y/n --- .../dev/proxyfox/bot/command/SystemCommands.kt | 1 - .../proxyfox/bot/command/context/DiscordContext.kt | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 1adc0e56..7ca95635 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -711,7 +711,6 @@ object SystemCommands : CommandRegistrar { ) database.dropSystem(ctx.getUser()!!) content = "System deleted." - components = null }, yesEmoji = Emojis.wastebasket, danger = true diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index 0dd2770e..52382ba2 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -82,7 +82,6 @@ abstract class DiscordContext(override val value: T) : CommandContext() { yes: Pair Unit>, no: Pair Unit> = "Cancel" to { content = "Action cancelled." - components = null }, timeout: Duration = 1.minutes, yesEmoji: DiscordPartialEmoji = Emojis.check, @@ -111,6 +110,7 @@ abstract class DiscordContext(override val value: T) : CommandContext() { scheduler.schedule(timeout) { if (!closed) { edit { + components = arrayListOf() timeoutAction() } close() @@ -118,11 +118,17 @@ abstract class DiscordContext(override val value: T) : CommandContext() { } } button("yes") { - edit(yes.second) + edit { + components = arrayListOf() + yes.second(this) + } close() } button("no") { - edit(no.second) + edit { + components = arrayListOf() + no.second(this) + } close() } } From c59a629bf65a5e1d6707ab50a569a93190d2ab22 Mon Sep 17 00:00:00 2001 From: Octal Date: Sat, 25 Feb 2023 18:27:26 -0600 Subject: [PATCH 108/137] Update gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f390c7eb..f961aa70 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ out/ mods/ proxyfox.db.properties .env -.pf-command-lock \ No newline at end of file +.pf-command-lock +.quilt/ +config/ \ No newline at end of file From 9a2cb00839bfe5a116bc6ecd197019d4b5ee8c5a Mon Sep 17 00:00:00 2001 From: Octal Date: Sat, 25 Feb 2023 20:11:22 -0600 Subject: [PATCH 109/137] Misc command refactors --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 2 +- .../dev/proxyfox/bot/command/Commands.kt | 10 +++ .../dev/proxyfox/bot/command/MiscCommands.kt | 66 +++++++++++-------- 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index bbe7fc63..0ecc0cc6 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -279,7 +279,7 @@ suspend fun handleError(err: Throwable, channel: MessageChannelBehavior) { if (it.className.startsWith("dev.proxyfox")) supCause += " at $it\n" } - cause += " Caused by ${suppressed.javaClass.name}: $supReason\n$supCause" + cause += " Suppressed: ${suppressed.javaClass.name}: $supReason\n$supCause" } channel.createMessage( "An unexpected error occurred.\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 19676925..6f3d57a0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -46,12 +46,22 @@ fun SubCommandBuilder.guild() { required = false } } + fun SubCommandBuilder.name(name: String = "name", required: Boolean = true) { string(name, "The $name to use") { this.required = required } } +fun SubCommandBuilder.enum(name: String, required: Boolean = true, enum: ArrayList) { + string(name, "The $name to use") { + this.required = required + for (value in enum) { + choice(value, value) + } + } +} + fun SubCommandBuilder.system(name: String = "system") { string(name, "The $name to use") { required = false diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index a9c5d856..fe4609a9 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -96,6 +96,16 @@ object MiscCommands : CommandRegistrar { true } } + subCommand("fox", "Gets a random fox picture") { + runs("info") { + getFox(this) + } + } + subCommand("time", "Displays the current time") { + runs("info") { + time(this) + } + } } deferChatInputCommand("moderation", "Moderator-only commands") { subCommand("role", "Access the role required for proxying") { @@ -139,42 +149,38 @@ object MiscCommands : CommandRegistrar { } } } - deferChatInputCommand("misc", "Other commands that don't fit in a category") { - subCommand("fox", "Gets a random fox picture") { - runs("misc") { - getFox(this) - } - } - subCommand("import", "Import a system") { + deferChatInputCommand("management", "Other management commands that don't fit in a category") { + subCommand("import-file", "Import a system") { attachment("import", "The file to import") { required = true } - runs("misc") { + runs("management") { import(this, value.interaction.command.attachments["import"]!!.url) } } + subCommand("import-url", "Import a system") { + name("file") + runs("management") { + import(this, value.interaction.command.strings["file"]!!) + } + } subCommand("export", "Export your system") { - runs("misc") { + runs("management") { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false export(this) } } - subCommand("time", "Displays the current time") { - runs("misc") { - time(this) - } - } subCommand("autoproxy", "Changes the autoproxy type") { - name("value") - runs("misc") { + enum("value", enum = arrayListOf("off", "latch", "front")) + runs("management") { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - val type: AutoProxyMode? = when(value.interaction.command.strings["value"]) { + val type: AutoProxyMode? = when (value.interaction.command.strings["value"]) { null -> null - "off", "disable" -> AutoProxyMode.OFF - "latch", "l" -> AutoProxyMode.LATCH - "front", "f" -> AutoProxyMode.FRONT + "off" -> AutoProxyMode.OFF + "latch" -> AutoProxyMode.LATCH + "front" -> AutoProxyMode.FRONT else -> AutoProxyMode.MEMBER } val member = if (type == AutoProxyMode.MEMBER) { @@ -188,11 +194,12 @@ object MiscCommands : CommandRegistrar { subCommand("proxy", "Toggles proxying for this server") { bool("value", "the value to set") guild() - runs("misc") { + runs("management") { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false val enabled = value.interaction.command.booleans["value"] - val guildId = value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id + val guildId = + value.interaction.command.integers["server"]?.toULong()?.let { Snowflake(it) } ?: getGuild()?.id guildId ?: run { respondFailure("Command not ran in server.") return@runs false @@ -206,16 +213,17 @@ object MiscCommands : CommandRegistrar { } } subCommand("serverautoproxy", "Changes the autoproxy type for the server") { - name("value") + enum("value", enum = arrayListOf("off", "latch", "front", "on")) guild() - runs("misc") { + runs("management") { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - val type: AutoProxyMode? = when(value.interaction.command.strings["value"]) { + val type: AutoProxyMode? = when (value.interaction.command.strings["value"]) { null -> null - "off", "disable" -> AutoProxyMode.OFF - "latch", "l" -> AutoProxyMode.LATCH - "front", "f" -> AutoProxyMode.FRONT + "off" -> AutoProxyMode.OFF + "latch" -> AutoProxyMode.LATCH + "front" -> AutoProxyMode.FRONT + "on" -> AutoProxyMode.FALLBACK else -> AutoProxyMode.MEMBER } val member = if (type == AutoProxyMode.MEMBER) { @@ -243,7 +251,7 @@ object MiscCommands : CommandRegistrar { int("message", "The message ID to edit") { required = false } - runs("misc") { + runs("management") { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system, true)) return@runs false val message = value.interaction.command.integers["message"]?.toULong()?.let { Snowflake(it) } From 95fd406a576ad90272973331e5ecb8d1d842cf35 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 27 Feb 2023 12:54:11 -0600 Subject: [PATCH 110/137] Useragent + include more git properties --- gradle.properties | 2 +- .../dev/proxyfox/bot/command/MiscCommands.kt | 15 ++++++++- .../dev/proxyfox/bot/webhook/ProxyContext.kt | 3 +- modules/common/build.gradle.kts | 20 ++++++++++-- .../kotlin/dev/proxyfox/common/FoxFetch.kt | 32 +++++++++++++++---- .../main/kotlin/dev/proxyfox/common/Util.kt | 23 +++++++++++-- .../common/src/main/resources/commit_hash.txt | 1 - .../common/src/main/resources/git.properties | 3 ++ 8 files changed, 83 insertions(+), 16 deletions(-) delete mode 100644 modules/common/src/main/resources/commit_hash.txt create mode 100644 modules/common/src/main/resources/git.properties diff --git a/gradle.properties b/gradle.properties index afcbf816..87e6d36b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ kotlin.code.style=official group=dev.proxyfox -version=2.0.11 +version=2.1.0 diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index fe4609a9..258b3c8a 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -857,12 +857,19 @@ object MiscCommands : CommandRegistrar { val shardid = ctx.getGuild()?.id?.value?.toShard() ?: 0 ctx.respondEmbed { title = "ProxyFox Debug" + field { + inline = true + name = "Version" + value = version + } + val gatewayPing = kord.gateway.gateways[shardid]!!.ping.value!! field { inline = true name = "Shard ID" value = "$shardid" } + field { inline = true name = "Gateway Ping" @@ -891,7 +898,7 @@ object MiscCommands : CommandRegistrar { field { inline = true - name = "Database Implementation" + name = "Database" value = database.getDatabaseName() } @@ -901,6 +908,12 @@ object MiscCommands : CommandRegistrar { value = hash } + field { + inline = true + name = "Git Branch" + value = branch + } + field { inline = true name = "JVM Version" diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt index b9d7896b..bc3c823a 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt @@ -19,6 +19,7 @@ import dev.proxyfox.bot.http import dev.proxyfox.bot.kord import dev.proxyfox.bot.markdownParser import dev.proxyfox.common.ellipsis +import dev.proxyfox.common.useragent import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord @@ -89,7 +90,7 @@ data class ProxyContext( headers { append( HttpHeaders.UserAgent, - "ProxyFox/2.1 (+https://github.com/The-ProxyFox-Group/ProxyFox/; +https://proxyfox.dev/)" + useragent ) } } diff --git a/modules/common/build.gradle.kts b/modules/common/build.gradle.kts index 938e56e2..677004b9 100644 --- a/modules/common/build.gradle.kts +++ b/modules/common/build.gradle.kts @@ -20,9 +20,16 @@ dependencies { tasks.withType { val hash = getCommitHash() + val branch = getBranch() inputs.property("hash", hash) - filesMatching("commit_hash.txt") { - expand("hash" to hash) + inputs.property("branch", branch) + inputs.property("version", rootProject.version) + filesMatching("git.properties") { + expand( + "hash" to hash, + "branch" to branch, + "version" to rootProject.version + ) } } @@ -34,4 +41,13 @@ fun getCommitHash(): String { } val str = stdout.toString(Charset.defaultCharset()) return str.substring(1, str.length - 1) +} + +fun getBranch(): String { + val stdout = ByteArrayOutputStream() + exec { + commandLine("git", "rev-parse", "--abbrev-ref", "HEAD") + standardOutput = stdout + } + return stdout.toString(Charset.defaultCharset()) } \ No newline at end of file diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt index f90289f0..1a035728 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/FoxFetch.kt @@ -8,17 +8,35 @@ package dev.proxyfox.common -import kotlinx.coroutines.* +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import java.net.* object FoxFetch { private val baseUrl = "https://api.tinyfox.dev" - private val url = URL("https://api.tinyfox.dev/img?animal=fox&json") + private val requestUrl = "https://api.tinyfox.dev/img?animal=fox&json" + private val json = Json { + ignoreUnknownKeys = true + isLenient = true + } + private val client = HttpClient { + install(UserAgent) { + agent = useragent + } + install(ContentNegotiation) { + json(json) + } + } - suspend fun fetch() = withContext(Dispatchers.IO) { - baseUrl + Json.parseToJsonElement(url.readText()).jsonObject["loc"]!!.jsonPrimitive.content + suspend fun fetch() = withContext(Dispatchers.IO) { + baseUrl + client.get(requestUrl).body().loc } + + data class Response(val loc: String) } diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt index 1b98139b..9a4ec2c4 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt @@ -13,8 +13,10 @@ import dev.kord.core.event.Event import dev.kord.core.on import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.io.StringReader import java.lang.management.* import java.nio.charset.Charset +import java.util.* import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -74,10 +76,25 @@ suspend inline fun T.applyAsync(block: suspend T.() -> Unit): T { return this } -//We just need a classloader to get a resource -val hash = object {}.javaClass.getResource("/commit_hash.txt")?.readText(Charset.defaultCharset()) ?: "Unknown Hash" +fun getGit(): Properties { + val input = logger.javaClass.getResource("/git.properties")?.readText(Charset.defaultCharset())!! + val properties = Properties() + properties.load(StringReader(input)) + return properties +} + +val gitProperties = getGit() + +val hash = gitProperties["hash"] as String + +val branch = gitProperties["branch"] as String + +val version = gitProperties["version"] as String + +val useragent = + "ProxyFox/$version@$branch#$hash (+https://github.com/The-ProxyFox-Group/ProxyFox/; +https://proxyfox.dev/)" -class DebugException: Exception("Debug Exception - Do Not Report") +class DebugException : Exception("Debug Exception - Do Not Report") val threadMXBean = ManagementFactory.getThreadMXBean() diff --git a/modules/common/src/main/resources/commit_hash.txt b/modules/common/src/main/resources/commit_hash.txt deleted file mode 100644 index ce8eab44..00000000 --- a/modules/common/src/main/resources/commit_hash.txt +++ /dev/null @@ -1 +0,0 @@ -${hash} \ No newline at end of file diff --git a/modules/common/src/main/resources/git.properties b/modules/common/src/main/resources/git.properties new file mode 100644 index 00000000..9ac77a9e --- /dev/null +++ b/modules/common/src/main/resources/git.properties @@ -0,0 +1,3 @@ +hash=${hash} +branch=${branch} +version=${version} \ No newline at end of file From ac2c662c833eed5ecc8d359e57a93e38e498fe92 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 27 Feb 2023 12:56:33 -0600 Subject: [PATCH 111/137] Rearrange debug embed --- .../dev/proxyfox/bot/command/MiscCommands.kt | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 258b3c8a..f4be3468 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -863,31 +863,29 @@ object MiscCommands : CommandRegistrar { value = version } - val gatewayPing = kord.gateway.gateways[shardid]!!.ping.value!! field { inline = true - name = "Shard ID" - value = "$shardid" + name = "Git Branch" + value = branch } field { inline = true - name = "Gateway Ping" - value = "$gatewayPing" + name = "Commit Hash" + value = hash } - val databasePing = database.ping() + val gatewayPing = kord.gateway.gateways[shardid]!!.ping.value!! field { inline = true - name = "Database Ping" - value = "$databasePing" + name = "Shard ID" + value = "$shardid" } - val totalPing = gatewayPing + databasePing field { inline = true - name = "Total Ping" - value = "$totalPing" + name = "Gateway Ping" + value = "$gatewayPing" } field { @@ -896,22 +894,24 @@ object MiscCommands : CommandRegistrar { value = "${(Clock.System.now() - startTime).inWholeHours} hours" } + val databasePing = database.ping() field { inline = true - name = "Database" - value = database.getDatabaseName() + name = "Database Ping" + value = "$databasePing" } + val totalPing = gatewayPing + databasePing field { inline = true - name = "Commit Hash" - value = hash + name = "Total Ping" + value = "$totalPing" } field { inline = true - name = "Git Branch" - value = branch + name = "Database" + value = database.getDatabaseName() } field { From cd462c5d9420a74eae31b4657981a749b37913e9 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 27 Feb 2023 12:58:49 -0600 Subject: [PATCH 112/137] Move uptime to before shard id --- .../kotlin/dev/proxyfox/bot/command/MiscCommands.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index f4be3468..28dc84db 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -875,6 +875,12 @@ object MiscCommands : CommandRegistrar { value = hash } + field { + inline = true + name = "Uptime" + value = "${(Clock.System.now() - startTime).inWholeHours} hours" + } + val gatewayPing = kord.gateway.gateways[shardid]!!.ping.value!! field { inline = true @@ -888,12 +894,6 @@ object MiscCommands : CommandRegistrar { value = "$gatewayPing" } - field { - inline = true - name = "Uptime" - value = "${(Clock.System.now() - startTime).inWholeHours} hours" - } - val databasePing = database.ping() field { inline = true From 7fac04b8e56da294dcf2b8e1d6415c506c00de51 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 27 Feb 2023 13:02:18 -0600 Subject: [PATCH 113/137] Tell people to report exceptions --- modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 0ecc0cc6..41d822bb 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -282,7 +282,7 @@ suspend fun handleError(err: Throwable, channel: MessageChannelBehavior) { cause += " Suppressed: ${suppressed.javaClass.name}: $supReason\n$supCause" } channel.createMessage( - "An unexpected error occurred.\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" + "An unexpected error occurred. Report this to us at https://discord.gg/q3yF8ay9V7\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" ) if (err is DebugException) return if (errorChannel == null && errorChannelId != null) From fc42f6172012bc86adc811e85f7e3ef4cab34d73 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 27 Feb 2023 13:58:09 -0600 Subject: [PATCH 114/137] Misc fixes related to application commands --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 23 +++++----- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 3 +- .../dev/proxyfox/bot/command/MiscCommands.kt | 15 ++++--- ...ProxyFoxMessageCommandCreateBuilderImpl.kt | 43 +++++++++++++++++++ 4 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxMessageCommandCreateBuilderImpl.kt diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 41d822bb..b9578121 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -39,6 +39,7 @@ import dev.kord.rest.json.request.ApplicationCommandCreateRequest import dev.kord.rest.request.KtorRequestException import dev.proxyfox.bot.command.* import dev.proxyfox.bot.command.interaction.ProxyFoxChatInputCreateBuilderImpl +import dev.proxyfox.bot.command.interaction.ProxyFoxMessageCommandCreateBuilderImpl import dev.proxyfox.common.* import dev.proxyfox.database.database import dev.proxyfox.database.records.member.MemberRecord @@ -195,10 +196,14 @@ suspend fun login() { suspend fun Kord.registerCommands() { printStep("Registering commands", 2) - createGlobalMessageCommand("Delete Message") - createGlobalMessageCommand("Fetch Message Info") - createGlobalMessageCommand("Ping Message Author") - createGlobalMessageCommand("Edit Message") + deferredCommands.addAll( + listOf( + ProxyFoxMessageCommandCreateBuilderImpl("Delete Message").toRequest(), + ProxyFoxMessageCommandCreateBuilderImpl("Fetch Message Info").toRequest(), + ProxyFoxMessageCommandCreateBuilderImpl("Ping Message Author").toRequest(), + ProxyFoxMessageCommandCreateBuilderImpl("Edit Message").toRequest() + ) + ) Commands { +SystemCommands +MemberCommands @@ -207,12 +212,10 @@ suspend fun Kord.registerCommands() { +MiscCommands } - scope.launch { - rest.interaction.createGlobalApplicationCommands( - resources.applicationId, - deferredCommands - ) - } + rest.interaction.createGlobalApplicationCommands( + resources.applicationId, + deferredCommands + ) } val deferredCommands = arrayListOf() diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index 0ddf8a2c..b23e1a1b 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -471,7 +471,8 @@ suspend fun ChatInputCommandInteractionCreateEvent.onInteract() { when (command.rootName) { "info" -> MiscCommands.infoInteractionExecutors "moderation" -> MiscCommands.moderationInteractionExecutors - "misc" -> MiscCommands.miscInteractionExecutors + "management" -> MiscCommands.managementInteractionExecutors + "pluralkit" -> MiscCommands.pluralkitInteractionExecutors else -> return }[command.name]?.let { it(InteractionCommandContext(this)) } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 28dc84db..93735d8d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -52,13 +52,15 @@ object MiscCommands : CommandRegistrar { private val roleMatcher = Regex("\\d+") var infoInteractionExecutors: HashMap Boolean> = hashMapOf() var moderationInteractionExecutors: HashMap Boolean> = hashMapOf() - var miscInteractionExecutors: HashMap Boolean> = hashMapOf() + var managementInteractionExecutors: HashMap Boolean> = hashMapOf() + var pluralkitInteractionExecutors: HashMap Boolean> = hashMapOf() fun SubCommandBuilder.runs(rootName: String, action: suspend InteractionCommandContext.() -> Boolean) { when (rootName) { "info" -> infoInteractionExecutors "moderation" -> moderationInteractionExecutors - "misc" -> miscInteractionExecutors + "management" -> managementInteractionExecutors + "pluralkit" -> pluralkitInteractionExecutors else -> return }[name] = action } @@ -159,9 +161,9 @@ object MiscCommands : CommandRegistrar { } } subCommand("import-url", "Import a system") { - name("file") + name("import") runs("management") { - import(this, value.interaction.command.strings["file"]!!) + import(this, value.interaction.command.strings["import"]!!) } } subCommand("export", "Export your system") { @@ -938,13 +940,14 @@ object MiscCommands : CommandRegistrar { private suspend fun import(ctx: DiscordContext, url: String?): Boolean { val uri = url.uri() + + ctx.deferResponse() + uri.invalidUrlMessage("import", exampleExport)?.let { ctx.respondFailure(it) return false } - ctx.deferResponse() - return try { val importer = withContext(Dispatchers.IO) { uri!!.toURL().openStream().reader().use { import(it, ctx.getUser()) } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxMessageCommandCreateBuilderImpl.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxMessageCommandCreateBuilderImpl.kt new file mode 100644 index 00000000..c4eb04d6 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxMessageCommandCreateBuilderImpl.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.interaction + +import dev.kord.common.Locale +import dev.kord.common.entity.ApplicationCommandType +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.optional.delegate.delegate +import dev.kord.rest.builder.interaction.GlobalMessageCommandCreateBuilder +import dev.kord.rest.json.request.ApplicationCommandCreateRequest + +class ProxyFoxMessageCommandCreateBuilderImpl(override var name: String) : GlobalMessageCommandCreateBuilder { + override val type: ApplicationCommandType + get() = ApplicationCommandType.Message + + + private val state = ProxyFoxApplicationCommandModifyStateHolder() + + override var nameLocalizations: MutableMap? by state::nameLocalizations.delegate() + + override var defaultMemberPermissions: Permissions? by state::defaultMemberPermissions.delegate() + override var dmPermission: Boolean? by state::dmPermission.delegate() + + @Deprecated("'defaultPermission' is deprecated in favor of 'defaultMemberPermissions' and 'dmPermission'. Setting 'defaultPermission' to false can be replaced by setting 'defaultMemberPermissions' to empty Permissions and 'dmPermission' to false ('dmPermission' is only available for global commands).") + override var defaultPermission: Boolean? by @Suppress("DEPRECATION") state::defaultPermission.delegate() + + override fun toRequest(): ApplicationCommandCreateRequest { + return ApplicationCommandCreateRequest( + name = name, + nameLocalizations = state.nameLocalizations, + type = type, + dmPermission = state.dmPermission, + defaultMemberPermissions = state.defaultMemberPermissions, + defaultPermission = @Suppress("DEPRECATION") state.defaultPermission, + ) + } +} \ No newline at end of file From 8f3ad693b20a5960f1cd0a1de2495b802ff099b4 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 27 Feb 2023 14:14:30 -0600 Subject: [PATCH 115/137] Fix import clashes by searching by member name before member id --- .../dev/proxyfox/database/etc/importer/PluralKitImporter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt index 381bca84..864e2026 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/etc/importer/PluralKitImporter.kt @@ -129,8 +129,8 @@ open class PluralKitImporter protected constructor( val memberName = pkMember.name.sanitise().ifEmptyThen(pkMember.id) ?: findNextId(allocatedIds) val member = run { if (!fresh) { - val record = pkMember.id?.let { database.fetchMemberFromSystem(system.id, it) } - ?: database.fetchMemberFromSystemAndName(system.id, memberName, caseSensitive = true) + val record = database.fetchMemberFromSystemAndName(system.id, memberName, caseSensitive = true) + ?: pkMember.id?.let { database.fetchMemberFromSystem(system.id, it) } if (record != null && seenMemberIds.add(record.id)) { assert(record.name == memberName) { "$record did not match $pkMember" } freshMember = false From c902672644bf04478b27b29808e8ba5362e0d254 Mon Sep 17 00:00:00 2001 From: Octal Date: Sun, 30 Apr 2023 23:37:39 -0500 Subject: [PATCH 116/137] Bump kord version, support voice messages, keep track of deleted messages --- gradle/libs.versions.toml | 2 +- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 22 +++++++--- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 21 ++++++++-- .../dev/proxyfox/bot/command/Commands.kt | 2 +- .../dev/proxyfox/bot/command/MiscCommands.kt | 2 +- .../command/context/DiscordMessageContext.kt | 2 +- .../ProxyFoxChatInputCreateBuilderImpl.kt | 3 +- ...ProxyFoxMessageCommandCreateBuilderImpl.kt | 2 + .../kotlin/dev/proxyfox/bot/prompts/Pager.kt | 11 +++-- .../dev/proxyfox/bot/webhook/GuildMessage.kt | 3 ++ .../dev/proxyfox/bot/webhook/ProxyContext.kt | 41 ++++++++++--------- .../kotlin/dev/proxyfox/database/Database.kt | 1 + .../dev/proxyfox/database/InMemoryDatabase.kt | 6 +++ .../dev/proxyfox/database/MongoDatabase.kt | 13 +++++- .../dev/proxyfox/database/NopDatabase.kt | 12 +++++- .../dev/proxyfox/database/ProxyDatabase.kt | 4 ++ 16 files changed, 107 insertions(+), 40 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7d0a974a..d54e4c0e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] guava = "31.1-jre" logback = "1.2.11" -kord = "0.8.0-M17" +kord = "0.9.x-SNAPSHOT" kotlin = "1.7.21" kotlinx_coroutines = "1.6.4" diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index b9578121..1e8b4cfc 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -10,7 +10,9 @@ package dev.proxyfox.bot import dev.kord.common.Color import dev.kord.common.EmptyBitSet -import dev.kord.common.entity.* +import dev.kord.common.entity.Permission +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.channel.ChannelBehavior @@ -25,8 +27,12 @@ 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.event.gateway.ReadyEvent -import dev.kord.core.event.interaction.* +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.ModalSubmitInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent +import dev.kord.core.event.message.MessageDeleteEvent import dev.kord.core.event.message.MessageUpdateEvent import dev.kord.core.event.message.ReactionAddEvent import dev.kord.core.on @@ -50,13 +56,15 @@ import io.ktor.client.engine.cio.* import io.ktor.client.request.forms.* import io.ktor.http.* import io.ktor.utils.io.jvm.javaio.* -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.count import kotlinx.coroutines.flow.fold +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock import kotlinx.datetime.Instant -import java.lang.Integer.* -import java.util.* +import java.lang.Integer.min import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit @@ -132,6 +140,10 @@ suspend fun login() { } } + kord.on { + onMessageDelete() + } + kord.on { try { onMessageUpdate() diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index b23e1a1b..777d26c0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -25,6 +25,7 @@ import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent import dev.kord.core.event.interaction.ModalSubmitInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent +import dev.kord.core.event.message.MessageDeleteEvent import dev.kord.core.event.message.MessageUpdateEvent import dev.kord.core.event.message.ReactionAddEvent import dev.kord.rest.builder.component.ActionRowBuilder @@ -81,10 +82,17 @@ suspend fun MessageCreateEvent.onMessageCreate() { val guild = guildChannel.getGuild() val hasStickers = message.stickers.isNotEmpty() // TODO: Boost to upload limit; 8 MiB is default. - val hasOversizedFiles = message.attachments.fold(0L) { size, attachment -> size + attachment.size } >= UPLOAD_LIMIT + val hasOversizedFiles = + message.attachments.fold(0L) { size, attachment -> size + attachment.size } >= UPLOAD_LIMIT val isOversizedMessage = content.length > 2000 if (hasStickers || hasOversizedFiles || isOversizedMessage) { - logger.trace("Denying proxying {} ({}) in {} ({}) due to Discord bot constraints", user.tag, user.id, guild.name, guild.id) + logger.trace( + "Denying proxying {} ({}) in {} ({}) due to Discord bot constraints", + user.tag, + user.id, + guild.name, + guild.id + ) return } @@ -92,6 +100,10 @@ suspend fun MessageCreateEvent.onMessageCreate() { } } +suspend fun MessageDeleteEvent.onMessageDelete() { + database.dropMessage(messageId) +} + suspend fun MessageUpdateEvent.onMessageUpdate() { val guild = kord.getGuildOrNull(new.guildId.value ?: return) ?: return val content = new.content.value ?: return @@ -122,6 +134,7 @@ suspend fun MessageUpdateEvent.onMessageUpdate() { new.embeds.value?.map { Embed(EmbedData.from(it), kord) } ?: emptyList(), new.messageReference.value?.id?.value?.let { channel.getMessage(it) }, message, + message.asMessage().flags ) handleProxying( @@ -262,7 +275,7 @@ suspend fun ReactionAddEvent.onReactionAdd() { val member = database.fetchMemberFromSystem(databaseMessage.systemId, databaseMessage.memberId) ?: return - val guild = getGuild() + val guild = getGuildOrNull() val settings = database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) val user = kord.getUser(Snowflake(databaseMessage.userId)) @@ -397,7 +410,7 @@ suspend fun MessageCommandInteractionCreateEvent.onInteract() { settings?.nickname?.let { field { name = "Server Name" - value = "> $it\n*For ${guild?.name}*" + value = "> $it\n*For ${guild.name}*" inline = true } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt index 6f3d57a0..f2150099 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/Commands.kt @@ -42,7 +42,7 @@ object Commands { } fun SubCommandBuilder.guild() { - int("server-id", "The ID for the server") { + integer("server-id", "The ID for the server") { required = false } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 93735d8d..21308ac2 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -250,7 +250,7 @@ object MiscCommands : CommandRegistrar { string("content", "The content to replace with") { required = true } - int("message", "The message ID to edit") { + integer("message", "The message ID to edit") { required = false } runs("management") { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index 3faabf32..abd81068 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -43,7 +43,7 @@ class DiscordMessageContext(message: Message, override val command: String): Dis } override suspend fun getMember(): Member? { - return value.getAuthorAsMember() + return value.getAuthorAsMemberOrNull() } override suspend fun respondEmbed( diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt index 9b805df2..5b39d00c 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxChatInputCreateBuilderImpl.kt @@ -23,8 +23,9 @@ import dev.kord.rest.json.request.ApplicationCommandCreateRequest * */ class ProxyFoxChatInputCreateBuilderImpl( override var name: String, - override var description: String, + override var description: String ) : GlobalChatInputCreateBuilder { + override var nsfw: Boolean? = false private val state = ProxyFoxApplicationCommandModifyStateHolder() override var nameLocalizations: MutableMap? by state::nameLocalizations.delegate() diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxMessageCommandCreateBuilderImpl.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxMessageCommandCreateBuilderImpl.kt index c4eb04d6..e790ef16 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxMessageCommandCreateBuilderImpl.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/interaction/ProxyFoxMessageCommandCreateBuilderImpl.kt @@ -19,6 +19,8 @@ class ProxyFoxMessageCommandCreateBuilderImpl(override var name: String) : Globa override val type: ApplicationCommandType get() = ApplicationCommandType.Message + override var nsfw: Boolean? = false + private val state = ProxyFoxApplicationCommandModifyStateHolder() diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt index 640b95fb..e60ca865 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt @@ -30,6 +30,7 @@ import dev.kord.core.event.message.ReactionAddEvent import dev.kord.core.on import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.component.SelectOptionBuilder +import dev.kord.rest.builder.component.options import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed import dev.kord.rest.builder.message.modify.embed @@ -84,10 +85,11 @@ class Pager( selector = EphemeralSelector(this@Pager, interaction.respondEphemeral { content = "Which page would you like to go to?" components += ActionRowBuilder().apply { - selectMenu(uuid) { + stringSelect(uuid) { val opts = pages / 25 options += if (opts != 0) { - (0..24).asSequence().map { (it * opts).toString() }.map { SelectOptionBuilder(it, it) } + (0..24).asSequence().map { (it * opts).toString() } + .map { SelectOptionBuilder(it, it) } } else { (1..pages).asSequence().map(Int::toString).map { SelectOptionBuilder(it, it) } } @@ -119,10 +121,11 @@ class Pager( selector = LegacySelector(this@Pager, reference.reply { content = "Which page would you like to go to?" components += ActionRowBuilder().apply { - selectMenu("menu") { + stringSelect("menu") { val opts = pages / 25 options += if (opts != 0) { - (0..24).asSequence().map { (it * opts + 1).toString() }.map { SelectOptionBuilder(it, it) } + (0..24).asSequence().map { (it * opts + 1).toString() } + .map { SelectOptionBuilder(it, it) } } else { (1..pages).asSequence().map(Int::toString).map { SelectOptionBuilder(it, it) } } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/GuildMessage.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/GuildMessage.kt index 3ca367db..a906cb98 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/GuildMessage.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/GuildMessage.kt @@ -8,6 +8,7 @@ package dev.proxyfox.bot.webhook +import dev.kord.common.entity.MessageFlags import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior @@ -30,6 +31,7 @@ data class GuildMessage( val embeds: Collection, val referencedMessage: Message?, val rawBehaviour: MessageBehavior, + val flags: MessageFlags? ) { constructor( message: Message, @@ -46,5 +48,6 @@ data class GuildMessage( embeds = message.embeds, referencedMessage = message.referencedMessage, rawBehaviour = message, + flags = message.flags ) } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt index dbb8ed7e..f28e350d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/webhook/ProxyContext.kt @@ -9,10 +9,11 @@ package dev.proxyfox.bot.webhook import dev.kord.common.Color +import dev.kord.common.entity.MessageFlag +import dev.kord.common.entity.MessageFlags import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior import dev.kord.core.entity.User -import dev.kord.rest.NamedFile import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException import dev.proxyfox.bot.http @@ -31,7 +32,6 @@ import io.ktor.client.request.forms.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.util.* -import io.ktor.utils.io.jvm.javaio.* import kotlinx.coroutines.delay /** @@ -85,6 +85,11 @@ data class ProxyContext( if (messageContent.isNotBlank()) content = messageContent username = buildAndSanitiseName() avatarUrl = resolvedAvatar + + if (message.flags?.contains(MessageFlag.IsVoiceMessage) == true) { + flags = MessageFlags(MessageFlag.IsVoiceMessage) + } + for (attachment in message.attachments) { val response: HttpResponse = http.get(urlString = attachment.url) { headers { @@ -94,35 +99,33 @@ data class ProxyContext( ) } } - files.add( - NamedFile( - attachment.filename, - ChannelProvider { response.content.toInputStream().toByteReadChannel() } - ) + addFile( + attachment.filename, + ChannelProvider { response.content } ) } if (reproxy) { - message.embeds.forEach { - if (it.author?.name?.endsWith(" ↩️") == true) { - embed { - color = Color(member.color) - author { - name = it.author?.name - icon = it.author?.iconUrl + message.embeds.forEach { + if (it.author?.name?.endsWith(" ↩️") == true) { + embed { + color = Color(member.color) + author { + name = it.author?.name + icon = it.author?.iconUrl + } + description = it.description } - description = it.description } } - } - } else message.referencedMessage?.let { ref -> + } else message.referencedMessage?.let { ref -> // Kord's official methods don't return a user if it's a webhook val user = User(ref.data.author, kord) val link = "https://discord.com/channels/${ref.getGuild().id}/${ref.channelId}/${ref.id}" embed { color = Color(member.color) author { - name = (ref.getAuthorAsMember()?.displayName ?: user.username) + " ↩️" - icon = user.avatar?.url ?: user.defaultAvatar.url + name = (ref.getAuthorAsMemberOrNull()?.displayName ?: user.username) + " ↩️" + icon = (user.avatar?.cdnUrl ?: user.defaultAvatar.cdnUrl).toUrl() url = link } var msgRef = markdownParser.parse(ref.content) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt index 36344201..9a253259 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt @@ -317,6 +317,7 @@ abstract class Database : AutoCloseable { open suspend fun createMessage(message: ProxiedMessageRecord) = updateMessage(message) abstract suspend fun fetchMessage(messageId: Snowflake): ProxiedMessageRecord? abstract suspend fun fetchLatestMessage(systemId: String, channelId: Snowflake): ProxiedMessageRecord? + abstract suspend fun dropMessage(messageId: Snowflake) open suspend fun createToken(systemId: String, type: TokenType): TokenRecord { val token = TokenRecord(generateUniqueToken(), systemId, type) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index 2d7f79e1..779861ad 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -256,6 +256,12 @@ class InMemoryDatabase : Database() { return messages.findLast { it.systemId == systemId && it.channelId == channelId.value } } + override suspend fun dropMessage(messageId: Snowflake) { + messages.removeIf { + it.newMessageId == messageId.value + } + } + override suspend fun fetchToken(token: String): TokenRecord? { if (!systemTokens.containsKey(token)) return null return systemTokens[token] diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index ce3aa8b4..e82a55d8 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -21,7 +21,10 @@ import dev.proxyfox.database.records.member.MemberProxyTagRecord import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.member.MemberServerSettingsRecord import dev.proxyfox.database.records.misc.* -import dev.proxyfox.database.records.system.* +import dev.proxyfox.database.records.system.SystemChannelSettingsRecord +import dev.proxyfox.database.records.system.SystemRecord +import dev.proxyfox.database.records.system.SystemServerSettingsRecord +import dev.proxyfox.database.records.system.SystemSwitchRecord import kotlinx.coroutines.reactive.awaitFirst import kotlinx.coroutines.reactive.awaitFirstOrElse import kotlinx.coroutines.reactive.awaitFirstOrNull @@ -277,6 +280,10 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { messages.find("systemId" eq systemId, "channelId" eq channelId).sort("{'creationDate':-1}").limit(1) .awaitFirstOrNull() + override suspend fun dropMessage(messageId: Snowflake) { + messages.deleteOne("oldMessageId" eq messageId.value) + } + override suspend fun fetchToken(token: String): TokenRecord? = systemTokens.findFirstOrNull("token" eq token) @@ -509,6 +516,10 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { if (witness.add(user)) userQueue += user.create() } + override suspend fun dropMessage(messageId: Snowflake) { + TODO("Not yet implemented") + } + override suspend fun createMessage(message: ProxiedMessageRecord) { if (witness.add(message)) proxiedMessageQueue += message.create() } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt index ffff89ab..647d43e1 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt @@ -98,7 +98,8 @@ class NopDatabase : Database() { memberId: String, systemId: String, memberName: String - ) {} + ) { + } override suspend fun updateMessage(message: ProxiedMessageRecord) {} @@ -109,13 +110,20 @@ class NopDatabase : Database() { channelId: Snowflake ): ProxiedMessageRecord? = null + override suspend fun dropMessage(messageId: Snowflake) {} + override suspend fun fetchToken(token: String): TokenRecord? = null override suspend fun updateToken(token: TokenRecord) = fail("Cannot store token for ${token.systemId}.") override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean = false - override suspend fun createSwitch(systemId: String, memberId: List, timestamp: Instant?): SystemSwitchRecord? = null + override suspend fun createSwitch( + systemId: String, + memberId: List, + timestamp: Instant? + ): SystemSwitchRecord? = null + override suspend fun dropSwitch(switch: SystemSwitchRecord) {} override suspend fun updateSwitch(switch: SystemSwitchRecord) {} diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt index bad60da2..30bb28d9 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt @@ -150,6 +150,10 @@ open class ProxyDatabase(protected val proxy: T) : Database() { return proxy.fetchLatestMessage(systemId, channelId) } + override suspend fun dropMessage(messageId: Snowflake) { + proxy.dropMessage(messageId) + } + override suspend fun fetchToken(token: String): TokenRecord? { return proxy.fetchToken(token) } From a4ec81589f16777b184b2518cbfdabacac1a5f25 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 1 May 2023 16:12:54 -0500 Subject: [PATCH 117/137] Clean up duplicated code in DiscordContext --- .../proxyfox/bot/command/context/DiscordContext.kt | 10 +++++++--- .../bot/command/context/DiscordMessageContext.kt | 13 ------------- .../command/context/InteractionCommandContext.kt | 12 ------------ 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index 52382ba2..ff367f83 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -37,15 +37,19 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes abstract class DiscordContext(override val value: T) : CommandContext() { + override suspend fun respondFailure(text: String, private: Boolean): T = respondPlain("❌ $text", private) + override suspend fun respondSuccess(text: String, private: Boolean): T = respondPlain("✅️ $text", private) + override suspend fun respondWarning(text: String, private: Boolean): T = respondPlain("⚠️ $text", private) + abstract fun getAttachment(): Attachment? abstract suspend fun getChannel(private: Boolean = false): MessageChannelBehavior abstract suspend fun getGuild(): Guild? abstract suspend fun getUser(): User? abstract suspend fun getMember(): Member? abstract suspend fun respondEmbed( - private: Boolean = false, - text: String? = null, - embed: suspend EmbedBuilder.() -> Unit + private: Boolean = false, + text: String? = null, + embed: suspend EmbedBuilder.() -> Unit ): T abstract suspend fun deferResponse(private: Boolean = false) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index abd81068..455d1a88 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -94,17 +94,4 @@ class DiscordMessageContext(message: Message, override val command: String): Dis override suspend fun respondPlain(text: String, private: Boolean): Message { return getChannel(private).createMessage(text) } - - override suspend fun respondSuccess(text: String, private: Boolean): Message { - return getChannel(private).createMessage("✅ $text") - } - - override suspend fun respondWarning(text: String, private: Boolean): Message { - return getChannel(private).createMessage("⚠️ $text") - } - - override suspend fun respondFailure(text: String, private: Boolean): Message { - return getChannel(private).createMessage("❌ $text") - } - } \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index 9a3b3749..9dea25b9 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -38,10 +38,6 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : return value.interaction.command.attachments.values.stream().findFirst().getOrNull() } - override suspend fun respondFailure(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { - return respondPlain("❌️ $text", private) - } - override suspend fun respondPlain(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { if (deferred != null) deferred!!.respond { @@ -57,14 +53,6 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : return value } - override suspend fun respondSuccess(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { - return respondPlain("✅️ $text", private) - } - - override suspend fun respondWarning(text: String, private: Boolean): ChatInputCommandInteractionCreateEvent { - return respondPlain("⚠️ $text", private) - } - override suspend fun getChannel(private: Boolean): MessageChannelBehavior { return if (private) value.interaction.user.getDmChannelOrNull() From 17434afc9743001a041dd55facc3175a4eab9f31 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 1 May 2023 17:00:09 -0500 Subject: [PATCH 118/137] Initial pluralkit sync implementation --- .../main/kotlin/dev/proxyfox/bot/Emojis.kt | 2 +- .../dev/proxyfox/bot/command/MiscCommands.kt | 81 +++++++++++++--- .../main/kotlin/dev/proxyfox/sync/PkSync.kt | 97 +++++++++++++++++-- 3 files changed, 158 insertions(+), 22 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt index c85e3378..efbe0551 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt @@ -17,7 +17,7 @@ object Emojis { val check = "✅".partial val multiply = "✖".partial val wastebasket = "🗑".partial - val move = "\uD83D\uDD00".partial + val move = "🔀".partial val ReactionEmoji.Unicode.partial get() = name.partial diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 21308ac2..c44ec556 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -34,6 +34,7 @@ import dev.proxyfox.database.records.misc.ServerSettingsRecord import dev.proxyfox.database.records.misc.TrustLevel import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord +import dev.proxyfox.sync.PkSync import io.ktor.client.request.forms.* import io.ktor.utils.io.jvm.javaio.* import kotlinx.coroutines.Dispatchers @@ -262,8 +263,34 @@ object MiscCommands : CommandRegistrar { } } deferChatInputCommand("pluralkit", "Commands for PluralKit integration with ProxyFox") { - subCommand("token", "Store you PluralKit token!") { - + subCommand("set-token", "Store your PluralKit token") { + name("token", required = true) + runs("pluralkit") { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + pkToken(this, system, value.interaction.command.strings["token"]!!, false) + } + } + subCommand("clear-token", "Removes your PluralKit token") { + runs("pluralkit") { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + pkToken(this, system, null, true) + } + } + subCommand("import", "Imports your system from PluralKit's API") { + runs("pluralkit") { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + syncPk(this, system, false) + } + } + subCommand("export", "Exports your system to PluralKit's API") { + runs("pluralkit") { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + syncPk(this, system, true) + } } } } @@ -723,11 +750,11 @@ object MiscCommands : CommandRegistrar { } } literal("pluralkit", "pk") { - literal("pull", "get", "download") { + literal("pull", "get", "download", "import") { } - literal("push", "set", "upload") { + literal("push", "set", "upload", "export") { } @@ -735,14 +762,14 @@ object MiscCommands : CommandRegistrar { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - token(this, system, null, false) + pkToken(this, system, null, false) } literal("clear", "reset", "remove") { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - token(this, system, null, true) + pkToken(this, system, null, true) } } @@ -750,7 +777,7 @@ object MiscCommands : CommandRegistrar { runs { val system = database.fetchSystemFromUser(getUser()) if (!checkSystem(this, system)) return@runs false - token(this, system, getToken(), false) + pkToken(this, system, getToken(), false) } } } @@ -758,11 +785,41 @@ object MiscCommands : CommandRegistrar { } @OptIn(DontExpose::class) - private suspend fun token( - ctx: DiscordContext, - system: SystemRecord, - token: String?, - clear: Boolean + private suspend fun syncPk(ctx: DiscordContext, system: SystemRecord, upload: Boolean): Boolean { + val res = if (upload) { + PkSync.push(system) + } else { + PkSync.pull(system) + } + + if (res.getA() == false) { + ctx.respondFailure("You don't have a PluralKit token registered.") + return false + } + + if (res.getB() != null) { + val b = res.getB()!! + val message = b.message.replace(system.pkToken!!, "[pktoken]") + ctx.respondFailure(""" + PluralKit returned an error: + `$message` + """.trimIndent()) + return false + } + + val message = if (upload) "exported" to "to" else "imported" to "from" + + ctx.respondSuccess("Successfully ${message.first} system data ${message.second} PluralKit") + + return true + } + + @OptIn(DontExpose::class) + private suspend fun pkToken( + ctx: DiscordContext, + system: SystemRecord, + token: String?, + clear: Boolean ): Boolean { if (clear) { if (system.pkToken == null) { diff --git a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt index f56dc3d9..661e242e 100644 --- a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt +++ b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt @@ -15,19 +15,36 @@ import dev.proxyfox.database.database import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.pluralkt.PluralKt import dev.proxyfox.pluralkt.Response +import dev.proxyfox.pluralkt.types.PkColor import dev.proxyfox.pluralkt.types.PkError import dev.proxyfox.pluralkt.types.PkMember +import dev.proxyfox.pluralkt.types.PkProxyTag +@OptIn(DontExpose::class) object PkSync { + sealed interface Either { + fun getA(): A? + fun getB(): B? + + class EitherA(private val a: A) : Either { + override fun getA() = a + override fun getB() = null + } + + class EitherB(private val b: B) : Either { + override fun getA() = null + override fun getB() = b + } + } + private fun Response.getSuccessOrNull() = if (isSuccess()) getSuccess() else null - @OptIn(DontExpose::class) - suspend fun pull(system: SystemRecord): PkError? { - val token = system.pkToken ?: return null + suspend fun pull(system: SystemRecord): Either { + val token = system.pkToken ?: return Either.EitherA(false) val systemResp = PluralKt.System.getMe(token).await() systemResp.getException().throwIfPresent() - val pkSystem = systemResp.getSuccessOrNull() ?: return systemResp.getError() + val pkSystem = systemResp.getSuccessOrNull() ?: return Either.EitherB(systemResp.getError()) system.name = pkSystem.name ?: system.name system.description = pkSystem.description ?: system.description @@ -38,15 +55,15 @@ object PkSync { val membersResp = PluralKt.Member.getMembers(pkSystem.id, token).await() membersResp.getException().throwIfPresent() - val pkMembers = membersResp.getSuccessOrNull() ?: return membersResp.getError() + val pkMembers = membersResp.getSuccessOrNull() ?: return Either.EitherB(membersResp.getError()) val memberToIdLookup = HashBiMap.create() val idToIdLookup = HashBiMap.create() for (pkMember in pkMembers) { val member = database.fetchMemberFromSystem(system.id, pkMember.id) ?: database.getOrCreateMember( - system.id, - pkMember.name + system.id, + pkMember.name )!! memberToIdLookup[pkMember] = member.id idToIdLookup[pkMember.id] = member.id @@ -57,7 +74,6 @@ object PkSync { member.color = pkMember.color?.color ?: member.color member.description = pkMember.description member.displayName = pkMember.displayName - member.timestamp = pkMember.created ?: member.timestamp member.keepProxy = pkMember.keepProxy database.updateMember(member) @@ -78,6 +94,69 @@ object PkSync { // TODO: groups once implemented - return null + return Either.EitherA(true) + } + + + suspend fun push(system: SystemRecord): Either { + val token = system.pkToken ?: return Either.EitherA(false) + + val systemResp = PluralKt.System.getMe(token).await() + systemResp.getException().throwIfPresent() + val pkSystem = systemResp.getSuccessOrNull() ?: return Either.EitherB(systemResp.getError()) + + pkSystem.name = system.name + pkSystem.description = system.description ?: pkSystem.description + pkSystem.tag = system.tag ?: pkSystem.tag + pkSystem.color = PkColor(system.color) + pkSystem.pronouns = system.pronouns ?: pkSystem.pronouns + + val systemPushResp = PluralKt.System.updateSystem(pkSystem, token).await() + if (systemPushResp.isError()) return Either.EitherB(systemPushResp.getError()) + + val members = database.fetchMembersFromSystem(system.id)!! + + val memberToIdLookup = HashBiMap.create() + val idToIdLookup = HashBiMap.create() + + for (member in members) { + var new = false + val pkMember = PluralKt.Member.getMember(member.id).await().getSuccessOrNull() + ?: PluralKt.Member.getMember(member.name).await().getSuccessOrNull() + ?: let { + new = true + PkMember() + } + pkMember.name = member.name + pkMember.pronouns = member.pronouns ?: pkMember.pronouns + pkMember.avatarUrl = member.avatarUrl ?: pkMember.avatarUrl + pkMember.color = PkColor(member.color) + pkMember.description = member.description ?: pkMember.description + pkMember.displayName = member.displayName ?: pkMember.displayName + pkMember.keepProxy = member.keepProxy + + val proxies = database.fetchProxiesFromSystemAndMember(system.id, member.id)!! + + pkMember.proxyTags.clear() + + for (proxy in proxies) { + val pkProxy = PkProxyTag() + pkProxy.prefix = proxy.prefix + pkProxy.suffix = proxy.suffix + pkMember.proxyTags.add(pkProxy) + } + + val newMem = if (new) { + PluralKt.Member.createMember(pkMember, token) + } else { + PluralKt.Member.updateMember(pkMember.id, pkMember, token) + }.await().getSuccessOrNull() ?: continue + memberToIdLookup[newMem] = member.id + idToIdLookup[newMem.id] = member.id + } + + // TODO: groups once implemented + + return Either.EitherA(true) } } From b613d4cde01940dceeafe716444166a226f5329d Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 1 May 2023 17:06:54 -0500 Subject: [PATCH 119/137] Better message deletion handling --- .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 4 +++- .../dev/proxyfox/database/MongoDatabase.kt | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index 777d26c0..a3f2c720 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -101,7 +101,9 @@ suspend fun MessageCreateEvent.onMessageCreate() { } suspend fun MessageDeleteEvent.onMessageDelete() { - database.dropMessage(messageId) + val message = database.fetchMessage(messageId) ?: return + message.deleted = true + database.updateMessage(message) } suspend fun MessageUpdateEvent.onMessageUpdate() { diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index e82a55d8..98e897c5 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -271,14 +271,22 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { } override suspend fun fetchMessage(messageId: Snowflake): ProxiedMessageRecord? = - messages.findFirstOrNull(or("newMessageId" eq messageId, "oldMessageId" eq messageId)) + messages.findFirstOrNull(or( + "newMessageId" eq messageId, + "oldMessageId" eq messageId, + "deleted" eq false + )) override suspend fun fetchLatestMessage( - systemId: String, - channelId: Snowflake + systemId: String, + channelId: Snowflake ): ProxiedMessageRecord? = - messages.find("systemId" eq systemId, "channelId" eq channelId).sort("{'creationDate':-1}").limit(1) - .awaitFirstOrNull() + messages.find( + "systemId" eq systemId, + "channelId" eq channelId, + "deleted" eq false + ).sort("{'creationDate':-1}").limit(1) + .awaitFirstOrNull() override suspend fun dropMessage(messageId: Snowflake) { messages.deleteOne("oldMessageId" eq messageId.value) From a860cd7c5a1290390a21c7677fa871b394546bee Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 1 May 2023 17:08:43 -0500 Subject: [PATCH 120/137] Update in-memory to match semantics change --- .../src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index 779861ad..85f99a7f 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -246,14 +246,14 @@ class InMemoryDatabase : Database() { } override suspend fun fetchMessage(messageId: Snowflake): ProxiedMessageRecord? { - return messages.find { it.oldMessageId == messageId.value || it.newMessageId == messageId.value } + return messages.find { (it.oldMessageId == messageId.value || it.newMessageId == messageId.value) && !it.deleted } } override suspend fun fetchLatestMessage( systemId: String, channelId: Snowflake ): ProxiedMessageRecord? { - return messages.findLast { it.systemId == systemId && it.channelId == channelId.value } + return messages.findLast { it.systemId == systemId && it.channelId == channelId.value && !it.deleted } } override suspend fun dropMessage(messageId: Snowflake) { From a50c73c5ea8f77a21eda060b7b4cb43901c1e06b Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 1 May 2023 17:21:44 -0500 Subject: [PATCH 121/137] Refactor some API things --- .../main/kotlin/dev/proxyfox/api/ApiMain.kt | 3 +- .../kotlin/dev/proxyfox/api/Authentication.kt | 8 +++-- .../dev/proxyfox/api/models/ApiError.kt | 17 +++++++++++ .../kotlin/dev/proxyfox/api/models/Message.kt | 22 +++++++------- .../kotlin/dev/proxyfox/api/models/System.kt | 29 +++++++++---------- .../dev/proxyfox/api/routes/MemberRoutes.kt | 16 ++++++---- .../dev/proxyfox/api/routes/MessageRoutes.kt | 4 ++- .../dev/proxyfox/api/routes/SwitchRoutes.kt | 5 ++-- .../dev/proxyfox/api/routes/SystemRoutes.kt | 6 ++-- .../dev/proxyfox/api/routes/TokenRoutes.kt | 5 ++-- 10 files changed, 69 insertions(+), 46 deletions(-) create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/models/ApiError.kt diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt index 72c98a7d..2714a3ad 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt @@ -17,7 +17,6 @@ import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.routing.* object ApiMain { - private const val version = "1" fun main() = embeddedServer(Netty, port = System.getenv("PORT")?.toIntOrNull() ?: 8080) { configureRouting() configurePlugins() @@ -31,7 +30,7 @@ object ApiMain { private fun Application.configureRouting() { routing { - route("/v${version}") { + route("/v1") { systemRoutes() memberRoutes() switchRoutes() diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt index 647d25a8..c1cc3e5f 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/Authentication.kt @@ -8,6 +8,7 @@ package dev.proxyfox.api +import dev.proxyfox.api.models.ApiError import dev.proxyfox.database.database import dev.proxyfox.database.records.misc.TokenType import io.ktor.http.* @@ -20,9 +21,10 @@ import io.ktor.util.pipeline.* @Suppress("FunctionName") fun ApiPlugin(name: String, accessFunction: TokenType.() -> Boolean) = createRouteScopedPlugin(name = name) { onCall { call -> - val tokenString = call.request.headers["Authorization"] ?: return@onCall call.respond("401 Unauthorized") - val token = database.fetchToken(tokenString) ?: return@onCall call.respond("401 Unauthorized") - if (token.type.accessFunction()) return@onCall call.respond("401 Unauthorized") + val tokenString = call.request.headers["Authorization"] + ?: return@onCall call.respond(ApiError(401, "Unauthorized")) + val token = database.fetchToken(tokenString) ?: return@onCall call.respond(ApiError(401, "Unauthorized")) + if (token.type.accessFunction()) return@onCall call.respond(ApiError(401, "Unauthorized")) } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/ApiError.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/ApiError.kt new file mode 100644 index 00000000..0f4e2536 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/ApiError.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ApiError( + val code: Int, + val message: String +) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt index aac287dc..cbd92e6e 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt @@ -13,16 +13,15 @@ import kotlinx.serialization.Serializable @Serializable data class Message( - val timestamp: String, - val sender: String, - val original: String, - val proxied: String, - val channel: String, - val guild: String, - val thread: String?, - val system: String, - val member: String, - val deleted: Boolean + val timestamp: String, + val sender: String, + val original: String, + val proxied: String, + val channel: String, + val guild: String, + val thread: String?, + val system: String, + val member: String ) { companion object { fun fromRecord(record: ProxiedMessageRecord) = Message( @@ -34,8 +33,7 @@ data class Message( guild = record.guildId.toString(), thread = record.threadId.toString(), system = record.systemId, - member = record.memberId, - deleted = record.deleted + member = record.memberId ) } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt index ab90d986..69a03205 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt @@ -10,28 +10,26 @@ package dev.proxyfox.api.models import dev.proxyfox.common.fromColor import dev.proxyfox.database.records.misc.AutoProxyMode -import dev.proxyfox.database.records.misc.TrustLevel import dev.proxyfox.database.records.system.SystemRecord import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class System( - val id: String, - val name: String?, - val description: String?, - val tag: String?, - val pronouns: String?, - val color: String?, - @SerialName("avatar_url") + val id: String, + val name: String?, + val description: String?, + val tag: String?, + val pronouns: String?, + val color: String?, + @SerialName("avatar_url") val avatarUrl: String?, - val timezone: String?, - val created: String, - @SerialName("autoProxy") + val timezone: String?, + val created: String, + @SerialName("autoProxy") val autoProxy: String?, - @SerialName("autoType") - val autoType: AutoProxyMode, - val trust: Map + @SerialName("autoType") + val autoType: AutoProxyMode ) { companion object { fun fromRecord(system: SystemRecord) = System( @@ -45,8 +43,7 @@ data class System( timezone = system.timezone, created = system.timestamp.toString(), autoProxy = system.autoProxy, - autoType = system.autoType, - trust = system.trust + autoType = system.autoType ) } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt index 427af381..7de92479 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MemberRoutes.kt @@ -10,6 +10,7 @@ package dev.proxyfox.api.routes import dev.kord.common.entity.Snowflake import dev.proxyfox.api.AccessPlugin +import dev.proxyfox.api.models.ApiError import dev.proxyfox.api.models.Member import dev.proxyfox.api.models.MemberGuildSettings import dev.proxyfox.database.database @@ -21,22 +22,25 @@ fun Route.memberRoutes() { route("/systems/{id}/members") { install(AccessPlugin) get { - val id = call.parameters["id"] ?: return@get call.respond("System not found") + val id = call.parameters["id"] ?: return@get call.respond(ApiError(404, "System Not Found")) call.respond(database.fetchMembersFromSystem(id)?.map(Member.Companion::fromRecord) ?: emptyList()) } route("/{member}") { install(AccessPlugin) get { - val id = call.parameters["id"] ?: return@get call.respond("System not found") - val member = database.fetchMemberFromSystem(id, call.parameters["member"]!!) ?: return@get call.respond("Member not found") + val id = call.parameters["id"] ?: return@get call.respond(ApiError(404, "System Not Found")) + val member = database.fetchMemberFromSystem(id, call.parameters["member"]!!) + ?: return@get call.respond(ApiError(404, "Member Not Found")) call.respond(Member.fromRecord(member)) } get("/guilds/{guild}") { - val id = call.parameters["id"] ?: return@get call.respond("System not found") - val member = database.fetchMemberFromSystem(id, call.parameters["member"]!!) ?: return@get call.respond("Member not found") - val guildSettings = database.fetchMemberServerSettingsFromSystemAndMember(Snowflake(call.parameters["guild"]!!).value, id, member.id) ?: return@get call.respond("Guild not found") + val id = call.parameters["id"] ?: return@get call.respond(ApiError(404, "System Not Found")) + val member = database.fetchMemberFromSystem(id, call.parameters["member"]!!) + ?: return@get call.respond(ApiError(404, "Member Not Found")) + val guildSettings = database.fetchMemberServerSettingsFromSystemAndMember(Snowflake(call.parameters["guild"]!!).value, id, member.id) + ?: return@get call.respond(ApiError(404, "Guild Not Found")) call.respond(MemberGuildSettings.fromRecord(guildSettings)) } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt index 212f9328..b9c3f1b9 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/MessageRoutes.kt @@ -9,6 +9,7 @@ package dev.proxyfox.api.routes import dev.kord.common.entity.Snowflake +import dev.proxyfox.api.models.ApiError import dev.proxyfox.api.models.Message import dev.proxyfox.database.database import io.ktor.server.application.* @@ -17,7 +18,8 @@ import io.ktor.server.routing.* fun Route.messageRoutes() { get("/messages/{id}") { - val message = database.fetchMessage(Snowflake(call.parameters["id"]!!)) ?: return@get call.respond("Message not found") + val message = database.fetchMessage(Snowflake(call.parameters["id"]!!)) + ?: return@get call.respond(ApiError(404, "Message Not Found")) call.respond(Message.fromRecord(message)) } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt index 868f668b..aed23dc0 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SwitchRoutes.kt @@ -9,6 +9,7 @@ package dev.proxyfox.api.routes import dev.proxyfox.api.getAccess +import dev.proxyfox.api.models.ApiError import dev.proxyfox.api.models.Member import dev.proxyfox.api.models.Switch import dev.proxyfox.database.database @@ -19,14 +20,14 @@ import io.ktor.server.routing.* fun Route.switchRoutes() { route("/systems/{id}/switches") { getAccess { - val id = call.parameters["id"] ?: return@getAccess call.respond("System not found") + val id = call.parameters["id"] ?: return@getAccess call.respond(ApiError(404, "System Not Found")) call.respond(database.fetchSwitchesFromSystem(id)?.map(Switch.Companion::fromRecord) ?: emptyList()) } } route("/systems/{id}/fronters") { getAccess { - val id = call.parameters["id"] ?: return@getAccess call.respond("System not found") + val id = call.parameters["id"] ?: return@getAccess call.respond(ApiError(404, "System Not Found")) call.respond(database.fetchFrontingMembersFromSystem(id)?.map(Member.Companion::fromRecord) ?: emptyList()) } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt index 08902adc..f0f51ccf 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/SystemRoutes.kt @@ -10,6 +10,7 @@ package dev.proxyfox.api.routes import dev.kord.common.entity.Snowflake import dev.proxyfox.api.getAccess +import dev.proxyfox.api.models.ApiError import dev.proxyfox.api.models.System import dev.proxyfox.api.models.SystemGuildSettings import dev.proxyfox.database.database @@ -20,12 +21,13 @@ import io.ktor.server.routing.* fun Route.systemRoutes() { route("/systems/{id}") { getAccess { - val system = database.fetchSystemFromId(call.parameters["id"]!!) ?: return@getAccess call.respond("System not found") + val system = database.fetchSystemFromId(call.parameters["id"]!!) + ?: return@getAccess call.respond(ApiError(404, "SystemNot Found")) call.respond(System.fromRecord(system)) } getAccess("/guilds/{guild}") { - val id = call.parameters["id"] ?: return@getAccess call.respond("System not found") + val id = call.parameters["id"] ?: return@getAccess call.respond(ApiError(404, "System Not Found")) val settings = database.getOrCreateServerSettingsFromSystem(Snowflake(call.parameters["guild"]!!).value, id) call.respond(SystemGuildSettings.fromRecord(settings)) } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt index 9fa9ae05..7afb6510 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/TokenRoutes.kt @@ -8,6 +8,7 @@ package dev.proxyfox.api.routes +import dev.proxyfox.api.models.ApiError import dev.proxyfox.api.models.Token import dev.proxyfox.database.database import io.ktor.server.application.* @@ -16,8 +17,8 @@ import io.ktor.server.routing.* fun Route.tokenRoutes() { get("/tokens") { - val tokenString = call.request.headers["Authorization"] ?: return@get call.respond("404: Not Found") - val token = database.fetchToken(tokenString) ?: return@get call.respond("404: Not Found") + val tokenString = call.request.headers["Authorization"] ?: return@get call.respond(ApiError(404, "Not Found")) + val token = database.fetchToken(tokenString) ?: return@get call.respond(ApiError(404, "Not Found")) call.respond(Token.fromRecord(token)) } } From cab2c3b1e607f12cd9ea21fc73dbd23fa3527200 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 1 May 2023 17:22:23 -0500 Subject: [PATCH 122/137] Remove unneeded indent --- modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt index cbd92e6e..db8afdc6 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt @@ -33,7 +33,7 @@ data class Message( guild = record.guildId.toString(), thread = record.threadId.toString(), system = record.systemId, - member = record.memberId + member = record.memberId ) } } \ No newline at end of file From 21ab55f985127c96652be5d933a725d4f99580e0 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 1 May 2023 19:33:11 -0500 Subject: [PATCH 123/137] Avoid unnecessary api lookups where possible --- .../dev/proxyfox/bot/command/MiscCommands.kt | 2 + .../main/kotlin/dev/proxyfox/sync/PkSync.kt | 58 +++++++++++++------ 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index c44ec556..67ec5c3c 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -786,6 +786,8 @@ object MiscCommands : CommandRegistrar { @OptIn(DontExpose::class) private suspend fun syncPk(ctx: DiscordContext, system: SystemRecord, upload: Boolean): Boolean { + ctx.deferResponse() + val res = if (upload) { PkSync.push(system) } else { diff --git a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt index 661e242e..e876d7cc 100644 --- a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt +++ b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt @@ -97,6 +97,13 @@ object PkSync { return Either.EitherA(true) } + fun Array.getOrNull(id: String, name: String): PkMember? { + for (member in this) + if (member.id == id) return member + for (member in this) + if (member.name == name) return member + return null + } suspend fun push(system: SystemRecord): Either { val token = system.pkToken ?: return Either.EitherA(false) @@ -105,6 +112,10 @@ object PkSync { systemResp.getException().throwIfPresent() val pkSystem = systemResp.getSuccessOrNull() ?: return Either.EitherB(systemResp.getError()) + val memberResp = PluralKt.Member.getMembers(pkSystem.id, token).await() + memberResp.getException().throwIfPresent() + val pkMembers = memberResp.getSuccessOrNull() ?: return Either.EitherB(memberResp.getError()) + pkSystem.name = system.name pkSystem.description = system.description ?: pkSystem.description pkSystem.tag = system.tag ?: pkSystem.tag @@ -112,6 +123,7 @@ object PkSync { pkSystem.pronouns = system.pronouns ?: pkSystem.pronouns val systemPushResp = PluralKt.System.updateSystem(pkSystem, token).await() + systemPushResp.getException().throwIfPresent() if (systemPushResp.isError()) return Either.EitherB(systemPushResp.getError()) val members = database.fetchMembersFromSystem(system.id)!! @@ -119,14 +131,34 @@ object PkSync { val memberToIdLookup = HashBiMap.create() val idToIdLookup = HashBiMap.create() + val newMems = ArrayList() + for (member in members) { var new = false - val pkMember = PluralKt.Member.getMember(member.id).await().getSuccessOrNull() - ?: PluralKt.Member.getMember(member.name).await().getSuccessOrNull() - ?: let { - new = true - PkMember() - } + val pkMember = pkMembers.getOrNull(member.id, member.name) ?: let { + new = true + PkMember() + } + val proxies = database.fetchProxiesFromSystemAndMember(system.id, member.id)!! + val pkProxies = ArrayList() + for (proxy in proxies) { + val pkProxy = PkProxyTag() + pkProxy.prefix = proxy.prefix + pkProxy.suffix = proxy.suffix + pkProxies.add(pkProxy) + } + + if ( + pkMember.name == member.name && + pkMember.pronouns == member.pronouns && + pkMember.avatarUrl == member.avatarUrl && + pkMember.color == PkColor(member.color) && + pkMember.description == member.description && + pkMember.displayName == member.displayName && + pkMember.keepProxy == member.keepProxy && + pkMember.proxyTags == pkProxies + ) continue + pkMember.name = member.name pkMember.pronouns = member.pronouns ?: pkMember.pronouns pkMember.avatarUrl = member.avatarUrl ?: pkMember.avatarUrl @@ -134,23 +166,15 @@ object PkSync { pkMember.description = member.description ?: pkMember.description pkMember.displayName = member.displayName ?: pkMember.displayName pkMember.keepProxy = member.keepProxy - - val proxies = database.fetchProxiesFromSystemAndMember(system.id, member.id)!! - - pkMember.proxyTags.clear() - - for (proxy in proxies) { - val pkProxy = PkProxyTag() - pkProxy.prefix = proxy.prefix - pkProxy.suffix = proxy.suffix - pkMember.proxyTags.add(pkProxy) - } + pkMember.proxyTags = pkProxies val newMem = if (new) { PluralKt.Member.createMember(pkMember, token) } else { PluralKt.Member.updateMember(pkMember.id, pkMember, token) }.await().getSuccessOrNull() ?: continue + + newMems.add(newMem) memberToIdLookup[newMem] = member.id idToIdLookup[newMem.id] = member.id } From 910ea4241ee058143af7f56dde3112e6853b4d98 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 1 May 2023 20:24:18 -0500 Subject: [PATCH 124/137] Bumb logback --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d54e4c0e..043e107a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] guava = "31.1-jre" -logback = "1.2.11" +logback = "1.4.7" kord = "0.9.x-SNAPSHOT" kotlin = "1.7.21" kotlinx_coroutines = "1.6.4" From 489af71a313e9d4f04d1e99d8af7e5e07863b3a1 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 1 May 2023 20:25:05 -0500 Subject: [PATCH 125/137] Fix pk sync's diff check --- .../dev/proxyfox/bot/command/MiscCommands.kt | 16 +++++- .../main/kotlin/dev/proxyfox/sync/PkSync.kt | 50 +++++++++++++------ 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 67ec5c3c..2f12dbe8 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -10,8 +10,11 @@ package dev.proxyfox.bot.command import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake +import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior +import dev.kord.core.behavior.edit +import dev.kord.core.entity.Message import dev.kord.rest.NamedFile import dev.kord.rest.builder.interaction.* import dev.proxyfox.bot.* @@ -784,14 +787,23 @@ object MiscCommands : CommandRegistrar { } } + class CommUpdater(val channel: MessageChannelBehavior) : PkSync.ProgressUpdater { + override suspend fun update(type: String, from: String, to: String) { + channel.createMessage("$type: $from -> $to") + } + } + @OptIn(DontExpose::class) private suspend fun syncPk(ctx: DiscordContext, system: SystemRecord, upload: Boolean): Boolean { ctx.deferResponse() + val updater = CommUpdater(ctx.getChannel(true)) + updater.channel.createMessage("Pk sync progress:") + val res = if (upload) { - PkSync.push(system) + PkSync.push(system, updater) } else { - PkSync.pull(system) + PkSync.pull(system, updater) } if (res.getA() == false) { diff --git a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt index e876d7cc..40025a4d 100644 --- a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt +++ b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt @@ -37,14 +37,21 @@ object PkSync { } } + interface ProgressUpdater { + suspend fun update(type: String, from: String, to: String) + } + private fun Response.getSuccessOrNull() = if (isSuccess()) getSuccess() else null - suspend fun pull(system: SystemRecord): Either { + suspend fun pull(system: SystemRecord, updater: ProgressUpdater): Either { val token = system.pkToken ?: return Either.EitherA(false) + updater.update("Start", "pk", "pf") + val systemResp = PluralKt.System.getMe(token).await() systemResp.getException().throwIfPresent() val pkSystem = systemResp.getSuccessOrNull() ?: return Either.EitherB(systemResp.getError()) + updater.update("System", pkSystem.id, system.id) system.name = pkSystem.name ?: system.name system.description = pkSystem.description ?: system.description @@ -65,6 +72,7 @@ object PkSync { system.id, pkMember.name )!! + updater.update("Member", pkMember.id, member.id) memberToIdLookup[pkMember] = member.id idToIdLookup[pkMember.id] = member.id @@ -105,16 +113,16 @@ object PkSync { return null } - suspend fun push(system: SystemRecord): Either { + suspend fun push(system: SystemRecord, updater: ProgressUpdater): Either { val token = system.pkToken ?: return Either.EitherA(false) + updater.update("Start", "pf", "pk") + val systemResp = PluralKt.System.getMe(token).await() systemResp.getException().throwIfPresent() val pkSystem = systemResp.getSuccessOrNull() ?: return Either.EitherB(systemResp.getError()) - val memberResp = PluralKt.Member.getMembers(pkSystem.id, token).await() - memberResp.getException().throwIfPresent() - val pkMembers = memberResp.getSuccessOrNull() ?: return Either.EitherB(memberResp.getError()) + updater.update("System", system.id, pkSystem.id) pkSystem.name = system.name pkSystem.description = system.description ?: pkSystem.description @@ -126,6 +134,10 @@ object PkSync { systemPushResp.getException().throwIfPresent() if (systemPushResp.isError()) return Either.EitherB(systemPushResp.getError()) + val memberResp = PluralKt.Member.getMembers(pkSystem.id, token).await() + memberResp.getException().throwIfPresent() + val pkMembers = memberResp.getSuccessOrNull() ?: return Either.EitherB(memberResp.getError()) + val members = database.fetchMembersFromSystem(system.id)!! val memberToIdLookup = HashBiMap.create() @@ -140,33 +152,41 @@ object PkSync { PkMember() } val proxies = database.fetchProxiesFromSystemAndMember(system.id, member.id)!! - val pkProxies = ArrayList() + val proxyList = ArrayList>() + var proxySame = true + for (proxy in pkMember.proxyTags) { + proxyList.add(Pair(proxy.prefix, proxy.suffix)) + } + for (proxy in proxies) { - val pkProxy = PkProxyTag() - pkProxy.prefix = proxy.prefix - pkProxy.suffix = proxy.suffix - pkProxies.add(pkProxy) + if (!proxyList.contains(Pair(proxy.prefix, proxy.suffix))) { + val pkProxy = PkProxyTag() + pkProxy.prefix = proxy.prefix + pkProxy.suffix = proxy.suffix + pkMember.proxyTags.add(pkProxy) + proxySame = false + } } if ( pkMember.name == member.name && pkMember.pronouns == member.pronouns && pkMember.avatarUrl == member.avatarUrl && - pkMember.color == PkColor(member.color) && + (pkMember.color?.color ?: -1) == member.color && pkMember.description == member.description && pkMember.displayName == member.displayName && pkMember.keepProxy == member.keepProxy && - pkMember.proxyTags == pkProxies + proxySame ) continue pkMember.name = member.name pkMember.pronouns = member.pronouns ?: pkMember.pronouns pkMember.avatarUrl = member.avatarUrl ?: pkMember.avatarUrl - pkMember.color = PkColor(member.color) + if (member.color != -1) + pkMember.color = PkColor(member.color) pkMember.description = member.description ?: pkMember.description pkMember.displayName = member.displayName ?: pkMember.displayName pkMember.keepProxy = member.keepProxy - pkMember.proxyTags = pkProxies val newMem = if (new) { PluralKt.Member.createMember(pkMember, token) @@ -174,6 +194,8 @@ object PkSync { PluralKt.Member.updateMember(pkMember.id, pkMember, token) }.await().getSuccessOrNull() ?: continue + updater.update("Member", member.id, newMem.id) + newMems.add(newMem) memberToIdLookup[newMem] = member.id idToIdLookup[newMem.id] = member.id From 3d38b3f0d6115ef7bdf69c007c398825f51bc81f Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 1 May 2023 20:36:57 -0500 Subject: [PATCH 126/137] Check diff in pull sync --- .../main/kotlin/dev/proxyfox/sync/PkSync.kt | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt index 40025a4d..b17ca67a 100644 --- a/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt +++ b/modules/sync/src/main/kotlin/dev/proxyfox/sync/PkSync.kt @@ -72,20 +72,12 @@ object PkSync { system.id, pkMember.name )!! - updater.update("Member", pkMember.id, member.id) + memberToIdLookup[pkMember] = member.id idToIdLookup[pkMember.id] = member.id - member.name = pkMember.name - member.pronouns = pkMember.pronouns - member.avatarUrl = pkMember.avatarUrl - member.color = pkMember.color?.color ?: member.color - member.description = pkMember.description - member.displayName = pkMember.displayName - member.keepProxy = pkMember.keepProxy - database.updateMember(member) - val proxies = database.fetchProxiesFromSystemAndMember(system.id, member.id)!! + var updatedProxies = false for (pkProxy in pkMember.proxyTags) { var hasProxy = false @@ -95,9 +87,32 @@ object PkSync { } } if (!hasProxy) { + updatedProxies = true database.createProxyTag(system.id, member.id, pkProxy.prefix, pkProxy.suffix) } } + + if ( + pkMember.name != member.name || + pkMember.pronouns != member.pronouns || + pkMember.avatarUrl != member.avatarUrl || + (pkMember.color?.color ?: -1) != member.color || + pkMember.description != member.description || + pkMember.displayName != member.displayName || + pkMember.keepProxy != member.keepProxy + ) { + member.name = pkMember.name + member.pronouns = pkMember.pronouns + member.avatarUrl = pkMember.avatarUrl + member.color = pkMember.color?.color ?: member.color + member.description = pkMember.description + member.displayName = pkMember.displayName + member.keepProxy = pkMember.keepProxy + database.updateMember(member) + updater.update("Member", pkMember.id, member.id) + } else if (updatedProxies) { + updater.update("Member", pkMember.id, member.id) + } } // TODO: groups once implemented From a4d6c43de6cae5009d4612ffa37bbb5ed19a2f2d Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 5 Jun 2023 15:02:51 -0500 Subject: [PATCH 127/137] Token creation and deletion --- .../dev/proxyfox/bot/command/MiscCommands.kt | 143 +++++++++++++++++- .../context/InteractionCommandContext.kt | 3 +- .../proxyfox/bot/command/menu/DiscordMenu.kt | 2 +- .../bot/command/menu/DiscordMessageMenu.kt | 25 ++- .../bot/command/menu/DiscordScreen.kt | 10 +- .../command/menu/InteractionCommandMenu.kt | 26 +++- .../kotlin/dev/proxyfox/database/Database.kt | 9 +- .../dev/proxyfox/database/InMemoryDatabase.kt | 34 +++++ .../dev/proxyfox/database/MongoDatabase.kt | 22 ++- .../dev/proxyfox/database/NopDatabase.kt | 7 + .../dev/proxyfox/database/ProxyDatabase.kt | 20 +++ .../database/records/misc/TokenRecord.kt | 4 +- .../database/records/misc/TokenType.kt | 3 +- 13 files changed, 278 insertions(+), 30 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 2f12dbe8..4170fc5e 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -8,17 +8,19 @@ package dev.proxyfox.bot.command +import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior -import dev.kord.core.behavior.edit -import dev.kord.core.entity.Message import dev.kord.rest.NamedFile +import dev.kord.rest.builder.component.option import dev.kord.rest.builder.interaction.* +import dev.kord.rest.builder.message.modify.actionRow import dev.proxyfox.bot.* import dev.proxyfox.bot.command.context.* +import dev.proxyfox.bot.command.menu.DiscordScreen import dev.proxyfox.bot.command.node.attachment import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil @@ -34,6 +36,7 @@ import dev.proxyfox.database.etc.importer.import import dev.proxyfox.database.records.member.MemberRecord import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.misc.ServerSettingsRecord +import dev.proxyfox.database.records.misc.TokenType import dev.proxyfox.database.records.misc.TrustLevel import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord @@ -264,6 +267,13 @@ object MiscCommands : CommandRegistrar { editMessage(this, system, message, value.interaction.command.strings["content"]!!) } } + subCommand("token", "Manage tokens for your system!") { + runs("management") { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system, true)) return@runs false + token(this, system) + } + } } deferChatInputCommand("pluralkit", "Commands for PluralKit integration with ProxyFox") { subCommand("set-token", "Store your PluralKit token") { @@ -754,11 +764,19 @@ object MiscCommands : CommandRegistrar { } literal("pluralkit", "pk") { literal("pull", "get", "download", "import") { - + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + syncPk(this, system, false) + } } literal("push", "set", "upload", "export") { - + runs { + val system = database.fetchSystemFromUser(getUser()) + if (!checkSystem(this, system)) return@runs false + syncPk(this, system, true) + } } literal("token") { @@ -813,7 +831,7 @@ object MiscCommands : CommandRegistrar { if (res.getB() != null) { val b = res.getB()!! - val message = b.message.replace(system.pkToken!!, "[pktoken]") + val message = b.message.replace(system.pkToken!!, "[pktoken]", true) ctx.respondFailure(""" PluralKit returned an error: `$message` @@ -920,9 +938,120 @@ object MiscCommands : CommandRegistrar { return true } - @Suppress("UNUSED_PARAMETER") private suspend fun token(ctx: DiscordContext, system: SystemRecord): Boolean { - ctx.respondWarning("Not yet implemented") + ctx.interactionMenu(true) { + val create = "create" { + this as DiscordScreen + button("api:access") { + val token = database.createToken(system.id, TokenType.API_ACCESS) + edit { + content = "Token created!\nID: ${token.id}\n${token.token}" + components = mutableListOf() + } + close() + } + + button("api:edit") { + val token = database.createToken(system.id, TokenType.API_EDIT) + edit { + content = "Token created!\nID: ${token.id}\n${token.token}" + components = mutableListOf() + } + close() + } + + button("system:transfer") { + val token = database.createToken(system.id, TokenType.SYSTEM_TRANSFER) + edit { + content = "Token created!\nID: ${token.id}\n${token.token}" + components = mutableListOf() + } + close() + } + + onInit { + edit { + content = "What type of token do you want to create?" + components = null + actionRow { + interactionButton(ButtonStyle.Secondary, "api:access") { + label = "API Access" + } + interactionButton(ButtonStyle.Secondary, "api:edit") { + label = "API Edit" + } + interactionButton(ButtonStyle.Secondary, "system:transfer") { + label = "System Transfer" + } + } + } + } + } + val delete = "delete" { + this as DiscordScreen + select("token") { + it.forEach { token -> + database.dropTokenById(system.id, token) + } + edit { + content = "Deleted tokens." + components = mutableListOf() + } + close() + } + button("all") { + database.dropTokens(system.id) + edit { + content = "Deleted all tokens." + components = mutableListOf() + } + close() + } + + onInit { + edit { + content = "Which tokens do you want to delete?" + components = null + actionRow { + stringSelect("token") { + placeholder = "Select tokens to delete" + database.fetchTokens(system.id).forEach { + option("${it.type} - ${it.id}", it.id) + } + } + } + } + } + } + default { + this as DiscordScreen + + button("create") { + setScreen(create) + } + + button("delete") { + setScreen(delete) + } + + onInit { + edit { + content = "What do you want to do?" + components = null + actionRow { + interactionButton(ButtonStyle.Primary, "create") { + emoji = Emojis.check + label = "Create a token" + } + interactionButton(ButtonStyle.Danger, "delete") { + emoji = Emojis.wastebasket + label = "Delete tokens" + } + } + } + } + } + } return true } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt index 9dea25b9..8d71c01f 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/InteractionCommandContext.kt @@ -126,12 +126,13 @@ class InteractionCommandContext(value: ChatInputCommandInteractionCreateEvent) : } override suspend fun interactionMenu(private: Boolean, action: suspend DiscordMenu.() -> Unit) { + val message = deferred ?: if (private) value.interaction.deferEphemeralResponse() else value.interaction.deferPublicResponse() val menu = InteractionCommandMenu(message.respond { content = "Thinking..." - }) + }, value.interaction.user.id) menu.action() menu.init() } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt index 98d78b36..3429e7ff 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMenu.kt @@ -30,4 +30,4 @@ abstract class DiscordMenu : CommandMenu() { } abstract suspend fun edit(builder: suspend MessageModifyBuilder.() -> Unit) -} \ No newline at end of file +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt index 64fc0cdc..9f3c9bd7 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt @@ -12,6 +12,7 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.edit import dev.kord.core.entity.Message import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent import dev.kord.rest.builder.message.modify.MessageModifyBuilder import dev.proxyfox.bot.kord import dev.proxyfox.common.onlyIf @@ -24,16 +25,28 @@ class DiscordMessageMenu(val message: Message, val userId: Snowflake) : DiscordM } override suspend fun init() { - jobs.add( - kord.onlyIf({ interaction.message.id }, message.id) { - interact(this) - } + jobs.addAll( + arrayListOf( + kord.onlyIf({ interaction.message.id }, message.id) { + buttonInteract(this) + }, + kord.onlyIf({ interaction.message.id }, message.id) { + + } + ) ) super.init() } - private suspend fun interact(button: ButtonInteractionCreateEvent) { + private suspend fun buttonInteract(button: ButtonInteractionCreateEvent) { if (button.interaction.user.id != userId) return + button.interaction.deferPublicMessageUpdate() active!!.click(button.interaction.componentId) } -} \ No newline at end of file + + private suspend fun selectInteract(select: SelectMenuInteractionCreateEvent) { + if (select.interaction.user.id != userId) return + select.interaction.deferPublicMessageUpdate() + (active!! as DiscordScreen).selects[select.interaction.componentId]?.let { it(select.interaction.values) } + } +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt index ecd02e39..03bcc0ce 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordScreen.kt @@ -10,14 +10,22 @@ package dev.proxyfox.bot.command.menu import dev.proxyfox.command.menu.CommandScreen +typealias SelectAction = suspend (List) -> Unit + class DiscordScreen(name: String) : CommandScreen(name) { private var initializer: suspend () -> Unit = {} + var selects = HashMap() + fun onInit(action: suspend () -> Unit) { initializer = action } + fun select(name: String, action: SelectAction) { + selects[name] = action + } + override suspend fun init() { initializer() } -} \ No newline at end of file +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt index 386638aa..d8a294ef 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/InteractionCommandMenu.kt @@ -8,15 +8,17 @@ package dev.proxyfox.bot.command.menu +import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.interaction.response.edit import dev.kord.core.entity.interaction.response.EphemeralMessageInteractionResponse import dev.kord.core.entity.interaction.response.MessageInteractionResponse import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent import dev.kord.rest.builder.message.modify.MessageModifyBuilder import dev.proxyfox.bot.kord import dev.proxyfox.common.onlyIf -class InteractionCommandMenu(val interaction: MessageInteractionResponse) : DiscordMenu() { +class InteractionCommandMenu(val interaction: MessageInteractionResponse, val userId: Snowflake) : DiscordMenu() { override suspend fun edit(builder: suspend MessageModifyBuilder.() -> Unit) { interaction.edit { builder() @@ -24,19 +26,31 @@ class InteractionCommandMenu(val interaction: MessageInteractionResponse) : Disc } override suspend fun init() { - jobs.add( - kord.onlyIf({ interaction.message.id }, interaction.message.id) { - interact(this) - } + jobs.addAll( + arrayListOf( + kord.onlyIf({ interaction.message.id }, interaction.message.id) { + buttonInteract(this) + }, + kord.onlyIf({ interaction.message.id }, interaction.message.id) { + selectInteract(this) + } + ) ) super.init() } - private suspend fun interact(button: ButtonInteractionCreateEvent) { + private suspend fun buttonInteract(button: ButtonInteractionCreateEvent) { if (interaction is EphemeralMessageInteractionResponse) button.interaction.deferEphemeralMessageUpdate() else button.interaction.deferPublicMessageUpdate() active!!.click(button.interaction.componentId) } + + private suspend fun selectInteract(select: SelectMenuInteractionCreateEvent) { + if (interaction is EphemeralMessageInteractionResponse) + select.interaction.deferEphemeralMessageUpdate() + else select.interaction.deferPublicMessageUpdate() + (active!! as DiscordScreen).selects[select.interaction.componentId]?.let { it(select.interaction.values) } + } } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt index 9a253259..2ff7c2be 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/Database.kt @@ -320,12 +320,19 @@ abstract class Database : AutoCloseable { abstract suspend fun dropMessage(messageId: Snowflake) open suspend fun createToken(systemId: String, type: TokenType): TokenRecord { - val token = TokenRecord(generateUniqueToken(), systemId, type) + val token = TokenRecord(generateUniqueToken(), firstFreeTokenId(systemId), systemId, type) updateToken(token) return token } + private suspend fun firstFreeTokenId(systemId: String): String = + fetchTokens(systemId).map(TokenRecord::id).firstFree() abstract suspend fun fetchToken(token: String): TokenRecord? + abstract suspend fun fetchTokenFromId(systemId: String, id: String): TokenRecord? + abstract suspend fun fetchTokens(systemId: String): List abstract suspend fun updateToken(token: TokenRecord) + abstract suspend fun dropToken(token: String) + abstract suspend fun dropTokenById(systemId: String, id: String) + abstract suspend fun dropTokens(systemId: String) open suspend fun containsToken(token: String): Boolean = fetchToken(token) != null open suspend fun generateUniqueToken(): String { var token = generateToken() diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index 85f99a7f..56dba67a 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -267,10 +267,44 @@ class InMemoryDatabase : Database() { return systemTokens[token] } + override suspend fun fetchTokenFromId(systemId: String, id: String): TokenRecord? { + return systemTokens.values.firstNotNullOfOrNull { + if (it.systemId == systemId && it.id == id) + it + null + } + } + + override suspend fun fetchTokens(systemId: String): List { + val out = ArrayList() + for (token in systemTokens.values) { + if (token.systemId == systemId) { + out.add(token) + } + } + return out + } + override suspend fun updateToken(token: TokenRecord) { systemTokens[token.token] = token } + override suspend fun dropToken(token: String) { + systemTokens.remove(token) + } + + override suspend fun dropTokenById(systemId: String, id: String) { + dropToken(fetchTokenFromId(systemId, id)?.token ?: return) + } + + override suspend fun dropTokens(systemId: String) { + for (token in systemTokens.values) { + if (token.systemId == systemId) { + systemTokens.remove(token.token) + } + } + } + override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { systems[record.systemId] ?: return false memberProxies[record.systemId] ?: memberProxies.set(record.systemId, arrayListOf()) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index 98e897c5..44d00371 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -295,10 +295,28 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { override suspend fun fetchToken(token: String): TokenRecord? = systemTokens.findFirstOrNull("token" eq token) + override suspend fun fetchTokenFromId(systemId: String, id: String): TokenRecord? = + systemTokens.findFirstOrNull("systemId" eq systemId, "id" eq id) + + override suspend fun fetchTokens(systemId: String): List = + systemTokens.find("systemId" eq systemId).toList() + override suspend fun updateToken(token: TokenRecord) { systemTokens.replaceOneById(token._id, token, upsert()).awaitFirst() } + override suspend fun dropToken(token: String) { + systemTokens.deleteOne("token" eq token).awaitFirst() + } + + override suspend fun dropTokenById(systemId: String, id: String) { + systemTokens.deleteOne("systemId" eq systemId, "id" eq id).awaitFirst() + } + + override suspend fun dropTokens(systemId: String) { + systemTokens.deleteMany("systemId" eq systemId) + } + override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { memberProxies.insertOne(record).awaitFirst() return true @@ -524,10 +542,6 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { if (witness.add(user)) userQueue += user.create() } - override suspend fun dropMessage(messageId: Snowflake) { - TODO("Not yet implemented") - } - override suspend fun createMessage(message: ProxiedMessageRecord) { if (witness.add(message)) proxiedMessageQueue += message.create() } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt index 647d43e1..fcb275cc 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/NopDatabase.kt @@ -113,8 +113,15 @@ class NopDatabase : Database() { override suspend fun dropMessage(messageId: Snowflake) {} override suspend fun fetchToken(token: String): TokenRecord? = null + override suspend fun fetchTokenFromId(systemId: String, id: String): TokenRecord? = null + + override suspend fun fetchTokens(systemId: String): List = listOf() override suspend fun updateToken(token: TokenRecord) = fail("Cannot store token for ${token.systemId}.") + override suspend fun dropToken(token: String) {} + override suspend fun dropTokenById(systemId: String, id: String) {} + + override suspend fun dropTokens(systemId: String) {} override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean = false diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt index 30bb28d9..d233ce48 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/ProxyDatabase.kt @@ -158,10 +158,30 @@ open class ProxyDatabase(protected val proxy: T) : Database() { return proxy.fetchToken(token) } + override suspend fun fetchTokenFromId(systemId: String, id: String): TokenRecord? { + return proxy.fetchTokenFromId(systemId, id) + } + + override suspend fun fetchTokens(systemId: String): List { + return proxy.fetchTokens(systemId) + } + override suspend fun updateToken(token: TokenRecord) { return proxy.updateToken(token) } + override suspend fun dropToken(token: String) { + proxy.dropToken(token) + } + + override suspend fun dropTokenById(systemId: String, id: String) { + proxy.dropTokenById(systemId, id) + } + + override suspend fun dropTokens(systemId: String) { + proxy.dropTokens(systemId) + } + override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { return proxy.createProxyTag(record) } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt index 9d095da4..e7d0192e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenRecord.kt @@ -18,12 +18,14 @@ import org.bson.types.ObjectId class TokenRecord : MongoRecord { @Contextual override var _id: ObjectId = ObjectId() + var id: PkId var token: String var systemId: PkId var type: TokenType - constructor(token: String, systemId: PkId, type: TokenType) { + constructor(token: String, id: PkId, systemId: PkId, type: TokenType) { this.token = token + this.id = id this.systemId = systemId this.type = type } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt index 2279dfa0..cac58ca3 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt @@ -19,8 +19,7 @@ import kotlinx.serialization.encoding.Encoder enum class TokenType(private val actualName: String) { SYSTEM_TRANSFER("system:transfer"), API_ACCESS("api:access"), - API_EDIT("api:edit") - ; + API_EDIT("api:edit"); override fun toString(): String { return actualName From 8a40ce54273526cf77fbdf476efb0502dbad325e Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 5 Jun 2023 15:31:47 -0500 Subject: [PATCH 128/137] Transfer systems using transfer tokens --- .../proxyfox/bot/command/CommonMessages.kt | 2 +- .../dev/proxyfox/bot/command/MiscCommands.kt | 55 ++++++++++++++++--- .../dev/proxyfox/database/InMemoryDatabase.kt | 1 + .../dev/proxyfox/database/MongoDatabase.kt | 4 +- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommonMessages.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommonMessages.kt index 9dba9959..03bee5d0 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommonMessages.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/CommonMessages.kt @@ -10,7 +10,7 @@ package dev.proxyfox.bot.command enum class CommonMessages(val builder: (Array) -> String) { TEXT_COMMAND({ - "`${dev.proxyfox.bot.prefix}>${it[0]}" + "`${dev.proxyfox.bot.prefix}>${it[0]}`" }), NOT_FOUND({ "${it[0]} not found. Create one using `/system create` or ${TEXT_COMMAND("system new")}" diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 4170fc5e..0ecaaa1d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -34,10 +34,7 @@ import dev.proxyfox.database.etc.exporter.Exporter import dev.proxyfox.database.etc.importer.ImporterException import dev.proxyfox.database.etc.importer.import import dev.proxyfox.database.records.member.MemberRecord -import dev.proxyfox.database.records.misc.AutoProxyMode -import dev.proxyfox.database.records.misc.ServerSettingsRecord -import dev.proxyfox.database.records.misc.TokenType -import dev.proxyfox.database.records.misc.TrustLevel +import dev.proxyfox.database.records.misc.* import dev.proxyfox.database.records.system.SystemRecord import dev.proxyfox.database.records.system.SystemServerSettingsRecord import dev.proxyfox.sync.PkSync @@ -712,6 +709,29 @@ object MiscCommands : CommandRegistrar { token(this, system) } } + literal("transfer") { + responds("Please provide a token to transfer from") + greedy("transfer") { + runs { + val system = database.fetchSystemFromUser(getUser()) + if (system != null) { + respondFailure("You can only run this command when you have no system registered.") + return@runs false + } + val token = database.fetchToken(it()) + if (token == null) { + respondFailure("Token not found.") + return@runs false + } + if (token.type != TokenType.SYSTEM_TRANSFER) { + respondFailure("Token isn't a transfer token.") + return@runs false + } + + transfer(this, token) + } + } + } literal("trust") { runs { val system = database.fetchSystemFromUser(getUser()) @@ -805,6 +825,24 @@ object MiscCommands : CommandRegistrar { } } + private suspend fun transfer(ctx: DiscordContext, token: TokenRecord): Boolean { + val system = database.fetchSystemFromId(token.systemId)!! + system.users.forEach { + val user = database.getOrCreateUser(it) + user.systemId = null + database.updateUser(user) + } + val id = ctx.getUser()!!.id.value + system.users = arrayListOf(id) + database.updateSystem(system) + val user = database.getOrCreateUser(id) + user.systemId = system.id + database.updateUser(user) + database.dropToken(token.token) + ctx.respondSuccess("System successfully transferred!") + return true + } + class CommUpdater(val channel: MessageChannelBehavior) : PkSync.ProgressUpdater { override suspend fun update(type: String, from: String, to: String) { channel.createMessage("$type: $from -> $to") @@ -813,10 +851,9 @@ object MiscCommands : CommandRegistrar { @OptIn(DontExpose::class) private suspend fun syncPk(ctx: DiscordContext, system: SystemRecord, upload: Boolean): Boolean { - ctx.deferResponse() + ctx.respondPlain("Pk sync progress:", true) val updater = CommUpdater(ctx.getChannel(true)) - updater.channel.createMessage("Pk sync progress:") val res = if (upload) { PkSync.push(system, updater) @@ -825,14 +862,14 @@ object MiscCommands : CommandRegistrar { } if (res.getA() == false) { - ctx.respondFailure("You don't have a PluralKit token registered.") + updater.channel.createMessage("You don't have a PluralKit token registered.") return false } if (res.getB() != null) { val b = res.getB()!! val message = b.message.replace(system.pkToken!!, "[pktoken]", true) - ctx.respondFailure(""" + updater.channel.createMessage(""" PluralKit returned an error: `$message` """.trimIndent()) @@ -841,7 +878,7 @@ object MiscCommands : CommandRegistrar { val message = if (upload) "exported" to "to" else "imported" to "from" - ctx.respondSuccess("Successfully ${message.first} system data ${message.second} PluralKit") + updater.channel.createMessage("Successfully ${message.first} system data ${message.second} PluralKit") return true } diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt index 56dba67a..75f5a59f 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/InMemoryDatabase.kt @@ -184,6 +184,7 @@ class InMemoryDatabase : Database() { systemServers.remove(system.id) systemChannels.remove(system.id) members.remove(system.id) + dropTokens(system.id) for (systemUserId in system.users) { users.remove(systemUserId) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt index 44d00371..6c0ac1f4 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/MongoDatabase.kt @@ -206,6 +206,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberProxies.deleteMany(filter).awaitFirst() memberServers.deleteMany(filter).awaitFirst() members.deleteMany(filter).awaitFirst() + dropTokens(system.id) systems.deleteOneById(system._id).awaitFirst() users.deleteMany(filter).awaitFirst() return true @@ -314,7 +315,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { } override suspend fun dropTokens(systemId: String) { - systemTokens.deleteMany("systemId" eq systemId) + systemTokens.deleteMany("systemId" eq systemId).awaitFirst() } override suspend fun createProxyTag(record: MemberProxyTagRecord): Boolean { @@ -599,6 +600,7 @@ class MongoDatabase(private val dbName: String = "ProxyFox") : Database() { memberQueue += DeleteManyModel(filter) systemQueue += system.delete() userQueue += DeleteManyModel(filter) + dropTokens(system.id) return true } From 9ce6a59dd18ab9e6e5e80b7e036cd5f24ce8075f Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 5 Jun 2023 17:08:22 -0500 Subject: [PATCH 129/137] Pager to use Menu from PF-C --- .../main/kotlin/dev/proxyfox/bot/Emojis.kt | 5 + .../proxyfox/bot/command/SwitchCommands.kt | 11 +- .../proxyfox/bot/command/SystemCommands.kt | 13 +- .../bot/command/context/DiscordContext.kt | 114 +++++++ .../bot/command/menu/DiscordMessageMenu.kt | 2 +- .../kotlin/dev/proxyfox/bot/prompts/Pager.kt | 323 ------------------ .../dev/proxyfox/bot/prompts/TimedPrompt.kt | 64 ---- 7 files changed, 133 insertions(+), 399 deletions(-) delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedPrompt.kt diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt index efbe0551..a3f10962 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/Emojis.kt @@ -18,6 +18,11 @@ object Emojis { val multiply = "✖".partial val wastebasket = "🗑".partial val move = "🔀".partial + val rewind = "⏪".partial + val fastforward = "⏩".partial + val last = "⬅".partial + val next = "➡".partial + val numbers = "\uD83D\uDD22".partial val ReactionEmoji.Unicode.partial get() = name.partial diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 762b2c80..8b89f36d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -17,7 +17,6 @@ import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.deferChatInputCommand import dev.proxyfox.bot.parseDuration -import dev.proxyfox.bot.prompts.Pager import dev.proxyfox.command.CommandParser import dev.proxyfox.command.NodeHolder import dev.proxyfox.command.node.builtin.greedy @@ -251,9 +250,13 @@ object SwitchCommands : CommandRegistrar { // We know the system exists here, will be non-null val switches = database.fetchSortedSwitchesFromSystem(system.id)!! - Pager.build(ctx.getUser()!!.id, ctx.getChannel(), switches, 20, { - title = "[$it] Front history of ${system.showName}" - }, { it.membersAsString("**", "**") + " ()\n" }) + ctx.pager( + switches, + 20, + { title = "[$it] Front history of ${system.showName}" }, + { membersAsString("**", "**") + " ()\n" }, + false + ) return true } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 7ca95635..64456a01 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -19,7 +19,6 @@ import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.command.context.system import dev.proxyfox.bot.command.node.attachment -import dev.proxyfox.bot.prompts.Pager import dev.proxyfox.command.CommandParser import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.fromColor @@ -500,23 +499,23 @@ object SystemCommands : CommandRegistrar { } val proxies = database.fetchProxiesFromSystem(system.id)!! - Pager.build( - ctx.getUser()!!.id, - ctx.getChannel(), + + ctx.pager( database.fetchMembersFromSystem(system.id)!!.sortedBy { if (byMessage) it.messageCount it.name }.map { m -> m to proxies.filter { it.memberId == m.id } }, 20, - { page -> system(system, nameTransformer = { "[$page] Members of $it" }) }, + { page -> system(system, nameTransformer = { "[$page] Members of $this" }) }, { - val str = if (it.second.isNotEmpty()) it.second.joinToString( + val str = if (second.isNotEmpty()) second.joinToString( "\uFEFF``, ``\uFEFF", " (``\uFEFF", "\uFEFF``)" ) else "" - "`${it.first.id}`\u2007•\u2007**${it.first.name}**${str}\n" + "`${first.id}`\u2007•\u2007**${first.name}**${str}\n" }, + false ) return true } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index ff367f83..a345cce8 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -16,9 +16,11 @@ import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.createMessage import dev.kord.core.entity.* import dev.kord.rest.NamedFile +import dev.kord.rest.builder.component.option import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.modify.MessageModifyBuilder import dev.kord.rest.builder.message.modify.actionRow +import dev.kord.rest.builder.message.modify.embed import dev.proxyfox.bot.Emojis import dev.proxyfox.bot.command.menu.DiscordMenu import dev.proxyfox.bot.command.menu.DiscordScreen @@ -27,12 +29,15 @@ import dev.proxyfox.bot.scheduler import dev.proxyfox.command.CommandContext import dev.proxyfox.command.NodeActionParam import dev.proxyfox.command.menu.CommandMenu +import dev.proxyfox.command.menu.CommandScreen import dev.proxyfox.command.node.CommandNode import dev.proxyfox.command.node.builtin.int import dev.proxyfox.command.node.builtin.string +import dev.proxyfox.common.ceilDiv import dev.proxyfox.database.database import dev.proxyfox.database.records.misc.ProxiedMessageRecord import dev.proxyfox.database.records.system.SystemRecord +import kotlin.math.min import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes @@ -138,6 +143,115 @@ abstract class DiscordContext(override val value: T) : CommandContext() { } } } + + suspend fun pager( + values: List, + pageSize: Int, + embedBuilder: suspend EmbedBuilder.(String) -> Unit, + getString: suspend T.() -> String, + private: Boolean + ) { + val pages = ceilDiv(values.size, pageSize) + var page = 0 + + interactionMenu(private) { + lateinit var default: CommandScreen + val select = "select" { + this as DiscordScreen + select("page") { + page = it[0].toInt() + setScreen(default) + } + + onInit { + edit { + content = "Select a page" + embeds = mutableListOf() + actionRow { + stringSelect("page") { + for (i in 0 until pages) { + option("${i+1}", "$i") + } + } + } + } + } + } + default = default { + this as DiscordScreen + val update: suspend () -> Unit = { + edit { + content = null + embeds = mutableListOf() + embed { + embedBuilder("${page+1} / $pages") + var str = "" + for (i in page * pageSize until min(page * pageSize + pageSize, values.size)) { + str += values[i]!!.getString() + } + description = str + } + actionRow { + interactionButton(ButtonStyle.Primary, "skipToFirst") { + emoji = Emojis.rewind + } + interactionButton(ButtonStyle.Primary, "back") { + emoji = Emojis.last + } + interactionButton(ButtonStyle.Primary, "next") { + emoji = Emojis.next + } + interactionButton(ButtonStyle.Primary, "skipToLast") { + emoji = Emojis.fastforward + } + } + actionRow { + interactionButton(ButtonStyle.Secondary, "select") { + emoji = Emojis.numbers + label = "Select Page" + } + interactionButton(ButtonStyle.Danger, "close") { + emoji = Emojis.multiply + label = "Close" + } + } + } + } + + button("skipToFirst") { + page = 0 + update() + } + button("back") { + page-- + if (page < 0) page = 0 + update() + } + button("next") { + page++ + if (page >= pages) page = pages-1 + update() + } + button("skipToLast") { + page = pages-1 + update() + } + button("select") { + setScreen(select) + } + button("close") { + edit { + content = "Pager closed." + embeds = mutableListOf() + components = mutableListOf() + } + close() + } + + onInit(update) + } + } + } } // Get a DiscordContext. diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt index 9f3c9bd7..ae4bd375 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/menu/DiscordMessageMenu.kt @@ -31,7 +31,7 @@ class DiscordMessageMenu(val message: Message, val userId: Snowflake) : DiscordM buttonInteract(this) }, kord.onlyIf({ interaction.message.id }, message.id) { - + selectInteract(this) } ) ) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt deleted file mode 100644 index e60ca865..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/Pager.kt +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (c) 2022-2023, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.prompts - -import dev.kord.common.entity.ButtonStyle -import dev.kord.common.entity.Snowflake -import dev.kord.core.behavior.MessageBehavior -import dev.kord.core.behavior.channel.MessageChannelBehavior -import dev.kord.core.behavior.channel.createMessage -import dev.kord.core.behavior.edit -import dev.kord.core.behavior.interaction.respondEphemeral -import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior -import dev.kord.core.behavior.interaction.response.edit -import dev.kord.core.behavior.interaction.updateEphemeralMessage -import dev.kord.core.behavior.interaction.updatePublicMessage -import dev.kord.core.behavior.reply -import dev.kord.core.builder.components.emoji -import dev.kord.core.entity.Message -import dev.kord.core.entity.ReactionEmoji -import dev.kord.core.event.interaction.ButtonInteractionCreateEvent -import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent -import dev.kord.core.event.message.MessageCreateEvent -import dev.kord.core.event.message.ReactionAddEvent -import dev.kord.core.on -import dev.kord.rest.builder.component.ActionRowBuilder -import dev.kord.rest.builder.component.SelectOptionBuilder -import dev.kord.rest.builder.component.options -import dev.kord.rest.builder.message.EmbedBuilder -import dev.kord.rest.builder.message.create.embed -import dev.kord.rest.builder.message.modify.embed -import dev.proxyfox.bot.kord -import dev.proxyfox.common.ceilDiv -import java.util.* -import kotlin.math.max -import kotlin.math.min -import kotlin.time.Duration.Companion.minutes - -// Created 2022-15-10T08:37:40 - -// TODO: Move to DiscordContext -/** - * @author Ampflower - * @since ${version} - **/ -class Pager( - runner: Snowflake, - reference: Message, - private val list: List, - private val pageSize: Int, - private val pages: Int = ceilDiv(list.size, pageSize), - private val embed: suspend EmbedBuilder.(String) -> Unit, - private val transform: suspend (T) -> String, -) : TimedPrompt( - runner, - reference, - 5.minutes, -) { - private var page = 0 - private var selector: ActiveSelector? = null - - init { - jobs = listOf( - kord.on(consumer = this::onInteraction), - kord.on(consumer = this::onReaction), - ) - } - - private suspend fun onInteraction(event: ButtonInteractionCreateEvent) = event.run { - val message = interaction.message - val user = interaction.user - if (user.id == runner && message == this@Pager.reference) { - when (interaction.componentId) { - "skipToFirst" -> page(0) - "back" -> page(page - 1) - "next" -> page(page + 1) - "skipToLast" -> page(pages - 1) - "selection" -> { - val uuid = UUID.randomUUID().toString() - selector = EphemeralSelector(this@Pager, interaction.respondEphemeral { - content = "Which page would you like to go to?" - components += ActionRowBuilder().apply { - stringSelect(uuid) { - val opts = pages / 25 - options += if (opts != 0) { - (0..24).asSequence().map { (it * opts).toString() } - .map { SelectOptionBuilder(it, it) } - } else { - (1..pages).asSequence().map(Int::toString).map { SelectOptionBuilder(it, it) } - } - placeholder = (page + 1).toString() - } - } - }, uuid) - } - - "close" -> { - // Kord's updatePublicMessage implementation is broken, see kordlib/kord#701 - interaction.deferPublicMessageUpdate() - close() - } - } - } - } - - private suspend fun onReaction(event: ReactionAddEvent) = event.run { - if (userId == runner && messageId == reference.id && channelId == reference.channelId) { - when (emoji.name) { - "⏪" -> page(0) - // It's the same emoji twice for both branches, - // just the other has `\uFEOF` at the end. - "⬅", "⬅\uFE0F" -> page(page - 1) - "➡", "➡\uFE0F" -> page(page + 1) - "⏩" -> page(pages - 1) - "\uD83D\uDD22" -> { - selector = LegacySelector(this@Pager, reference.reply { - content = "Which page would you like to go to?" - components += ActionRowBuilder().apply { - stringSelect("menu") { - val opts = pages / 25 - options += if (opts != 0) { - (0..24).asSequence().map { (it * opts + 1).toString() } - .map { SelectOptionBuilder(it, it) } - } else { - (1..pages).asSequence().map(Int::toString).map { SelectOptionBuilder(it, it) } - } - placeholder = (page + 1).toString() - } - } - components += cancel - }) - } - - "❌", "✖", "✖\uFE0F" -> { - close() - } - } - if (guildId != null) message.deleteReaction(userId, emoji) - } - } - - private suspend fun ButtonInteractionCreateEvent.page(inPage: Int) { - // Kord's updatePublicMessage implementation is broken, see kordlib/kord#701 - interaction.deferPublicMessageUpdate() - this@Pager.page(inPage) - } - - private suspend fun page(inPage: Int) { - bump() - page = min(max(0, inPage), pages - 1) - reference.edit { - embed { - embed("${page + 1} / $pages") - description = buildString(list, page, pageSize, transform) - } - } - } - - override suspend fun close() { - reference.edit { components = mutableListOf() } - closeInternal() - } - - override suspend fun closeInternal() { - selector?.close() - super.closeInternal() - } - - private interface ActiveSelector { - suspend fun close() - } - - private class EphemeralSelector( - val pager: Pager<*>, - val message: EphemeralMessageInteractionResponseBehavior, - val `discord is a massive pain by not returning the message that would eliminate the need for this`: String, - ) : ActiveSelector { - private val ephemeralListener = message.kord.on { - if (interaction.componentId == `discord is a massive pain by not returning the message that would eliminate the need for this`) { - pager.page(interaction.values[0].toInt() - 1) - interaction.updateEphemeralMessage { content = "Pager changed to page ${pager.page + 1}." } - closeInternal() - } - } - - private val messageListener = message.kord.on { - if (pager.runner == message.author?.id && pager.reference.channel == message.channel) { - val page = message.content.toIntOrNull() ?: return@on - pager.page(page - 1) - message.delete("Intercepted by pager.") - this@EphemeralSelector.message.edit { - content = "Pager changed to page ${pager.page + 1}." - components = mutableListOf() - } - closeInternal() - } - } - - override suspend fun close() { - message.edit { - content = "Pager closed." - components = mutableListOf() - } - closeInternal() - } - - fun closeInternal() { - ephemeralListener.cancel() - messageListener.cancel() - pager.selector = null - } - } - - private class LegacySelector( - val pager: Pager<*>, - val message: MessageBehavior, - ) : ActiveSelector { - private val primaryListener = message.kord.on { - val message = interaction.message - val user = interaction.user - if (user.id == pager.runner && message == this@LegacySelector.message) { - pager.page(interaction.values[0].toInt() - 1) - interaction.updatePublicMessage { - content = "Pager changed to page ${pager.page + 1}." - components += freeDelete - } - closeInternal() - } - } - - private val messageListener = message.kord.on { - if (pager.runner == message.author?.id && pager.reference.channel == message.channel) { - val page = message.content.toIntOrNull() ?: return@on - pager.page(page - 1) - message.delete("Intercepted by pager.") - this@LegacySelector.message.edit { - content = "Pager changed to page ${pager.page + 1}." - components = mutableListOf(freeDelete) - } - closeInternal() - } - } - - override suspend fun close() { - message.delete() - closeInternal() - } - - fun closeInternal() { - primaryListener.cancel() - messageListener.cancel() - pager.selector = null - } - } - - companion object { - private val cancel = ActionRowBuilder().apply { - interactionButton(ButtonStyle.Secondary, "cancel") { - emoji(ReactionEmoji.Unicode("✖")) - label = "Cancel" - } - } - - private val freeDelete = ActionRowBuilder().apply { - interactionButton(ButtonStyle.Secondary, "free-delete") { - emoji(ReactionEmoji.Unicode("✖")) - label = "Delete message" - } - } - - suspend fun build( - runner: Snowflake, - channel: MessageChannelBehavior, - list: List, - pageSize: Int, - embed: suspend EmbedBuilder.(String) -> Unit, - transform: suspend (T) -> String, - ): Pager { - val pages = ceilDiv(list.size, pageSize) - val message = channel.createMessage { - embed { - embed("1 / $pages") - description = buildString(list, 0, pageSize, transform) - } - components += ActionRowBuilder().apply { - interactionButton(ButtonStyle.Primary, "skipToFirst") { emoji(ReactionEmoji.Unicode("⏪")) } - interactionButton(ButtonStyle.Primary, "back") { emoji(ReactionEmoji.Unicode("⬅")) } - interactionButton(ButtonStyle.Primary, "next") { emoji(ReactionEmoji.Unicode("➡")) } - interactionButton(ButtonStyle.Primary, "skipToLast") { emoji(ReactionEmoji.Unicode("⏩")) } - } - components += ActionRowBuilder().apply { - interactionButton(ButtonStyle.Secondary, "selection") { - emoji(ReactionEmoji.Unicode("\uD83D\uDD22")) - label = "Select Page" - } - interactionButton(ButtonStyle.Danger, "close") { - // :heavy_multiplication_x: is used instead of :x: - // due to providing better contrast in almost every case. - emoji(ReactionEmoji.Unicode("✖️")) - label = "Close" - } - } - } - return Pager(runner, message, list, pageSize, pages, embed, transform) - } - - private suspend fun buildString( - list: List, - page: Int, - size: Int, - transform: suspend (T) -> String, - ) = buildString { - for (i in page * size until min(page * size + size, list.size)) { - append(transform(list[i])) - } - } - } -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedPrompt.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedPrompt.kt deleted file mode 100644 index 63120962..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/prompts/TimedPrompt.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2022-2023, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.prompts - -import dev.kord.common.entity.Snowflake -import dev.kord.core.entity.Message -import dev.proxyfox.bot.scope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.datetime.Clock -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes - -// Created 2022-17-10T05:20:36 - -/** - * @author Ampflower - * @since ${version} - **/ -abstract class TimedPrompt( - protected val runner: Snowflake, - protected val reference: Message, - timeout: Duration = oneMinute, -) { - protected lateinit var jobs: Collection - private var lastUpdate = Clock.System.now() - - private val timerJob = scope.launch { - val delay = maxOf(timeout, oneMinute) - var now = Clock.System.now() - var comparison = lastUpdate + delay - - // Busy wait for cancelling the prompt. - while (comparison > now) { - delay(maxOf(comparison - now, oneMinute)) - now = Clock.System.now() - comparison = lastUpdate + delay - } - - close() - } - - abstract suspend fun close() - - protected fun bump() { - lastUpdate = Clock.System.now() - } - - protected open suspend fun closeInternal() { - jobs.forEach(Job::cancel) - timerJob.cancel() - } - - companion object { - val oneMinute = 1.minutes - } -} \ No newline at end of file From a83022f1152efcaf0e56fa15f1191dbf4c76cb09 Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 5 Jun 2023 17:20:19 -0500 Subject: [PATCH 130/137] Send file link as well on deleted system --- .../src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt | 2 +- .../src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index 0ecaaa1d..ae551a51 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -1203,7 +1203,7 @@ object MiscCommands : CommandRegistrar { null, NamedFile("system.json", ChannelProvider { export.byteInputStream().toByteReadChannel() }) ) - message.channel.createMessage(message.attachments.elementAt(0).url) + message.channel.createMessage(message.attachments.first().url) ctx.respondSuccess("Check your DMs~") return true } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index 64456a01..b3589214 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -704,10 +704,11 @@ object SystemCommands : CommandRegistrar { "The data will be lost forever (A long time!)", yes = "Delete system" to { val export = Exporter.export(ctx.getUser()!!.id.value) - ctx.respondFiles( + val message = ctx.respondFiles( null, NamedFile("system.json", ChannelProvider { export.byteInputStream().toByteReadChannel() }) ) + ctx.getChannel(true).createMessage(message.attachments.first().url) database.dropSystem(ctx.getUser()!!) content = "System deleted." }, From e2986fceebcc1bfc1e745b43d037bd3a39f1072a Mon Sep 17 00:00:00 2001 From: Octal Date: Mon, 5 Jun 2023 17:40:27 -0500 Subject: [PATCH 131/137] rip I used the wrong thing lol --- .../src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt | 2 +- .../src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt index 8b89f36d..563aeaf4 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SwitchCommands.kt @@ -253,7 +253,7 @@ object SwitchCommands : CommandRegistrar { ctx.pager( switches, 20, - { title = "[$it] Front history of ${system.showName}" }, + { title = "[$it] Front history of ${system.name ?: system.id}" }, { membersAsString("**", "**") + " ()\n" }, false ) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index b3589214..add4f440 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -506,7 +506,7 @@ object SystemCommands : CommandRegistrar { it.name }.map { m -> m to proxies.filter { it.memberId == m.id } }, 20, - { page -> system(system, nameTransformer = { "[$page] Members of $this" }) }, + { page -> system(system, nameTransformer = { "[$page] Members of ${system.name ?: system.id}" }) }, { val str = if (second.isNotEmpty()) second.joinToString( "\uFEFF``, ``\uFEFF", From b324dac57927c9719d418937367e674b68126525 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Mon, 10 Jul 2023 23:16:18 -0500 Subject: [PATCH 132/137] Update dependencies --- gradle/libs.versions.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 043e107a..d0d41aa0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -guava = "31.1-jre" -logback = "1.4.7" -kord = "0.9.x-SNAPSHOT" -kotlin = "1.7.21" -kotlinx_coroutines = "1.6.4" +guava = "32.1.1-jre" +logback = "1.4.8" +kord = "0.10.0" +kotlin = "1.9.0" +kotlinx_coroutines = "1.7.2" # ProxyFox Libraries proxyfox_command = "1.7" From 0b07b1b7198325718b842b58c6ced61e26cd9d4b Mon Sep 17 00:00:00 2001 From: Ampflower Date: Mon, 10 Jul 2023 23:18:52 -0500 Subject: [PATCH 133/137] Improve error handling for overflowing messages --- .../main/kotlin/dev/proxyfox/bot/BotUtil.kt | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt index 1e8b4cfc..97828b08 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/BotUtil.kt @@ -284,21 +284,31 @@ suspend fun handleError(err: Throwable, channel: MessageChannelBehavior) { val reason = err.message?.replace(webhook, "[WEBHOOK]")?.replace(token, "[TOKEN]") var cause = "" err.stackTrace.forEach { - if (it.className.startsWith("dev.proxyfox")) - cause += " at $it\n" + if (it.className.startsWith("dev.proxyfox.")) + cause += " at ${it.pfString()}\n" } for (suppressed in err.suppressed) { var supCause = "" val supReason = suppressed.message?.replace(webhook, "[WEBHOOK]")?.replace(token, "[TOKEN]") suppressed.stackTrace.forEach { - if (it.className.startsWith("dev.proxyfox")) - supCause += " at $it\n" + if (it.className.startsWith("dev.proxyfox.")) + supCause += " at ${it.pfString()}\n" } cause += " Suppressed: ${suppressed.javaClass.name}: $supReason\n$supCause" } - channel.createMessage( - "An unexpected error occurred. Report this to us at https://discord.gg/q3yF8ay9V7\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause```" - ) + try { + channel.createMessage( + "An unexpected error occurred. Report this to us at https://discord.gg/q3yF8ay9V7\nTimestamp: `$timestamp`\n```\n${err.javaClass.name}: $reason\n$cause".let { + if (it.length > 1997) { + it.substring(0, it.lastIndexOf('\n', 1997)) + "```" + } else { + "$it```" + } + } + ) + } catch (any: Exception) { + logger.warn("Cannot send the error message to sender", any) + } if (err is DebugException) return if (errorChannel == null && errorChannelId != null) errorChannel = kord.getChannel(errorChannelId) as TextChannel @@ -313,6 +323,16 @@ suspend fun handleError(err: Throwable, channel: MessageChannelBehavior) { } } +fun StackTraceElement.pfString(): String { + val names = className.split('.') + val clazz = names.last() + var path = "" + + names.dropLast(1).forEach { path += it[0] + "." } + + return "$path.$clazz($fileName:$lineNumber)" +} + fun findUnixValue(args: Array, key: String): String? { for (i in args.indices) { if (args[i].startsWith(key)) { @@ -321,6 +341,7 @@ fun findUnixValue(args: Array, key: String): String? { } return null } + fun hasUnixValue(args: Array, key: String): Boolean { for (i in args.indices) { if (args[i].startsWith(key)) { From b13ab669ac2933b419e869fbac81c6010c125cfc Mon Sep 17 00:00:00 2001 From: Ampflower Date: Mon, 10 Jul 2023 23:19:18 -0500 Subject: [PATCH 134/137] Don't require name argument on member command --- .../src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 6933aa8f..71dc51bb 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -122,7 +122,7 @@ object MemberCommands : CommandRegistrar { } access("member", "servernick") { member() - name() + name(required = false) guild() raw() clear() From 8dbd2871472fa6cbdd919287fb8522ca0fa60755 Mon Sep 17 00:00:00 2001 From: Octal Date: Tue, 11 Jul 2023 23:46:36 -0500 Subject: [PATCH 135/137] :3 --- .../main/kotlin/dev/proxyfox/api/ApiMain.kt | 1 + .../dev/proxyfox/api/models/ApiError.kt | 4 +- .../kotlin/dev/proxyfox/api/models/Coffee.kt | 30 +++++ .../dev/proxyfox/api/routes/CoffeeRoutes.kt | 104 ++++++++++++++++++ 4 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/models/Coffee.kt create mode 100644 modules/api/src/main/kotlin/dev/proxyfox/api/routes/CoffeeRoutes.kt diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt index 2714a3ad..527fb3d0 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/ApiMain.kt @@ -36,6 +36,7 @@ object ApiMain { switchRoutes() messageRoutes() tokenRoutes() + coffeeRoutes() } } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/ApiError.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/ApiError.kt index 0f4e2536..e9e0bcd4 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/ApiError.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/ApiError.kt @@ -12,6 +12,6 @@ import kotlinx.serialization.Serializable @Serializable data class ApiError( - val code: Int, - val message: String + val code: Int, + val message: String ) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Coffee.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Coffee.kt new file mode 100644 index 00000000..c1ac4fb8 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Coffee.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.models + +import kotlinx.serialization.Serializable + +@Serializable +data class Coffee( + val id: String, + var state: BrewState, +) + +@Serializable +enum class BrewState { + STARTING, + BREWING, + READY; + + fun advance(): BrewState = when (this) { + STARTING -> BREWING + BREWING -> READY + READY -> READY + } +} \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/CoffeeRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/CoffeeRoutes.kt new file mode 100644 index 00000000..71cdff78 --- /dev/null +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/CoffeeRoutes.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.api.routes + +import dev.proxyfox.api.models.ApiError +import dev.proxyfox.api.models.BrewState +import dev.proxyfox.api.models.Coffee +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +var coffees = HashMap() + +fun generateIdString(): String { + val chars = 'A'..'Z' + var str = ""; + for (i in 0..5) { + str += chars.random() + } + + return str +} + +fun Route.coffeeRoutes() { + route("/coffee") { + method(CoffeeMethods.Brew) { handle { + if (Math.random() < 0.0625) { + call.respond(CoffeeCodes.Teapot, ApiError(418, "I'm a teapot!")) + return@handle + } + + val id = generateIdString() + + if (coffees.containsKey(id)) { + call.respond(CoffeeCodes.Teapot, ApiError(418, "I'm a teapot!")) + return@handle + } + + val coffee = Coffee( + id, + BrewState.STARTING + ) + + coffees[id] = coffee + + call.respond(HttpStatusCode.OK, coffee) + }} + + get("/{coffee}") { + val id = call.parameters["coffee"] + + if (id == null) { + call.respond(CoffeeCodes.Teapot, ApiError(418, "I'm a teapot!")) + return@get + } + + val coffee = coffees[id] + + if (coffee == null) { + call.respond(CoffeeCodes.Teapot, ApiError(418, "I'm a teapot!")) + return@get + } + + coffee.state = coffee.state.advance() + + call.respond(HttpStatusCode.OK, coffee) + } + + route("/{coffee}", CoffeeMethods.When) { handle { + val id = call.parameters["coffee"] + + if (id == null) { + call.respond(CoffeeCodes.Teapot, ApiError(418, "I'm a teapot!")) + return@handle + } + + val coffee = coffees[id] + + if (coffee == null) { + call.respond(CoffeeCodes.Teapot, ApiError(418, "I'm a teapot!")) + return@handle + } + + call.respond(HttpStatusCode.OK, coffee) + coffees.remove(id) + }} + } +} + +object CoffeeMethods { + val Brew = HttpMethod("BREW") + val When = HttpMethod("WHEN") +} + +object CoffeeCodes { + val Teapot = HttpStatusCode(418, "I'm a Teapot!") +} From 22b67fef0186022db95f266120d61cdcd39db7cd Mon Sep 17 00:00:00 2001 From: Octal Date: Sun, 16 Jul 2023 14:42:24 -0500 Subject: [PATCH 136/137] [no ci] start migration to pf-c 2.0 --- gradle/libs.versions.toml | 2 +- modules/bot/build.gradle.kts | 1 + .../kotlin/dev/proxyfox/bot/MessageHandler.kt | 12 +- .../proxyfox/bot/command/MemberCommands.kt | 562 +---------------- .../dev/proxyfox/bot/command/MiscCommands.kt | 583 ++---------------- .../proxyfox/bot/command/SystemCommands.kt | 2 +- .../bot/command/context/DiscordContext.kt | 4 + .../command/context/DiscordMessageContext.kt | 1 - .../bot/command/node/AttachmentNode.kt | 37 -- .../bot/command/text/MemberTextCommands.kt | 386 ++++++++++++ .../bot/command/text/MiscTextCommands.kt | 305 +++++++++ .../proxyfox/bot/command/text/PkCommands.kt | 59 ++ .../bot/command/text/TrustCommands.kt | 103 ++++ .../bot/command/types/CommandAttachment.kt | 44 ++ .../bot/command/types/CommandBoolean.kt | 46 ++ .../bot/command/types/CommandProxyMode.kt | 52 ++ .../bot/command/types/CommandSnowflake.kt | 47 ++ .../main/kotlin/dev/proxyfox/common/Util.kt | 4 + .../database/records/misc/AutoProxyMode.kt | 3 + 19 files changed, 1100 insertions(+), 1153 deletions(-) delete mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/MemberTextCommands.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/MiscTextCommands.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/PkCommands.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/TrustCommands.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandAttachment.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandBoolean.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandProxyMode.kt create mode 100644 modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandSnowflake.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d0d41aa0..5e119cac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ kotlin = "1.9.0" kotlinx_coroutines = "1.7.2" # ProxyFox Libraries -proxyfox_command = "1.7" +proxyfox_command = "2.0" pluralkt = "1.8" markt = "1.4" diff --git a/modules/bot/build.gradle.kts b/modules/bot/build.gradle.kts index bf0086ec..592fbe92 100644 --- a/modules/bot/build.gradle.kts +++ b/modules/bot/build.gradle.kts @@ -9,6 +9,7 @@ plugins { application alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.serialization) alias(libs.plugins.shadow) } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt index a3f2c720..a09359ec 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/MessageHandler.kt @@ -88,7 +88,7 @@ suspend fun MessageCreateEvent.onMessageCreate() { if (hasStickers || hasOversizedFiles || isOversizedMessage) { logger.trace( "Denying proxying {} ({}) in {} ({}) due to Discord bot constraints", - user.tag, + user.username, user.id, guild.name, guild.id @@ -118,8 +118,8 @@ suspend fun MessageUpdateEvent.onMessageUpdate() { if (hasStickers || hasOversizedFiles || isOversizedMessage) { logger.trace( - "Denying proxying {}#{} ({}) in {} ({}) due to Discord bot constraints", - authorRaw.username, authorRaw.discriminator, authorRaw.id, guild.name, guild.id + "Denying proxying {} ({}) in {} ({}) due to Discord bot constraints", + authorRaw.username, authorRaw.id, guild.name, guild.id ) return } @@ -158,7 +158,7 @@ private suspend fun handleProxying( val server = database.getOrCreateServerSettings(guild) server.proxyRole.let { if (it != 0UL && !user.asMember(guild.id).roleIds.contains(Snowflake(it))) { - logger.trace("Denying proxying {} ({}) in {} ({}) due to missing role {}", user.tag, user.id, guild.name, guild.id, it) + logger.trace("Denying proxying {} ({}) in {} ({}) due to missing role {}", user.username, user.id, guild.name, guild.id, it) return } } @@ -283,7 +283,7 @@ suspend fun ReactionAddEvent.onReactionAdd() { val user = kord.getUser(Snowflake(databaseMessage.userId)) getUser().getDmChannel().createMessage { - content = "Message by ${member.showDisplayName()} was sent by <@${databaseMessage.userId}> (${user?.tag ?: "Unknown user"})" + content = "Message by ${member.showDisplayName()} was sent by <@${databaseMessage.userId}> (${user?.username ?: "Unknown user"})" embed { val systemName = system.name ?: system.id author { @@ -394,7 +394,7 @@ suspend fun MessageCommandInteractionCreateEvent.onInteract() { interaction.respondEphemeral { content = - "Message by ${member.showDisplayName()} was sent by <@${databaseMessage.userId}> (${user?.tag ?: "Unknown user"})" + "Message by ${member.showDisplayName()} was sent by <@${databaseMessage.userId}> (${user?.username ?: "Unknown user"})" embed { val systemName = system.name ?: system.id author { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt index 71dc51bb..c5fd3244 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MemberCommands.kt @@ -14,16 +14,8 @@ import dev.kord.rest.builder.interaction.subCommand import dev.proxyfox.bot.* import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.InteractionCommandContext -import dev.proxyfox.bot.command.context.guild -import dev.proxyfox.bot.command.context.runs -import dev.proxyfox.bot.command.node.attachment +import dev.proxyfox.bot.command.text.MemberTextCommands import dev.proxyfox.command.CommandParser -import dev.proxyfox.command.NodeHolder -import dev.proxyfox.command.node.CommandNode -import dev.proxyfox.command.node.builtin.greedy -import dev.proxyfox.command.node.builtin.literal -import dev.proxyfox.command.node.builtin.string -import dev.proxyfox.command.node.builtin.unixLiteral import dev.proxyfox.common.fromColor import dev.proxyfox.common.ifBlankThenNull import dev.proxyfox.common.notBlank @@ -315,560 +307,10 @@ object MemberCommands : CommandRegistrar { } } - suspend fun > CommandNode.registerMemberCommands( - getMem: suspend DiscordContext.() -> String, - getSys: suspend DiscordContext.() -> SystemRecord? - ) { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - access(this, system, member) - } - - literal("remame", "name") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - rename(this, system, member, null, false) - } - unixLiteral("raw") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - rename(this, system, member, null, true) - } - } - greedy("name") { getName -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - rename(this, system, member, getName(), false) - } - } - } - - literal("nickname", "nick", "displayname", "dn") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - nickname(this, system, member, null, false, false) - } - unixLiteral("clear", "remove") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - nickname(this, system, member, null, false, true) - } - } - unixLiteral("raw") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - nickname(this, system, member, null, true, false) - } - } - greedy("name") { getName -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val name = getName() - nickname(this, system, member, name, false, false) - } - } - } - - literal("servername", "servernick", "sn") { - guild { getGuildId -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) - servername(this, system, serverMember!!, null, false, false) - } - unixLiteral("clear", "remove") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) - servername(this, system, serverMember!!, null, false, true) - } - } - unixLiteral("raw") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) - servername(this, system, serverMember!!, null, true, false) - } - } - greedy("name") { getName -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) - servername(this, system, serverMember!!, getName(), false, false) - } - } - } - } - - literal("description", "desc", "d") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - description(this, system, member, null, false, false) - } - unixLiteral("raw") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - description(this, system, member, null, true, false) - } - } - unixLiteral("clear", "remove") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - description(this, system, member, null, false, true) - } - } - greedy("description") { getDesc -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - description(this, system, member, getDesc(), false, false) - } - } - } - - literal("avatar", "pfp") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - avatar(this, system, member, null, false) - } - unixLiteral("clear", "remove") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - avatar(this, system, member, null, true) - } - } - attachment("avatar") { getAvatar -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - avatar(this, system, member, getAvatar().url, false) - } - } - string("avatar") { getAvatar -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - avatar(this, system, member, getAvatar(), false) - } - } - } - - literal("serveravatar", "serverpfp", "sp", "sa") { - guild { getGuildId -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) - serverAvatar(this, system, serverMember!!, null, false) - } - unixLiteral("clear", "remove") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) - serverAvatar(this, system, serverMember!!, null, true) - } - } - attachment("avatar") { getAvatar -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) - serverAvatar(this, system, serverMember!!, getAvatar().url, false) - } - } - greedy("avatar") { getAvatar -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverMember = - database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id) - serverAvatar(this, system, serverMember!!, getAvatar(), false) - } - } - } - } - - literal("autoproxy", "ap") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - autoproxy(this, system, member, null) - } - // TODO: BooleanNode - literal("disable", "off", "false", "0") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - autoproxy(this, system, member, false) - } - } - literal("enable", "on", "true", "1") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - autoproxy(this, system, member, true) - } - } - } - - literal("proxy", "p") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - proxy(this, system, member, null) - } - - literal("remove", "rem", "delete", "del") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - removeProxy(this, system, member, false, null) - } - greedy("proxy") { getProxy -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - extractProxyFromTag(this, getProxy()) ?: return@runs false - val proxy = database.fetchProxyTagFromMessage(getUser(), getProxy()) - proxy ?: run { - respondFailure("Proxy tag doesn't exist in this member.") - return@runs false - } - if (proxy.memberId != member.id) { - respondFailure("Proxy tag doesn't exist in this member.") - return@runs false - } - removeProxy(this, system, member, false, proxy) - } - } - } - - literal("add", "create") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - proxy(this, system, member, null) - } - - greedy("proxy") { getProxy -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false - proxy(this, system, member, proxy) - } - } - } - - greedy("proxy") { getProxy -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val proxy = extractProxyFromTag(this, getProxy()) ?: return@runs false - proxy(this, system, member, proxy) - } - } - } - - literal("pronouns") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - pronouns(this, system, member, null, false, false) - } - unixLiteral("clear", "remove") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - pronouns(this, system, member, null, false, true) - } - } - unixLiteral("raw") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - pronouns(this, system, member, null, true, false) - } - } - greedy("pronouns") { getPronouns -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - pronouns(this, system, member, getPronouns(), false, false) - } - } - } - - literal("color", "colour", "c") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - color(this, system, member, null) - } - greedy("color") { getColor -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - color(this, system, member, getColor().toColor()) - } - } - } - - literal("birthday","bday","birth","bd") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - birthday(this, system, member, null, false) - } - unixLiteral("clear", "remove") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - birthday(this, system, member, null, true) - } - } - greedy("birthday") { getBirthday -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - birthday(this, system, member, tryParseLocalDate(getBirthday())?.first, false) - } - } - } - literal("delete", "remove", "del", "rem") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - delete(this, system, member) - } - } - } - - suspend fun > NodeHolder.registerBaseMemberCommands(getSys: suspend DiscordContext.() -> SystemRecord?) { - literal("member", "m") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - empty(this) - } - - string("member") { - registerMemberCommands(it, getSys) - } - literal("delete", "remove", "del") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - delete(this, system, null) - } - greedy("member") { getMem -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - delete(this, system, member) - } - } - } - literal("create", "add", "new", "c") { - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - create(this, system, null) - } - greedy("member") { getMem -> - runs { - val system = getSys() - if (!checkSystem(this, system)) return@runs false - create(this, system, getMem()) - } - } - } - } - } - override val displayName: String = "Member" override suspend fun CommandParser>.registerTextCommands() { - registerBaseMemberCommands { - database.fetchSystemFromUser(getUser()) - } + this += MemberTextCommands } suspend fun empty(ctx: DiscordContext): Boolean { diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt index ae551a51..45ee6a3a 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/MiscCommands.kt @@ -21,11 +21,12 @@ import dev.kord.rest.builder.message.modify.actionRow import dev.proxyfox.bot.* import dev.proxyfox.bot.command.context.* import dev.proxyfox.bot.command.menu.DiscordScreen -import dev.proxyfox.bot.command.node.attachment +import dev.proxyfox.bot.command.text.MiscTextCommands +import dev.proxyfox.bot.command.text.PkCommands +import dev.proxyfox.bot.command.text.TrustCommands import dev.proxyfox.bot.webhook.GuildMessage import dev.proxyfox.bot.webhook.WebhookUtil import dev.proxyfox.command.CommandParser -import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.* import dev.proxyfox.common.annotations.DontExpose import dev.proxyfox.database.database @@ -53,7 +54,7 @@ import kotlin.math.floor * @author Oliver * */ object MiscCommands : CommandRegistrar { - private val roleMatcher = Regex("\\d+") + val roleMatcher = Regex("\\d+") var infoInteractionExecutors: HashMap Boolean> = hashMapOf() var moderationInteractionExecutors: HashMap Boolean> = hashMapOf() var managementInteractionExecutors: HashMap Boolean> = hashMapOf() @@ -306,526 +307,12 @@ object MiscCommands : CommandRegistrar { } override suspend fun CommandParser>.registerTextCommands() { - literal("import") { - runs { - import(this, null) - } - attachment("file") { getFile -> - runs { - import(this, getFile().url) - } - } - greedy("file") { getFile -> - runs { - import(this, getFile()) - } - } - } - literal("export") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - export(this) - } - } - literal("time") { - runs(::time) - } - literal("help") { - responds(help) - } - literal("explain") { - responds(explain) - } - literal("invite") { - responds(invite) - } - literal("source") { - responds(source) - } - literal("proxy", "p") { - guild { getGuildId -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) - serverProxy(this, serverSystem, null) - } - literal("on", "enable") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) - serverProxy(this, serverSystem, true) - } - } - literal("off", "disable") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) - serverProxy(this, serverSystem, false) - } - } - } - } - literal("autoproxy", "ap") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - proxy(this, system, null, null) - } - literal("off", "disable", "o") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - proxy(this, system, AutoProxyMode.OFF, null) - } - } - literal("latch", "l") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - proxy(this, system, AutoProxyMode.LATCH, null) - } - } - literal("front", "f") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - proxy(this, system, AutoProxyMode.FRONT, null) - } - } - greedy("member") { getMem -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - proxy(this, system, AutoProxyMode.MEMBER, member) - } - } - } - literal("serverautoproxy", "sap") { - guild { getGuildId -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) - serverAutoProxy(this, serverSystem, null, null) - } - literal("off", "disable", "o") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) - serverAutoProxy(this, serverSystem, AutoProxyMode.OFF, null) - } - } - literal("on", "enable", "fallback", "fb") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) - serverAutoProxy(this, serverSystem, AutoProxyMode.FALLBACK, null) - } - } - literal("latch", "l") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) - serverAutoProxy(this, serverSystem, AutoProxyMode.LATCH, null) - } - } - literal("front", "f") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) - serverAutoProxy(this, serverSystem, AutoProxyMode.FRONT, null) - } - } - greedy("member") { getMem -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member)) return@runs false - val guildId = getGuildId() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - val guild = kord.getGuildOrNull(guildId) ?: run { - respondFailure("Cannot find server. Am I in it?") - return@runs false - } - val serverSystem = database.getOrCreateServerSettingsFromSystem(guild, system.id) - serverAutoProxy(this, serverSystem, AutoProxyMode.MEMBER, member) - } - } - } - } - literal("role") { - runs { - role(this, null, false) - } - unixLiteral("clear", "remove") { - runs { - role(this, null, true) - } - } - greedy("role") { getRole -> - runs { - role(this, getRole(), false) - } - } - } - literal("moddelay") { - runs { - val guild = getGuild() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - delay(this, database.getOrCreateServerSettings(guild), null) - } - greedy("delay") { getDelay -> - runs { - val guild = getGuild() ?: run { - respondFailure("Command not ran in server.") - return@runs false - } - delay(this, database.getOrCreateServerSettings(guild), getDelay()) - } - } - } - literal("forcetag", "requiretag") { - runs { - forceTag(this, null) - } - literal("on", "true", "enable", "1") { - runs { - forceTag(this, true) - } - } - literal("off", "false", "disable", "0") { - runs { - forceTag(this, false) - } - } - } - literal("delete", "del") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system, true)) return@runs false - deleteMessage(this, system, null) - } - int("message") { getMessage -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system, true)) return@runs false - deleteMessage(this, system, Snowflake(getMessage())) - } - } - } - literal("reproxy", "rp") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system, true)) return@runs false - reproxyMessage(this, system, null, null) - } - int("message") { getMessage -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system, true)) return@runs false - reproxyMessage(this, system, Snowflake(getMessage()), null) - } - greedy("member") { getMem -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system, true)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member, true)) return@runs false - reproxyMessage(this, system, Snowflake(getMessage()), member) - } - } - } - greedy("member") { getMem -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system, true)) return@runs false - val member = database.findMember(system.id, getMem()) - if (!checkMember(this, member, true)) return@runs false - reproxyMessage(this, system, null, member) - } - } - } - literal("info", "i") { - runs { - fetchMessageInfo(this, null) - } - int("message") { getMessage -> - runs { - fetchMessageInfo(this, Snowflake(getMessage())) - } - } - } - literal("ping", "p") { - runs { - pingMessageAuthor(this, null) - } - int("message") { getMessage -> - runs { - pingMessageAuthor(this, Snowflake(getMessage())) - } - } - } - literal("edit", "e") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system, true)) return@runs false - editMessage(this, system, null, null) - } - int("message") { getMessage -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system, true)) return@runs false - editMessage(this, system, Snowflake(getMessage()), null) - } - - greedy("content") { getContent -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system, true)) return@runs false - editMessage(this, system, Snowflake(getMessage()), getContent()) - } - } - } - greedy("content") { getContent -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system, true)) return@runs false - editMessage(this, system, null, getContent()) - } - } - } - literal("channel", "c") { - responds("Please provide a channel subcommand") - literal("proxy", "p") { - runs { - channelProxy(this, null, null) - } - string("channel") { getChannelMention -> - runs { - channelProxy(this, getChannelMention(), null) - } - literal("on", "true", "enable", "1") { - runs { - channelProxy(this, getChannelMention(), true) - } - } - literal("off", "false", "disable", "0") { - runs { - channelProxy(this, getChannelMention(), false) - } - } - } - } - } - literal("debug") { - runs(::debug) - } - literal("fox") { - runs(::getFox) - } - literal("token", "t") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - token(this, system) - } - } - literal("transfer") { - responds("Please provide a token to transfer from") - greedy("transfer") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (system != null) { - respondFailure("You can only run this command when you have no system registered.") - return@runs false - } - val token = database.fetchToken(it()) - if (token == null) { - respondFailure("Token not found.") - return@runs false - } - if (token.type != TokenType.SYSTEM_TRANSFER) { - respondFailure("Token isn't a transfer token.") - return@runs false - } - - transfer(this, token) - } - } - } - literal("trust") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - respondFailure("Please provide a user to perform this action on") - false - } - int("user") { getId -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - trust(this, system, getId(), null) - } - literal("none", "remove", "clear") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - trust(this, system, getId(), TrustLevel.NONE) - } - } - literal("access", "see", "view") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - trust(this, system, getId(), TrustLevel.ACCESS) - } - } - literal("member", "m") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - trust(this, system, getId(), TrustLevel.MEMBER) - } - } - literal("switch", "sw") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - trust(this, system, getId(), TrustLevel.SWITCH) - } - } - literal("full", "all", "everything") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - trust(this, system, getId(), TrustLevel.FULL) - } - } - } - } - literal("pluralkit", "pk") { - literal("pull", "get", "download", "import") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - syncPk(this, system, false) - } - } - - literal("push", "set", "upload", "export") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - syncPk(this, system, true) - } - } - - literal("token") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - pkToken(this, system, null, false) - } - - literal("clear", "reset", "remove") { - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - pkToken(this, system, null, true) - } - } - - greedy("token") { getToken -> - runs { - val system = database.fetchSystemFromUser(getUser()) - if (!checkSystem(this, system)) return@runs false - pkToken(this, system, getToken(), false) - } - } - } - } + this += MiscTextCommands + this += TrustCommands + this += PkCommands } - private suspend fun transfer(ctx: DiscordContext, token: TokenRecord): Boolean { + suspend fun transfer(ctx: DiscordContext, token: TokenRecord): Boolean { val system = database.fetchSystemFromId(token.systemId)!! system.users.forEach { val user = database.getOrCreateUser(it) @@ -850,7 +337,7 @@ object MiscCommands : CommandRegistrar { } @OptIn(DontExpose::class) - private suspend fun syncPk(ctx: DiscordContext, system: SystemRecord, upload: Boolean): Boolean { + suspend fun syncPk(ctx: DiscordContext, system: SystemRecord, upload: Boolean): Boolean { ctx.respondPlain("Pk sync progress:", true) val updater = CommUpdater(ctx.getChannel(true)) @@ -884,7 +371,7 @@ object MiscCommands : CommandRegistrar { } @OptIn(DontExpose::class) - private suspend fun pkToken( + suspend fun pkToken( ctx: DiscordContext, system: SystemRecord, token: String?, @@ -918,7 +405,8 @@ object MiscCommands : CommandRegistrar { return true } - private suspend fun forceTag(ctx: DiscordContext, enabled: Boolean?): Boolean { + // TODO: Allow user to specify guild + suspend fun forceTag(ctx: DiscordContext, enabled: Boolean?): Boolean { val server = database.getOrCreateServerSettings(ctx.getGuild() ?: run { ctx.respondFailure("You are not in a server.") return false @@ -941,7 +429,7 @@ object MiscCommands : CommandRegistrar { return true } - private suspend fun getFox(ctx: DiscordContext): Boolean { + suspend fun getFox(ctx: DiscordContext): Boolean { ctx.respondEmbed { val fox = FoxFetch.fetch() title = "**Link**" @@ -951,7 +439,7 @@ object MiscCommands : CommandRegistrar { return true } - private suspend fun trust( + suspend fun trust( ctx: DiscordContext, system: SystemRecord, user: ULong, @@ -975,7 +463,7 @@ object MiscCommands : CommandRegistrar { return true } - private suspend fun token(ctx: DiscordContext, system: SystemRecord): Boolean { + suspend fun token(ctx: DiscordContext, system: SystemRecord): Boolean { ctx.interactionMenu(true) { val create = "create" { this as DiscordScreen @@ -1092,7 +580,7 @@ object MiscCommands : CommandRegistrar { return true } - private suspend fun debug(ctx: DiscordContext): Boolean { + suspend fun debug(ctx: DiscordContext): Boolean { val shardid = ctx.getGuild()?.id?.value?.toShard() ?: 0 ctx.respondEmbed { title = "ProxyFox Debug" @@ -1175,7 +663,7 @@ object MiscCommands : CommandRegistrar { throw DebugException() } - private suspend fun import(ctx: DiscordContext, url: String?): Boolean { + suspend fun import(ctx: DiscordContext, url: String?): Boolean { val uri = url.uri() ctx.deferResponse() @@ -1197,7 +685,7 @@ object MiscCommands : CommandRegistrar { } } - private suspend fun export(ctx: DiscordContext): Boolean { + suspend fun export(ctx: DiscordContext): Boolean { val export = Exporter.export(ctx.getUser()!!.id.value) val message = ctx.respondFiles( null, @@ -1208,32 +696,32 @@ object MiscCommands : CommandRegistrar { return true } - private suspend fun time(ctx: DiscordContext): Boolean { + suspend fun time(ctx: DiscordContext): Boolean { val date = System.currentTimeMillis() / 1000 ctx.respondSuccess("It is currently ") return true } // TODO: Provide better help - private const val help: String = + const val help: String = """To view commands for ProxyFox, visit For quick setup: - /system create - /member create - /member proxy-add""" - private const val explain: String = + const val explain: String = """ProxyFox is modern Discord bot designed to help systems communicate. It uses discord's webhooks to generate "pseudo-users" which different members of the system can use. Someone will likely be willing to explain further if need be.""" - private val invite: String = + val invite: String = """Use to invite ProxyFox to your server! To get support, head on over to https://discord.gg/q3yF8ay9V7""" - private const val source: String = - "Source code for ProxyFox is available at https://github.com/The-ProxyFox-Group/ProxyFox!" + const val source: String = + "Source code for ProxyFox is available at !" - private suspend fun proxy(ctx: DiscordContext, system: SystemRecord, mode: AutoProxyMode?, member: MemberRecord?): Boolean { + suspend fun proxy(ctx: DiscordContext, system: SystemRecord, mode: AutoProxyMode?, member: MemberRecord?): Boolean { mode ?: run { val currMember = system.autoProxy?.let { database.fetchMemberFromSystem(system.id, it) } ctx.respondSuccess("Autoproxy is set to ${currMember?.showDisplayName() ?: system.autoType.name}") @@ -1247,7 +735,7 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun serverAutoProxy(ctx: DiscordContext, systemServer: SystemServerSettingsRecord, mode: AutoProxyMode?, member: MemberRecord?): Boolean { + suspend fun serverAutoProxy(ctx: DiscordContext, systemServer: SystemServerSettingsRecord, mode: AutoProxyMode?, member: MemberRecord?): Boolean { mode ?: run { val currMember = systemServer.autoProxy?.let { database.fetchMemberFromSystem(systemServer.systemId, it) } ctx.respondSuccess("Autoproxy is set to ${currMember?.showDisplayName() ?: systemServer.autoProxyMode.name}") @@ -1261,7 +749,7 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun serverProxy(ctx: DiscordContext, systemServer: SystemServerSettingsRecord, enabled: Boolean?): Boolean { + suspend fun serverProxy(ctx: DiscordContext, systemServer: SystemServerSettingsRecord, enabled: Boolean?): Boolean { enabled ?: run { ctx.respondSuccess("Proxy for this server is currently ${if (systemServer.proxyEnabled) "enabled" else "disabled"}.") return false @@ -1273,7 +761,8 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun role(ctx: DiscordContext, roleRaw: String?, clear: Boolean): Boolean { + // TODO: Allow user to specify guild + suspend fun role(ctx: DiscordContext, roleRaw: String?, clear: Boolean): Boolean { val server = database.getOrCreateServerSettings(ctx.getGuild() ?: run { ctx.respondFailure("You are not in a server.") return false @@ -1307,7 +796,7 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun delay(ctx: DiscordContext, server: ServerSettingsRecord, delayStr: String?): Boolean { + suspend fun delay(ctx: DiscordContext, server: ServerSettingsRecord, delayStr: String?): Boolean { if (!ctx.hasRequired(Permission.ManageGuild)) { ctx.respondFailure("You do not have the proper permissions to run this command") return false @@ -1335,7 +824,7 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun deleteMessage(ctx: DiscordContext, system: SystemRecord, message: Snowflake?): Boolean { + suspend fun deleteMessage(ctx: DiscordContext, system: SystemRecord, message: Snowflake?): Boolean { val messages = ctx.getDatabaseMessage(system, message) val discordMessage = messages.first discordMessage ?: run { @@ -1359,7 +848,7 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun reproxyMessage(ctx: DiscordContext, system: SystemRecord, message: Snowflake?, member: MemberRecord?): Boolean { + suspend fun reproxyMessage(ctx: DiscordContext, system: SystemRecord, message: Snowflake?, member: MemberRecord?): Boolean { member ?: run { ctx.respondFailure("Please provide the member to reproxy as.") return false @@ -1417,7 +906,7 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun fetchMessageInfo(ctx: DiscordContext, message: Snowflake?): Boolean { + suspend fun fetchMessageInfo(ctx: DiscordContext, message: Snowflake?): Boolean { val messages = ctx.getDatabaseMessage(null, message) val discordMessage = messages.first discordMessage ?: run { @@ -1490,7 +979,7 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun editMessage(ctx: DiscordContext, system: SystemRecord, message: Snowflake?, content: String?): Boolean { + suspend fun editMessage(ctx: DiscordContext, system: SystemRecord, message: Snowflake?, content: String?): Boolean { val messages = ctx.getDatabaseMessage(system, message) val discordMessage = messages.first discordMessage ?: run { @@ -1521,7 +1010,7 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun pingMessageAuthor(ctx: DiscordContext, message: Snowflake?): Boolean { + suspend fun pingMessageAuthor(ctx: DiscordContext, message: Snowflake?): Boolean { val messages = ctx.getDatabaseMessage(null, message) val discordMessage = messages.first if (discordMessage == null) { @@ -1540,7 +1029,7 @@ To get support, head on over to https://discord.gg/q3yF8ay9V7""" return true } - private suspend fun channelProxy(ctx: DiscordContext, channel: String?, value: Boolean?): Boolean { + suspend fun channelProxy(ctx: DiscordContext, channel: String?, value: Boolean?): Boolean { if (!ctx.hasRequired(Permission.ManageChannels)) { ctx.respondFailure("You do not have the proper permissions to run this command") return false diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt index add4f440..11481ce4 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/SystemCommands.kt @@ -18,7 +18,7 @@ import dev.proxyfox.bot.command.context.DiscordContext import dev.proxyfox.bot.command.context.InteractionCommandContext import dev.proxyfox.bot.command.context.runs import dev.proxyfox.bot.command.context.system -import dev.proxyfox.bot.command.node.attachment +import dev.proxyfox.bot.command.types.attachment import dev.proxyfox.command.CommandParser import dev.proxyfox.command.node.builtin.* import dev.proxyfox.common.fromColor diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt index a345cce8..818dad77 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordContext.kt @@ -86,6 +86,10 @@ abstract class DiscordContext(override val value: T) : CommandContext() { abstract suspend fun interactionMenu(private: Boolean = false, action: suspend DiscordMenu.() -> Unit) + suspend fun getSys(system: String? = null): SystemRecord? = + if (system == null) + database.fetchSystemFromUser(getUser()!!.id) + else database.fetchSystemFromId(system) suspend fun timedYesNoPrompt( message: String, yes: Pair Unit>, diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt index 455d1a88..b0ed038d 100644 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/context/DiscordMessageContext.kt @@ -22,7 +22,6 @@ import dev.proxyfox.database.records.system.SystemRecord import kotlin.jvm.optionals.getOrNull class DiscordMessageContext(message: Message, override val command: String): DiscordContext(message) { - @OptIn(ExperimentalStdlibApi::class) override fun getAttachment(): Attachment? { return value.attachments.stream().findFirst().getOrNull() } diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt deleted file mode 100644 index 74ec055b..00000000 --- a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/node/AttachmentNode.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2022-2023, The ProxyFox Group - * - * This Source Code 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 dev.proxyfox.bot.command.node - -import dev.kord.core.entity.Attachment -import dev.proxyfox.bot.command.context.DiscordContext -import dev.proxyfox.command.NodeActionParam -import dev.proxyfox.command.NodeHolder -import dev.proxyfox.command.node.CommandNode -import dev.proxyfox.command.node.Priority - -class AttachmentNode>(override val name: String) : CommandNode() { - override val priority: Priority = Priority.SEMI_STATIC - override fun parse(str: String, ctx: C): Int { - val attachment = ctx.getAttachment() ?: return -1 - ctx[name] = attachment - return 0 - } -} - -suspend fun > NodeHolder.attachment( - name: String, - action: NodeActionParam -): CommandNode { - val node = AttachmentNode(name) - node.action { - this[name] ?: throw NullPointerException("Parameter $name for $command is null!") - } - addNode(node) - return node -} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/MemberTextCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/MemberTextCommands.kt new file mode 100644 index 00000000..94186f23 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/MemberTextCommands.kt @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.text + +import dev.proxyfox.bot.command.MemberCommands +import dev.proxyfox.bot.command.checkMember +import dev.proxyfox.bot.command.checkSystem +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.types.CommandAttachment +import dev.proxyfox.bot.command.types.CommandBoolean +import dev.proxyfox.bot.command.types.CommandSnowflake +import dev.proxyfox.bot.kord +import dev.proxyfox.command.Command +import dev.proxyfox.command.Context +import dev.proxyfox.command.LiteralArgument +import dev.proxyfox.command.types.GreedyString +import dev.proxyfox.command.types.UnixList +import dev.proxyfox.common.find +import dev.proxyfox.common.toColor +import dev.proxyfox.database.database +import dev.proxyfox.database.tryParseLocalDate + + +@Suppress("UNUSED") +@LiteralArgument("member", "mem", "m") +object MemberTextCommands { + @Command + suspend fun delete( + @Context ctx: DiscordContext, + @LiteralArgument("delete", "remove", "del") literal: Unit, + memberId: GreedyString? + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + if (memberId == null) { + MemberCommands.delete(ctx, system, null) + return + } + val member = database.fetchMemberFromSystem(system.id, memberId.value) + if (!checkMember(ctx, member)) return + MemberCommands.delete(ctx, system, member) + } + + @Command + suspend fun delete( + @Context ctx: DiscordContext, + memberId: GreedyString?, + @LiteralArgument("delete", "remove", "del") literal: Unit + ) = delete(ctx, Unit, memberId) + + @Command + suspend fun create( + @Context ctx: DiscordContext, + @LiteralArgument("create", "c", "new", "add") literal: Unit, + name: GreedyString? + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + MemberCommands.create(ctx, system, name?.value) + } + + @Command + suspend fun name( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("name", "rename") literal: Unit, + unixValues: UnixList?, + name: GreedyString? + ) { + val raw = unixValues.find("raw") + + val name = name?.value?.ifEmpty { null } + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + MemberCommands.rename(ctx, system, member, name, raw) + } + + @Command + suspend fun nickname( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("nickname", "nick", "displayname", "dn") literal: Unit, + unixValues: UnixList?, + name: GreedyString? + ) { + val raw = unixValues.find("raw") + val clear = unixValues.find("clear") || unixValues.find("remove") + + val name = name?.value?.ifEmpty { null } + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + MemberCommands.nickname(ctx, system, member, name, raw, clear) + } + + @Command + suspend fun serverNick( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("servername", "servernick", "sn") literal: Unit, + guildId: CommandSnowflake?, + unixValues: UnixList?, + name: GreedyString? + ) { + val raw = unixValues.find("raw") + val clear = unixValues.find("clear") || unixValues.find("remove") + + val name = name?.value?.ifEmpty { null } + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + val guild = guildId?.let { kord.getGuild(guildId.snowflake) } ?: ctx.getGuild() ?: run { + ctx.respondFailure("Cannot find guild.") + return@serverNick + } + + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id)!! + + MemberCommands.servername(ctx, system, serverMember, name, raw, clear) + } + + @Command + suspend fun description( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("description", "desc") literal: Unit, + unixValues: UnixList?, + desc: GreedyString? + ) { + val raw = unixValues.find("raw") + val clear = unixValues.find("clear") || unixValues.find("remove") + + val desc = desc?.value?.ifEmpty { null } + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + MemberCommands.description(ctx, system, member, desc, raw, clear) + } + + @Command + suspend fun avatar( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("avatar", "pfp") literal: Unit, + unixValues: UnixList?, + attachment: CommandAttachment?, + url: GreedyString? + ) { + val clear = unixValues.find("clear") || unixValues.find("remove") + + val url = url?.value?.ifEmpty { null } ?: attachment?.attachment?.url + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + MemberCommands.avatar(ctx, system, member, url, clear) + } + + @Command + suspend fun serverAvatar( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("serveravatar", "sa", "serverpfp", "sp") literal: Unit, + guildId: CommandSnowflake?, + unixValues: UnixList?, + attachment: CommandAttachment?, + url: GreedyString? + ) { + val clear = unixValues.find("clear") || unixValues.find("remove") + + val url = url?.value?.ifEmpty { null } ?: attachment?.attachment?.url + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + val guild = guildId?.let { kord.getGuild(guildId.snowflake) } ?: ctx.getGuild() ?: run { + ctx.respondFailure("Cannot find guild.") + return@serverAvatar + } + + val serverMember = + database.fetchMemberServerSettingsFromSystemAndMember(guild, system.id, member.id)!! + + MemberCommands.serverAvatar(ctx, system, serverMember, url, clear) + } + + @Command + suspend fun autoproxy( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("autoproxy", "ap") literal: Unit, + boolean: CommandBoolean? + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + MemberCommands.autoproxy(ctx, system, member, boolean?.value) + } + + @Command + suspend fun proxyAdd( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("autoproxy", "ap") literal1: Unit, + @LiteralArgument("add", "create", "new") literal2: Unit, + proxy: GreedyString? + ) { + val proxytag = proxy?.value + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + if (proxytag == null) { + MemberCommands.proxy(ctx, system, member, null) + return + } + + val proxy = extractProxyFromTag(ctx, proxytag) ?: return + + MemberCommands.proxy(ctx, system, member, proxy) + } + + @Command + suspend fun proxyDelete( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("autoproxy", "ap") literal1: Unit, + @LiteralArgument("remove", "rem", "delete", "del") literal2: Unit, + proxy: GreedyString? + ) { + val proxytag = proxy?.value + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + if (proxytag == null || !proxytag.contains("text") ) { + MemberCommands.removeProxy(ctx, system, member, false, null) + return + } + + val proxyDb = database.fetchProxyTagFromMessage(ctx.getUser(), proxytag) + proxyDb ?: run { + ctx.respondFailure("Proxy tag doesn't exist in this member.") + return@proxyDelete + } + if (proxyDb.memberId != member.id) { + ctx.respondFailure("Proxy tag doesn't exist in this member.") + return + } + + MemberCommands.removeProxy(ctx, system, member, true, proxyDb) + } + + @Command + suspend fun proxy( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("autoproxy", "ap") literal: Unit, + proxy: GreedyString? + ) = proxyAdd(ctx, memberId, Unit, Unit, proxy) + + @Command + suspend fun pronouns( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("pronouns", "bluehair") pronounsliteral: Unit, + unixValues: UnixList?, + pronouns: GreedyString? + ) { + val raw = unixValues.find("raw") + val clear = unixValues.find("clear") || unixValues.find("remove") + + val pronouns = pronouns?.value?.ifEmpty { null } + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + MemberCommands.pronouns(ctx, system, member, pronouns, raw, clear) + } + + @Command + suspend fun color( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("color", "colour") literal: Unit, + color: GreedyString? + ) { + val color = color?.value?.ifEmpty { null } + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + MemberCommands.color(ctx, system, member, color?.toColor()) + } + + @Command + suspend fun birthday( + @Context ctx: DiscordContext, + memberId: String, + @LiteralArgument("birthday", "bday", "birth", "bd") literal: Unit, + unixValues: UnixList?, + birthday: GreedyString? + ) { + val clear = unixValues.find("clear") + + val birthday = birthday?.value?.ifEmpty { null } + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + MemberCommands.birthday(ctx, system, member, tryParseLocalDate(birthday)?.first, clear) + } + + @Command + suspend fun access( + @Context ctx: DiscordContext, + memberId: String + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + val member = database.fetchMemberFromSystem(system.id, memberId) + if (!checkMember(ctx, member)) return + + MemberCommands.access(ctx, system, member) + } + + @Command + suspend fun empty( + @Context ctx: DiscordContext + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MemberCommands.empty(ctx) + } + + suspend fun extractProxyFromTag(ctx: DiscordContext, proxy: String): Pair? { + if (!proxy.contains("text")) { + ctx.respondFailure("Given proxy tag does not contain `text`.") + return null + } + val prefix = proxy.substring(0, proxy.indexOf("text")) + val suffix = proxy.substring(4 + prefix.length, proxy.length) + if (prefix.isEmpty() && suffix.isEmpty()) { + ctx.respondFailure("Proxy tag must contain either a prefix or a suffix.") + return null + } + return Pair(prefix, suffix) + } +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/MiscTextCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/MiscTextCommands.kt new file mode 100644 index 00000000..3685b622 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/MiscTextCommands.kt @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.text + +import dev.proxyfox.bot.command.MiscCommands +import dev.proxyfox.bot.command.checkMember +import dev.proxyfox.bot.command.checkSystem +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.types.CommandAttachment +import dev.proxyfox.bot.command.types.CommandBoolean +import dev.proxyfox.bot.command.types.CommandProxyMode +import dev.proxyfox.bot.command.types.CommandSnowflake +import dev.proxyfox.bot.kord +import dev.proxyfox.command.Command +import dev.proxyfox.command.Context +import dev.proxyfox.command.LiteralArgument +import dev.proxyfox.command.types.GreedyString +import dev.proxyfox.command.types.UnixList +import dev.proxyfox.common.find +import dev.proxyfox.database.database +import dev.proxyfox.database.records.misc.AutoProxyMode +import dev.proxyfox.database.records.misc.TokenType +import dev.proxyfox.database.records.misc.TrustLevel + +@Suppress("UNUSED") +object MiscTextCommands { + @Command + suspend fun import( + @Context ctx: DiscordContext, + @LiteralArgument("import") literal: Unit, + attachment: CommandAttachment?, + url: GreedyString? + ) { + val url = url?.value?.ifEmpty { null } ?: attachment?.attachment?.url + + MiscCommands.import(ctx, url) + } + + @Command + suspend fun export( + @Context ctx: DiscordContext, + @LiteralArgument("export") literal: Unit + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + MiscCommands.export(ctx) + } + + @Command + suspend fun time( + @Context ctx: DiscordContext, + @LiteralArgument("time") literal: Unit + ) = MiscCommands.time(ctx) + + @Command + suspend fun help( + @Context ctx: DiscordContext, + @LiteralArgument("help") literal: Unit + ) = ctx.respondPlain(MiscCommands.help) + + @Command + suspend fun explain( + @Context ctx: DiscordContext, + @LiteralArgument("help") literal: Unit + ) = ctx.respondPlain(MiscCommands.explain) + + @Command + suspend fun invite( + @Context ctx: DiscordContext, + @LiteralArgument("help") literal: Unit + ) = ctx.respondPlain(MiscCommands.invite) + + @Command + suspend fun source( + @Context ctx: DiscordContext, + @LiteralArgument("help") literal: Unit + ) = ctx.respondPlain(MiscCommands.source) + + @Command + suspend fun debug( + @Context ctx: DiscordContext, + @LiteralArgument("debug") literal: Unit + ) = MiscCommands.debug(ctx) + + @Command + suspend fun fox( + @Context ctx: DiscordContext, + @LiteralArgument("fox") literal: Unit + ) = MiscCommands.getFox(ctx) + + @Command + suspend fun proxy( + @Context ctx: DiscordContext, + @LiteralArgument("proxy", "p") literal: Unit, + guildId: CommandSnowflake?, + boolean: CommandBoolean? + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + val guild = guildId?.let { kord.getGuild(guildId.snowflake) } ?: ctx.getGuild() ?: run { + ctx.respondFailure("Cannot find guild.") + return@proxy + } + + val systemServer = database.getOrCreateServerSettingsFromSystem(guild, system.id) + + MiscCommands.serverProxy(ctx, systemServer, boolean?.value) + } + + @Command + suspend fun autoproxy( + @Context ctx: DiscordContext, + @LiteralArgument("autoproxy", "ap") literal: Unit, + mode: CommandProxyMode?, + member: GreedyString? + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + val member = member?.value?.let { + val member = database.fetchMemberFromSystem(system.id, it) + if (!checkMember(ctx, member)) return@autoproxy + member + } + + MiscCommands.proxy(ctx, system, mode?.value, member) + } + + @Command + suspend fun serverAutoproxy( + @Context ctx: DiscordContext, + @LiteralArgument("serverautoproxy", "sap") literal: Unit, + guildId: CommandSnowflake?, + mode: CommandProxyMode?, + member: GreedyString? + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + val guild = guildId?.let { kord.getGuild(guildId.snowflake) } ?: ctx.getGuild() ?: run { + ctx.respondFailure("Cannot find guild.") + return@serverAutoproxy + } + + val systemServer = database.getOrCreateServerSettingsFromSystem(guild, system.id) + + val member = member?.value?.let { + val member = database.fetchMemberFromSystem(system.id, it) + if (!checkMember(ctx, member)) return@serverAutoproxy + member + } + + val mode = mode?.value?.let { if (it == AutoProxyMode.OFF) AutoProxyMode.FALLBACK else it } + + MiscCommands.serverAutoProxy(ctx, systemServer, mode, member) + } + + @Command + suspend fun role( + @Context ctx: DiscordContext, + @LiteralArgument("role") literal: Unit, + unixValues: UnixList?, + role: GreedyString? + ) { + val clear = unixValues.find("clear") + + MiscCommands.role(ctx, role?.value, clear) + } + + @Command + suspend fun modDelay( + @Context ctx: DiscordContext, + @LiteralArgument("role") literal: Unit, + guildId: CommandSnowflake?, + delay: GreedyString? + ) { + val guild = guildId?.let { kord.getGuild(guildId.snowflake) } ?: ctx.getGuild() ?: run { + ctx.respondFailure("Cannot find guild.") + return@modDelay + } + + val settings = database.getOrCreateServerSettings(guild) + + MiscCommands.delay(ctx, settings, delay?.value) + } + + @Command + suspend fun forceTag( + @Context ctx: DiscordContext, + @LiteralArgument("forcetag", "requiretag") literal: Unit, + boolean: CommandBoolean? + ) = MiscCommands.forceTag(ctx, boolean?.value) + + @Command + suspend fun deleteMessage( + @Context ctx: DiscordContext, + @LiteralArgument("delete", "del") literal: Unit, + messageId: CommandSnowflake? + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.deleteMessage(ctx, system, messageId?.snowflake) + } + + @Command + suspend fun reproxyMessage( + @Context ctx: DiscordContext, + @LiteralArgument("reproxy", "rp") literal: Unit, + messageId: CommandSnowflake?, + member: GreedyString? + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + val member = member?.value?.let { + val member = database.fetchMemberFromSystem(system.id, it) + if (!checkMember(ctx, member)) return@reproxyMessage + member + } + + MiscCommands.reproxyMessage(ctx, system, messageId?.snowflake, member) + } + + @Command + suspend fun fetchMessage( + @Context ctx: DiscordContext, + @LiteralArgument("info", "i") literal: Unit, + messageId: CommandSnowflake? + ) = MiscCommands.fetchMessageInfo(ctx, messageId?.snowflake) + + @Command + suspend fun pingMessage( + @Context ctx: DiscordContext, + @LiteralArgument("ping") literal: Unit, + messageId: CommandSnowflake? + ) = MiscCommands.pingMessageAuthor(ctx, messageId?.snowflake) + + @Command + suspend fun editMessage( + @Context ctx: DiscordContext, + @LiteralArgument("edit", "e") literal: Unit, + messageId: CommandSnowflake?, + content: GreedyString? + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.editMessage(ctx, system, messageId?.snowflake, content?.value) + } + + @Command + suspend fun channelProxy( + @Context ctx: DiscordContext, + @LiteralArgument("channelproxy", "cp") literal1: Unit, + channelId: CommandSnowflake?, + boolean: CommandBoolean? + ) = MiscCommands.channelProxy(ctx,(channelId?.snowflake ?: ctx.getChannel(false).id).toString() , boolean?.value) + + @Command + suspend fun token( + @Context ctx: DiscordContext, + @LiteralArgument("token", "t") literal: Unit + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.token(ctx, system) + } + + suspend fun transfer( + @Context ctx: DiscordContext, + @LiteralArgument("transfer") literal: Unit, + token: GreedyString? + ) { + if (token == null) { + ctx.respondFailure("Please provide a token to transfer from") + return + } + + if (ctx.getSys() != null) { + ctx.respondFailure("You can only run this command when you have no system registered.") + return + } + + val token = database.fetchToken(token.value) + if (token == null) { + ctx.respondFailure("Token not found.") + return + } + if (token.type != TokenType.SYSTEM_TRANSFER) { + ctx.respondFailure("Token isn't a transfer token.") + return + } + + MiscCommands.transfer(ctx, token) + } +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/PkCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/PkCommands.kt new file mode 100644 index 00000000..dea947f7 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/PkCommands.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.text + +import dev.proxyfox.bot.command.MiscCommands +import dev.proxyfox.bot.command.checkSystem +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.command.Command +import dev.proxyfox.command.Context +import dev.proxyfox.command.LiteralArgument +import dev.proxyfox.command.types.GreedyString +import dev.proxyfox.command.types.UnixList +import dev.proxyfox.common.find + +@Suppress("UNUSED") +@LiteralArgument("pluralkit", "pk") +object PkCommands { + @Command + suspend fun pull( + @Context ctx: DiscordContext, + @LiteralArgument("pull", "get", "download", "import") literal: Unit + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.syncPk(ctx, system, false) + } + @Command + suspend fun push( + @Context ctx: DiscordContext, + @LiteralArgument("push", "set", "upload", "export") literal: Unit + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.syncPk(ctx, system, true) + } + + @Command + suspend fun token( + @Context ctx: DiscordContext, + @LiteralArgument("token") literal: Unit, + unixValues: UnixList?, + token: GreedyString? + ) { + val clear = unixValues.find("clear") || unixValues.find("remove") + + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.pkToken(ctx, system, token?.value, clear) + } +} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/TrustCommands.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/TrustCommands.kt new file mode 100644 index 00000000..270ae868 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/text/TrustCommands.kt @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.text + +import dev.proxyfox.bot.command.MiscCommands +import dev.proxyfox.bot.command.checkSystem +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.bot.command.types.CommandSnowflake +import dev.proxyfox.command.Command +import dev.proxyfox.command.Context +import dev.proxyfox.command.LiteralArgument +import dev.proxyfox.database.records.misc.TrustLevel + +@Suppress("UNUSED") +@LiteralArgument("trust") +object TrustCommands { + @Command + suspend fun none( + @Context ctx: DiscordContext, + userId: CommandSnowflake, + @LiteralArgument("none", "remove", "clear") literal: Unit + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.trust(ctx, system, userId.snowflake.value, TrustLevel.NONE) + } + + @Command + suspend fun access( + @Context ctx: DiscordContext, + userId: CommandSnowflake, + @LiteralArgument("access", "see", "view") literal: Unit + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.trust(ctx, system, userId.snowflake.value, TrustLevel.ACCESS) + } + + @Command + suspend fun switch( + @Context ctx: DiscordContext, + userId: CommandSnowflake, + @LiteralArgument("switch", "sw") literal: Unit + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.trust(ctx, system, userId.snowflake.value, TrustLevel.SWITCH) + } + + @Command + suspend fun member( + @Context ctx: DiscordContext, + userId: CommandSnowflake, + @LiteralArgument("member", "mem", "m") literal: Unit + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.trust(ctx, system, userId.snowflake.value, TrustLevel.MEMBER) + } + + @Command + suspend fun full( + @Context ctx: DiscordContext, + userId: CommandSnowflake, + @LiteralArgument("full", "all", "everything") literal: Unit + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.trust(ctx, system, userId.snowflake.value, TrustLevel.FULL) + } + + @Command + suspend fun noLiteral( + @Context ctx: DiscordContext, + userId: CommandSnowflake + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + MiscCommands.trust(ctx, system, userId.snowflake.value, null) + } + + @Command + suspend fun noId( + @Context ctx: DiscordContext + ) { + val system = ctx.getSys() + if (!checkSystem(ctx, system)) return + + ctx.respondFailure("Please provide a user to perform this action on") + } +} \ No newline at end of file diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandAttachment.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandAttachment.kt new file mode 100644 index 00000000..1c588408 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandAttachment.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.types + +import dev.kord.core.entity.Attachment +import dev.proxyfox.bot.command.context.DiscordContext +import dev.proxyfox.command.CommandDecoder +import dev.proxyfox.command.types.CommandSerializer +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.serializer + +@JvmInline +@Serializable(with = CommandAttachmentSerializer::class) +value class CommandAttachment(val attachment: Attachment) + +@OptIn(InternalSerializationApi::class) +private class CommandAttachmentSerializer : CommandSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CommandAttachment") + + override fun decodeCommand(decoder: CommandDecoder): CommandAttachment { + val context = decoder.context as? DiscordContext ?: decoder.fails("Not discord context") + val attachment = context.getAttachment() ?: decoder.fails("No attachment") + return CommandAttachment(attachment) + } + + override fun decodeRegular(decoder: Decoder): CommandAttachment = + CommandAttachment(Attachment::class.serializer().deserialize(decoder)) + + override fun serialize(encoder: Encoder, value: CommandAttachment) = + Attachment::class.serializer().serialize(encoder, value.attachment) +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandBoolean.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandBoolean.kt new file mode 100644 index 00000000..b3186312 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandBoolean.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.types + +import dev.proxyfox.command.CommandDecoder +import dev.proxyfox.command.types.CommandSerializer +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.serializer + +@JvmInline +@Serializable(with = CommandBooleanSerializer::class) +value class CommandBoolean(val value: Boolean) + +@OptIn(InternalSerializationApi::class) +private class CommandBooleanSerializer : CommandSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CommandBoolean") + + override fun decodeCommand(decoder: CommandDecoder): CommandBoolean { + decoder.cursor.checkout() + val bool = decoder.cursor.extractString(false).lowercase() + if (arrayOf("true", "false", "enable", "disable", "on", "off", "1", "0").contains(bool)) { + decoder.cursor.rollback() + decoder.fails("Not boolean") + } + decoder.cursor.commit() + val value = arrayOf("true", "enable", "on", "1").contains(bool) + return CommandBoolean(value) + } + + override fun decodeRegular(decoder: Decoder): CommandBoolean = + CommandBoolean(Boolean::class.serializer().deserialize(decoder)) + + override fun serialize(encoder: Encoder, value: CommandBoolean) = + Boolean::class.serializer().serialize(encoder, value.value) +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandProxyMode.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandProxyMode.kt new file mode 100644 index 00000000..a1b815b6 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandProxyMode.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.types + +import dev.proxyfox.command.CommandDecoder +import dev.proxyfox.command.types.CommandSerializer +import dev.proxyfox.database.records.misc.AutoProxyMode +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.serializer + +@JvmInline +value class CommandProxyMode(val value: AutoProxyMode) + +@OptIn(InternalSerializationApi::class) +private class CommandProxyModeSerializer : CommandSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CommandProxyMode") + + override fun decodeCommand(decoder: CommandDecoder): CommandProxyMode { + decoder.cursor.checkout() + val string = decoder.cursor.extractString(false).lowercase() + decoder.cursor.inc() + val mode = when (string) { + "off", "disable", "o" -> AutoProxyMode.OFF + "latch", "l", -> AutoProxyMode.LATCH + "front", "f" -> AutoProxyMode.FRONT + "fallback", "fb" -> AutoProxyMode.FALLBACK + "member", "m" -> AutoProxyMode.MEMBER + else -> { + decoder.cursor.rollback() + decoder.fails("Not AutoProxyMode") + } + } + decoder.cursor.inc() + return CommandProxyMode(mode) + } + + override fun decodeRegular(decoder: Decoder): CommandProxyMode = + CommandProxyMode(AutoProxyMode::class.serializer().deserialize(decoder)) + + override fun serialize(encoder: Encoder, value: CommandProxyMode) = + AutoProxyMode::class.serializer().serialize(encoder, value.value) +} diff --git a/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandSnowflake.kt b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandSnowflake.kt new file mode 100644 index 00000000..c7cb9830 --- /dev/null +++ b/modules/bot/src/main/kotlin/dev/proxyfox/bot/command/types/CommandSnowflake.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022-2023, The ProxyFox Group + * + * This Source Code 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 dev.proxyfox.bot.command.types + +import dev.kord.common.entity.Snowflake +import dev.proxyfox.command.CommandDecoder +import dev.proxyfox.command.types.CommandSerializer +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.serializer + +@JvmInline +@Serializable(with = CommandSnowflakeSerializer::class) +value class CommandSnowflake(val snowflake: Snowflake) + +@OptIn(InternalSerializationApi::class) +private class CommandSnowflakeSerializer : CommandSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CommandSnowflake") + + override fun decodeCommand(decoder: CommandDecoder): CommandSnowflake { + decoder.cursor.checkout() + val num = decoder.cursor.extractString(false).toULongOrNull() + decoder.cursor.inc() + if (num == null) { + decoder.cursor.rollback() + decoder.fails("Not ULong") + } + decoder.cursor.commit() + return CommandSnowflake(Snowflake(num)) + } + + override fun decodeRegular(decoder: Decoder): CommandSnowflake = + CommandSnowflake(Snowflake::class.serializer().deserialize(decoder)) + + override fun serialize(encoder: Encoder, value: CommandSnowflake) = + Snowflake::class.serializer().serialize(encoder, value.snowflake) +} diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt index 9a4ec2c4..77b10e2d 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt @@ -11,6 +11,7 @@ package dev.proxyfox.common import dev.kord.core.Kord import dev.kord.core.event.Event import dev.kord.core.on +import dev.proxyfox.command.types.UnixList import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.StringReader @@ -125,3 +126,6 @@ inline fun Kord.onlyIf( ) = on { if (getter() == compare) executor() } + +fun UnixList?.find(value: String) = + this?.list?.contains(value) ?: false diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/AutoProxyMode.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/AutoProxyMode.kt index b2ffdad9..8d6b86df 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/AutoProxyMode.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/AutoProxyMode.kt @@ -8,6 +8,8 @@ package dev.proxyfox.database.records.misc +import kotlinx.serialization.Serializable + // Created 2022-11-04T12:10:37 /** @@ -15,6 +17,7 @@ package dev.proxyfox.database.records.misc * * @author Ampflower **/ +@Serializable enum class AutoProxyMode { /** AutoProxy is disabled; it will not automatically switch via use of tags or switch. */ OFF, From 1334a5675aa40c7bd6996af8fab25ff3d34a4fba Mon Sep 17 00:00:00 2001 From: Octal Date: Wed, 26 Jul 2023 11:04:59 -0500 Subject: [PATCH 137/137] fix some api stuff + documentation too --- .../kotlin/dev/proxyfox/api/models/Member.kt | 38 +++++++++++-- .../api/models/MemberGuildSettings.kt | 12 +++++ .../kotlin/dev/proxyfox/api/models/Message.kt | 54 +++++++++++++------ .../dev/proxyfox/api/models/ProxyTag.kt | 6 +++ .../kotlin/dev/proxyfox/api/models/Switch.kt | 22 ++++++-- .../kotlin/dev/proxyfox/api/models/System.kt | 50 ++++++++++++----- .../api/models/SystemGuildSettings.kt | 17 ++++-- .../kotlin/dev/proxyfox/api/models/Token.kt | 14 ++++- .../dev/proxyfox/api/routes/CoffeeRoutes.kt | 5 ++ .../main/kotlin/dev/proxyfox/common/Util.kt | 4 ++ .../records/misc/ProxiedMessageRecord.kt | 2 + .../database/records/misc/TokenType.kt | 2 +- 12 files changed, 182 insertions(+), 44 deletions(-) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt index 321b86f3..986f7dc4 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Member.kt @@ -9,15 +9,43 @@ package dev.proxyfox.api.models import dev.proxyfox.common.fromColor +import dev.proxyfox.database.PkId import dev.proxyfox.database.database +import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMillisecondSerializer +import dev.proxyfox.database.etc.ktx.serializaton.LocalDateLongMillisecondSerializer import dev.proxyfox.database.records.member.MemberRecord import kotlinx.coroutines.runBlocking +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +/** + * Represents a member. + * + * Accessed with the `/system/{sysid}/members` or + * `/system/{sysid}/members/{memid}` routes. + * + * Requires a token to access. + * + * @param id the Pk-compatible ID of the member + * @param name the name of the member + * @param displayName the display name of the member + * @param description the description of the member + * @param color the color of the member (in a hexadecimal RGB format) + * @param avatarUrl the URL for the member's avatar + * @param keepProxy whether the member keeps their proxy tags in proxied messages + * @param autoProxy whether autoproxy is enabled for this member + * @param messageCount the amount of messages this member has sent + * @param created the timestamp of the member creation + * @param birthday the member's birthday + * @param age the age of the member + * @param role the role of the member + * @param proxyTags the member's proxy tags + * */ @Serializable data class Member( - val id: String, + val id: PkId, val name: String, @SerialName("display_name") val displayName: String?, @@ -32,8 +60,8 @@ data class Member( val autoProxy: Boolean, @SerialName("message_count") val messageCount: ULong, - val created: String, - val birthday: String?, + val created: Instant, + val birthday: LocalDate?, val age: String?, val role: String?, @SerialName("proxy_tags") @@ -51,8 +79,8 @@ data class Member( keepProxy = member.keepProxy, autoProxy = member.autoProxy, messageCount = member.messageCount, - created = member.timestamp.toString(), - birthday = member.birthday.toString(), + created = member.timestamp, + birthday = member.birthday, age = member.age, role = member.role, proxyTags = runBlocking { database.fetchProxiesFromSystemAndMember(member.systemId, member.id)?.map(ProxyTag::fromRecord) ?: emptyList() } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt index b29456a5..7293a1a6 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/MemberGuildSettings.kt @@ -12,6 +12,18 @@ import dev.proxyfox.database.records.member.MemberServerSettingsRecord import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +/** + * Represents a member's guild settings. + * + * Accessed with the `/systems/{sysid}/members/{memid}/guilds/{guildid}` route. + * + * Requires a token to access. + * + * @param displayName the display name of this member for this guild + * @param avatarUrl the url for the avatar of this member for this guild + * @param autoProxy whether autoproxy is enabled for this member for this guild + * @param proxyEnabled whether proxying is enabled for this member for this guild + * */ @Serializable data class MemberGuildSettings( @SerialName("display_name") diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt index db8afdc6..281eb68e 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Message.kt @@ -8,30 +8,52 @@ package dev.proxyfox.api.models +import dev.kord.common.entity.Snowflake +import dev.proxyfox.common.snowflake +import dev.proxyfox.database.PkId +import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMillisecondSerializer import dev.proxyfox.database.records.misc.ProxiedMessageRecord +import kotlinx.datetime.Instant import kotlinx.serialization.Serializable +/** + * Represents a proxied message. + * + * Accessed in the `/messages/{id}` route. + * + * Doesn't require a token. + * + * @param timestamp the time of creation + * @param sender the Discord account ID of the author + * @param original the message ID of the original message + * @param proxied the message ID of the new (proxied) message + * @param channel the ID of the channel the message was sent in + * @param guild the ID of the guild the message was sent in + * @param thread the ID of the thread the message was sent in, if applicable + * @param system the Pk-formatted ID of the system that created the message + * @param member the Pk-formatted ID of the member that created the message + * */ @Serializable data class Message( - val timestamp: String, - val sender: String, - val original: String, - val proxied: String, - val channel: String, - val guild: String, - val thread: String?, - val system: String, - val member: String + val timestamp: Instant, + val sender: Snowflake, + val original: Snowflake, + val proxied: Snowflake, + val channel: Snowflake, + val guild: Snowflake, + val thread: Snowflake?, + val system: PkId, + val member: PkId ) { companion object { fun fromRecord(record: ProxiedMessageRecord) = Message( - timestamp = record.creationDate.toString(), - sender = record.userId.toString(), - original = record.oldMessageId.toString(), - proxied = record.newMessageId.toString(), - channel = record.channelId.toString(), - guild = record.guildId.toString(), - thread = record.threadId.toString(), + timestamp = record.creationDate, + sender = record.userId.snowflake, + original = record.oldMessageId.snowflake, + proxied = record.newMessageId.snowflake, + channel = record.channelId.snowflake, + guild = record.guildId.snowflake, + thread = record.threadId?.snowflake, system = record.systemId, member = record.memberId ) diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt index fc2f71b5..f3dbd71a 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/ProxyTag.kt @@ -11,6 +11,12 @@ package dev.proxyfox.api.models import dev.proxyfox.database.records.member.MemberProxyTagRecord import kotlinx.serialization.Serializable +/** + * Represents a proxy tag. + * + * @param prefix the prefix for the proxy + * @param suffix the suffix for the proxy + * */ @Serializable data class ProxyTag(val prefix: String?, val suffix: String?) { companion object { diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt index beb5bc06..463464f1 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Switch.kt @@ -8,20 +8,34 @@ package dev.proxyfox.api.models +import dev.proxyfox.database.PkId +import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMillisecondSerializer import dev.proxyfox.database.records.system.SystemSwitchRecord +import kotlinx.datetime.Instant import kotlinx.serialization.Serializable +/** + * Represents a switch. + * + * Accessed via the `/systems/{sysid}/switches` or `/system/{sysid}/fronters` routes. + * + * Requires a token to access. + * + * @param id the Pk-formatted ID of the switch + * @param members the Pk-formatted IDs of the members in this switch + * @param timestamp the timestamp of switch creation + * */ @Serializable data class Switch( - val id: String, - val members: List, - val timestamp: String + val id: PkId, + val members: List, + val timestamp: Instant ) { companion object { fun fromRecord(record: SystemSwitchRecord) = Switch( id = record.id, members = record.memberIds, - timestamp = record.timestamp.toString() + timestamp = record.timestamp ) } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt index 69a03205..7abd5ace 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/System.kt @@ -9,27 +9,49 @@ package dev.proxyfox.api.models import dev.proxyfox.common.fromColor +import dev.proxyfox.database.PkId +import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMillisecondSerializer import dev.proxyfox.database.records.misc.AutoProxyMode import dev.proxyfox.database.records.system.SystemRecord +import kotlinx.datetime.Instant import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +/** + * Represents a system. + * + * Accessed via the `/systems/{sysid}` route. + * + * Requires a token to access. + * + * @param id the Pk-formatted ID of the system + * @param name the name of the system + * @param description the description of the system + * @param tag the system tag + * @param pronouns the pronouns for the system + * @param color the default color for the system (in a hexadecimal RGB format) + * @param avatarUrl the URL for the default avatar + * @param timezone the timezone of the system + * @param created the timestamp of the creation of the system + * @param autoProxy the ID of the member that's currently being autoproxied + * @param autoType the current mode of autoproxy + * */ @Serializable data class System( - val id: String, - val name: String?, - val description: String?, - val tag: String?, - val pronouns: String?, - val color: String?, - @SerialName("avatar_url") + val id: PkId, + val name: String?, + val description: String?, + val tag: String?, + val pronouns: String?, + val color: String?, + @SerialName("avatar_url") val avatarUrl: String?, - val timezone: String?, - val created: String, - @SerialName("autoProxy") + val timezone: String?, + val created: Instant, + @SerialName("auto_proxy") val autoProxy: String?, - @SerialName("autoType") - val autoType: AutoProxyMode + @SerialName("auto_type") + val autoType: AutoProxyMode ) { companion object { fun fromRecord(system: SystemRecord) = System( @@ -41,9 +63,9 @@ data class System( color = system.color.fromColor(), avatarUrl = system.avatarUrl, timezone = system.timezone, - created = system.timestamp.toString(), + created = system.timestamp, autoProxy = system.autoProxy, - autoType = system.autoType + autoType = system.autoType ) } } \ No newline at end of file diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt index fa910824..3c64722e 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/SystemGuildSettings.kt @@ -13,20 +13,31 @@ import dev.proxyfox.database.records.system.SystemServerSettingsRecord import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +/** + * Represents a system's guild settings + * + * Accessed via the `/systems/{sysid}/guilds/{guildid}` route. + * + * Requires a token to access. + * + * @param proxyEnabled whether proxying is enabled in this guild + * @param autoProxy the ID of the member that's currently being autoproxied (if autoProxyMode is not FALLBACK) + * @param autoType the current mode of autoproxy + * */ @Serializable data class SystemGuildSettings( @SerialName("proxy_enabled") val proxyEnabled: Boolean, @SerialName("auto_proxy") val autoProxy: String?, - @SerialName("auto_proxy_mode") - val autoProxyMode: AutoProxyMode + @SerialName("auto_type") + val autoType: AutoProxyMode ) { companion object { fun fromRecord(record: SystemServerSettingsRecord) = SystemGuildSettings( proxyEnabled = record.proxyEnabled, autoProxy = record.autoProxy, - autoProxyMode = record.autoProxyMode + autoType = record.autoProxyMode ) } } diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt index afca1e58..50fcd512 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/models/Token.kt @@ -8,16 +8,28 @@ package dev.proxyfox.api.models +import dev.proxyfox.database.PkId import dev.proxyfox.database.records.misc.TokenRecord import dev.proxyfox.database.records.misc.TokenType import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +/** + * Represents a token + * + * Accessed via the `/tokens` route + * + * Requires a token to access. + * + * @param token the token + * @param systemId the ID for the system it's attached to + * @param type the type of token it is + * */ @Serializable data class Token( val token: String, @SerialName("system_id") - val systemId: String, + val systemId: PkId, val type: TokenType ) { companion object { diff --git a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/CoffeeRoutes.kt b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/CoffeeRoutes.kt index 71cdff78..b4d4af1a 100644 --- a/modules/api/src/main/kotlin/dev/proxyfox/api/routes/CoffeeRoutes.kt +++ b/modules/api/src/main/kotlin/dev/proxyfox/api/routes/CoffeeRoutes.kt @@ -15,6 +15,7 @@ import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* +import kotlinx.coroutines.withTimeout var coffees = HashMap() @@ -50,6 +51,10 @@ fun Route.coffeeRoutes() { coffees[id] = coffee + withTimeout(600_000) { + coffees.remove(id) + } + call.respond(HttpStatusCode.OK, coffee) }} diff --git a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt index 77b10e2d..0e06b799 100644 --- a/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt +++ b/modules/common/src/main/kotlin/dev/proxyfox/common/Util.kt @@ -8,6 +8,7 @@ package dev.proxyfox.common +import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.event.Event import dev.kord.core.on @@ -129,3 +130,6 @@ inline fun Kord.onlyIf( fun UnixList?.find(value: String) = this?.list?.contains(value) ?: false + +val ULong.snowflake: Snowflake + get() = Snowflake(this) diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt index f4f6df6a..8801c80e 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/ProxiedMessageRecord.kt @@ -9,6 +9,7 @@ package dev.proxyfox.database.records.misc import dev.proxyfox.database.PkId +import dev.proxyfox.database.etc.ktx.serializaton.InstantLongMillisecondSerializer import dev.proxyfox.database.records.MongoRecord import kotlinx.datetime.Clock import kotlinx.serialization.Contextual @@ -19,6 +20,7 @@ import org.bson.types.ObjectId class ProxiedMessageRecord : MongoRecord { @Contextual override var _id: ObjectId = ObjectId() + @Serializable(InstantLongMillisecondSerializer::class) var creationDate = Clock.System.now() var memberName: String = "" var userId: ULong = 0UL diff --git a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt index cac58ca3..79a46748 100644 --- a/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt +++ b/modules/database/src/main/kotlin/dev/proxyfox/database/records/misc/TokenType.kt @@ -30,7 +30,7 @@ enum class TokenType(private val actualName: String) { companion object { fun of(name: String): TokenType? { - for (type in TokenType.values()) { + for (type in entries) { if (type.toString() == name) return type } return null