Skip to content

Commit

Permalink
Clean usernames when dehoisting
Browse files Browse the repository at this point in the history
  • Loading branch information
duncte123 committed Jan 31, 2024
1 parent c42c369 commit 63b715a
Showing 1 changed file with 85 additions and 30 deletions.
115 changes: 85 additions & 30 deletions bot/src/main/kotlin/me/duncte123/skybot/commands/mod/DeHoistUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,107 @@ import me.duncte123.botcommons.messaging.MessageUtils.sendSuccess
import me.duncte123.skybot.Variables
import me.duncte123.skybot.commands.guild.mod.ModBaseCommand
import me.duncte123.skybot.objects.command.CommandContext
import me.duncte123.skybot.utils.FinderUtils
import me.duncte123.skybot.utils.GuildSettingsUtils
import net.dv8tion.jda.api.Permission
import net.dv8tion.jda.api.entities.Member
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent
import net.dv8tion.jda.api.hooks.ListenerAdapter
import java.lang.Character.UnicodeBlock
import java.text.Normalizer
import kotlin.random.Random

// private val regex = "[!\"#\$%&'()*+,-./](?:.*)".toRegex()
// private const val dehoistChar = "▪"

// Allow special kinds of unicode.
private val allowedUnicodeBlocks = setOf(
UnicodeBlock.ARABIC,
UnicodeBlock.GREEK,
UnicodeBlock.THAI,
)

private fun cleanUsername(username: String): String {
return Normalizer.normalize(username, Normalizer.Form.NFKC)
// TODO: is turning this into a char array more efficient?
.toCharArray()
.filter { it.isWhitespace() || it.isLetterOrDigit() || UnicodeBlock.of(it) in allowedUnicodeBlocks }
// .map { if (it.isTitleCase()) it.lowercase() else it }
.joinToString("") {
if (it.isTitleCase()) it.lowercase() else it.toString()
}
.trim()
}

private val Member.cleanedDisplayName: String
get() {
val cleanedName = cleanUsername(effectiveName)

if (cleanedName.isBlank()) {
return "Member_${Random.nextInt(guild.memberCount)}"
}

if (cleanedName == effectiveName) {
return effectiveName
}

return cleanedName
}

private fun shouldDehoist(member: Member): Boolean {
return member.cleanedDisplayName != member.effectiveName &&
member.guild.selfMember.hasPermission(Permission.NICKNAME_MANAGE)
}

private fun canAutoDehoist(member: Member, variables: Variables): Boolean {
return shouldDehoist(member) && GuildSettingsUtils.getGuild(member.guild.idLong, variables).isAutoDeHoist
}

class DeHoistCommand : ModBaseCommand() {
init {
this.requiresArgs = true
this.name = "dehoist"
this.help = "De-hoists a user"
this.usage = "<@user>"
this.help = "De-hoists a user and cleans their username"
this.usage = "<@user>/all"
this.userPermissions = arrayOf(Permission.NICKNAME_MANAGE)
this.botPermissions = arrayOf(Permission.NICKNAME_MANAGE)
}

override fun execute(ctx: CommandContext) {
val mentionedMembers = ctx.message.mentions.members
val args = ctx.args

if (mentionedMembers.size == 0) {
if (args.isEmpty()) {
this.sendUsageInstructions(ctx)
return
}

val toDehoist = mentionedMembers[0]
val selfMember = ctx.guild.selfMember
val firstArg = args.first()

if (firstArg == "all") {
sendMsg(ctx, "Cleaning all members with a hoisted or unicode username, please wait...")

ctx.guild.loadMembers {
if (selfMember.canInteract(it) && shouldDehoist(it)) {
it.modifyNickname(it.cleanedDisplayName)
.reason("de-hoist/nickname cleaning by ${ctx.author.asTag}")
.queue()
}
}.onSuccess {
sendMsg(ctx, "Cleaning complete!")
}
return
}

val foundMembers = FinderUtils.searchMembers(firstArg, ctx)

if (foundMembers.isEmpty()) {
this.sendUsageInstructions(ctx)
return
}

val toDehoist = foundMembers[0]
val member = ctx.member

if (!selfMember.canInteract(toDehoist)) {
Expand All @@ -61,43 +135,24 @@ class DeHoistCommand : ModBaseCommand() {
return
}

ctx.guild.modifyNickname(toDehoist, "\u25AA" + toDehoist.effectiveName)
.reason("de-hoist ctx ${ctx.author.asTag}").queue()
ctx.guild.modifyNickname(toDehoist, toDehoist.cleanedDisplayName)
.reason("de-hoist/nickname cleaning ${ctx.author.asTag}").queue()
sendSuccess(ctx.message)
}
}

class DeHoistListener(private val variables: Variables) : ListenerAdapter() {
private val regex = "[!\"#\$%&'()*+,-./](?:.*)".toRegex()
private val dehoistChar = ""

override fun onGuildMemberJoin(event: GuildMemberJoinEvent) {
if (shouldChangeName(event.member)) {
// the char \uD82F\uDCA2 or \u1BCA2 is a null char that puts a member to the bottom
event.guild.modifyNickname(event.member, dehoistChar + event.member.effectiveName)
if (canAutoDehoist(event.member, variables)) {
event.guild.modifyNickname(event.member, event.member.cleanedDisplayName)
.reason("auto de-hoist").queue()
}
}

override fun onGuildMemberUpdateNickname(event: GuildMemberUpdateNicknameEvent) {
if (shouldChangeName(event.member)) {
// the char \uD82F\uDCA2 or \u1BCA2 is a null char that puts a member to the bottom
event.guild.modifyNickname(event.member, dehoistChar + event.member.effectiveName)
if (canAutoDehoist(event.member, variables)) {
event.guild.modifyNickname(event.member, event.member.cleanedDisplayName)
.reason("auto de-hoist").queue()
}
}

/*
* This checks if we should change the nickname of a member to de-hoist it
* @return [Boolean] true if we should change the nickname
*/
private fun shouldChangeName(member: Member): Boolean {
val memberName = member.effectiveName
val matcher = regex.matches(memberName)
return (
!memberName.startsWith(dehoistChar) && matcher &&
member.guild.selfMember.hasPermission(Permission.NICKNAME_MANAGE) &&
GuildSettingsUtils.getGuild(member.guild.idLong, this.variables).isAutoDeHoist
)
}
}

0 comments on commit 63b715a

Please sign in to comment.