Skip to content
Merged
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
10 changes: 5 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2024 Avery Carroll and Logan Devecka
* Copyright 2017-2026 Avery Carroll and Logan Devecka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,8 +16,8 @@

plugins {
application
kotlin("jvm") version "2.3.0"
kotlin("plugin.serialization") version "2.3.0"
kotlin("jvm") version "2.3.10"
kotlin("plugin.serialization") version "2.3.10"
id("com.github.gmazzo.buildconfig") version "6.0.7"
id("io.ktor.plugin") version "3.4.0"
}
Expand All @@ -44,8 +44,8 @@ dependencies {
exclude(module = "tink")
}

implementation("ch.qos.logback:logback-classic:1.5.25")
implementation("io.sentry:sentry-logback:8.31.0")
implementation("ch.qos.logback:logback-classic:1.5.29")
implementation("io.sentry:sentry-logback:8.32.0")
implementation("net.jodah:expiringmap:0.5.11")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/sandrabot/sandra/Main.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2024 Avery Carroll and Logan Devecka
* Copyright 2017-2026 Avery Carroll and Logan Devecka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -111,7 +111,7 @@ private fun shutdownHook(sandra: Sandra) = with(sandra) {
try {
// disable any auxiliary services first
botList.shutdown()
blocklist.shutdown()
access.shutdown()
subscriptions.shutdown()

// stop accepting new requests and sign out from discord
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/sandrabot/sandra/Sandra.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2024 Avery Carroll and Logan Devecka
* Copyright 2017-2026 Avery Carroll and Logan Devecka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -43,7 +43,7 @@ class Sandra(val settings: SandraConfig, val redis: RedisManager) {
val commands = CommandManager()
val messages = MessageManager()
val statistics = StatisticsManager()
val blocklist = BlocklistManager(this)
val access = AccessManager(this)
val botList = BotListService(this)
val config = ConfigurationManager(this)
val lastfm = LastRequestManager(this)
Expand Down
8 changes: 7 additions & 1 deletion src/main/kotlin/com/sandrabot/sandra/config/FeatureConfig.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2024 Avery Carroll and Logan Devecka
* Copyright 2017-2026 Avery Carroll and Logan Devecka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@

package com.sandrabot.sandra.config

import com.sandrabot.sandra.entities.FeatureFlag
import kotlinx.serialization.Serializable

/**
Expand All @@ -24,6 +25,11 @@ import kotlinx.serialization.Serializable
@Serializable
data class FeatureConfig(

/**
* A set of features [FeatureFlag] that should be disabled globally.
*/
val disabledFeatures: Set<FeatureFlag> = setOf(),

/**
* The channel where feedback messages should be sent.
* **(Default: 0)**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 Avery Carroll and Logan Devecka
* Copyright 2017-2026 Avery Carroll and Logan Devecka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,16 +14,10 @@
* limitations under the License.
*/

package com.sandrabot.sandra.entities.blocklist
package com.sandrabot.sandra.entities

enum class FeatureType {
enum class FeatureFlag {

COMMANDS, FEEDBACK, GUILD_CHAT, GLOBAL_EXPERIENCE, MESSAGES

}

enum class TargetType {

GUILD, USER
ALL, AUTO_ROLE, COMMANDS, EXPERIENCE, FEEDBACK, LASTFM, LOGGING, MINIGAMES, NOTIFY, SOCIAL, WELCOME

}
78 changes: 0 additions & 78 deletions src/main/kotlin/com/sandrabot/sandra/entities/blocklist/entry.kt

This file was deleted.

12 changes: 10 additions & 2 deletions src/main/kotlin/com/sandrabot/sandra/listeners/GuildListener.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package com.sandrabot.sandra.listeners
import com.sandrabot.sandra.Sandra
import com.sandrabot.sandra.config.GuildConfig
import com.sandrabot.sandra.constants.ContentStore
import com.sandrabot.sandra.entities.FeatureFlag
import com.sandrabot.sandra.utils.cleanRoleData
import com.sandrabot.sandra.utils.isFeatureRestricted
import dev.minn.jda.ktx.events.CoroutineEventListener
import net.dv8tion.jda.api.Permission
import net.dv8tion.jda.api.entities.Role
Expand Down Expand Up @@ -66,6 +68,8 @@ class GuildListener(private val sandra: Sandra) : CoroutineEventListener {

// FEATURE: Default Bot Role
if (guildConfig.autoRolesEnabled && guildConfig.defaultBotRole != 0L) {
// check with the access manager first thing
if (isFeatureRestricted(sandra, event.guild.idLong, FeatureFlag.AUTO_ROLE)) return
// there's nothing we can do if we don't have this permission
if (!selfMember.hasPermission(Permission.MANAGE_ROLES)) return
val botRole = event.guild.getRoleById(guildConfig.defaultBotRole) ?: run {
Expand Down Expand Up @@ -94,8 +98,10 @@ class GuildListener(private val sandra: Sandra) : CoroutineEventListener {
}

private fun handleAutoRoles(event: GenericGuildMemberEvent, guildConfig: GuildConfig) {
val selfMember = event.guild.selfMember
// first things first, check with the access manager
if (isFeatureRestricted(sandra, event.guild.idLong, FeatureFlag.AUTO_ROLE)) return

val selfMember = event.guild.selfMember
// there's nothing we can do if we don't have this permission
if (!selfMember.hasPermission(Permission.MANAGE_ROLES)) return
// a single role may be given for multiple reasons in certain situations
Expand Down Expand Up @@ -148,8 +154,10 @@ class GuildListener(private val sandra: Sandra) : CoroutineEventListener {
}

private fun onMemberRemove(event: GuildMemberRemoveEvent) {
val guildConfig = sandra.config[event.guild]
// don't preserve role data for restricted guilds
if (isFeatureRestricted(sandra, event.guild.idLong, FeatureFlag.AUTO_ROLE)) return

val guildConfig = sandra.config[event.guild]
// FEATURE: Save Roles
if (guildConfig.autoRolesEnabled && guildConfig.saveRolesEnabled) {
// attempt to store the member's current roles
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2024 Avery Carroll and Logan Devecka
* Copyright 2017-2026 Avery Carroll and Logan Devecka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,7 +21,7 @@ import com.sandrabot.sandra.events.CommandEvent
import com.sandrabot.sandra.events.asEphemeral
import com.sandrabot.sandra.exceptions.MissingArgumentException
import com.sandrabot.sandra.exceptions.MissingPermissionException
import com.sandrabot.sandra.utils.checkCommandBlocklist
import com.sandrabot.sandra.utils.isAccessRestricted
import com.sandrabot.sandra.utils.missingPermissionMessage
import dev.minn.jda.ktx.events.CoroutineEventListener
import net.dv8tion.jda.api.events.GenericEvent
Expand Down Expand Up @@ -52,8 +52,18 @@ class InteractionListener(private val sandra: Sandra) : CoroutineEventListener {

// the first thing we want to do is wrap this with our own object
val event = CommandEvent(command, slashEvent, sandra)
// check the blocklist to prevent responding in actively blocked contexts
if (checkCommandBlocklist(event)) return

// log the command context information for usage history
val channel = if (event.guild == null) "direct message"
else "${event.channel.name} [${event.channel.id}] | ${event.guild.name} [${event.guild.id}]"
LOGGER.info("$path | ${event.user.name} [${event.user.id}] | $channel | ${slashEvent.commandString}")

// and the next thing is gonna be checking with the access manager
if (event.isAccessRestricted() && !event.isOwner) {
event.replyWarning(event.getAny("core.restricted")).asEphemeral().queue()
return
}

// restrict owner commands from being used by anyone
if (command.isOwnerOnly && !event.isOwner) {
event.replyError(event.getAny("core.owner_only")).asEphemeral().queue()
Expand All @@ -73,11 +83,6 @@ class InteractionListener(private val sandra: Sandra) : CoroutineEventListener {
}
}

// log the command context information for usage history
val channel = if (event.guild == null) "direct message"
else "${event.channel.name} [${event.channel.id}] | ${event.guild.name} [${event.guild.id}]"
LOGGER.info("$path | ${event.user.name} [${event.user.id}] | $channel | ${slashEvent.commandString}")

// execute the command, catch any exceptions and log them
try {
command.execute(event)
Expand Down
28 changes: 11 additions & 17 deletions src/main/kotlin/com/sandrabot/sandra/listeners/MessageListener.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2024 Avery Carroll and Logan Devecka
* Copyright 2017-2026 Avery Carroll and Logan Devecka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,8 +18,8 @@ package com.sandrabot.sandra.listeners

import com.sandrabot.sandra.Sandra
import com.sandrabot.sandra.constants.Emotes
import com.sandrabot.sandra.entities.FeatureFlag
import com.sandrabot.sandra.entities.LocaleContext
import com.sandrabot.sandra.entities.blocklist.FeatureType
import com.sandrabot.sandra.utils.*
import dev.minn.jda.ktx.events.CoroutineEventListener
import net.dv8tion.jda.api.Permission
Expand Down Expand Up @@ -72,9 +72,6 @@ class MessageListener(private val sandra: Sandra) : CoroutineEventListener {
val guildId = event.guild.idLong
val channelId = event.channel.idLong

// Check the blocklist to prevent responding in actively blocked contexts
if (checkBlocklist(sandra, event.channel, authorId, guildId, FeatureType.MESSAGES)) return

val guildConfig = sandra.config.getGuild(guildId)
val memberConfig = guildConfig.getMember(authorId)
val channelConfig = guildConfig.getChannel(channelId)
Expand All @@ -91,7 +88,7 @@ class MessageListener(private val sandra: Sandra) : CoroutineEventListener {
val multiplier = guildConfig.computeMultiplier(channelConfig)
if (memberConfig.awardExperience(randomExperience(multiplier))) {
// Check if the guild has level up notifications enabled
if (guildConfig.experienceNotifyEnabled) {
if (guildConfig.experienceNotifyEnabled && isFeatureAllowed(sandra, guildId, FeatureFlag.NOTIFY)) {
// Check if the guild has a specific channel for notifications
// Otherwise, check if this channel can receive notifications
val notifyChannel = if (guildConfig.experienceNotifyChannel != 0L) {
Expand Down Expand Up @@ -119,11 +116,10 @@ class MessageListener(private val sandra: Sandra) : CoroutineEventListener {
}

// Feature: Global Experience
// Check to make sure this user is allowed to gain global experience
if (!checkBlocklist(sandra, event.channel, authorId, guildId, FeatureType.GLOBAL_EXPERIENCE)) {
// Check to see if this user can receive experience
// Award a random amount of experience between 15 and 25
if (userConfig.canExperience()) userConfig.awardExperience(randomExperience())
// Check to see if this user can receive experience
// Award a random amount of experience between 15 and 25
if (userConfig.canExperience() && isContextAllowed(sandra, authorId, guildId, FeatureFlag.EXPERIENCE)) {
userConfig.awardExperience(randomExperience())
}

// TODO Feature: Message Replies
Expand All @@ -132,12 +128,10 @@ class MessageListener(private val sandra: Sandra) : CoroutineEventListener {
/**
* Processes any private messages that the bot receives.
*/
private fun handlePrivateMessage(event: MessageReceivedEvent) = buildString {
append("Direct Message: ", event.author.name, " [", event.author.id, "] | ", event.message.contentDisplay)
with(event.message.attachments) {
if (isNotEmpty()) forEach { append("\nDirect Message Attachment: ${it.url}") }
}
}.let { LOGGER.info(it) }
private fun handlePrivateMessage(event: MessageReceivedEvent) {
LOGGER.info("Direct Message: ${event.author.name} [${event.author.id}] | ${event.message.contentDisplay}")
event.message.attachments.forEach { LOGGER.info("Direct Message Attachment: ${it.url}") }
}

private companion object {
private val LOGGER = LoggerFactory.getLogger(MessageListener::class.java)
Expand Down
Loading