diff --git a/commands.md b/commands.md index 015453b..296be17 100644 --- a/commands.md +++ b/commands.md @@ -54,19 +54,20 @@ | rules | [Message] | List the rules of this guild. Pass a message ID to edit existing rules embed. | ## User -| Commands | Arguments | Description | -| ------------- | ----------------------------------------- | ---------------------------------------------------------- | -| alts | User | Use this to view a user's alt accounts. | -| ban | LowerUserArg, [Delete message days], Text | Ban a member from this guild. | -| getBanReason | User | Get a ban reason for a banned user | -| history, h, H | User | Use this to view a user's record. | -| link | Main Account, Alt Account | Link a user's alt account with their main | -| reset | LowerUserArg | Reset a user's record, and any linked accounts | -| selfHistory | | View your infraction history (contents will be DM'd) | -| setBanReason | User, Reason | Set a ban reason for a banned user | -| unban | User | Unban a banned member from this guild. | -| unlink | Main Account, Alt Account | Link a user's alt account with their main | -| whatpfp | User | Perform a reverse image search of a User's profile picture | +| Commands | Arguments | Description | +| --------------- | ----------------------------------------- | --------------------------------------------------------------- | +| alts | User | Use this to view a user's alt accounts. | +| ban | LowerUserArg, [Delete message days], Text | Ban a member from this guild. | +| deletedMessages | LowerUserArg | View a users messages deleted using the delete message reaction | +| getBanReason | User | Get a ban reason for a banned user | +| history, h, H | User | Use this to view a user's record. | +| link | Main Account, Alt Account | Link a user's alt account with their main | +| reset | LowerUserArg | Reset a user's record, and any linked accounts | +| selfHistory | | View your infraction history (contents will be DM'd) | +| setBanReason | User, Reason | Set a ban reason for a banned user | +| unban | User | Unban a banned member from this guild. | +| unlink | Main Account, Alt Account | Link a user's alt account with their main | +| whatpfp | User | Perform a reverse image search of a User's profile picture | ## Utility | Commands | Arguments | Description | diff --git a/src/main/kotlin/me/ddivad/judgebot/arguments/LowerMemberArg.kt b/src/main/kotlin/me/ddivad/judgebot/arguments/LowerMemberArg.kt index 84ab0a7..e1bc712 100644 --- a/src/main/kotlin/me/ddivad/judgebot/arguments/LowerMemberArg.kt +++ b/src/main/kotlin/me/ddivad/judgebot/arguments/LowerMemberArg.kt @@ -1,6 +1,7 @@ package me.ddivad.judgebot.arguments import dev.kord.core.entity.Member +import me.ddivad.judgebot.dataclasses.Configuration import me.jakejmattson.discordkt.arguments.* import me.jakejmattson.discordkt.commands.CommandEvent import me.jakejmattson.discordkt.extensions.isSelf @@ -15,6 +16,7 @@ open class LowerMemberArg(override val name: String = "LowerMemberArg") : Argume override suspend fun convert(arg: String, args: List, event: CommandEvent<*>): ArgumentResult { val guild = event.guild ?: return Error("No guild found") + val configuration = event.discord.getInjectionObjects(Configuration :: class) val member = arg.toSnowflakeOrNull()?.let { guild.getMemberOrNull(it) } ?: return Error("Not found") val author = event.author.asMember(event.guild!!.id) @@ -22,7 +24,8 @@ open class LowerMemberArg(override val name: String = "LowerMemberArg") : Argume return when { event.discord.permissions.isHigherLevel(event.discord, member, author) || event.author.isSelf() -> Error("You don't have the permission to use this command on the target user.") - event.author == member -> Error("You can't use this command on yourself!") + (event.author == member && member.id.toString() != configuration.ownerId) -> + Error("You can't use this command on yourself!") else -> Success(member) } } diff --git a/src/main/kotlin/me/ddivad/judgebot/arguments/LowerUserArg.kt b/src/main/kotlin/me/ddivad/judgebot/arguments/LowerUserArg.kt index c96d99b..9b69893 100644 --- a/src/main/kotlin/me/ddivad/judgebot/arguments/LowerUserArg.kt +++ b/src/main/kotlin/me/ddivad/judgebot/arguments/LowerUserArg.kt @@ -1,6 +1,7 @@ package me.ddivad.judgebot.arguments import dev.kord.core.entity.User +import me.ddivad.judgebot.dataclasses.Configuration import me.jakejmattson.discordkt.arguments.* import me.jakejmattson.discordkt.commands.CommandEvent import me.jakejmattson.discordkt.extensions.isSelf @@ -15,6 +16,7 @@ open class LowerUserArg(override val name: String = "LowerUserArg") : Argument, event: CommandEvent<*>): ArgumentResult { val guild = event.guild ?: return Error("No guild found") + val configuration = event.discord.getInjectionObjects(Configuration :: class) val user = arg.toSnowflakeOrNull()?.let { guild.kord.getUser(it) } ?: return Error("User Not Found") val member = guild.getMemberOrNull(user.id) ?: return Success(user) @@ -23,7 +25,8 @@ open class LowerUserArg(override val name: String = "LowerUserArg") : Argument Error("You don't have the permission to use this command on the target user.") - event.author == member -> Error("You can't use this command on yourself!") + (event.author == member && member.id.toString() != configuration.ownerId) -> + Error("You can't use this command on yourself!") else -> Success(member.asUser()) } } diff --git a/src/main/kotlin/me/ddivad/judgebot/commands/UserCommands.kt b/src/main/kotlin/me/ddivad/judgebot/commands/UserCommands.kt index 248a054..83e8085 100644 --- a/src/main/kotlin/me/ddivad/judgebot/commands/UserCommands.kt +++ b/src/main/kotlin/me/ddivad/judgebot/commands/UserCommands.kt @@ -1,6 +1,7 @@ package me.ddivad.judgebot.commands import dev.kord.common.kColor +import dev.kord.rest.Image import dev.kord.x.emoji.Emojis import dev.kord.x.emoji.addReaction import kotlinx.coroutines.flow.toList @@ -16,10 +17,9 @@ import me.ddivad.judgebot.services.LoggingService import me.ddivad.judgebot.services.infractions.BanService import me.jakejmattson.discordkt.arguments.* import me.jakejmattson.discordkt.commands.commands -import me.jakejmattson.discordkt.extensions.mutualGuilds -import me.jakejmattson.discordkt.extensions.pfpUrl -import me.jakejmattson.discordkt.extensions.sendPrivateMessage +import me.jakejmattson.discordkt.extensions.* import java.awt.Color +import java.text.SimpleDateFormat @Suppress("unused") fun createUserCommands( @@ -195,4 +195,57 @@ fun createUserCommands( .startPublicly(discord, author, channel) } } + + command("deletedMessages") { + description = "View a users messages deleted using the delete message reaction" + requiredPermission = Permissions.STAFF + execute(LowerUserArg) { + val target = args.first + val guildMember = databaseService.users.getOrCreateUser(target, guild).getGuildInfo(guild.id.toString()) + val guildConfiguration = config[guild.asGuild().id.value] + + val deletedMessages = databaseService.messageDeletes + .getMessageDeletesForMember(guild.id.toString(), target.id.toString()) + .sortedByDescending { it.dateTime } + .map { "Deleted on **${SimpleDateFormat("dd/MM/yyyy HH:mm").format(it.dateTime)}** \n[Message Link](${it.messageLink})" } + .chunked(6) + + respondMenu { + deletedMessages.forEachIndexed { index, list -> + page { + color = discord.configuration.theme + author { + name = "Deleted messages for ${target.tag}" + icon = target.pfpUrl + } + description = """ + **Showing messages deleted using ${guildConfiguration?.reactions?.deleteMessageReaction}** + ${target.tag} has **${guildMember.deletedMessageCount.deleteReaction}** deletions + """.trimIndent() + + list.forEach { + field { + value = it + } + } + + footer { + icon = guild.getIconUrl(Image.Format.PNG) ?: "" + text = "${guild.name} | Page ${index + 1} of ${deletedMessages.size}" + } + } + } + if (deletedMessages.size > 1) { + buttons { + button("Prev.", Emojis.arrowLeft) { + previousPage() + } + button("Next", Emojis.arrowRight) { + nextPage() + } + } + } + } + } + } } diff --git a/src/main/kotlin/me/ddivad/judgebot/dataclasses/MessageDelete.kt b/src/main/kotlin/me/ddivad/judgebot/dataclasses/MessageDelete.kt new file mode 100644 index 0000000..3b7e04d --- /dev/null +++ b/src/main/kotlin/me/ddivad/judgebot/dataclasses/MessageDelete.kt @@ -0,0 +1,10 @@ +package me.ddivad.judgebot.dataclasses + +import java.util.Date + +data class MessageDelete( + val userId: String, + val guildId: String, + val messageLink: String?, + val dateTime: Long = Date().time, +) \ No newline at end of file diff --git a/src/main/kotlin/me/ddivad/judgebot/listeners/StaffReactionListeners.kt b/src/main/kotlin/me/ddivad/judgebot/listeners/StaffReactionListeners.kt index cad7c26..b8ece2e 100644 --- a/src/main/kotlin/me/ddivad/judgebot/listeners/StaffReactionListeners.kt +++ b/src/main/kotlin/me/ddivad/judgebot/listeners/StaffReactionListeners.kt @@ -14,6 +14,7 @@ import me.ddivad.judgebot.services.infractions.MuteService import me.ddivad.judgebot.services.infractions.RoleState import me.jakejmattson.discordkt.dsl.listeners import me.jakejmattson.discordkt.extensions.isSelf +import me.jakejmattson.discordkt.extensions.jumpLink import me.jakejmattson.discordkt.extensions.sendPrivateMessage @Suppress("unused") @@ -30,8 +31,9 @@ fun onStaffReactionAdd( val staffMember = user.asMemberOrNull(guild.id) ?: return@on val msg = message.asMessage() val messageAuthor = msg.author?.asMemberOrNull(guild.id) ?: return@on - if (discord.permissions.hasPermission(discord, staffMember, Permissions.MODERATOR) + if ((discord.permissions.hasPermission(discord, staffMember, Permissions.MODERATOR) && discord.permissions.isHigherLevel(discord, staffMember, messageAuthor) + || staffMember.id.toString() == configuration.ownerId) ) { when (this.emoji.name) { guildConfiguration.reactions.gagReaction -> { @@ -57,7 +59,7 @@ fun onStaffReactionAdd( loggingService.staffReactionUsed(guild, staffMember, messageAuthor, this.emoji) } guildConfiguration.reactions.deleteMessageReaction -> { - msg.delete() + message.delete() databaseService.users.addMessageDelete( guild, databaseService.users.getOrCreateUser(messageAuthor, guild.asGuild()), @@ -73,7 +75,8 @@ fun onStaffReactionAdd( " Message deleted without notification." ) } - loggingService.staffReactionUsed(guild, staffMember, messageAuthor, this.emoji) + val deleteLogMessage = loggingService.deleteReactionUsed(guild, staffMember, messageAuthor, this.emoji, msg) + databaseService.messageDeletes.createMessageDeleteRecord(guildId.toString(), messageAuthor, deleteLogMessage.first()?.jumpLink()) } Emojis.question.unicode -> { if (this.user.isSelf() || msg.author != this.message.kord.getSelf()) return@on diff --git a/src/main/kotlin/me/ddivad/judgebot/services/DatabaseService.kt b/src/main/kotlin/me/ddivad/judgebot/services/DatabaseService.kt index c5da639..d361701 100644 --- a/src/main/kotlin/me/ddivad/judgebot/services/DatabaseService.kt +++ b/src/main/kotlin/me/ddivad/judgebot/services/DatabaseService.kt @@ -2,6 +2,7 @@ package me.ddivad.judgebot.services import me.ddivad.judgebot.services.database.GuildOperations import me.ddivad.judgebot.services.database.JoinLeaveOperations +import me.ddivad.judgebot.services.database.MessageDeleteOperations import me.ddivad.judgebot.services.database.UserOperations import me.jakejmattson.discordkt.annotations.Service @@ -9,5 +10,6 @@ import me.jakejmattson.discordkt.annotations.Service open class DatabaseService( val users: UserOperations, val guilds: GuildOperations, - val joinLeaves: JoinLeaveOperations + val joinLeaves: JoinLeaveOperations, + val messageDeletes: MessageDeleteOperations ) \ No newline at end of file diff --git a/src/main/kotlin/me/ddivad/judgebot/services/LoggingService.kt b/src/main/kotlin/me/ddivad/judgebot/services/LoggingService.kt index 241b702..81c2bb9 100644 --- a/src/main/kotlin/me/ddivad/judgebot/services/LoggingService.kt +++ b/src/main/kotlin/me/ddivad/judgebot/services/LoggingService.kt @@ -79,6 +79,19 @@ class LoggingService(private val configuration: Configuration) { suspend fun staffReactionUsed(guild: Guild, moderator: User, target: Member, reaction: ReactionEmoji) = log(guild, "**Info ::** ${reaction.name} used by ${moderator.username} on ${target.mention}") + suspend fun deleteReactionUsed(guild: Guild, moderator: User, target: Member, reaction: ReactionEmoji, message: Message): List { + val msg = message.content.chunked(1800) + val firstMessage = logAndReturnMessage(guild, + "**Info ::** ${reaction.name} used by ${moderator.username} on ${target.mention}\n" + + "**Message:**```\n" + + "${msg.first()}\n```") + + val rest = msg.takeLast(msg.size - 1).map { + logAndReturnMessage(guild, "**Continued:**```\n$it\n```")} + + return listOf(firstMessage).plus(rest) + } + suspend fun pointDecayApplied(guild: Guild, target: GuildMember, newPoints: Int, pointsDeducted: Int, weeksSinceLastInfraction: Int) { val user = guild.kord.getUser(Snowflake(target.userId)) @@ -95,6 +108,11 @@ class LoggingService(private val configuration: Configuration) { println("${SimpleDateFormat("dd/M/yyyy HH:mm:ss").format(Date())} > ${guild.name} > $message") } + private suspend fun logAndReturnMessage(guild: Guild, message: String): Message? { + println("${SimpleDateFormat("dd/M/yyyy HH:mm:ss").format(Date())} > ${guild.name} > $message") + return getLoggingChannel(guild)?.createMessage(message) + } + private suspend fun getLoggingChannel(guild: Guild): TextChannel? { val channelId = configuration[guild.id.value]?.loggingConfiguration?.loggingChannel.takeIf { it!!.isNotEmpty() } ?: return null diff --git a/src/main/kotlin/me/ddivad/judgebot/services/database/MessageDeleteOperations.kt b/src/main/kotlin/me/ddivad/judgebot/services/database/MessageDeleteOperations.kt new file mode 100644 index 0000000..6edf30d --- /dev/null +++ b/src/main/kotlin/me/ddivad/judgebot/services/database/MessageDeleteOperations.kt @@ -0,0 +1,26 @@ +package me.ddivad.judgebot.services.database + +import dev.kord.core.entity.Member +import me.ddivad.judgebot.dataclasses.MessageDelete +import me.jakejmattson.discordkt.annotations.Service +import org.litote.kmongo.and +import org.litote.kmongo.eq + +@Service +class MessageDeleteOperations(connection: ConnectionService) { + private val messageDeleteCollection = connection.db.getCollection("MessageDelete") + + suspend fun createMessageDeleteRecord(guildId: String, target: Member, messageLink: String?) { + val record = MessageDelete(target.id.toString(), guildId, messageLink) + messageDeleteCollection.insertOne(record) + } + + suspend fun getMessageDeletesForMember(guildId: String, userId: String): List { + return messageDeleteCollection.find( + and( + MessageDelete::guildId eq guildId, + MessageDelete::userId eq userId, + ) + ).toList() + } +} \ No newline at end of file