Skip to content
This repository has been archived by the owner on Jul 8, 2024. It is now read-only.

Upgrade to Discord.js v13 #35

Open
wants to merge 3 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
1,482 changes: 1,080 additions & 402 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "A discord bot for the official Smogon discord https://discord.gg/smogon",
"main": "./dist/app.js",
"scripts": {
"test": "npm run lint && npm run unit-test",
"test": "npm run lint",
"unit-test": "jest",
"lint": "eslint . --cache --ext .ts && tsc",
"fix": "eslint . --cache --ext .ts --fix",
Expand All @@ -18,17 +18,19 @@
"author": "Andrew Werner",
"license": "MIT",
"dependencies": {
"@types/node": "^12.7.8",
"@discordjs/rest": "^0.3.0",
"@types/node": "^17.0.23",
"@types/pg": "^7.14.1",
"@types/ws": "^7.2.3",
"discord.js": "^12.1.1",
"@types/ws": "^7.4.7",
"discord-api-types": "^0.31.0",
"discord.js": "^13.6.0",
"dotenv": "^8.1.0",
"eslint-plugin-jest": "^24.3.5",
"pg": "^8.0.3",
"pg-mem": "^1.9.6",
"pg-mem": "^1.8.6",
"postgrator": "^4.0.1",
"sqlutils": "^1.2.1",
"typescript": "^4.1.5"
"typescript": "^4.6.3"
},
"devDependencies": {
"@pkmn/eslint-config": "^1.1.0",
Expand Down
35 changes: 25 additions & 10 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,26 @@ interface IDatabaseInsert {
channel?: DiscordChannel;
}

export const client = new Discord.Client();
export const client = new Discord.Client({
intents: [
// For reporting ban info
Discord.Intents.FLAGS.GUILD_BANS,
// For checking if a user is set as offline
Discord.Intents.FLAGS.GUILD_PRESENCES,
// For reading messages
Discord.Intents.FLAGS.GUILD_MESSAGES,
Discord.Intents.FLAGS.DIRECT_MESSAGES,
// For checking reactions on messages
Discord.Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
Discord.Intents.FLAGS.DIRECT_MESSAGE_REACTIONS,
// TODO determine if this is needed
// Discord.Intents.FLAGS.GUILDS,
],
});

// Necessary to match process.on('uncaughtException')
// eslint-disable-next-line @typescript-eslint/ban-types
export async function onError(err: Error | {} | null | undefined, detail = '') {
export async function onError(err: any, detail = '') {
if (!err) return console.error('Error with no details thrown.');
// Don't flood the error report channel, only report 1 error per minute.
if (Date.now() > lastErrorReport + (1000 * 60)) {
Expand Down Expand Up @@ -195,7 +210,7 @@ client.on('ready', () => void (async () => {

// Fires when we get a new message from discord. We ignore messages that aren't commands or are from a bot.
client.on('message', (m) => void (async msg => {
if (msg.webhookID) return;
if (msg.webhookId) return;
await verifyData(msg);
if (msg.author.bot) return;
if (!msg.content.startsWith(prefix)) {
Expand All @@ -209,7 +224,7 @@ client.on('message', (m) => void (async msg => {
await monitor.execute();
}
} catch (e) {
await onError(e, 'A chat monitor crashed: ');
await onError(e as Error, 'A chat monitor crashed: ');
}
}
return;
Expand All @@ -219,18 +234,18 @@ client.on('message', (m) => void (async msg => {
if (lockdown) return msg.reply('The bot is restarting soon, please try again in a minute.');

const cmdID = toID(msg.content.slice(prefix.length).split(' ')[0]);
let command = commands.get(cmdID);
if (typeof command === 'string') command = commands.get(command);
let Command = commands.get(cmdID);
if (typeof Command === 'string') Command = commands.get(Command);
// Throw if it's another alias
if (typeof command === 'string') throw new Error(`Alias "${cmdID}" did not point to command.`);
if (!command) return;
if (typeof Command === 'string') throw new Error(`Alias "${cmdID}" did not point to command.`);
if (!Command) return;

// 100% not an alias, so it must be a command class.
const cmd = new (command as Constructable<BaseCommand>)(msg);
const cmd = new Command(msg);
try {
await cmd.execute();
} catch (e) {
await onError(e, 'A chat command crashed: ');
await onError(e as Error, 'A chat command crashed: ');
await msg.channel.send(
'\u274C - An error occured while trying to run your command. The error has been logged, and we will fix it soon.'
);
Expand Down
23 changes: 15 additions & 8 deletions src/command_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export abstract class BaseCommand {

// All custom permissions need to resolve above.
if (!permissions.includes(permission)) throw new Error(`Unhandled custom permission: ${permission}.`);
return member.hasPermission((permission as Discord.PermissionResolvable), {checkAdmin: true, checkOwner: true});
return member.permissions.has((permission as Discord.PermissionResolvable));
}

/**
Expand Down Expand Up @@ -143,7 +143,7 @@ export abstract class BaseCommand {
if (channel.guild && channel.guild.id !== this.guild.id) return;
}
if (authorVisibilitity) {
const guildMember = channel.guild.member(this.author.id);
const guildMember = channel.guild.members.cache.get(this.author.id);
if (!guildMember) return; // User not in guild and cannot see channel
const permissions = channel.permissionsFor(guildMember);
if (!permissions) {
Expand Down Expand Up @@ -271,6 +271,12 @@ export abstract class BaseCommand {
return channel.send('\u274C ' + msg);
}

protected embedReply(embeds: Discord.MessageEmbedOptions[], channel?: DiscordChannel):
Promise<Discord.Message> | void {
HoeenCoder marked this conversation as resolved.
Show resolved Hide resolved
if (!channel) channel = this.channel;
return channel.send({embeds: embeds});
}

/**
* Send a reply in a code block
* @param msg The message to reply with
Expand All @@ -288,7 +294,7 @@ export abstract class BaseCommand {
* If one is not setup, the message is dropped.
* @param msg The message to send
*/
protected async sendLog(msg: string | Discord.MessageEmbed): Promise<Discord.Message | void> {
protected async sendLog(msg: string | Discord.MessageOptions): Promise<Discord.Message | void> {
if (!toID(msg) || !this.guild) return;
const res = await database.queryWithResults('SELECT logchannel FROM servers WHERE serverid = $1', [this.guild.id]);
const channel = this.getChannel(res[0].logchannel, false, false);
Expand Down Expand Up @@ -353,15 +359,16 @@ export abstract class ReactionPageTurner {
async initialize(messageOrChannel: Discord.Message | DiscordChannel): Promise<void> {
if (this.message) throw new Error('Reaction Page Turner already initialized.');
if (!(messageOrChannel instanceof Discord.Message)) {
this.message = await messageOrChannel.send(this.buildPage());
this.message = await messageOrChannel.send({embeds: [this.buildPage()]});
} else {
this.message = messageOrChannel;
}

const filter: Discord.CollectorFilter = (reaction, user) => (
this.targetReactions.includes(reaction.emoji.name) && this.user.id === user.id
this.options.filter = (reaction, user) => (
this.targetReactions.includes(reaction.emoji.name || '') && this.user.id === user.id
);
this.collector = new Discord.ReactionCollector(this.message, filter, this.options);

this.collector = new Discord.ReactionCollector(this.message, this.options);

this.collector.on('collect', (reaction) => void this.collect(reaction));
this.collector.on('end', this.end.bind(this));
Expand Down Expand Up @@ -413,7 +420,7 @@ export abstract class ReactionPageTurner {
throw new Error(`Unexpected reaction on page turner: ${reaction.emoji.name}`);
}

await this.message.edit(this.buildPage());
await this.message.edit({embeds: [this.buildPage()]});
}

protected end(): void {
Expand Down
5 changes: 3 additions & 2 deletions src/commands/boosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ async function updateBoosters() {
for (const [guildId, guild] of client.guilds.cache) {
const res = await database.queryWithResults('SELECT userid FROM userlist WHERE serverid = $1 AND boosting IS NOT NULL', [guildId]);
const boosting = res.map(r => r.userid);
const logchannelResult = await database.queryWithResults('SELECT logchannel FROM servers WHERE serverid = $1', [guildId]);
const logChannel = client.channels.cache.get(logchannelResult[0].logchannel) as DiscordChannel;
const logchannelResult = (await database.queryWithResults('SELECT logchannel FROM servers WHERE serverid = $1', [guildId]))[0];
const logChannel = logchannelResult
? client.channels.cache.get(logchannelResult.logchannel) as DiscordChannel : undefined;
await guild.members.fetch();

for (const [id, gm] of guild.members.cache) {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class Eval extends BaseCommand {
result = await eval(this.target);
if (result === '') result = '""';
} catch (e) {
result = `An error occured: ${e.toString()}`;
result = `An error occured: ${(e as Error).toString()}`;
}
await this.sendCode(result);
}
Expand All @@ -60,7 +60,7 @@ export class Query extends BaseCommand {
const res = await database.queryWithResults(this.target, undefined);
await this.sendCode(this.formatResponse(res));
} catch (err) {
await this.sendCode(`An error occured: ${err.toString()}`);
await this.sendCode(`An error occured: ${(err as Error).toString()}`);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/commands/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class Help extends BaseCommand {
timestamp: Date.now(),
};

await this.channel.send({embed: embed});
await this.embedReply([embed]);
} else {
// General help
const data: {[key: string]: string}[] = [];
Expand Down
9 changes: 4 additions & 5 deletions src/commands/moderation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ export class Whois extends BaseCommand {
private async getJoinPosition(user: Discord.GuildMember): Promise<string> {
const guild = user.guild;
await guild.members.fetch();
const orderedList = guild.members.cache
.array()
const orderedList = [...guild.members.cache.values()]
.sort((a, b) => (a.joinedTimestamp || Date.now()) - (b.joinedTimestamp || Date.now()));

return `${orderedList.indexOf(user) + 1} of ${orderedList.length}`;
Expand Down Expand Up @@ -82,7 +81,7 @@ export class Whois extends BaseCommand {
},
};

await this.channel.send({embed: embed});
await this.embedReply([embed]);
}

static help(): string {
Expand Down Expand Up @@ -124,7 +123,7 @@ export class WhoHas extends BaseCommand {
},
};

void this.channel.send({embed: embed});
void this.embedReply([embed]);
}

static help(): string {
Expand Down Expand Up @@ -152,7 +151,7 @@ abstract class StickyCommand extends BaseCommand {
if (await this.can('EVAL', user.user)) return true;

// Server owner override
if (this.guild.ownerID === user.user.id) return true;
if (this.guild.ownerId === user.user.id) return true;

await this.guild.roles.fetch();
const highestRole = [...user.roles.cache.values()].sort((a, b) => b.comparePositionTo(a))[0];
Expand Down
2 changes: 1 addition & 1 deletion src/commands/rmt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class RaterList extends ReactionPageTurner {

private buildFormat(): Discord.EmbedFieldData[] {
return [{
name: this.format,
name: this.format || 'List of Team Raters',
value: this.data.map(v => `${v.name}#${v.discriminator}`).join(', ') || 'No Team Raters Found.',
}];
}
Expand Down
36 changes: 18 additions & 18 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ async function getLogChannel(guild: Discord.Guild): Promise<Discord.TextChannel
async function fetchAuditLog(
type: Discord.GuildAuditLogsAction,
guild: Discord.Guild
): Promise<Discord.GuildAuditLogsEntry | void> {
if (!guild.me?.hasPermission('VIEW_AUDIT_LOG')) return;
): Promise<Discord.GuildAuditLogsEntry<Discord.GuildAuditLogsAction> | void> {
if (!guild.me?.permissions.has('VIEW_AUDIT_LOG')) return;

const log = (await guild.fetchAuditLogs({
limit: 1,
Expand Down Expand Up @@ -56,15 +56,14 @@ client.on('messageDelete', (oldMsg: Discord.Message | Discord.PartialMessage) =>
if (!logChannel) return; // Nowhere to log to

const log = await fetchAuditLog('MESSAGE_DELETE', oldMessage.guild);
if (!log || log.executor.id === oldMessage.author.id) return; // Not a mod delete
if (!log || !log.executor || log.executor.id === oldMessage.author.id) return; // Not a mod delete or unable to tell
HoeenCoder marked this conversation as resolved.
Show resolved Hide resolved

// Don't report for private channels
await oldMessage.guild.roles.fetch();
const everyone = oldMessage.guild.roles.everyone; // everyone is always a role
if (!everyone) throw new Error('Unable to find the everyone role in the messageDelete event');
const permissions = (oldMessage.channel as Discord.TextChannel | Discord.NewsChannel)
.permissionOverwrites
.get(everyone.id);
.permissionOverwrites.cache.get(everyone.id);
if (permissions?.deny.has('VIEW_CHANNEL')) {
// There are custom permissions for @everyone on this channel, and @everyone cannot view the channel.
return;
Expand Down Expand Up @@ -98,7 +97,7 @@ client.on('messageDelete', (oldMsg: Discord.Message | Discord.PartialMessage) =>
timestamp: Date.now(),
};

await logChannel.send({embed: embed});
await logChannel.send({embeds: [embed]});
})(oldMsg);
});

Expand All @@ -110,7 +109,7 @@ client.on('guildMemberRemove', (u: Discord.GuildMember | Discord.PartialGuildMem

const log = await fetchAuditLog('MEMBER_KICK', user.guild);

if (!log || log.executor.id === user.user.id) return; // No log or user left of their own will
if (!log || !log.executor || log.executor.id === user.user.id) return; // No log or user left of their own will

const embed: Discord.MessageEmbedOptions = {
color: 0x6194fd,
Expand All @@ -131,12 +130,13 @@ client.on('guildMemberRemove', (u: Discord.GuildMember | Discord.PartialGuildMem
],
};

await logChannel.send({embed: embed});
await logChannel.send({embeds: [embed]});
})(u);
});

async function banChange(guild: Discord.Guild, user: Discord.User | Discord.PartialUser, unbanned = false) {
user = (user as Discord.User);
async function banChange(ban: Discord.GuildBan, unbanned = false) {
const user = ban.user;
const guild = ban.guild;
const logChannel = await getLogChannel(guild);
if (!logChannel) return; // Nowhere to log to

Expand All @@ -152,25 +152,25 @@ async function banChange(guild: Discord.Guild, user: Discord.User | Discord.Part
fields: [
{
name: user.tag,
value: log ? `by <@${log.executor.id}>` : 'by Unknown',
value: log?.executor ? `by <@${log.executor.id}>` : 'by Unknown',
},
{
name: 'Reason',
value: log ? log.reason || 'N/A' : 'N/A',
value: typeof ban.reason === 'string' ? ban.reason : 'N/A',
},
],
timestamp: Date.now(),
};

await logChannel.send({embed: embed});
await logChannel.send({embeds: [embed]});
}

client.on('guildBanAdd', (guild: Discord.Guild, user: Discord.User | Discord.PartialUser) => {
void banChange(guild, user);
client.on('guildBanAdd', (ban: Discord.GuildBan) => {
void banChange(ban);
});

client.on('guildBanRemove', (guild: Discord.Guild, user: Discord.User | Discord.PartialUser) => {
void banChange(guild, user, true);
client.on('guildBanRemove', (ban: Discord.GuildBan) => {
void banChange(ban, true);
});

client.on('guildMemberAdd', (m: Discord.GuildMember | Discord.PartialGuildMember) => {
Expand All @@ -189,7 +189,7 @@ client.on('guildMemberAdd', (m: Discord.GuildMember | Discord.PartialGuildMember
if (!sticky.length) return; // User rejoined and had 0 sticky roles.

// Re-assign sticky roles
if (!bot.hasPermission('MANAGE_ROLES')) {
if (!bot.permissions.has('MANAGE_ROLES')) {
// Bot can't assign roles due to lack of permissions
const channel = await getLogChannel(guild);
const msg = '[WARN] Bot tried to assign sticky (persistant) roles to a user joining the server, but lacks the MANAGE_ROLES permission.';
Expand Down
4 changes: 2 additions & 2 deletions src/lib/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class ExternalPostgresDatabase implements Database {
async query(statement: string, args?: any[]) {
try {
await this.pool.query(statement, args);
} catch (e) {
} catch (e: any) {
throw new Error(`Error while quering database: ${e.message}\n` +
`Query: ${statement}\nArgs: ${args}\n`);
}
Expand All @@ -58,7 +58,7 @@ export class ExternalPostgresDatabase implements Database {
try {
const result = await this.pool.query(statement, args);
return result.rows;
} catch (e) {
} catch (e: any) {
throw new Error(`Error while quering database: ${e.message}\n` +
`Query: ${statement}\nArgs: ${args}\n`);
}
Expand Down
2 changes: 1 addition & 1 deletion src/monitors/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class ActivityMonitor extends BaseMonitor {
await this.guild.roles.fetch();
const everyone = this.guild.roles.everyone; // everyone is always a role
if (!everyone) throw new Error('Unable to find the everyone role when logging linecounts.');
const permissions = this.channel.permissionOverwrites.get(everyone.id);
const permissions = this.channel.permissionOverwrites.resolve(everyone.id);
if (permissions?.deny.has('VIEW_CHANNEL')) {
// There are custom permissions for @everyone on this channel, and @everyone cannot view the channel.
return false;
Expand Down
Loading