const {Collection} = require("@projectdysnomia/dysnomia"); const murmurhash = require("murmurhash").v3; const {tinycolor} = require("@ctrl/tinycolor"); const logger = require("../lib/logger.js"); function pastelize(id) { const hue = murmurhash(id) % 360; const hex = tinycolor(`hsl(${hue},75%,60%)`).toHex(); return parseInt(hex, 16); } function formatUsername(user) { return user.discriminator && user.discriminator != "0" ? `${user.username}#${user.discriminator}` : `@${user.username}`; } function getTopColor(msg, id, fallback = 0x7289da) { if (!msg.guildID) return fallback; const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID); const roles = guild.members .get(id) .roles.map((role) => guild.roles.get(role)) .filter((role) => role.color); roles.sort((a, b) => b.position - a.position); return roles[0]?.color || fallback; } function safeString(string, newLines = true) { string = string ? string.toString() : ""; string = string.replace(/`/g, "'"); string = string.replace(/<@/g, "<@\u200b"); string = string.replace(/<#/g, "<#\u200b"); string = string.replace(/<&/g, "<&\u200b"); if (newLines) string = string.replace(/\n/g, " "); return string; } function formatTime(number) { let seconds = parseInt(number) / 1000; const days = Math.floor(seconds / 86400); seconds = seconds % 86400; const hours = Math.floor(seconds / 3600); seconds = seconds % 3600; const minutes = Math.floor(seconds / 60); seconds = Math.floor(seconds % 60); return ( (days !== 0 ? `${days.toString().padStart(2, "0")}:` : "") + (hours !== 0 ? `${hours.toString().padStart(2, "0")}:` : "") + `${minutes.toString().padStart(2, "0")}:${seconds .toString() .padStart(2, "0")}` ); } function readableTime(number) { const seconds = number / 1000; const days = seconds / 60 / 60 / 24; const years = days / 365.25; if (years >= 1) { return `${years.toFixed(2)} years`; } else { return `${days.toFixed(2)} days`; } } async function isGif(url) { const type = await fetch(url).then((res) => res.headers.get("Content-Type")); return type == "image/gif"; } async function findLastImage(msg, gif = false) { const messages = await msg.channel.getMessages(20); let img; for (const message of messages) { if (message.attachments.length > 0) { img = message.attachments[0].url; if (gif && (await isGif(img))) { break; } else { break; } } else if (message.embeds.length > 0) { img = message.embeds[0]?.thumbnail?.url || message.embeds[0]?.image?.url; if (img) { if (gif && (await isGif(img))) { break; } else { break; } } } } return await new Promise((resolve, reject) => { if (!img) { reject("Image not found in last 20 messages."); } else { resolve(img); } }); } const urlRegex = /((https?):\/)?\/?([^:/\s]+)((\/\w+)*\/)([\w\-.]+)/; async function getImage(msg, str) { const refMsg = msg.referencedMessage; if (refMsg) { if (refMsg.attachments[0] && refMsg.attachments[0].url) { return refMsg.attachments[0].url; } else if (//.test(refMsg.content)) { const id = refMsg.content.match(//)[1]; return `https://cdn.discordapp.com/emojis/${id}.png?v=1`; } else if (/<@!?([0-9]*)>/.test(refMsg.content)) { const id = refMsg.content.match(/<@!?([0-9]*)>/)[1]; const user = await hf.bot.requestHandler.request( "GET", "/users/" + id, true ); if (user) return `https://cdn.discordapp.com/avatars/${id}/${user.avatar}.png?size=1024`; } } const img = await findLastImage(msg, false); if (!str) { if (img) return img; } if (msg.attachments[0] && msg.attachments[0].url) { return msg.attachments[0].url; } else if (urlRegex.test(str)) { return str; } else if (//.test(str)) { const id = str.match(//)[1]; return `https://cdn.discordapp.com/emojis/${id}.png?v=1`; } else if (/<@!?([0-9]*)>/.test(str)) { const id = str.match(/<@!?([0-9]*)>/)[1]; const user = await hf.bot.requestHandler.request( "GET", "/users/" + id, true ); if (user) return `https://cdn.discordapp.com/avatars/${id}/${user.avatar}.png?size=1024`; } else if (img) { return img; } return null; } async function hastebin(body) { const res = await fetch(`${hf.config.haste_provider}/documents`, { method: "POST", body, }).then((r) => r.json()); return `<${hf.config.haste_provider}/${res.key}>`; } hf.selectionMessages = hf.selectionMessages ?? new Collection(); async function selectionMessage( msg, heading, options, timeout = 30000, maxItems = 1 ) { const data = { content: heading, allowedMentions: { repliedUser: false, }, messageReference: { messageID: msg.id, }, components: [ { type: 1, components: [ { type: 3, custom_id: msg.id, options: [], max_values: maxItems, }, ], }, { type: 1, components: [ { type: 2, style: 4, label: "Cancel", custom_id: "cancel", }, ], }, ], }; options.slice(0, 25).forEach((value) => { data.components[0].components[0].options.push({ label: value.display, value: value.key, description: value.description, }); }); if (options.length > 25) { data.content += `\n\nDisplaying 25/${options.length} results`; } const displayMessage = await msg.channel .createMessage(data) .catch((err) => logger.error( "selectionMessage", "Failed to create selection message: " + err ) ); return await new Promise((resolve, reject) => { function listener(interaction) { const user = interaction.member.user || interaction.user; if ( user.id == msg.author.id && interaction.channel.id == msg.channel.id && interaction.message.components[0].components[0].custom_id == msg.id ) { if (interaction.data.custom_id == "cancel") { hf.events.remove("interactionCreate", `selection.${msg.id}`); clearTimeout(hf.selectionMessages.get(msg.id)); hf.selectionMessages.delete(msg.id); interaction.deferUpdate(); displayMessage.delete(); reject("Canceled"); } else { hf.events.remove("interactionCreate", `selection.${msg.id}`); clearTimeout(hf.selectionMessages.get(msg.id)); hf.selectionMessages.delete(msg.id); interaction.deferUpdate(); displayMessage.delete(); let result; if (maxItems > 1) { result = options .filter((opt) => interaction.data.values.includes(opt.key)) .map((opt) => opt.key); } else { result = options.filter( (opt) => opt.key == interaction.data.values[0] )[0].value; } resolve(result); } } } hf.events.add("interactionCreate", `selection.${msg.id}`, listener); hf.selectionMessages.set( msg.id, setTimeout(() => { hf.events.remove("interactionCreate", `selection.${msg.id}`); hf.selectionMessages.delete(msg.id); reject("Request timed out"); }, timeout) ); }); } async function lookupUser(msg, str, filter) { if (/[0-9]{17,21}/.test(str)) { return await hf.bot.requestHandler.request( "GET", "/users/" + str.match(/([0-9]{17,21})/)[1], true ); } let users; if (filter) { users = hf.bot.users.filter(filter).values(); } else if (msg.guildID) { const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID); users = guild.members.values(); } else { users = hf.bot.users.values(); } if (/(.+?)#([0-9]{4})/.test(str)) { const [_, name, discrim] = str.match(/(.+?)#([0-9]{4})/); for (const user of users) { if ( user.username.toLowerCase() == name.toLowerCase() && user.discriminator == discrim ) { return user; } } } const selection = []; for (const user of users) { if ( user.username.toLowerCase() == str.toLowerCase() || (user.nickname && user.nickname == str.toLowerCase()) ) { return user; } else if ( user.username.toLowerCase().indexOf(str.toLowerCase()) > -1 || (user.nick && user.nick.indexOf(str.toLowerCase()) > -1) ) { selection.push({ value: user, key: user.id, display: `${formatUsername(user)}${user.nick ? ` (${user.nick})` : ""}`, }); } } selection.sort((a, b) => a.display - b.display); if (selection.length == 0) { return "No results"; } else if (selection.length == 1) { return selection[0].value; } else { selection.splice(20); try { return await selectionMessage( msg, "Multiple users found, please pick from this list:", selection ); } catch (out) { return out; } } } // https://stackoverflow.com/a/39243641 const htmlEntities = { nbsp: " ", cent: "¢", pound: "£", yen: "¥", euro: "€", copy: "©", reg: "®", lt: "<", gt: ">", quot: '"', amp: "&", apos: "'", }; function parseHtmlEntities(str) { return str.replace(/&([^;]+);/g, function (entity, entityCode) { var match; if (entityCode in htmlEntities) { return htmlEntities[entityCode]; /*eslint no-cond-assign: 0*/ } else if ((match = entityCode.match(/^#x([\da-fA-F]+)$/))) { return String.fromCharCode(parseInt(match[1], 16)); /*eslint no-cond-assign: 0*/ } else if ((match = entityCode.match(/^#(\d+)$/))) { return String.fromCharCode(~~match[1]); } else { return entity; } }); } const UPLOAD_LIMIT = 8388608; const UPLOAD_LIMIT_TIER_2 = 52428800; const UPLOAD_LIMIT_TIER_3 = 104857600; function getUploadLimit(guild) { if (!guild) return UPLOAD_LIMIT; if (guild.premiumTier == 2) return UPLOAD_LIMIT_TIER_2; if (guild.premiumTier == 3) return UPLOAD_LIMIT_TIER_3; return UPLOAD_LIMIT; } module.exports = { pastelize, formatUsername, getTopColor, safeString, formatTime, readableTime, isGif, findLastImage, getImage, hastebin, selectionMessage, lookupUser, parseHtmlEntities, getUploadLimit, };