import { ClientUtil } from 'discord-akairo'; import { BotClient } from './BotClient'; import { User } from 'discord.js'; import { promisify } from 'util'; import { exec } from 'child_process'; import got from 'got'; import { TextChannel } from 'discord.js'; import { MessageEmbed } from 'discord.js'; import { GuildMember } from 'discord.js'; interface hastebinRes { key: string; } export interface uuidRes { uuid: string; username: string; username_history?: | { username: string; }[] | null; textures: { custom: boolean; slim: boolean; skin: { url: string; data: string; }; raw: { value: string; signature: string; }; }; created_at: string; } export class Util extends ClientUtil { /** * The client of this ClientUtil * @type {BotClient} */ public client: BotClient; /** * The hastebin urls used to post to hastebin, attempts to post in order * @type {string[]} */ public hasteURLs = [ 'https://hst.sh', 'https://hasteb.in', 'https://hastebin.com', 'https://mystb.in', 'https://haste.clicksminuteper.net', 'https://paste.pythondiscord.com', 'https://haste.unbelievaboat.com', 'https://haste.tyman.tech' ]; /** * A simple promise exec method */ private exec = promisify(exec); /** * Creates this client util * @param client The client to initialize with */ constructor(client: BotClient) { super(client); } /** * Maps an array of user ids to user objects. * @param ids The list of IDs to map * @returns The list of users mapped */ public async mapIDs(ids: string[]): Promise { return await Promise.all(ids.map((id) => this.client.users.fetch(id))); } /** * Capitalizes the first letter of the given text * @param text The text to capitalize * @returns The capitalized text */ public capitalize(text: string): string { return text.charAt(0).toUpperCase() + text.slice(1); } /** * Runs a shell command and gives the output * @param command The shell command to run * @returns The stdout and stderr of the shell command */ public async shell( command: string ): Promise<{ stdout: string; stderr: string; }> { return await this.exec(command); } /** * Posts text to hastebin * @param content The text to post * @returns The url of the posted text */ public async haste(content: string): Promise { for (const url of this.hasteURLs) { try { const res: hastebinRes = await got .post(`${url}/documents`, { body: content }) .json(); return `${url}/${res.key}`; } catch (e) { // pass } } throw new Error('No urls worked. (wtf)'); } /** * Logs something but only in dev mode * @param content The thing to log */ public devLog(content: unknown): void { if (this.client.config.dev) console.log(content); } /** * Resolves a user-provided string into a user object, if possible * @param text The text to try and resolve * @returns The user resolved or null */ public async resolveUserAsync(text: string): Promise { const idReg = /\d{17,19}/; const idMatch = text.match(idReg); if (idMatch) { try { const user = await this.client.users.fetch(text); return user; } catch { // pass } } const mentionReg = /<@!?(?\d{17,19})>/; const mentionMatch = text.match(mentionReg); if (mentionMatch) { try { const user = await this.client.users.fetch(mentionMatch.groups.id); return user; } catch { // pass } } const user = this.client.users.cache.find((u) => u.username === text); if (user) return user; return null; } /** * Appends the correct ordinal to the given number * @param n The number to append an ordinal to * @returns The number with the ordinal */ public ordinal(n: number): string { const s = ['th', 'st', 'nd', 'rd'], v = n % 100; return n + (s[(v - 20) % 10] || s[v] || s[0]); } /** * Chunks an array to the specified size * @param arr The array to chunk * @param perChunk The amount of items per chunk * @returns The chunked array */ public chunk(arr: T[], perChunk: number): T[][] { return arr.reduce((all, one, i) => { const ch = Math.floor(i / perChunk); all[ch] = [].concat(all[ch] || [], one); return all; }, []); } /** * Logs a message to console and log channel as info * @param message The message to send */ public async info(message: string): Promise { console.log(`INFO: ${message}`); const channel = (await this.client.channels.fetch( this.client.config.channels.log )) as TextChannel; await channel.send(`INFO: ${message}`); } /** * Logs a message to console and log channel as a warning * @param message The message to send */ public async warn(message: string): Promise { console.warn(`WARN: ${message}`); const channel = (await this.client.channels.fetch( this.client.config.channels.log )) as TextChannel; await channel.send(`WARN: ${message}`); } /** * Logs a message to console and log channel as an error * @param message The message to send */ public async error(message: string): Promise { console.error(`ERROR: ${message}`); const channel = (await this.client.channels.fetch( this.client.config.channels.error )) as TextChannel; await channel.send(`ERROR: ${message}`); } /** * The colors used throught the bot */ public colors = { default: '#1FD8F1', error: '#ff0000', success: '#00ff02', red: '#ff0000', blue: '#0055ff', aqua: '#00bbff', purple: '#8400ff', blurple: '#5440cd', pink: '#ff00e6', green: '#00ff1e', darkgreen: '#008f11', gold: '#b59400', yellow: '#ffff00', white: '#ffffff', gray: '#a6a6a6', lightgray: '#cfcfcf', darkgray: '#7a7a7a', black: '#000000', orange: '#E86100' }; /** * A simple utility to create and embed with the needed style for the bot */ public createEmbed( color?: string, author?: User | GuildMember ): MessageEmbed { if (author instanceof GuildMember) { author = author.user; // Convert to User if GuildMember } let embed = new MessageEmbed().setTimestamp(); if (author) embed = embed.setAuthor( author.username, author.displayAvatarURL({ dynamic: true }), `https://discord.com/users/${author.id}` ); if (color) embed = embed.setColor(color); return embed; } public async mcUUID(username: string): Promise { const apiRes = (await got .get(`https://api.ashcon.app/mojang/v2/user/${username}`) .json()) as uuidRes; return apiRes.uuid; } }