misc.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /**
  2. * @module lib/misc
  3. * This module contains miscellaneous functions that don't fit in another category - [see the documentation for more info](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#misc)
  4. */
  5. import type { ListWithLength, Prettify, Stringifiable } from "./types.js";
  6. /** Which plural form to use when auto-pluralizing */
  7. export type PluralType = "auto" | "-s" | "-ies";
  8. /**
  9. * Automatically pluralizes the given string an `-s` or `-ies` to the passed {@linkcode term}, if {@linkcode num} is not equal to 1.
  10. * By default, words ending in `-y` will have it replaced with `-ies`, and all other words will simply have `-s` appended.
  11. * If {@linkcode num} resolves to NaN or the {@linkcode pluralType} is wrong, it defaults to the {@linkcode pluralType} `auto` and sets {@linkcode num} to 2.
  12. * @param term The term, written in singular form, to auto-convert to plural form
  13. * @param num A number, or list-like value that has either a `length`, `count` or `size` property, like an array, Map or discord.js Collection - does not support iterables, they need to be converted to an array first
  14. * @param pluralType Which plural form to use when auto-pluralizing. Defaults to `"auto"`, which removes the last char and uses `-ies` for words ending in `y` and simply appends `-s` for all other words
  15. */
  16. export function autoPlural(word: Stringifiable, num: number | ListWithLength, pluralType: PluralType = "auto"): string {
  17. if(typeof num !== "number") {
  18. if("length" in num)
  19. num = num.length;
  20. else if("size" in num)
  21. num = num.size;
  22. else if("count" in num)
  23. num = num.count;
  24. }
  25. const pType: Exclude<PluralType, "auto"> = pluralType === "auto"
  26. ? String(word).endsWith("y") ? "-ies" : "-s"
  27. : pluralType;
  28. if(!["-s", "-ies"].includes(pType) || isNaN(num))
  29. num = 2;
  30. switch(pType) {
  31. case "-s":
  32. return `${word}${num === 1 ? "" : "s"}`;
  33. case "-ies":
  34. return `${String(word).slice(0, -1)}${num === 1 ? "y" : "ies"}`;
  35. default:
  36. return String(word);
  37. }
  38. }
  39. /**
  40. * Inserts the passed values into a string at the respective placeholders.
  41. * The placeholder format is `%n`, where `n` is the 1-indexed argument number.
  42. * @param input The string to insert the values into
  43. * @param values The values to insert, in order, starting at `%1`
  44. */
  45. export function insertValues(input: string, ...values: Stringifiable[]): string {
  46. return input.replace(/%\d/gm, (match) => {
  47. const argIndex = Number(match.substring(1)) - 1;
  48. return (values[argIndex] ?? match)?.toString();
  49. });
  50. }
  51. /** Pauses async execution for the specified time in ms */
  52. export function pauseFor(time: number): Promise<void> {
  53. return new Promise<void>((res) => {
  54. setTimeout(() => res(), time);
  55. });
  56. }
  57. /** Options for the `fetchAdvanced()` function */
  58. export type FetchAdvancedOpts = Prettify<
  59. Partial<{
  60. /** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
  61. timeout: number;
  62. }> & RequestInit
  63. >;
  64. /** Calls the fetch API with special options like a timeout */
  65. export async function fetchAdvanced(input: string | RequestInfo | URL, options: FetchAdvancedOpts = {}): Promise<Response> {
  66. const { timeout = 10000 } = options;
  67. const ctl = new AbortController();
  68. const { signal, ...restOpts } = options;
  69. signal?.addEventListener("abort", () => ctl.abort());
  70. let sigOpts: Partial<RequestInit> = {},
  71. id: ReturnType<typeof setTimeout> | undefined = undefined;
  72. if(timeout >= 0) {
  73. id = setTimeout(() => ctl.abort(), timeout);
  74. sigOpts = { signal: ctl.signal };
  75. }
  76. try {
  77. const res = await fetch(input, {
  78. ...restOpts,
  79. ...sigOpts,
  80. });
  81. typeof id !== "undefined" && clearTimeout(id);
  82. return res;
  83. }
  84. catch(err) {
  85. typeof id !== "undefined" && clearTimeout(id);
  86. throw new Error("Error while calling fetch", { cause: err });
  87. }
  88. }
  89. /**
  90. * A ValueGen value is either its type, a promise that resolves to its type, or a function that returns its type, either synchronous or asynchronous.
  91. * ValueGen allows for the utmost flexibility when applied to any type, as long as {@linkcode consumeGen()} is used to get the final value.
  92. * @template TValueType The type of the value that the ValueGen should yield
  93. */
  94. export type ValueGen<TValueType> = TValueType | Promise<TValueType> | (() => TValueType | Promise<TValueType>);
  95. /**
  96. * Turns a {@linkcode ValueGen} into its final value.
  97. * @template TValueType The type of the value that the ValueGen should yield
  98. */
  99. export async function consumeGen<TValueType>(valGen: ValueGen<TValueType>): Promise<TValueType> {
  100. return await (typeof valGen === "function"
  101. ? (valGen as (() => Promise<TValueType> | TValueType))()
  102. : valGen
  103. )as TValueType;
  104. }
  105. /**
  106. * A StringGen value is either a string, anything that can be converted to a string, or a function that returns one of the previous two, either synchronous or asynchronous, or a promise that returns a string.
  107. * StringGen allows for the utmost flexibility when dealing with strings, as long as {@linkcode consumeStringGen()} is used to get the final string.
  108. */
  109. export type StringGen = ValueGen<Stringifiable>;
  110. /**
  111. * Turns a {@linkcode StringGen} into its final string value.
  112. * @template TStrUnion The union of strings that the StringGen should yield - this allows for finer type control compared to {@linkcode consumeGen()}
  113. */
  114. export async function consumeStringGen<TStrUnion extends string>(strGen: StringGen): Promise<TStrUnion> {
  115. return (
  116. typeof strGen === "string"
  117. ? strGen
  118. : String(
  119. typeof strGen === "function"
  120. ? await strGen()
  121. : strGen
  122. )
  123. ) as TStrUnion;
  124. }