discord-rebackup
Version:
An rewamped fork of the discord-backup module ! by the iHorizon Team
676 lines (675 loc) • 31.2 kB
JavaScript
;
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;
}