add white list for cleaner

This commit is contained in:
Louis Mazin 2026-04-15 00:14:41 +02:00
parent b6f3ee262b
commit 9aca4c7995
3 changed files with 318 additions and 3 deletions

View File

@ -0,0 +1,247 @@
const { SlashCommandBuilder, MessageFlags } = require('discord.js');
const fs = require('node:fs/promises');
const path = require('node:path');
const ROLE_RYGAINLAND = '1444684935632912394';
const KEEP_LIST_FILE_PATH = path.resolve(__dirname, 'server-clean-keep.json');
const normUid32 = (val) => {
if (!val) return '';
const s = String(val).replace(/-/g, '').trim().toLowerCase();
if (s.length !== 32) return '';
if (!/^[0-9a-f]{32}$/.test(s)) return '';
return s;
};
async function readKeepList() {
try {
const raw = await fs.readFile(KEEP_LIST_FILE_PATH, 'utf8');
const parsed = JSON.parse(raw);
const discordIds = Array.isArray(parsed.discordIds)
? parsed.discordIds.map((x) => String(x || '').trim()).filter(Boolean)
: [];
const playerIds = Array.isArray(parsed.playerIds)
? parsed.playerIds.map(normUid32).filter(Boolean)
: [];
return {
discordIds: [...new Set(discordIds)],
playerIds: [...new Set(playerIds)],
};
} catch (error) {
if (error && error.code === 'ENOENT') {
return { discordIds: [], playerIds: [] };
}
throw error;
}
}
async function writeKeepList(keepList) {
const payload = {
discordIds: [...new Set((keepList.discordIds || []).map((x) => String(x || '').trim()).filter(Boolean))],
playerIds: [...new Set((keepList.playerIds || []).map(normUid32).filter(Boolean))],
};
await fs.writeFile(KEEP_LIST_FILE_PATH, JSON.stringify(payload, null, 2), 'utf8');
}
module.exports = {
data: new SlashCommandBuilder()
.setName('server-clean-keep')
.setDescription('Gérer la liste blanche des joueurs protégés du /server-clean')
.addSubcommand((sub) =>
sub
.setName('list')
.setDescription('Afficher la liste blanche actuelle')
)
.addSubcommand((sub) =>
sub
.setName('add-discord')
.setDescription('Ajouter un compte Discord à la liste blanche')
.addUserOption((opt) =>
opt
.setName('membre')
.setDescription('Compte Discord à protéger')
.setRequired(true)
)
)
.addSubcommand((sub) =>
sub
.setName('remove-discord')
.setDescription('Retirer un compte Discord de la liste blanche')
.addUserOption((opt) =>
opt
.setName('membre')
.setDescription('Compte Discord à retirer')
.setRequired(true)
)
)
.addSubcommand((sub) =>
sub
.setName('add-player')
.setDescription('Ajouter un Player ID Palworld à la liste blanche')
.addStringOption((opt) =>
opt
.setName('player-id')
.setDescription('UID hexadécimal (32 caractères)')
.setRequired(true)
)
)
.addSubcommand((sub) =>
sub
.setName('remove-player')
.setDescription('Retirer un Player ID Palworld de la liste blanche')
.addStringOption((opt) =>
opt
.setName('player-id')
.setDescription('UID hexadécimal (32 caractères)')
.setRequired(true)
)
),
async execute(interaction) {
if (!interaction.member.roles.cache.has(ROLE_RYGAINLAND)) {
await interaction.reply({
content: '❌ Il faut avoir le rôle Rygainland pour utiliser cette commande.',
flags: MessageFlags.Ephemeral,
});
return;
}
const sub = interaction.options.getSubcommand();
try {
const keepList = await readKeepList();
if (sub === 'list') {
const discordText = keepList.discordIds.length
? keepList.discordIds.map((id) => `- <@${id}> (${id})`).join('\n')
: '- (vide)';
const playerText = keepList.playerIds.length
? keepList.playerIds.map((id) => `- ${id}`).join('\n')
: '- (vide)';
await interaction.reply({
content:
'🛡️ Liste blanche /server-clean\n\n' +
`Discord IDs (${keepList.discordIds.length})\n${discordText}\n\n` +
`Player IDs (${keepList.playerIds.length})\n${playerText}`,
flags: MessageFlags.Ephemeral,
});
return;
}
if (sub === 'add-discord') {
const user = interaction.options.getUser('membre', true);
if (!keepList.discordIds.includes(user.id)) {
keepList.discordIds.push(user.id);
await writeKeepList(keepList);
await interaction.reply({
content: `✅ Ajouté à la liste blanche Discord: <@${user.id}> (${user.id})`,
flags: MessageFlags.Ephemeral,
});
return;
}
await interaction.reply({
content: ` Déjà présent dans la liste blanche Discord: <@${user.id}>`,
flags: MessageFlags.Ephemeral,
});
return;
}
if (sub === 'remove-discord') {
const user = interaction.options.getUser('membre', true);
const before = keepList.discordIds.length;
keepList.discordIds = keepList.discordIds.filter((id) => id !== user.id);
if (keepList.discordIds.length !== before) {
await writeKeepList(keepList);
await interaction.reply({
content: `✅ Retiré de la liste blanche Discord: <@${user.id}> (${user.id})`,
flags: MessageFlags.Ephemeral,
});
return;
}
await interaction.reply({
content: ` Ce compte n'était pas dans la liste blanche Discord: <@${user.id}>`,
flags: MessageFlags.Ephemeral,
});
return;
}
if (sub === 'add-player') {
const raw = interaction.options.getString('player-id', true);
const uid = normUid32(raw);
if (!uid) {
await interaction.reply({
content: '❌ Player ID invalide. Format attendu: 32 caractères hexadécimaux.',
flags: MessageFlags.Ephemeral,
});
return;
}
if (!keepList.playerIds.includes(uid)) {
keepList.playerIds.push(uid);
await writeKeepList(keepList);
await interaction.reply({
content: `✅ Player ID ajouté à la liste blanche: ${uid}`,
flags: MessageFlags.Ephemeral,
});
return;
}
await interaction.reply({
content: ` Player ID déjà présent dans la liste blanche: ${uid}`,
flags: MessageFlags.Ephemeral,
});
return;
}
if (sub === 'remove-player') {
const raw = interaction.options.getString('player-id', true);
const uid = normUid32(raw);
if (!uid) {
await interaction.reply({
content: '❌ Player ID invalide. Format attendu: 32 caractères hexadécimaux.',
flags: MessageFlags.Ephemeral,
});
return;
}
const before = keepList.playerIds.length;
keepList.playerIds = keepList.playerIds.filter((id) => id !== uid);
if (keepList.playerIds.length !== before) {
await writeKeepList(keepList);
await interaction.reply({
content: `✅ Player ID retiré de la liste blanche: ${uid}`,
flags: MessageFlags.Ephemeral,
});
return;
}
await interaction.reply({
content: ` Player ID non trouvé dans la liste blanche: ${uid}`,
flags: MessageFlags.Ephemeral,
});
return;
}
await interaction.reply({
content: '❌ Sous-commande inconnue.',
flags: MessageFlags.Ephemeral,
});
} catch (error) {
console.error('Erreur server-clean-keep:', error);
await interaction.reply({
content: `❌ Erreur: ${error.message || String(error)}`,
flags: MessageFlags.Ephemeral,
}).catch(() => {});
}
},
};

