UNPKG

discord-rebackup

Version:

An rewamped fork of the discord-backup module ! by the iHorizon Team

676 lines (675 loc) 31.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.enableSelfbotMode = enableSelfbotMode; exports.disableSelfbotMode = disableSelfbotMode; exports.fetchChannelPermissions = fetchChannelPermissions; exports.fetchVoiceChannelData = fetchVoiceChannelData; exports.fetchStageChannelData = fetchStageChannelData; exports.fetchChannelMessages = fetchChannelMessages; exports.fetchTextChannelData = fetchTextChannelData; exports.loadCategory = loadCategory; exports.loadChannel = loadChannel; exports.clearGuild = clearGuild; const logger_1 = require("./logger"); // Support pour discord.js v14 et discord.js-selfbot-v13 let ChannelType; let OverwriteType; let GuildPremiumTier; // Importer les deux bibliothèques let discordjs; let discordjsSelfbot; // Indicateur pour savoir si nous utilisons le mode selfbot let isSelfbotMode = false; try { // discord.js v14 discordjs = require('discord.js'); ChannelType = discordjs.ChannelType; OverwriteType = discordjs.OverwriteType; GuildPremiumTier = discordjs.GuildPremiumTier; // Essayer d'importer discord.js-selfbot-v13 aussi try { discordjsSelfbot = require('discord.js-selfbot-v13'); } catch (e) { } } catch (e) { // Si discord.js n'est pas disponible, utiliser discord.js-selfbot-v13 par défaut try { discordjsSelfbot = require('discord.js-selfbot-v13'); isSelfbotMode = true; // Définir les constantes pour la compatibilité ChannelType = { GuildText: 'GUILD_TEXT', GuildVoice: 'GUILD_VOICE', GuildCategory: 'GUILD_CATEGORY', GuildAnnouncement: 'GUILD_NEWS', GuildStageVoice: 'GUILD_STAGE_VOICE', GuildForum: 'GUILD_FORUM', GuildMedia: 'GUILD_MEDIA', }; OverwriteType = { Role: 'role', Member: 'member' }; GuildPremiumTier = { None: 'NONE', Tier1: 'TIER_1', Tier2: 'TIER_2', Tier3: 'TIER_3' }; } catch (e) { throw new Error('Aucune bibliothèque Discord.js n\'est disponible'); } } // Fonction pour activer le mode selfbot function enableSelfbotMode() { if (discordjsSelfbot) { isSelfbotMode = true; return true; } else { return false; } } // Fonction pour désactiver le mode selfbot function disableSelfbotMode() { if (discordjs) { isSelfbotMode = false; return true; } else { return false; } } // Import des types nécessaires en fonction de la version disponible if (isSelfbotMode) { discordjs = discordjsSelfbot; } else { try { // Tenter d'importer discord.js v14 discordjs = require('discord.js'); } catch { // Si échec, on suppose que c'est discord.js-selfbot-v13 discordjs = require('discord.js-selfbot-v13'); } } const { CategoryChannel, Collection, Guild, GuildFeature, GuildDefaultMessageNotifications, GuildSystemChannelFlags, Message, OverwriteData, Snowflake, TextChannel, VoiceChannel, NewsChannel, ThreadChannel, Webhook, GuildExplicitContentFilter, GuildVerificationLevel, FetchMessagesOptions, StageChannel } = discordjs; const MaxBitratePerTier = { [GuildPremiumTier.None]: 64000, [GuildPremiumTier.Tier1]: 128000, [GuildPremiumTier.Tier2]: 256000, [GuildPremiumTier.Tier3]: 384000 }; /** * Gets the permissions for a channel */ function fetchChannelPermissions(channel) { const permissions = []; try { const typedChannel = channel; // Vérifier si permissionOverwrites existe et a une propriété cache if (!typedChannel.permissionOverwrites || !typedChannel.permissionOverwrites.cache) { console.error(`Le canal ${typedChannel.name || typedChannel.id || 'inconnu'} n'a pas de permissionOverwrites valides`); return permissions; } typedChannel.permissionOverwrites.cache .filter((p) => p.type === OverwriteType.Role || p.type === 'role') .forEach((perm) => { try { // For each overwrites permission if (!typedChannel.guild || !typedChannel.guild.roles || !typedChannel.guild.roles.cache) { return; // Skip if guild or roles cache is not available } const role = typedChannel.guild.roles.cache.get(perm.id); if (role) { // Vérifier si perm.allow et perm.deny existent et ont une propriété bitfield const allowBitfield = perm.allow && perm.allow.bitfield ? perm.allow.bitfield.toString() : '0'; const denyBitfield = perm.deny && perm.deny.bitfield ? perm.deny.bitfield.toString() : '0'; permissions.push({ roleName: role.name, allow: allowBitfield, deny: denyBitfield }); } } catch (permError) { console.error(`Erreur lors de la récupération des permissions pour le rôle ${perm?.id || 'inconnu'}: ${permError}`); } }); } catch (error) { console.error(`Erreur lors de la récupération des permissions du canal: ${error}`); } return permissions; } /** * Fetches the voice channel data that is necessary for the backup */ async function fetchVoiceChannelData(channel) { return new Promise(async (resolve) => { const typedChannel = channel; const channelData = { type: ChannelType.GuildVoice, name: typedChannel.name, bitrate: typedChannel.bitrate, userLimit: typedChannel.userLimit, parent: typedChannel.parent ? typedChannel.parent.name : null, permissions: fetchChannelPermissions(typedChannel) }; /* Return channel data */ resolve(channelData); }); } async function fetchStageChannelData(channel) { return new Promise(async (resolve) => { const typedChannel = channel; const channelData = { type: typedChannel.type, name: typedChannel.name, bitrate: typedChannel.bitrate, userLimit: typedChannel.userLimit, parent: typedChannel.parent ? typedChannel.parent.name : null, permissions: fetchChannelPermissions(typedChannel) }; /* Return channel data */ resolve(channelData); }); } async function fetchChannelMessages(channel, options) { let messages = []; const typedChannel = channel; const messageCount = isNaN(options.maxMessagesPerChannel) ? 10 : options.maxMessagesPerChannel; const fetchOptions = { limit: 100 }; let lastMessageId; let fetchComplete = false; while (!fetchComplete) { if (lastMessageId) { fetchOptions.before = lastMessageId; } const fetched = await typedChannel.messages.fetch(fetchOptions); if (fetched.size === 0) { break; } lastMessageId = fetched.last().id; await Promise.all(fetched.map(async (msg) => { if (!msg.author || messages.length >= messageCount) { fetchComplete = true; return; } const files = await Promise.all(msg.attachments.map(async (a) => { let attach = a.url; if (a.url && ['png', 'jpg', 'jpeg', 'jpe', 'jif', 'jfif', 'jfi'].includes(a.url)) { if (options.saveImages && options.saveImages === 'base64') { attach = (await (fetch(a.url).then((res) => res.buffer()))).toString('base64'); } } return { name: a.name, attachment: attach }; })); messages.push({ username: msg.author.username, avatar: msg.author.displayAvatarURL(), content: msg.cleanContent, embeds: msg.embeds, files, pinned: msg.pinned, sentAt: msg.createdAt.toISOString(), }); })); } return messages; } /** * Fetches the text channel data that is necessary for the backup */ async function fetchTextChannelData(channel, options) { return new Promise(async (resolve) => { const typedChannel = channel; const channelData = { type: typedChannel.type, name: typedChannel.name, nsfw: typedChannel.nsfw, rateLimitPerUser: typedChannel.type === ChannelType.GuildText || typedChannel.type === 'GUILD_TEXT' ? typedChannel.rateLimitPerUser : undefined, parent: typedChannel.parent ? typedChannel.parent.name : null, topic: typedChannel.topic, permissions: fetchChannelPermissions(typedChannel), messages: [], isNews: typedChannel.type === ChannelType.GuildAnnouncement || typedChannel.type === 'GUILD_NEWS', threads: [] }; /* Fetch channel threads */ if (typedChannel.threads && typedChannel.threads.cache && typedChannel.threads.cache.size > 0) { await Promise.all(typedChannel.threads.cache.map(async (thread) => { const typedThread = thread; const threadData = { type: typedThread.type, name: typedThread.name, archived: typedThread.archived, autoArchiveDuration: typedThread.autoArchiveDuration, locked: typedThread.locked, rateLimitPerUser: typedThread.rateLimitPerUser, messages: [] }; try { threadData.messages = await fetchChannelMessages(typedThread, options); /* Return thread data */ channelData.threads.push(threadData); } catch (error) { channelData.threads.push(threadData); } })); } /* Fetch channel messages */ try { channelData.messages = await fetchChannelMessages(typedChannel, options); /* Return channel data */ resolve(channelData); } catch (error) { resolve(channelData); } }); } /** * Creates a category for the guild */ async function loadCategory(categoryData, guild) { return new Promise((resolve) => { // Vérifier si nous sommes en mode selfbot let categoryPromise; if (isSelfbotMode && discordjsSelfbot) { // Utiliser discord.js-selfbot-v13 directement categoryPromise = guild.channels.create(categoryData.name, { type: 'GUILD_CATEGORY' }); } else { // discord.js v14 categoryPromise = guild.channels.create({ name: categoryData.name, type: ChannelType.GuildCategory }); } categoryPromise.then(async (category) => { // When the category is created const finalPermissions = categoryData.permissions.map((perm) => { const role = guild.roles.cache.find((r) => r.name === perm.roleName); if (role) { return { id: role.id, allow: BigInt(perm.allow), deny: BigInt(perm.deny), type: OverwriteType.Role }; } else return null; }).filter((perm) => perm !== null); await category.permissionOverwrites.set(finalPermissions); resolve(category); // Return the category }); }); } /** * Create a channel and returns it */ async function loadChannel(channelData, guild, category, options) { return new Promise(async (resolve) => { const loadMessages = (channel, messages, previousWebhook) => { return new Promise(async (resolve) => { // Vérifier si le canal est un thread ou un canal de texte let webhook = previousWebhook || null; (0, logger_1.debug)(`Canal: ${channel.name}, Est un thread: ${channel.isThread}, Peut récupérer webhooks: ${!!channel.fetchWebhooks}`); (0, logger_1.debug)(`Mode selfbot: ${isSelfbotMode}, Discord.js-selfbot-v13 disponible: ${!!discordjsSelfbot}`); // Vérifier si le canal est un thread en appelant la fonction isThread si elle existe const isThreadChannel = typeof channel.isThread === 'function' ? channel.isThread() : channel.isThread; (0, logger_1.debug)(`Vérification si ${channel.name} est un thread: ${isThreadChannel}`); if (!webhook && !isThreadChannel && channel.fetchWebhooks) { try { (0, logger_1.debug)(`Tentative de récupération des webhooks pour le canal ${channel.name}`); const webhooks = await channel.fetchWebhooks(); (0, logger_1.debug)(`Webhooks récupérés: ${webhooks.size}`); webhook = webhooks.find((w) => w.name === 'MessagesBackup') || null; (0, logger_1.debug)(`Webhook MessagesBackup trouvé: ${!!webhook}`); } catch (err) { (0, logger_1.error)('Erreur lors de la récupération des webhooks:', err); } } if (!webhook && !isThreadChannel && channel.createWebhook) { try { (0, logger_1.debug)(`Tentative de création d'un webhook pour le canal ${channel.name}`); if (isSelfbotMode && discordjsSelfbot) { // discord.js-selfbot-v13 (0, logger_1.debug)(`Création d'un webhook en mode selfbot`); webhook = await channel.createWebhook('MessagesBackup', { avatar: channel.client.user.displayAvatarURL() }).catch((err) => { (0, logger_1.error)('Erreur lors de la création du webhook en mode selfbot:', err); return null; }); } else { // discord.js v14 (0, logger_1.debug)(`Création d'un webhook en mode normal`); webhook = await channel.createWebhook({ name: 'MessagesBackup', avatar: channel.client.user.displayAvatarURL() }).catch((err) => { (0, logger_1.error)('Erreur lors de la création du webhook en mode normal:', err); return null; }); } (0, logger_1.debug)(`Webhook créé avec succès: ${!!webhook}`); } catch (err) { (0, logger_1.error)('Erreur lors de la création du webhook:', err); } } if (!webhook) { (0, logger_1.debug)(`Aucun webhook disponible pour le canal ${channel.name}, impossible d'envoyer les messages`); return resolve(undefined); } (0, logger_1.debug)(`Webhook disponible pour le canal ${channel.name}, prêt à envoyer ${messages.length} messages`); // Filtrer les messages vides messages = messages .filter((m) => m.content.length > 0 || m.embeds.length > 0 || m.files.length > 0) .reverse(); // Si maxMessagesPerChannel est défini et n'est pas -1 (valeur spéciale pour "tous les messages") if (options.maxMessagesPerChannel && options.maxMessagesPerChannel !== -1) { (0, logger_1.debug)(`Limitation à ${options.maxMessagesPerChannel} messages pour le canal ${channel.name}`); messages = messages.slice(messages.length - options.maxMessagesPerChannel); } else if (options.maxMessagesPerChannel === -1) { (0, logger_1.debug)(`Restauration de tous les ${messages.length} messages pour le canal ${channel.name}`); } for (const msg of messages) { try { // Préparer les options de base pour l'envoi du message const messageOptions = { content: msg.content.length ? msg.content : undefined, username: msg.username, avatarURL: msg.avatar, embeds: msg.embeds, allowedMentions: options.allowedMentions }; // Ajouter les pièces jointes si présentes if (msg.files && msg.files.length > 0) { try { // Gestion des pièces jointes compatible avec les deux versions const buffer = await fetch(msg.files[0].attachment) .then((res) => res.buffer()) .catch((error) => { return null; }); if (buffer) { // Créer l'attachment en fonction de la version disponible if (isSelfbotMode && discordjsSelfbot) { // discord.js-selfbot-v13 messageOptions.files = [{ attachment: buffer, name: msg.files[0].name }]; } else { // discord.js v14 const { AttachmentBuilder } = require('discord.js'); messageOptions.files = [new AttachmentBuilder(buffer, { name: msg.files[0].name })]; } } } catch (error) { } } // Envoyer le message via le webhook (0, logger_1.debug)(`Tentative d'envoi d'un message via webhook dans le canal ${channel.name}`); (0, logger_1.debug)(`Options du message:`, JSON.stringify(messageOptions, null, 2)); const sentMsg = await webhook.send(messageOptions) .then((msg) => { (0, logger_1.debug)(`Message envoyé avec succès via webhook dans le canal ${channel.name}`); return msg; }) .catch((err) => { (0, logger_1.error)(`Erreur lors de l'envoi du message via webhook:`, err); resolve(undefined); }); if (msg.pinned && sentMsg) await sentMsg.pin(); } catch (error) { } } resolve(webhook); }); }; // Create the channel let channel = null; // Préparer les options de création de canal en fonction du mode let channelPromise; if (isSelfbotMode && discordjsSelfbot) { // Mode selfbot avec discord.js-selfbot-v13 // Déterminer le type de canal pour discord.js-selfbot-v13 let channelType = 'GUILD_TEXT'; const channelTypeStr = String(channelData.type); if (channelTypeStr === String(ChannelType.GuildVoice) || channelTypeStr === 'GUILD_VOICE') { channelType = 'GUILD_VOICE'; } else if (channelTypeStr === String(ChannelType.GuildAnnouncement) || channelTypeStr === 'GUILD_NEWS') { channelType = 'GUILD_NEWS'; } else if (channelTypeStr === String(ChannelType.GuildStageVoice) || channelTypeStr === 'GUILD_STAGE_VOICE') { channelType = 'GUILD_STAGE_VOICE'; } // Créer les options pour discord.js-selfbot-v13 const createOptions = { type: channelType }; // Ajouter le parent si disponible if (category) { createOptions.parent = category.id; } // Ajouter les options spécifiques au type de canal if (channelType === 'GUILD_TEXT' || channelType === 'GUILD_NEWS') { if (channelData.topic) { createOptions.topic = channelData.topic; } if (typeof channelData.nsfw === 'boolean') { createOptions.nsfw = channelData.nsfw; } if (channelData.rateLimitPerUser) { createOptions.rate_limit_per_user = channelData.rateLimitPerUser; } } else if (channelType === 'GUILD_VOICE') { // Downgrade bitrate si nécessaire if (channelData.bitrate) { let bitrate = channelData.bitrate; const bitrates = Object.values(MaxBitratePerTier); while (bitrate > MaxBitratePerTier[guild.premiumTier]) { bitrate = bitrates[guild.premiumTier]; } createOptions.bitrate = bitrate; } if (channelData.userLimit) { createOptions.userLimit = channelData.userLimit; } } channelPromise = guild.channels.create(channelData.name, createOptions); } else { // Mode normal avec discord.js v14 const createOptions = { name: channelData.name, parent: category }; // Définir le type de canal pour discord.js v14 const channelTypeStr = String(channelData.type); if (channelTypeStr === String(ChannelType.GuildText) || channelTypeStr === 'GUILD_TEXT') { createOptions.type = ChannelType.GuildText; } else if (channelTypeStr === String(ChannelType.GuildVoice) || channelTypeStr === 'GUILD_VOICE') { createOptions.type = ChannelType.GuildVoice; } else if (channelTypeStr === String(ChannelType.GuildAnnouncement) || channelTypeStr === 'GUILD_NEWS') { createOptions.type = ChannelType.GuildAnnouncement; } else if (channelTypeStr === String(ChannelType.GuildStageVoice) || channelTypeStr === 'GUILD_STAGE_VOICE') { createOptions.type = ChannelType.GuildStageVoice; } // Ajouter les options spécifiques au type de canal if (channelTypeStr === String(ChannelType.GuildText) || channelTypeStr === 'GUILD_TEXT' || channelTypeStr === String(ChannelType.GuildAnnouncement) || channelTypeStr === 'GUILD_NEWS' || channelTypeStr === String(ChannelType.GuildForum) || channelTypeStr === 'GUILD_FORUM' || channelTypeStr === String(ChannelType.GuildMedia) || channelTypeStr === 'GUILD_MEDIA' || channelTypeStr === String(ChannelType.GuildStageVoice) || channelTypeStr === 'GUILD_STAGE_VOICE') { createOptions.topic = channelData.topic; createOptions.nsfw = channelData.nsfw; createOptions.rateLimitPerUser = channelData.rateLimitPerUser; } else if (channelTypeStr === String(ChannelType.GuildVoice) || channelTypeStr === 'GUILD_VOICE') { // Downgrade bitrate let bitrate = channelData.bitrate; const bitrates = Object.values(MaxBitratePerTier); while (bitrate > MaxBitratePerTier[guild.premiumTier]) { bitrate = bitrates[guild.premiumTier]; } createOptions.bitrate = bitrate; createOptions.userLimit = channelData.userLimit; } channelPromise = guild.channels.create(createOptions); } channelPromise.then(async (channel) => { /* Update channel permissions */ const finalPermissions = channelData.permissions.map((perm) => { const role = guild.roles.cache.find((r) => r.name === perm.roleName); if (role) { return { id: role.id, allow: BigInt(perm.allow), deny: BigInt(perm.deny), type: OverwriteType.Role }; } else return null; }).filter((perm) => perm !== null); await channel.permissionOverwrites.set(finalPermissions); if (channelData.type === ChannelType.GuildText || String(channelData.type) === 'GUILD_TEXT') { /* Load messages */ let webhook; (0, logger_1.debug)(`Canal de type texte détecté: ${channel.name}, Type: ${channelData.type}`); // Vérifier si le canal a des messages if (channelData.messages && channelData.messages.length > 0) { (0, logger_1.debug)(`Le canal ${channel.name} a ${channelData.messages.length} messages à charger`); (0, logger_1.debug)(`Premier message: ${JSON.stringify(channelData.messages[0], null, 2)}`); try { webhook = await loadMessages(channel, channelData.messages); (0, logger_1.debug)(`Messages chargés avec succès dans le canal ${channel.name}, Webhook créé: ${!!webhook}`); } catch (err) { (0, logger_1.error)(`Erreur lors du chargement des messages dans le canal ${channel.name}:`, err); } } else { (0, logger_1.debug)(`Le canal ${channel.name} n'a pas de messages à charger ou la propriété messages est manquante`); (0, logger_1.debug)(`Propriétés du canal: ${JSON.stringify(channelData, null, 2)}`); } const loadThreadMessages = async () => { if (channelData.threads && channelData.threads.length > 0) { // Vérifier si le canal a la propriété threads if (!channel.threads) { (0, logger_1.debug)('Le canal ne supporte pas les threads'); return; } await Promise.all(channelData.threads.map(async (threadData) => { let autoArchiveDuration = threadData.autoArchiveDuration; //if (!guild.features.includes('SEVEN_DAY_THREAD_ARCHIVE') && autoArchiveDuration === 10080) autoArchiveDuration = 4320; //if (!guild.features.includes('THREE_DAY_THREAD_ARCHIVE') && autoArchiveDuration === 4320) autoArchiveDuration = 1440; try { // Vérifier si le thread existe déjà const existingThread = channel.threads && channel.threads.cache ? channel.threads.cache.find((t) => t.name === threadData.name) : null; if (existingThread) { await loadMessages(existingThread, threadData.messages, webhook); } else if (channel.threads && channel.threads.create) { // Créer un nouveau thread const newThread = await channel.threads.create({ name: threadData.name, autoArchiveDuration }); await loadMessages(newThread, threadData.messages, webhook); } } catch (error) { } })); } }; await loadThreadMessages(); return channel; } else { resolve(channel); // Return the channel } }); }); } /** * Delete all roles, all channels, all emojis, etc... of a guild */ async function clearGuild(guild) { // Delete roles guild.roles.cache .filter((role) => role.editable && role.id !== guild.id) .forEach(async (role) => { try { await role.delete(); } catch { } }); // Delete channels guild.channels.cache.forEach(async (channel) => { try { await channel.delete(); } catch { } }); // Delete emojis guild.emojis.cache.forEach(async (emoji) => { try { await emoji.delete(); } catch { } }); // Delete webhooks const webhooks = await guild.fetchWebhooks(); webhooks.forEach(async (webhook) => { await webhook.delete(); }); // Unban members const bans = await guild.bans.fetch(); bans.forEach(async (ban) => { await guild.members.unban(ban.user.id); }); guild.setAFKChannel(null); guild.setAFKTimeout(60 * 5); guild.setIcon(null); guild.setBanner(null).catch(() => { }); guild.setSplash(null).catch(() => { }); guild.setDefaultMessageNotifications(GuildDefaultMessageNotifications.OnlyMentions); guild.setWidgetSettings({ enabled: false, channel: null }); if (!guild.features.includes(GuildFeature.Community)) { guild.setExplicitContentFilter(GuildExplicitContentFilter.Disabled); guild.setVerificationLevel(GuildVerificationLevel.None); } guild.setSystemChannel(null); guild.setSystemChannelFlags([GuildSystemChannelFlags.SuppressGuildReminderNotifications, GuildSystemChannelFlags.SuppressJoinNotifications, GuildSystemChannelFlags.SuppressPremiumSubscriptions]); return; }