Skip to content

Commit

Permalink
Remove most hard dependencies on GUILDS env var
Browse files Browse the repository at this point in the history
plus also change how ban syncing works to make it more tolerable to large ban counts
  • Loading branch information
sschr15 committed Mar 4, 2024
1 parent c0182a3 commit 6dd079d
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 81 deletions.
5 changes: 4 additions & 1 deletion src/main/kotlin/org/quiltmc/community/_Checks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import dev.kord.core.entity.Member
import dev.kord.core.event.Event
import dev.kord.core.event.interaction.InteractionCreateEvent
import io.github.oshai.kotlinlogging.KotlinLogging
import org.quiltmc.community.database.collections.GlobalSettingsCollection
import org.quiltmc.community.database.collections.ServerSettingsCollection
import org.quiltmc.community.database.getSettings

Expand Down Expand Up @@ -83,7 +84,7 @@ suspend fun CheckContext<*>.hasPermissionInMainGuild(perm: Permission) {
return
}

val guild = event.kord.getGuildOrNull(MAIN_GUILD)!!
val guild = event.kord.getGuild(MAIN_GUILD)
val member = guild.getMemberOrNull(user.id)

if (member == null) {
Expand Down Expand Up @@ -120,6 +121,8 @@ suspend fun CheckContext<*>.inLadysnakeGuild() {

fail("Must be in one of the Ladysnake servers")
} else {
if (getKoin().get<GlobalSettingsCollection>().get()?.ladysnakeGuilds?.contains(guild.id) == true) return

if (guild.id !in GUILDS) {
fail("Must be in one of the Ladysnake servers")
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/org/quiltmc/community/_Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -475,3 +475,5 @@ val User.identifier: String get() = try {
// whoops kordex is probably already updated
"@$username"
}

suspend fun getGuilds() = getKoin().get<GlobalSettingsCollection>().get()?.ladysnakeGuilds ?: GUILDS
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.toList
import kotlinx.datetime.Instant
import org.koin.core.component.inject
import org.quiltmc.community.GUILDS
import org.quiltmc.community.database.collections.ServerApplicationCollection
import org.quiltmc.community.database.collections.ServerSettingsCollection
import org.quiltmc.community.database.entities.ServerApplication
import org.quiltmc.community.database.entities.ServerSettings
import org.quiltmc.community.getGuilds
import org.quiltmc.community.hasBaseModeratorRole
import org.quiltmc.community.inLadysnakeGuild
import kotlin.time.Duration.Companion.seconds
Expand All @@ -68,7 +68,7 @@ class ApplicationsExtension : Extension() {
get() = "${toDiscord(TimestampType.LongDateTime)} (${toDiscord(TimestampType.RelativeTime)})"

override suspend fun setup() {
GUILDS.forEach {
getGuilds().forEach {
ephemeralSlashCommand {
name = "applications"
description = "Commands related to managing server applications"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ import dev.kord.core.behavior.getChannelOfOrNull
import dev.kord.core.entity.channel.GuildMessageChannel
import dev.kord.rest.builder.message.embed
import kotlinx.datetime.toInstant
import org.quiltmc.community.GUILDS
import org.quiltmc.community.OVERRIDING_USERS
import org.quiltmc.community.any
import org.quiltmc.community.copyFrom
import org.quiltmc.community.getGuilds

class MessageEditExtension : Extension() {
override val name = "message-modification"

override suspend fun setup() {
GUILDS.forEach {
getGuilds().forEach {
ephemeralSlashCommand {
name = "edit"
description = "Edit a message sent by ${kord.getSelf().username}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ package org.quiltmc.community.modes.quilt.extensions
import com.kotlindiscord.kord.extensions.checks.hasPermission
import com.kotlindiscord.kord.extensions.checks.types.CheckContext
import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand
import com.kotlindiscord.kord.extensions.components.buttons.EphemeralInteractionButtonContext
import com.kotlindiscord.kord.extensions.components.components
import com.kotlindiscord.kord.extensions.components.ephemeralButton
import com.kotlindiscord.kord.extensions.extensions.Extension
import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand
import com.kotlindiscord.kord.extensions.extensions.event
Expand All @@ -30,14 +33,17 @@ import dev.kord.core.entity.Guild
import dev.kord.core.event.Event
import dev.kord.core.event.guild.BanAddEvent
import dev.kord.core.event.guild.BanRemoveEvent
import dev.kord.rest.builder.message.EmbedBuilder
import dev.kord.rest.builder.message.embed
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.toList
import kotlinx.datetime.Instant
import org.koin.core.component.inject
import org.quiltmc.community.GUILDS
import org.quiltmc.community.asUser
import org.quiltmc.community.database.collections.GlobalSettingsCollection
import org.quiltmc.community.getModLogChannel
import org.quiltmc.community.inLadysnakeGuild
import org.quiltmc.community.modes.quilt.extensions.moderation.ModerationExtension
Expand All @@ -50,6 +56,7 @@ class SyncExtension : Extension() {
override val name: String = "sync"

private val logger = KotlinLogging.logger {}
private val globalSettings: GlobalSettingsCollection by inject()

private suspend fun <T : Event> CheckContext<T>.hasBanPerms() {
fail(
Expand Down Expand Up @@ -114,6 +121,8 @@ class SyncExtension : Extension() {
action {
val guilds = getGuilds()

require(guilds.isNotEmpty()) { "Impossible: command run in guild but no guilds were found" }

logger.info { "Syncing bans for ${guilds.size} guilds." }

guilds.forEach {
Expand Down Expand Up @@ -142,39 +151,74 @@ class SyncExtension : Extension() {
}
}

val syncStatus = CurrentState(allBans.size, 0, guilds.first())

respond {
content = "Collected ${allBans.size} bans."
components {
ephemeralButton {
label = "Check progress"
action {
with(syncStatus) {
showState()
}
}
}
}
}

guilds.forEach { guild ->
syncStatus.syncingFor = guild
syncStatus.bansSynced = 0

val newBans = mutableListOf<Pair<Snowflake, String>>()

allBans.forEach { (userId, reason) ->
if (guild.getBanOrNull(userId) == null) {
syncedBans[guild] = (syncedBans[guild] ?: 0) + 1
allBans.forEach { (userId, reason) ->
syncStatus.bansSynced++

val newReason = "Synced: ${reason ?: "No reason given"}"
@Suppress("TooGenericExceptionCaught")
try {
if (guild.getBanOrNull(userId) == null) {
syncedBans[guild] = (syncedBans[guild] ?: 0) + 1

guild.ban(userId) {
this.reason = newReason
}
val newReason = "Synced: ${reason ?: "No reason given"}"

newBans.add(userId to newReason)
}
}
guild.ban(userId) {
this.reason = newReason
}

newBans.add(userId to newReason)
}
} catch (t: Throwable) {
if (syncStatus.previousException != null) {
t.addSuppressed(syncStatus.previousException)
throw t
}

guild.getModLogChannel()?.createEmbed {
title = "Synced bans"
description = "**Added bans:**\n" + newBans.joinToString("\n") { (id, reason) ->
"`$id` (<@!$id>) - $reason"
syncStatus.previousException = t
}
}
}

respond {
embed {
title = "Bans synced"
guild.getModLogChannel()?.createEmbed {
title = "Synced bans"
description = "**Added bans:**\n" + newBans.joinToString("\n") { (id, reason) ->
"`$id` (<@!$id>) - $reason"
}

description = syncedBans.map { "**${it.key.name}**: ${it.value} added" }
.joinToString("\n")
if (syncStatus.previousException != null) {
logger.error(syncStatus.previousException!!) { "An error occurred during ban syncing" }

field {
name = "Error"
value = "An error occurred during ban syncing. One or more bans may not have been synced."
}
}
}

syncStatus.previousException = null
}

syncStatus.syncingFor = null
}
}

Expand Down Expand Up @@ -394,29 +438,59 @@ class SyncExtension : Extension() {
}
}
}

// event<MemberUpdateEvent> {
// check { inLadysnakeGuild() }
//
// action {
// try {
// val guilds = getGuilds().filter { it.id != event.guildId }
//
// for (guild in guilds) {
// val guildMember = guild.getMemberOrNull(event.member.id) ?: continue
//
// if (guildMember.timeoutUntil != event.member.timeoutUntil) {
// guildMember.edit {
// timeoutUntil = event.member.timeoutUntil
// }
// }
// }
// } catch (e: RestRequestException) {
// logger.error(e) { "Failed to sync member timeout ${event.member.id} (JSON error ${e.error?.code})" }
// }
// }
// }
}

private suspend fun getGuilds() = GUILDS.mapNotNull { kord.getGuildOrNull(it) }
private suspend fun getGuilds() = (globalSettings.get()?.ladysnakeGuilds ?: GUILDS)
.mapNotNull { kord.getGuildOrNull(it) }

private class CurrentState(
val bansToSync: Int,
var bansSynced: Int,
var syncingFor: Guild?,
var previousException: Throwable? = null
) {
val completionPercentage: Double
get() = bansSynced.toDouble() / bansToSync

@Suppress("MagicNumber")
suspend fun EphemeralInteractionButtonContext<*>.showState() {
respond {
embed {
title = "Current Sync Status"

if (syncingFor == null) {
description = "Completed syncing bans."
return@embed
}

description = "Syncing bans for ${syncingFor!!.name} (${syncingFor!!.id.value})\n" +
"Progress: $bansSynced/$bansToSync " +
"(${(completionPercentage * 100).toInt()}%)"

if (previousException != null) {
description += "\nDuring processing, an error occurred."
field {
val t = previousException!!
if (t.toString().length <= EmbedBuilder.Field.Limits.name) {
name = t.toString()
value = t.stackTraceToString().substringAfter("\n")
} else {
name = t::class.simpleName
?.takeIf { it.length <= EmbedBuilder.Field.Limits.name }
?: "Error"

value = t.stackTraceToString() // Keep the message / toString() first line
}

if (value.length > EmbedBuilder.Field.Limits.value) {
value = value.take(EmbedBuilder.Field.Limits.value - 4)
.substringBeforeLast("\n") +
"\n..."
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSub
import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand
import com.kotlindiscord.kord.extensions.commands.converters.impl.*
import com.kotlindiscord.kord.extensions.extensions.*
import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator
import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder
import com.kotlindiscord.kord.extensions.utils.dm
import com.kotlindiscord.kord.extensions.utils.download
import com.kotlindiscord.kord.extensions.utils.getKoin
Expand Down Expand Up @@ -614,7 +612,7 @@ class UserFunExtension : Extension() {
content = """
|#$id:
|> ${quote.quote.replace("\n", "\n> ")}
|*- ${quote.author}*
|*\- ${quote.author}*
""".trimMargin()
allowedMentions {
// By defining this, all mentions are prohibited, so nobody gets pinged from the quote
Expand Down Expand Up @@ -673,38 +671,26 @@ class UserFunExtension : Extension() {
val quotes = quoteCollection.getAll()
val user = user.asUser()

val paginator = PaginatorBuilder()

val pageTitle = "List of quotes"

quotes.map {
val chunkedQuotes = quotes.map {
val author = it.author
val quote = it.quote
val id = it._id

"""
|#$id:
|> $quote
|*- $author*
""".trimMargin()
}.toList().chunked(QUOTES_PER_PAGE).forEach { quotesInPage ->
paginator.page {
title = pageTitle
description = quotesInPage.joinToString("\n\n")
}
}

val channel = user.getDmChannelOrNull()
if (channel == null) {
respond {
content = "**Error:** You must allow DMs from the but to list quotes."
}
} else {
val messagePaginator = MessageButtonPaginator(targetChannel = channel, builder = paginator)
messagePaginator.send()

respond {
content = "Sent! Check your DMs!"
|#$id:
|> $quote
|*\- $author*
""".trimMargin()
}.toList().chunked(QUOTES_PER_PAGE)

editingPaginator {
chunkedQuotes.forEach { quotesInPage ->
page {
title = pageTitle
description = quotesInPage.joinToString("\n\n")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ class UtilityExtension : Extension() {
}
}

GUILDS.forEach { guildId ->
getGuilds().forEach { guildId ->
ephemeralSlashCommand(::SelfTimeoutArguments) {
name = "self-timeout"
description = "Time yourself out for up to three days"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class FilterExtension : Extension() {
}
}

GUILDS.forEach { guildId ->
getGuilds().forEach { guildId ->
ephemeralSlashCommand {
name = "filters"
description = "Filter management commands"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class GithubExtension : Extension() {
}

override suspend fun setup() {
for (guildId in GUILDS) {
for (guildId in getGuilds()) {
ephemeralSlashCommand {
name = "github"
description = "GitHub management commands"
Expand Down
Loading

0 comments on commit 6dd079d

Please sign in to comment.