From 37a121011bf147ea1e56b299b7e2b5dfe3574532 Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Mon, 26 Jul 2021 15:33:09 -0400 Subject: fix: refactored eval, sanitize decode, refactor price and fix trying to use an emoji as a color --- src/commands/dev/eval.ts | 45 ++------ src/commands/utilities/decode.ts | 36 ++---- src/commands/utilities/price.ts | 124 ++++++++------------- src/inhibitors/noCache.ts | 4 +- src/lib/extensions/discord-akairo/BushClient.ts | 4 +- .../extensions/discord-akairo/BushClientUtil.ts | 49 +++++++- src/lib/extensions/global.d.ts | 3 + 7 files changed, 113 insertions(+), 152 deletions(-) diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts index af6219b..ecac5f0 100644 --- a/src/commands/dev/eval.ts +++ b/src/commands/dev/eval.ts @@ -1,39 +1,8 @@ import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; import { exec } from 'child_process'; -import { MessageEmbed as _MessageEmbed, Util } from 'discord.js'; +import { MessageEmbed as _MessageEmbed } from 'discord.js'; import { transpile } from 'typescript'; -import { inspect, InspectOptions, promisify } from 'util'; - -const mapCredential = (old: string) => { - const mapping = { - ['token']: 'Main Token', - ['devToken']: 'Dev Token', - ['betaToken']: 'Beta Token', - ['hypixelApiKey']: 'Hypixel Api Key' - }; - return mapping[old] || old; -}; -const redact = (text: string) => { - for (const credentialName in client.config.credentials) { - const credential = client.config.credentials[credentialName]; - const replacement = mapCredential(credentialName); - const escapeRegex = /[.*+?^${}()|[\]\\]/g; - text = text.replace(new RegExp(credential.toString().replace(escapeRegex, '\\$&'), 'g'), `[${replacement} Omitted]`); - text = text.replace( - new RegExp([...credential.toString()].reverse().join('').replace(escapeRegex, '\\$&'), 'g'), - `[${replacement} Omitted]` - ); - } - return text; -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const inspectCleanRedactCodeblock = async (input: any, language: 'ts' | 'js', inspectOptions?: InspectOptions) => { - input = typeof input !== 'string' && inspectOptions !== undefined ? inspect(input, inspectOptions) : input; - input = Util.cleanCodeBlockContent(input); - input = redact(input); - return client.util.codeblock(input, 1024, language); -}; +import { promisify } from 'util'; export default class EvalCommand extends BushCommand { public constructor() { @@ -142,18 +111,18 @@ export default class EvalCommand extends BushCommand { { Canvas } = await import('node-canvas'); /* eslint-enable @typescript-eslint/no-unused-vars */ - const inputJS = await inspectCleanRedactCodeblock(code.js, 'js'); - const inputTS = code.lang === 'ts' ? await inspectCleanRedactCodeblock(code.ts, 'ts') : undefined; + const inputJS = await util.inspectCleanRedactCodeblock(code.js, 'js'); + const inputTS = code.lang === 'ts' ? await util.inspectCleanRedactCodeblock(code.ts, 'ts') : undefined; try { const rawOutput = code[code.lang].replace(/ /g, '').includes('9+10' || '10+9') ? '21' : await eval(code.js); - const output = await inspectCleanRedactCodeblock(rawOutput, 'js', { + const output = await util.inspectCleanRedactCodeblock(rawOutput, 'js', { depth: args.sel_depth || 0, showHidden: args.hidden, getters: true, showProxy: true }); const proto = args.show_proto - ? await inspectCleanRedactCodeblock(Object.getPrototypeOf(rawOutput), 'js', { + ? await util.inspectCleanRedactCodeblock(Object.getPrototypeOf(rawOutput), 'js', { depth: 1, getters: true, showHidden: true @@ -169,7 +138,7 @@ export default class EvalCommand extends BushCommand { embed.setTitle(`${emojis.errorFull} Code was not able to be evaluated.`).setColor(colors.error); if (inputTS) embed.addField('📥 Input (typescript)', inputTS).addField('📥 Input (transpiled javascript)', inputJS); else embed.addField('📥 Input', inputJS); - embed.addField('📤 Output', await inspectCleanRedactCodeblock(e?.stack || e, 'js')); + embed.addField('📤 Output', await util.inspectCleanRedactCodeblock(e?.stack || e, 'js')); } embed.setTimestamp().setFooter(message.author.tag, message.author.displayAvatarURL({ dynamic: true })); diff --git a/src/commands/utilities/decode.ts b/src/commands/utilities/decode.ts index 97ff444..aa3d455 100644 --- a/src/commands/utilities/decode.ts +++ b/src/commands/utilities/decode.ts @@ -89,43 +89,21 @@ export default class DecodeCommand extends BushCommand { } public async exec( - message: BushMessage, + message: BushMessage | AkairoMessage, { from, to, data }: { from: BufferEncoding; to: BufferEncoding; data: string } ): Promise { - const encodeOrDecode = message?.util?.parsed?.alias?.charAt(0).toUpperCase() + message?.util?.parsed?.alias?.slice(1); + const encodeOrDecode = util.capitalizeFirstLetter(message?.util?.parsed?.alias) || 'Decoded'; const decodedEmbed = new MessageEmbed() .setTitle(`${encodeOrDecode} Information`) - .addField('📥 Input', await this.client.util.codeblock(data, 1024)); + .addField('📥 Input', await util.inspectCleanRedactCodeblock(data, null)); try { const decoded = Buffer.from(data, from).toString(to); - decodedEmbed - .setColor(this.client.util.colors.success) - .addField('📤 Output', await this.client.util.codeblock(decoded, 1024)); - } catch (error) { - decodedEmbed - .setColor(this.client.util.colors.error) - .addField(`📤 Error ${encodeOrDecode.slice(1)}ing`, await this.client.util.codeblock(error.stack, 1024)); - } - return await message.util.send({ embeds: [decodedEmbed], allowedMentions: AllowedMentions.none() }); - } - - public async execSlash( - message: AkairoMessage, - { from, to, data }: { from: BufferEncoding; to: BufferEncoding; data: string } - ): Promise { - const decodedEmbed = new MessageEmbed() - .setTitle(`Decoded Information`) - .addField('📥 Input', await this.client.util.codeblock(data, 1024)); - try { - const decoded = Buffer.from(data, from).toString(to); - decodedEmbed - .setColor(this.client.util.colors.success) - .addField('📤 Output', await this.client.util.codeblock(decoded, 1024)); + decodedEmbed.setColor(util.colors.success).addField('📤 Output', await util.inspectCleanRedactCodeblock(decoded, null)); } catch (error) { decodedEmbed - .setColor(this.client.util.colors.error) - .addField(`📤 Error decoding`, await this.client.util.codeblock(error.stack, 1024)); + .setColor(util.colors.error) + .addField(`📤 Error ${encodeOrDecode.slice(1)}ing`, await util.inspectCleanRedactCodeblock(error.stack, null)); } - return await message.interaction.reply({ embeds: [decodedEmbed], allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ embeds: [decodedEmbed], allowedMentions: AllowedMentions.none() }); } } diff --git a/src/commands/utilities/price.ts b/src/commands/utilities/price.ts index 21781d1..d04544b 100644 --- a/src/commands/utilities/price.ts +++ b/src/commands/utilities/price.ts @@ -1,5 +1,5 @@ import { Constants } from 'discord-akairo'; -import { ColorResolvable, MessageEmbed } from 'discord.js'; +import { MessageEmbed } from 'discord.js'; import Fuse from 'fuse.js'; import fetch from 'node-fetch'; import { BushCommand, BushMessage } from '../../lib'; @@ -47,6 +47,8 @@ interface AuctionAverages { }; } +type Results = [Bazaar, LowestBIN, LowestBIN, AuctionAverages]; + export default class PriceCommand extends BushCommand { public constructor() { super('price', { @@ -99,113 +101,81 @@ export default class PriceCommand extends BushCommand { public async exec(message: BushMessage, { item, strict }: { item: string; strict: boolean }): Promise { const errors = new Array(); - const [bazaar, currentLowestBIN, averageLowestBIN, auctionAverages]: [Bazaar, LowestBIN, LowestBIN, AuctionAverages] = + const [bazaar, currentLowestBIN, averageLowestBIN, auctionAverages] = ( await Promise.all([ - fetch('https://api.hypixel.net/skyblock/bazaar') - .then((resp) => resp.json()) - .catch(() => errors.push('bazaar')), - fetch('https://moulberry.codes/lowestbin.json') - .then((resp) => resp.json()) - .catch(() => errors.push('current lowest BIN')), - fetch('https://moulberry.codes/auction_averages_lbin/3day.json') - .then((resp) => resp.json()) - .catch(() => errors.push('average Lowest BIN')), - fetch('https://moulberry.codes/auction_averages/3day.json') - .then((resp) => resp.json()) - .catch(() => errors.push('auction average')) - ]); + fetch('https://api.hypixel.net/skyblock/bazaar').catch(() => errors.push('bazaar')), + fetch('https://moulberry.codes/lowestbin.json').catch(() => errors.push('current lowest BIN')), + fetch('https://moulberry.codes/auction_averages_lbin/3day.json').catch(() => errors.push('average Lowest BIN')), + fetch('https://moulberry.codes/auction_averages/3day.json').catch(() => errors.push('auction average')) + ]) + ).map((request) => (typeof request === 'number' ? null : request.json())) as unknown as Results; let parsedItem = item.toString().toUpperCase().replace(/ /g, '_').replace(/'S/g, ''); const priceEmbed = new MessageEmbed(); if (errors?.length) { - priceEmbed.setFooter(`Could not fetch data from ${this.client.util.oxford(errors, 'and', '')}`); + priceEmbed.setFooter(`Could not fetch data from ${util.oxford(errors, 'and', '')}`); } - //combines all the item names from each - const itemNames = Array.from( - new Set( - (averageLowestBIN ? Object.keys(averageLowestBIN) : []).concat( - currentLowestBIN ? Object.keys(currentLowestBIN) : [], - auctionAverages ? Object.keys(auctionAverages) : [], - bazaar?.products ? Object.keys(bazaar.products) : [] - ) - ) + // create a set from all the item names so that there are no duplicates for the fuzzy search + const itemNames = new Set( + ...Object.keys(averageLowestBIN || {}), + ...Object.keys(currentLowestBIN || {}), + ...Object.keys(auctionAverages || {}), + ...Object.keys(bazaar?.products || {}) ); // fuzzy search - if (!strict) { - parsedItem = new Fuse(itemNames)?.search(parsedItem)[0]?.item; - } + if (!strict) parsedItem = new Fuse(Array.from(itemNames))?.search(parsedItem)[0]?.item; - // If bazaar item then it there should not be any ah data + // iif bazaar item then it there should not be any ah data if (bazaar['products'][parsedItem]) { const bazaarPriceEmbed = new MessageEmbed() - .setColor( - errors?.length - ? (this.client.util.emojis.warn as ColorResolvable) - : (this.client.util.colors.success as ColorResolvable) + .setColor(errors?.length ? util.colors.warn : util.colors.success) + .setTitle(`Bazaar Information for **${parsedItem}**`) + .addField('Sell Price', addBazaarInformation('sellPrice', 2, true)) + .addField('Buy Price', addBazaarInformation('buyPrice', 2, true)) + .addField( + 'Margin', + (+addBazaarInformation('buyPrice', 2, false) - +addBazaarInformation('sellPrice', 2, false)).toLocaleString() ) - .setTitle(`Bazaar Information for \`${parsedItem}\``) - .addField('Sell Price', Bazaar('sellPrice', 2, true)) - .addField('Buy Price', Bazaar('buyPrice', 2, true)) - .addField('Margin', (Number(Bazaar('buyPrice', 2, false)) - Number(Bazaar('sellPrice', 2, false))).toLocaleString()) - .addField('Current Sell Orders', Bazaar('sellOrders', 0, true)) - .addField('Current Buy Orders', Bazaar('buyOrders', 0, true)); + .addField('Current Sell Orders', addBazaarInformation('sellOrders', 0, true)) + .addField('Current Buy Orders', addBazaarInformation('buyOrders', 0, true)); return await message.util.reply({ embeds: [bazaarPriceEmbed] }); } - // Checks if the item exists in any of the action information otherwise it is not a valid item + // checks if the item exists in any of the action information otherwise it is not a valid item if (currentLowestBIN?.[parsedItem] || averageLowestBIN?.[parsedItem] || auctionAverages?.[parsedItem]) { priceEmbed - .setColor(this.client.util.colors.success) + .setColor(util.colors.success) .setTitle(`Price Information for \`${parsedItem}\``) .setFooter('All information is based on the last 3 days.'); } else { const errorEmbed = new MessageEmbed(); errorEmbed - .setColor(this.client.util.colors.error) - .setDescription( - `${this.client.util.emojis.error} \`${parsedItem}\` is not a valid item id, or it has no auction data.` - ); + .setColor(util.colors.error) + .setDescription(`${util.emojis.error} \`${parsedItem}\` is not a valid item id, or it has no auction data.`); return await message.util.reply({ embeds: [errorEmbed] }); } - if (currentLowestBIN?.[parsedItem]) { - const currentLowestBINPrice = currentLowestBIN[parsedItem].toLocaleString(); - priceEmbed.addField('Current Lowest BIN', currentLowestBINPrice); - } - if (averageLowestBIN?.[parsedItem]) { - const averageLowestBINPrice = averageLowestBIN[parsedItem].toLocaleString(); - priceEmbed.addField('Average Lowest BIN', averageLowestBINPrice); - } - if (auctionAverages?.[parsedItem]?.price) { - const auctionAveragesPrice = auctionAverages[parsedItem].price.toLocaleString(); - priceEmbed.addField('Average Auction Price', auctionAveragesPrice); - } - if (auctionAverages?.[parsedItem]?.count) { - const auctionAveragesCountPrice = auctionAverages[parsedItem].count.toLocaleString(); - priceEmbed.addField('Average Auction Count', auctionAveragesCountPrice); - } - if (auctionAverages?.[parsedItem]?.sales) { - const auctionAveragesSalesPrice = auctionAverages[parsedItem].sales.toLocaleString(); - priceEmbed.addField('Average Auction Sales', auctionAveragesSalesPrice); - } - if (auctionAverages?.[parsedItem]?.clean_price) { - const auctionAveragesCleanPrice = auctionAverages[parsedItem].clean_price.toLocaleString(); - priceEmbed.addField('Average Auction Clean Price', auctionAveragesCleanPrice); - } - if (auctionAverages?.[parsedItem]?.clean_sales) { - const auctionAveragesCleanSales = auctionAverages[parsedItem].clean_sales.toLocaleString(); - priceEmbed.addField('Average Auction Clean Sales', auctionAveragesCleanSales); - } + addPrice('Current Lowest BIN', currentLowestBIN?.[parsedItem]); + addPrice('Average Lowest BIN', averageLowestBIN?.[parsedItem]); + addPrice('Average Auction Price', auctionAverages?.[parsedItem]?.price); + addPrice('Average Auction Count', auctionAverages?.[parsedItem]?.count); + addPrice('Average Auction Sales', auctionAverages?.[parsedItem]?.sales); + addPrice('Average Auction Clean Price', auctionAverages?.[parsedItem]?.clean_price); + addPrice('Average Auction Clean Sales', auctionAverages?.[parsedItem]?.clean_sales); + return await message.util.reply({ embeds: [priceEmbed] }); - //Helper functions - function Bazaar(Information: string, digits: number, commas: boolean): string { + // helper functions + function addBazaarInformation(Information: string, digits: number, commas: boolean): string { const price = bazaar?.products[parsedItem]?.quick_status?.[Information]; - const a = Number(Number(price).toFixed(digits)); - return commas ? a?.toLocaleString() : a?.toString(); + const roundedPrice = Number(Number(price).toFixed(digits)); + return commas ? roundedPrice?.toLocaleString() : roundedPrice?.toString(); + } + function addPrice(name: string, price: number | undefined) { + if (price) priceEmbed.addField(name, price.toFixed(2).toLocaleString()); } } } diff --git a/src/inhibitors/noCache.ts b/src/inhibitors/noCache.ts index 9d6e560..9bddedd 100644 --- a/src/inhibitors/noCache.ts +++ b/src/inhibitors/noCache.ts @@ -11,8 +11,8 @@ export default class NoCacheInhibitor extends BushInhibitor { public async exec(message: BushMessage | BushSlashMessage): Promise { if (this.client.isOwner(message.author)) return false; - for (const property in this.client.cache) { - if (property === undefined || property === null) { + for (const property in this.client.cache.global) { + if (!this.client.cache.global[property]) { this.client.console.debug(`NoCacheInhibitor blocked message.`); return true; } diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index 8599e76..c283eb9 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -261,7 +261,9 @@ export class BushClient extends AkairoClient { /** Starts the bot */ public async start(): Promise { - global.client = this; // makes the client a global object + // global objects + global.client = this; + global.util = this.util; try { await this._init(); diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 1ba94c2..e8fe7fa 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -38,7 +38,7 @@ import { } from 'discord.js'; import got from 'got'; import humanizeDuration from 'humanize-duration'; -import { promisify } from 'util'; +import { inspect, InspectOptions, promisify } from 'util'; import { ActivePunishment, ActivePunishmentType } from '../../models/ActivePunishment'; import { BushNewsChannel } from '../discord.js/BushNewsChannel'; import { BushTextChannel } from '../discord.js/BushTextChannel'; @@ -449,21 +449,56 @@ export class BushClientUtil extends ClientUtil { /** * Surrounds text in a code block with the specified language and puts it in a hastebin if its too long. * - * * Embed Description Limit = 2048 characters + * * Embed Description Limit = 4096 characters * * Embed Field Limit = 1024 characters */ - public async codeblock(code: string, length: number, language: 'ts' | 'js' | 'sh' | 'json' | '' = ''): Promise { + public async codeblock(code: string, length: number, language?: 'ts' | 'js' | 'sh' | 'json'): Promise { let hasteOut = ''; const tildes = '```'; - const formattingLength = 2 * tildes.length + language.length + 2 * '\n'.length; + const formattingLength = 2 * tildes.length + language?.length ?? 0 + 2 * '\n'.length; if (code.length + formattingLength >= length) hasteOut = 'Too large to display. Hastebin: ' + (await this.haste(code)); const code2 = hasteOut ? code.substring(0, length - (hasteOut.length + '\n'.length + formattingLength)) : code; return ( - tildes + language + '\n' + Util.cleanCodeBlockContent(code2) + '\n' + tildes + (hasteOut.length ? '\n' + hasteOut : '') + tildes + language ?? + '' + '\n' + Util.cleanCodeBlockContent(code2) + '\n' + tildes + (hasteOut.length ? '\n' + hasteOut : '') ); } + private mapCredential(old: string) { + const mapping = { + ['token']: 'Main Token', + ['devToken']: 'Dev Token', + ['betaToken']: 'Beta Token', + ['hypixelApiKey']: 'Hypixel Api Key' + }; + return mapping[old] || old; + } + + /** + * Redacts credentials from a string + */ + public redact(text: string) { + for (const credentialName in client.config.credentials) { + const credential = client.config.credentials[credentialName]; + const replacement = this.mapCredential(credentialName); + const escapeRegex = /[.*+?^${}()|[\]\\]/g; + text = text.replace(new RegExp(credential.toString().replace(escapeRegex, '\\$&'), 'g'), `[${replacement} Omitted]`); + text = text.replace( + new RegExp([...credential.toString()].reverse().join('').replace(escapeRegex, '\\$&'), 'g'), + `[${replacement} Omitted]` + ); + } + return text; + } + + public async inspectCleanRedactCodeblock(input: any, language: 'ts' | 'js', inspectOptions?: InspectOptions) { + input = typeof input !== 'string' && inspectOptions !== undefined ? inspect(input, inspectOptions) : input; + input = Util.cleanCodeBlockContent(input); + input = this.redact(input); + return client.util.codeblock(input, 1024, language); + } + public async slashRespond( interaction: CommandInteraction, responseOptions: BushSlashSendMessageType | BushSlashEditMessageType @@ -754,4 +789,8 @@ export class BushClientUtil extends ClientUtil { const autoModPhases = await message.guild.getSetting('autoModPhases'); if (autoModPhases.includes(message.content.toString()) && message.deletable) message.delete(); } + + public capitalizeFirstLetter(string: string): string { + return string.charAt(0)?.toUpperCase() + string.slice(1); + } } diff --git a/src/lib/extensions/global.d.ts b/src/lib/extensions/global.d.ts index 8c7a117..d4c5f61 100644 --- a/src/lib/extensions/global.d.ts +++ b/src/lib/extensions/global.d.ts @@ -1,9 +1,12 @@ import { BushClient } from './discord-akairo/BushClient'; +import { BushClientUtil } from './discord-akairo/BushClientUtil'; declare global { namespace NodeJS { export interface Global { client: BushClient; + util: BushClientUtil; } } const client: BushClient; + const util: BushClientUtil; } -- cgit