@@ -23,33 +23,107 @@ import me.duncte123.botcommons.messaging.MessageUtils.sendSuccess
2323import me.duncte123.skybot.Variables
2424import me.duncte123.skybot.commands.guild.mod.ModBaseCommand
2525import me.duncte123.skybot.objects.command.CommandContext
26+ import me.duncte123.skybot.utils.FinderUtils
2627import me.duncte123.skybot.utils.GuildSettingsUtils
2728import net.dv8tion.jda.api.Permission
2829import net.dv8tion.jda.api.entities.Member
2930import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent
3031import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent
3132import net.dv8tion.jda.api.hooks.ListenerAdapter
33+ import java.lang.Character.UnicodeBlock
34+ import java.text.Normalizer
35+ import kotlin.random.Random
36+
37+ // private val regex = "[!\"#\$%&'()*+,-./](?:.*)".toRegex()
38+ // private const val dehoistChar = "▪"
39+
40+ // Allow special kinds of unicode.
41+ private val allowedUnicodeBlocks = setOf (
42+ UnicodeBlock .ARABIC ,
43+ UnicodeBlock .GREEK ,
44+ UnicodeBlock .THAI ,
45+ )
46+
47+ private fun cleanUsername (username : String ): String {
48+ return Normalizer .normalize(username, Normalizer .Form .NFKC )
49+ // TODO: is turning this into a char array more efficient?
50+ .toCharArray()
51+ .filter { it.isWhitespace() || it.isLetterOrDigit() || UnicodeBlock .of(it) in allowedUnicodeBlocks }
52+ // .map { if (it.isTitleCase()) it.lowercase() else it }
53+ .joinToString(" " ) {
54+ if (it.isTitleCase()) it.lowercase() else it.toString()
55+ }
56+ .trim()
57+ }
58+
59+ private val Member .cleanedDisplayName: String
60+ get() {
61+ val cleanedName = cleanUsername(effectiveName)
62+
63+ if (cleanedName.isBlank()) {
64+ return " Member_${Random .nextInt(guild.memberCount)} "
65+ }
66+
67+ if (cleanedName == effectiveName) {
68+ return effectiveName
69+ }
70+
71+ return cleanedName
72+ }
73+
74+ private fun shouldDehoist (member : Member ): Boolean {
75+ return member.cleanedDisplayName != member.effectiveName &&
76+ member.guild.selfMember.hasPermission(Permission .NICKNAME_MANAGE )
77+ }
78+
79+ private fun canAutoDehoist (member : Member , variables : Variables ): Boolean {
80+ return shouldDehoist(member) && GuildSettingsUtils .getGuild(member.guild.idLong, variables).isAutoDeHoist
81+ }
3282
3383class DeHoistCommand : ModBaseCommand () {
3484 init {
3585 this .requiresArgs = true
3686 this .name = " dehoist"
37- this .help = " De-hoists a user"
38- this .usage = " <@user>"
87+ this .help = " De-hoists a user and cleans their username "
88+ this .usage = " <@user>/all "
3989 this .userPermissions = arrayOf(Permission .NICKNAME_MANAGE )
4090 this .botPermissions = arrayOf(Permission .NICKNAME_MANAGE )
4191 }
4292
4393 override fun execute (ctx : CommandContext ) {
44- val mentionedMembers = ctx.message.mentions.members
94+ val args = ctx.args
4595
46- if (mentionedMembers.size == 0 ) {
96+ if (args.isEmpty() ) {
4797 this .sendUsageInstructions(ctx)
4898 return
4999 }
50100
51- val toDehoist = mentionedMembers[0 ]
52101 val selfMember = ctx.guild.selfMember
102+ val firstArg = args.first()
103+
104+ if (firstArg == " all" ) {
105+ sendMsg(ctx, " Cleaning all members with a hoisted or unicode username, please wait..." )
106+
107+ ctx.guild.loadMembers {
108+ if (selfMember.canInteract(it) && shouldDehoist(it)) {
109+ it.modifyNickname(it.cleanedDisplayName)
110+ .reason(" de-hoist/nickname cleaning by ${ctx.author.asTag} " )
111+ .queue()
112+ }
113+ }.onSuccess {
114+ sendMsg(ctx, " Cleaning complete!" )
115+ }
116+ return
117+ }
118+
119+ val foundMembers = FinderUtils .searchMembers(firstArg, ctx)
120+
121+ if (foundMembers.isEmpty()) {
122+ this .sendUsageInstructions(ctx)
123+ return
124+ }
125+
126+ val toDehoist = foundMembers[0 ]
53127 val member = ctx.member
54128
55129 if (! selfMember.canInteract(toDehoist)) {
@@ -61,43 +135,24 @@ class DeHoistCommand : ModBaseCommand() {
61135 return
62136 }
63137
64- ctx.guild.modifyNickname(toDehoist, " \u25AA " + toDehoist.effectiveName )
65- .reason(" de-hoist ctx ${ctx.author.asTag} " ).queue()
138+ ctx.guild.modifyNickname(toDehoist, toDehoist.cleanedDisplayName )
139+ .reason(" de-hoist/nickname cleaning ${ctx.author.asTag} " ).queue()
66140 sendSuccess(ctx.message)
67141 }
68142}
69143
70144class DeHoistListener (private val variables : Variables ) : ListenerAdapter() {
71- private val regex = " [!\" #\$ %&'()*+,-./](?:.*)" .toRegex()
72- private val dehoistChar = " ▪"
73-
74145 override fun onGuildMemberJoin (event : GuildMemberJoinEvent ) {
75- if (shouldChangeName(event.member)) {
76- // the char \uD82F\uDCA2 or \u1BCA2 is a null char that puts a member to the bottom
77- event.guild.modifyNickname(event.member, dehoistChar + event.member.effectiveName)
146+ if (canAutoDehoist(event.member, variables)) {
147+ event.guild.modifyNickname(event.member, event.member.cleanedDisplayName)
78148 .reason(" auto de-hoist" ).queue()
79149 }
80150 }
81151
82152 override fun onGuildMemberUpdateNickname (event : GuildMemberUpdateNicknameEvent ) {
83- if (shouldChangeName(event.member)) {
84- // the char \uD82F\uDCA2 or \u1BCA2 is a null char that puts a member to the bottom
85- event.guild.modifyNickname(event.member, dehoistChar + event.member.effectiveName)
153+ if (canAutoDehoist(event.member, variables)) {
154+ event.guild.modifyNickname(event.member, event.member.cleanedDisplayName)
86155 .reason(" auto de-hoist" ).queue()
87156 }
88157 }
89-
90- /*
91- * This checks if we should change the nickname of a member to de-hoist it
92- * @return [Boolean] true if we should change the nickname
93- */
94- private fun shouldChangeName (member : Member ): Boolean {
95- val memberName = member.effectiveName
96- val matcher = regex.matches(memberName)
97- return (
98- ! memberName.startsWith(dehoistChar) && matcher &&
99- member.guild.selfMember.hasPermission(Permission .NICKNAME_MANAGE ) &&
100- GuildSettingsUtils .getGuild(member.guild.idLong, this .variables).isAutoDeHoist
101- )
102- }
103158}
0 commit comments