const pool = require("../Database/database.js"); const crypto = require("crypto"); const { ChannelType } = require("discord.js"); function generateBackupId() { return "BKP-" + crypto.randomBytes(8).toString("hex").toUpperCase(); } async function saveGuildBackup(guild, userId) { if (!guild || !guild.available) throw new Error("Le serveur n’est pas disponible."); const existing = await pool.query(`SELECT COUNT(*) FROM backups WHERE user_id = $1`, [userId]); if (parseInt(existing.rows[0].count) >= 10) throw new Error("Vous avez atteint la limite de 10 sauvegardes."); await guild.roles.fetch().catch(() => {}); await guild.channels.fetch().catch(() => {}); const roles = [...guild.roles.cache.values()] .filter(r => r.name !== "@everyone") .map(r => ({ id: r.id, name: r.name, color: r.color, position: r.position, permissions: r.permissions.bitfield.toString(), hoist: r.hoist, mentionable: r.mentionable })); const channels = [...guild.channels.cache.values()].map(c => ({ id: c.id, name: c.name, type: c.type, parent: c.parentId || null, position: c.rawPosition ?? c.position ?? 0, topic: c.topic ?? null, nsfw: !!c.nsfw, rateLimitPerUser: c.rateLimitPerUser ?? 0, bitrate: c.bitrate ?? null, userLimit: c.userLimit ?? null, permissionOverwrites: Array.from(c.permissionOverwrites?.cache?.values() || []).map(po => ({ id: po.id, type: po.type, allow: po.allow.bitfield.toString(), deny: po.deny.bitfield.toString() })) })); const backupId = generateBackupId(); await pool.query( `INSERT INTO backups (backup_id, user_id, guild_id, guild_name, data, created_at) VALUES ($1, $2, $3, $4, $5, NOW())`, [backupId, userId, guild.id, guild.name, JSON.stringify({ roles, channels })] ); return { backupId, rolesCount: roles.length, channelsCount: channels.length }; } async function restoreUserBackup(guild, userId, backupId) { const res = await pool.query( `SELECT data FROM backups WHERE user_id=$1 AND backup_id=$2`, [userId, backupId] ); if (!res.rows.length) throw new Error("Sauvegarde introuvable ou non autorisée."); let data = res.rows[0].data; if (typeof data === "string") data = JSON.parse(data); const { roles, channels } = data; const botMember = guild.members.me; const botHighest = botMember.roles.highest.position; console.log(`🔁 Restauration : ${roles.length} rôles, ${channels.length} salons`); for (const ch of guild.channels.cache.values()) { try { await ch.delete().catch(() => {}); } catch {} } for (const r of guild.roles.cache.values()) { if (r.name !== "@everyone" && r.position < botHighest && r.editable) { try { await r.delete().catch(() => {}); } catch {} } } const createdRoles = []; for (const r of roles.sort((a, b) => a.position - b.position)) { try { const role = await guild.roles.create({ name: r.name, color: r.color || null, hoist: r.hoist, mentionable: r.mentionable, permissions: BigInt(r.permissions || 0) }); createdRoles.push({ oldId: r.id, newRole: role, position: r.position }); } catch (err) { console.warn(`⚠️ Erreur création rôle ${r.name}: ${err.message}`); } } try { const manageableRoles = createdRoles.filter(r => { const roleObj = guild.roles.cache.get(r.newRole.id); return roleObj && roleObj.editable; }); await guild.roles.setPositions( manageableRoles.map(r => ({ role: r.newRole.id, position: Math.min(r.position, botHighest - 1) })) ); } catch (err) { console.warn("⚠️ Impossible d’appliquer l’ordre des rôles :", err.message); } const categoryMap = new Map(); const categories = channels .filter(c => c.type === ChannelType.GuildCategory) .sort((a, b) => a.position - b.position); for (const cat of categories) { try { const newCat = await guild.channels.create({ name: cat.name, type: ChannelType.GuildCategory, position: cat.position }); categoryMap.set(cat.id, newCat.id); } catch (err) { console.warn(`⚠️ Erreur création catégorie ${cat.name}: ${err.message}`); } } const nonCategories = channels .filter(c => c.type !== ChannelType.GuildCategory) .sort((a, b) => a.position - b.position); for (const c of nonCategories) { const parent = c.parent ? categoryMap.get(c.parent) || null : null; let type = c.type; const allowedTypes = [ ChannelType.GuildText, ChannelType.GuildVoice, ChannelType.GuildCategory, ChannelType.GuildAnnouncement, ChannelType.GuildStageVoice, ChannelType.GuildForum, ChannelType.GuildMedia ]; if (!allowedTypes.includes(type)) type = ChannelType.GuildText; if (type === ChannelType.GuildAnnouncement) type = ChannelType.GuildText; const options = { name: c.name, type, parent, nsfw: c.nsfw, position: c.position, permissionOverwrites: (c.permissionOverwrites || []).map(po => ({ id: po.id, type: po.type, allow: BigInt(po.allow || 0), deny: BigInt(po.deny || 0) })) }; if (type === ChannelType.GuildText) { options.topic = c.topic; options.rateLimitPerUser = c.rateLimitPerUser ?? 0; } if (type === ChannelType.GuildVoice || type === ChannelType.GuildStageVoice) { options.bitrate = c.bitrate ?? 64000; options.userLimit = c.userLimit ?? 0; } try { const created = await guild.channels.create(options); if (c.permissionOverwrites && c.permissionOverwrites.length) { for (const ow of c.permissionOverwrites) { try { await created.permissionOverwrites.create(ow.id, { allow: BigInt(ow.allow || 0), deny: BigInt(ow.deny || 0), type: ow.type }).catch(() => {}); } catch {} } } } catch (err) { console.warn(`⚠️ Erreur création salon "${c.name}" (${c.type}): ${err.message}`); } } console.log("✅ Restauration terminée avec succès !"); return true; } async function listUserBackups(userId) { const res = await pool.query( `SELECT backup_id, guild_name, created_at FROM backups WHERE user_id=$1 ORDER BY created_at DESC`, [userId] ); return res.rows; } async function deleteUserBackup(userId, backupId) { const res = await pool.query( `DELETE FROM backups WHERE user_id=$1 AND backup_id=$2 RETURNING backup_id`, [userId, backupId] ); return res.rowCount > 0; } async function getBackupInfo(backupId, userId) { const res = await pool.query( `SELECT data FROM backups WHERE backup_id=$1 AND user_id=$2`, [backupId, userId] ); if (!res.rows.length) throw new Error("Sauvegarde introuvable ou non autorisée."); const raw = res.rows[0].data; const data = typeof raw === "string" ? JSON.parse(raw) : raw; return { roles: (data.roles || []).map(r => r.name), categories: (data.channels || []) .filter(c => c.type === ChannelType.GuildCategory) .map(c => c.name), channels: (data.channels || []) .filter(c => c.type !== ChannelType.GuildCategory) .map(c => c.name) }; } module.exports = { saveGuildBackup, listUserBackups, deleteUserBackup, restoreUserBackup, getBackupInfo };