util.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import { GuildMember, User } from "discord.js";
  2. import { getRepository, In } from "typeorm";
  3. import { KnownUser } from "@shared/db/entity/KnownUser";
  4. import { Option, } from "@shared/common/async_utils";
  5. import humanizeDuration from "humanize-duration";
  6. import * as t from "io-ts";
  7. import YAML from "yaml";
  8. import { PathReporter } from "io-ts/PathReporter";
  9. const VALID_EXTENSIONS = new Set([
  10. "png",
  11. "jpg",
  12. "jpeg",
  13. "bmp",
  14. ]);
  15. export type EsModuleClass<T> = { prototype: T; new(...params: unknown[]): T; };
  16. export function isModuleClass<T>(obj: unknown) : obj is EsModuleClass<T> {
  17. return Object.prototype.hasOwnProperty.call(obj, "prototype");
  18. }
  19. export function isDevelopment(): boolean {
  20. return process.env.NODE_ENV == "dev";
  21. }
  22. export function parseYaml<A>(type: t.Type<A>, yaml: string): Option<{ result: A }, { errors: string[] }> {
  23. const t = type.decode(YAML.parse(yaml));
  24. if (t._tag == "Left"){
  25. return { ok: false, errors: PathReporter.report(t) };
  26. } else {
  27. return { ok: true, result: t.right };
  28. }
  29. }
  30. export function isValidImage(fileName?: string | null): boolean {
  31. if (!fileName)
  32. return false;
  33. const extPosition = fileName.lastIndexOf(".");
  34. if (extPosition < 0)
  35. return false;
  36. const ext = fileName.substring(extPosition + 1).toLowerCase();
  37. return VALID_EXTENSIONS.has(ext);
  38. }
  39. export async function isAuthorisedAsync(member: GuildMember | User | null | undefined): Promise<boolean> {
  40. if (!member)
  41. return false;
  42. const repo = getRepository(KnownUser);
  43. const user = await repo.findOne({
  44. where: { userID: member.id },
  45. select: ["canModerate"]
  46. });
  47. if (user && user.canModerate)
  48. return true;
  49. if (member instanceof GuildMember) {
  50. const role = await repo.findOne({
  51. select: ["userID"],
  52. where: {
  53. userID: In(member.roles.cache.keyArray()),
  54. canModerate: true
  55. }
  56. });
  57. if (role)
  58. return true;
  59. }
  60. return false;
  61. }
  62. export function compareNumbers<T>(prop: (o: T) => number) {
  63. return (a: T, b: T): -1 | 0 | 1 => {
  64. const ap = prop(a);
  65. const bp = prop(b);
  66. if (ap < bp)
  67. return 1;
  68. else if (ap > bp)
  69. return -1;
  70. return 0;
  71. };
  72. }
  73. export type Dict<TVal> = { [key: string]: TVal };
  74. export function getNumberEnums<E>(e : Record<keyof E, number>) : number[] {
  75. return Object.keys(e).filter(k => typeof e[k as keyof E] === "number").map(k => e[k as keyof E]);
  76. }
  77. export function formatString(str: string, vars: Record<string, string>): string {
  78. return Object.keys(vars).filter(s => Object.prototype.hasOwnProperty.call(vars, s)).reduce((s, cur) => s.replace(`{${cur}}`, vars[cur]), str);
  79. }
  80. export function parseArgs(str: string): string[] {
  81. const result: string[] = [];
  82. let quoteMode = false;
  83. let escapeNext = false;
  84. let buffer = "";
  85. for (const char of str) {
  86. if (!escapeNext)
  87. if (char == "\"") {
  88. if (!quoteMode) {
  89. quoteMode = true;
  90. } else {
  91. quoteMode = false;
  92. result.push(buffer);
  93. buffer = "";
  94. }
  95. continue;
  96. } else if (char == "\\") {
  97. escapeNext = true;
  98. continue;
  99. } else if (/\s/.test(char)) {
  100. if (buffer.length == 0)
  101. continue;
  102. if (!quoteMode) {
  103. result.push(buffer);
  104. buffer = "";
  105. continue;
  106. }
  107. }
  108. buffer += char;
  109. escapeNext = false;
  110. }
  111. if (buffer.length != 0)
  112. result.push(buffer);
  113. return result;
  114. }
  115. export const UNIT_MEASURES: humanizeDuration.UnitMeasuresOptions = {
  116. ms: 1,
  117. s: 1000,
  118. m: 60 * 1000,
  119. h: 60 * 60 * 1000,
  120. d: 24 * 60 * 60 * 1000,
  121. w: 7 * 24 * 60 * 60 * 1000,
  122. mo: 30 * 24 * 60 * 60 * 1000,
  123. y: 365 * 24 * 60 * 60 * 1000
  124. };
  125. const durationUnits = UNIT_MEASURES as Record<string, number>;
  126. const durationPattern = new RegExp(`(\\d+(?:[.,]?\\d+)?)\\s*(${Object.keys(durationUnits).sort((a, b) => b.length - a.length).join("|")})`, "g");
  127. export function parseDuration(s: string): number | undefined {
  128. if (!s)
  129. return undefined;
  130. s = s.trim();
  131. let buffer = s;
  132. let result = 0;
  133. let match;
  134. while ((match = durationPattern.exec(s))) {
  135. buffer = buffer.replace(match[0], "").trim();
  136. result += +match[1] * durationUnits[match[2]];
  137. }
  138. if (buffer.length != 0)
  139. return undefined;
  140. return result;
  141. }