This commit is contained in:
2022-02-13 21:39:29 -06:00
parent 983f742f0d
commit f5ab2b4297
10 changed files with 2730 additions and 279 deletions

55
.eslintrc.json Normal file
View File

@@ -0,0 +1,55 @@
{
"root": true,
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": { "project": ["./tsconfig.json"] },
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/strict-boolean-expressions": [
2,
{
"allowString" : false,
"allowNumber" : false
}
],
/* important */
"prefer-const": "error",
"quotes": ["error", "single"],
"block-scoped-var": "error",
"camelcase": "error",
"consistent-this": ["error", "that"],
"no-else-return": "error",
"no-eq-null": "error",
"no-floating-decimal": "error",
"no-implicit-coercion": "error",
"no-implied-eval": "error",
"no-invalid-this": "error",
"require-await": "error",
"yoda": "error",
"semi": ["error", "always"],
"semi-style": ["error", "last"],
/* less important */
"no-unreachable-loop": "error",
"no-unused-private-class-members": "error",
"no-use-before-define": "error",
"no-unmodified-loop-condition": "error",
"no-duplicate-imports": "error",
"no-promise-executor-return": "error",
"no-self-compare": "error",
"no-constructor-return": "error",
"no-template-curly-in-string": "error",
"array-callback-return": "error",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error"
},
"ignorePatterns": ["src/**/*.test.ts", "src/frontend/generated/*"]
}

2393
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "^17.0.17", "@types/node": "^17.0.17",
"@typescript-eslint/eslint-plugin": "^5.11.0",
"discord-api-types": "^0.26.1", "discord-api-types": "^0.26.1",
"npm-watch": "^0.11.0", "npm-watch": "^0.11.0",
"ts-node": "^10.4.0", "ts-node": "^10.4.0",

View File

