diff --git a/.eslintrc.json b/.eslintrc.json index a105527..3bbd18f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,7 @@ "plugin:@typescript-eslint/recommended" ], "parser": "@typescript-eslint/parser", - "parserOptions": { "project": ["./tsconfig.json"] }, + "parserOptions": { "project": ["./tsconfig.eslint.json"] }, "plugins": [ "@typescript-eslint" ], diff --git a/dist/api.js b/dist/api.js index b2879f4..c27d834 100644 --- a/dist/api.js +++ b/dist/api.js @@ -26,7 +26,7 @@ exports.getPlayerInteraction = exports.getPlayer = void 0; /*eslint prefer-const: "error"*/ const cheerio = __importStar(require("cheerio")); const https_1 = __importDefault(require("https")); -const util_1 = require("./util"); +const main_1 = require("./util/main"); const Lang = __importStar(require("./lang")); const uniteApiRegex = { //$1 = name, $2 = id @@ -109,7 +109,7 @@ function readHTML(html) { extraLines.push(line); } if (lines.length === 0) - throw (0, util_1.emsg)('Unable to read data, please try again'); + throw (0, main_1.emsg)('Unable to read data, please try again'); //bring the first lines removed back into the data lines = [ ...lines, @@ -122,7 +122,7 @@ function readHTML(html) { if (regex[0].test(line)) { //is master/has elo const regexData = line.match(regex[0]); if (!regexData || regexData.length < 4) - throw (0, util_1.emsg)('Unable to read data, please try again'); + throw (0, main_1.emsg)('Unable to read data, please try again'); foundData.level = regexData[1]; foundData.rank = regexData[2]; foundData.elo = regexData[3]; @@ -130,7 +130,7 @@ function readHTML(html) { else { //is not master/has a class const regexData = line.match(regex[1]); if (!regexData || regexData.length < 4) - throw (0, util_1.emsg)('Unable to read data, please try again'); + throw (0, main_1.emsg)('Unable to read data, please try again'); foundData.level = regexData[1]; foundData.rank = regexData[2]; foundData.class = regexData[3]; @@ -210,7 +210,7 @@ async function getPlayerInteraction(interaction) { await interaction.deferReply(); const data = await getPlayer(username); if (data === null) - throw (0, util_1.emsg)('api.noUser'); + throw (0, main_1.emsg)('api.noUser'); else sendPlayerEmbed(interaction, data); } diff --git a/dist/index.js b/dist/index.js index 21d8c53..65dbe3c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -25,7 +25,7 @@ const api_1 = require("./api"); const discord_1 = require("./discord"); const Lang = __importStar(require("./lang")); const queue_1 = require("./queue"); -const util_1 = require("./util"); +const main_1 = require("./util/main"); const CLIENT = new discord_js_1.Client({ intents: [discord_js_1.Intents.FLAGS.GUILDS] }); //init logs with a timestamp console.log(new Date().toISOString() + '\n\n'); @@ -76,7 +76,7 @@ CLIENT.on('interactionCreate', async (interaction) => { } } catch (e) { - if (e instanceof util_1.errorMessage) { + if (e instanceof main_1.errorMessage) { if (interaction.deferred || interaction.replied) interaction.editReply(e.msg); else diff --git a/dist/lang.js b/dist/lang.js index b0b9594..6dca50a 100644 --- a/dist/lang.js +++ b/dist/lang.js @@ -1,100 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getEmbed = exports.get = exports.setLang = void 0; -const discord_js_1 = require("discord.js"); -/* UTIL FUNCS */ -function template(str, args) { - return str.replace(/{\w+}/g, str => { - const key = str.substring(1, str.length - 1); - if (key in args) - return args[key]; - return key; - }); -} -function bigString(str) { - if (typeof str === 'object') - return str.join('\n'); - return str; -} -function resolveColor(color) { - color = color.replace(/[^0-9a-f]/gi, ''); - const colorNum = [0, 0, 0]; - if (color.length === 3 || color.length === 6) { - const colorSpl = /([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/.exec(color); - if (!colorSpl) - return colorNum; - for (let i = 0; i < colorSpl.length && i < colorNum.length; i++) - colorNum[i] = parseInt(colorSpl[i], 16); - } - return colorNum; -} -function embedObjStr(embedData, fallback = '') { - if (embedData.content !== undefined) - return bigString(embedData.content); - if (embedData.description !== undefined) - return bigString(embedData.description); - return fallback; -} -function embedObjEmbed(embedObj, args = {}) { - const embed = new discord_js_1.MessageEmbed(), { author, color, description, fields, footer, image, thumbnail, timestamp, title, url } = embedObj; - if (author !== undefined) { - let authorFix; - if (typeof author === 'string') - authorFix = { - name: template(author, args) - }; - else { - const { name, icon, url } = author; - authorFix = { - name: template(name, args) - }; - if (icon !== undefined) - authorFix.icon = template(icon, args); - if (url !== undefined) - authorFix.url = template(url, args); - } - embed.setAuthor(authorFix); - } - if (footer !== undefined) { - let footerFix; - if (typeof footer === 'string') - footerFix = { - text: template(footer, args) - }; - else { - const { text, icon } = footer; - footerFix = { - text: template(text, args) - }; - if (icon !== undefined) - footerFix.icon = template(icon, args); - } - embed.setFooter(footerFix); - } - if (color !== undefined) - embed.setColor(resolveColor(template(color, args))); - if (description !== undefined) - embed.setDescription(template(bigString(description), args)); - if (image !== undefined) - embed.setImage(template(image, args)); - if (thumbnail !== undefined) - embed.setThumbnail(template(thumbnail, args)); - if (title !== undefined) - embed.setTitle(template(title, args)); - if (url !== undefined) - embed.setURL(template(url, args)); - if (timestamp === true) - embed.setTimestamp(); - else if (typeof timestamp === 'string') - embed.setTimestamp(new Date(template(timestamp, args))); - else if (timestamp !== false) - embed.setTimestamp(timestamp); - fields?.forEach(field => { - embed.addField(template(field.name, args), template(bigString(field.value), args), field.inline); - }); - return embed; -} -/* LANG */ +const lang_1 = require("./util/lang"); const LANG = { en: { main: { @@ -190,9 +97,9 @@ function get(id, args = {}) { if (key in finding) { const found = finding[key]; if (typeof found === 'string') - return template(found, args); + return (0, lang_1.template)(found, args); if (found.embed === true) - return embedObjStr(found, id); + return (0, lang_1.embedObjStr)(found, id); finding = found; } else @@ -219,11 +126,11 @@ function getEmbed(id, args = {}, otherOptions = {}) { if (key in finding) { const found = finding[key]; if (typeof found === 'string') { - embedData.content = template(found, args); + embedData.content = (0, lang_1.template)(found, args); break; } if (found.embed === true) { - const embedObj = found, { content } = embedObj, embed = embedObjEmbed(embedObj, args); + const embedObj = found, { content } = embedObj, embed = (0, lang_1.embedObjEmbed)(embedObj, args); embedData.embeds.push(embed); if (content !== undefined) embedData.content = content; diff --git a/dist/queue.js b/dist/queue.js index 7b577ae..0c41e6c 100644 --- a/dist/queue.js +++ b/dist/queue.js @@ -26,9 +26,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.QueueCommands = exports.queueContains = exports.discordInit = exports.queueRemove = exports.queueCreate = void 0; const discord_js_1 = require("discord.js"); const fs = __importStar(require("fs")); -const util_1 = require("./util"); const Lang = __importStar(require("./lang")); -//load queues from file +const discord_1 = require("./util/discord"); +const main_1 = require("./util/main"); +//load queues from file` if (!fs.existsSync('./queues.json')) fs.writeFileSync('./queues.json', '{}'); const _QUEUE = fs.readFileSync('./queues.json').toString(), QUEUE = new Map(); @@ -101,7 +102,7 @@ exports.discordInit = discordInit; function getInfo(interaction) { const info = QUEUE.get(interaction.channelId); if (!info) - throw (0, util_1.emsg)('discord.noQueue'); + throw (0, main_1.emsg)('discord.noQueue'); return info; } /** @@ -111,8 +112,8 @@ function getInfo(interaction) { * @returns object containing each */ const getAll = (interaction) => ({ - member: (0, util_1.getMember)(interaction), - channel: (0, util_1.getChannel)(interaction), + member: (0, discord_1.getMember)(interaction), + channel: (0, discord_1.getChannel)(interaction), info: getInfo(interaction) }); /** @@ -133,11 +134,11 @@ exports.queueContains = queueContains; * @throws errorMessage class if it cannot be left */ function open(interaction) { - (0, util_1.memberIsModThrow)(interaction); + (0, discord_1.memberIsModThrow)(interaction); const { channelId } = interaction, teamsize = interaction.options.getInteger('teamsize', true); const existing = QUEUE.get(channelId); if (existing) - throw (0, util_1.emsg)(Lang.get('error.discord.noCreate', { + throw (0, main_1.emsg)(Lang.get('error.discord.noCreate', { teamsize: existing.teamsize.toString() })); queueCreate(channelId, teamsize); @@ -151,7 +152,7 @@ function open(interaction) { * @throws errorMessage class if it cannot be joined */ async function close(interaction) { - (0, util_1.memberIsModThrow)(interaction); + (0, discord_1.memberIsModThrow)(interaction); QUEUE.delete(interaction.channelId); await interaction.reply(Lang.get('discord.close')); } @@ -177,7 +178,7 @@ async function queue(interaction) { async function join(interaction) { const { member, info, channel } = getAll(interaction); if (queueContains(interaction)) - throw (0, util_1.emsg)('discord.inQueue'); + throw (0, main_1.emsg)('discord.inQueue'); info.players.push(member); QUEUE.set(interaction.channelId, info); await interaction.reply(Lang.get('discord.join')); @@ -191,7 +192,7 @@ async function join(interaction) { async function leave(interaction) { const { member, info } = getAll(interaction); if (!queueContains(interaction)) - throw (0, util_1.emsg)('discord.notInQueue'); + throw (0, main_1.emsg)('discord.notInQueue'); info.players.splice(info.players.indexOf(member), 1); QUEUE.set(interaction.channelId, info); await interaction.reply(Lang.get('discord.leave')); diff --git a/dist/util/discord.js b/dist/util/discord.js new file mode 100644 index 0000000..4ceb2ad --- /dev/null +++ b/dist/util/discord.js @@ -0,0 +1,41 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.memberIsModThrow = exports.memberIsMod = exports.getChannel = exports.getMember = void 0; +const discord_js_1 = require("discord.js"); +const main_1 = require("./main"); +/** + * get the GuildMember of an interaction + * @param interaction + * @throws errorMessage class if it cannot be read + * @returns member + */ +function getMember(interaction) { + const member = interaction.member; + if (!(member instanceof discord_js_1.GuildMember)) + throw (0, main_1.emsg)('general.noMember'); + return member; +} +exports.getMember = getMember; +/** + * get the TextChannel of an interaction + * @param interaction + * @throws errorMessage class if it cannot be read + * @returns member + */ +function getChannel(interaction) { + const channel = interaction.channel; + if (!(channel instanceof discord_js_1.TextChannel)) + throw (0, main_1.emsg)('general.noChannel'); + return channel; +} +exports.getChannel = getChannel; +function memberIsMod(interaction) { + const member = getMember(interaction); + return member.permissionsIn(interaction.channelId).has('MANAGE_MESSAGES'); +} +exports.memberIsMod = memberIsMod; +function memberIsModThrow(interaction) { + if (!memberIsMod(interaction)) + throw (0, main_1.emsg)('discord.notMod'); +} +exports.memberIsModThrow = memberIsModThrow; diff --git a/dist/util/lang.js b/dist/util/lang.js new file mode 100644 index 0000000..5ae0a29 --- /dev/null +++ b/dist/util/lang.js @@ -0,0 +1,100 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.embedObjEmbed = exports.embedObjStr = exports.resolveColor = exports.bigString = exports.template = void 0; +const discord_js_1 = require("discord.js"); +function template(str, args) { + return str.replace(/{\w+}/g, str => { + const key = str.substring(1, str.length - 1); + if (key in args) + return args[key]; + return key; + }); +} +exports.template = template; +function bigString(str) { + if (typeof str === 'object') + return str.join('\n'); + return str; +} +exports.bigString = bigString; +function resolveColor(color) { + color = color.replace(/[^0-9a-f]/gi, ''); + const colorNum = [0, 0, 0]; + if (color.length === 3 || color.length === 6) { + const colorSpl = /([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/.exec(color); + if (!colorSpl) + return colorNum; + for (let i = 0; i < colorSpl.length && i < colorNum.length; i++) + colorNum[i] = parseInt(colorSpl[i], 16); + } + return colorNum; +} +exports.resolveColor = resolveColor; +function embedObjStr(embedData, fallback = '') { + if (embedData.content !== undefined) + return bigString(embedData.content); + if (embedData.description !== undefined) + return bigString(embedData.description); + return fallback; +} +exports.embedObjStr = embedObjStr; +function embedObjEmbed(embedObj, args = {}) { + const embed = new discord_js_1.MessageEmbed(), { author, color, description, fields, footer, image, thumbnail, timestamp, title, url } = embedObj; + if (author !== undefined) { + let authorFix; + if (typeof author === 'string') + authorFix = { + name: template(author, args) + }; + else { + const { name, icon, url } = author; + authorFix = { + name: template(name, args) + }; + if (icon !== undefined) + authorFix.icon = template(icon, args); + if (url !== undefined) + authorFix.url = template(url, args); + } + embed.setAuthor(authorFix); + } + if (footer !== undefined) { + let footerFix; + if (typeof footer === 'string') + footerFix = { + text: template(footer, args) + }; + else { + const { text, icon } = footer; + footerFix = { + text: template(text, args) + }; + if (icon !== undefined) + footerFix.icon = template(icon, args); + } + embed.setFooter(footerFix); + } + if (color !== undefined) + embed.setColor(resolveColor(template(color, args))); + if (description !== undefined) + embed.setDescription(template(bigString(description), args)); + if (image !== undefined) + embed.setImage(template(image, args)); + if (thumbnail !== undefined) + embed.setThumbnail(template(thumbnail, args)); + if (title !== undefined) + embed.setTitle(template(title, args)); + if (url !== undefined) + embed.setURL(template(url, args)); + if (timestamp === true) + embed.setTimestamp(); + else if (typeof timestamp === 'string') + embed.setTimestamp(new Date(template(timestamp, args))); + else if (timestamp !== false) + embed.setTimestamp(timestamp); + fields?.forEach(field => { + embed.addField(template(field.name, args), template(bigString(field.value), args), field.inline); + }); + return embed; +} +exports.embedObjEmbed = embedObjEmbed; diff --git a/dist/util/main.js b/dist/util/main.js new file mode 100644 index 0000000..b37d09a --- /dev/null +++ b/dist/util/main.js @@ -0,0 +1,60 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.emsg = exports.errorMessage = exports.shuffle = void 0; +const Lang = __importStar(require("../lang")); +/** + * shuffles an array + * https://stackoverflow.com/a/2450976/2856416 + * @param array an array + * @returns an array but shuffled + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function shuffle(array) { + let currentIndex = array.length, randomIndex; + // While there remain elements to shuffle... + while (currentIndex != 0) { + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + // And swap it with the current element. + [array[currentIndex], array[randomIndex]] = [ + array[randomIndex], array[currentIndex] + ]; + } + return array; +} +exports.shuffle = shuffle; +class errorMessage { + constructor(msg, ephemeral = true) { + this.msg = msg; + this.ephemeral = ephemeral; + } +} +exports.errorMessage = errorMessage; +/** + * a simple class to contain an error message and related data + * @param msg error message + * @param ephemeral (default=true) + * @returns new errorMessage + */ +const emsg = (msg, ephemeral = true) => new errorMessage(Lang.get(`error.${msg}`), ephemeral); +exports.emsg = emsg; diff --git a/src/api.ts b/src/api.ts index 810ef14..7b9c96a 100644 --- a/src/api.ts +++ b/src/api.ts @@ -3,8 +3,9 @@ import * as cheerio from 'cheerio'; import { CommandInteraction } from 'discord.js'; import { IncomingMessage } from 'http'; import http from 'https'; -import { emsg } from './util'; +import { emsg } from './util/main'; import * as Lang from './lang'; +import { uniteApiData } from './types/api'; const uniteApiRegex = { //$1 = name, $2 = id @@ -16,20 +17,6 @@ const uniteApiRegex = { ] }; -type uniteApiData = { - name: string, - id: string, - avatar: string, - - level: string, - rank: string, - class: string|null, - elo: string|null, - battles: string, - wins: string, - winrate: string -} - /** * gets the html of the uniteApi page for the player * @param name name of player diff --git a/src/index.ts b/src/index.ts index cf2230d..d391da0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { getPlayerInteraction } from './api'; import { registerCommands } from './discord'; import * as Lang from './lang'; import { discordInit, QueueCommands } from './queue'; -import { errorMessage } from './util'; +import { errorMessage } from './util/main'; const CLIENT = new Client({ intents: [Intents.FLAGS.GUILDS] }); //init logs with a timestamp diff --git a/src/lang.ts b/src/lang.ts index bb1b5ac..249b47b 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -1,199 +1,4 @@ -import { MessageEmbed } from 'discord.js'; - -/* TYPES */ - -//this is a generic type, and needs 'any' -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type basicObject = {[keys: string]: any}; -type basicObjectStr = {[keys: string]: string}; - -type URL = string; - -type bigString = string | string[]; -interface embedData { - content?: string, - embeds: MessageEmbed[] -} -interface embedField { - name: string, - value: bigString, - inline?: boolean -} - -interface authorData { - name: string, - url?: string, - icon?: string -} -interface footerData { - text: string, - icon?: string -} -interface embedObj { - embed: true, - - content?: string, - title?: string, - description?: bigString, - url?: URL, - color?: string, - footer?: string | footerData, - thumbnail?: URL, - image?: URL, - author?: string | authorData, - fields?: embedField[], - timestamp?: boolean | string | number -} - -type LangObj = { [keys:string]: LangObj | embedObj | string } -type LangObjWhole = { [langid:string]: LangObj } - -/* UTIL FUNCS */ - -function template(str: string, args: basicObject): string { - - return str.replace(/{\w+}/g, str => { - - const key = str.substring(1, str.length-1); - - if (key in args) - return args[key]; - - return key; - - }); - -} - -function bigString(str: bigString): string { - if (typeof str === 'object') - return str.join('\n'); - return str; -} - -function resolveColor(color: string): [number, number, number] { - color = color.replace(/[^0-9a-f]/gi, ''); - - const colorNum: [number, number, number] = [0, 0, 0]; - - if (color.length === 3 || color.length === 6) { - - const colorSpl = /([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/.exec(color); - - if (!colorSpl) - return colorNum; - - for (let i = 0; i < colorSpl.length && i < colorNum.length; i++) - colorNum[i] = parseInt(colorSpl[i], 16); - - } - - return colorNum; -} - -function embedObjStr(embedData: embedObj, fallback = ''): string { - - if (embedData.content !== undefined) - return bigString(embedData.content); - - if (embedData.description !== undefined) - return bigString(embedData.description); - - return fallback; -} - -function embedObjEmbed(embedObj: embedObj, args: basicObjectStr = {}): MessageEmbed { - const embed = new MessageEmbed(), - { author, color, description, fields, footer, image, thumbnail, timestamp, title, url } = embedObj; - - if (author !== undefined) { - - let authorFix: authorData; - - if (typeof author === 'string') - authorFix = { - name: template(author, args) - }; - else { - - const {name, icon, url} = author; - - authorFix = { - name: template(name, args) - }; - - if (icon !== undefined) - authorFix.icon = template(icon, args); - - if (url !== undefined) - authorFix.url = template(url, args); - - } - - embed.setAuthor(authorFix); - - } - - if (footer !== undefined) { - - let footerFix: footerData; - - if (typeof footer === 'string') - footerFix = { - text: template(footer, args) - }; - else { - - const {text, icon} = footer; - - footerFix = { - text: template(text, args) - }; - - if (icon !== undefined) - footerFix.icon = template(icon, args); - - } - - embed.setFooter(footerFix); - - } - - if (color !== undefined) - embed.setColor(resolveColor(template(color, args))); - - if (description !== undefined) - embed.setDescription(template(bigString(description), args)); - - if (image !== undefined) - embed.setImage(template(image, args)); - - if (thumbnail !== undefined) - embed.setThumbnail(template(thumbnail, args)); - - if (title !== undefined) - embed.setTitle(template(title, args)); - - if (url !== undefined) - embed.setURL(template(url, args)); - - if (timestamp === true) - embed.setTimestamp(); - else if (typeof timestamp === 'string') - embed.setTimestamp(new Date(template(timestamp, args))); - else if (timestamp !== false) - embed.setTimestamp(timestamp); - - fields?.forEach(field => { - embed.addField(template(field.name, args), template(bigString(field.value), args), field.inline); - }); - - return embed; -} - -/* LANG */ - - +import { embedObjEmbed, embedObjStr, template } from './util/lang'; const LANG: LangObjWhole = { diff --git a/src/queue.ts b/src/queue.ts index d001010..98de286 100644 --- a/src/queue.ts +++ b/src/queue.ts @@ -5,10 +5,12 @@ join message should contain your current position in the queue, editing it to ke import { Client, CommandInteraction, TextChannel } from 'discord.js'; import * as fs from 'fs'; -import { emsg, getChannel, getMember, memberIsModThrow, queueInfo, queueInfoBase } from './util'; import * as Lang from './lang'; +import { queueInfo, queueInfoBase } from './types/queue'; +import { getChannel, getMember, memberIsModThrow } from './util/discord'; +import { emsg } from './util/main'; -//load queues from file +//load queues from file` if (!fs.existsSync('./queues.json')) fs.writeFileSync('./queues.json', '{}'); diff --git a/src/types/api.d.ts b/src/types/api.d.ts new file mode 100644 index 0000000..1fbdf3a --- /dev/null +++ b/src/types/api.d.ts @@ -0,0 +1,13 @@ +export interface uniteApiData { + name: string, + id: string, + avatar: string, + + level: string, + rank: string, + class: string|null, + elo: string|null, + battles: string, + wins: string, + winrate: string +} \ No newline at end of file diff --git a/src/types/lang.d.ts b/src/types/lang.d.ts new file mode 100644 index 0000000..9f3978e --- /dev/null +++ b/src/types/lang.d.ts @@ -0,0 +1,55 @@ +//this is a generic type, and needs 'any' +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type basicObject = {[keys: string]: any}; +type basicObjectStr = {[keys: string]: string}; + +type bigString = string | string[]; +interface embedData { + content?: string, + embeds: MessageEmbed[] +} +interface embedField { + name: string, + value: bigString, + inline?: boolean +} + +interface authorData { + name: string, + url?: string, + icon?: string +} +interface footerData { + text: string, + icon?: string +} +interface embedObj { + embed: true, + + content?: string, + title?: string, + description?: bigString, + /** + * URL + */ + url?: string, + /** + * #FFFFFF + */ + color?: string, + footer?: string | footerData, + thumbnail?: string, + /** + * URL + */ + image?: string, + /** + * URL + */ + author?: string | authorData, + fields?: embedField[], + timestamp?: boolean | string | number +} + +type LangObj = { [keys:string]: LangObj | embedObj | string } +type LangObjWhole = { [langid:string]: LangObj } \ No newline at end of file diff --git a/src/types/queue.d.ts b/src/types/queue.d.ts new file mode 100644 index 0000000..f3c63e0 --- /dev/null +++ b/src/types/queue.d.ts @@ -0,0 +1,8 @@ +import { GuildMember } from 'discord.js'; + +export interface queueInfoBase { + teamsize: number +} +export interface queueInfo extends queueInfoBase { + players: GuildMember[] +} \ No newline at end of file diff --git a/src/util.ts b/src/util.ts deleted file mode 100644 index a41f5fe..0000000 --- a/src/util.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { CommandInteraction, GuildMember, TextChannel } from 'discord.js'; -import * as Lang from './lang'; - -/** - * shuffles an array - * https://stackoverflow.com/a/2450976/2856416 - * @param array an array - * @returns an array but shuffled - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function shuffle(array: any[]) { - let currentIndex = array.length, randomIndex; - - // While there remain elements to shuffle... - while (currentIndex != 0) { - - // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex--; - - // And swap it with the current element. - [array[currentIndex], array[randomIndex]] = [ - array[randomIndex], array[currentIndex]]; - } - - return array; -} - - - -export class errorMessage { - public msg: string; - public ephemeral: boolean; - - constructor(msg: string, ephemeral = true) { - this.msg = msg; - this.ephemeral = ephemeral; - } -} - -/** - * a simple class to contain an error message and related data - * @param msg error message - * @param ephemeral (default=true) - * @returns new errorMessage - */ -export const emsg = (msg: string, ephemeral = true) => new errorMessage(Lang.get(`error.${msg}`), ephemeral); - - - -export interface queueInfoBase { - teamsize: number -} -export interface queueInfo extends queueInfoBase{ - players: GuildMember[] -} - -/** - * get the GuildMember of an interaction - * @param interaction - * @throws errorMessage class if it cannot be read - * @returns member - */ -export function getMember(interaction: CommandInteraction): GuildMember { - const member = interaction.member; - - if (!(member instanceof GuildMember)) - throw emsg('general.noMember'); - - return member; -} - -/** - * get the TextChannel of an interaction - * @param interaction - * @throws errorMessage class if it cannot be read - * @returns member - */ -export function getChannel(interaction: CommandInteraction): TextChannel { - const channel = interaction.channel; - - if (!(channel instanceof TextChannel)) - throw emsg('general.noChannel'); - - return channel; -} - - -export function memberIsMod(interaction: CommandInteraction): boolean { - const member = getMember(interaction); - return member.permissionsIn(interaction.channelId).has('MANAGE_MESSAGES'); -} - -export function memberIsModThrow(interaction: CommandInteraction) { - if (!memberIsMod(interaction)) - throw emsg('discord.notMod'); -} diff --git a/src/util/discord.ts b/src/util/discord.ts new file mode 100644 index 0000000..2379604 --- /dev/null +++ b/src/util/discord.ts @@ -0,0 +1,43 @@ +import { CommandInteraction, GuildMember, TextChannel } from 'discord.js'; +import { emsg } from './main'; + +/** + * get the GuildMember of an interaction + * @param interaction + * @throws errorMessage class if it cannot be read + * @returns member + */ +export function getMember(interaction: CommandInteraction): GuildMember { + const member = interaction.member; + + if (!(member instanceof GuildMember)) + throw emsg('general.noMember'); + + return member; +} + +/** + * get the TextChannel of an interaction + * @param interaction + * @throws errorMessage class if it cannot be read + * @returns member + */ +export function getChannel(interaction: CommandInteraction): TextChannel { + const channel = interaction.channel; + + if (!(channel instanceof TextChannel)) + throw emsg('general.noChannel'); + + return channel; +} + + +export function memberIsMod(interaction: CommandInteraction): boolean { + const member = getMember(interaction); + return member.permissionsIn(interaction.channelId).has('MANAGE_MESSAGES'); +} + +export function memberIsModThrow(interaction: CommandInteraction) { + if (!memberIsMod(interaction)) + throw emsg('discord.notMod'); +} \ No newline at end of file diff --git a/src/util/lang.ts b/src/util/lang.ts new file mode 100644 index 0000000..c09dc3b --- /dev/null +++ b/src/util/lang.ts @@ -0,0 +1,142 @@ +import { MessageEmbed } from 'discord.js'; + +export function template(str: string, args: basicObject): string { + + return str.replace(/{\w+}/g, str => { + + const key = str.substring(1, str.length-1); + + if (key in args) + return args[key]; + + return key; + + }); + +} + +export function bigString(str: bigString): string { + if (typeof str === 'object') + return str.join('\n'); + return str; +} + +export function resolveColor(color: string): [number, number, number] { + color = color.replace(/[^0-9a-f]/gi, ''); + + const colorNum: [number, number, number] = [0, 0, 0]; + + if (color.length === 3 || color.length === 6) { + + const colorSpl = /([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/.exec(color); + + if (!colorSpl) + return colorNum; + + for (let i = 0; i < colorSpl.length && i < colorNum.length; i++) + colorNum[i] = parseInt(colorSpl[i], 16); + + } + + return colorNum; +} + +export function embedObjStr(embedData: embedObj, fallback = ''): string { + + if (embedData.content !== undefined) + return bigString(embedData.content); + + if (embedData.description !== undefined) + return bigString(embedData.description); + + return fallback; +} + +export function embedObjEmbed(embedObj: embedObj, args: basicObjectStr = {}): MessageEmbed { + const embed = new MessageEmbed(), + { author, color, description, fields, footer, image, thumbnail, timestamp, title, url } = embedObj; + + if (author !== undefined) { + + let authorFix: authorData; + + if (typeof author === 'string') + authorFix = { + name: template(author, args) + }; + else { + + const {name, icon, url} = author; + + authorFix = { + name: template(name, args) + }; + + if (icon !== undefined) + authorFix.icon = template(icon, args); + + if (url !== undefined) + authorFix.url = template(url, args); + + } + + embed.setAuthor(authorFix); + + } + + if (footer !== undefined) { + + let footerFix: footerData; + + if (typeof footer === 'string') + footerFix = { + text: template(footer, args) + }; + else { + + const {text, icon} = footer; + + footerFix = { + text: template(text, args) + }; + + if (icon !== undefined) + footerFix.icon = template(icon, args); + + } + + embed.setFooter(footerFix); + + } + + if (color !== undefined) + embed.setColor(resolveColor(template(color, args))); + + if (description !== undefined) + embed.setDescription(template(bigString(description), args)); + + if (image !== undefined) + embed.setImage(template(image, args)); + + if (thumbnail !== undefined) + embed.setThumbnail(template(thumbnail, args)); + + if (title !== undefined) + embed.setTitle(template(title, args)); + + if (url !== undefined) + embed.setURL(template(url, args)); + + if (timestamp === true) + embed.setTimestamp(); + else if (typeof timestamp === 'string') + embed.setTimestamp(new Date(template(timestamp, args))); + else if (timestamp !== false) + embed.setTimestamp(timestamp); + + fields?.forEach(field => { + embed.addField(template(field.name, args), template(bigString(field.value), args), field.inline); + }); + + return embed; +} \ No newline at end of file diff --git a/src/util/main.ts b/src/util/main.ts new file mode 100644 index 0000000..3b66c3d --- /dev/null +++ b/src/util/main.ts @@ -0,0 +1,47 @@ +import * as Lang from '../lang'; + +/** + * shuffles an array + * https://stackoverflow.com/a/2450976/2856416 + * @param array an array + * @returns an array but shuffled + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function shuffle(array: any[]) { + let currentIndex = array.length, randomIndex; + + // While there remain elements to shuffle... + while (currentIndex != 0) { + + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + + // And swap it with the current element. + [array[currentIndex], array[randomIndex]] = [ + array[randomIndex], array[currentIndex]]; + } + + return array; +} + + + +export class errorMessage { + public msg: string; + public ephemeral: boolean; + + constructor(msg: string, ephemeral = true) { + this.msg = msg; + this.ephemeral = ephemeral; + } +} + +/** + * a simple class to contain an error message and related data + * @param msg error message + * @param ephemeral (default=true) + * @returns new errorMessage + */ +export const emsg = (msg: string, ephemeral = true) => new errorMessage(Lang.get(`error.${msg}`), ephemeral); + diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..e03ba65 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,11 @@ +// Special typescript project file, used by eslint only. +{ + "extends": "./tsconfig.json", + "include": [ + // repeated from base config's "include" setting + "src", + + // these are the eslint-only inclusions + ".eslintrc.json", + ] +} diff --git a/tsconfig.json b/tsconfig.json index b00c853..52bd8f2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,5 +11,11 @@ "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true, - } + "typeRoots": [ + "./src/types/" + ] + }, + "include": [ + "src" + ] }