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
6 changes: 6 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -989,5 +989,11 @@
"label-jump": "Jump to Message",
"no-message-link": "This ping was blocked by AutoMod",
"list-entry-text": "%index. **Pinged %target** at %time\n%link"
},
"name-list-cleaner": {
"owner-cannot-be-renamed": "The owner of the server (%u) cannot be renamed.",
"nickname-error": "An error occurred while trying to change the nickname of %u: %e",
"nickname-reset": "The nickname of %u has been reset, as it started with a disallowed character.",
"nickname-changed": "The nickname of %u has been changed, as it started with a disallowed character."
}
}
90 changes: 90 additions & 0 deletions modules/name-list-cleaner/configs/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"description": {
"en": "Configure the function of the module here",
"de": "Stelle hier die Funktionen des Modules ein"
},
"humanName": {
"en": "Configuration",
"de": "Konfiguration"
},
"filename": "config.json",
"content": [
{
"name": "keepNickname",
"humanName": {
"en": "Keep nickname",
"de": "Nickname behalten"
},
"default": {
"en": true
},
"description": {
"en": "If yes: special characters will be removed from nicknames; if no: the nickname will be removed and the username will be shown instead.",
"de": "Wenn ja: Sonderzeichen werden aus den Nicknames entfernt; wenn nein: der Nickname wird entfernt und stattdessen der Benutzername angezeigt."
},
"type": "boolean"
},
{
"name": "symbolWhitelist",
"humanName": {
"en": "Character Whitelist/Blacklist",
"de": "Zeichen Whitelist/Blacklist"
},
"default": {
"en": []
},
"description": {
"en": "A list of characters that should be allowed (whitelist) or blocked (blacklist) at the start of usernames. If the list is empty, all non-alphanumeric characters will be removed.",
"de": "Eine Liste von Zeichen, die am Anfang von Benutzernamen erlaubt (Whitelist) oder blockiert (Blacklist) werden sollen. Wenn die Liste leer ist, werden alle nicht-alphanumerischen Zeichen entfernt."
},
"type": "array",
"content": "string"
},
{
"name": "isBlacklist",
"humanName": {
"en": "Use blacklist instead of whitelist",
"de": "Blacklist statt Whitelist verwenden"
},
"default": {
"en": false
},
"description": {
"en": "If yes: the list of characters will be treated as a blacklist (characters in the list will be blocked); if no: the list will be treated as a whitelist (only characters in the list and alphanumeric characters will be allowed).",
"de": "Wenn ja: die Liste der Zeichen wird als Blacklist behandelt (Zeichen in der Liste werden blockiert); wenn nein: die Liste wird als Whitelist behandelt (nur Zeichen in der Liste und alphanumerische Zeichen werden erlaubt)."
},
"type": "boolean"
},
{
"name": "userWhitelist",
"humanName": {
"en": "User Whitelist",
"de": "Benutzer-Whitelist"
},
"default": {
"en": []
},
"description": {
"en": "A list of user IDs that should be exempt from the username check. Usernames of these users will not be modified.",
"de": "Eine Liste von Benutzer-IDs, die von der Benutzernamenprüfung ausgenommen werden sollen. Die Benutzernamen dieser Benutzer werden nicht geändert."
},
"type": "array",
"content": "userID"
},
{
"name": "alsoCheckUsernames",
"humanName": {
"en": "Also check usernames",
"de": "Auch Benutzernamen überprüfen"
},
"default": {
"en": false
},
"description": {
"en": "If yes: not only nicknames but also usernames will be checked for special characters and modified accordingly. If no: only nicknames will be checked and modified, usernames will be left unchanged.",
"de": "Wenn ja: nicht nur Nicknames, sondern auch Benutzernamen werden auf Sonderzeichen überprüft und entsprechend geändert. Wenn nein: nur Nicknames werden überprüft und geändert, Benutzernamen bleiben unverändert."
},
"type": "boolean"
}
]
}
7 changes: 7 additions & 0 deletions modules/name-list-cleaner/events/botReady.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const {renameMember} = require('../renameMember');

module.exports.run = async function (client) {
for (const member of client.guild.members.cache.values()) {
await renameMember(client, member);
}
}
9 changes: 9 additions & 0 deletions modules/name-list-cleaner/events/guildMemberUpdate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const {renameMember} = require("../renameMember");
module.exports.run = async function (client, oldGuildMember, newGuildMember) {

if (!client.botReadyAt) return;
if (newGuildMember.guild.id !== client.guild.id) return;
if (oldGuildMember.nickname === newGuildMember.nickname && oldGuildMember.user.username === newGuildMember.user.username) return;
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

guildMemberUpdate generally won’t fire for global username changes (those emit userUpdate), so the oldGuildMember.user.username !== newGuildMember.user.username check is unlikely to catch username edits. If alsoCheckUsernames is meant to react to username changes, add a userUpdate event handler (and map the updated user to the guild member) instead of relying on guildMemberUpdate.

Suggested change
if (oldGuildMember.nickname === newGuildMember.nickname && oldGuildMember.user.username === newGuildMember.user.username) return;
if (oldGuildMember.nickname === newGuildMember.nickname) return;

Copilot uses AI. Check for mistakes.
await renameMember(client, newGuildMember);
}

