Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Working version for discord.js 14 #486

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@
},
"homepage": "https://github.com/Androz2091/discord-backup#readme",
"dependencies": {
"discord.js": "^14.2.0"
"discord.js": "^14.7.0"
},
"devDependencies": {
"@types/node": "^17.0.30",
"@types/node-fetch": "^2.5.7",
"@types/ws": "^7.2.4",
"prettier": "^2.0.4",
"tslint": "^6.1.1",
"@types/node": "^18.11.9",
"@types/node-fetch": "^2.6.2",
"@types/ws": "^8.5.3",
"prettier": "^2.8.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.6.4"
"typescript": "^4.9.3"
}
}
8 changes: 4 additions & 4 deletions src/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
TextChannelData,
VoiceChannelData
} from './types';
import type { CategoryChannel, ChannelType, Collection, Guild, GuildChannel, Snowflake, TextChannel, ThreadChannel, VoiceChannel } from 'discord.js';
import { CategoryChannel, ChannelType, Collection, Guild, GuildChannel, Snowflake, TextChannel, ThreadChannel, VoiceChannel } from 'discord.js';
import nodeFetch from 'node-fetch';
import { fetchChannelPermissions, fetchTextChannelData, fetchVoiceChannelData } from './util';
import { MemberData } from './types/MemberData';
Expand Down Expand Up @@ -122,7 +122,7 @@ export async function getChannels(guild: Guild, options: CreateOptions) {
children: [] // The children channels of the category
};
// Gets the children channels of the category and sort them by position
const children = category.children.sort((a, b) => a.position - b.position).toJSON();
const children = category.children.cache.sort((a, b) => a.position - b.position).toJSON();
for (const child of children) {
// For each child channel
if (child.type === ChannelType.GuildText || child.type === ChannelType.GuildNews) {
Expand All @@ -140,13 +140,13 @@ export async function getChannels(guild: Guild, options: CreateOptions) {
.filter((ch) => {
return !ch.parent && ch.type !== ChannelType.GuildCategory
//&& ch.type !== 'GUILD_STORE' // there is no way to restore store channels, ignore them
&& ch.type !== ChannelType.GuildNewsThread && ch.type !== ChannelType.GuildPrivateThread && ch.type !== ChannelType.GuildPublicThread // threads will be saved with fetchTextChannelData
&& ch.type !== ChannelType.AnnouncementThread && ch.type !== ChannelType.PrivateThread && ch.type !== ChannelType.PublicThread // threads will be saved with fetchTextChannelData
}) as Collection<Snowflake, Exclude<GuildChannel, ThreadChannel>>)
.sort((a, b) => a.position - b.position)
.toJSON();
for (const channel of others) {
// For each channel
if (channel.type === ChnanelType.GuildText || channel.type === ChannelType.GuildNews) {
if (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildAnnouncement) {
const channelData: TextChannelData = await fetchTextChannelData(channel as TextChannel, options); // Gets the channel data
channels.others.push(channelData); // Update channels object
} else {
Expand Down
12 changes: 6 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ export const create = async (
) => {
return new Promise<BackupData>(async (resolve, reject) => {

const intents = new IntentsBitField(guild.client.options.intents);
if (!intents.has(IntentsBitField.Flags.Guilds)) return reject('Guilds intent is required');
const intents = guild.client.options.intents;
if (!intents.has(IntentsBitField.Flags.Guilds)) return reject('Guilds intent is required');

try {
const backupData: BackupData = {
Expand All @@ -97,15 +97,15 @@ export const create = async (
members: [],
createdTimestamp: Date.now(),
guildID: guild.id,
id: options.backupID ?? SnowflakeUtil.generate(Date.now())
id: options.backupID ?? SnowflakeUtil.generate({ timestamp: Date.now() }).toString()
};
if (guild.iconURL()) {
if (guild.iconURL({})) {
if (options && options.saveImages && options.saveImages === 'base64') {
backupData.iconBase64 = (
await nodeFetch(guild.iconURL({ dynamic: true })).then((res) => res.buffer())
await nodeFetch(guild.iconURL({})).then((res) => res.buffer())
).toString('base64');
}
backupData.iconURL = guild.iconURL({ dynamic: true });
backupData.iconURL = guild.iconURL({});
}
if (guild.splashURL()) {
if (options && options.saveImages && options.saveImages === 'base64') {
Expand Down
10 changes: 5 additions & 5 deletions src/load.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { BackupData, LoadOptions } from './types';
import type { ChannelType, Emoji, Guild, GuildFeature, GuildChannel, Role, VoiceChannel } from 'discord.js';
import { ChannelType, Emoji, Guild, GuildFeature, Role, VoiceChannel, NewsChannel, ForumChannel, TextChannel, Snowflake, StageChannel, GuildBasedChannel } from 'discord.js';
import { loadCategory, loadChannel } from './util';

/**
Expand Down Expand Up @@ -97,7 +97,7 @@ export const loadChannels = (guild: Guild, backupData: BackupData, options: Load
export const loadAFK = (guild: Guild, backupData: BackupData): Promise<Guild[]> => {
const afkPromises: Promise<Guild>[] = [];
if (backupData.afk) {
afkPromises.push(guild.setAFKChannel(guild.channels.cache.find((ch) => ch.name === backupData.afk.name && ch.type === ChannelType.GuildVoice) as VoiceChannel));
afkPromises.push(guild.setAFKChannel(guild.channels.cache.find((ch: GuildBasedChannel) => ch.name === backupData.afk.name && ch.type === ChannelType.GuildVoice) as VoiceChannel));
afkPromises.push(guild.setAFKTimeout(backupData.afk.timeout));
}
return Promise.all(afkPromises);
Expand All @@ -110,9 +110,9 @@ export const loadEmojis = (guild: Guild, backupData: BackupData): Promise<Emoji[
const emojiPromises: Promise<Emoji>[] = [];
backupData.emojis.forEach((emoji) => {
if (emoji.url) {
emojiPromises.push(guild.emojis.create(emoji.url, emoji.name));
emojiPromises.push(guild.emojis.create({ name: emoji.name, attachment: emoji.url }));
} else if (emoji.base64) {
emojiPromises.push(guild.emojis.create(Buffer.from(emoji.base64, 'base64'), emoji.name));
emojiPromises.push(guild.emojis.create({ name: emoji.name, attachment: Buffer.from(emoji.base64, 'base64') }));
}
});
return Promise.all(emojiPromises);
Expand Down Expand Up @@ -142,7 +142,7 @@ export const loadEmbedChannel = (guild: Guild, backupData: BackupData): Promise<
embedChannelPromises.push(
guild.setWidgetSettings({
enabled: backupData.widget.enabled,
channel: guild.channels.cache.find((ch) => ch.name === backupData.widget.channel)
channel: <TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | Snowflake>guild.channels.cache.find((ch) => ch.name === backupData.widget.channel)
})
);
}
Expand Down
8 changes: 4 additions & 4 deletions src/types/BackupData.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { DefaultMessageNotificationLevel, ExplicitContentFilterLevel, Snowflake, VerificationLevel } from 'discord.js';
import { GuildDefaultMessageNotifications, GuildExplicitContentFilter, Snowflake, GuildVerificationLevel } from 'discord.js';
import { AfkData, BanData, ChannelsData, EmojiData, RoleData, WidgetData } from './';
import { MemberData } from './MemberData';

export interface BackupData {
name: string;
iconURL?: string;
iconBase64?: string;
verificationLevel: VerificationLevel;
explicitContentFilter: ExplicitContentFilterLevel;
defaultMessageNotifications: DefaultMessageNotificationLevel | number;
verificationLevel: GuildVerificationLevel;
explicitContentFilter: GuildExplicitContentFilter;
defaultMessageNotifications: GuildDefaultMessageNotifications | number;
afk?: AfkData;
widget: WidgetData;
splashURL?: string;
Expand Down
4 changes: 2 additions & 2 deletions src/types/BaseChannelData.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { TextBasedChannelTypes, VoiceBasedChannelTypes, ThreadChannelTypes } from 'discord.js';
import { TextBasedChannelTypes, VoiceBasedChannelTypes, ThreadChannelType } from 'discord.js';
import { ChannelPermissionsData } from './';

export interface BaseChannelData {
type: TextBasedChannelTypes | VoiceBasedChannelTypes | ThreadChannelTypes;
type: TextBasedChannelTypes | VoiceBasedChannelTypes | ThreadChannelType;
name: string;
parent?: string;
permissions: ChannelPermissionsData[];
Expand Down
7 changes: 3 additions & 4 deletions src/types/MessageData.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { MessageEmbed, FileOptions } from 'discord.js';

import { Embed, RawFile } from 'discord.js';
export interface MessageData {
username: string;
avatar?: string;
content?: string;
embeds?: MessageEmbed[];
files?: FileOptions[];
embeds?: Embed[];
files?: RawFile[];
pinned?: boolean;
sentAt: string;
}
4 changes: 2 additions & 2 deletions src/types/ThreadChannelData.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Snowflake, ThreadAutoArchiveDuration, ThreadChannelTypes } from "discord.js";
import { ThreadAutoArchiveDuration, ThreadChannelType } from "discord.js";
import { MessageData } from "./MessageData";

export interface ThreadChannelData {
type: ThreadChannelTypes;
type: ThreadChannelType;
name: string;
archived: boolean;
autoArchiveDuration: ThreadAutoArchiveDuration;
Expand Down
2 changes: 0 additions & 2 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { GuildFeatures } from 'discord.js';

export * from './AfkData';
export * from './BackupData';
export * from './BackupInfos';
Expand Down
71 changes: 36 additions & 35 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import type {
ThreadChannelData,
VoiceChannelData
} from './types';
import type {
import {
CategoryChannel,
ChannelLogsQueryOptions,
ChannelType,
ChannelType,
Collection,
Guild,
GuildFeature,
GuildDefaultMessageNotifications,
GuildDefaultMessageNotifications,
GuildSystemChannelFlags,
GuildChannelCreateOptions,
Message,
Expand All @@ -24,18 +23,21 @@ import type {
TextChannel,
VoiceChannel,
NewsChannel,
PremiumTier,
ThreadChannel,
GuildFeatures,
Webhook
Webhook,
GuildPremiumTier,
GuildExplicitContentFilter,
GuildVerificationLevel,
OverwriteType,
AttachmentBuilder
} from 'discord.js';
import nodeFetch from 'node-fetch';

const MaxBitratePerTier: Record<PremiumTier, number> = {
None: 64000,
Tier1: 128000,
Tier2: 256000,
Tier3: 384000
const MaxBitratePerTier: Record<GuildPremiumTier, number> = {
[GuildPremiumTier.None]: 64000,
[GuildPremiumTier.Tier1]: 128000,
[GuildPremiumTier.Tier2]: 256000,
[GuildPremiumTier.Tier3]: 384000
};

/**
Expand All @@ -44,7 +46,7 @@ const MaxBitratePerTier: Record<PremiumTier, number> = {
export function fetchChannelPermissions(channel: TextChannel | VoiceChannel | CategoryChannel | NewsChannel) {
const permissions: ChannelPermissionsData[] = [];
channel.permissionOverwrites.cache
.filter((p) => p.type === 'role')
.filter((p) => p.type === OverwriteType.Role)
.forEach((perm) => {
// For each overwrites permission
const role = channel.guild.roles.cache.get(perm.id);
Expand Down Expand Up @@ -77,10 +79,10 @@ export async function fetchVoiceChannelData(channel: VoiceChannel) {
});
}

export async function fetchChannelMessages (channel: TextChannel | NewsChannel | ThreadChannel, options: CreateOptions): Promise<MessageData[]> {
export async function fetchChannelMessages(channel: TextChannel | NewsChannel | ThreadChannel, options: CreateOptions): Promise<MessageData[]> {
let messages: MessageData[] = [];
const messageCount: number = isNaN(options.maxMessagesPerChannel) ? 10 : options.maxMessagesPerChannel;
const fetchOptions: ChannelLogsQueryOptions = { limit: 100 };
const fetchOptions: { limit?: number, before?: Snowflake, after?: Snowflake, around?: Snowflake } = { limit: 100 };
let lastMessageId: Snowflake;
let fetchComplete: boolean = false;
while (!fetchComplete) {
Expand All @@ -106,23 +108,23 @@ export async function fetchChannelMessages (channel: TextChannel | NewsChannel |
}
return {
name: a.name,
attachment: attach
data: attach
};
}))
messages.push({
username: msg.author.username,
avatar: msg.author.displayAvatarURL(),
content: msg.cleanContent,
embeds: msg.embeds,
files,
files: files,
pinned: msg.pinned,
sentAt: msg.createdAt.toISOString(),
});
}));
}

return messages;
}
}

/**
* Fetches the text channel data that is necessary for the backup
Expand Down Expand Up @@ -165,7 +167,6 @@ export async function fetchTextChannelData(channel: TextChannel | NewsChannel, o
/* Fetch channel messages */
try {
channelData.messages = await fetchChannelMessages(channel, options);

/* Return channel data */
resolve(channelData);
} catch {
Expand All @@ -179,9 +180,7 @@ export async function fetchTextChannelData(channel: TextChannel | NewsChannel, o
*/
export async function loadCategory(categoryData: CategoryData, guild: Guild) {
return new Promise<CategoryChannel>((resolve) => {
guild.channels.create(categoryData.name, {
type: ChannelType.GuildCategory
}).then(async (category) => {
guild.channels.create({ name: categoryData.name, type: ChannelType.GuildCategory }).then(async (category) => {
// When the category is created
const finalPermissions: OverwriteData[] = [];
categoryData.permissions.forEach((perm) => {
Expand Down Expand Up @@ -211,25 +210,27 @@ export async function loadChannel(
) {
return new Promise(async (resolve) => {

const loadMessages = (channel: TextChannel | ThreadChannel, messages: MessageData[], previousWebhook?: Webhook): Promise<Webhook|void> => {
const loadMessages = (channel: TextChannel | ThreadChannel, messages: MessageData[], previousWebhook?: Webhook): Promise<Webhook | void> => {
return new Promise(async (resolve) => {
const webhook = previousWebhook || await (channel as TextChannel).createWebhook({
name: 'MessagesBackup',
avatar: channel.client.user.displayAvatarURL()
}).catch(() => {});
}).catch(() => { });
if (!webhook) return resolve();
messages = messages
.filter((m) => m.content.length > 0 || m.embeds.length > 0 || m.files.length > 0)
.sort((a, b) => { return new Date(a.sentAt) < new Date(b.sentAt) ? 1 : -1 })
.reverse();
messages = messages.slice(messages.length - options.maxMessagesPerChannel);
for (const msg of messages) {
const files = msg.files.map(file => new AttachmentBuilder(<string | Buffer>file.data, { name: file.name }));
const sentMsg = await webhook
.send({
content: msg.content.length ? msg.content : undefined,
username: msg.username,
avatarURL: msg.avatar,
embeds: msg.embeds,
files: msg.files,
files: files,
allowedMentions: options.allowedMentions,
threadId: channel.isThread() ? channel.id : undefined
})
Expand Down Expand Up @@ -258,7 +259,7 @@ export async function loadChannel(
let bitrate = (channelData as VoiceChannelData).bitrate;
const bitrates = Object.values(MaxBitratePerTier);
while (bitrate > MaxBitratePerTier[guild.premiumTier]) {
bitrate = bitrates[Object.keys(MaxBitratePerTier).indexOf(guild.premiumTier) - 1];
bitrate = bitrates[Object.keys(MaxBitratePerTier).indexOf(guild.premiumTier.toString()) - 1];
}
createOptions.bitrate = bitrate;
createOptions.userLimit = (channelData as VoiceChannelData).userLimit;
Expand All @@ -280,9 +281,9 @@ export async function loadChannel(
await channel.permissionOverwrites.set(finalPermissions);
if (channelData.type === ChannelType.GuildText) {
/* Load messages */
let webhook: Webhook|void;
let webhook: Webhook | void;
if ((channelData as TextChannelData).messages.length > 0) {
webhook = await loadMessages(channel as TextChannel, (channelData as TextChannelData).messages).catch(() => {});
webhook = await loadMessages(channel as TextChannel, (channelData as TextChannelData).messages).catch(() => { });
}
/* Load threads */
if ((channelData as TextChannelData).threads.length > 0) { //&& guild.features.includes('THREADS_ENABLED')) {
Expand Down Expand Up @@ -314,27 +315,27 @@ export async function clearGuild(guild: Guild) {
guild.roles.cache
.filter((role) => !role.managed && role.editable && role.id !== guild.id)
.forEach((role) => {
role.delete().catch(() => {});
role.delete().catch(() => { });
});
guild.channels.cache.forEach((channel) => {
channel.delete().catch(() => {});
channel.delete().catch(() => { });
});
guild.emojis.cache.forEach((emoji) => {
emoji.delete().catch(() => {});
emoji.delete().catch(() => { });
});
const webhooks = await guild.fetchWebhooks();
webhooks.forEach((webhook) => {
webhook.delete().catch(() => {});
webhook.delete().catch(() => { });
});
const bans = await guild.bans.fetch();
bans.forEach((ban) => {
guild.members.unban(ban.user).catch(() => {});
guild.members.unban(ban.user).catch(() => { });
});
guild.setAFKChannel(null);
guild.setAFKTimeout(60 * 5);
guild.setIcon(null);
guild.setBanner(null).catch(() => {});
guild.setSplash(null).catch(() => {});
guild.setBanner(null).catch(() => { });
guild.setSplash(null).catch(() => { });
guild.setDefaultMessageNotifications(GuildDefaultMessageNotifications.OnlyMentions);
guild.setWidgetSettings({
enabled: false,
Expand Down
Loading