Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=6.0.18-SNAPSHOT
version=6.0.20-SNAPSHOT
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.slne.surf.discord.command.console.impl

import dev.slne.surf.discord.command.console.ConsoleCommand
import dev.slne.surf.discord.ticket.database.whitelist.SocialsMigration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.springframework.stereotype.Component

@Component
class SocialsMigrationCommand(
private val discordScope: CoroutineScope
) : ConsoleCommand {
override val name = "migrate-socials"

override fun execute(args: List<String>) {
discordScope.launch {
println("Starting social whitelist migration...")
SocialsMigration.migrateLegacy()

println("\nMigration finished.")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import dev.slne.surf.discord.ticket.database.messages.attachments.TicketAttachme
import dev.slne.surf.discord.ticket.database.ticket.TicketTable
import dev.slne.surf.discord.ticket.database.ticket.data.TicketDataTable
import dev.slne.surf.discord.ticket.database.ticket.staff.TicketStaffTable
import dev.slne.surf.discord.ticket.database.whitelist.SocialsTable
import dev.slne.surf.discord.ticket.database.whitelist.FreebuildWhitelistTable
import dev.slne.surf.discord.ticket.database.whitelist.SocialConnectionsTable
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import org.jetbrains.annotations.ApiStatus
Expand Down Expand Up @@ -44,7 +45,8 @@ class DatabaseConfiguration {
TicketStaffTable,
TicketMessagesTable,
TicketAttachmentsTable,
SocialsTable
SocialConnectionsTable,
FreebuildWhitelistTable
)
}
logger.info("Connected to database ${botConfig.database.database} at ${botConfig.database.hostname}:${botConfig.database.port}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dev.slne.surf.discord.dsl.modal
import dev.slne.surf.discord.interaction.modal.DiscordModal
import dev.slne.surf.discord.messages.translatable
import dev.slne.surf.discord.ticket.database.whitelist.SocialService
import dev.slne.surf.discord.util.PlayerLookupService
import net.dv8tion.jda.api.components.selections.SelectOption
import net.dv8tion.jda.api.components.selections.StringSelectMenu
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent
Expand All @@ -12,7 +13,8 @@ import org.springframework.stereotype.Component

@Component
class SurvivalWhitelistEditModal(
private val socialService: SocialService
private val socialService: SocialService,
private val playerLookupService: PlayerLookupService
) : DiscordModal {
override val id = "whitelist:modal:edit-survival"

Expand All @@ -32,6 +34,13 @@ class SurvivalWhitelistEditModal(
required = true
}

textInput {
id = "whitelist:modal:edit-survival:old-discord-id"
label = "Interne Verwaltungs-ID (nicht ändern!)"
value = data[2]
required = true
}
Comment on lines +37 to +42
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old-discord-id is used as the record selector for updates, but it's a normal editable text input. Despite the label warning, users can accidentally change it and end up editing a different whitelist entry than intended. Consider not taking this value from user input (e.g., keep the original ID in server-side state / modal custom-id payload), or at least validating it against the originally opened entry before applying changes.

Copilot uses AI. Check for mistakes.

selectMenu(
translatable("whitelist.survival.edit.modal.blocked.label"),
StringSelectMenu
Expand All @@ -46,6 +55,9 @@ class SurvivalWhitelistEditModal(
}

override suspend fun onSubmit(event: ModalInteractionEvent) {
val oldDiscordId =
event.getValue("whitelist:modal:edit-survival:old-discord-id")?.asString?.toLongOrNull()
?: return
val minecraftName =
event.getValue("whitelist:modal:edit-survival:minecraft-name")?.asString ?: return
val discordId =
Expand All @@ -56,11 +68,57 @@ class SurvivalWhitelistEditModal(
?.toBooleanStrictOrNull() ?: return

val discordName = event.jda.getUserById(discordId)?.name ?: discordId.toString()
val oldWhitelist = socialService.getWhitelist(oldDiscordId)

if (oldWhitelist == null) {
event.reply(translatable("whitelist.embed.information.no_whitelist"))
.setEphemeral(true)
.queue()
return
}

socialService.updateWhitelist(discordId, minecraftName, blocked)
val minecraftUuid = playerLookupService.getUuid(minecraftName)

event.reply(translatable("whitelist.embed.information.successfully-edited", discordName))
if (minecraftUuid == null) {
event.reply(translatable("whitelist.survival.edit.modal.invalid_minecraft_name"))
.setEphemeral(true)
.queue()
return
}

if (oldWhitelist.minecraftUuid != minecraftUuid) {
if (!socialService.updateMinecraftName(oldDiscordId, minecraftName)) {
event.reply(translatable("whitelist.survival.edit.modal.failed.minecraft"))
.setEphemeral(true)
.queue()
return
}
}

if (oldWhitelist.blocked != blocked) {
if (!socialService.updateBlocked(oldDiscordId, blocked)) {
event.reply(translatable("whitelist.survival.edit.modal.failed.blocked"))
.setEphemeral(true)
.queue()
return
}
}

if (oldWhitelist.discordId != discordId) {
if (!socialService.updateDiscordId(oldDiscordId, discordId)) {
event.reply(translatable("whitelist.survival.edit.modal.failed.discord"))
.setEphemeral(true)
.queue()
return
}
}

event.reply(
translatable(
"whitelist.embed.information.successfully-edited",
discordName
)
)
.setEphemeral(true)
.queue()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum class DiscordPermission {
WHITELIST_BYPASS,
WHITELIST_EDIT,
WHITELIST_DELETE,
WHITELIST_CREATE,

TICKET_CLOSE,
TICKET_CLOSE_BYPASS_CLAIM,
Expand Down
26 changes: 14 additions & 12 deletions src/main/kotlin/dev/slne/surf/discord/permission/permission-util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,6 @@ private val guildPermissionConfig: Map<Long, Map<Long, Set<DiscordPermission>>>
// Server Admin
949704206888079490 to setOf(*DiscordPermission.entries.toTypedArray()),

// Twitch Mod
651104534529179660 to setOf(
DiscordPermission.TICKET_REPLY_DEADLINE,
DiscordPermission.TICKET_CLOSE,
DiscordPermission.TICKET_CLAIM,
DiscordPermission.TICKET_SUPPORT_TWITCH_VIEW
),


// Discord Moderation
156164562499010560 to setOf(
DiscordPermission.COMMAND_TICKET_ADD,
Expand All @@ -61,7 +52,8 @@ private val guildPermissionConfig: Map<Long, Map<Long, Set<DiscordPermission>>>
DiscordPermission.WHITELIST_EDIT,
DiscordPermission.TICKET_APPLICATION_TWITCH_MODERATOR,
DiscordPermission.WHITELIST_DELETE,
DiscordPermission.TICKET_COMPLAINT_VIEW
DiscordPermission.TICKET_COMPLAINT_VIEW,
DiscordPermission.WHITELIST_CREATE
),

// Management
Expand Down Expand Up @@ -89,7 +81,8 @@ private val guildPermissionConfig: Map<Long, Map<Long, Set<DiscordPermission>>>
DiscordPermission.WHITELIST_BYPASS,
DiscordPermission.WHITELIST_EDIT,
DiscordPermission.WHITELIST_DELETE,
DiscordPermission.TICKET_COMPLAINT_VIEW
DiscordPermission.TICKET_COMPLAINT_VIEW,
DiscordPermission.WHITELIST_CREATE
),

// Developer
Expand Down Expand Up @@ -117,7 +110,8 @@ private val guildPermissionConfig: Map<Long, Map<Long, Set<DiscordPermission>>>
DiscordPermission.WHITELIST_VIEW,
DiscordPermission.WHITELIST_BYPASS,
DiscordPermission.WHITELIST_EDIT,
DiscordPermission.WHITELIST_DELETE
DiscordPermission.WHITELIST_DELETE,
DiscordPermission.WHITELIST_CREATE
),

// Moderator
Expand Down Expand Up @@ -154,6 +148,14 @@ private val guildPermissionConfig: Map<Long, Map<Long, Set<DiscordPermission>>>
DiscordPermission.WHITELIST_VIEW
),

// Twitch Mod
651104534529179660 to setOf(
DiscordPermission.TICKET_REPLY_DEADLINE,
DiscordPermission.TICKET_CLOSE,
DiscordPermission.TICKET_CLAIM,
DiscordPermission.TICKET_SUPPORT_TWITCH_VIEW
),

// Community Management
1403107386415386736L to setOf(
DiscordPermission.COMMAND_FAQ
Expand Down
4 changes: 4 additions & 0 deletions src/main/kotlin/dev/slne/surf/discord/ticket/TicketType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ enum class TicketType(
displayName = "Bug nicht reproduzierbar",
description = "Der Fehler konnte nicht reproduziert werden."
),
TicketCloseReason.of(
displayName = "Externer Fehler",
description = "Der Fehler liegt außerhalb unseres Einflusses und wurde an zuständige Stellen weitergeleitet."
),
TicketCloseReason.of(
displayName = "Bug behoben",
description = "Der gemeldete Fehler wurde behoben. Danke für deinen Bugreport!"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package dev.slne.surf.discord.ticket.command.whitelist

import dev.minn.jda.ktx.coroutines.await
import dev.slne.surf.discord.command.CommandOption
import dev.slne.surf.discord.command.CommandOptionType
import dev.slne.surf.discord.command.DiscordCommand
import dev.slne.surf.discord.command.SlashCommand
import dev.slne.surf.discord.config.botConfig
import dev.slne.surf.discord.jda
import dev.slne.surf.discord.messages.translatable
import dev.slne.surf.discord.permission.DiscordPermission
import dev.slne.surf.discord.permission.hasPermission
import dev.slne.surf.discord.ticket.database.whitelist.SocialService
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import org.springframework.stereotype.Component

@DiscordCommand(
name = "wl-create",
description = "Whitelist Eintrag erstellen",
options = [CommandOption(
name = "discord-user",
description = "Der Discord Nutzer, der gewhitelisted werden soll",
type = CommandOptionType.USER,
required = true
),
CommandOption(
name = "minecraft-name",
description = "Der Minecraft Name, der gewhitelisted werden soll.",
type = CommandOptionType.STRING,
required = true
)]
)
@Component
class CreateWhitelistCommand(
private val socialService: SocialService
) : SlashCommand {
override suspend fun execute(event: SlashCommandInteractionEvent) {
if (!event.member.hasPermission(DiscordPermission.WHITELIST_CREATE)) {
event.reply(translatable("no-permission")).setEphemeral(true).queue()
return
}

val userId = event.getOption("discord-user")?.asUser?.idLong ?: return
val minecraftName = event.getOption("minecraft-name")?.asString ?: return

if (socialService.getWhitelist(userId) != null) {
event.reply(translatable("whitelist.command.already-whitelisted.discord"))
.setEphemeral(true)
.queue()
return
}

if (socialService.getWhitelist(minecraftName) != null) {
event.reply(translatable("whitelist.command.already-whitelisted.minecraft"))
.setEphemeral(true)
.queue()
return
}

val whitelist = socialService.whitelist(userId, minecraftName)

val discordUser = event.jda.retrieveUserById(userId).await()

jda.guilds.forEach {
val role = it.getRoleById(botConfig.whitelistedRoleId) ?: return@forEach
it.addRoleToMember(discordUser, role).queue()
}

event.reply(
translatable(
"whitelist.command.create.success",
"<@${whitelist?.discordId}>",
whitelist?.getMinecraftName() ?: whitelist?.minecraftUuid.toString()
)
)
Comment on lines +60 to +75
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

socialService.whitelist(...) can return null when the Minecraft name can't be resolved. The command continues anyway, retrieves the Discord user, assigns roles, and replies with placeholders derived from nullable whitelist (e.g. <@null> / null). Add an early null-check and reply with an error message before performing role updates.

Copilot uses AI. Check for mistakes.
.setEphemeral(true)
.queue()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.slne.surf.discord.ticket.database.util

import dev.slne.surf.discord.ticket.database.column.offsetDateTime
import org.jetbrains.exposed.v1.core.dao.id.LongIdTable

open class AuditableLongIdTable(name: String) : LongIdTable(name) {
val createdAt = offsetDateTime("created_at")
val updatedAt = offsetDateTime("updated_at")
}
Loading