@@ -1,3 +1,4 @@
/*eslint prefer-const: "error"*/
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { CommandInteraction, MessageEmbed } from 'discord.js'; import { CommandInteraction, MessageEmbed } from 'discord.js';
import { IncomingMessage } from 'http'; import { IncomingMessage } from 'http';
@@ -12,7 +13,7 @@ const uniteApiRegex = {
/lv\.(\d+) (\w+) \((\d+)\)/i, //master /lv\.(\d+) (\w+) \((\d+)\)/i, //master
/lv\.(\d+) (\w+): class (\d+)/i //other /lv\.(\d+) (\w+): class (\d+)/i //other
] ]
} };
type uniteApiData = { type uniteApiData = {
name: string, name: string,
@@ -76,30 +77,30 @@ function getHTML(name: string): Promise<string> {
* @throws errorMessage class if the request fails * @throws errorMessage class if the request fails
*/ */
function readHTML(html: string): uniteApiData { function readHTML(html: string): uniteApiData {
let $ = cheerio.load(html) const $ = cheerio.load(html),
let metaElems = $('meta').toArray(),
foundData: uniteApiData = { foundData: uniteApiData = {
name: "", name: '',
id: "", id: '',
avatar: "", avatar: '',
level: "", level: '',
rank: "", rank: '',
elo: null, elo: null,
class: null, class: null,
battles: "", battles: '',
wins: "", wins: '',
winrate: "" winrate: ''
}; };
let metaElems = $('meta').toArray();
//filter down to just ones named "og:..." //filter down to just ones named "og:..."
metaElems = metaElems.filter(el => el.attribs.property?.startsWith('og:')); metaElems = metaElems.filter(el => el.attribs.property?.startsWith('og:'));
metaElems.forEach(el => { metaElems.forEach(el => {
let attr = el.attribs; const attr = el.attribs;
if (attr.property === 'og:title') { if (attr.property === 'og:title') {
let data = uniteApiRegex.ogtitle.exec(attr.content); const data = uniteApiRegex.ogtitle.exec(attr.content);
if (data !== null && data.length >= 3) { if (data !== null && data.length >= 3) {
foundData.name = data[1]; foundData.name = data[1];
foundData.id = data[2]; foundData.id = data[2];
@@ -107,17 +108,17 @@ function readHTML(html: string): uniteApiData {
} else if (attr.property === 'og:description') { } else if (attr.property === 'og:description') {
//all lines //all lines
let lines = attr.content.split('\n').map(l => l.trim()), let lines = attr.content.split('\n').map(l => l.trim());
extraLines: string[] = []; const extraLines: string[] = [];
//ensure first line is correct //ensure first line is correct
while (lines.length && !/pok.mon unite/i.test(lines[0])) { while ((lines.length > 0) && !/pok.mon unite/i.test(lines[0])) {
let line = lines.shift(); const line = lines.shift();
if (line) if (line !== undefined)
extraLines.push(line); extraLines.push(line);
} }
if (!lines.length) if (lines.length === 0)
throw emsg('Unable to read data, please try again'); throw emsg('Unable to read data, please try again');
//bring the first lines removed back into the data //bring the first lines removed back into the data
@@ -129,13 +130,12 @@ function readHTML(html: string): uniteApiData {
//first line //first line
{ {
//will be only text after "pokemon unite:" //will be only text after "pokemon unite:"
let line = lines[0].split(':').slice(1).join(':').trim(); const line = lines[0].split(':').slice(1).join(':').trim(),
regex = uniteApiRegex.ogdescription;
let regex = uniteApiRegex.ogdescription;
if (regex[0].test(line)) { //is master/has elo if (regex[0].test(line)) { //is master/has elo
let regexData = line.match(regex[0]); const regexData = line.match(regex[0]);
if (!regexData || regexData.length < 4) if (!regexData || regexData.length < 4)
throw emsg('Unable to read data, please try again'); throw emsg('Unable to read data, please try again');
@@ -146,7 +146,7 @@ function readHTML(html: string): uniteApiData {
} else { //is not master/has a class } else { //is not master/has a class
let regexData = line.match(regex[1]); const regexData = line.match(regex[1]);
if (!regexData || regexData.length < 4) if (!regexData || regexData.length < 4)
throw emsg('Unable to read data, please try again'); throw emsg('Unable to read data, please try again');
@@ -164,7 +164,7 @@ function readHTML(html: string): uniteApiData {
//rest of lines //rest of lines
lines.forEach(line => { lines.forEach(line => {
let split = line.split(':').map(l => l.trim()), const split = line.split(':').map(l => l.trim()),
key = split[0].toLowerCase().replace(/[^\w]/g, ''), key = split[0].toLowerCase().replace(/[^\w]/g, ''),
value = split[1]; value = split[1];
@@ -186,7 +186,9 @@ function readHTML(html: string): uniteApiData {
}); });
foundData.avatar = $('.player-card-image img').attr('src') || ""; const imgSrc = $('.player-card-image img').attr('src');
foundData.avatar = imgSrc !== undefined ? imgSrc : '';
foundData.avatar = foundData.avatar.replace('../', 'https://uniteapi.dev/'); foundData.avatar = foundData.avatar.replace('../', 'https://uniteapi.dev/');
return foundData; return foundData;
@@ -199,7 +201,7 @@ function readHTML(html: string): uniteApiData {
* @returns boolean, valid or invalid * @returns boolean, valid or invalid
*/ */
function verifyData(data: uniteApiData): boolean { function verifyData(data: uniteApiData): boolean {
if (data.id.length) if (data.id.length > 0)
return true; return true;
return false; return false;
} }
@@ -210,8 +212,8 @@ function verifyData(data: uniteApiData): boolean {
* @returns player data * @returns player data
*/ */
export async function getPlayer(name: string): Promise<uniteApiData|null> { export async function getPlayer(name: string): Promise<uniteApiData|null> {
let html = await getHTML(name); const html = await getHTML(name),
let data = readHTML(html); data = readHTML(html);
if (verifyData(data)) if (verifyData(data))
return data; return data;
return null; return null;
@@ -219,13 +221,19 @@ export async function getPlayer(name: string): Promise<uniteApiData|null> {
async function sendPlayerEmbed(interaction: CommandInteraction, data: uniteApiData) { async function sendPlayerEmbed(interaction: CommandInteraction, data: uniteApiData) {
let embed = new MessageEmbed() let eloStr: string;
if (data.elo !== null)
eloStr = `(${data.elo})`;
else
eloStr = `Class ${data.class}`;
const embed = new MessageEmbed()
.setTitle(`${data.name} (${data.id})`) .setTitle(`${data.name} (${data.id})`)
.setURL(`https://uniteapi.dev/p/${encodeURIComponent(data.name)}`) .setURL(`https://uniteapi.dev/p/${encodeURIComponent(data.name)}`)
.setTimestamp() .setTimestamp()
.setThumbnail(data.avatar) .setThumbnail(data.avatar)
.setDescription(`Level ${data.level} .setDescription(`Level ${data.level}
${data.rank} ${data.elo ? `(${data.elo})` : `Class ${data.class}`} ${data.rank} ${eloStr}
**Battles** ${data.battles} **Battles** ${data.battles}
**Wins** ${data.wins} **Wins** ${data.wins}
@@ -240,10 +248,10 @@ ${data.rank} ${data.elo ? `(${data.elo})` : `Class ${data.class}`}
* @throws errorMessage class if the user cannot be found * @throws errorMessage class if the user cannot be found
*/ */
export async function getPlayerInteraction(interaction: CommandInteraction) { export async function getPlayerInteraction(interaction: CommandInteraction) {
let username = interaction.options.getString('username', true); const username = interaction.options.getString('username', true);
await interaction.deferReply(); await interaction.deferReply();
let data = await getPlayer(username); const data = await getPlayer(username);
if (data === null) if (data === null)
throw emsg('api.noUser'); throw emsg('api.noUser');
else else

View File

@@ -1,3 +1,4 @@
/* eslint-disable camelcase */
import { REST } from '@discordjs/rest'; import { REST } from '@discordjs/rest';
import { Routes } from 'discord-api-types/v9'; import { Routes } from 'discord-api-types/v9';
@@ -45,8 +46,8 @@ const commands = [
} }
] ]
} }
]/*, ];
commandNames = commands.map(c => c.name);*/ /*commandNames = commands.map(c => c.name);*/
/** /**
* register/reload commands on guild(s) * register/reload commands on guild(s)

View File

@@ -3,7 +3,7 @@ import { Client, Intents } from 'discord.js';
import * as fs from 'fs'; import * as fs from 'fs';
import { getPlayerInteraction } from './api'; import { getPlayerInteraction } from './api';
import { registerCommands } from './discord'; import { registerCommands } from './discord';
import { Lang } from './lang'; import * as Lang from './lang';
import { discordInit, QueueCommands } from './queue'; import { discordInit, QueueCommands } from './queue';
import { errorMessage } from './util'; import { errorMessage } from './util';
const CLIENT = new Client({ intents: [Intents.FLAGS.GUILDS] }); const CLIENT = new Client({ intents: [Intents.FLAGS.GUILDS] });

View File

@@ -26,7 +26,7 @@ const LANG: LangObjWhole = {
discord: { discord: {
noQueue: 'There is not an active queue in this channel, type `/open` to create one', noQueue: 'There is not an active queue in this channel, type `/open` to create one',
noChannel: 'Unable to find channel {channelId} for teams of {teamsize}', noChannel: 'Unable to find channel {channelId} for teams of {teamsize}',
noCreate: 'There is already an active queue in this channel for teams of ${teamsize}', noCreate: 'There is already an active queue in this channel for teams of {teamsize}',
inQueue: 'You are already in the queue', inQueue: 'You are already in the queue',
notInQueue: 'You aren\'t in the queue', notInQueue: 'You aren\'t in the queue',
notMod: 'Member is not a moderator' notMod: 'Member is not a moderator'
@@ -45,11 +45,10 @@ const LANG: LangObjWhole = {
} }
} };
export namespace Lang {
var LANGID = 'en'; let LANGID = 'en';
if (!(LANGID in LANG)) if (!(LANGID in LANG))
throw 'language id does not exist'; throw 'language id does not exist';
@@ -64,7 +63,7 @@ export namespace Lang {
return str.replace(/{\w+}/g, str => { return str.replace(/{\w+}/g, str => {
let key = str.substring(1, str.length-1); const key = str.substring(1, str.length-1);
if (key in args) if (key in args)
return args[key]; return args[key];
@@ -82,15 +81,15 @@ export namespace Lang {
*/ */
export function get(id: string, args: {[keys: string]: string} = {}): string {//discord.error.noActiveQueue export function get(id: string, args: {[keys: string]: string} = {}): string {//discord.error.noActiveQueue
let keySpl = id.split('.').map(k => k.trim()).filter(k => k); const keySpl = id.split('.').map(k => k.trim()).filter(k => k);
let finding = LANG[LANGID]; let finding = LANG[LANGID];
for (let key of keySpl) { for (const key of keySpl) {
if (key in finding) { if (key in finding) {
let found = finding[key]; const found = finding[key];
if (typeof found === 'string') if (typeof found === 'string')
return template(found, args); return template(found, args);
@@ -104,5 +103,3 @@ export namespace Lang {
return id; return id;
} }
}

View File

@@ -3,10 +3,10 @@
join message should contain your current position in the queue, editing it to keep it current join message should contain your current position in the queue, editing it to keep it current
*/ */
import { Client, CommandInteraction, MessageEmbed, TextChannel } from "discord.js"; import { Client, CommandInteraction, MessageEmbed, TextChannel } from 'discord.js';
import * as fs from 'fs'; import * as fs from 'fs';
import { emsg, getChannel, getMember, memberIsModThrow, queueInfo, queueInfoBase } from "./util"; import { emsg, getChannel, getMember, memberIsModThrow, queueInfo, queueInfoBase } from './util';
import { Lang } from './lang'; import * as Lang from './lang';
//load queues from file //load queues from file
if (!fs.existsSync('./queues.json')) if (!fs.existsSync('./queues.json'))
@@ -17,22 +17,24 @@ const _QUEUE = fs.readFileSync('./queues.json').toString(),
try { try {
let queueJson = JSON.parse(_QUEUE); const queueJson = JSON.parse(_QUEUE);
for (let channelId in queueJson) { for (const channelId in queueJson) {
let {teamsize} = queueJson[channelId] as queueInfoBase; const {teamsize} = queueJson[channelId] as queueInfoBase;
if (teamsize) if (teamsize !== 0)
QUEUE.set(channelId, { teamsize, players: [] }) QUEUE.set(channelId, { teamsize, players: [] });
} }
} catch(e) {} } catch(e) {
//do nothing
}
function SaveQueue() { function SaveQueue() {
let queueJson = Object.fromEntries(QUEUE), const queueJson = Object.fromEntries(QUEUE),
queueFileJson: {[keys: string]: queueInfoBase} = {}; queueFileJson: {[keys: string]: queueInfoBase} = {};
for (let channelId of QUEUE.keys()) for (const channelId of QUEUE.keys())
queueFileJson[channelId] = { teamsize: queueJson[channelId].teamsize }; queueFileJson[channelId] = { teamsize: queueJson[channelId].teamsize };
fs.writeFileSync('./queues.json', JSON.stringify(queueFileJson, null, 2)); fs.writeFileSync('./queues.json', JSON.stringify(queueFileJson, null, 2));
@@ -40,17 +42,17 @@ function SaveQueue() {
} }
async function checkQueue(channel: TextChannel) { async function checkQueue(channel: TextChannel) {
let info = QUEUE.get(channel.id); const info = QUEUE.get(channel.id);
if (!info) if (!info)
return; return;
if (info.players.length >= info.teamsize) { if (info.players.length >= info.teamsize) {
let team = info.players.splice(0, info.teamsize).map(m => m.toString()); const team = info.players.splice(0, info.teamsize).map(m => m.toString());
//TODO add embeds to lang.ts //TODO add embeds to lang.ts
let embed = new MessageEmbed() const embed = new MessageEmbed()
.setTitle('Team') .setTitle('Team')
.setDescription(team.join('\n')); .setDescription(team.join('\n'));
@@ -59,35 +61,33 @@ async function checkQueue(channel: TextChannel) {
} }
} }
namespace Queue {
export function create(channelId: string, teamsize: number) {
export function queueCreate(channelId: string, teamsize: number) {
if (!QUEUE.has(channelId)) { if (!QUEUE.has(channelId)) {
QUEUE.set(channelId, {teamsize, players: []}); QUEUE.set(channelId, {teamsize, players: []});
SaveQueue(); SaveQueue();
} }
} }
export function remove(channelId: string) { export function queueRemove(channelId: string) {
if (QUEUE.has(channelId)) { if (QUEUE.has(channelId)) {
QUEUE.delete(channelId); QUEUE.delete(channelId);
SaveQueue(); SaveQueue();
} }
} }
}
SaveQueue(); SaveQueue();
export async function discordInit(client: Client) { export async function discordInit(client: Client) {
for (let channelId of QUEUE.keys()) { for (const channelId of QUEUE.keys()) {
let info = QUEUE.get(channelId), const info = QUEUE.get(channelId),
channel = await client.channels.fetch(channelId); channel = await client.channels.fetch(channelId);
if (!info) { //no idea what could cause this but TS complains if (!info) { //no idea what could cause this but TS complains
Queue.remove(channelId); queueRemove(channelId);
continue; continue;
} }
@@ -96,7 +96,7 @@ export async function discordInit(client: Client) {
channelId, channelId,
teamsize: info.teamsize teamsize: info.teamsize
}); });
Queue.remove(channelId); queueRemove(channelId);
continue; continue;
} }
@@ -105,7 +105,6 @@ export async function discordInit(client: Client) {
} }
} }
export namespace QueueCommands {
/** /**
* get the queueInfo of an interaction * get the queueInfo of an interaction
@@ -114,7 +113,7 @@ export namespace QueueCommands {
* @returns queue info * @returns queue info
*/ */
function getInfo(interaction: CommandInteraction): queueInfo { function getInfo(interaction: CommandInteraction): queueInfo {
let info = QUEUE.get(interaction.channelId); const info = QUEUE.get(interaction.channelId);
if (!info) if (!info)
throw emsg('discord.noQueue'); throw emsg('discord.noQueue');
@@ -141,7 +140,7 @@ export namespace QueueCommands {
*/ */
export function queueContains(interaction: CommandInteraction): boolean { export function queueContains(interaction: CommandInteraction): boolean {
let {member, info} = getAll(interaction); const {member, info} = getAll(interaction);
if (info.players.map(m=>m.id).includes(member.id)) if (info.players.map(m=>m.id).includes(member.id))
return true; return true;
@@ -151,38 +150,28 @@ export namespace QueueCommands {
} }
/** /**
* creates a queue from an interaction * opens a queue
* @param interaction * @param interaction
* @throws errorMessage class if it cannot be left * @throws errorMessage class if it cannot be left
*/ */
export function queueCreate(interaction: CommandInteraction) { function open(interaction: CommandInteraction) {
memberIsModThrow(interaction); memberIsModThrow(interaction);
let {channelId} = interaction, const {channelId} = interaction,
teamsize = interaction.options.getInteger('teamsize', true); teamsize = interaction.options.getInteger('teamsize', true);
let existing = QUEUE.get(channelId) const existing = QUEUE.get(channelId);
if (existing) if (existing)
throw emsg(Lang.get('error.discord.noCreate', { throw emsg(Lang.get('error.discord.noCreate', {
teamsize: existing.teamsize.toString() teamsize: existing.teamsize.toString()
})); }));
Queue.create(channelId, teamsize); queueCreate(channelId, teamsize);
interaction.reply(Lang.get('discord.create', { interaction.reply(Lang.get('discord.create', {
teamsize: teamsize.toString() teamsize: teamsize.toString()
})) }));
}
/**
* opens a queue
* @param interaction
* @throws errorMessage class if it cannot be left
*/
export async function open(interaction: CommandInteraction) {
queueCreate(interaction);
} }
@@ -191,7 +180,7 @@ export namespace QueueCommands {
* @param interaction * @param interaction
* @throws errorMessage class if it cannot be joined * @throws errorMessage class if it cannot be joined
*/ */
export async function close(interaction: CommandInteraction) { async function close(interaction: CommandInteraction) {
memberIsModThrow(interaction); memberIsModThrow(interaction);
QUEUE.delete(interaction.channelId); QUEUE.delete(interaction.channelId);
@@ -205,11 +194,11 @@ export namespace QueueCommands {
* @param interaction * @param interaction
* @throws errorMessage class if it cannot be left * @throws errorMessage class if it cannot be left
*/ */
export async function queue(interaction: CommandInteraction) { async function queue(interaction: CommandInteraction) {
let info = getInfo(interaction); const info = getInfo(interaction);
let embed = new MessageEmbed() const embed = new MessageEmbed()
.setTitle('Active Queue') .setTitle('Active Queue')
.addField('Team Size', info.teamsize.toString(), true) .addField('Team Size', info.teamsize.toString(), true)
.addField('Players Joined', info.players.length.toString(), true) .addField('Players Joined', info.players.length.toString(), true)
@@ -224,9 +213,9 @@ export namespace QueueCommands {
* @param interaction * @param interaction
* @throws errorMessage class if it cannot be readied * @throws errorMessage class if it cannot be readied
*/ */
export async function join(interaction: CommandInteraction) { async function join(interaction: CommandInteraction) {
let {member, info, channel} = getAll(interaction); const {member, info, channel} = getAll(interaction);
if (queueContains(interaction)) if (queueContains(interaction))
throw emsg('discord.inQueue'); throw emsg('discord.inQueue');
@@ -246,9 +235,9 @@ export namespace QueueCommands {
* @param interaction * @param interaction
* @throws errorMessage class if it cannot be reset * @throws errorMessage class if it cannot be reset
*/ */
export async function leave(interaction: CommandInteraction) { async function leave(interaction: CommandInteraction) {
let {member, info} = getAll(interaction); const {member, info} = getAll(interaction);
if (!queueContains(interaction)) if (!queueContains(interaction))
throw emsg('discord.notInQueue'); throw emsg('discord.notInQueue');
@@ -261,4 +250,10 @@ export namespace QueueCommands {
} }
} export const QueueCommands = {
open,
close,
queue,
join,
leave
};

0
src/queueCommands.ts Normal file
View File

View File

@@ -1,5 +1,5 @@
import { CommandInteraction, GuildMember, TextChannel } from "discord.js"; import { CommandInteraction, GuildMember, TextChannel } from 'discord.js';
import { Lang } from "./lang"; import * as Lang from './lang';
/** /**
* shuffles an array * shuffles an array
@@ -7,6 +7,7 @@ import { Lang } from "./lang";
* @param array an array * @param array an array
* @returns an array but shuffled * @returns an array but shuffled
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function shuffle(array: any[]) { export function shuffle(array: any[]) {
let currentIndex = array.length, randomIndex; let currentIndex = array.length, randomIndex;
@@ -31,7 +32,7 @@ export class errorMessage {
public msg: string; public msg: string;
public ephemeral: boolean; public ephemeral: boolean;
constructor(msg: string, ephemeral: boolean = true) { constructor(msg: string, ephemeral = true) {
this.msg = msg; this.msg = msg;
this.ephemeral = ephemeral; this.ephemeral = ephemeral;
} }
@@ -43,7 +44,7 @@ export class errorMessage {
* @param ephemeral (default=true) * @param ephemeral (default=true)
* @returns new errorMessage * @returns new errorMessage
*/ */
export const emsg = (msg: string, ephemeral: boolean = true) => new errorMessage(Lang.get(`error.${msg}`), ephemeral); export const emsg = (msg: string, ephemeral = true) => new errorMessage(Lang.get(`error.${msg}`), ephemeral);
@@ -61,7 +62,7 @@ export interface queueInfo extends queueInfoBase{
* @returns member * @returns member
*/ */
export function getMember(interaction: CommandInteraction): GuildMember { export function getMember(interaction: CommandInteraction): GuildMember {
let member = interaction.member; const member = interaction.member;
if (!(member instanceof GuildMember)) if (!(member instanceof GuildMember))
throw emsg('general.noMember'); throw emsg('general.noMember');
@@ -76,7 +77,7 @@ export function getMember(interaction: CommandInteraction): GuildMember {
* @returns member * @returns member
*/ */
export function getChannel(interaction: CommandInteraction): TextChannel { export function getChannel(interaction: CommandInteraction): TextChannel {
let channel = interaction.channel; const channel = interaction.channel;
if (!(channel instanceof TextChannel)) if (!(channel instanceof TextChannel))
throw emsg('general.noChannel'); throw emsg('general.noChannel');
@@ -86,7 +87,7 @@ export function getChannel(interaction: CommandInteraction): TextChannel {
export function memberIsMod(interaction: CommandInteraction): boolean { export function memberIsMod(interaction: CommandInteraction): boolean {
let member = getMember(interaction); const member = getMember(interaction);
return member.permissionsIn(interaction.channelId).has('MANAGE_MESSAGES'); return member.permissionsIn(interaction.channelId).has('MANAGE_MESSAGES');
} }