From 458d3f3d76d6f0b5e21e1b057898a34fbaa51daa Mon Sep 17 00:00:00 2001 From: ashley zomo Date: Mon, 14 Feb 2022 14:37:10 -0600 Subject: [PATCH] added embed support to lang --- .eslintrc.json | 4 + dist/api.js | 26 ++--- dist/lang.js | 160 +++++++++++++++++++++++++-- src/api.ts | 29 ++--- src/lang.ts | 288 +++++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 450 insertions(+), 57 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 6bdbc4e..a105527 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -17,6 +17,10 @@ "allowNumber" : false } ], + + /* dev */ + "no-debugger": "off", + /* important */ "prefer-const": "error", "quotes": ["error", "single"], diff --git a/dist/api.js b/dist/api.js index 24d257d..b2879f4 100644 --- a/dist/api.js +++ b/dist/api.js @@ -25,9 +25,9 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.getPlayerInteraction = exports.getPlayer = void 0; /*eslint prefer-const: "error"*/ const cheerio = __importStar(require("cheerio")); -const discord_js_1 = require("discord.js"); const https_1 = __importDefault(require("https")); const util_1 = require("./util"); +const Lang = __importStar(require("./lang")); const uniteApiRegex = { //$1 = name, $2 = id ogtitle: /unite api - (.+) \((.*)\)/i, @@ -187,18 +187,18 @@ async function sendPlayerEmbed(interaction, data) { eloStr = `(${data.elo})`; else eloStr = `Class ${data.class}`; - const embed = new discord_js_1.MessageEmbed() - .setTitle(`${data.name} (${data.id})`) - .setURL(`https://uniteapi.dev/p/${encodeURIComponent(data.name)}`) - .setTimestamp() - .setThumbnail(data.avatar) - .setDescription(`Level ${data.level} -${data.rank} ${eloStr} - -**Battles** ${data.battles} -**Wins** ${data.wins} -**Win Rate** ${data.winrate}`); - await interaction.editReply({ embeds: [embed] }); + await interaction.editReply(Lang.getEmbed('api.player', { + name: data.name, + id: data.id, + nameEncoded: encodeURIComponent(data.name), + avatar: data.avatar, + level: data.level, + rank: data.rank, + elo: eloStr, + battles: data.battles, + wins: data.wins, + winrate: data.winrate + })); } /** * calls getPlayer() with the name from the interaction diff --git a/dist/lang.js b/dist/lang.js index e783d70..1042446 100644 --- a/dist/lang.js +++ b/dist/lang.js @@ -1,6 +1,100 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.get = exports.setLang = void 0; +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 = { en: { main: { @@ -13,6 +107,23 @@ const LANG = { join: 'Joined the queue', leave: 'Left the queue' }, + api: { + player: { + embed: true, + title: '{name} ({id})', + url: 'https://uniteapi.dev/p/{nameEncoded}', + timestamp: true, + thumbnail: '{avatar}', + description: [ + 'Level {level}', + '{rank} {elo}', + '', + '**Battles** {battles}', + '**Wins** {wins}', + '**Win Rate** {winrate}' + ] + } + }, error: { main: { missingToken: 'Missing Discord Token, please enter the bot token into the token file' @@ -35,6 +146,7 @@ const LANG = { } } }; +/* MAIN */ let LANGID = 'en'; if (!(LANGID in LANG)) throw 'language id does not exist'; @@ -45,16 +157,8 @@ function setLang(langid) { throw 'language id does not exist'; } exports.setLang = setLang; -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; - }); -} /** - * reads language json + * reads language json (just strings) * @param id ex: discord.error.noActiveQueue * @returns language value, defaults to `id` parameter */ @@ -66,6 +170,8 @@ function get(id, args = {}) { const found = finding[key]; if (typeof found === 'string') return template(found, args); + if (found.embed === true) + return embedObjStr(found, id); finding = found; } else @@ -74,3 +180,37 @@ function get(id, args = {}) { return id; } exports.get = get; +/** + * reads language json as an object (could be embed or just string) + * @param id ex: discord.error.noActiveQueue + * @returns language value, defaults to `id` parameter + */ +function getEmbed(id, args = {}) { + const embedData = { + embeds: [] + }; + const keySpl = id.split('.').map(k => k.trim()).filter(k => k); + let finding = LANG[LANGID]; + for (const key of keySpl) { + if (key in finding) { + const found = finding[key]; + if (typeof found === 'string') { + embedData.content = template(found, args); + break; + } + if (found.embed === true) { + const embedObj = found, { content } = embedObj, embed = embedObjEmbed(embedObj, args), embedData = { + embeds: [embed] + }; + if (content !== undefined) + embedData.content = content; + return embedData; + } + finding = found; + } + else + break; + } + return embedData; +} +exports.getEmbed = getEmbed; diff --git a/src/api.ts b/src/api.ts index ded8cbd..810ef14 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,9 +1,10 @@ /*eslint prefer-const: "error"*/ import * as cheerio from 'cheerio'; -import { CommandInteraction, MessageEmbed } from 'discord.js'; +import { CommandInteraction } from 'discord.js'; import { IncomingMessage } from 'http'; import http from 'https'; import { emsg } from './util'; +import * as Lang from './lang'; const uniteApiRegex = { //$1 = name, $2 = id @@ -91,6 +92,7 @@ function readHTML(html: string): uniteApiData { wins: '', winrate: '' }; + let metaElems = $('meta').toArray(); //filter down to just ones named "og:..." @@ -227,19 +229,18 @@ async function sendPlayerEmbed(interaction: CommandInteraction, data: uniteApiDa else eloStr = `Class ${data.class}`; - const embed = new MessageEmbed() - .setTitle(`${data.name} (${data.id})`) - .setURL(`https://uniteapi.dev/p/${encodeURIComponent(data.name)}`) - .setTimestamp() - .setThumbnail(data.avatar) - .setDescription(`Level ${data.level} -${data.rank} ${eloStr} - -**Battles** ${data.battles} -**Wins** ${data.wins} -**Win Rate** ${data.winrate}`); - - await interaction.editReply({embeds: [embed]}); + await interaction.editReply(Lang.getEmbed('api.player', { + name: data.name, + id: data.id, + nameEncoded: encodeURIComponent(data.name), + avatar: data.avatar, + level: data.level, + rank: data.rank, + elo: eloStr, + battles: data.battles, + wins: data.wins, + winrate: data.winrate + })); } /** diff --git a/src/lang.ts b/src/lang.ts index b30ebab..4bf768f 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -1,6 +1,197 @@ -type LangObj = { [keys:string]: LangObj | string } +import { MessageEmbed } from 'discord.js'; + +/* TYPES */ + +type basicObject = {[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: basicObject = {}): 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 */ + + + const LANG: LangObjWhole = { en: { @@ -17,6 +208,24 @@ const LANG: LangObjWhole = { leave: 'Left the queue' }, + api: { + player: { + embed: true, + title: '{name} ({id})', + url: 'https://uniteapi.dev/p/{nameEncoded}', + timestamp: true, + thumbnail: '{avatar}', + description: [ + 'Level {level}', + '{rank} {elo}', + '', + '**Battles** {battles}', + '**Wins** {wins}', + '**Win Rate** {winrate}' + ] + } + }, + error: { main: { @@ -47,6 +256,7 @@ const LANG: LangObjWhole = { }; +/* MAIN */ let LANGID = 'en'; if (!(LANGID in LANG)) @@ -59,27 +269,12 @@ export function setLang(langid: string) { throw 'language id does not exist'; } -function template(str: string, args: {[keys: string]: string}): string { - - return str.replace(/{\w+}/g, str => { - - const key = str.substring(1, str.length-1); - - if (key in args) - return args[key]; - - return key; - - }); - -} - /** - * reads language json + * reads language json (just strings) * @param id ex: discord.error.noActiveQueue * @returns language value, defaults to `id` parameter */ -export function get(id: string, args: {[keys: string]: string} = {}): string {//discord.error.noActiveQueue +export function get(id: string, args: basicObject = {}): string { const keySpl = id.split('.').map(k => k.trim()).filter(k => k); @@ -94,7 +289,10 @@ export function get(id: string, args: {[keys: string]: string} = {}): string {// if (typeof found === 'string') return template(found, args); - finding = found; + if (found.embed === true) + return embedObjStr(found as embedObj, id); + + finding = found as LangObj; } else break; @@ -102,4 +300,54 @@ export function get(id: string, args: {[keys: string]: string} = {}): string {// } return id; -} \ No newline at end of file +} + +/** + * reads language json as an object (could be embed or just string) + * @param id ex: discord.error.noActiveQueue + * @returns language value, defaults to `id` parameter + */ +export function getEmbed(id: string, args: basicObject = {}): embedData { + + const embedData: embedData = { + embeds: [] + }; + + const keySpl = id.split('.').map(k => k.trim()).filter(k => k); + + let finding = LANG[LANGID]; + + for (const key of keySpl) { + + if (key in finding) { + + const found = finding[key]; + + if (typeof found === 'string') { + embedData.content = template(found, args); + break; + } + + if (found.embed === true) { + const embedObj = found as embedObj, + {content} = embedObj, + embed = embedObjEmbed(embedObj, args), + embedData: embedData = { + embeds: [embed] + }; + + if (content !== undefined) + embedData.content = content; + + return embedData; + } + + finding = found as LangObj; + + } else + break; + + } + + return embedData; +}