24 changes: 24 additions & 0 deletions modules/name-list-cleaner/module.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "name-list-cleaner",
"humanReadableName": {
"en": "Name List Cleaner",
"de": "Namenslisten Cleaner"
},
"author": {
"name": "hfgd",
"link": "https://github.com/hfgd123",
"scnxOrgID": "2"
},
"openSourceURL": "https://github.com/hfgd123/CustomDCBot/tree/main/modules/name-list-cleaner",
"config-example-files": [
"configs/config.json"
],
"events-dir": "/events",
"tags": [
"tools"
],
"description": {
"en": "Remove special characters from the beginning of usernames",
"de": "Entferne Sonderzeichen vom Anfang von Benutzernamen"
}
}
65 changes: 65 additions & 0 deletions modules/name-list-cleaner/renameMember.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const {localize} = require("../../src/functions/localize");
renameMember = async function (client, guildMember) {
let newName;
Comment on lines +2 to +3
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

renameMember is assigned without const/let/function declaration, which creates an implicit global variable in non-strict mode and can lead to hard-to-debug collisions. Declare it explicitly (e.g., const renameMember = … or async function renameMember(…)).

Copilot uses AI. Check for mistakes.
const moduleConf = client.configurations['name-list-cleaner']['config'];
if (moduleConf.userWhitelist.includes(guildMember.user.id)) return;


if (guildMember.nickname !== null) {
newName = await checkUsername(client, guildMember.nickname, false);
if (newName === guildMember.nickname) return;
} else if (moduleConf.alsoCheckUsernames) {
newName = await checkUsername(client, guildMember.user.username, true);
if (newName === guildMember.user.username) return;
} else return;
if (guildMember.guild.ownerId === guildMember.id) {
client.logger.error('[name-list-cleaner] ' + localize('name-list-cleaner', 'owner-cannot-be-renamed', {u: guildMember.user.username}))
return;
}
if (moduleConf.keepNickname) {
try {
await guildMember.setNickname(newName, localize('name-list-cleaner', 'nickname-changed', {u: guildMember.user.username}));
} catch (e) {
client.logger.error('[name-list-cleaner] ' + localize('name-list-cleaner', 'nickname-error', {u: guildMember.user.username, e: e}))
}
} else {
if (guildMember.nickname === null) {
return;
}
try {
await guildMember.setNickname(null, localize('name-list-cleaner', 'nickname-reset', {u: guildMember.user.username}));
} catch (e) {
client.logger.error('[name-list-cleaner] ' + localize('name-list-cleaner', 'nickname-error', {u: guildMember.user.username, e: e}))
}
}
}

module.exports.renameMember = renameMember;

async function checkUsername(client, name, isUsername) {
const moduleConf = client.configurations['name-list-cleaner']['config'];
if (name.length === 0) {
if (isUsername) {
return 'User'
} else {
return null;
}
}
if (moduleConf.symbolWhitelist.length === 0) {
if (name.charAt(0).match(/^[a-zA-Z0-9]$/)) {
return name;
} else {
return await checkUsername(client, name.substring(1), isUsername);
}
Comment on lines +48 to +53
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

checkUsername uses charAt(0) and substring(1), which operate on UTF-16 code units. This breaks the notion of “first character” for surrogate pairs / emoji (and makes it impossible to whitelist an emoji reliably). Consider iterating by Unicode code points/graphemes (e.g., [...name][0] / slicing by code points, or Intl.Segmenter if you need grapheme clusters).

Copilot uses AI. Check for mistakes.
} else if (!moduleConf.symbolWhitelist.includes(name.charAt(0)) && !moduleConf.isBlacklist) {
if (name.charAt(0).match(/^[a-zA-Z0-9]$/)) {
Comment on lines +49 to +55
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

checkUsername treats only ASCII [a-zA-Z0-9] as alphanumeric. This will strip leading non-ASCII letters/digits (e.g., ü/ß/Ж/漢/١) even though they are valid characters in Discord names, causing unexpected renames. Consider using a Unicode-aware check (e.g., Unicode property escapes) or clarify the intended restriction.

Suggested change
if (name.charAt(0).match(/^[a-zA-Z0-9]$/)) {
return name;
} else {
return await checkUsername(client, name.substring(1), isUsername);
}
} else if (!moduleConf.symbolWhitelist.includes(name.charAt(0)) && !moduleConf.isBlacklist) {
if (name.charAt(0).match(/^[a-zA-Z0-9]$/)) {
if (name.charAt(0).match(/^[\p{L}\p{N}]$/u)) {
return name;
} else {
return await checkUsername(client, name.substring(1), isUsername);
}
} else if (!moduleConf.symbolWhitelist.includes(name.charAt(0)) && !moduleConf.isBlacklist) {
if (name.charAt(0).match(/^[\p{L}\p{N}]$/u)) {

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you want it to accept anything else, you can whitelist it, ig

Copy link
Member

Choose a reason for hiding this comment

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

imagine whitelisting every chinese character

Copy link
Contributor Author

Choose a reason for hiding this comment

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

true... But at the same time: Where would you draw the line

return name;
} else {
return await checkUsername(client, name.substring(1), isUsername);
}
} else if (moduleConf.symbolWhitelist.includes(name.charAt(0)) && moduleConf.isBlacklist) {
return await checkUsername(client, name.substring(1), isUsername);
} else {
return name;
}
}