nyx

The first CODM discrod bot -- cath.exe Template
git clone https://codeberg.org/night0721/nyx
Log | Files | Refs | LICENSE

function.js (23865B)


      1 const {
      2   EmbedBuilder,
      3   ActionRowBuilder,
      4   ButtonBuilder,
      5   ButtonStyle,
      6 } = require("discord.js");
      7 /**
      8  * Returns a random element from an array
      9  * @returns {any}
     10  */
     11 Array.prototype.random = function () {
     12   return this[~~(Math.random() * this.length)];
     13 };
     14 
     15 function rndint(max, min) {
     16   return Math.floor(Math.random() * (max - (min ? min : 0))) + (min ? min : 0);
     17 }
     18 
     19 const months = [
     20   "January",
     21   "February",
     22   "March",
     23   "April",
     24   "May",
     25   "June",
     26   "July",
     27   "August",
     28   "September",
     29   "October",
     30   "November",
     31   "December",
     32 ];
     33 
     34 function parseDate(date) {
     35   let dow = date.getDate().toString();
     36   return `${date.toLocaleDateString("en-US", {
     37     weekday: "long",
     38   })}, ${months[date.getMonth()]} ${
     39     dow.endsWith("1")
     40       ? `${dow}st`
     41       : dow.endsWith("2")
     42       ? `${dow}nd`
     43       : dow.endsWith("3")
     44       ? `${dow}rd`
     45       : `${dow}th`
     46   } ${date.getFullYear()}, ${date.toLocaleTimeString()}`;
     47 }
     48 
     49 function parseShortDate(date) {
     50   let dow = date.getDate().toString();
     51   return `${months[date.getMonth()]} ${
     52     dow.endsWith("1")
     53       ? `${dow}st`
     54       : dow.endsWith("2")
     55       ? `${dow}nd`
     56       : dow.endsWith("3")
     57       ? `${dow}rd`
     58       : `${dow}th`
     59   } ${date.getFullYear()}`;
     60 }
     61 function timer(timestamp) {
     62   const timeLeft = timestamp;
     63   const days = Math.floor(timeLeft / 86400000);
     64   const hours = Math.floor(timeLeft / 3600000) - days * 24;
     65   const minutes = Math.floor(timeLeft / 60000) - days * 1440 - hours * 60;
     66   const seconds =
     67     Math.floor(timeLeft / 1000) - days * 86400 - hours * 3600 - minutes * 60;
     68   const mseconds = timeLeft / 1000 - days * 86400 - hours * 3600 - minutes * 60;
     69   let string = "";
     70   if (days) string = string + `${days} ${days == 1 ? "day " : "days "}`;
     71   if (hours) string = string + `${hours} ${hours == 1 ? "hour " : "hours "}`;
     72   if (minutes) {
     73     string = string + `${minutes} ${minutes == 1 ? "minute " : "minutes "}`;
     74   }
     75   if (seconds) {
     76     string = string + `${seconds} ${seconds == 1 ? "second " : "seconds "}`;
     77   }
     78   if (!string.length) string = `${mseconds.toFixed(1)} second`;
     79   return string;
     80 }
     81 function sleep(ms) {
     82   new Promise(resolve => setTimeout(resolve, ms));
     83 }
     84 function toHHMMSS(str) {
     85   const sec_num = parseInt(str, 10);
     86   let hours = Math.floor(sec_num / 3600);
     87   let minutes = Math.floor((sec_num - hours * 3600) / 60);
     88   let seconds = sec_num - hours * 3600 - minutes * 60;
     89   if (hours < 10) {
     90     hours = "0" + hours;
     91   }
     92   if (minutes < 10) {
     93     minutes = "0" + minutes;
     94   }
     95   if (seconds < 10) {
     96     seconds = "0" + seconds;
     97   }
     98   return hours + ":" + minutes + ":" + seconds;
     99 }
    100 function fixPermissions(arr = Array) {
    101   const permissions = {
    102     ADMINISTRATOR: "Administrator",
    103     VIEW_AUDIT_LOG: "View Audit Log",
    104     VIEW_GUILD_INSIGHTS: "View Server Insights",
    105     MANAGE_GUILD: "Manage Server",
    106     MANAGE_ROLES: "Manage Roles",
    107     MANAGE_CHANNELS: "Manage Channels",
    108     KICK_MEMBERS: "Kick Members",
    109     BAN_MEMBERS: "Ban Members",
    110     CREATE_INSTANT_INVITE: "Create Invite",
    111     CHANGE_NICKNAME: "Change Nickname",
    112     MANAGE_NICKNAMES: "Manage Nicknames",
    113     MANAGE_EMOJIS_AND_STICKERS: "Manage Emojis and Stickers",
    114     MANAGE_WEBHOOKS: "Manage Webhooks",
    115     VIEW_CHANNEL: "Read Text Channels & See Voice Channels",
    116     SEND_MESSAGES: "Send Messages",
    117     SEND_TTS_MESSAGES: "Send TTS Messages",
    118     MANAGE_MESSAGES: "Manage Messages",
    119     EMBED_LINKS: "Embed Links",
    120     ATTACH_FILES: "Attach Files",
    121     READ_MESSAGE_HISTORY: "Read Message History",
    122     MENTION_EVERYONE: "Mention @everyone, @here, and All Roles",
    123     USE_EXTERNAL_EMOJIS: "Use External Emojis",
    124     ADD_REACTIONS: "Add Reactions",
    125     CONNECT: "Connect",
    126     SPEAK: "Speak",
    127     STREAM: "Video",
    128     MUTE_MEMBERS: "Mute Members",
    129     DEAFEN_MEMBERS: "Deafen Members",
    130     MOVE_MEMBERS: "Move Members",
    131     USE_VAD: "Use Voice Activity",
    132     PRIORITY_SPEAKER: "Priority Speaker",
    133     REQUEST_TO_SPEAK: "Request to Speak",
    134     MANAGE_THREADS: "Manage Threads",
    135     USE_PUBLIC_THREADS: "Use Public Threads",
    136     USE_PRIVATE_THREADS: "Use Private Threads",
    137     USE_EXTERNAL_STICKERS: "Use External Stickers",
    138     USE_APPLICATION_COMMANDS: "Use Application Commands",
    139   };
    140   const final = [];
    141   for (const perm in permissions) {
    142     if (arr.includes(perm)) final.push(`โœ”๏ธ ${permissions[perm]}`);
    143     else final.push(`โŒ ${permissions[perm]}`);
    144   }
    145   return `${`\`\`\`diff\n${final.join("\n")}\`\`\``}`;
    146 }
    147 function formatPerms(perm) {
    148   return perm
    149     .toLowerCase()
    150     .replace(/(^|"|_)(\S)/g, s => s.toUpperCase())
    151     .replace(/_/g, " ")
    152     .replace(/Guild/g, "Server")
    153     .replace(/Use Vad/g, "Use Voice Acitvity");
    154 }
    155 function trimArray(arr = []) {
    156   if (arr.length > 10) {
    157     const length = arr.length - 10;
    158     arr = arr.slice(0, 10);
    159     arr.push(`\n${length} more...`);
    160   }
    161   return arr.join(" **|** ");
    162 }
    163 function checkDays(date) {
    164   const now = new Date();
    165   const diff = now.getTime() - date.getTime();
    166   const days = Math.floor(diff / 86400000);
    167   return days + (days == 1 ? " day" : " days") + " ago";
    168 }
    169 function format(str) {
    170   return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
    171 }
    172 function fixFeatures(arr = []) {
    173   const all = {
    174     ANIMATED_ICON: "Animated Icon",
    175     BANNER: "Banner",
    176     COMMERCE: "Commerce",
    177     COMMUNITY: "Community",
    178     DISCOVERABLE: "Discoverable",
    179     FEATURABLE: "Featurable",
    180     INVITE_SPLASH: "Invite Splash",
    181     MEMBER_VERIFICATION_GATE_ENABLED: "Member Verification Gate Enabled",
    182     NEWS: "News",
    183     PARTNERED: "Partnered",
    184     PREVIEW_ENABLED: "Preview Enabled",
    185     VANITY_URL: "Vanity URL",
    186     VERIFIED: "Verified",
    187     VIP_REGIONS: "VIP Region",
    188     WELCOME_SCREEN_ENABLED: "Welcome Screen Enabled",
    189     TICKETED_EVENTS_ENABLED: "Ticketed Events Enabled",
    190     MONETIZATION_ENABLED: "Monetization Enabled",
    191     MORE_STICKERS: "More Stickers",
    192     THREE_DAY_THREAD_ARCHIVE: "Three Day Thread Archive",
    193     SEVEN_DAY_THREAD_ARCHIVE: "Seven Day Thread Archive",
    194     PRIVATE_THREADS: "Private Threads,",
    195   };
    196   const final = [];
    197   for (const feature in all) {
    198     if (arr.includes(feature)) final.push(`โœ… ${all[feature]}`);
    199   }
    200   return `${final.join("\n")}`;
    201 }
    202 function cooldown(dbtime, defaults, msg) {
    203   const expiration_time = dbtime + defaults;
    204   const time_left = expiration_time - Date.now();
    205   const slow = [
    206     "Keep it slow...",
    207     "Calm down",
    208     "Stop it. Get some help.",
    209     "Too fast",
    210     "Slow down little bit",
    211   ];
    212   const slowed = slow[Math.floor(Math.random() * slow.length)];
    213   return msg.followUp({
    214     embeds: [
    215       new EmbedBuilder()
    216         .setColor("Random")
    217         .setTimestamp()
    218         .setTitle(slowed)
    219         .setDescription(
    220           `Wait **${timer(
    221             time_left
    222           )}** to use the command again!\nThe default cooldown is **${timer(
    223             defaults
    224           )}**`
    225         ),
    226     ],
    227   });
    228 }
    229 const s = 1000;
    230 const m = s * 60;
    231 const h = m * 60;
    232 const d = h * 24;
    233 const mn = d * 30;
    234 const w = d * 7;
    235 const y = d * 365.25;
    236 
    237 /**
    238  * @param {String|Number} val
    239  * @param {Object} [options]
    240  * @throws {Error} throw an error if val is not a non-empty string or a number
    241  * @return {String|Number}
    242  */
    243 
    244 function ms(val, options) {
    245   options = options || {};
    246   const type = typeof val;
    247   if (type === "string" && val.length > 0) {
    248     return parse(val);
    249   } else if (type === "number" && isFinite(val)) {
    250     return options.long ? fmtLong(val) : fmtShort(val);
    251   }
    252   throw new Error(
    253     "val is not a non-empty string or a valid number. val=" +
    254       JSON.stringify(val)
    255   );
    256 }
    257 
    258 /**
    259  * @param {String} str
    260  * @return {Number}
    261  */
    262 
    263 function parse(str) {
    264   str = String(str);
    265   if (str.length > 100) {
    266     return;
    267   }
    268   const match =
    269     /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|months?|mths|mn|years?|yrs?|y)?$/i.exec(
    270       str
    271     );
    272   if (!match) {
    273     return;
    274   }
    275   const n = parseFloat(match[1]);
    276   const type = (match[2] || "ms").toLowerCase();
    277   switch (type) {
    278     case "years":
    279     case "year":
    280     case "yrs":
    281     case "yr":
    282     case "y":
    283       return n * y;
    284     case "month":
    285     case "months":
    286     case "mth":
    287     case "mths":
    288       return n * mn;
    289     case "weeks":
    290     case "week":
    291     case "w":
    292       return n * w;
    293     case "days":
    294     case "day":
    295     case "d":
    296       return n * d;
    297     case "hours":
    298     case "hour":
    299     case "hrs":
    300     case "hr":
    301     case "h":
    302       return n * h;
    303     case "minutes":
    304     case "minute":
    305     case "mins":
    306     case "min":
    307     case "m":
    308       return n * m;
    309     case "seconds":
    310     case "second":
    311     case "secs":
    312     case "sec":
    313     case "s":
    314       return n * s;
    315     case "milliseconds":
    316     case "millisecond":
    317     case "msecs":
    318     case "msec":
    319     case "ms":
    320       return n;
    321     default:
    322       return undefined;
    323   }
    324 }
    325 
    326 /**
    327  * Short format for `ms`.
    328  *
    329  * @param {Number} ms
    330  * @return {String}
    331  * @api private
    332  */
    333 
    334 function fmtShort(ms) {
    335   const msAbs = Math.abs(ms);
    336   if (msAbs >= mn) {
    337     return Math.round(ms / mn) + "mo";
    338   }
    339   if (msAbs >= w) {
    340     return Math.round(ms / w) + "w";
    341   }
    342   if (msAbs >= d) {
    343     return Math.round(ms / d) + "d";
    344   }
    345   if (msAbs >= h) {
    346     return Math.round(ms / h) + "h";
    347   }
    348   if (msAbs >= m) {
    349     return Math.round(ms / m) + "m";
    350   }
    351   if (msAbs >= s) {
    352     return Math.round(ms / s) + "s";
    353   }
    354   return ms + "ms";
    355 }
    356 
    357 /**
    358  * @param {Number} ms
    359  * @return {String}
    360  */
    361 
    362 function fmtLong(ms) {
    363   const msAbs = Math.abs(ms);
    364   if (msAbs >= mn) {
    365     return plural(ms, msAbs, mn, "month");
    366   }
    367   if (msAbs >= w) {
    368     return plural(ms, msAbs, w, "week");
    369   }
    370   if (msAbs >= d) {
    371     return plural(ms, msAbs, d, "day");
    372   }
    373   if (msAbs >= h) {
    374     return plural(ms, msAbs, h, "hour");
    375   }
    376   if (msAbs >= m) {
    377     return plural(ms, msAbs, m, "minute");
    378   }
    379   if (msAbs >= s) {
    380     return plural(ms, msAbs, s, "second");
    381   }
    382   return ms + " ms";
    383 }
    384 function plural(ms, msAbs, nz, name) {
    385   const isPlural = msAbs >= nz * 1.5;
    386   return Math.round(ms / nz) + " " + name + (isPlural ? "s" : "");
    387 }
    388 async function confirmation(message, author, validReactions, time = 60000) {
    389   try {
    390     for (const reaction of validReactions) await message.react(reaction);
    391     const filter = (reaction, user) =>
    392       validReactions.includes(reaction.emoji.name) && user.id === author.id;
    393 
    394     return message
    395       .awaitReactions({ filter, max: 1, time: time })
    396       .then(collected => collected.first() && collected.first().emoji.name);
    397   } catch (_) {}
    398 }
    399 function selectRandom(array = []) {
    400   return array[Math.floor(Math.random() * array.length)];
    401 }
    402 function getAllTextFromEmbed(embed) {
    403   let text = "";
    404   function getTime(now) {
    405     const date = new Date(now);
    406     const escape = value => `0${value}`.slice(-2);
    407     const ampm = date.getHours() >= 12 ? "PM" : "AM";
    408 
    409     return `${date.getMonth()}/${date.getDate()}/${date.getFullYear()} at ${escape(
    410       date.getHours()
    411     )}:${escape(date.getMinutes())}:${escape(date.getSeconds())}${ampm}`;
    412   }
    413 
    414   if (embed.title) {
    415     text += `**${embed.title
    416       .replace(/(https?:\/\/)?discord\.gg\/(\w+)/g, "Invite")
    417       .replace(/\[(.*)\]\((.*)\)/g, "Hyper link")}**`;
    418   }
    419   if (embed.description) {
    420     text += `\n${embed.description
    421       .replace(/(https?:\/\/)?discord\.gg\/(\w+)/g, "Invite")
    422       .replace(/\[(.*)\]\((.*)\)/g, "Hyper link")}`;
    423   }
    424   if (embed.fields) {
    425     text += "\n";
    426     for (const field of embed.fields) {
    427       text += `\n**${field.name
    428         .replace(/(https?:\/\/)?discord\.gg\/(\w+)/g, "Invite")
    429         .replace(/\[(.*)\]\((.*)\)/g, "Hyper link")}**\n ${field.value
    430         .replace(/(https?:\/\/)?discord\.gg\/(\w+)/g, "Invite")
    431         .replace(/\[(.*)\]\((.*)\)/g, "Hyper link")}`;
    432     }
    433   }
    434   if (embed.footer) {
    435     let field = `\n\n**${embed.footer.text
    436       .replace(/(https?:\/\/)?discord\.gg\/(\w+)/g, "Invite")
    437       .replace(/\[(.*)\]\((.*)\)/g, "Hyper link")}`;
    438 
    439     if (embed.timestamp) {
    440       const time =
    441         embed.timestamp instanceof Date
    442           ? getTime(embed.timestamp.getTime())
    443           : embed.timestamp;
    444       field += `at ${time}`;
    445     }
    446 
    447     text += `${field}**`;
    448   }
    449 
    450   return text;
    451 }
    452 function clean(text) {
    453   if (typeof text === "string") {
    454     return text
    455       .replace(/`/g, "`" + String.fromCharCode(8203))
    456       .replace(/@/g, "@" + String.fromCharCode(8203));
    457   } else {
    458     return text;
    459   }
    460 }
    461 function tips(interaction, client) {
    462   const all = [
    463     "You can report bugs by using `/report` and send a suggestion by `/suggest` !",
    464     "If a gun isn't there, please be paitent and wait for the us to get the stats",
    465     "We all recruiting for Javascript bot developers (Total: 4) Please DM the bot for more info",
    466   ];
    467   const ran = Math.floor(Math.random() * 50) + 2;
    468   const rTip = all[Math.floor(Math.random() * all.length)];
    469   if (ran <= 11) {
    470     interaction.channel.send({
    471       embeds: [
    472         new EmbedBuilder()
    473           .setTitle("Tips")
    474           .setColor(client.color)
    475           .setDescription(`**๐Ÿ’ก Did you know**\n${rTip}`)
    476           .setFooter({
    477             text: `Made by ${client.author}`,
    478             iconURL: client.user.displayAvatarURL({ dynamic: true }),
    479           })
    480           .setTimestamp()
    481           .setURL(client.web),
    482       ],
    483     });
    484   }
    485 }
    486 function inviteLink(client_id) {
    487   return `https://discord.com/oauth2/authorize?client_id=${client_id}&permissions=1512097384560&scope=bot%20applications.commands`;
    488 }
    489 function buttons(client) {
    490   const invite = new ButtonBuilder()
    491     .setLabel("Invite the bot!")
    492     .setStyle(ButtonStyle.Link)
    493     .setEmoji("896527406100283462")
    494     .setURL(inviteLink(client.user.id));
    495   const support = new ButtonBuilder()
    496     .setLabel("Support Server")
    497     .setStyle(ButtonStyle.Link)
    498     .setEmoji("867093614403256350")
    499     .setURL(client.invite);
    500   const website = new ButtonBuilder()
    501     .setLabel("Website")
    502     .setStyle(ButtonStyle.Link)
    503     .setEmoji("๐Ÿ–ฅ")
    504     .setURL(client.web);
    505   const youtube = new ButtonBuilder()
    506     .setLabel("YouTube")
    507     .setStyle(ButtonStyle.Link)
    508     .setEmoji("841186450497339412")
    509     .setURL("https://www.youtube.com/@night0721");
    510   const kofi = new ButtonBuilder()
    511     .setLabel("Ko-fi")
    512     .setStyle(ButtonStyle.Link)
    513     .setEmoji("900590344364757013")
    514     .setURL("https://ko-fi.com/cathteam");
    515   const row = new ActionRowBuilder().addComponents(
    516     invite,
    517     support,
    518     website,
    519     youtube,
    520     kofi
    521   );
    522   return [row];
    523 }
    524 const colorize = (...args) => ({
    525   black: `\x1b[30m${args.join(" ")}`,
    526   red: `\x1b[31m${args.join(" ")}`,
    527   green: `\x1b[32m${args.join(" ")}`,
    528   yellow: `\x1b[33m${args.join(" ")}`,
    529   blue: `\x1b[34m${args.join(" ")}`,
    530   magenta: `\x1b[35m${args.join(" ")}`,
    531   cyan: `\x1b[36m${args.join(" ")}`,
    532   white: `\x1b[37m${args.join(" ")}`,
    533   bgBlack: `\x1b[40m${args.join(" ")}\x1b[0m`,
    534   bgRed: `\x1b[41m${args.join(" ")}\x1b[0m`,
    535   bgGreen: `\x1b[42m${args.join(" ")}\x1b[0m`,
    536   bgYellow: `\x1b[43m${args.join(" ")}\x1b[0m`,
    537   bgBlue: `\x1b[44m${args.join(" ")}\x1b[0m`,
    538   bgMagenta: `\x1b[45m${args.join(" ")}\x1b[0m`,
    539   bgCyan: `\x1b[46m${args.join(" ")}\x1b[0m`,
    540   bgWhite: `\x1b[47m${args.join(" ")}\x1b[0m`,
    541 });
    542 const leven = (te, t) => {
    543   if (!te.length) return t.length;
    544   if (!t.length) return te.length;
    545   const arr = [];
    546   for (let i = 0; i <= t.length; i++) {
    547     arr[i] = [i];
    548     for (let j = 1; j <= te.length; j++) {
    549       arr[i][j] =
    550         i === 0
    551           ? j
    552           : Math.min(
    553               arr[i - 1][j] + 1,
    554               arr[i][j - 1] + 1,
    555               arr[i - 1][j - 1] + (te[j - 1] === t[i - 1] ? 0 : 1)
    556             );
    557     }
    558   }
    559   return arr[t.length][te.length];
    560 };
    561 function chunk(arr, size) {
    562   Array.from({ length: Math.ceil(arr.length / size) }, (v, i) => {
    563     arr.slice(i * size, i * size + size);
    564     return arr;
    565   });
    566 }
    567 function progressBar(value, maxValue, size) {
    568   const percentage = value / maxValue;
    569   const progress = Math.round(size * percentage);
    570   const emptyProgress = size - progress;
    571   const progressText = "โ–‡".repeat(progress);
    572   const emptyProgressText = "โ€”".repeat(emptyProgress);
    573   const percentageText = Math.round(percentage * 100) + "%";
    574 
    575   const Bar = progressText + emptyProgressText;
    576   return { Bar, percentageText };
    577 }
    578 function prettyMs(milliseconds, options = {}) {
    579   const pluralize = (word, count) => (count === 1 ? word : `${word}s`);
    580   const SECOND_ROUNDING_EPSILON = 0.0000001;
    581   if (!Number.isFinite(milliseconds)) {
    582     throw new TypeError("Expected a finite number");
    583   }
    584 
    585   if (options.colonNotation) {
    586     options.compact = false;
    587     options.formatSubMilliseconds = false;
    588     options.separateMilliseconds = false;
    589     options.verbose = false;
    590   }
    591 
    592   if (options.compact) {
    593     options.secondsDecimalDigits = 0;
    594     options.millisecondsDecimalDigits = 0;
    595   }
    596 
    597   const result = [];
    598 
    599   const floorDecimals = (value, decimalDigits) => {
    600     const flooredInterimValue = Math.floor(
    601       value * 10 ** decimalDigits + SECOND_ROUNDING_EPSILON
    602     );
    603     const flooredValue = Math.round(flooredInterimValue) / 10 ** decimalDigits;
    604     return flooredValue.toFixed(decimalDigits);
    605   };
    606 
    607   const add = (value, long, short, valueString) => {
    608     if (
    609       (result.length === 0 || !options.colonNotation) &&
    610       value === 0 &&
    611       !(options.colonNotation && short === "m")
    612     ) {
    613       return;
    614     }
    615 
    616     valueString = (valueString || value || "0").toString();
    617     let prefix;
    618     let suffix;
    619     if (options.colonNotation) {
    620       prefix = result.length > 0 ? ":" : "";
    621       suffix = "";
    622       const wholeDigits = valueString.includes(".")
    623         ? valueString.split(".")[0].length
    624         : valueString.length;
    625       const minLength = result.length > 0 ? 2 : 1;
    626       valueString =
    627         "0".repeat(Math.max(0, minLength - wholeDigits)) + valueString;
    628     } else {
    629       prefix = "";
    630       suffix = options.verbose ? " " + pluralize(long, value) : short;
    631     }
    632 
    633     result.push(prefix + valueString + suffix);
    634   };
    635 
    636   const parsed = parseMilliseconds(milliseconds);
    637 
    638   add(Math.trunc(parsed.days / 365), "year", "y");
    639   add(parsed.days % 365, "day", "d");
    640   add(parsed.hours, "hour", "h");
    641   add(parsed.minutes, "minute", "m");
    642 
    643   if (
    644     options.separateMilliseconds ||
    645     options.formatSubMilliseconds ||
    646     (!options.colonNotation && milliseconds < 1000)
    647   ) {
    648     add(parsed.seconds, "second", "s");
    649     if (options.formatSubMilliseconds) {
    650       add(parsed.milliseconds, "millisecond", "ms");
    651       add(parsed.microseconds, "microsecond", "ยตs");
    652       add(parsed.nanoseconds, "nanosecond", "ns");
    653     } else {
    654       const millisecondsAndBelow =
    655         parsed.milliseconds +
    656         parsed.microseconds / 1000 +
    657         parsed.nanoseconds / 1e6;
    658 
    659       const millisecondsDecimalDigits =
    660         typeof options.millisecondsDecimalDigits === "number"
    661           ? options.millisecondsDecimalDigits
    662           : 0;
    663 
    664       const roundedMiliseconds =
    665         millisecondsAndBelow >= 1
    666           ? Math.round(millisecondsAndBelow)
    667           : Math.ceil(millisecondsAndBelow);
    668 
    669       const millisecondsString = millisecondsDecimalDigits
    670         ? millisecondsAndBelow.toFixed(millisecondsDecimalDigits)
    671         : roundedMiliseconds;
    672 
    673       add(
    674         Number.parseFloat(millisecondsString, 10),
    675         "millisecond",
    676         "ms",
    677         millisecondsString
    678       );
    679     }
    680   } else {
    681     const seconds = (milliseconds / 1000) % 60;
    682     const secondsDecimalDigits =
    683       typeof options.secondsDecimalDigits === "number"
    684         ? options.secondsDecimalDigits
    685         : 1;
    686     const secondsFixed = floorDecimals(seconds, secondsDecimalDigits);
    687     const secondsString = options.keepDecimalsOnWholeSeconds
    688       ? secondsFixed
    689       : secondsFixed.replace(/\.0+$/, "");
    690     add(Number.parseFloat(secondsString, 10), "second", "s", secondsString);
    691   }
    692 
    693   if (result.length === 0) {
    694     return "0" + (options.verbose ? " milliseconds" : "ms");
    695   }
    696 
    697   if (options.compact) {
    698     return result[0];
    699   }
    700 
    701   if (typeof options.unitCount === "number") {
    702     const separator = options.colonNotation ? "" : " ";
    703     return result.slice(0, Math.max(options.unitCount, 1)).join(separator);
    704   }
    705 
    706   return options.colonNotation ? result.join("") : result.join(" ");
    707 }
    708 function parseMilliseconds(milliseconds) {
    709   if (typeof milliseconds !== "number") {
    710     throw new TypeError("Expected a number");
    711   }
    712 
    713   return {
    714     days: Math.trunc(milliseconds / 86400000),
    715     hours: Math.trunc(milliseconds / 3600000) % 24,
    716     minutes: Math.trunc(milliseconds / 60000) % 60,
    717     seconds: Math.trunc(milliseconds / 1000) % 60,
    718     milliseconds: Math.trunc(milliseconds) % 1000,
    719     microseconds: Math.trunc(milliseconds * 1000) % 1000,
    720     nanoseconds: Math.trunc(milliseconds * 1e6) % 1000,
    721   };
    722 }
    723 const default_opts = {
    724   hoursPerDay: 24,
    725   daysPerWeek: 7,
    726   weeksPerMonth: 4,
    727   monthsPerYear: 12,
    728   daysPerYear: 365.25,
    729 };
    730 const UNIT_MAP = {
    731   ms: ["ms", "milli", "millisecond", "milliseconds"],
    732   s: ["s", "sec", "secs", "second", "seconds"],
    733   m: ["m", "min", "mins", "minute", "minutes"],
    734   h: ["h", "hr", "hrs", "hour", "hours"],
    735   d: ["d", "day", "days"],
    736   w: ["w", "week", "weeks"],
    737   mth: ["mon", "mth", "mths", "month", "months"],
    738   y: ["y", "yr", "yrs", "year", "years"],
    739 };
    740 
    741 /**
    742  * Parse a timestring
    743  *
    744  * @param   {string} string
    745  * @param   {string} returnUnit
    746  * @param   {Object} opts
    747  * @returns {number}
    748  */
    749 
    750 function parseTimestring(string, returnUnit, opts) {
    751   opts = Object.assign({}, default_opts, opts || {});
    752 
    753   let totalSeconds = 0;
    754   const unitValues = getUnitValues(opts);
    755   const groups = string
    756     .toLowerCase()
    757     .replace(/[^.\w+-]+/g, "")
    758     .match(/[-+]?[0-9.]+[a-z]+/g);
    759 
    760   if (groups === null) {
    761     throw new Error(`The string [${string}] could not be parsed by timestring`);
    762   }
    763 
    764   groups.forEach(group => {
    765     const value = group.match(/[0-9.]+/g)[0];
    766     const unit = group.match(/[a-z]+/g)[0];
    767 
    768     totalSeconds += getSeconds(value, unit, unitValues);
    769   });
    770 
    771   if (returnUnit) {
    772     return convert(totalSeconds, returnUnit, unitValues);
    773   }
    774 
    775   return totalSeconds;
    776 }
    777 function getUnitValues(opts) {
    778   const unitValues = {
    779     ms: 0.001,
    780     s: 1,
    781     m: 60,
    782     h: 3600,
    783   };
    784 
    785   unitValues.d = opts.hoursPerDay * unitValues.h;
    786   unitValues.w = opts.daysPerWeek * unitValues.d;
    787   unitValues.mth = (opts.daysPerYear / opts.monthsPerYear) * unitValues.d;
    788   unitValues.y = opts.daysPerYear * unitValues.d;
    789 
    790   return unitValues;
    791 }
    792 function getUnitKey(unit) {
    793   for (const key of Object.keys(UNIT_MAP)) {
    794     if (UNIT_MAP[key].indexOf(unit) > -1) {
    795       return key;
    796     }
    797   }
    798   throw new Error(`The unit [${unit}] is not supported by timestring`);
    799 }
    800 function getSeconds(value, unit, unitValues) {
    801   return value * unitValues[getUnitKey(unit)];
    802 }
    803 function convert(value, unit, unitValues) {
    804   return value / unitValues[getUnitKey(unit)];
    805 }
    806 
    807 module.exports = {
    808   rndint,
    809   parseDate,
    810   parseShortDate,
    811   timer,
    812   sleep,
    813   toHHMMSS,
    814   fixPermissions,
    815   trimArray,
    816   formatPerms,
    817   checkDays,
    818   format,
    819   fixFeatures,
    820   cooldown,
    821   ms,
    822   confirmation,
    823   selectRandom,
    824   getAllTextFromEmbed,
    825   clean,
    826   tips,
    827   inviteLink,
    828   buttons,
    829   colorize,
    830   leven,
    831   chunk,
    832   progressBar,
    833   parseTimestring,
    834   prettyMs,
    835 };