View File

@ -0,0 +1,4 @@
{
"discordIds": [],
"playerIds": []
}

View File

@ -20,6 +20,8 @@ const execFileAsync = promisify(execFile);
const ROLE_RYGAINLAND = '1444684935632912394';
const keepListFilePath = path.resolve(__dirname, 'server-clean-keep.json');
const normUid32 = (val) => {
if (!val) return '';
const s = String(val).replace(/-/g, '').trim().toLowerCase();
@ -30,6 +32,45 @@ const normUid32 = (val) => {
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const parseCsv = (val) =>
String(val || '')
.split(',')
.map((x) => x.trim())
.filter(Boolean);
async function loadKeepList() {
const keepDiscordIds = new Set(parseCsv(process.env.SERVER_CLEAN_KEEP_DISCORD_IDS));
const keepPlayerIds = new Set(parseCsv(process.env.SERVER_CLEAN_KEEP_PLAYER_IDS).map(normUid32).filter(Boolean));
try {
const raw = await fs.readFile(keepListFilePath, 'utf8');
const parsed = JSON.parse(raw);
if (Array.isArray(parsed.discordIds)) {
for (const id of parsed.discordIds) {
const clean = String(id || '').trim();
if (clean) keepDiscordIds.add(clean);
}
}
if (Array.isArray(parsed.playerIds)) {
for (const uid of parsed.playerIds) {
const clean = normUid32(uid);
if (clean) keepPlayerIds.add(clean);
}
}
} catch (error) {
if (error && error.code !== 'ENOENT') {
throw new Error(`Impossible de lire ${path.basename(keepListFilePath)}: ${error.message || String(error)}`);
}
}
return {
keepDiscordIds,
keepPlayerIds,
};
}
async function waitForServerState({ headers, baseUrl, serverId, wanted, timeoutMs }) {
const started = Date.now();
while (Date.now() - started < timeoutMs) {
@ -158,10 +199,26 @@ module.exports = {
for (const l of targetLinks) byDiscord.set(String(l.discord_id), l);
const deduped = [...byDiscord.values()];
const keepList = await loadKeepList();
const filteredLinks = [];
let keepListExcludedCount = 0;
for (const link of deduped) {
const discordId = String(link.discord_id || '').trim();
const uid = normUid32(link.player_id);
if (keepList.keepDiscordIds.has(discordId) || (uid && keepList.keepPlayerIds.has(uid))) {
keepListExcludedCount += 1;
continue;
}
filteredLinks.push(link);
}
const uidsToDelete = [];
const missingPlayerId = [];
for (const link of deduped) {
for (const link of filteredLinks) {
const uid = normUid32(link.player_id);
if (!uid) {
missingPlayerId.push(link);
@ -174,7 +231,10 @@ module.exports = {
const extra = missingPlayerId.length
? `\n⚠️ ${missingPlayerId.length} membre(s) ciblé(s) sans Player ID en DB (impossible à supprimer automatiquement).`
: '';
await interaction.editReply(`✅ Aucun joueur supprimable trouvé.${extra}`);
const keepText = keepListExcludedCount
? `\n🛡️ Exclus via liste blanche: ${keepListExcludedCount}`
: '';
await interaction.editReply(`✅ Aucun joueur supprimable trouvé.${extra}${keepText}`);
return;
}
@ -286,6 +346,10 @@ module.exports = {
? `\n⚠️ Non supprimés (pas de Player ID en DB): ${missingPlayerId.length}`
: '';
const keepText = keepListExcludedCount
? `\n🛡️ Exclus via liste blanche: ${keepListExcludedCount}`
: '';
const okText = running ? '✅ Nettoyage terminé et serveur redémarré.' : '⚠️ Nettoyage terminé, mais le serveur n\'a pas confirmé son état running.';
// Keep output short; deleterReport can be long.
@ -295,7 +359,7 @@ module.exports = {
await interaction.editReply(
`${okText}\n` +
`👥 Joueurs supprimés (ciblés): ${uidsToDelete.length}${missingText}\n` +
`👥 Joueurs supprimés (ciblés): ${uidsToDelete.length}${missingText}${keepText}\n` +
(reportSummary ? `📄 players-deleter: ${reportSummary}` : '')
);