diff --git a/.env.example b/.env.example index cf10e0b8..3a7c9181 100644 --- a/.env.example +++ b/.env.example @@ -33,3 +33,10 @@ POINT_LIMITER_IN_MINUTES= VAR_DETECT_LIMIT=1800000 JUST_ASK_DETECT_LIMIT=86400000 + +# Required for onboarding feature +NEW_USER_ROLE= +ONBOARDING_CHANNEL= +JOIN_LOG_CHANNEL= +INTRO_CHANNEL= +INTRO_ROLE= diff --git a/.eslintrc.cjs b/.eslintrc.cjs index b2c10955..a621fa36 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,12 +1,10 @@ +const { createConfig } = require('eslint-config-galex/dist/createConfig'); +const { getDependencies } = require('eslint-config-galex/dist/getDependencies'); const { - createConfig, - getDependencies, -} = require('eslint-config-galex/src/createConfig'); -const { - createTSOverride, -} = require('eslint-config-galex/src/overrides/typescript'); + createTypeScriptOverride, +} = require('eslint-config-galex/dist/overrides/typescript'); -const tsOverride = createTSOverride({ +const tsOverride = createTypeScriptOverride({ ...getDependencies(), rules: { '@typescript-eslint/no-floating-promises': 0, diff --git a/package.json b/package.json index 755ee6e0..6586a273 100644 --- a/package.json +++ b/package.json @@ -23,41 +23,41 @@ "author": "", "license": "ISC", "dependencies": { - "@mdn/browser-compat-data": "5.0.3", - "@sentry/node": "7.0.0", + "@mdn/browser-compat-data": "5.1.3", + "@sentry/node": "7.3.1", "compare-versions": "4.1.3", "cross-env": "^7.0.3", "date-fns": "2.28.0", - "discord.js": "13.7.0", + "discord.js": "13.8.1", "dom-parser": "0.1.6", "domyno": "1.0.1", "fuse.js": "6.6.2", "html-entities": "2.3.3", "lodash-es": "4.17.21", - "mongoose": "6.3.5", + "mongoose": "6.4.1", "node-cache": "5.1.2", - "node-fetch": "3.2.5", + "node-fetch": "3.2.6", "node-html-parser": "5.3.3", - "ts-node": "10.8.0", - "typescript": "4.7.2" + "ts-node": "10.8.1", + "typescript": "4.7.4" }, "devDependencies": { - "@sentry/types": "7.0.0", + "@sentry/types": "7.3.1", "@types/dom-parser": "0.1.1", "@types/html-entities": "1.3.4", - "@types/jest": "28.1.0", + "@types/jest": "28.1.3", "@types/mongoose": "5.11.97", - "@types/node": "17.0.39", + "@types/node": "18.0.0", "@types/node-fetch": "3.0.3", "dotenv": "16.0.1", - "eslint": "8.16.0", - "eslint-config-galex": "4.1.3", + "eslint": "8.18.0", + "eslint-config-galex": "4.1.4", "husky": "8.0.1", - "jest": "28.1.0", - "lint-staged": "13.0.0", - "nodemon": "2.0.16", - "prettier": "2.6.2", - "ts-jest": "28.0.4" + "jest": "28.1.1", + "lint-staged": "13.0.3", + "nodemon": "2.0.18", + "prettier": "2.7.1", + "ts-jest": "28.0.5" }, "husky": { "hooks": { diff --git a/src/Intl.d.ts b/src/Intl.d.ts deleted file mode 100644 index 80305eb0..00000000 --- a/src/Intl.d.ts +++ /dev/null @@ -1,52 +0,0 @@ - -// modified from https://github.com/wessberg/intl-list-format/blob/master/src/typings.d.ts -declare namespace Intl { - function getCanonicalLocales(locales: string | string[] | undefined): string[]; - - type Locale = string; - type Locales = Locale[]; - type Type = "conjunction" | "disjunction" | "unit"; - type Style = "long" | "short" | "narrow"; - type LocaleMatcher = "lookup" | "best fit"; - - type ListFormatOptions = { - type: Type; - style: Style; - localeMatcher: LocaleMatcher; - } - - type ResolvedListFormatOptions = { - type: Type; - style: Style; - locale: Locale; - } - - type ElementPartition = { - type: "element"; - value: ListPartition[] | string; - } - - type ListPartitionBase = { - value: string; - } - - type LiteralPartition = ListPartitionBase & { - type: "literal"; - } - - type ListPartition = ElementPartition | LiteralPartition; - - type ListPartitions = readonly ListPartition[]; - - class ListFormat { - public constructor(locales?: Locale | Locales | undefined, options?: Partial); - - public static supportedLocalesOf(locales: Locale | Locales, options?: Intl.SupportedLocalesOptions | undefined): Locales; - - public format(list?: Iterable): string; - - public formatToParts(list?: Iterable): ListPartitions; - - public resolvedOptions(): ResolvedListFormatOptions; - } -} diff --git a/src/env.ts b/src/env.ts index 5a86a6ff..92ec7990 100644 --- a/src/env.ts +++ b/src/env.ts @@ -3,36 +3,42 @@ export const IS_PROD = process.env.NODE_ENV === 'production'; export const SERVER_ID = IS_PROD ? '434487340535382016' : process.env.SERVER_ID; -export const {DUMMY_TOKEN} = process.env; -export const {DISCORD_TOKEN} = process.env; -export const {REPO_LINK} = process.env; - -export const {MOD_CHANNEL} = process.env; -export const {NUMBER_OF_ALLOWED_MESSAGES} = process.env; -export const {CACHE_REVALIDATION_IN_SECONDS} = process.env; -export const {FINAL_CACHE_EXPIRATION_IN_SECONDS} = process.env; - -export const {JOB_POSTINGS_CHANNEL} = process.env; -export const {AWAIT_MESSAGE_TIMEOUT} = process.env; -export const {MINIMAL_COMPENSATION} = process.env; -export const {MINIMAL_AMOUNT_OF_WORDS} = process.env; -export const {POST_LIMITER_IN_HOURS} = process.env; - -export const {API_CACHE_ENTRIES_LIMIT} = process.env; -export const {API_CACHE_EXPIRATION_IN_SECONDS} = process.env; -export const {API_CACHE_REVALIDATION_WINDOW_IN_SECONDS} = process.env; - -export const {MONGO_URI} = process.env; -export const {HELPFUL_ROLE_ID} = process.env; -export const {HELPFUL_ROLE_EXEMPT_ID} = process.env; -export const {HELPFUL_ROLE_POINT_THRESHOLD} = process.env; -export const {POINT_DECAY_TIMER} = process.env; -export const {ADMIN_ROLE_ID} = process.env; -export const {MOD_ROLE_ID} = process.env; - -export const {POINT_LIMITER_IN_MINUTES} = process.env; +export const { DUMMY_TOKEN } = process.env; +export const { DISCORD_TOKEN } = process.env; +export const { REPO_LINK } = process.env; + +export const { MOD_CHANNEL } = process.env; +export const { NUMBER_OF_ALLOWED_MESSAGES } = process.env; +export const { CACHE_REVALIDATION_IN_SECONDS } = process.env; +export const { FINAL_CACHE_EXPIRATION_IN_SECONDS } = process.env; + +export const { JOB_POSTINGS_CHANNEL } = process.env; +export const { AWAIT_MESSAGE_TIMEOUT } = process.env; +export const { MINIMAL_COMPENSATION } = process.env; +export const { MINIMAL_AMOUNT_OF_WORDS } = process.env; +export const { POST_LIMITER_IN_HOURS } = process.env; + +export const { API_CACHE_ENTRIES_LIMIT } = process.env; +export const { API_CACHE_EXPIRATION_IN_SECONDS } = process.env; +export const { API_CACHE_REVALIDATION_WINDOW_IN_SECONDS } = process.env; + +export const { MONGO_URI } = process.env; +export const { HELPFUL_ROLE_ID } = process.env; +export const { HELPFUL_ROLE_EXEMPT_ID } = process.env; +export const { HELPFUL_ROLE_POINT_THRESHOLD } = process.env; +export const { POINT_DECAY_TIMER } = process.env; +export const { ADMIN_ROLE_ID } = process.env; +export const { MOD_ROLE_ID } = process.env; + +export const { POINT_LIMITER_IN_MINUTES } = process.env; export const VAR_DETECT_LIMIT = Number.parseInt(process.env.VAR_DETECT_LIMIT) || 1_800_000; export const JUST_ASK_DETECT_LIMIT = Number.parseInt(process.env.JUST_ASK_DETECT_LIMIT) || 86_400_000; + +export const { NEW_USER_ROLE } = process.env; +export const { ONBOARDING_CHANNEL } = process.env; +export const { JOIN_LOG_CHANNEL } = process.env; +export const { INTRO_CHANNEL } = process.env; +export const { INTRO_ROLE } = process.env; diff --git a/src/types.d.ts b/src/types.d.ts index 739f046c..aa246f09 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -3,7 +3,6 @@ import type { Client, CommandInteraction, Guild, - Interaction, } from 'discord.js'; export type CommandDataWithHandler = ChatInputApplicationCommandData & { diff --git a/src/v2/autorespond/code_parsing/hasVarInSource.ts b/src/v2/autorespond/code_parsing/hasVarInSource.ts index 3bd17579..c477bbdd 100644 --- a/src/v2/autorespond/code_parsing/hasVarInSource.ts +++ b/src/v2/autorespond/code_parsing/hasVarInSource.ts @@ -1,4 +1,3 @@ - import ts from 'typescript'; export function hasVarInSource(source: string): boolean { diff --git a/src/v2/autorespond/html_parsing/hasDeprecated.ts b/src/v2/autorespond/html_parsing/hasDeprecated.ts index e9606751..732405a1 100644 --- a/src/v2/autorespond/html_parsing/hasDeprecated.ts +++ b/src/v2/autorespond/html_parsing/hasDeprecated.ts @@ -4,129 +4,135 @@ import { mapʹ } from '../../utils/map.js'; const deprecatedElementsMap = new Map([ [ - "acronym", - "The HTML element allows authors to clearly indicate a sequence of characters that compose an acronym or abbreviation for a word." + 'acronym', + 'The HTML element allows authors to clearly indicate a sequence of characters that compose an acronym or abbreviation for a word.', ], [ - "applet", - "The obsolete HTML Applet Element () embeds a Java applet into the document; this element has been deprecated in favor of object." + 'applet', + 'The obsolete HTML Applet Element () embeds a Java applet into the document; this element has been deprecated in favor of object.', ], [ - "basefont", - "The HTML element is deprecated. It sets a default font face, size, and color for the other elements which are descended from its parent element. With this set, the font's size can then be varied relative to the base size using the font element." + 'basefont', + "The HTML element is deprecated. It sets a default font face, size, and color for the other elements which are descended from its parent element. With this set, the font's size can then be varied relative to the base size using the font element.", ], [ - "bgsound", - "The HTML element is deprecated. It sets up a sound file to play in the background while the page is used; use audio instead." + 'bgsound', + 'The HTML element is deprecated. It sets up a sound file to play in the background while the page is used; use audio instead.', ], [ - "big", - "The HTML deprecated element renders the enclosed text at a font size one level larger than the surrounding text (medium becomes large, for example). The size is capped at the browser's maximum permitted font size." + 'big', + "The HTML deprecated element renders the enclosed text at a font size one level larger than the surrounding text (medium becomes large, for example). The size is capped at the browser's maximum permitted font size.", ], [ - "blink", - "The HTML element is a non-standard element which causes the enclosed text to flash slowly." + 'blink', + 'The HTML element is a non-standard element which causes the enclosed text to flash slowly.', ], [ - "center", - "The
HTML element is a block-level element that displays its block-level or inline contents centered horizontally within its containing element. The container is usually, but isn't required to be, body." + 'center', + "The
HTML element is a block-level element that displays its block-level or inline contents centered horizontally within its containing element. The container is usually, but isn't required to be, body.", ], [ - "content", - "The HTML element—an obsolete part of the Web Components suite of technologies—was used inside of Shadow DOM as an insertion point, and wasn't meant to be used in ordinary HTML. It has now been replaced by the slot element, which creates a point in the DOM at which a shadow DOM can be inserted." + 'content', + "The HTML element—an obsolete part of the Web Components suite of technologies—was used inside of Shadow DOM as an insertion point, and wasn't meant to be used in ordinary HTML. It has now been replaced by the slot element, which creates a point in the DOM at which a shadow DOM can be inserted.", ], [ - "dir", - "The HTML element is used as a container for a directory of files and/or folders, potentially with styles and icons applied by the user agent. Do not use this obsolete element; instead, you should use the ul element for lists, including lists of files." + 'dir', + 'The HTML element is used as a container for a directory of files and/or folders, potentially with styles and icons applied by the user agent. Do not use this obsolete element; instead, you should use the ul element for lists, including lists of files.', ], [ - "font", - "The HTML element defines the font size, color and face for its content." + 'font', + 'The HTML element defines the font size, color and face for its content.', ], [ - "frame", - "The HTML element defines a particular area in which another HTML document can be displayed. A frame should be used within a frameset." + 'frame', + 'The HTML element defines a particular area in which another HTML document can be displayed. A frame should be used within a frameset.', ], [ - "frameset", - "The HTML element is used to contain frame elements." + 'frameset', + 'The HTML element is used to contain frame elements.', ], [ - "hgroup", - "The
HTML element represents a multi-level heading for a section of a document. It groups a set of

elements." + 'hgroup', + 'The
HTML element represents a multi-level heading for a section of a document. It groups a set of

elements.', ], [ - "image", - "The HTML element is an obsolete remnant of an ancient version of HTML lost in the mists of time; use the standard img element instead. Seriously, the specification even literally uses the words \"Don't ask\" when describing this element." + 'image', + 'The HTML element is an obsolete remnant of an ancient version of HTML lost in the mists of time; use the standard img element instead. Seriously, the specification even literally uses the words "Don\'t ask" when describing this element.', ], [ - "keygen", - "The HTML element exists to facilitate generation of key material, and submission of the public key as part of an HTML form. This mechanism is designed for use with Web-based certificate management systems. It is expected that the element will be used in an HTML form along with other information needed to construct a certificate request, and that the result of the process will be a signed certificate." + 'keygen', + 'The HTML element exists to facilitate generation of key material, and submission of the public key as part of an HTML form. This mechanism is designed for use with Web-based certificate management systems. It is expected that the element will be used in an HTML form along with other information needed to construct a certificate request, and that the result of the process will be a signed certificate.', ], [ - "marquee", - "The HTML element is used to insert a scrolling area of text. You can control what happens when the text reaches the edges of its content area using its attributes." + 'marquee', + 'The HTML element is used to insert a scrolling area of text. You can control what happens when the text reaches the edges of its content area using its attributes.', ], [ - "menuitem", - "The HTML element represents a command that a user is able to invoke through a popup menu. This includes context menus, as well as menus that might be attached to a menu button." + 'menuitem', + 'The HTML element represents a command that a user is able to invoke through a popup menu. This includes context menus, as well as menus that might be attached to a menu button.', ], [ - "nobr", - "The HTML element prevents the text it contains from automatically wrapping across multiple lines, potentially resulting in the user having to scroll horizontally to see the entire width of the text." + 'nobr', + 'The HTML element prevents the text it contains from automatically wrapping across multiple lines, potentially resulting in the user having to scroll horizontally to see the entire width of the text.', ], [ - "noembed", - "The HTML element is an obsolete, non-standard way to provide alternative, or \"fallback\", content for browsers that do not support the embed element or do not support the type of embedded content an author wishes to use. This element was deprecated in HTML 4.01 and above in favor of placing fallback content between the opening and closing tags of an object element." + 'noembed', + 'The <noembed> HTML element is an obsolete, non-standard way to provide alternative, or "fallback", content for browsers that do not support the embed element or do not support the type of embedded content an author wishes to use. This element was deprecated in HTML 4.01 and above in favor of placing fallback content between the opening and closing tags of an object element.', ], [ - "noframes", - "The <noframes> HTML element provides content to be presented in browsers that don't support (or have disabled support for) the frame element. Although most commonly-used browsers support frames, there are exceptions, including certain special-use browsers including some mobile browsers, as well as text-mode browsers." + 'noframes', + "The <noframes> HTML element provides content to be presented in browsers that don't support (or have disabled support for) the frame element. Although most commonly-used browsers support frames, there are exceptions, including certain special-use browsers including some mobile browsers, as well as text-mode browsers.", ], [ - "plaintext", - "The <plaintext> HTML element renders everything following the start tag as raw text, ignoring any following HTML. There is no closing tag, since everything after it is considered raw text." + 'plaintext', + 'The <plaintext> HTML element renders everything following the start tag as raw text, ignoring any following HTML. There is no closing tag, since everything after it is considered raw text.', ], [ - "rb", - "The <rb> HTML element is used to delimit the base text component of a  ruby annotation, i.e. the text that is being annotated. One <rb> element should wrap each separate atomic segment of the base text." + 'rb', + 'The <rb> HTML element is used to delimit the base text component of a  ruby annotation, i.e. the text that is being annotated. One <rb> element should wrap each separate atomic segment of the base text.', ], [ - "rtc", - "The <rtc> HTML element embraces semantic annotations of characters presented in a ruby of rb elements used inside of ruby element. rb elements can have both pronunciation (rt) and semantic (rtc) annotations." + 'rtc', + 'The <rtc> HTML element embraces semantic annotations of characters presented in a ruby of rb elements used inside of ruby element. rb elements can have both pronunciation (rt) and semantic (rtc) annotations.', ], [ - "shadow", - "The <shadow> HTML element—an obsolete part of the Web Components technology suite—was intended to be used as a shadow DOM insertion point. You might have used it if you have created multiple shadow roots under a shadow host. It is not useful in ordinary HTML." + 'shadow', + 'The <shadow> HTML element—an obsolete part of the Web Components technology suite—was intended to be used as a shadow DOM insertion point. You might have used it if you have created multiple shadow roots under a shadow host. It is not useful in ordinary HTML.', ], [ - "spacer", - "The <spacer> HTML element is an obsolete HTML element which allowed insertion of empty spaces on pages. It was devised by Netscape to accomplish the same effect as a single-pixel layout image, which was something web designers used to use to add white spaces to web pages without actually using an image. However, <spacer> no longer supported by any major browser and the same effects can now be achieved using simple CSS." + 'spacer', + 'The <spacer> HTML element is an obsolete HTML element which allowed insertion of empty spaces on pages. It was devised by Netscape to accomplish the same effect as a single-pixel layout image, which was something web designers used to use to add white spaces to web pages without actually using an image. However, <spacer> no longer supported by any major browser and the same effects can now be achieved using simple CSS.', ], [ - "strike", - "The <strike> HTML element places a strikethrough (horizontal line) over text." + 'strike', + 'The <strike> HTML element places a strikethrough (horizontal line) over text.', ], [ - "tt", - "The <tt> HTML element creates inline text which is presented using the user agent default monospace font face. This element was created for the purpose of rendering text as it would be displayed on a fixed-width display such as a teletype, text-only screen, or line printer." + 'tt', + 'The <tt> HTML element creates inline text which is presented using the user agent default monospace font face. This element was created for the purpose of rendering text as it would be displayed on a fixed-width display such as a teletype, text-only screen, or line printer.', ], [ - "xmp", - "The <xmp> HTML element renders text between the start and end tags without interpreting the HTML in between and using a monospaced font. The HTML2 specification recommended that it should be rendered wide enough to allow 80 characters per line." - ] -]) + 'xmp', + 'The <xmp> HTML element renders text between the start and end tags without interpreting the HTML in between and using a monospaced font. The HTML2 specification recommended that it should be rendered wide enough to allow 80 characters per line.', + ], +]); -const deprecatedElSelector = [...deprecatedElementsMap.keys()].join(',') +const deprecatedElSelector = [...deprecatedElementsMap.keys()].join(','); -export function hasDeprecatedHTMLElementInSource(str: string) { - const root = parse(str) +export function hasDeprecatedHTMLElementInSource( + str: string +): string[][] | false { + const root = parse(str); - const deprecatedElements = root.querySelectorAll(deprecatedElSelector) + const deprecatedElements = root.querySelectorAll(deprecatedElSelector); - const uniqueElements = new Set(deprecatedElements.map(item => item.rawTagName.toLowerCase())) - if(uniqueElements.size > 0) { - return [...mapʹ(item => [item, deprecatedElementsMap.get(item)], uniqueElements)] + const uniqueElements = new Set( + deprecatedElements.map(item => item.rawTagName.toLowerCase()) + ); + if (uniqueElements.size > 0) { + return [ + ...mapʹ(item => [item, deprecatedElementsMap.get(item)], uniqueElements), + ]; } - return false + return false; } diff --git a/src/v2/autorespond/html_parsing/index.ts b/src/v2/autorespond/html_parsing/index.ts index 8c4984d4..feca751c 100644 --- a/src/v2/autorespond/html_parsing/index.ts +++ b/src/v2/autorespond/html_parsing/index.ts @@ -10,7 +10,7 @@ import { hasDeprecatedHTMLElementInSource } from './hasDeprecated.js'; const jsCodeBlocks = createCodeBlockCapturer(['html']); -const getDeprecatedElements = pipe([ +const getDeprecatedElements = pipe<string, Iterable<string>>([ jsCodeBlocks, pluck('code'), map(hasDeprecatedHTMLElementInSource), @@ -20,7 +20,7 @@ const getDeprecatedElements = pipe([ const mdnDeprecatedElUri = '<https://developer.mozilla.org/en-US/docs/Web/HTML/Element#obsolete_and_deprecated_elements'; const isA = _.mapper({ 1: 'is a', 2: 'are both' }, 'are all'); -const mdnLink = ([item]: [any]): string => +const mdnLink = (item: string): string => `<https://developer.mozilla.org/en-US/docs/Web/HTML/Element/${item.toLowerCase()}>`; export function detectDeprecatedHTML(msg: Message): boolean { diff --git a/src/v2/autorespond/justask.ts b/src/v2/autorespond/justask.ts index dc20c0c3..d88bc2b4 100644 --- a/src/v2/autorespond/justask.ts +++ b/src/v2/autorespond/justask.ts @@ -15,7 +15,7 @@ const heuristicJustAskRegex = new RegExp( 'ui' ); -export function detectVagueQuestion(msg: Message):boolean { +export function detectVagueQuestion(msg: Message): boolean { const content = stripMarkdownQuote(msg.cleanContent); if (content.split(' ').length < 50 && heuristicJustAskRegex.test(content)) { msg.reply(` @@ -26,5 +26,5 @@ Here's why https://sol.gfxile.net/dontask.html `); return true; } - return false + return false; } diff --git a/src/v2/autorespond/thanks/checker.ts b/src/v2/autorespond/thanks/checker.ts index 3dc335b9..3fab395d 100644 --- a/src/v2/autorespond/thanks/checker.ts +++ b/src/v2/autorespond/thanks/checker.ts @@ -11,7 +11,8 @@ type ThankDef = typeof thanks[number]; const wordBoundaryBefore = String.raw`(?<=^|$|\P{L})`; const wordBoundaryAfter = String.raw`(?=^|$|\P{L})`; -const wordBoundarableRegex = /\p{Changes_When_Uppercased}|\p{Changes_When_Lowercased}/u; +const wordBoundarableRegex = + /\p{Changes_When_Uppercased}|\p{Changes_When_Lowercased}/u; const nonNegativableEnglish = ['cheers']; const english = [ 'ty', @@ -28,8 +29,17 @@ const english = [ 'thanx', 'thnx', ]; -const negativeEnglish = [...english - .flatMap(word => [`n ${ word}`, `n${ word}`, `no${ word}`, `no ${ word}`]), 'no need to thank', 'thanks,? but no thanks', 'thanks for nothing']; +const negativeEnglish = [ + ...english.flatMap(word => [ + `n ${word}`, + `n${word}`, + `no${word}`, + `no ${word}`, + ]), + 'no need to thank', + 'thanks,? but no thanks', + 'thanks for nothing', +]; const partitionWrap = partition( (str: string) => !!wordBoundarableRegex.test(str) @@ -74,12 +84,12 @@ const noThanksRegex = new RegExp( ); const hasThanks = str => { - thanksRegex.lastIndex = -1 - return thanksRegex.exec(removeDiacritics(str) - .replace(/\s+/, ' ') - .replace(noThanksRegex, '')); -} -const keywordValidator = (str: string) => { + thanksRegex.lastIndex = -1; + return thanksRegex.exec( + removeDiacritics(str).replace(/\s+/u, ' ').replace(noThanksRegex, '') + ); +}; +const keywordValidator = (str: string): boolean => { return Boolean(hasThanks(str)); }; diff --git a/src/v2/autorespond/thanks/createResponse.ts b/src/v2/autorespond/thanks/createResponse.ts index bbe4a78d..3e1f50d1 100644 --- a/src/v2/autorespond/thanks/createResponse.ts +++ b/src/v2/autorespond/thanks/createResponse.ts @@ -1,30 +1,37 @@ import { MessageButton } from 'discord.js'; import { MessageActionRow, MessageSelectMenu } from 'discord.js'; -import type { User } from 'discord.js'; +import type { User, MessageOptions } from 'discord.js'; import type { EmbedField, Collection } from 'discord.js'; import { clampLength } from '../../utils/clampStr.js'; import { createEmbed } from '../../utils/discordTools.js'; -export function createResponse(thankedUsers: Collection<string, User>, authorId: string) { +export function createResponse( + thankedUsers: Collection<string, User>, + authorId: string +): MessageOptions { const title = `Point${thankedUsers.size === 1 ? '' : 's'} received!`; - const description = `<@!${authorId}> has given a point to ${thankedUsers.size === 1 + const description = `<@!${authorId}> has given a point to ${ + thankedUsers.size === 1 ? `<@!${thankedUsers.first().id}>` - : 'the users mentioned below'}!`; + : 'the users mentioned below' + }!`; - const fields: EmbedField[] = thankedUsers.size > 1 - ? [...thankedUsers].map(([, u], i) => ({ - inline: false, - name: `${(i + 1).toString()}.`, - value: `<@!${u.id}>`, - })) - : []; + const fields: EmbedField[] = + thankedUsers.size > 1 + ? [...thankedUsers].map(([, u], i) => ({ + inline: false, + name: `${(i + 1).toString()}.`, + value: `<@!${u.id}>`, + })) + : []; const output = createEmbed({ description, fields, - footerText: 'Thank a helpful member by replying "thanks @username" or saying "thanks" in a reply or thread.', + footerText: + 'Thank a helpful member by replying "thanks @username" or saying "thanks" in a reply or thread.', provider: 'helper', title, }).embed; @@ -35,19 +42,19 @@ export function createResponse(thankedUsers: Collection<string, User>, authorId: new MessageActionRow().addComponents( thankedUsers.size > 1 ? new MessageSelectMenu() - .setCustomId(`thanks🤔${authorId}🤔select`) - .setPlaceholder('Accidentally Thank someone? Un-thank them here!') - .setMinValues(1) - .setOptions( - thankedUsers.map(user => ({ - label: clampLength(user.username, 25), - value: user.id, - })) - ) + .setCustomId(`thanks🤔${authorId}🤔select`) + .setPlaceholder('Accidentally Thank someone? Un-thank them here!') + .setMinValues(1) + .setOptions( + thankedUsers.map(user => ({ + label: clampLength(user.username, 25), + value: user.id, + })) + ) : new MessageButton() - .setCustomId(`thanks🤔${authorId}🤔${thankedUsers.first().id}`) - .setStyle('SECONDARY') - .setLabel('This was an accident, UNDO!') + .setCustomId(`thanks🤔${authorId}🤔${thankedUsers.first().id}`) + .setStyle('SECONDARY') + .setLabel('This was an accident, UNDO!') ), ], }; diff --git a/src/v2/autorespond/thanks/db_model.ts b/src/v2/autorespond/thanks/db_model.ts index c0098ee4..963bf8ac 100644 --- a/src/v2/autorespond/thanks/db_model.ts +++ b/src/v2/autorespond/thanks/db_model.ts @@ -1,40 +1,45 @@ import type { Document } from 'mongoose'; -import mongoose from 'mongoose' +import mongoose from 'mongoose'; -const { model, Schema } = mongoose +const { model, Schema } = mongoose; - -const schema = new Schema({ - guild: { - required: true, - type: String - }, - channel: { - required: true, - type: String - }, - thanker: { - required: true, - type: String - }, - thankees: { - type: [String], - required: true +const schema = new Schema( + { + guild: { + required: true, + type: String, + }, + channel: { + required: true, + type: String, + }, + thanker: { + required: true, + type: String, + }, + thankees: { + type: [String], + required: true, + }, + responseMsgId: { + type: String, + }, }, - responseMsgId: { - type: String + { + timestamps: true, } -}, { - timestamps: true -}) +); -export const ThanksInteraction = model('thanksMessageInteraction', schema) +export const ThanksInteraction = model<ThanksInteractionType>( + 'thanksMessageInteraction', + schema +); export type ThanksInteractionType = Document & { - guild: string, - thanker: string, - channel: string, - thankees: string[], - responseMsgId: string, - createdAt: Date, - updatedAt: Date + guild: string; + thanker: string; + channel: string; + thankees: string[]; + responseMsgId: string; + createdAt: Date; + updatedAt: Date; }; diff --git a/src/v2/autorespond/thanks/index.ts b/src/v2/autorespond/thanks/index.ts index e1f24c14..02bcfd99 100644 --- a/src/v2/autorespond/thanks/index.ts +++ b/src/v2/autorespond/thanks/index.ts @@ -41,8 +41,7 @@ const handleThanks = async (msg: Message): Promise<void> => { const reply = await getReply(msg); if (botId || (msg.mentions.users.size === 0 && !reply)) { if ( - msg.channel.type === 'GUILD_PRIVATE_THREAD' || - msg.channel.type === 'GUILD_PUBLIC_THREAD' + ['GUILD_PRIVATE_THREAD','GUILD_PUBLIC_THREAD'].includes(msg.channel.type) ) { await handleThreadThanks(msg); } @@ -218,7 +217,7 @@ function attachUndoThanksListener(client: Client): void { .map((item, x) => ({ ...item, name: `${x + 1}` })); const oldSelect = oldMsg.components[0].components[0] as MessageSelectMenu; - const newOptions = oldSelect.options + const nuOptions = oldSelect.options .filter(item => !removeThankees.includes(item.value)) .map(({ label, value }) => ({ label, value })); @@ -227,8 +226,8 @@ function attachUndoThanksListener(client: Client): void { components: [ new MessageActionRow().addComponents( new MessageSelectMenu(oldSelect) - .setOptions(newOptions) - .setMaxValues(newOptions.length) + .setOptions(nuOptions) + .setMaxValues(nuOptions.length) ), ], }); diff --git a/src/v2/autorespond/thanks/nothanks.ts b/src/v2/autorespond/thanks/nothanks.ts index a0f41781..ab61134d 100644 --- a/src/v2/autorespond/thanks/nothanks.ts +++ b/src/v2/autorespond/thanks/nothanks.ts @@ -1,3 +1,5 @@ +// We're just treeting this like a giant JSON file but in TS +/* eslint-disable import/no-anonymous-default-export, import/no-default-export */ export default [ { language: 'Arabic', symbol: 'ar', text: 'لا شكرا' }, { language: 'Dutch', symbol: 'nl', text: 'Nee bedankt' }, diff --git a/src/v2/autorespond/thanks/nothankyou.ts b/src/v2/autorespond/thanks/nothankyou.ts index 04d7fe7e..d3daa8de 100644 --- a/src/v2/autorespond/thanks/nothankyou.ts +++ b/src/v2/autorespond/thanks/nothankyou.ts @@ -1,3 +1,5 @@ +// We're just treeting this like a giant JSON file but in TS +/* eslint-disable import/no-anonymous-default-export, import/no-default-export */ export default [ { language: 'Arabic', symbol: 'ar', text: 'لا، شكرا' }, { language: 'Dutch', symbol: 'nl', text: 'Nee, dank u' }, diff --git a/src/v2/autorespond/thanks/thanks.ts b/src/v2/autorespond/thanks/thanks.ts index 6343043e..93948a83 100644 --- a/src/v2/autorespond/thanks/thanks.ts +++ b/src/v2/autorespond/thanks/thanks.ts @@ -1,3 +1,5 @@ +// We're just treeting this like a giant JSON file but in TS +/* eslint-disable import/no-anonymous-default-export, import/no-default-export */ export default [ { language: 'Arabic', symbol: 'ar', text: 'شكر' }, { language: 'Dutch', symbol: 'nl', text: 'bedankt' }, diff --git a/src/v2/autorespond/thanks/thankyou.ts b/src/v2/autorespond/thanks/thankyou.ts index 5e8ebdb2..e1f02bbf 100644 --- a/src/v2/autorespond/thanks/thankyou.ts +++ b/src/v2/autorespond/thanks/thankyou.ts @@ -1,3 +1,5 @@ +// We're just treeting this like a giant JSON file but in TS +/* eslint-disable import/no-anonymous-default-export, import/no-default-export */ export default [ { language: 'Arabic', symbol: 'ar', text: 'شكرا' }, { language: 'Dutch', symbol: 'nl', text: 'dank je' }, diff --git a/src/v2/autorespond/thanks/threadThanks.ts b/src/v2/autorespond/thanks/threadThanks.ts index 50528b10..6ebd3f30 100644 --- a/src/v2/autorespond/thanks/threadThanks.ts +++ b/src/v2/autorespond/thanks/threadThanks.ts @@ -23,14 +23,22 @@ import { ThanksInteraction } from './db_model.js'; const memoryCache = new Map<string, Message>(); export async function handleThreadThanks(msg: Message): Promise<void> { - const { channel } = msg; + const { channel, author, id: msgId } = msg; if (!channel.isThread()) { return; } - const oldResponseId = [msg.author.id, msg.channel.id].join('|'); + const oldResponseId = [author.id, channel.id].join('|'); if (memoryCache.has(oldResponseId)) { - await memoryCache.get(oldResponseId).delete().catch(error => { console.error("message already deleted"); }).finally(() => { memoryCache.delete(oldResponseId) }); + await memoryCache + .get(oldResponseId) + .delete() + .catch(error => { + console.error('message already deleted'); + }) + .finally(() => { + memoryCache.delete(oldResponseId); + }); } // channel.members.fetch should return a collection const [members, previousInteractions]: [ @@ -41,7 +49,7 @@ export async function handleThreadThanks(msg: Message): Promise<void> { Collection<string, ThreadMember> >, ThanksInteraction.find({ - thanker: msg.author.id, + thanker: author.id, createdAt: { $gte: Date.now() - Number.parseInt(POINT_LIMITER_IN_MINUTES) * 60_000, }, @@ -53,7 +61,7 @@ export async function handleThreadThanks(msg: Message): Promise<void> { const alreadyThanked = []; const otherMembers = members.filter(x => { - const notSelf = x.user.id !== msg.author.id; + const notSelf = x.user.id !== author.id; const notBot = !x.user.bot; const notTimeout = !previouslyThankedIds.has(x.user.id); @@ -92,19 +100,19 @@ export async function handleThreadThanks(msg: Message): Promise<void> { })) ) .setMinValues(1) - .setCustomId(`threadThanks🤔${msg.id}🤔select🤔${msg.author.id}`) + .setCustomId(`threadThanks🤔${msgId}🤔select🤔${author.id}`) ), new MessageActionRow().addComponents( new MessageButton() .setLabel('Nevermind') .setStyle('SECONDARY') - .setCustomId(`threadThanks🤔${msg.id}🤔cancel🤔${msg.author.id}`) + .setCustomId(`threadThanks🤔${msgId}🤔cancel🤔${author.id}`) ), ], }); - if (msg.channel?.id) { - memoryCache.set([msg.author.id, msg.channel.id].join('|'), response); + if (channel?.id) { + memoryCache.set([author.id, channel.id].join('|'), response); } } @@ -115,13 +123,13 @@ export function attachThreadThanksHandler(client: Client): void { if (!(interaction.isSelectMenu() || interaction.isButton())) { return; } - const {channel} = interaction; - const [category, msgId, type, userId] = interaction.customId.split('🤔'); + const { channel, customId, user, message, guild } = interaction; + const [category, msgId, type, userId] = customId.split('🤔'); if (category !== 'threadThanks') { return; } - if (interaction.user.id !== userId) { + if (user.id !== userId) { interaction.reply({ content: "That's not for you! That prompt is for someone else.", ephemeral: true, @@ -131,7 +139,7 @@ export function attachThreadThanksHandler(client: Client): void { if (type === 'cancel') { await Promise.all([ - interaction.channel.messages.delete(interaction.message.id), + channel.messages.delete(message.id), interaction.reply({ content: 'Sure thing, message removed!', ephemeral: true, @@ -142,9 +150,9 @@ export function attachThreadThanksHandler(client: Client): void { if (type === 'select') { const { values } = interaction as SelectMenuInteraction; - interaction.channel.messages.delete(interaction.message.id); - const msgPromise = interaction.channel.messages.fetch(msgId); - const thankedMembers = await interaction.guild.members.fetch({ + channel.messages.delete(message.id); + const msgPromise = channel.messages.fetch(msgId); + const thankedMembers = await guild.members.fetch({ user: values, }); @@ -152,31 +160,31 @@ export function attachThreadThanksHandler(client: Client): void { thankedMembers.map(item => [item.user.id, item.user]) ); - const responseData = createResponse(thankedUsers, interaction.user.id); + const responseData = createResponse(thankedUsers, user.id); let response: Message; const msg = await msgPromise; - if (!msg) { - response = await msg.channel.send(responseData); - } else { + if (msg) { response = await msg.reply(responseData); + } else { + response = await msg.channel.send(responseData); } - const name = [interaction.channelId, interaction.user.id].join('|'); + const name = [channel.id, user.id].join('|'); if (memoryCache.has(name)) { const item = memoryCache.get(name); memoryCache.delete(name); await item.delete(); } - if (channel.isThread() && channel.ownerId === interaction.user.id) { + if (channel.isThread() && channel.ownerId === user.id) { sendCloseThreadQuery(interaction); } await ThanksInteraction.create({ thanker: userId, - guild: interaction.guildId, - channel: interaction.channelId, + guild: guild.id, + channel: channel.id, thankees: thankedUsers.map(u => u.id), responseMsgId: response.id, }); @@ -202,7 +210,7 @@ function sendCloseThreadQuery( }); } -export function attachThreadClose(client: Client) { +export function attachThreadClose(client: Client): void { client.on( 'interactionCreate', asyncCatch(async interaction => { diff --git a/src/v2/cache/cacheFns.ts b/src/v2/cache/cacheFns.ts index dd9f2811..874be0c0 100644 --- a/src/v2/cache/cacheFns.ts +++ b/src/v2/cache/cacheFns.ts @@ -34,7 +34,8 @@ export async function upsert(options: CacheUpsertOptions) { // Cannot use destructuring due to the properties being optional and TS not liking it // eslint-disable-next-line unicorn/consistent-destructuring - const expireTime = "expiresAt" in options ? options.expiresAt : Date.now() + options.expiresIn; + const expireTime = + 'expiresAt' in options ? options.expiresAt : Date.now() + options.expiresIn; const result = await GenericCache.findOneAndUpdate( { diff --git a/src/v2/cache/index.ts b/src/v2/cache/index.ts index 165d004c..9770ebcb 100644 --- a/src/v2/cache/index.ts +++ b/src/v2/cache/index.ts @@ -6,15 +6,14 @@ export * from './cacheFns.js'; type ConditionLimit = { delay: number; type: string; meta?: unknown }; -export function limitFnByUser<T extends (...args: unknown[]) => boolean | Promise<boolean>>( - fn: T, - { delay, type = `${fn.name}|${delay}` }: ConditionLimit -) { +export function limitFnByUser< + T extends (...args: unknown[]) => boolean | Promise<boolean> +>(fn: T, { delay, type = `${fn.name}|${delay}` }: ConditionLimit) { return async (msg: Message): Promise<boolean> => { const user = msg.author.id; const guild = msg.guild?.id; - if(!guild) { + if (!guild) { return; } diff --git a/src/v2/cache/model.ts b/src/v2/cache/model.ts index 5f66df04..ac9485f8 100644 --- a/src/v2/cache/model.ts +++ b/src/v2/cache/model.ts @@ -1,7 +1,6 @@ -import type { Document, Model} from 'mongoose'; import mongoose from 'mongoose'; -const { model, Schema } = mongoose +const { model, Schema } = mongoose; const schema = new Schema({ guild: { @@ -26,12 +25,12 @@ const schema = new Schema({ }, }); -export const GenericCache = model<GenericCacheType>('GenericCache', schema) +export const GenericCache = model<GenericCacheType>('GenericCache', schema); -type GenericCacheType = { +export type GenericCacheType = { guild: string; type: string; timestamp: number; user: string; meta?: unknown; -} +}; diff --git a/src/v2/commands/about/index.ts b/src/v2/commands/about/index.ts index f24bfdf8..e698aaa3 100644 --- a/src/v2/commands/about/index.ts +++ b/src/v2/commands/about/index.ts @@ -55,7 +55,7 @@ export const aboutInteraction: CommandDataWithHandler = { if (content) { interaction.reply( - `${user ? `${user}\n` : ''} ${valueOrCall(content).trim()}` + `${user ? `${user.toString()}\n` : ''} ${valueOrCall(content).trim()}` ); return; } diff --git a/src/v2/commands/db_model.ts b/src/v2/commands/db_model.ts index fa0fb3da..cf07dcf8 100644 --- a/src/v2/commands/db_model.ts +++ b/src/v2/commands/db_model.ts @@ -1,7 +1,7 @@ import type { Document } from 'mongoose'; import mongoose from 'mongoose'; -const { model, Schema } = mongoose +const { model, Schema } = mongoose; const schema = new Schema({ guild: { @@ -9,18 +9,23 @@ const schema = new Schema({ }, name: { required: true, - type: String + type: String, }, commandId: { required: true, - type: String + type: String, }, applicationId: { required: true, - type: String - } + type: String, + }, } as const); export const Command = model<CommandType>('commands', schema); -export type CommandType = Document & { guild?: string, name: string, commandId: string, applicationId: string } +export type CommandType = Document & { + guild?: string; + name: string; + commandId: string; + applicationId: string; +}; diff --git a/src/v2/commands/index.ts b/src/v2/commands/index.ts index 1583f18a..d8caec37 100644 --- a/src/v2/commands/index.ts +++ b/src/v2/commands/index.ts @@ -1,4 +1,5 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ +// This is required so far in this file +/* eslint-disable no-await-in-loop */ import type { ApplicationCommand, ApplicationCommandData, @@ -13,8 +14,10 @@ import { filter } from 'domyno'; import { isEqual } from 'lodash-es'; import type { CommandDataWithHandler } from '../../types'; +import { setupCommands } from '../modules/mod/commands/index.js'; +import { roleCommands } from '../modules/roles/commands/index.js'; import { asyncCatch } from '../utils/asyncCatch.js'; -import { map, mapʹ } from '../utils/map.js'; +import { map } from '../utils/map.js'; import { merge } from '../utils/merge.js'; import { normalizeApplicationCommandData } from '../utils/normalizeCommand.js'; import { pipe } from '../utils/pipe.js'; @@ -46,6 +49,8 @@ export const guildCommands = new Map( shitpostInteraction, npmInteraction, whynoInteraction, + roleCommands, + setupCommands, // warn // Not used atm ].map(command => [command.name, command]) ); // placeholder for now @@ -123,13 +128,13 @@ export const registerCommands = async (client: Client): Promise<void> => { for (const { onAttach } of applicationCommands.values()) { // We're attaching these so it's fine - + onAttach?.(client); } for (const { onAttach } of guildCommands.values()) { // We're attaching these so it's fine - + onAttach?.(client); } @@ -218,9 +223,7 @@ async function addCommands( } function getDestination( - commandManager: - | ApplicationCommandManager - | GuildApplicationCommandManager + commandManager: ApplicationCommandManager | GuildApplicationCommandManager ) { return 'guild' in commandManager ? `Guild: ${commandManager.guild.name}` @@ -275,7 +278,7 @@ function deleteRemovedCommands( ) { const destination = getDestination(cmdMgr); return map(async (name: string) => { - const existing = existingCommands.get(name)!; + const existing = existingCommands.get(name); console.warn(`Deleting ${name} from ${destination}`); return cmdMgr.delete(existing.id); diff --git a/src/v2/commands/mdn/index.ts b/src/v2/commands/mdn/index.ts index 2bcd69b9..d3d8736a 100644 --- a/src/v2/commands/mdn/index.ts +++ b/src/v2/commands/mdn/index.ts @@ -1,4 +1,3 @@ - import type { ButtonInteraction, Client, @@ -18,12 +17,15 @@ import { URL } from 'url'; import type { CommandDataWithHandler } from '../../../types'; import { clampLength, clampLengthMiddle } from '../../utils/clampStr.js'; -import { invalidResponse, noResults, unknownError } from '../../utils/errors.js'; +import { + invalidResponse, + noResults, + unknownError, +} from '../../utils/errors.js'; import { getSearchUrl } from '../../utils/urlTools.js'; import useData from '../../utils/useData.js'; - -const list = new (Intl as any).ListFormat() +const list = new Intl.ListFormat(); const provider = 'mdn'; type SearchResponse = { @@ -63,8 +65,6 @@ const fetch: typeof useData = useData; const buildDirectUrl = (path: string) => new URL(path, 'https://developer.mozilla.org/en-US/docs/').toString(); - - const mdnHandler = async ( client: Client, interaction: CommandInteraction @@ -114,7 +114,6 @@ const mdnHandler = async ( .setCustomId(`mdn🤔${msgId}🤔cancel`) ); - await deferral; const int = (await interaction.editReply({ content: 'Please pick 1 - 5 options below to display', components: [selectRow, buttonRow], @@ -123,35 +122,46 @@ const mdnHandler = async ( const interactionCollector = int.createMessageComponentCollector< MessageComponentTypes.SELECT_MENU | MessageComponentTypes.BUTTON >({ - filter: item => item.user.id === interaction.user.id && item.customId.startsWith(`mdn🤔${msgId}`), + filter: item => + item.user.id === interaction.user.id && + item.customId.startsWith(`mdn🤔${msgId}`), }); - interactionCollector.once('collect', async (interaction:ButtonInteraction|SelectMenuInteraction) => { - await interaction.deferUpdate(); - if (interaction.isButton()) { - await int.delete(); - return; + interactionCollector.once( + 'collect', + async (interaction: ButtonInteraction | SelectMenuInteraction) => { + await interaction.deferUpdate(); + if (interaction.isButton()) { + await int.delete(); + return; + } + const valueSet = new Set(interaction.values); + const values = collection.filter((_, key) => valueSet.has(key)); + + await interaction.editReply({ + content: `Displaying Results for ${list.format( + values.map(({ title }) => title) + )}`, + components: [], + }); + + interaction.channel.send({ + content: `Results for "${searchTerm}"`, + embeds: values.map(({ title, summary, slug }) => + new MessageEmbed() + .setTitle(`${maybeClippy()} ${title}`) + .setDescription( + summary + .split('\n') + .map(item => item.trim()) + .join(' ') + ) + .setURL(buildDirectUrl(slug)) + .setColor('WHITE') + ), + }); } - const valueSet = new Set(interaction.values); - const values = collection.filter((_, key) => valueSet.has(key)); - - await interaction.editReply({ - content: `Displaying Results for ${list.format(values.map(({title}) => title))}`, - components: [] - }) - - interaction.channel.send({ - content: `Results for "${searchTerm}"`, - embeds: values.map(({ title, summary, slug }) => - new MessageEmbed() - .setTitle(`${maybeClippy()} ${title}`) - .setDescription(summary.split('\n').map(item => item.trim()).join(' ')) - .setURL(buildDirectUrl(slug)) - .setColor('WHITE') - ), - }); - }); - + ); } catch (error) { console.error(error); interaction.editReply(unknownError); diff --git a/src/v2/commands/npm/index.ts b/src/v2/commands/npm/index.ts index 70a6566b..52a19ffd 100644 --- a/src/v2/commands/npm/index.ts +++ b/src/v2/commands/npm/index.ts @@ -63,17 +63,16 @@ const getFirstTenResults = pipe<Iterable<NPMResponse>, NPMEmbed[]>([ collect, ]); - // msg: Message, searchTerm: string const handleNpmCommand = async ( client: Client, interaction: CommandInteraction ): Promise<void> => { const searchTerm = interaction.options.getString('name'); - await interaction.deferReply({ephemeral: true}); + await interaction.deferReply({ ephemeral: true }); try { const json = await fetch<NPMResponse[]>({ - isInvalidData: json => json.length === 0, + isInvalidData: (json: unknown[]) => json.length === 0, msg: interaction, provider, searchTerm, @@ -88,11 +87,9 @@ const handleNpmCommand = async ( if (firstTenResults.length === 1) { await interaction.channel.send({ content: `Displaying result for ${searchTerm}`, - embeds: [ - createNPMEmbed(firstTenResults[0], interaction.user), - ] + embeds: [createNPMEmbed(firstTenResults[0], interaction.user)], }); - await interaction.editReply('👇 Displaying results below') + await interaction.editReply('👇 Displaying results below'); return; } @@ -115,7 +112,6 @@ const handleNpmCommand = async ( ) ); - const int = (await interaction.editReply({ content: 'Please pick 1 option below to display', components: [selectRow], @@ -148,7 +144,9 @@ const handleNpmCommand = async ( interaction.channel.send({ content: `Results for "${searchTerm}"`, - embeds: values.map((npmEmbed) => createNPMEmbed(npmEmbed, interaction.user)), + embeds: values.map(npmEmbed => + createNPMEmbed(npmEmbed, interaction.user) + ), }); } ); @@ -175,15 +173,18 @@ type NPMEmbed = { author: { name: string; url: string }; }; -const createNPMEmbed = ({ - externalUrls, - name, - url, - description, - lastUpdate, - maintainers, - author, -}: NPMEmbed, user: User): MessageEmbed => +const createNPMEmbed = ( + { + externalUrls, + name, + url, + description, + lastUpdate, + maintainers, + author, + }: NPMEmbed, + user: User +): MessageEmbed => new MessageEmbed() .setAuthor(`📦 Last updated ${lastUpdate}`) .setTitle(`${name}`) diff --git a/src/v2/commands/php/index.ts b/src/v2/commands/php/index.ts index 41eb5389..922396ac 100644 --- a/src/v2/commands/php/index.ts +++ b/src/v2/commands/php/index.ts @@ -4,13 +4,9 @@ import type { Client, CommandInteraction, Message, - SelectMenuInteraction -} from 'discord.js'; -import { - MessageButton, - MessageActionRow, - MessageSelectMenu, + SelectMenuInteraction, } from 'discord.js'; +import { MessageButton, MessageActionRow, MessageSelectMenu } from 'discord.js'; import type { MessageComponentTypes } from 'discord.js/typings/enums'; import type { Node } from 'dom-parser'; import DOMParser from 'dom-parser'; @@ -85,30 +81,31 @@ const makeRequest = requester; const parseText = textParser; const metadataExtractor = extractMetadataFromResult; -const handler = async (client: Client, interaction: CommandInteraction): Promise<void> => { +const handler = async ( + client: Client, + interaction: CommandInteraction +): Promise<void> => { const searchTerm = interaction.options.getString('query'); - const defer = interaction.deferReply() + const defer = interaction.deferReply(); try { const { error, text, searchUrl } = await makeRequest(searchTerm); if (error) { - await defer + await defer; await interaction.editReply(invalidResponse); return; } const { isDirect, results } = parseText(text); if (isDirect) { - await defer + await defer; await interaction.editReply(buildDirectUrl(provider, searchTerm)); return; } + const msgId = Math.random().toString(16); - const msgId = Math.random().toString(16) - - const selectRow = new MessageActionRow() - .addComponents( - new MessageSelectMenu() + const selectRow = new MessageActionRow().addComponents( + new MessageSelectMenu() .setCustomId(`php🤔${msgId}🤔select`) .setMaxValues(5) .setMinValues(1) @@ -120,43 +117,50 @@ const handler = async (client: Client, interaction: CommandInteraction): Promise label: clampLengthMiddle(title, 25), description: clampLength(url, 50), value: String(index), - } + }; }) ) - ) - - - const buttonRow = new MessageActionRow().addComponents( - new MessageButton() - .setLabel('Cancel') - .setStyle('SECONDARY') - .setCustomId(`mdn🤔${msgId}🤔cancel`) - ); - - await defer - const int = (await interaction.editReply({ - content: 'Please pick 1 - 5 options below to display', - components: [selectRow, buttonRow], - })) as Message; - - const interactionCollector = int.createMessageComponentCollector<MessageComponentTypes.BUTTON | MessageComponentTypes.SELECT_MENU>({ - filter: item => item.user.id === interaction.user.id && item.customId.startsWith(`php🤔${msgId}`), + ); + + const buttonRow = new MessageActionRow().addComponents( + new MessageButton() + .setLabel('Cancel') + .setStyle('SECONDARY') + .setCustomId(`mdn🤔${msgId}🤔cancel`) + ); + + await defer; + const int = (await interaction.editReply({ + content: 'Please pick 1 - 5 options below to display', + components: [selectRow, buttonRow], + })) as Message; + + const interactionCollector = int.createMessageComponentCollector< + MessageComponentTypes.BUTTON | MessageComponentTypes.SELECT_MENU + >({ + filter: item => + item.user.id === interaction.user.id && + item.customId.startsWith(`php🤔${msgId}`), }); - interactionCollector.once('collect', async (interaction:ButtonInteraction | SelectMenuInteraction) => { - await interaction.deferUpdate(); - if (interaction.isButton()) { - await int.delete(); - return; + interactionCollector.once( + 'collect', + async (interaction: ButtonInteraction | SelectMenuInteraction) => { + await interaction.deferUpdate(); + if (interaction.isButton()) { + await int.delete(); + return; + } + const urls = interaction.values.map( + x => metadataExtractor(results[x].firstChild).url + ); + + await interaction.editReply({ + components: [], + content: urls.join('\n'), + }); } - const urls = interaction.values.map(x => metadataExtractor(results[x].firstChild).url) - - - await interaction.editReply({ - components: [], - content: urls.join('\n') - }); - }); + ); } catch (error) { // eslint-disable-next-line no-console console.error(error); @@ -164,7 +168,7 @@ const handler = async (client: Client, interaction: CommandInteraction): Promise } }; -export const phpCommand : CommandDataWithHandler = { +export const phpCommand: CommandDataWithHandler = { name: 'php', description: 'search and link something from php.net', handler, diff --git a/src/v2/commands/please/handlers/format/exampleFns.ts b/src/v2/commands/please/handlers/format/exampleFns.ts index ec3baa98..50022d9f 100644 --- a/src/v2/commands/please/handlers/format/exampleFns.ts +++ b/src/v2/commands/please/handlers/format/exampleFns.ts @@ -1,10 +1,12 @@ +// This file is literally just containing code to use in examples +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ export const LINE_SEPARATOR = '\n'; function formatFn(fn: Function) { return fn .toString() .split(LINE_SEPARATOR) - .map(line => `> ${ line}`) + .map(line => `> ${line}`) .join(LINE_SEPARATOR); } diff --git a/src/v2/commands/please/index.ts b/src/v2/commands/please/index.ts index 6dac4a4a..a180fa59 100644 --- a/src/v2/commands/please/index.ts +++ b/src/v2/commands/please/index.ts @@ -1,4 +1,8 @@ -import type { ApplicationCommandOptionChoiceData, Client, CommandInteraction } from 'discord.js'; +import type { + ApplicationCommandOptionChoiceData, + Client, + CommandInteraction, +} from 'discord.js'; import type { CommandDataWithHandler } from '../../../types'; import { map } from '../../utils/map.js'; @@ -9,8 +13,12 @@ import { english } from './handlers/english.js'; import { format } from './handlers/format/index.js'; import { justAsk } from './handlers/justask.js'; - -const pleaseMessages = new Map<string, ValueOrNullary<string>>([format, code, justAsk, english]); +const pleaseMessages = new Map<string, ValueOrNullary<string>>([ + format, + code, + justAsk, + english, +]); const mapTransformToChoices = map( (item: string): ApplicationCommandOptionChoiceData => ({ @@ -28,7 +36,7 @@ export const pleaseInteraction: CommandDataWithHandler = { if (content) { await interaction.reply( - `${user ? `${user}\n` : ''}${valueOrCall(content).trim()}` + `${user ? `${user.toString()}\n` : ''}${valueOrCall(content).trim()}` ); } }, diff --git a/src/v2/commands/points/index.ts b/src/v2/commands/points/index.ts index f9e72aff..2a86ef30 100644 --- a/src/v2/commands/points/index.ts +++ b/src/v2/commands/points/index.ts @@ -256,14 +256,17 @@ async function handlePointsSet( } const [prev, curr] = result; - const output = createPointsEmbed(`A user's points have been set to ${points}`, [ - { - inline: false, - name: 'User', - value: `${guildMember.user}`, - }, - adminEmbedField(interaction), - ]); + const output = createPointsEmbed( + `A user's points have been set to ${points}`, + [ + { + inline: false, + name: 'User', + value: `${guildMember.user}`, + }, + adminEmbedField(interaction), + ] + ); if ( prev >= HELPFUL_ROLE_POINT_THRESHOLD_NUM && @@ -362,7 +365,7 @@ export const pointsHandlers: CommandDataWithHandler = { description: 'point commands', handler: handlePoints, name: 'points', - guildValidate: (guild) => guild.id === SERVER_ID, + guildValidate: guild => guild.id === SERVER_ID, options: [ { name: 'leaderboard', diff --git a/src/v2/commands/post/env.ts b/src/v2/commands/post/env.ts index 732c8888..f02c1a79 100644 --- a/src/v2/commands/post/env.ts +++ b/src/v2/commands/post/env.ts @@ -1,8 +1,5 @@ import { IS_PROD, - - - POST_LIMITER_IN_HOURS, // Used for informing the user about the limiter AWAIT_MESSAGE_TIMEOUT as AMT, // Renamed for shadowing MINIMAL_AMOUNT_OF_WORDS as MAOW, // Renamed for shadowing as well @@ -19,14 +16,11 @@ const POST_LIMITER = IS_PROD // convert string to int const MINIMAL_AMOUNT_OF_WORDS = Number.parseInt(MAOW); -export { - - - - MINIMAL_AMOUNT_OF_WORDS, - POST_LIMITER, - - AWAIT_MESSAGE_TIMEOUT, -}; +export { MINIMAL_AMOUNT_OF_WORDS, POST_LIMITER, AWAIT_MESSAGE_TIMEOUT }; -export {MOD_CHANNEL, JOB_POSTINGS_CHANNEL, MINIMAL_COMPENSATION, POST_LIMITER_IN_HOURS} from '../../env.js'; \ No newline at end of file +export { + MOD_CHANNEL, + JOB_POSTINGS_CHANNEL, + MINIMAL_COMPENSATION, + POST_LIMITER_IN_HOURS, +} from '../../env.js'; diff --git a/src/v2/commands/post/index.ts b/src/v2/commands/post/index.ts index 769ee10d..9ff0192a 100644 --- a/src/v2/commands/post/index.ts +++ b/src/v2/commands/post/index.ts @@ -1,4 +1,3 @@ - import type { Message, CollectorFilter, @@ -11,9 +10,7 @@ import type { CommandInteraction, } from 'discord.js'; import { MessageButton } from 'discord.js'; -import { - MessageActionRow, -} from 'discord.js'; +import { MessageActionRow } from 'discord.js'; import { filter } from 'domyno'; import type { CommandDataWithHandler } from '../../../types'; @@ -21,7 +18,10 @@ import { SERVER_ID } from '../../env.js'; import { cache } from '../../spam_filter/index.js'; import { MultistepForm } from '../../utils/MultistepForm.js'; import { asyncCatch } from '../../utils/asyncCatch.js'; -import { createEmbed, createMarkdownCodeBlock } from '../../utils/discordTools.js'; +import { + createEmbed, + createMarkdownCodeBlock, +} from '../../utils/discordTools.js'; import { map } from '../../utils/map.js'; import { pipe } from '../../utils/pipe.js'; import { capitalize } from '../../utils/string.js'; @@ -75,9 +75,7 @@ const getTargetChannel = ( guild: Guild, name: string ): TextChannel | ThreadChannel => - guild.channels.cache.get(name) as - | TextChannel - | ThreadChannel; + guild.channels.cache.get(name) as TextChannel | ThreadChannel; const generateURL = (guildID: string, channelID: string, msgID: string) => `https://discordapp.com/channels/${guildID}/${channelID}/${msgID}`; @@ -165,26 +163,27 @@ const sendAlert = ( const generateFields = pipe<Answers, Iterable<OutputField>>([ filter( ([key, val]: [string, string]) => - !['guidelines'].includes(key) && !(key === 'remote' && val.toLowerCase() === 'onsite') + !['guidelines'].includes(key) && + !(key === 'remote' && val.toLowerCase() === 'onsite') ), map(([key, val]: [string, string]): OutputField => { let value = val; switch (key) { case 'compensation': - value = val + value = val; break; case 'compensation_type': - value = capitalize(val) + value = capitalize(val); break; case 'remote': - value = val === 'remote' ? "Yes" : "No"; + value = val === 'remote' ? 'Yes' : 'No'; break; } return { inline: false, name: capitalize(key.replace('_', ' ')), - value: createMarkdownCodeBlock(value.replace(/```/g,'')), + value: createMarkdownCodeBlock(value.replace(/```/gu, '')), }; }), ]); @@ -304,7 +303,6 @@ const handleJobPostingRequest = async ( } cache.set(entry.key, entry.value, POST_LIMITER); - await interaction.reply({ content: `I've DMed you to start the process.`, ephemeral: true, @@ -317,10 +315,10 @@ const handleJobPostingRequest = async ( const answers = (await form.getResult('guidelines')) as unknown as Answers; - console.log(answers) + console.log(answers); // Just return if the iteration breaks due to invalid input if (!answers) { - cache.del(entry.key) + cache.del(entry.key); return; } @@ -336,13 +334,12 @@ const handleJobPostingRequest = async ( // Store the job post in the cache cache.set(entry.key, entry.value, POST_LIMITER); } catch (error) { - cache.del(entry.key) + cache.del(entry.key); interaction.reply( 'Please temporarily enable direct messages as the bot cares about your privacy.' ); - // eslint-disable-next-line no-console console.error('post.handleJobPostingRequest', error); } @@ -352,64 +349,68 @@ export const jobPostCommand: CommandDataWithHandler = { name: 'post', description: 'Start the process of creating a new job post', handler: handleJobPostingRequest, - guildValidate: (guild) => guild.id === SERVER_ID, + guildValidate: guild => guild.id === SERVER_ID, onAttach: client => { - client.on('interactionCreate', asyncCatch(async interaction => { - if (!interaction.isButton()) { - return; - } - - const [category, userId, type] = interaction.customId.split('🤔'); - - if (category !== 'job') { - return; - } + client.on( + 'interactionCreate', + asyncCatch(async interaction => { + if (!interaction.isButton()) { + return; + } - const message = await interaction.channel.messages.fetch(interaction.message.id) + const [category, userId, type] = interaction.customId.split('🤔'); - if (type === 'delete') { - if (interaction.user.id !== userId) { - interaction.reply({ - content: "You don't have permission to delete this post", - ephemeral: true, - }); + if (category !== 'job') { return; } + const message = await interaction.channel.messages.fetch( + interaction.message.id + ); - await message.delete(); + if (type === 'delete') { + if (interaction.user.id !== userId) { + interaction.reply({ + content: "You don't have permission to delete this post", + ephemeral: true, + }); + return; + } - interaction.reply({ - content: 'Your job post was deleted', - ephemeral: true, - }); - } + await message.delete(); - if (type === 'response') { - await interaction.deferReply({ ephemeral: true }); - const dmChannel = await interaction.user.createDM(); - try { - dmChannel.send({ - content: `The user you want to DM is <@!${userId}>. The job posting can be found here: ${message.url}.\nA copy of the job posting is below for reference as well`, - embeds: message.embeds - }); - interaction.editReply({ - content: 'Please check your dms', - components: [ - new MessageActionRow().addComponents( - new MessageButton() - .setStyle('LINK') - .setURL(`https://discord.com/channels/@me/${dmChannel.id}`) - .setLabel('Go to DMs') - ), - ], + interaction.reply({ + content: 'Your job post was deleted', + ephemeral: true, }); - } catch { - interaction.editReply( - `I tried to send you a DM but your DMs appear to be off. Heres the user you wish to DM <@${userId}>. If that doesn't work, please enable your DMs and try again.` - ); } - } - })); + + if (type === 'response') { + await interaction.deferReply({ ephemeral: true }); + const dmChannel = await interaction.user.createDM(); + try { + dmChannel.send({ + content: `The user you want to DM is <@!${userId}>. The job posting can be found here: ${message.url}.\nA copy of the job posting is below for reference as well`, + embeds: message.embeds, + }); + interaction.editReply({ + content: 'Please check your dms', + components: [ + new MessageActionRow().addComponents( + new MessageButton() + .setStyle('LINK') + .setURL(`https://discord.com/channels/@me/${dmChannel.id}`) + .setLabel('Go to DMs') + ), + ], + }); + } catch { + interaction.editReply( + `I tried to send you a DM but your DMs appear to be off. Heres the user you wish to DM <@${userId}>. If that doesn't work, please enable your DMs and try again.` + ); + } + } + }) + ); }, }; diff --git a/src/v2/commands/post/questions.ts b/src/v2/commands/post/questions.ts index dc6065d6..40b2b8b9 100644 --- a/src/v2/commands/post/questions.ts +++ b/src/v2/commands/post/questions.ts @@ -15,30 +15,25 @@ const isNotShort = (str: string): boolean => const questions = new Map( Object.entries({ remote: { - body: - 'Type `yes` if your position is remote and `no` if it requires a location.', + body: 'Type `yes` if your position is remote and `no` if it requires a location.', validate: (answer: string): boolean => allowCertainAnswers(['yes', 'no'], answer), }, location: { - body: - 'Provide the location in a single message. If you wish not to share the location reply with `no`.', + body: 'Provide the location in a single message. If you wish not to share the location reply with `no`.', validate: isNotEmpty, }, description: { - body: - 'With a single message provide a short description of the job.\nTypically job postings include a description of the job, estimated hours, technical knowledge requirements, scope, and desired qualifications.', + body: 'With a single message provide a short description of the job.\nTypically job postings include a description of the job, estimated hours, technical knowledge requirements, scope, and desired qualifications.', validate: isNotShort, }, compensation_type: { - body: - 'Type `project` if your compensation amount is for the project or type `hourly` if your compensation amount is for an hourly rate.', + body: 'Type `project` if your compensation amount is for the project or type `hourly` if your compensation amount is for an hourly rate.', validate: (answer: string): boolean => allowCertainAnswers(['project', 'hourly'], answer), }, compensation: { - body: - 'Provide the compensation amount for this job using **only** numbers.', + body: 'Provide the compensation amount for this job using **only** numbers.', validate: (answer: string): boolean => { const value = Number.parseFloat(answer.split('$').join('')); const minimalCompensation = Number.parseFloat(MINIMAL_COMPENSATION); @@ -46,8 +41,7 @@ const questions = new Map( }, }, contact: { - body: - 'Provide the method that applicants should apply for your job (e.g., DM, email, website application, etc.) and any additional information that you think would be helpful to potential applicants.', + body: 'Provide the method that applicants should apply for your job (e.g., DM, email, website application, etc.) and any additional information that you think would be helpful to potential applicants.', validation: isNotEmpty, }, }) diff --git a/src/v2/commands/post/questions.v2.ts b/src/v2/commands/post/questions.v2.ts index ebbd9de4..c5710a21 100644 --- a/src/v2/commands/post/questions.v2.ts +++ b/src/v2/commands/post/questions.v2.ts @@ -6,22 +6,29 @@ import { POST_LIMITER_IN_HOURS, } from './env.js'; -const isNotEmpty = (str: string): boolean => str.replace(/\W+/, '').length > 0 -const isNotTooLong = length => (str:string) => { - const isLessThan1000 = str.length < length - if(!isLessThan1000) { - return `Your message is ${str.length} characters long, the limit is ${length} characters, please shorten your input` +const isNotEmpty = (str: string): boolean => str.replace(/\W+/u, '').length > 0; +const isNotTooLong = length => (str: string) => { + const isLessThan1000 = str.length < length; + if (!isLessThan1000) { + return `Your message is ${str.length} characters long, the limit is ${length} characters, please shorten your input`; } - return true -} -const and = <T,K>(...fns:((input:T) => K)[]) => (input:T):K | true=> { - for (const fn of fns) { - const item: unknown = fn(input) - if(item !== true) {return item as K} - } - return true -} -const dollarFormat = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }) + return true; +}; +const and = + <T, K>(...fns: ((input: T) => K)[]) => + (input: T): K | true => { + for (const fn of fns) { + const item: unknown = fn(input); + if (item !== true) { + return item as K; + } + } + return true; + }; +const dollarFormat = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', +}); /* Since checking if the input string is empty is not practical for this use-case, this function checks if the provided input has at the very least `MINIMAL_AMOUNT_OF_WORDS` words in it. @@ -37,10 +44,10 @@ const greeterMessage = 2. Your job must not be related to cryptocurrency, blockchain, NFTs, Web3 technologies, or gambling in any way.\n 3. Your job must provide at least $${MINIMAL_COMPENSATION} in compensation.\n 4. You can only post a job once every ${ - Number.parseInt(POST_LIMITER_IN_HOURS, 10) === 1 - ? 'hour' - : `${POST_LIMITER_IN_HOURS} hours` - }.\n + Number.parseInt(POST_LIMITER_IN_HOURS, 10) === 1 + ? 'hour' + : `${POST_LIMITER_IN_HOURS} hours` + }.\n 5. You agree not to abuse our job posting service or circumvent any server rules, and you understand that doing so will result in a ban.\n`, 'md' )} @@ -88,13 +95,13 @@ export const questions = { location: { type: 'text', body: 'Provide the location in a single message. If you wish not to share the location reply with `no`.', - validate: and<string, string|boolean>(isNotEmpty, isNotTooLong(30)), + validate: and<string, string | boolean>(isNotEmpty, isNotTooLong(30)), next: (value: string): string => 'description', }, description: { type: 'text', body: 'With a single message provide a short description of the job.\nTypically job postings include a description of the job, estimated hours, technical knowledge requirements, scope, and desired qualifications.', - validate: and<string, string|boolean>(isNotShort, isNotTooLong(1000)), + validate: and<string, string | boolean>(isNotShort, isNotTooLong(1000)), next: (value: string): string => 'compensation_type', }, compensation_type: { @@ -128,18 +135,20 @@ export const questions = { } if (value < minimalCompensation) { - return `The minimum compensation is ${dollarFormat.format(minimalCompensation)}.`; + return `The minimum compensation is ${dollarFormat.format( + minimalCompensation + )}.`; } return true; }, format: (value: string): string => dollarFormat.format(Number.parseFloat(value.split('$').join(''))), - next: () => 'contact' + next: () => 'contact', }, contact: { type: 'text', body: 'Provide the method that applicants should apply for your job (e.g., DM, email, website application, etc.) and any additional information that you think would be helpful to potential applicants.', - validate: and<string, string|boolean>(isNotEmpty, isNotTooLong(100)), + validate: and<string, string | boolean>(isNotEmpty, isNotTooLong(100)), }, } as const; diff --git a/src/v2/commands/resource/index.ts b/src/v2/commands/resource/index.ts index d2778331..d303d575 100644 --- a/src/v2/commands/resource/index.ts +++ b/src/v2/commands/resource/index.ts @@ -1,4 +1,8 @@ -import type { ApplicationCommandOptionChoiceData, Client, CommandInteraction } from 'discord.js'; +import type { + ApplicationCommandOptionChoiceData, + Client, + CommandInteraction, +} from 'discord.js'; import { ApplicationCommandOptionType } from '../../../enums.js'; import type { CommandDataWithHandler } from '../../../types'; diff --git a/src/v2/commands/shitpost/index.ts b/src/v2/commands/shitpost/index.ts index fb7de4ae..a881bd5e 100644 --- a/src/v2/commands/shitpost/index.ts +++ b/src/v2/commands/shitpost/index.ts @@ -1,7 +1,12 @@ import type { ApplicationCommandOptionChoiceData } from 'discord.js'; import type { CommandDataWithHandler } from '../../../types'; -import { ADMIN_ROLE_ID, HELPFUL_ROLE_ID, MOD_ROLE_ID, SERVER_ID } from '../../env.js'; +import { + ADMIN_ROLE_ID, + HELPFUL_ROLE_ID, + MOD_ROLE_ID, + SERVER_ID, +} from '../../env.js'; import { map } from '../../utils/map.js'; import type { ValueOrNullary } from '../../utils/valueOrCall.js'; import { valueOrCall } from '../../utils/valueOrCall.js'; @@ -49,19 +54,22 @@ export const shitpostInteraction: CommandDataWithHandler = { const topic = interaction.options.getString('topic'); const replacement = interaction.options.getString('replacement'); const content = aboutMessages.get(topic); - const {roles} = interaction.member; + const { roles } = interaction.member; - if(canUseCommand(roles)){ - await interaction.reply({ephemeral: true, content: "This is only available to helpful members"}) + if (canUseCommand(roles)) { + await interaction.reply({ + ephemeral: true, + content: 'This is only available to helpful members', + }); return; } - if (/<@[!&]?\d+>/.test(replacement)) { + if (/<@[!&]?\d+>/u.test(replacement)) { await interaction.reply({ content: "Please don't try to tag users with this feature!", ephemeral: true, }); - return + return; } if (content) { @@ -92,11 +100,17 @@ export const shitpostInteraction: CommandDataWithHandler = { function canUseCommand(roles) { if (Array.isArray(roles)) { - if (![HELPFUL_ROLE_ID, MOD_ROLE_ID, ADMIN_ROLE_ID].some(role => roles.includes(role))) { + if ( + ![HELPFUL_ROLE_ID, MOD_ROLE_ID, ADMIN_ROLE_ID].some(role => + roles.includes(role) + ) + ) { return true; } } else { - return ![HELPFUL_ROLE_ID, MOD_ROLE_ID, ADMIN_ROLE_ID].some(role => roles.cache.has(HELPFUL_ROLE_ID)) + return ![HELPFUL_ROLE_ID, MOD_ROLE_ID, ADMIN_ROLE_ID].some(role => + roles.cache.has(HELPFUL_ROLE_ID) + ); } return false; } diff --git a/src/v2/commands/warn/index.ts b/src/v2/commands/warn/index.ts index 65cdbbef..dae8e35e 100644 --- a/src/v2/commands/warn/index.ts +++ b/src/v2/commands/warn/index.ts @@ -1,23 +1,13 @@ -import type { - ApplicationCommandOptionChoiceData, - Client, - CommandInteraction, -} from 'discord.js'; -import { zip } from 'domyno'; +import type { Client, CommandInteraction } from 'discord.js'; import Fuse from 'fuse.js'; import type { CommandDataWithHandler } from '../../../types'; import { asyncCatch } from '../../utils/asyncCatch.js'; -import { callOrValue } from '../../utils/callOrValue.js'; -import { map } from '../../utils/map.js'; import { pluckʹ } from '../../utils/pluck.js'; import { _ } from '../../utils/pluralize.js'; -import type { ValueOrNullary } from '../../utils/valueOrCall.js'; -import { valueOrCall } from '../../utils/valueOrCall.js'; - -const listFormatter = new Intl.ListFormat() -const rulesId = '904060678699089950' +const listFormatter = new Intl.ListFormat(); +const rulesId = '904060678699089950'; const rules = [ 'Disrespectful', @@ -42,18 +32,19 @@ const fuse = new Fuse(rules, { keys: ['name', 'value'], }); - export const warn: CommandDataWithHandler = { description: 'Quick response for common "why no" or "why not..." questions', handler: async ( client: Client, interaction: CommandInteraction ): Promise<void> => { - const user =interaction.options.getUser('user') - const rules = interaction.options.getString('rules').split('|') - const reason =interaction.options.getString('reason') + const user = interaction.options.getUser('user'); + const rules = interaction.options.getString('rules').split('|'); + const reason = interaction.options.getString('reason'); - const warnMessage = _`Rule${_.s} ${rules}. Please read the <#${rulesId}>. ${reason ?? ''}` + const warnMessage = _`Rule${_.s} ${rules}. Please read the <#${rulesId}>. ${ + reason ?? '' + }`; await interaction.reply(`Debug Received: user: ${user} @@ -63,27 +54,30 @@ warn msg: ${warnMessage(rules.length)} `); }, onAttach(client) { - client.on('interactionCreate', asyncCatch(async interaction => { - if (!interaction.isAutocomplete()) { - return; - } - const result = interaction.options.getFocused() as string; - console.log(result) - const resultsParts = result.split(/&| and |,|\|/gi).filter(x => x.trim()); - if (resultsParts.length === 0) { - return interaction.respond(rules); - } - const results = resultsParts - .map(x => fuse.search(x)) - .filter(x => x.length); - const perms = removeRepeated<{ value: string, name: string}>(permutations(results)); - const values = perms.map(item => - comb( - item.map(x=>x.item) - ) - ); - await interaction.respond(values); - })); + client.on( + 'interactionCreate', + asyncCatch(async interaction => { + if (!interaction.isAutocomplete()) { + return; + } + const result = interaction.options.getFocused(); + console.log(result); + const resultsParts = result + .split(/&| and |,|\|/giu) + .filter(x => x.trim()); + if (resultsParts.length === 0) { + return interaction.respond(rules); + } + const results = resultsParts + .map(x => fuse.search(x)) + .filter(x => x.length); + const perms = removeRepeated<{ value: string; name: string }>( + permutations(results) + ); + const values = perms.map(item => comb(item.map(x => x.item))); + await interaction.respond(values); + }) + ); }, name: 'warn', options: [ @@ -109,13 +103,17 @@ warn msg: ${warnMessage(rules.length)} }; function permutations<T>(items: T[][]) { - if(items.length === 0) {return []} + if (items.length === 0) { + return []; + } const [item, ...rest] = items; - if (rest.length === 0) {return item.map(x => [x]);} + if (rest.length === 0) { + return item.map(x => [x]); + } const latterPers = permutations(rest); - return item.flatMap(item => latterPers.map(x => [item, ...x])) + return item.flatMap(item => latterPers.map(x => [item, ...x])); } function comb(items: { name: string; value: string }[]) { @@ -130,10 +128,12 @@ function comb(items: { name: string; value: string }[]) { } function removeRepeated<T>(arr: Fuse.FuseResult<T>[][]) { - console.log(arr) + console.log(arr); return arr.filter(x => { - const s = new Set(x.map(i=>i.refIndex)) - if(s.size !== x.length) {return false} - return true - }) + const s = new Set(x.map(i => i.refIndex)); + if (s.size !== x.length) { + return false; + } + return true; + }); } diff --git a/src/v2/commands/whyno/handlers/channel.ts b/src/v2/commands/whyno/handlers/channel.ts index e79d1e0e..8fa4682b 100644 --- a/src/v2/commands/whyno/handlers/channel.ts +++ b/src/v2/commands/whyno/handlers/channel.ts @@ -2,5 +2,5 @@ export const channel: [string, string] = [ 'channel', `> Why isn't there a channel for \`foo\`? -It's usually because we don't get enough questions or discussion on foo to warrant a dedicated channel. If we see an uptick in conversations around foo, we will discuss opening a channel.` +It's usually because we don't get enough questions or discussion on foo to warrant a dedicated channel. If we see an uptick in conversations around foo, we will discuss opening a channel.`, ]; diff --git a/src/v2/commands/whyno/index.ts b/src/v2/commands/whyno/index.ts index 92036235..f0068f49 100644 --- a/src/v2/commands/whyno/index.ts +++ b/src/v2/commands/whyno/index.ts @@ -1,4 +1,8 @@ -import type { ApplicationCommandOptionChoiceData, Client, CommandInteraction } from 'discord.js'; +import type { + ApplicationCommandOptionChoiceData, + Client, + CommandInteraction, +} from 'discord.js'; import type { CommandDataWithHandler } from '../../../types'; import { map } from '../../utils/map.js'; @@ -9,7 +13,7 @@ import { jquery } from './handlers/jquery.js'; const whynoMessages = new Map<string, ValueOrNullary<string>>([ jquery, - channel + channel, ]); const mapTransformToChoices = map( @@ -20,16 +24,18 @@ const mapTransformToChoices = map( ); export const whynoInteraction: CommandDataWithHandler = { - description: - 'Quick response for common "why no" or "why not..." questions', - handler: async (client: Client, interaction: CommandInteraction): Promise<void> => { + description: 'Quick response for common "why no" or "why not..." questions', + handler: async ( + client: Client, + interaction: CommandInteraction + ): Promise<void> => { const topic = interaction.options.get('topic').value as string; const user = interaction.options.getUser('tag'); const content = whynoMessages.get(topic); if (content) { interaction.reply( - `${user ? `${user}\n` : ''} ${valueOrCall(content).trim()}` + `${user ? `${user.toString()}\n` : ''} ${valueOrCall(content).trim()}` ); return; } diff --git a/src/v2/helpful_role/db_model.ts b/src/v2/helpful_role/db_model.ts index ef8d0070..30e85301 100644 --- a/src/v2/helpful_role/db_model.ts +++ b/src/v2/helpful_role/db_model.ts @@ -1,6 +1,6 @@ import mongoose from 'mongoose'; -const { model, Schema } = mongoose +const { model, Schema } = mongoose; const schema = new Schema({ guild: { diff --git a/src/v2/helpful_role/point_decay.ts b/src/v2/helpful_role/point_decay.ts index d9175941..e496a87c 100644 --- a/src/v2/helpful_role/point_decay.ts +++ b/src/v2/helpful_role/point_decay.ts @@ -16,21 +16,25 @@ import HelpfulRoleMember from './db_model.js'; import type { IUser } from '.'; type DecayCache = { - type: "POINT_DECAY", - user: "", - guild: string, + type: 'POINT_DECAY'; + user: ''; + guild: string; meta: { - lastDecay: number - } -} + lastDecay: number; + }; +}; const HOUR_IN_MS = 3_600_000; -let lastDecay -let timeout +let lastDecay; +let timeout; -export const decay = async ( - { guild, userId }: {guild:Guild, userId?: string}, -): Promise<void> => { +export const decay = async ({ + guild, + userId, +}: { + guild: Guild; + userId?: string; +}): Promise<void> => { try { const users: IUser[] = await HelpfulRoleMember.find({ guild: guild.id, @@ -46,6 +50,8 @@ export const decay = async ( user.points = nextPoints > 0 ? nextPoints : 0; + // This isn't time sensitive and it's better for us to save some than none here + // eslint-disable-next-line no-await-in-loop await user.save(); const member = guild.members.cache.get(user.user); @@ -76,7 +82,7 @@ export const decay = async ( ].filter(Boolean); await modChannel.send({ - embeds:[ + embeds: [ createEmbed({ description: `The point decay affected ${users.length} user${ users.length === 1 ? '' : 's' @@ -85,79 +91,88 @@ export const decay = async ( footerText: 'Point Decay System', provider: 'spam', title: 'Point Decay Alert', - }).embed - ] + }).embed, + ], }); - } catch (error) { // eslint-disable-next-line no-console console.error('catch -> decay(msg):', error); } }; -export const getTimeDiffToDecay = async (): Promise<{ diff: number; timestamp: number }> => { - if(!lastDecay) { - return new Promise((resolve) => { +export const getTimeDiffToDecay = async (): Promise<{ + diff: number; + timestamp: number; +}> => { + if (!lastDecay) { + return new Promise(resolve => { setTimeout(async () => { - resolve(await getTimeDiffToDecay()) - }, 500) - }) + resolve(await getTimeDiffToDecay()); + }, 500); + }); } const timestamp = Date.now(); return { - diff: (timestamp - lastDecay) / (HOUR_IN_MS), + diff: (timestamp - lastDecay) / HOUR_IN_MS, timestamp, }; }; -const pointDecaySystem = async (decayData: {guild: Guild, userId?:string}): Promise<void> => { +const pointDecaySystem = async (decayData: { + guild: Guild; + userId?: string; +}): Promise<void> => { const { diff, timestamp } = await getTimeDiffToDecay(); - const timer = Number.parseFloat(POINT_DECAY_TIMER) + const timer = Number.parseFloat(POINT_DECAY_TIMER); if (diff >= timer) { - await saveLastDecay(timestamp) + await saveLastDecay(timestamp); await decay(decayData); - setDecayTimeout(decayData.guild, (Number.parseFloat(POINT_DECAY_TIMER) ?? .5) * HOUR_IN_MS) + setDecayTimeout( + decayData.guild, + (Number.parseFloat(POINT_DECAY_TIMER) ?? 0.5) * HOUR_IN_MS + ); } else { - setDecayTimeout(decayData.guild, (timer - diff) * HOUR_IN_MS) + setDecayTimeout(decayData.guild, (timer - diff) * HOUR_IN_MS); } }; -function setDecayTimeout (guild: Guild, ms: number) { - clearTimeout(timeout) +function setDecayTimeout(guild: Guild, ms: number) { + clearTimeout(timeout); timeout = setTimeout(() => { - pointDecaySystem({guild}) - }, ms) + pointDecaySystem({ guild }); + }, ms); } export default pointDecaySystem; -export const loadLastDecayFromDB = async ():Promise<void> => { - const cache = await get({ +export const loadLastDecayFromDB = async (): Promise<void> => { + const cache = (await get({ type: 'POINT_DECAY', - user: "", + user: '', guild: SERVER_ID, - }) as DecayCache + })) as DecayCache; - lastDecay = cache?.meta?.lastDecay ?? -1 -} + lastDecay = cache?.meta?.lastDecay ?? -1; +}; -export const saveLastDecay = async (timestamp: number = Date.now()): Promise<void> => { +export const saveLastDecay = async ( + timestamp: number = Date.now() +): Promise<void> => { await upsert({ expiresAt: Number.MAX_SAFE_INTEGER, guild: SERVER_ID, type: 'POINT_DECAY', - user: "", + user: '', meta: { - lastDecay: timestamp - } - }) + lastDecay: timestamp, + }, + }); - lastDecay = timestamp - -} + lastDecay = timestamp; +}; diff --git a/src/v2/helpful_role/point_handler.ts b/src/v2/helpful_role/point_handler.ts index 87aac125..f13b1f89 100644 --- a/src/v2/helpful_role/point_handler.ts +++ b/src/v2/helpful_role/point_handler.ts @@ -16,7 +16,7 @@ import type { IUser } from '.'; const grantHelpfulRole = async (user: GuildMember, msg: Message) => { // Check if the user has the role - if (user.roles.cache.find(r => r.id === HELPFUL_ROLE_ID)) { + if (user.roles.cache.some(r => r.id === HELPFUL_ROLE_ID)) { return; } @@ -102,7 +102,10 @@ const pointHandler = async ( ); // Check if the user has enough points to be given the helpful role - if (!guildMember.roles.cache.has(HELPFUL_ROLE_EXEMPT_ID) && user.points >= Number.parseInt(HELPFUL_ROLE_POINT_THRESHOLD)) { + if ( + !guildMember.roles.cache.has(HELPFUL_ROLE_EXEMPT_ID) && + user.points >= Number.parseInt(HELPFUL_ROLE_POINT_THRESHOLD) + ) { await grantHelpfulRole(guildMember, msg); } diff --git a/src/v2/index.ts b/src/v2/index.ts index 58f92462..49c15c21 100644 --- a/src/v2/index.ts +++ b/src/v2/index.ts @@ -28,12 +28,16 @@ import { } from './autorespond/thanks/threadThanks.js'; import { limitFnByUser } from './cache/index.js'; import { registerCommands } from './commands/index.js'; -import pointDecaySystem, { loadLastDecayFromDB } from './helpful_role/point_decay.js'; +import pointDecaySystem, { + loadLastDecayFromDB, +} from './helpful_role/point_decay.js'; import { registerMessageContextMenu } from './message_context/index.js'; +import { attach as attachOnboarding } from './modules/onboarding/index.js'; +import { getOnboardingStart } from './modules/onboarding/utils/onboardingStart.js'; import { registerUserContextMenu } from './user_context/index.js'; -import { asyncCatch } from './utils/asyncCatch.js'; import { stripMarkdownQuote } from './utils/content_format.js'; + const NON_COMMAND_MSG_TYPES = new Set([ 'GUILD_TEXT', 'GUILD_PRIVATE_THREAD', @@ -48,7 +52,7 @@ if (IS_PROD) { // This date is used to check if the message's been created before the bot's started export const startTime = new Date(); -loadLastDecayFromDB() +loadLastDecayFromDB(); const client = new Client({ intents: [ @@ -83,14 +87,20 @@ client.on('ready', () => { client.once('ready', async (): Promise<void> => { pointDecaySystem({ - guild: client.guilds.resolve(SERVER_ID) - }) + guild: client.guilds.resolve(SERVER_ID), + }); registerCommands(client); registerUserContextMenu(client); registerMessageContextMenu(client); attachUndoThanksListener(client); attachThreadThanksHandler(client); attachThreadClose(client); + if (await getOnboardingStart()) { + console.info("Onboarding functionality added") + attachOnboarding(client); + } else { + console.info("Onboarding functionality not added") + } void client.user.setActivity(`@${client.user.username} --help`); diff --git a/src/v2/message_context/index.ts b/src/v2/message_context/index.ts index d2ca7bbc..fb0f6e84 100644 --- a/src/v2/message_context/index.ts +++ b/src/v2/message_context/index.ts @@ -1,5 +1,3 @@ - - import type { Client } from 'discord.js'; import { Collection } from 'discord.js'; @@ -9,14 +7,18 @@ import { Collection } from 'discord.js'; const guildCommands = new Collection([]); // placeholder for now -export const registerMessageContextMenu = async (client: Client): Promise<void> => { - const existingCommands = await client.application.commands.fetch() - existingCommands.sweep(x => x.type !== "MESSAGE") +export const registerMessageContextMenu = async ( + client: Client +): Promise<void> => { + const existingCommands = await client.application.commands.fetch(); + existingCommands.sweep(x => x.type !== 'MESSAGE'); - client.application.commands.set([]) + client.application.commands.set([]); - client.on("interactionCreate", interaction => { - if(!interaction.isContextMenu() || interaction.targetType !== "MESSAGE") {return} - console.log(interaction) - }) -} + client.on('interactionCreate', interaction => { + if (!interaction.isContextMenu() || interaction.targetType !== 'MESSAGE') { + return; + } + console.log(interaction); + }); +}; diff --git a/src/v2/modules/mod/commands/index.ts b/src/v2/modules/mod/commands/index.ts new file mode 100644 index 00000000..0fde1c27 --- /dev/null +++ b/src/v2/modules/mod/commands/index.ts @@ -0,0 +1,57 @@ +import type { CommandDataWithHandler } from '../../../../types'; +import { SERVER_ID } from '../../../env.js'; +import { debugOnboarding } from './onboardingBegin.js'; +import { setupOnboardingMsg } from './onboardingMsg.js'; +import { setupRoles } from './roles.js'; + +export const setupCommands: CommandDataWithHandler = { + name: 'setup', + description: 'Setup Commands', + async handler(client, interaction) { + const cmd = [ + interaction.options.getSubcommandGroup(), + interaction.options.getSubcommand(), + ].join('.'); + switch (cmd) { + case 'roles.message': + await setupRoles(interaction); + break; + case 'onboarding.debug': + debugOnboarding(interaction); + break; + + case 'onboarding.message': + setupOnboardingMsg(interaction); + break; + } + }, + guildValidate: guild => guild.id === SERVER_ID, + defaultPermission: false, + options: [ + { + name: 'roles', + type: 'SUB_COMMAND_GROUP', + description: 'Post the role change post here', + options: [ + { + name: 'message', + type: 'SUB_COMMAND', + description: 'Post the onboarding command here', + }, + ], + }, + { + name: 'onboarding', + type: 'SUB_COMMAND_GROUP', + description: 'onboarding setup commands', + options: [ + { + name: 'message', + type: 'SUB_COMMAND', + description: 'Post the onboarding command here', + }, + { name: 'debug', type: 'SUB_COMMAND', description: 'For now... debug info' }, + ], + }, + ], +}; diff --git a/src/v2/modules/mod/commands/onboardingBegin.ts b/src/v2/modules/mod/commands/onboardingBegin.ts new file mode 100644 index 00000000..822f16e8 --- /dev/null +++ b/src/v2/modules/mod/commands/onboardingBegin.ts @@ -0,0 +1,30 @@ +import type { CommandInteraction} from 'discord.js'; + +import { + NEW_USER_ROLE, + ONBOARDING_CHANNEL, + JOIN_LOG_CHANNEL, + INTRO_CHANNEL, + INTRO_ROLE, +} from '../../../env.js'; +// import { attach } from '../../onboarding/index'; +// import { setOnboardingStart } from '../../onboarding/utils/onboardingStart'; + +export async function debugOnboarding( + interaction: CommandInteraction +): Promise<void> { + interaction.reply({ + content: ` +DEBUG: +New User Role: <@&${NEW_USER_ROLE}> +Onboarding Channel: <#${ONBOARDING_CHANNEL}> +Join Log Channel: <#${JOIN_LOG_CHANNEL}> +Intro Channel: <#${INTRO_CHANNEL}> +New User Role: <@&${INTRO_ROLE}> + `, + }); + + // const foo = await setOnboardingStart(); + + // await attach(interaction.client); +} diff --git a/src/v2/modules/mod/commands/onboardingMsg.ts b/src/v2/modules/mod/commands/onboardingMsg.ts new file mode 100644 index 00000000..9f18a6de --- /dev/null +++ b/src/v2/modules/mod/commands/onboardingMsg.ts @@ -0,0 +1,17 @@ +import type { CommandInteraction } from 'discord.js'; + +export async function setupOnboardingMsg( + interaction: CommandInteraction +): Promise<void> { + await interaction.deferReply({ ephemeral: true }); + + interaction.channel.send({ + content: ` +:wave: Hi! If you're in this channel then you should have been invited to a private thread to begin the onboarding. If not, please give it a moment as the bot might be down or busy. + `, + }); + + await interaction.editReply({ + content: 'Done.', + }); +} diff --git a/src/v2/modules/mod/commands/roles.ts b/src/v2/modules/mod/commands/roles.ts new file mode 100644 index 00000000..b52143fe --- /dev/null +++ b/src/v2/modules/mod/commands/roles.ts @@ -0,0 +1,71 @@ +import type { CommandInteraction } from 'discord.js'; +import { MessageActionRow, MessageButton, MessageEmbed } from 'discord.js'; +import { chunk } from 'domyno'; + +import { map } from '../../../utils/map.js'; +import { pipe } from '../../../utils/pipe.js'; +import { NOTIFY_ROLES } from '../../roles/consts/notifyRoles.js'; +import { ROLES } from '../../roles/consts/roles.js'; + +const generateButtons = (roles: typeof ROLES | typeof NOTIFY_ROLES) => + roles.map(item => + new MessageButton() + .setCustomId(`roles🤔toggle🤔${item.name}`) + .setLabel(item.name) + .setStyle('SECONDARY') + .setEmoji(item.emoji) + ); +const chunkAndRowify = pipe< + Iterable<MessageButton>, + Iterable<MessageActionRow> +>([chunk(5), map(x => new MessageActionRow().addComponents(...x))]); + +export async function setupRoles( + interaction: CommandInteraction +): Promise<void> { + await interaction.deferReply({ ephemeral: true }); + + interaction.channel.send({ + content: + 'You will need embeds enabled to interact with several features in this server.', + embeds: [ + new MessageEmbed() + .setTitle('Assign Yourself Roles Below') + .setDescription( + `Click on the reaction that corresponds with the role you're interested in.` + ) + .setColor('GREEN'), + new MessageEmbed() + .setColor('GREEN') + .setTitle('⭐ IMPORTANT: Access Role-Locked Channels') + .setDescription( + 'In order to see any general channel, you must have at least one role from the list below (excluding Community Announcement roles). If you would like access to a technology-specific channel, you must add that role to your profile. Add as many roles as you like.' + ), + new MessageEmbed() + .setColor('YELLOW') + .setDescription( + `:warning: Note: You can add and remove roles faster using the \`/roles change\` command` + ), + ], + components: [ + ...chunkAndRowify([ + ...generateButtons(ROLES), + new MessageButton() + .setCustomId('roles🤔toggle🤔All Development') + .setLabel('All Channels') + .setStyle('SECONDARY') + .setEmoji('🤓'), + ]), + ], + }); + + await interaction.channel.send({ + content: + 'We also have some roles for if you wish to be notified about various optional announcements, which you can opt into here:', + components: [...chunkAndRowify(generateButtons(NOTIFY_ROLES))], + }); + + await interaction.editReply({ + content: 'Done.', + }); +} diff --git a/src/v2/modules/mod/index.ts b/src/v2/modules/mod/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/v2/modules/onboarding/consts/rules.ts b/src/v2/modules/onboarding/consts/rules.ts new file mode 100644 index 00000000..a7f31587 --- /dev/null +++ b/src/v2/modules/onboarding/consts/rules.ts @@ -0,0 +1,62 @@ +export const rules = [ + { + title: `Be respectful.`, + description: `Remember the human! No insulting, no bullying, no dogpiling, no name-calling. Do speak in a way that builds up and encourages both the people you are speaking with and the server as a whole. `, + }, + { + title: `Follow the Discord Terms of Service and Community Guidelines.`, + description: `This includes but is not limited to asking about hacking, cheating, modifications to the discord client, and any discussion about piracy.`, + }, + { + title: `Keep discussions limited to the most appropriate channel.`, + description: `If multiple channels fit the criteria, pick one; if unsure, feel free to ask. Job postings must be posted in job-postings using the /post command.`, + }, + { + title: `Do not share NSFW content.`, + description: `Our users aren't all 18+`, + }, + { + title: `Do not spam.`, + description: `This includes but is not limited to: within the same channel, across multiple channels, or using large images`, + }, + { + title: `Do not self-promote without contributing to the community.`, + description: `Joining the community with the purpose to promote your own projects or business is disrespectful.`, + }, + { + title: `Do not reveal any personal information about another user.`, + description: `Additionally, we encourage you to consider what you share about yourself and what consequences sharing personal information may have. `, + }, + { + title: `Do not DM users without prior permission.`, + description: `Ensure that you have explicit permission to DM a user before DMing them.`, + }, + { + title: `Do not use inappropriate usernames.`, + description: `This includes, but is not limited to: hard-to-tag, impersonations, advertisements, offensive, hoisting, or excessively changed names.`, + }, + { + title: `Do not attempt to mass ping.`, + description: `In general, please DM @:police_officer: Modmail to report issues.`, + }, + { + title: `Do not participate in academic dishonesty.`, + description: `You can ask for help understanding homework but we will not do the work for you. `, + }, + { + title: `Do not post shortened URLs.`, + description: `We don't want to have to check every shortened url to see if it's malicious`, + }, + { + title: `Do not upload code directly to Discord.`, + description: `Use the /please code command to find third-party sites that can effectively share your code with others.`, + }, + { + title: `Do not DM users to avoid enforcement of these rules.`, + description: `Self explanatory.`, + }, + { + title: `Do not advertise Web3 technologies.`, + description: `You may discuss Web3 technologies, but you cannot share links to or solicit work for these technologies.`, + }, +]; diff --git a/src/v2/modules/onboarding/db/user_state.ts b/src/v2/modules/onboarding/db/user_state.ts new file mode 100644 index 00000000..ceecc69b --- /dev/null +++ b/src/v2/modules/onboarding/db/user_state.ts @@ -0,0 +1,53 @@ +import type { Document } from 'mongoose'; +import mongoose from 'mongoose'; + +const { model, Schema } = mongoose; +const schema = new Schema( + { + guild: { + type: String, + required: true, + }, + userId: { + type: String, + required: true, + }, + rolesOnLeave: { + type: [ + { + name: String, + id: String, + }, + ], + }, + rulesAgreedDate: { + type: Date, + }, + state: { + type: String, + required: true, + }, + threadId: { + type: String, + }, + }, + { timestamps: true } +); + +export const UserState = model<UserStateType>('user_state', schema); +export type OnboardingState = + | 'START' + | 'INTRODUCTION' + | 'ROLE_SELECTION' + | 'ONBOARDED'; + +export type UserStateType = Document & { + guild: string; + userId: string; + rulesAgreedDate?: Date; + rolesOnLeave?: { name: string; id: string }[]; + state: OnboardingState; + threadId?: string; + createdAt: Date; + updatedAt: Date; +}; diff --git a/src/v2/modules/onboarding/events/handleIntroductionMsg.ts b/src/v2/modules/onboarding/events/handleIntroductionMsg.ts new file mode 100644 index 00000000..4895b971 --- /dev/null +++ b/src/v2/modules/onboarding/events/handleIntroductionMsg.ts @@ -0,0 +1,35 @@ +import type { Message } from 'discord.js'; + +import { INTRO_CHANNEL, NEW_USER_ROLE } from '../../../env.js'; +import { UserState } from '../db/user_state.js'; +import { continueOnboarding } from '../utils/continueOnboarding.js'; +import { getThread } from '../utils/getThread.js'; + +export const handleIntroductionMsg = async (msg: Message): Promise<void> => { + if ( + msg.channelId === INTRO_CHANNEL && + msg.member.roles.cache.some(x => x.id === NEW_USER_ROLE) + ) { + const oldState = await UserState.findOne({ + guild: msg.guild.id, + userId: msg.author.id, + }); + + const thread = await getThread(msg.guild, oldState.threadId); + + const pinned = await thread.messages.fetchPinned(); + const introMsg = pinned.last(); + + introMsg.edit({ + components: [], + }); + introMsg.reply({ + content: `Thanks for introducing yourself ${msg.member}!`, + }); + + oldState.state = 'ROLE_SELECTION'; + await Promise.all([oldState.save(), introMsg.unpin()]); + + continueOnboarding(msg.guild, msg.member, oldState, false); + } +}; diff --git a/src/v2/modules/onboarding/events/handleMemberLeave.ts b/src/v2/modules/onboarding/events/handleMemberLeave.ts new file mode 100644 index 00000000..882870ae --- /dev/null +++ b/src/v2/modules/onboarding/events/handleMemberLeave.ts @@ -0,0 +1,37 @@ +import type { GuildMember } from 'discord.js'; + +import { SERVER_ID } from '../../../env.js'; +import { UserState } from '../db/user_state.js'; + +export const handleMemberLeave = async (member: GuildMember): Promise<void> => { + const { user, roles } = member; + + const oldState = await UserState.findOne({ + guild: SERVER_ID, + userId: user.id, + }); + + const memRoles = roles.cache.filter(x => x.name !== '@everyone'); + + if (!oldState) { + if (memRoles.size === 0) { + return; + } + UserState.create({ + guild: SERVER_ID, + userId: user.id, + state: 'ONBOARDED', + rolesOnLeave: memRoles.map(x => ({ name: x.name, id: x.id })), + }); + return; + } + + if (memRoles.size === 0 && !oldState.threadId) { + await oldState.deleteOne(); + return; + } + oldState.rolesOnLeave = roles.cache + .filter(x => x.name !== '@everyone') + .map(x => ({ name: x.name, id: x.id })); + oldState.save(); +}; diff --git a/src/v2/modules/onboarding/events/handleNewMember.ts b/src/v2/modules/onboarding/events/handleNewMember.ts new file mode 100644 index 00000000..c38211a6 --- /dev/null +++ b/src/v2/modules/onboarding/events/handleNewMember.ts @@ -0,0 +1,105 @@ +import type { GuildMember, Guild, TextChannel } from 'discord.js'; +import { MessageEmbed, MessageActionRow, MessageButton } from 'discord.js'; + +import { NEW_USER_ROLE, ONBOARDING_CHANNEL, SERVER_ID } from '../../../env.js'; +import { rules } from '../consts/rules.js'; +import { UserState } from '../db/user_state.js'; +import { continueOnboarding } from '../utils/continueOnboarding.js'; +import { sneakPin } from '../utils/sneakPin.js'; + +export const handleNewMember = async (member: GuildMember): Promise<void> => { + const { guild, user, roles } = member; + + const oldState = await UserState.findOne({ + guild: SERVER_ID, + userId: user.id, + }); + + if (oldState?.rolesOnLeave) { + const guildRoles = await guild.roles.fetch(); + const ids = new Set(oldState.rolesOnLeave.map(({ id }) => id)); + const names = new Set(oldState.rolesOnLeave.map(({ name }) => name)); + roles.set(guildRoles.filter(x => names.has(x.name) || ids.has(x.id))); + } + + if (oldState?.state === 'ONBOARDED') { + return; // Don't reonboard people who have been onboarded + } + + await roles.add(NEW_USER_ROLE); + + if (!oldState) { + await beginOnboarding(guild, member); + return; + } + + await continueOnboarding(guild, member, oldState, true); + + // False Positive + // eslint-disable-next-line no-promise-executor-return + await new Promise<void>(resolve => setTimeout(resolve, 10_000)); +}; + +async function beginOnboarding(guild: Guild, member: GuildMember) { + const onboardingChannel = guild.channels.resolve( + ONBOARDING_CHANNEL + ) as TextChannel; + + const thread = await createOnboardingThread(onboardingChannel, member); + + const state = await UserState.create({ + guild: guild.id, + userId: member.user.id, + state: 'START', + threadId: thread.id, + }); + await thread.send( + `Hi ${member.toString()}, welcome to ${ + guild.name + }! Before you can get access to the rest of the server, we just need to go over a few things. + +First, to be able to interact with several of the features of this server, you **will** need embeds enabled. +Second:` + ); + + const rulesMsg = await thread.send({ + embeds: [ + new MessageEmbed() + .setTitle('📋 Our Community rules') + .setColor('GOLD') + .addFields( + rules.map((x, i) => ({ + name: `${i + 1}. ${x.title}`, + value: x.description, + })) + ), + ], + components: [ + new MessageActionRow().addComponents([ + new MessageButton() + .setStyle('SECONDARY') + .setLabel('Just giving you a bit of time to read the rules...') + .setEmoji('⏲') + .setCustomId('onboarding🤔rules_agreed') + .setDisabled(true), + ]), + ], + }); + + await sneakPin(rulesMsg); + await continueOnboarding(guild, member, state, false); +} +async function createOnboardingThread( + onboardingChannel: TextChannel, + member: GuildMember +) { + const obj = { + name: `Hi ${member.displayName}! 👋`, + reason: `Onboarding ${member.toString()}`, + } + try { + return await onboardingChannel.threads.create({...obj, type: 'GUILD_PRIVATE_THREAD'}); + } catch { + return await onboardingChannel.threads.create({...obj, type: 'GUILD_PUBLIC_THREAD'}); + } +} diff --git a/src/v2/modules/onboarding/events/handleNotifyRolesSelected.ts b/src/v2/modules/onboarding/events/handleNotifyRolesSelected.ts new file mode 100644 index 00000000..afb73a3e --- /dev/null +++ b/src/v2/modules/onboarding/events/handleNotifyRolesSelected.ts @@ -0,0 +1,54 @@ +import type { GuildMember, Interaction, Message } from 'discord.js'; + +import { UserState } from '../db/user_state.js'; +import { continueOnboarding } from '../utils/continueOnboarding.js'; + +export const handleNotifyRolesSelected = async ( + interaction: Interaction +): Promise<void> => { + if (!interaction.isSelectMenu()) { + return; + } + + const [type, subType] = interaction.customId.split('🤔'); + if (type !== 'onboarding' || subType !== 'notify_roles') { + return; + } + + const message = interaction.message as Message; + const member = interaction.member as GuildMember; + const picked = new Set(interaction.values); + + const { guild, user } = interaction; + + const pickedRoles = guild.roles.cache.filter(x => picked.has(x.name)); + + member.roles.add(pickedRoles); + interaction.reply({ + content: `:loudspeaker: I've given you the roles to notify you about ${new Intl.ListFormat().format( + [...pickedRoles.values()].map(x => x.name) + )}`, + }); + const msg = interaction.message as Message; + + msg.edit({ + components: msg.components.map(x => { + x.components[0].disabled = true; + return x; + }), + }); + + const oldState = await UserState.findOne({ + guild: guild.id, + userId: user.id, + }); + + if (oldState.state !== 'ONBOARDED') { + oldState.state = 'ONBOARDED'; + await oldState.save(); + + await message.unpin(); + + continueOnboarding(guild, member, oldState); + } +}; diff --git a/src/v2/modules/onboarding/events/handleRoleSelected.ts b/src/v2/modules/onboarding/events/handleRoleSelected.ts new file mode 100644 index 00000000..0c2fb3e6 --- /dev/null +++ b/src/v2/modules/onboarding/events/handleRoleSelected.ts @@ -0,0 +1,57 @@ +import type { GuildMember, Interaction, Message } from 'discord.js'; + +import { UserState } from '../db/user_state.js'; +import { continueOnboarding } from '../utils/continueOnboarding.js'; + +export const handleRoleSelected = async ( + interaction: Interaction +): Promise<void> => { + if (!interaction.isSelectMenu() && !interaction.isButton()) { + return; + } + + const [type, subType, name] = interaction.customId.split('🤔'); + if (type !== 'onboarding' || subType !== 'roles') { + return; + } + + const message = interaction.message as Message; + const member = interaction.member as GuildMember; + const picked = new Set(interaction.isButton() ? [name] : interaction?.values); + + const { guild, user } = interaction; + const pickedRoles = guild.roles.cache.filter( + x => picked.has(x.name) || x.name === 'All Development' + ); + + await member.roles.add(pickedRoles); + + const oldState = await UserState.findOne({ + guild: guild.id, + userId: user.id, + }); + + const msg = interaction.message as Message; + + await interaction.reply({ + content: `I've given you the roles to give you access the channels for ${new Intl.ListFormat().format( + [...pickedRoles.values()].map(x => x.name) + )}`, + }); + + await msg.edit({ + components: msg.components.map(x => { + x.components[0].disabled = true; + return x; + }), + }); + + if (oldState.state !== 'ONBOARDED') { + oldState.state = 'ONBOARDED'; + await oldState.save(); + + await message.unpin(); + + continueOnboarding(guild, member, oldState); + } +}; diff --git a/src/v2/modules/onboarding/events/handleRulesAgree.ts b/src/v2/modules/onboarding/events/handleRulesAgree.ts new file mode 100644 index 00000000..e6695fc4 --- /dev/null +++ b/src/v2/modules/onboarding/events/handleRulesAgree.ts @@ -0,0 +1,50 @@ +import type { GuildMember, Interaction, Message } from 'discord.js'; +import { MessageActionRow, MessageButton } from 'discord.js'; + +import { UserState } from '../db/user_state.js'; +import { continueOnboarding } from '../utils/continueOnboarding.js'; + +export const handleRulesAgree = async ( + interaction: Interaction +): Promise<void> => { + if (!interaction.isButton()) { + return; + } + + const [type, subType] = interaction.customId.split('🤔'); + if (type !== 'onboarding' || subType !== 'rules_agreed') { + return; + } + + const message = interaction.message as Message; + + await message.edit({ + components: [ + new MessageActionRow().addComponents([ + new MessageButton() + .setStyle('SUCCESS') + .setLabel(`You've agreed to the rules.`) + .setCustomId('onboarding🤔rules_agreed') + .setDisabled(true), + ]), + ], + }); + interaction.reply('Great! Just a couple more steps.'); + + const oldState = await UserState.findOne({ + guild: interaction.guild.id, + userId: interaction.user.id, + }); + + oldState.state = 'INTRODUCTION'; + oldState.rulesAgreedDate = new Date(); + await oldState.save(); + + await message.unpin(); + + continueOnboarding( + interaction.guild, + interaction.member as GuildMember, + oldState + ); +}; diff --git a/src/v2/modules/onboarding/events/handleSkipIntro.ts b/src/v2/modules/onboarding/events/handleSkipIntro.ts new file mode 100644 index 00000000..c8f9417f --- /dev/null +++ b/src/v2/modules/onboarding/events/handleSkipIntro.ts @@ -0,0 +1,52 @@ +import type { Interaction, GuildMember } from 'discord.js'; + +import { UserState } from '../db/user_state.js'; +import { continueOnboarding } from '../utils/continueOnboarding.js'; +import { getThread } from '../utils/getThread.js'; + +export const handleSkipIntro = async ( + interaction: Interaction +): Promise<void> => { + if (!interaction.isButton()) { + return; + } + + const [type, subtype] = interaction.customId.split('🤔'); + + if (type !== 'onboarding' || subtype !== 'skip_intro') { + return; + } + + const oldState = await UserState.findOne({ + guild: interaction.guild.id, + userId: interaction.user.id, + }); + + const thread = await getThread(interaction.guild, oldState.threadId); + + const pinned = await thread.messages.fetchPinned(); + const introMsg = pinned.last(); + + try { + await interaction.reply( + 'No worries, you can always introduce yourself later' + ); + } catch {} + + try { + await introMsg?.edit({ + components: [], + }); + } catch {} + + oldState.state = 'ROLE_SELECTION'; + + await Promise.all([oldState.save(), introMsg.unpin()]); + + continueOnboarding( + interaction.guild, + interaction.member as GuildMember, + oldState, + false + ); +}; diff --git a/src/v2/modules/onboarding/events/handleThreadArchived.ts b/src/v2/modules/onboarding/events/handleThreadArchived.ts new file mode 100644 index 00000000..672f8796 --- /dev/null +++ b/src/v2/modules/onboarding/events/handleThreadArchived.ts @@ -0,0 +1,51 @@ +import type { ThreadChannel } from 'discord.js'; + +import { INTRO_ROLE, ONBOARDING_CHANNEL, NEW_USER_ROLE } from '../../../env.js'; +import { UserState } from '../db/user_state.js'; + +export async function handleThreadArchived( + oldThread: ThreadChannel, + thread: ThreadChannel +): Promise<void> { + // ignore it if it's not an archive change + if (oldThread.parentId !== ONBOARDING_CHANNEL) { + return; + } + if (oldThread.archived || !thread.archived) { + return; + } + + const userState = await UserState.findOne({ + threadId: oldThread.id, + }); + + if (!userState) { + return; + } + + const member = await oldThread.guild.members.fetch(userState.userId); + + if (!member) { + return; + } + + if (userState.state === 'ONBOARDED') { + member.roles.remove([NEW_USER_ROLE, INTRO_ROLE]); + } else { + try { + const dmChannel = await member.createDM(); + await dmChannel.send({ + content: `You've been kicked from the Web Dev Discord Server as you did not complete onboarding in the alloted time. If you'd like to join again, here is a new invite: https://discord.gg/web`, + }); + } catch (error) { + console.error(`Failed to create DM thread:`, error); + } + try { + await userState?.deleteOne(); + } catch { + console.error('Failed to delete user state'); + } + + member.kick(`User did not complete onboarding in the alloted time.`); + } +} diff --git a/src/v2/modules/onboarding/index.ts b/src/v2/modules/onboarding/index.ts new file mode 100644 index 00000000..830d4214 --- /dev/null +++ b/src/v2/modules/onboarding/index.ts @@ -0,0 +1,100 @@ +import type { Client, GuildMember, Message, TextChannel } from 'discord.js'; +import { Permissions } from 'discord.js'; + +import { SERVER_ID } from '../../env.js'; +import { NEW_USER_ROLE, ONBOARDING_CHANNEL, JOIN_LOG_CHANNEL } from '../../env.js'; +import { UserState } from './db/user_state.js'; +import { handleIntroductionMsg } from './events/handleIntroductionMsg.js'; +import { handleMemberLeave } from './events/handleMemberLeave.js'; +import { handleNewMember } from './events/handleNewMember.js'; +import { handleNotifyRolesSelected } from './events/handleNotifyRolesSelected.js'; +import { handleRoleSelected } from './events/handleRoleSelected.js'; +import { handleRulesAgree } from './events/handleRulesAgree.js'; +import { handleSkipIntro } from './events/handleSkipIntro.js'; +import { handleThreadArchived } from './events/handleThreadArchived.js'; +import { getMessagesUntil } from './utils/getMessagesUntil.js'; +import { limitToWebDevServer } from './utils/limitToWebDevServer.js'; +import { getOnboardingStart } from './utils/onboardingStart.js'; + +export async function attach(client: Client): Promise<void> { + const guild = client.guilds.resolve(SERVER_ID); + const role = guild.roles.cache.get(NEW_USER_ROLE); + addNewRolePermissions(guild, role); + + client.on('guildMemberAdd', limitToWebDevServer(handleNewMember)); + client.on('interactionCreate', limitToWebDevServer(handleRoleSelected)); + client.on( + 'interactionCreate', + limitToWebDevServer(handleNotifyRolesSelected) + ); + client.on('interactionCreate', limitToWebDevServer(handleRulesAgree)); + client.on('interactionCreate', limitToWebDevServer(handleSkipIntro)); + client.on('messageCreate', limitToWebDevServer(handleIntroductionMsg)); + client.on('guildMemberRemove', limitToWebDevServer(handleMemberLeave)); + client.on('threadUpdate', limitToWebDevServer(handleThreadArchived)); + + client.on('interactionCreate', async interaction => { + if (!interaction.isButton()) { + return; + } + + const [type, subtype] = interaction.customId.split('🤔'); + + if (type === 'debug' && subtype === 'new_user') { + await UserState.deleteOne({ + userId: interaction.user.id, + }); + handleNewMember(interaction.member as GuildMember); + } + }); + + await playCatchup(guild); +} +async function playCatchup(guild) { + const catchUpFrom = await getOnboardingStart(); + + if (!catchUpFrom) { + return; + } + + const joinLogChannel = (await guild.channels.fetch( + JOIN_LOG_CHANNEL + )) as TextChannel; + const userState = await UserState.findOne({}).sort('updatedAt'); + + const userMap = new Map<string, Message>(); + for await (const message of getMessagesUntil( + joinLogChannel, + userState?.updatedAt ?? new Date(catchUpFrom) + )) { + if ( + message.type === 'GUILD_MEMBER_JOIN' && + message.member && + !userMap.has(message.member.id) + ) { + userMap.set(message.member.id, message); + } + } + + for (const [, message] of userMap) { + handleNewMember(message.member); + } +} + +function addNewRolePermissions(guild, role) { + const perms = new Permissions(Permissions.DEFAULT); + perms.remove(Permissions.FLAGS.VIEW_CHANNEL); + + for (const [, channel] of guild.channels.cache) { + if ( + 'permissionOverwrites' in channel && + channel.id !== ONBOARDING_CHANNEL + ) { + try { + channel.permissionOverwrites.create(role, perms.serialize()); + } catch (error) { + console.error(error); + } + } + } +} diff --git a/src/v2/modules/onboarding/steps/handleIntroduction.ts b/src/v2/modules/onboarding/steps/handleIntroduction.ts new file mode 100644 index 00000000..6f98f3a3 --- /dev/null +++ b/src/v2/modules/onboarding/steps/handleIntroduction.ts @@ -0,0 +1,42 @@ +import type { Guild, GuildMember } from 'discord.js'; +import { MessageActionRow, MessageButton } from 'discord.js'; + +import { INTRO_CHANNEL, INTRO_ROLE } from '../../../env.js'; +import type { UserStateType } from '../db/user_state'; +import { getThread } from '../utils/getThread.js'; +import { sneakPin } from '../utils/sneakPin.js'; + +export async function handleIntroduction( + guild: Guild, + member: GuildMember, + oldState: UserStateType, + fromStart: boolean +): Promise<void> { + const thread = await getThread(guild, oldState.threadId); + const pinned = await thread.messages.fetchPinned(); + + if (pinned.size === 0) { + const msg = await thread.send({ + content: `:point_left: You should now have access to the <#${INTRO_CHANNEL}> channel. It would be great if you could introduce yourself in that channel. We understand if you don't want to, however, so feel free to skip this step now, or do it later`, + components: [ + new MessageActionRow().addComponents([ + new MessageButton() + .setLabel('Skip') + .setStyle('DANGER') + .setEmoji('⏩') + .setCustomId('onboarding🤔skip_intro'), + ]), + ], + }); + + await sneakPin(msg); + + member.roles.add(INTRO_ROLE); + } + + if (fromStart) { + await pinned.last().reply({ + content: `Hey ${member.toString()}, seems like something went wrong during your onboarding, this could be because you left during it or the bot was down. You should be able to continue from here.`, + }); + } +} diff --git a/src/v2/modules/onboarding/steps/handleOnboarded.ts b/src/v2/modules/onboarding/steps/handleOnboarded.ts new file mode 100644 index 00000000..ca8503e6 --- /dev/null +++ b/src/v2/modules/onboarding/steps/handleOnboarded.ts @@ -0,0 +1,21 @@ +import type { Guild, GuildMember } from 'discord.js'; + +import type { UserStateType } from '../db/user_state'; +import { getThread } from '../utils/getThread.js'; + +export async function handleOnboarded( + guild: Guild, + member: GuildMember, + oldState: UserStateType, + fromStart: boolean +): Promise<void> { + const thread = await getThread(guild, oldState.threadId); + + if (!fromStart) { + thread.send(`🎉 That's it! We hope you enjoy your time here ${member.toString()}. If you want to update your roles again, you can do that here: <#460881799430537237>. + +You have access to this thread and the onboarding channel until <t:${Math.round( + thread.archivedAt.getTime() / 1000 + )}>.`); + } +} diff --git a/src/v2/modules/onboarding/steps/handleRoleSelection.ts b/src/v2/modules/onboarding/steps/handleRoleSelection.ts new file mode 100644 index 00000000..ebfe9b15 --- /dev/null +++ b/src/v2/modules/onboarding/steps/handleRoleSelection.ts @@ -0,0 +1,78 @@ +import type { Guild, GuildMember } from 'discord.js'; +import { MessageButton } from 'discord.js'; +import { MessageSelectMenu } from 'discord.js'; +import { MessageActionRow } from 'discord.js'; + +import { NOTIFY_ROLES } from '../../roles/consts/notifyRoles.js'; +import { ROLES } from '../../roles/consts/roles.js'; +import type { UserStateType } from '../db/user_state'; +import { getThread } from '../utils/getThread.js'; +import { sneakPin } from '../utils/sneakPin.js'; + +export async function handleRoleSelection( + guild: Guild, + member: GuildMember, + oldState: UserStateType, + fromStart: boolean +): Promise<void> { + const thread = await getThread(guild, oldState.threadId); + const pinned = await thread.messages.fetchPinned(); + if (pinned.size > 0) { + return; + } + const rolesMsg = await thread.send({ + content: `**Final Step!** + +We have quite a few channels, so to gain access to them, you'll need to opt in to viewing them. Fortunately, that's the step you're on now. Use the select box below to pick which channels you'd like to see, or hit the button to opt in to viewing all the channels.`, + components: [ + new MessageActionRow().addComponents([ + new MessageSelectMenu() + .addOptions( + ROLES.map(x => ({ + label: x.name, + value: x.name, + })) + ) + .setCustomId('onboarding🤔roles') + .setMinValues(1) + .setMaxValues(ROLES.length) + .setPlaceholder("Pick which roles you're interested in"), + ]), + new MessageActionRow().addComponents( + new MessageButton() + .setCustomId('onboarding🤔roles🤔All Development') + .setLabel('View All Development Channels') + .setStyle('PRIMARY') + ), + ], + }); + + const notificationRoles = await thread.send({ + content: + 'We also have some roles for if you wish to be notified about various optional announcements, which you can opt into here:', + components: [ + new MessageActionRow().addComponents([ + new MessageSelectMenu() + .addOptions( + NOTIFY_ROLES.map(x => ({ + label: x.name, + value: x.name, + emoji: x.emoji, + description: x.description, + })) + ) + .setCustomId('onboarding🤔notify_roles') + .setMinValues(1) + .setMaxValues(NOTIFY_ROLES.length), + ]), + ], + }); + + await sneakPin(rolesMsg); + await sneakPin(notificationRoles); + if (fromStart) { + await rolesMsg.reply({ + content: `Hey ${member.toString()}, seems like something went wrong during your onboarding, this could be because you left during it or the bot was down. You should be able to continue from here.`, + }); + } +} diff --git a/src/v2/modules/onboarding/steps/handleStart.ts b/src/v2/modules/onboarding/steps/handleStart.ts new file mode 100644 index 00000000..f8e609e9 --- /dev/null +++ b/src/v2/modules/onboarding/steps/handleStart.ts @@ -0,0 +1,36 @@ +import type { Guild, GuildMember } from 'discord.js'; +import { MessageActionRow, MessageButton } from 'discord.js'; + +import type { UserStateType } from '../db/user_state'; +import { getThread } from '../utils/getThread.js'; + +export async function handleStart( + guild: Guild, + member: GuildMember, + oldState: UserStateType, + fromStart: boolean +): Promise<void> { + const thread = await getThread(guild, oldState.threadId); + const pinned = await thread.messages.fetchPinned(); + + const rulesMsg = pinned.last(); + + setTimeout(async () => { + await rulesMsg.edit({ + components: [ + new MessageActionRow().addComponents([ + new MessageButton() + .setStyle('SUCCESS') + .setLabel('I have read, and agree to follow the rules') + .setCustomId('onboarding🤔rules_agreed'), + ]), + ], + }); + }, 15_000); + + if (fromStart) { + await pinned.last().reply({ + content: `Hey ${member.toString()}, seems like something went wrong during your onboarding, this could be because you left during it or the bot was down. You should be able to continue from here.`, + }); + } +} diff --git a/src/v2/modules/onboarding/utils/continueOnboarding.ts b/src/v2/modules/onboarding/utils/continueOnboarding.ts new file mode 100644 index 00000000..05fa6e1e --- /dev/null +++ b/src/v2/modules/onboarding/utils/continueOnboarding.ts @@ -0,0 +1,34 @@ +import type { GuildMember, Guild, TextChannel } from 'discord.js'; + +import { ONBOARDING_CHANNEL } from '../../../env.js'; +import type { UserStateType } from '../db/user_state'; +import { handleIntroduction } from '../steps/handleIntroduction.js'; +import { handleOnboarded } from '../steps/handleOnboarded.js'; +import { handleRoleSelection } from '../steps/handleRoleSelection.js'; +import { handleStart } from '../steps/handleStart.js'; + +export async function continueOnboarding( + guild: Guild, + member: GuildMember, + oldState: UserStateType, + fromStart = false +): Promise<void> { + const channel = guild.channels.resolve(ONBOARDING_CHANNEL) as TextChannel; + + switch (oldState.state) { + case 'START': + handleStart(guild, member, oldState, fromStart); + return; + case 'INTRODUCTION': + handleIntroduction(guild, member, oldState, fromStart); + return; + case 'ROLE_SELECTION': + handleRoleSelection(guild, member, oldState, fromStart); + return; + case 'ONBOARDED': + handleOnboarded(guild, member, oldState, fromStart); + return; + default: + console.error("Shouldn't have gotten here D:"); + } +} diff --git a/src/v2/modules/onboarding/utils/getMessagesUntil.ts b/src/v2/modules/onboarding/utils/getMessagesUntil.ts new file mode 100644 index 00000000..dd4ac10b --- /dev/null +++ b/src/v2/modules/onboarding/utils/getMessagesUntil.ts @@ -0,0 +1,26 @@ +import type { Message, TextChannel } from 'discord.js'; +import { filter } from 'domyno'; + +export async function* getMessagesUntil( + channel: TextChannel, + date: Date, + limit = 100 +): AsyncIterableIterator<Message> { + let before; + const timestamp = date.getTime(); + + const msgAfter = filter<Message>( + (msg: Message) => msg.createdTimestamp > timestamp + ); + + while (true) { + // this is fine this is a async generator function + // eslint-disable-next-line no-await-in-loop + const messages = await channel.messages.fetch({ limit, before }); + if (messages.size === 0) { + return; + } + yield* msgAfter(messages.values()); + before = messages.last().id; + } +} diff --git a/src/v2/modules/onboarding/utils/getThread.ts b/src/v2/modules/onboarding/utils/getThread.ts new file mode 100644 index 00000000..c79c59e1 --- /dev/null +++ b/src/v2/modules/onboarding/utils/getThread.ts @@ -0,0 +1,14 @@ +import type { Guild, TextChannel, ThreadChannel } from 'discord.js'; + +import { ONBOARDING_CHANNEL } from '../../../env.js'; + +export async function getThread( + guild: Guild, + id: string +): Promise<ThreadChannel> { + const channel = (await guild.channels.fetch( + ONBOARDING_CHANNEL + )) as TextChannel; + + return channel.threads.fetch(id); +} diff --git a/src/v2/modules/onboarding/utils/limitToWebDevServer.ts b/src/v2/modules/onboarding/utils/limitToWebDevServer.ts new file mode 100644 index 00000000..5685bb60 --- /dev/null +++ b/src/v2/modules/onboarding/utils/limitToWebDevServer.ts @@ -0,0 +1,17 @@ +import { SERVER_ID } from '../../../env.js'; + +export function limitToWebDevServer< + HasGuildId extends { guild: { id: string } }, + Output, + VArgs extends readonly unknown[] +>( + fn: (g: HasGuildId, ...vargs: VArgs) => Output +): (a: HasGuildId, ...vargs: VArgs) => Output { + return (x: HasGuildId, ...args: VArgs) => { + if (x.guild.id !== SERVER_ID) { + return; + } + + return fn(x, ...args); + }; +} diff --git a/src/v2/modules/onboarding/utils/onboardingStart.ts b/src/v2/modules/onboarding/utils/onboardingStart.ts new file mode 100644 index 00000000..46c7076f --- /dev/null +++ b/src/v2/modules/onboarding/utils/onboardingStart.ts @@ -0,0 +1,33 @@ +import { get, upsert } from '../../../cache/index.js'; +import type { GenericCacheType } from '../../../cache/model.js'; +import { SERVER_ID } from '../../../env.js'; + +type OnboardingStartCache = { + meta: { + onboardingStart: number; + }; +} & GenericCacheType; + +const _getOnboardingCache = (): Promise<OnboardingStartCache> => + get({ + guild: SERVER_ID, + type: 'ONBOARDING_START', + user: '', + }) as unknown as Promise<OnboardingStartCache>; + +export const getOnboardingStart = async (): Promise<number | undefined> => { + const cache = await _getOnboardingCache(); + + return cache?.meta.onboardingStart; +}; + +export const setOnboardingStart = (): Promise<OnboardingStartCache> => + upsert({ + expiresAt: Number.MAX_SAFE_INTEGER, + guild: SERVER_ID, + type: 'ONBOARDING_START', + user: '', + meta: { + onboardingStart: Date.now(), + }, + }) as unknown as Promise<OnboardingStartCache>; diff --git a/src/v2/modules/onboarding/utils/sneakPin.ts b/src/v2/modules/onboarding/utils/sneakPin.ts new file mode 100644 index 00000000..99bdbf6c --- /dev/null +++ b/src/v2/modules/onboarding/utils/sneakPin.ts @@ -0,0 +1,13 @@ +import type { Message } from 'discord.js'; + +export const sneakPin = async (msg: Message): Promise<void> => { + const awaitedPinned = msg.channel.awaitMessages({ + filter: x => x.type === 'CHANNEL_PINNED_MESSAGE', + max: 1, + }); + + await msg.pin(); + + const pinned = await awaitedPinned; + await pinned.first().delete(); +}; diff --git a/src/v2/modules/onboarding/utils/streamFilter.ts b/src/v2/modules/onboarding/utils/streamFilter.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/v2/modules/roles/commands/change.ts b/src/v2/modules/roles/commands/change.ts new file mode 100644 index 00000000..1170851f --- /dev/null +++ b/src/v2/modules/roles/commands/change.ts @@ -0,0 +1,28 @@ +import type { CommandInteraction, GuildMember } from 'discord.js'; + +import { generateRoleSelect } from '../utils/generateRoleSelect.js'; +import { getAddRemoveRoles } from '../utils/getAddRemoveRoles.js'; + +export function change(interaction: CommandInteraction): void { + const [addRoles, removeRoles] = getAddRemoveRoles( + interaction.member as GuildMember + ); + interaction.reply({ + ephemeral: true, + content: 'Please select the roles you wish to add or remove', + components: [ + addRoles.length > 0 && + generateRoleSelect( + 'Which roles would you like to join?', + 'roles🤔add', + addRoles + ), + removeRoles.length > 0 && + generateRoleSelect( + 'Which roles would you like to leave?', + 'roles🤔remove', + removeRoles + ), + ].filter(Boolean), + }); +} diff --git a/src/v2/modules/roles/commands/index.ts b/src/v2/modules/roles/commands/index.ts new file mode 100644 index 00000000..804c0870 --- /dev/null +++ b/src/v2/modules/roles/commands/index.ts @@ -0,0 +1,54 @@ +import type { CommandDataWithHandler } from '../../../../types'; +import { SERVER_ID } from '../../../env.js'; +import { handleAddRemoveRole } from '../events/handleAddRemoveRole.js'; +import { handleAutoCompleteRole } from '../events/handleAutoCompleteRole.js'; +import { change } from './change.js'; +import { suggest } from './suggest.js'; + +export const roleCommands: CommandDataWithHandler = { + name: 'roles', + description: 'update your roles', + async handler(client, interaction) { + switch (interaction.options.getSubcommand()) { + case 'suggest': + await suggest(interaction); + break; + case 'change': + change(interaction); + break; + } + }, + onAttach(client) { + client.on('interactionCreate', handleAddRemoveRole); + + client.on('interactionCreate', handleAutoCompleteRole); + }, + guildValidate: guild => guild.id === SERVER_ID, + defaultPermission: false, + options: [ + { + name: 'suggest', + type: 'SUB_COMMAND', + description: 'Suggest a role to a user', + options: [ + { + name: 'role', + type: 'STRING', + description: 'role', + autocomplete: true, + required: true, + }, + { + name: 'user', + type: 'USER', + description: 'The user you want to suggested the roles to', + }, + ], + }, + { + name: 'change', + type: 'SUB_COMMAND', + description: 'Change your roles', + }, + ], +}; diff --git a/src/v2/modules/roles/commands/suggest.ts b/src/v2/modules/roles/commands/suggest.ts new file mode 100644 index 00000000..16efdba2 --- /dev/null +++ b/src/v2/modules/roles/commands/suggest.ts @@ -0,0 +1,24 @@ +import type { CommandInteraction, GuildMember } from 'discord.js'; +import { MessageActionRow, MessageButton } from 'discord.js'; + +export async function suggest(interaction: CommandInteraction): Promise<void> { + await interaction.deferReply({}); + + const member = interaction.member as GuildMember; + const user = interaction.options.getUser('user'); + const role = interaction.options.getString('role', true); + + interaction.editReply({ + content: `Hey${ + user ? ` ${user}` : '' + }, ${member.toString()} is suggesting that you join the ${role} role.`, + components: [ + new MessageActionRow().addComponents( + new MessageButton() + .setLabel(`Join ${role} Role`) + .setCustomId(`roles🤔add🤔${role}`) + .setStyle('PRIMARY') + ), + ], + }); +} diff --git a/src/v2/modules/roles/consts/notifyRoles.ts b/src/v2/modules/roles/consts/notifyRoles.ts new file mode 100644 index 00000000..ccc335a8 --- /dev/null +++ b/src/v2/modules/roles/consts/notifyRoles.ts @@ -0,0 +1,23 @@ +export const NOTIFY_ROLES = [ + { + name: 'Community Updates', + description: 'Get notified for community updates', + emoji: '📢', + }, + { + name: 'Event Announcements', + description: "Get notified for when we're doing events", + emoji: '📆', + }, + { + name: 'WebJam Announcements', + description: "Get notified for when we're doing WebJams", + emoji: '⏳', + }, + { + name: 'Gamer Group', + description: + 'Get notified when people want to play something, usually Jackbox', + emoji: '🎮', + }, +]; diff --git a/src/v2/modules/roles/consts/roles.ts b/src/v2/modules/roles/consts/roles.ts new file mode 100644 index 00000000..c6044425 --- /dev/null +++ b/src/v2/modules/roles/consts/roles.ts @@ -0,0 +1,82 @@ +export const ROLES = [ + { + name: '.NET', + emoji: '💻', + }, + { + name: 'CSS', + emoji: '💻', + }, + { + name: 'Go', + emoji: '💻', + }, + { + name: 'HTML', + emoji: '💻', + }, + { + name: 'Java', + emoji: '💻', + }, + { + name: 'JS', + emoji: '💻', + }, + { + name: 'PHP', + emoji: '💻', + }, + { + name: 'Python', + emoji: '💻', + }, + { + name: 'TS', + emoji: '💻', + }, + + { + name: 'Angular', + emoji: '🛠', + }, + { + name: 'React', + emoji: '🛠', + }, + { + name: 'Svelte', + emoji: '🛠', + }, + { + name: 'Vue', + emoji: '🛠', + }, + { + name: 'WordPress', + emoji: '🛠', + }, + { + name: 'Databases', + emoji: '🗃', + }, + { + name: 'DevOps', + emoji: '🗃', + }, + { + name: 'SEO', + emoji: '🗃', + }, + { + name: 'Graphic Design', + emoji: '🎨', + }, + { + name: 'UX', + emoji: '🎨', + }, + // { + // name: 'All Development', + // }, +]; diff --git a/src/v2/modules/roles/events/handleAddRemoveRole.ts b/src/v2/modules/roles/events/handleAddRemoveRole.ts new file mode 100644 index 00000000..e4f180ca --- /dev/null +++ b/src/v2/modules/roles/events/handleAddRemoveRole.ts @@ -0,0 +1,95 @@ +import type { + ButtonInteraction, + GuildMember, + Interaction, + Role, +} from 'discord.js'; + +import { generateRoleSelect } from '../utils/generateRoleSelect.js'; +import { getAddRemoveRoles } from '../utils/getAddRemoveRoles.js'; + +const listFormatter = new Intl.ListFormat(); + +async function toggle(interaction: ButtonInteraction, role: Role) { + const member = interaction.member as GuildMember; + const isAdd = !member.roles.resolve(role.id); + if (isAdd) { + await member.roles.add(role.id); + } else { + await member.roles.remove(role.id); + } + + return interaction.reply({ + ephemeral: true, + content: `I've ${isAdd ? 'added you to' : 'removed you from'} the ${ + role.name + } role.`, + }); +} + +export const handleAddRemoveRole = async ( + interaction: Interaction +): Promise<void> => { + const member = interaction.member as GuildMember; + if (!interaction.isButton() && !interaction.isSelectMenu()) { + return; + } + + const [type, subtype, role] = interaction.customId.split('🤔'); + if (type !== 'roles') { + return; + } + + const roleNames = new Set<string>( + interaction.isButton() ? [role] : interaction.values + ); + + if (roleNames.size === 0) { + return; + } + + const roles = member.guild.roles.cache + .filter(x => roleNames.has(x.name)) + .map(x => x); + + if (subtype === 'add') { + await member.roles.add(roles); + } else if (subtype === 'remove') { + await member.roles.remove(roles); + } else { + return toggle(interaction as ButtonInteraction, roles[0]); + } + + if (interaction.isSelectMenu()) { + const [addRoles, removeRoles] = getAddRemoveRoles( + interaction.member as GuildMember + ); + + interaction.update({ + content: `✅ You've been ${ + subtype === 'add' ? 'added to' : 'removed from' + } ${listFormatter.format(roleNames)}`, + components: [ + addRoles.length > 0 && + generateRoleSelect( + 'Which roles would you like to join?', + 'roles🤔add', + addRoles + ), + removeRoles.length > 0 && + generateRoleSelect( + 'Which roles would you like to leave?', + 'roles🤔remove', + removeRoles + ), + ].filter(Boolean), + }); + } else { + interaction.reply({ + ephemeral: true, + content: `You've been ${ + subtype === 'add' ? 'added to' : 'removed from' + } ${listFormatter.format(roleNames)}`, + }); + } +}; diff --git a/src/v2/modules/roles/events/handleAutoCompleteRole.ts b/src/v2/modules/roles/events/handleAutoCompleteRole.ts new file mode 100644 index 00000000..6ce99ab9 --- /dev/null +++ b/src/v2/modules/roles/events/handleAutoCompleteRole.ts @@ -0,0 +1,21 @@ +import type { Interaction } from 'discord.js'; + +import { ROLES } from '../consts/roles.js'; + +export const handleAutoCompleteRole = async ( + interaction: Interaction +): Promise<void> => { + if (interaction.isAutocomplete()) { + const focused = interaction.options.getFocused(); + const listedRoles = ROLES.filter(roles => + roles.name.toLowerCase().includes(focused.toLowerCase()) + ); + + await interaction.respond( + listedRoles.map(x => ({ + name: x.name, + value: x.name, + })) + ); + } +}; diff --git a/src/v2/modules/roles/index.ts b/src/v2/modules/roles/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/v2/modules/roles/utils/generateRoleSelect.ts b/src/v2/modules/roles/utils/generateRoleSelect.ts new file mode 100644 index 00000000..74e9cf55 --- /dev/null +++ b/src/v2/modules/roles/utils/generateRoleSelect.ts @@ -0,0 +1,15 @@ +import { MessageActionRow, MessageSelectMenu } from 'discord.js'; + +export function generateRoleSelect( + placeholder: string, + customId: string, + roles: string[] +): MessageActionRow { + return new MessageActionRow().addComponents( + new MessageSelectMenu() + .setOptions(roles.map(role => ({ label: role, value: role }))) + .setCustomId(customId) + .setMinValues(1) + .setPlaceholder(placeholder) + ); +} diff --git a/src/v2/modules/roles/utils/getAddRemoveRoles.ts b/src/v2/modules/roles/utils/getAddRemoveRoles.ts new file mode 100644 index 00000000..666a8de9 --- /dev/null +++ b/src/v2/modules/roles/utils/getAddRemoveRoles.ts @@ -0,0 +1,15 @@ +import type { GuildMember } from 'discord.js'; + +import { partitionʹ } from '../../../utils/partition.js'; +import { NOTIFY_ROLES } from '../consts/notifyRoles.js'; +import { ROLES } from '../consts/roles.js'; + +const roleNames = new Set([...ROLES, ...NOTIFY_ROLES].map(x => x.name)); + +export const getAddRemoveRoles = ( + member: GuildMember +): [string[], string[]] => { + const existingRoles = new Set(member.roles.cache.map(x => x.name)); + + return partitionʹ(role => !existingRoles.has(role), roleNames); +}; diff --git a/src/v2/spam_filter/handler.ts b/src/v2/spam_filter/handler.ts index 4e5450bb..b9f97b23 100644 --- a/src/v2/spam_filter/handler.ts +++ b/src/v2/spam_filter/handler.ts @@ -1,11 +1,11 @@ -import type { TextChannel, GuildChannel } from 'discord.js'; +// import type { TextChannel, GuildChannel } from 'discord.js'; -import { MOD_CHANNEL } from '../env.js'; -import { createEmbed, createMarkdownBash } from '../utils/discordTools.js'; +// import { MOD_CHANNEL } from '../env.js'; +// import { createEmbed, createMarkdownBash } from '../utils/discordTools.js'; -import type { SpammerMetadata } from './index.js'; +// import type { SpammerMetadata } from './index.js'; -type ModChannel = TextChannel & Pick<GuildChannel, 'name'>; +// type ModChannel = TextChannel & Pick<GuildChannel, 'name'>; // const spamFilterHandler = async ({ // userID, diff --git a/src/v2/user_context/index.ts b/src/v2/user_context/index.ts index ab9a7bbd..3317a881 100644 --- a/src/v2/user_context/index.ts +++ b/src/v2/user_context/index.ts @@ -1,4 +1,3 @@ - import type { Client } from 'discord.js'; import { Collection } from 'discord.js'; diff --git a/src/v2/utils/Cache.ts b/src/v2/utils/Cache.ts index 751856bc..6b2e81d0 100644 --- a/src/v2/utils/Cache.ts +++ b/src/v2/utils/Cache.ts @@ -75,7 +75,7 @@ type CacheOptions = { * @memberof Options */ maxKeys?: number; -} +}; const wrap = <T>( value: T, @@ -160,7 +160,7 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { * @param key cache key * @returns The value stored in the key */ - public get<T extends Value = Value>(key: Key): T | undefined { + public get<T extends Value = Value>(key: Key): T | undefined { const data = this.#data.get(key); addBreadcrumb({ @@ -180,7 +180,7 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { * @param keys an array of keys * @returns an object containing the values stored in the matching keys */ - public mget<T extends Value = Value>(keys: Key[]): Map<Key, T> { + public mget<T extends Value = Value>(keys: Key[]): Map<Key, T> { const output: Map<Key, T> = new Map(); addBreadcrumb({ @@ -209,7 +209,11 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { * it to a serialized JSON * @param ttl The time to live in seconds. */ - public set<T extends Value = Value>(key: Key, value: T, ttl?: number): boolean { + public set<T extends Value = Value>( + key: Key, + value: T, + ttl?: number + ): boolean { const usedTtl = ttl ?? this.options.stdTTL; addBreadcrumb({ @@ -231,7 +235,9 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { * * @param keyValueSet an array of object which includes key,value and ttl */ - public mset<T extends Value = Value>(keyValueSet: ValueSetItem<Key, T>[]): boolean { + public mset<T extends Value = Value>( + keyValueSet: ValueSetItem<Key, T>[] + ): boolean { const len = keyValueSet.length; addBreadcrumb({ level: 'debug', @@ -250,7 +256,7 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { * @param cb Callback function * @returns Number of deleted keys */ - public del(keys: Key | Key[]): number { + public del(keys: Key | Key[]): number { let deleted = 0; const keyArr = castArray(keys); const data = this.#data; @@ -282,7 +288,7 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { * @param key cache key * @returns The value stored in the key */ - public take<T extends Value = Value>(key: Key): T | undefined { + public take<T extends Value = Value>(key: Key): T | undefined { const output = this.get(key); if (this.#data.has(key)) { @@ -295,7 +301,7 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { /** * reset or redefine the ttl of a key. If `ttl` is not passed or set to 0 it's similar to `.del()` */ - public ttl(key: Key, ttl: number = this.options.stdTTL): boolean { + public ttl(key: Key, ttl: number = this.options.stdTTL): boolean { if (!key) { return false; } @@ -309,8 +315,7 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { } return true; } - return false; - + return false; } public getTtl(key: Key): number | undefined { @@ -322,15 +327,14 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { if (this.#data.has(key) && this._check(key, value)) { return value.t; } - return void 0; - + return void 0; } /** * list all keys within this cache * @returns An array of all keys */ - public keys(): IterableIterator<Key> { + public keys(): IterableIterator<Key> { return this.#data.keys(); } @@ -348,14 +352,14 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { * @param key cache key to check * @returns Boolean indicating if the key is cached or not */ - public has(key: Key): boolean { + public has(key: Key): boolean { return this.has(key) && this._check(key, this.#data.get(key)); } /** * flush the whole data and reset the stats */ - public flushAll(_startPeriod = true): void { + public flushAll(_startPeriod = true): void { this.#data.clear(); this._killCheckPeriod(); this._checkData(_startPeriod); @@ -365,14 +369,15 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { /** * This will clear the interval timeout which is set on checkperiod option. */ - public close(): void { + public close(): void { this._killCheckPeriod(); } /** * flush the stats and reset all counters to 0 */ - public flushStats(): void {} + // eslint-disable-next-line @typescript-eslint/no-empty-function + public flushStats(): void {} private _check(key: Key, data) { let ret = true; @@ -388,7 +393,7 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { private _killCheckPeriod() { if (this.checkTimeout != null) { - const {checkTimeout} = this; + const { checkTimeout } = this; this.checkTimeout = null; clearTimeout(checkTimeout); } @@ -407,7 +412,7 @@ export class Cache<Value = unknown, Key = unknown> extends EventEmitter { this.options.checkperiod * 1000, startPeriod ); - if (this.checkTimeout != null && this.checkTimeout.unref != null) { + if (this.checkTimeout?.unref != null) { this.checkTimeout.unref(); } } diff --git a/src/v2/utils/ExternalResolver.ts b/src/v2/utils/DeferredPromise.ts similarity index 73% rename from src/v2/utils/ExternalResolver.ts rename to src/v2/utils/DeferredPromise.ts index 5c509c4c..35460ec9 100644 --- a/src/v2/utils/ExternalResolver.ts +++ b/src/v2/utils/DeferredPromise.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/explicit-member-accessibility */ -export class ExternalResolver<T> extends Promise<T> { +export class DeferredPromise<T> extends Promise<T> { readonly #resolve: (value: T) => void; readonly #reject: (reason: unknown) => void; @@ -21,29 +20,29 @@ export class ExternalResolver<T> extends Promise<T> { this.#reject = rejector; } - public get settled() { + public static get [Symbol.species](): PromiseConstructor { + return Promise; + } + + public get settled(): boolean { return this.#resolved || this.#rejected; } - public get resolved() { + public get resolved(): boolean { return this.#resolved; } - public get rejected() { + public get rejected(): boolean { return this.#rejected; } public resolve(value: T): void { - this.#resolved = true + this.#resolved = true; this.#resolve(value); } public reject(reason: unknown): void { - this.#rejected = true + this.#rejected = true; this.#reject(reason); } - - public static get [Symbol.species](): PromiseConstructor { - return Promise; - } } diff --git a/src/v2/utils/MultistepForm.ts b/src/v2/utils/MultistepForm.ts index 11b00975..4c2dee89 100644 --- a/src/v2/utils/MultistepForm.ts +++ b/src/v2/utils/MultistepForm.ts @@ -3,10 +3,8 @@ import type { ButtonInteraction, - Client, DMChannel, Message, - MessageActionRowComponent, MessageActionRowComponentResolvable, MessageButtonStyle, PartialMessage, @@ -14,10 +12,9 @@ import type { ThreadChannel, User, } from 'discord.js'; -import { MessageComponentTypes } from 'discord.js/typings/enums'; import { AWAIT_MESSAGE_TIMEOUT } from '../../env.js'; -import { ExternalResolver } from './ExternalResolver.js'; +import { DeferredPromise } from './DeferredPromise.js'; export type QuestionBase = { body: string; @@ -31,13 +28,13 @@ export type ButtonQuestion = { value: string; style?: MessageButtonStyle; }[]; -buttonDelay?: number; + buttonDelay?: number; } & QuestionBase; export type TextQuestion = { type: 'text'; validate?: (input: string) => boolean | string; - format?: (input:string) => string + format?: (input: string) => string; } & QuestionBase; export type MultiStepFormStep = ButtonQuestion | TextQuestion; @@ -68,7 +65,9 @@ export class MultistepForm<T extends Record<string, MultiStepFormStep>> { public async getButtonResponse<P extends ButtonQuestion>( step: P ): Promise<P['buttons'][number]['value'] | typeof __cancelled__> { - const resolver = new ExternalResolver<P['buttons'][number]['value'] | typeof __cancelled__>(); + const resolver = new DeferredPromise< + P['buttons'][number]['value'] | typeof __cancelled__ + >(); const message = await this.#channel.send({ content: `**${step.body}**`, components: [ @@ -92,11 +91,10 @@ export class MultistepForm<T extends Record<string, MultiStepFormStep>> { }, step.buttonDelay); } - const collector = - message.createMessageComponentCollector({ - componentType: 'BUTTON', - filter: interaction => interaction.user.id === this.#user.id, - }); + const collector = message.createMessageComponentCollector({ + componentType: 'BUTTON', + filter: interaction => interaction.user.id === this.#user.id, + }); const handler = async (interaction: ButtonInteraction) => { if (interaction.isButton()) { @@ -120,15 +118,17 @@ export class MultistepForm<T extends Record<string, MultiStepFormStep>> { collector.on('collect', handler); setTimeout(() => { - if(resolver.settled) {return} + if (resolver.settled) { + return; + } - collector.off('collect', handler) - resolver.resolve(__cancelled__) + collector.off('collect', handler); + resolver.resolve(__cancelled__); message.edit({ - components: [] - }) - message.channel.send("Timed out. Please restart the process.") - }, Number(AWAIT_MESSAGE_TIMEOUT) * 1000 ) + components: [], + }); + message.channel.send('Timed out. Please restart the process.'); + }, Number(AWAIT_MESSAGE_TIMEOUT) * 1000); return resolver; } @@ -139,7 +139,7 @@ export class MultistepForm<T extends Record<string, MultiStepFormStep>> { `**${step.body}**\n\nOr click below to cancel.` ); - const deleteResolver = new ExternalResolver<typeof __cancelled__>(); + const deleteResolver = new DeferredPromise<typeof __cancelled__>(); const handleDelete = (msg: Message | PartialMessage) => { if (msg.id !== message.id) { @@ -157,18 +157,15 @@ export class MultistepForm<T extends Record<string, MultiStepFormStep>> { const res = await Promise.race([ this.#channel.awaitMessages({ time: Number.parseInt(AWAIT_MESSAGE_TIMEOUT) * 1000, - filter: (message) => { - const value = step.validate(message.cleanContent) - const fromUser = message.author.id === userId + filter: message => { + const value = step.validate(message.cleanContent); + const fromUser = message.author.id === userId; - if(typeof value === 'string' && fromUser) { - this.#channel.send(value) + if (typeof value === 'string' && fromUser) { + this.#channel.send(value); } - return ( - fromUser && - typeof value === 'boolean' && value - ); + return fromUser && typeof value === 'boolean' && value; }, max: 1, }), @@ -182,11 +179,11 @@ export class MultistepForm<T extends Record<string, MultiStepFormStep>> { return __cancelled__; } - const value = res.first().content.trim() + const value = res.first().content.trim(); return step.format?.(value) ?? value; } catch { this.#channel.send(`You have timed out. Please try again`); - return __cancelled__ + return __cancelled__; } } @@ -195,7 +192,11 @@ export class MultistepForm<T extends Record<string, MultiStepFormStep>> { const state: Map<keyof T, unknown> = new Map(); - while (currentStep !== undefined && currentStep !==__cancelled__&& currentStep in this.#steps) { + while ( + currentStep !== undefined && + currentStep !== __cancelled__ && + currentStep in this.#steps + ) { const step = this.#steps[currentStep]; let value; if (step.type === 'button') { @@ -214,7 +215,7 @@ export class MultistepForm<T extends Record<string, MultiStepFormStep>> { currentStep = step?.next?.(value, state); } - return currentStep !== __cancelled__ ? state : null; + return currentStep === __cancelled__ ? null : state; } private genButtons<P extends ButtonQuestion>( @@ -227,7 +228,7 @@ export class MultistepForm<T extends Record<string, MultiStepFormStep>> { label: button.label, style: button.style ?? 'PRIMARY', customId: `${this.#id}🤔${button.value}`, - disabled: disabled ?? undefined + disabled: disabled ?? undefined, }) ); } @@ -252,11 +253,10 @@ export class MultistepForm<T extends Record<string, MultiStepFormStep>> { ], }); - const collector = - message.createMessageComponentCollector({ - componentType: 'BUTTON', - filter: interaction => interaction.user.id === this.#user.id, - }); + const collector = message.createMessageComponentCollector({ + componentType: 'BUTTON', + filter: interaction => interaction.user.id === this.#user.id, + }); const handler = (interaction: ButtonInteraction) => { if (interaction.isButton()) { diff --git a/src/v2/utils/asyncCatch.ts b/src/v2/utils/asyncCatch.ts index 9a9cf9d7..88bca201 100644 --- a/src/v2/utils/asyncCatch.ts +++ b/src/v2/utils/asyncCatch.ts @@ -1,9 +1,11 @@ -export function asyncCatch<T extends readonly unknown[],K>(fn:(...args:T) => Promise<K> ): (...args:T) => Promise<K> { +export function asyncCatch<T extends readonly unknown[], K>( + fn: (...args: T) => Promise<K> +): (...args: T) => Promise<K> { return async (...args: T): Promise<K> => { try { - return await fn(...args) - } catch(error) { - console.error(error) + return await fn(...args); + } catch (error) { + console.error(error); } - } + }; } diff --git a/src/v2/utils/callOrValue.ts b/src/v2/utils/callOrValue.ts index bac27e25..3f2b28d9 100644 --- a/src/v2/utils/callOrValue.ts +++ b/src/v2/utils/callOrValue.ts @@ -1,4 +1,3 @@ - export function callOrValue<T = unknown>(item: unknown, ...args: unknown[]): T { if (typeof item === 'function') { return item(...args); diff --git a/src/v2/utils/clampStr.ts b/src/v2/utils/clampStr.ts index b6a15c7a..537ff3db 100644 --- a/src/v2/utils/clampStr.ts +++ b/src/v2/utils/clampStr.ts @@ -7,7 +7,7 @@ export const clampLength = (str: string, maxLength: number): string => { export const clampLengthMiddle = (str: string, maxLength: number): string => { if (str.length > maxLength) { - const firstHalf = str.slice(0, maxLength / 2 -3); + const firstHalf = str.slice(0, maxLength / 2 - 3); const secondHalf = str.slice(str.length - maxLength / 2); return `${firstHalf}...${secondHalf}`; } diff --git a/src/v2/utils/content_format.ts b/src/v2/utils/content_format.ts index 94b7df9c..8a4c21be 100644 --- a/src/v2/utils/content_format.ts +++ b/src/v2/utils/content_format.ts @@ -1,8 +1,9 @@ import type { Message } from 'discord.js'; -const linebreakPattern = /\n/gim; +const linebreakPattern = /\n/gimu; -export const generateCleanContent = (msg: Message) => +export const generateCleanContent = (msg: Message): string => msg.cleanContent.replace(linebreakPattern, ' ').toLowerCase(); -export const stripMarkdownQuote = (msg: string) => msg.replace(/^> .+$/gm, ''); +export const stripMarkdownQuote = (msg: string): string => + msg.replace(/^> .+$/gmu, ''); diff --git a/src/v2/utils/delayedMessageAutoDeletion.ts b/src/v2/utils/delayedMessageAutoDeletion.ts index edb00073..837a159a 100644 --- a/src/v2/utils/delayedMessageAutoDeletion.ts +++ b/src/v2/utils/delayedMessageAutoDeletion.ts @@ -7,7 +7,7 @@ const THIRTY_SECONDS_IN_MS = 30 * 1000; export const delayedMessageAutoDeletion = ( msg: Message, timeout = THIRTY_SECONDS_IN_MS -) => { +): void => { // required so tests on CI dont crash if (msg) { setTimeout(async () => { @@ -28,6 +28,4 @@ export const delayedMessageAutoDeletion = ( } }, timeout); } - - }; diff --git a/src/v2/utils/discordTools.ts b/src/v2/utils/discordTools.ts index 00737d33..d505e6b9 100644 --- a/src/v2/utils/discordTools.ts +++ b/src/v2/utils/discordTools.ts @@ -1,5 +1,11 @@ import { MessageEmbed } from 'discord.js'; -import type { Message, MessageEditOptions, EmbedField , MessagePayload } from 'discord.js'; +import type { + Message, + MessageEditOptions, + EmbedField, + MessagePayload, + MessageReaction, +} from 'discord.js'; import { REPO_LINK } from '../env.js'; import { delayedMessageAutoDeletion } from './delayedMessageAutoDeletion.js'; @@ -13,7 +19,6 @@ import { import { providers } from './urlTools.js'; export const createMarkdownLink = (title: string, url: string): string => - `[${title}](${url.replace(/\)/gu, '\\)')})`; export const BASE_DESCRIPTION = ` @@ -33,7 +38,7 @@ export type Provider = | 'composer' | 'mdn' | 'bundlephobia' - | 'php' + | 'php'; type ListEmbed = { provider: Provider; @@ -49,7 +54,7 @@ export const createListEmbed = ({ url, footerText, description, -}: ListEmbed) => { +}: ListEmbed): { embed: MessageEmbed } => { if (providers[provider]) { const { createTitle } = providers[provider]; @@ -77,8 +82,7 @@ export type Embed = { const spamMeta = { color: 0xfe_5f_55, - icon: - 'https://github.com/ljosberinn/webdev-support-bot/blob/master/logo.png?raw=true', + icon: 'https://github.com/ljosberinn/webdev-support-bot/blob/master/logo.png?raw=true', }; export const createEmbed = ({ @@ -127,7 +131,7 @@ export const adjustDescriptionLength = ( position: number, name: string, description: string -) => { +): string => { const positionLength = position.toString().length + 2; const nameLength = name.length; const descriptionLength = description.length; @@ -162,7 +166,7 @@ export const adjustDescriptionLength = ( return description; }; -export const adjustTitleLength = (title: string) => { +export const adjustTitleLength = (title: string): string => { const titleLength = title.length; const cleansedTitle = @@ -173,17 +177,21 @@ export const adjustTitleLength = (title: string) => { )}...` : title; - return cleansedTitle.replace(/\n/gm, ' '); + return cleansedTitle.replace(/\n/gmu, ' '); }; -export const createMarkdownListItem = (index: number, content: string) => - `${index + 1}. ${content}`; +export const createMarkdownListItem = ( + index: number, + content: string +): string => `${index + 1}. ${content}`; -export const createMarkdownBash = (string: string) => +export const createMarkdownBash = (string: string): string => ['```bash', string, '```'].join('\n'); -export const createMarkdownCodeBlock = (string: string, language = '') => - [`\`\`\`${language}`, string, '```'].join('\n'); +export const createMarkdownCodeBlock = ( + string: string, + language = '' +): string => [`\`\`\`${language}`, string, '```'].join('\n'); export const createDescription = (items: unknown[]): string => [...items, BASE_DESCRIPTION].join('\n'); @@ -192,22 +200,27 @@ export const findEarlyReaction = ( { reactions }: Message, id: string, currentlyValidEmojis: string[] -) => +): MessageReaction => reactions.cache.find( ({ users, emoji: { name } }) => currentlyValidEmojis.includes(name) && - !!users.cache.find(user => user.id === id) + users.cache.some(user => user.id === id) ); -export const clearReactions = ({ reactions }: Message) => - reactions.removeAll().catch(error => { +export const clearReactions = ({ + reactions, +}: Message): Promise<undefined | Message> => { + try { + return reactions.removeAll(); + } catch (error) { // eslint-disable-next-line no-console console.error(error); // eslint-disable-next-line no-console console.info( 'Attempting to remove reactions: message probably deleted or insufficient rights.' ); - }); + } +}; export const getChosenResult = async <T>( sentMsg: Message, @@ -231,6 +244,8 @@ export const getChosenResult = async <T>( } try { + // this needs to be serialised + // eslint-disable-next-line no-await-in-loop await sentMsg.react(emoji); } catch { // eslint-disable-next-line no-console @@ -258,8 +273,8 @@ export const getChosenResult = async <T>( try { const collectedReactions = await sentMsg.awaitReactions({ - filter: reactionFilterBuilder(id, emojis), - ...awaitReactionConfig + filter: reactionFilterBuilder(id, emojis), + ...awaitReactionConfig, }); const emojiName = collectedReactions.first().emoji.name; diff --git a/src/v2/utils/errors.ts b/src/v2/utils/errors.ts index 86930f6d..7d56105c 100644 --- a/src/v2/utils/errors.ts +++ b/src/v2/utils/errors.ts @@ -2,7 +2,7 @@ import { REPO_LINK } from '../env.js'; export const invalidResponse = 'sorry, your request could not be processed. Please try again at a later time.'; -export const noResults = (search: string) => +export const noResults = (search: string): string => `sorry, could not find anything for \`${search}\`.`; export const unknownError = `sorry, something went wrong. If this issue persists, please file an issue at ${REPO_LINK}`; export const missingRightsDeletion = diff --git a/src/v2/utils/flatten.ts b/src/v2/utils/flatten.ts index 77920ea3..6feb8d04 100644 --- a/src/v2/utils/flatten.ts +++ b/src/v2/utils/flatten.ts @@ -1,4 +1,4 @@ -export function* flatten<T>(iter: Iterable<Iterable<T>>) { +export function* flatten<T>(iter: Iterable<Iterable<T>>): IterableIterator<T> { for (const item of iter) { yield* item; } diff --git a/src/v2/utils/merge.ts b/src/v2/utils/merge.ts index 104964c2..90df251e 100644 --- a/src/v2/utils/merge.ts +++ b/src/v2/utils/merge.ts @@ -1,4 +1,4 @@ -export function* merge<T>(...iterables: Iterable<T>[]) { +export function* merge<T>(...iterables: Iterable<T>[]): IterableIterator<T> { for (const iterable of iterables) { yield* iterable; } diff --git a/src/v2/utils/normalizeCommand.ts b/src/v2/utils/normalizeCommand.ts index bbee0da4..8097e957 100644 --- a/src/v2/utils/normalizeCommand.ts +++ b/src/v2/utils/normalizeCommand.ts @@ -7,53 +7,48 @@ import type { ApplicationCommandSubCommandData, ApplicationCommandSubGroupData, } from 'discord.js'; -import { - ApplicationCommand, - ChatInputApplicationCommandData, -} from 'discord.js'; export function normalizeApplicationCommandData< T extends ApplicationCommandData >(cmd: T): T { - if (!("type" in cmd) || cmd.type === 'CHAT_INPUT') { + if (!('type' in cmd) || cmd.type === 'CHAT_INPUT') { return { ...cmd, - type: "CHAT_INPUT", + type: 'CHAT_INPUT', defaultPermission: cmd.defaultPermission ?? false, options: (cmd.options ?? []).map(normalizeApplicationOptionData), }; } - return {...cmd} + return { ...cmd }; } -function normalizeApplicationOptionData <T extends -| ApplicationCommandOptionData -| ApplicationCommandSubCommandData -| ApplicationCommandSubGroupData -| ApplicationCommandNonOptionsData -| ApplicationCommandChoicesData -|ApplicationCommandChannelOptionData>( - option:T - -): T { +function normalizeApplicationOptionData< + T extends + | ApplicationCommandOptionData + | ApplicationCommandSubCommandData + | ApplicationCommandSubGroupData + | ApplicationCommandNonOptionsData + | ApplicationCommandChoicesData + | ApplicationCommandChannelOptionData +>(option: T): T { if (option.type === 'SUB_COMMAND') { return { ...option, options: (option.options ?? []).map(normalizeApplicationOptionData), - required: (option as unknown as {required: boolean}).required ?? false, - } + required: (option as unknown as { required: boolean }).required ?? false, + }; } if (option.type === 'SUB_COMMAND_GROUP') { return { ...option, options: (option.options ?? []).map(normalizeApplicationOptionData), - required: (option as unknown as {required: boolean}).required ?? false, - } + required: (option as unknown as { required: boolean }).required ?? false, + }; } return { ...option, - required: (option as unknown as {required: boolean}).required ?? false, - } ; + required: (option as unknown as { required: boolean }).required ?? false, + }; } diff --git a/src/v2/utils/pluck.ts b/src/v2/utils/pluck.ts index 5b998db3..afbcaf0b 100644 --- a/src/v2/utils/pluck.ts +++ b/src/v2/utils/pluck.ts @@ -1,4 +1,4 @@ -export function* pluckʹ<T extends Record<K, V>, V, K extends keyof any>( +export function* pluckʹ<T extends Record<K, V>, V, K extends PropertyKey>( iter: Iterable<T>, str: K ): Iterable<V> { @@ -7,7 +7,7 @@ export function* pluckʹ<T extends Record<K, V>, V, K extends keyof any>( } } -export function pluck<K extends keyof any>(key: K) { +export function pluck<K extends PropertyKey>(key: K) { return <V, T extends Record<K, V> = Record<K, V>>( iter: Iterable<T> ): Iterable<V> => pluckʹ(iter, key); diff --git a/src/v2/utils/pluralize.ts b/src/v2/utils/pluralize.ts index d4f0c909..3418cd45 100644 --- a/src/v2/utils/pluralize.ts +++ b/src/v2/utils/pluralize.ts @@ -3,7 +3,7 @@ import { callOrValue } from './callOrValue.js'; type PluralizeFunction = { ( strs: TemplateStringsArray, - ...exprs: (((n: number) => string) | unknown)[] + ...exprs: (((n: number) => string) | string | { toString(): string })[] ): (n: number) => string; s: (n: number) => string; mapper: typeof mapper; @@ -14,13 +14,15 @@ const listFormatter = new Intl.ListFormat(); const pluralize = (( strs: TemplateStringsArray, - ...exprs: (((n: number) => string) | unknown)[] + ...exprs: (((n: number) => string) | string | { toString(): string })[] ) => { return (n: number) => strs.reduce((acc, item, i) => { const exp = exprs[i - 1]; - if (Array.isArray(exp)) {return acc + listFormatter.format(exp) + item;} - return acc + callOrValue(exp, n) + item; + if (Array.isArray(exp)) { + return acc + listFormatter.format(exp) + item; + } + return `${acc}${callOrValue<string>(exp, n)}${item}`; }); }) as PluralizeFunction; diff --git a/src/v2/utils/reactions.ts b/src/v2/utils/reactions.ts index 71b2c444..909d0719 100644 --- a/src/v2/utils/reactions.ts +++ b/src/v2/utils/reactions.ts @@ -1,4 +1,4 @@ -import type { User } from 'discord.js'; +import type { MessageReaction, User } from 'discord.js'; export const validReactions = { deletion: '❌', @@ -6,13 +6,12 @@ export const validReactions = { indices: ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟'], }; -export const reactionFilterBuilder = ( - initialMessageAuthorId: string, - currentlyValidEmojis: string[] -) => ({ emoji: { name } }, user: User) => - user.id === initialMessageAuthorId && - // validate reaction via whitelist - currentlyValidEmojis.includes(name); +export const reactionFilterBuilder = + (initialMessageAuthorId: string, currentlyValidEmojis: string[]) => + ({ emoji: { name } }: MessageReaction, user: User): boolean => + user.id === initialMessageAuthorId && + // validate reaction via whitelist + currentlyValidEmojis.includes(name); export const awaitReactionConfig = { errors: ['time'], diff --git a/src/v2/utils/search.ts b/src/v2/utils/search.ts index e1eb8ab8..fe47d81d 100644 --- a/src/v2/utils/search.ts +++ b/src/v2/utils/search.ts @@ -1,11 +1,14 @@ -type ObjectKeyType = string | number | symbol; +// +/* eslint-disable @typescript-eslint/no-explicit-any */ -export function search(fn: (path: ObjectKeyType[], value: any) => any) { +export function search( + fn: (path: PropertyKey[], value: any) => any +): (obj: any, skipOnMatch?: boolean) => Iterable<[PropertyKey[], unknown]> { function* _recursiveSearch( value: any, - path: ObjectKeyType[] = [], - seen: Set<any>, - skipOnMatch: boolean + path: PropertyKey[] = [], + seen: Set<any> = new Set(), + skipOnMatch = false ) { if (fn(path, value)) { yield [path, value]; @@ -21,15 +24,19 @@ export function search(fn: (path: ObjectKeyType[], value: any) => any) { const len = value.length; for (let i = 0; i < len; i++) { const item = value[i]; - if (seen.has(item)) {continue;} - yield* _recursiveSearch(item, path.concat(i), seen, skipOnMatch); + if (seen.has(item)) { + continue; + } + yield* _recursiveSearch(item, [...path, i], seen, skipOnMatch); } return; } for (const [key, val] of Object.entries(value)) { - if (seen.has(val)) {continue;} - yield* _recursiveSearch(val, path.concat(key), seen, skipOnMatch); + if (seen.has(val)) { + continue; + } + yield* _recursiveSearch(val, [...path, key], seen, skipOnMatch); } } return function* (obj: any, skipOnMatch = false) { diff --git a/src/v2/utils/some.ts b/src/v2/utils/some.ts index b7fa6529..6b6b788b 100644 --- a/src/v2/utils/some.ts +++ b/src/v2/utils/some.ts @@ -1,10 +1,12 @@ export function some<T>( - predicate: (item: T, index: number, iter: Iterable<T>) => any + predicate: (item: T, index: number, iter: Iterable<T>) => unknown ) { - return function (iter: Iterable<T>) { + return function (iter: Iterable<T>): boolean { let i = 0; for (const item of iter) { - if (predicate(item, i++, iter)) {return true;} + if (predicate(item, i++, iter)) { + return true; + } } return false; }; diff --git a/src/v2/utils/urlTools.ts b/src/v2/utils/urlTools.ts index 85d1396c..820e402f 100644 --- a/src/v2/utils/urlTools.ts +++ b/src/v2/utils/urlTools.ts @@ -1,8 +1,6 @@ -import type { CommandInteraction, Interaction } from 'discord.js'; -import { Message } from 'discord.js'; +import type { CommandInteraction } from 'discord.js'; import type { HeadersInit } from 'node-fetch'; -import { delayedMessageAutoDeletion } from './delayedMessageAutoDeletion.js'; import type { Provider } from './discordTools'; import { noResults, invalidResponse } from './errors.js'; import useData from './useData.js'; @@ -30,7 +28,7 @@ export const providers: ProviderMap = { getExtendedInfoUrl: () => '', help: '', icon: '', - search: '' + search: '', }, bundlephobia: { color: 0xff_ff_ff, @@ -123,7 +121,7 @@ export const KEYWORD_REGEXP = new RegExp( 'iu' ); -export const getSearchUrl = (provider: Provider, search: string) => { +export const getSearchUrl = (provider: Provider, search: string): string => { if (providers[provider]) { return providers[provider].search.replace(SEARCH_TERM, encodeURI(search)); } @@ -131,16 +129,19 @@ export const getSearchUrl = (provider: Provider, search: string) => { throw new Error(`provider not implemeted: ${provider}`); }; -export const buildDirectUrl = (provider: Provider, href: string) => { +export const buildDirectUrl = (provider: Provider, href: string): string => { if (providers[provider]) { - return providers[provider].direct.replace(TERM, href.replace(/^\//,'')); + return providers[provider].direct.replace(TERM, href.replace(/^\//u, '')); } throw new Error(`provider not implemeted: ${provider}`); }; -export const getExtendedInfoUrl = (provider: Provider, term: string) => { - if (providers[provider] && providers[provider].getExtendedInfoUrl) { +export const getExtendedInfoUrl = ( + provider: Provider, + term: string +): string => { + if (providers[provider]?.getExtendedInfoUrl) { return providers[provider].getExtendedInfoUrl(term); } @@ -153,8 +154,8 @@ type GetDataParams = { msg: CommandInteraction; provider: Provider; searchTerm: string; - sanitizeData?: (data: any) => Partial<any>; - isInvalidData: (data: any) => boolean; + sanitizeData?: (data: unknown) => Partial<unknown>; + isInvalidData: (data: unknown) => boolean; headers?: HeadersInit; }; diff --git a/src/v2/utils/useData.test.ts b/src/v2/utils/useData.test.ts index b9539d98..07ad6ede 100644 --- a/src/v2/utils/useData.test.ts +++ b/src/v2/utils/useData.test.ts @@ -1,13 +1,14 @@ +import type { Response } from 'node-fetch'; import fetch from 'node-fetch'; import useData from './useData.js'; jest.mock('node-fetch'); +const urlGen = () => `http://example.com/?q=${Date.now()}`; describe('useData', () => { - const urlGen = () => `http://example.com/?q=${Date.now()}`; const headers = { headers: {} }; - const fetchMock: jest.MockedFunction<typeof fetch> = fetch as any; + const fetchMock = fetch as jest.MockedFunction<typeof fetch>; beforeEach(jest.clearAllMocks); @@ -16,11 +17,12 @@ describe('useData', () => { fetchMock.mockResolvedValue({ ok: false, - } as any); + } as unknown as Response); const response = await useData(url, 'text'); + expect(fetchMock).toBeCalledWith(url, headers); - expect(response).toEqual({ + expect(response).toStrictEqual({ error: true, json: null, text: null, @@ -37,12 +39,12 @@ describe('useData', () => { fetchMock.mockResolvedValue({ json: jsonMock, ok: true, - } as any); + } as unknown as Response); return jsonMock; }, lastResponse => { - expect(lastResponse.json).toEqual({ test: 'cached' }); + expect(lastResponse.json).toStrictEqual({ test: 'cached' }); }, ], [ @@ -54,22 +56,24 @@ describe('useData', () => { fetchMock.mockResolvedValue({ ok: true, text: textMock, - } as any); + } as unknown as Response); return textMock; }, lastResponse => { - expect(lastResponse.text).toEqual('text'); + expect(lastResponse.text).toBe('text'); }, ], - ])( + ] as const)( 'should cache entries for type: `%s`', async (type: Parameters<typeof useData>['1'], mock, assertResponse) => { const url = urlGen(); const mockTarget = mock(); const response = await useData(url, type); + expect(fetchMock).toBeCalledWith(url, headers); + assertResponse(response); const allCachedResponses = await Promise.all([ @@ -81,7 +85,10 @@ describe('useData', () => { expect(mockTarget).toBeCalledTimes(1); expect(fetchMock).toBeCalledTimes(1); - allCachedResponses.forEach(lastResponse => assertResponse(lastResponse)); + + allCachedResponses.forEach(lastResponse => { + assertResponse(lastResponse); + }); } ); }); diff --git a/src/v2/utils/useData.ts b/src/v2/utils/useData.ts index 5edf089f..99bf27b4 100644 --- a/src/v2/utils/useData.ts +++ b/src/v2/utils/useData.ts @@ -57,7 +57,10 @@ type FetchWithFormat<Format> = ( const doFetch: <TParsedResponse>( cacheKey: string, mapper: ResponseMapper<TParsedResponse> -) => FetchWithFormat<TParsedResponse> = <TParsedResponse>(cacheKey, mapper):FetchWithFormat<TParsedResponse> => { +) => FetchWithFormat<TParsedResponse> = <TParsedResponse>( + cacheKey, + mapper +): FetchWithFormat<TParsedResponse> => { const casedCacheKey = cacheKey.toLowerCase(); const cachedResponse = apiCache.get(casedCacheKey); @@ -73,6 +76,8 @@ const doFetch: <TParsedResponse>( timestamp: Date.now(), }); + // this should be fine? + // eslint-disable-next-line @typescript-eslint/no-base-to-string const timeLabel = `Time took for url=${encodeURIComponent(url.toString())}`; // eslint-disable-next-line no-console console.time(timeLabel); @@ -87,36 +92,36 @@ const doFetch: <TParsedResponse>( const responseMapper: <T>( type: ResponseTypes -) => (response: Response) => Promise<UnknownData<T>> = <T>( - type: string -) => async response => { - if (!response.ok) { - return { - error: true, - json: null, - text: null, - } as unknown as Promise<UnknownData<T>>; - } +) => (response: Response) => Promise<UnknownData<T>> = + <T>(type: string) => + async response => { + if (!response.ok) { + return { + error: true, + json: null, + text: null, + } as unknown as Promise<UnknownData<T>>; + } - if (type === 'json') { - const json = await response.json(); - return { - error: false, - json, - text: null, - }as unknown as Promise<UnknownData<T>>; - } + if (type === 'json') { + const json = await response.json(); + return { + error: false, + json, + text: null, + } as unknown as Promise<UnknownData<T>>; + } - if (type === 'text') { - const text = await response.text(); + if (type === 'text') { + const text = await response.text(); - return { - error: false, - json: null, - text, - }as unknown as Promise<UnknownData<T>>; - } -}; + return { + error: false, + json: null, + text, + } as unknown as Promise<UnknownData<T>>; + } + }; const useData = <T>( url: string, diff --git a/tsconfig.json b/tsconfig.json index 366bfebf..d5013626 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,5 +13,5 @@ "esModuleInterop": true }, "include": ["./src/**/*.ts", "./src/*.d.ts"], - "exclude": ["node_modules", "./src/**/__fixtures__/*", "./src/**/*.test.ts"] + "exclude": ["node_modules", "./src/**/__fixtures__/*"] } diff --git a/yarn.lock b/yarn.lock index 3ce73880..bda55870 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,12 +16,45 @@ dependencies: "@babel/highlight" "^7.16.7" +"@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + "@babel/compat-data@^7.17.10": version "7.17.10" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz" integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw== -"@babel/core@7.17.10", "@babel/core@^7.11.6", "@babel/core@^7.12.3": +"@babel/compat-data@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.6.tgz#8b37d24e88e8e21c499d4328db80577d8882fa53" + integrity sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ== + +"@babel/core@7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.2.tgz#87b2fcd7cce9becaa7f5acebdc4f09f3dd19d876" + integrity sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-compilation-targets" "^7.18.2" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helpers" "^7.18.2" + "@babel/parser" "^7.18.0" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": version "7.17.10" resolved "https://registry.npmjs.org/@babel/core/-/core-7.17.10.tgz" integrity sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA== @@ -42,10 +75,10 @@ json5 "^2.2.1" semver "^6.3.0" -"@babel/eslint-parser@7.17.0": - version "7.17.0" - resolved "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.17.0.tgz" - integrity sha512-PUEJ7ZBXbRkbq3qqM/jZ2nIuakUBqCYc7Qf52Lj7dlZ6zERnqisdHioL0l4wwQZnmskMeasqUNzLBFKs3nylXA== +"@babel/eslint-parser@7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.18.2.tgz#e14dee36c010edfb0153cf900c2b0815e82e3245" + integrity sha512-oFQYkE8SuH14+uR51JVAmdqwKYXGRjEXx7s+WiagVjqQ+HPE+nnwyF2qlVG8evUsUHmPcA+6YXMEDbIhEyQc5A== dependencies: eslint-scope "^5.1.1" eslint-visitor-keys "^2.1.0" @@ -60,6 +93,15 @@ "@jridgewell/gen-mapping" "^0.3.0" jsesc "^2.5.1" +"@babel/generator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.6.tgz#9ab2d46d3cbf631f0e80f72e72874a04c3fc12a9" + integrity sha512-AIwwoOS8axIC5MZbhNHRLKi3D+DMpvDf9XUcu3pIVAfOHFT45f4AoDAltRbHIQomCipkCZxrNkfpOEHhJz/VKw== + dependencies: + "@babel/types" "^7.18.6" + "@jridgewell/gen-mapping" "^0.3.0" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz" @@ -67,6 +109,13 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-compilation-targets@^7.17.10": version "7.18.2" resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz" @@ -77,11 +126,26 @@ browserslist "^4.20.2" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.18.2": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz#18d35bfb9f83b1293c22c55b3d576c1315b6ed96" + integrity sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg== + dependencies: + "@babel/compat-data" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.20.2" + semver "^6.3.0" + "@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": version "7.18.2" resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz" integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== +"@babel/helper-environment-visitor@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7" + integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q== + "@babel/helper-function-name@^7.17.9": version "7.17.9" resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz" @@ -90,6 +154,14 @@ "@babel/template" "^7.16.7" "@babel/types" "^7.17.0" +"@babel/helper-function-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz#8334fecb0afba66e6d87a7e8c6bb7fed79926b83" + integrity sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw== + dependencies: + "@babel/template" "^7.18.6" + "@babel/types" "^7.18.6" + "@babel/helper-hoist-variables@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz" @@ -97,6 +169,13 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-module-imports@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz" @@ -104,6 +183,13 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-module-transforms@^7.17.7": version "7.18.0" resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz" @@ -118,11 +204,30 @@ "@babel/traverse" "^7.18.0" "@babel/types" "^7.18.0" +"@babel/helper-module-transforms@^7.18.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz#57e3ca669e273d55c3cda55e6ebf552f37f483c8" + integrity sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0": version "7.17.12" resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz" integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== +"@babel/helper-plugin-utils@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz#9448974dd4fb1d80fefe72e8a0af37809cd30d6d" + integrity sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg== + "@babel/helper-simple-access@^7.17.7": version "7.18.2" resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz" @@ -130,6 +235,13 @@ dependencies: "@babel/types" "^7.18.2" +"@babel/helper-simple-access@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" + integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-split-export-declaration@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz" @@ -137,16 +249,33 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-validator-identifier@^7.15.7", "@babel/helper-validator-identifier@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + "@babel/helper-validator-option@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz" integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + "@babel/helpers@^7.17.9": version "7.17.9" resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz" @@ -156,6 +285,15 @@ "@babel/traverse" "^7.17.9" "@babel/types" "^7.17.0" +"@babel/helpers@^7.18.2": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.6.tgz#4c966140eaa1fcaa3d5a8c09d7db61077d4debfd" + integrity sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ== + dependencies: + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + "@babel/highlight@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz" @@ -165,11 +303,25 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.10", "@babel/parser@^7.18.0": version "7.18.4" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz" integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow== +"@babel/parser@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.6.tgz#845338edecad65ebffef058d3be851f1d28a63bc" + integrity sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw== + "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" @@ -212,6 +364,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-syntax-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" @@ -293,6 +452,17 @@ "@babel/plugin-syntax-jsx" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/plugin-transform-react-jsx@^7.17.12": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.6.tgz#2721e96d31df96e3b7ad48ff446995d26bc028ff" + integrity sha512-Mz7xMPxoy9kPS/JScj6fJs03TZ/fZ1dJPlMjRAgTaxaS0fUBk8FV/A2rRgfPsVCZqALNwMexD+0Uaf5zlcKPpw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.18.6" + "@babel/plugin-transform-react-pure-annotations@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz" @@ -301,15 +471,15 @@ "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" -"@babel/preset-react@7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz" - integrity sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA== +"@babel/preset-react@7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.17.12.tgz#62adbd2d1870c0de3893095757ed5b00b492ab3d" + integrity sha512-h5U+rwreXtZaRBEQhW1hOJLMq8XNJBQ/9oymXiCXTuT/0uOwpbT0gUt+sXeOqoXBgNuUKI7TaObVwoEyWkpFgA== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-validator-option" "^7.16.7" "@babel/plugin-transform-react-display-name" "^7.16.7" - "@babel/plugin-transform-react-jsx" "^7.16.7" + "@babel/plugin-transform-react-jsx" "^7.17.12" "@babel/plugin-transform-react-jsx-development" "^7.16.7" "@babel/plugin-transform-react-pure-annotations" "^7.16.7" @@ -337,6 +507,15 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/template@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" + integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + "@babel/traverse@^7.17.10", "@babel/traverse@^7.17.9", "@babel/traverse@^7.18.0", "@babel/traverse@^7.7.2": version "7.18.2" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.2.tgz" @@ -353,6 +532,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.18.2", "@babel/traverse@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.6.tgz#a228562d2f46e89258efa4ddd0416942e2fd671d" + integrity sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-function-name" "^7.18.6" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.17.10", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3": version "7.18.4" resolved "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz" @@ -361,6 +556,14 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" +"@babel/types@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.6.tgz#5d781dd10a3f0c9f1f931bd19de5eb26ec31acf0" + integrity sha512-NdBNzPDwed30fZdDQtVR7ZgaO4UKjuaQFH9VArS+HMnurlOY0JWN+4ROlu/iapMFwjRQU4pOG4StZfDmulEwGA== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -373,22 +576,22 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@discordjs/builders@^0.13.0": - version "0.13.0" - resolved "https://registry.npmjs.org/@discordjs/builders/-/builders-0.13.0.tgz" - integrity sha512-4L9y26KRNNU8Y7J78SRUN1Uhava9D8jfit/YqEaKi8gQRc7PdqKqk2poybo6RXaiyt/BgKYPfcjxT7WvzGfYCA== +"@discordjs/builders@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-0.14.0.tgz#1915af10fa1c30d738b08b1ed0ae3c224fd491d7" + integrity sha512-+fqLIqa9wN3R+kvlld8sgG0nt04BAZxdCDP4t2qZ9TJsquLWA+xMtT8Waibb3d4li4AQS+IOfjiHAznv/dhHgQ== dependencies: - "@sapphire/shapeshift" "^2.0.0" + "@sapphire/shapeshift" "^3.1.0" "@sindresorhus/is" "^4.6.0" - discord-api-types "^0.31.1" + discord-api-types "^0.33.3" fast-deep-equal "^3.1.3" ts-mixer "^6.0.1" - tslib "^2.3.1" + tslib "^2.4.0" -"@discordjs/collection@^0.6.0": - version "0.6.0" - resolved "https://registry.npmjs.org/@discordjs/collection/-/collection-0.6.0.tgz" - integrity sha512-Ieaetb36l0nmAS5X9Upqk4W7euAO6FdXPxn3I8vBAKEcoIzEZI1mcVcPfCfagGJZSgBKpENnAnKkP4GAn+MV8w== +"@discordjs/collection@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.7.0.tgz#1a6c00198b744ba2b73a64442145da637ac073b8" + integrity sha512-R5i8Wb8kIcBAFEPLLf7LVBQKBDYUL+ekb23sOgpkpyGT+V4P7V83wTxcsqmX+PbqHt4cEHn053uMWfRqh/Z/nA== "@eslint/eslintrc@^1.3.0": version "1.3.0" @@ -435,28 +638,28 @@ resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/console/-/console-28.1.0.tgz" - integrity sha512-tscn3dlJFGay47kb4qVruQg/XWlmvU0xp3EJOjzzY+sBaI+YgwKcvAmTcyYU7xEiLLIY5HCdWRooAL8dqkFlDA== +"@jest/console@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.1.tgz#305f8ca50b6e70413839f54c0e002b60a0f2fd7d" + integrity sha512-0RiUocPVFEm3WRMOStIHbRWllG6iW6E3/gUPnf4lkrVFyXIIDeCe+vlKeYyFOMhB2EPE6FLFCNADSOOQMaqvyA== dependencies: - "@jest/types" "^28.1.0" + "@jest/types" "^28.1.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^28.1.0" - jest-util "^28.1.0" + jest-message-util "^28.1.1" + jest-util "^28.1.1" slash "^3.0.0" -"@jest/core@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/core/-/core-28.1.0.tgz" - integrity sha512-/2PTt0ywhjZ4NwNO4bUqD9IVJfmFVhVKGlhvSpmEfUCuxYf/3NHcKmRFI+I71lYzbTT3wMuYpETDCTHo81gC/g== +"@jest/core@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.1.tgz#086830bec6267accf9af5ca76f794858e9f9f092" + integrity sha512-3pYsBoZZ42tXMdlcFeCc/0j9kOlK7MYuXs2B1QbvDgMoW1K9NJ4G/VYvIbMb26iqlkTfPHo7SC2JgjDOk/mxXw== dependencies: - "@jest/console" "^28.1.0" - "@jest/reporters" "^28.1.0" - "@jest/test-result" "^28.1.0" - "@jest/transform" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/console" "^28.1.1" + "@jest/reporters" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.1" + "@jest/types" "^28.1.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" @@ -464,80 +667,80 @@ exit "^0.1.2" graceful-fs "^4.2.9" jest-changed-files "^28.0.2" - jest-config "^28.1.0" - jest-haste-map "^28.1.0" - jest-message-util "^28.1.0" + jest-config "^28.1.1" + jest-haste-map "^28.1.1" + jest-message-util "^28.1.1" jest-regex-util "^28.0.2" - jest-resolve "^28.1.0" - jest-resolve-dependencies "^28.1.0" - jest-runner "^28.1.0" - jest-runtime "^28.1.0" - jest-snapshot "^28.1.0" - jest-util "^28.1.0" - jest-validate "^28.1.0" - jest-watcher "^28.1.0" + jest-resolve "^28.1.1" + jest-resolve-dependencies "^28.1.1" + jest-runner "^28.1.1" + jest-runtime "^28.1.1" + jest-snapshot "^28.1.1" + jest-util "^28.1.1" + jest-validate "^28.1.1" + jest-watcher "^28.1.1" micromatch "^4.0.4" - pretty-format "^28.1.0" + pretty-format "^28.1.1" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-28.1.0.tgz" - integrity sha512-S44WGSxkRngzHslhV6RoAExekfF7Qhwa6R5+IYFa81mpcj0YgdBnRSmvHe3SNwOt64yXaE5GG8Y2xM28ii5ssA== +"@jest/environment@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.1.tgz#c4cbf85283278d768f816ebd1a258ea6f9e39d4f" + integrity sha512-9auVQ2GzQ7nrU+lAr8KyY838YahElTX9HVjbQPPS2XjlxQ+na18G113OoBhyBGBtD6ZnO/SrUy5WR8EzOj1/Uw== dependencies: - "@jest/fake-timers" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/fake-timers" "^28.1.1" + "@jest/types" "^28.1.1" "@types/node" "*" - jest-mock "^28.1.0" + jest-mock "^28.1.1" -"@jest/expect-utils@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.0.tgz" - integrity sha512-5BrG48dpC0sB80wpeIX5FU6kolDJI4K0n5BM9a5V38MGx0pyRvUBSS0u2aNTdDzmOrCjhOg8pGs6a20ivYkdmw== +"@jest/expect-utils@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.1.tgz#d84c346025b9f6f3886d02c48a6177e2b0360587" + integrity sha512-n/ghlvdhCdMI/hTcnn4qV57kQuV9OTsZzH1TTCVARANKhl6hXJqLKUkwX69ftMGpsbpt96SsDD8n8LD2d9+FRw== dependencies: jest-get-type "^28.0.2" -"@jest/expect@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/expect/-/expect-28.1.0.tgz" - integrity sha512-be9ETznPLaHOmeJqzYNIXv1ADEzENuQonIoobzThOYPuK/6GhrWNIJDVTgBLCrz3Am73PyEU2urQClZp0hLTtA== +"@jest/expect@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.1.tgz#ea4fcc8504b45835029221c0dc357c622a761326" + integrity sha512-/+tQprrFoT6lfkMj4mW/mUIfAmmk/+iQPmg7mLDIFOf2lyf7EBHaS+x3RbeR0VZVMe55IvX7QRoT/2aK3AuUXg== dependencies: - expect "^28.1.0" - jest-snapshot "^28.1.0" + expect "^28.1.1" + jest-snapshot "^28.1.1" -"@jest/fake-timers@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.0.tgz" - integrity sha512-Xqsf/6VLeAAq78+GNPzI7FZQRf5cCHj1qgQxCjws9n8rKw8r1UYoeaALwBvyuzOkpU3c1I6emeMySPa96rxtIg== +"@jest/fake-timers@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.1.tgz#47ce33296ab9d680c76076d51ddbe65ceb3337f1" + integrity sha512-BY/3+TyLs5+q87rGWrGUY5f8e8uC3LsVHS9Diz8+FV3ARXL4sNnkLlIB8dvDvRrp+LUCGM+DLqlsYubizGUjIA== dependencies: - "@jest/types" "^28.1.0" + "@jest/types" "^28.1.1" "@sinonjs/fake-timers" "^9.1.1" "@types/node" "*" - jest-message-util "^28.1.0" - jest-mock "^28.1.0" - jest-util "^28.1.0" + jest-message-util "^28.1.1" + jest-mock "^28.1.1" + jest-util "^28.1.1" -"@jest/globals@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/globals/-/globals-28.1.0.tgz" - integrity sha512-3m7sTg52OTQR6dPhsEQSxAvU+LOBbMivZBwOvKEZ+Rb+GyxVnXi9HKgOTYkx/S99T8yvh17U4tNNJPIEQmtwYw== +"@jest/globals@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.1.tgz#c0a7977f85e26279cc090d9adcdf82b8a34c4061" + integrity sha512-dEgl/6v7ToB4vXItdvcltJBgny0xBE6xy6IYQrPJAJggdEinGxCDMivNv7sFzPcTITGquXD6UJwYxfJ/5ZwDSg== dependencies: - "@jest/environment" "^28.1.0" - "@jest/expect" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/environment" "^28.1.1" + "@jest/expect" "^28.1.1" + "@jest/types" "^28.1.1" -"@jest/reporters@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.0.tgz" - integrity sha512-qxbFfqap/5QlSpIizH9c/bFCDKsQlM4uAKSOvZrP+nIdrjqre3FmKzpTtYyhsaVcOSNK7TTt2kjm+4BJIjysFA== +"@jest/reporters@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.1.tgz#9389f4bb3cce4d9b586f6195f83c79cd2a1c8662" + integrity sha512-597Zj4D4d88sZrzM4atEGLuO7SdA/YrOv9SRXHXRNC+/FwPCWxZhBAEzhXoiJzfRwn8zes/EjS8Lo6DouGN5Gg== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^28.1.0" - "@jest/test-result" "^28.1.0" - "@jest/transform" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/console" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.1" + "@jest/types" "^28.1.1" "@jridgewell/trace-mapping" "^0.3.7" "@types/node" "*" chalk "^4.0.0" @@ -550,8 +753,9 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-util "^28.1.0" - jest-worker "^28.1.0" + jest-message-util "^28.1.1" + jest-util "^28.1.1" + jest-worker "^28.1.1" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -574,42 +778,42 @@ callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.0.tgz" - integrity sha512-sBBFIyoPzrZho3N+80P35A5oAkSKlGfsEFfXFWuPGBsW40UAjCkGakZhn4UQK4iQlW2vgCDMRDOob9FGKV8YoQ== +"@jest/test-result@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.1.tgz#c6f18d1bbb01aa88925dd687872a75f8414b317a" + integrity sha512-hPmkugBktqL6rRzwWAtp1JtYT4VHwv8OQ+9lE5Gymj6dHzubI/oJHMUpPOt8NrdVWSrz9S7bHjJUmv2ggFoUNQ== dependencies: - "@jest/console" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/console" "^28.1.1" + "@jest/types" "^28.1.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.0.tgz" - integrity sha512-tZCEiVWlWNTs/2iK9yi6o3AlMfbbYgV4uuZInSVdzZ7ftpHZhCMuhvk2HLYhCZzLgPFQ9MnM1YaxMnh3TILFiQ== +"@jest/test-sequencer@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.1.tgz#f594ee2331df75000afe0d1ae3237630ecec732e" + integrity sha512-nuL+dNSVMcWB7OOtgb0EGH5AjO4UBCt68SLP08rwmC+iRhyuJWS9MtZ/MpipxFwKAlHFftbMsydXqWre8B0+XA== dependencies: - "@jest/test-result" "^28.1.0" + "@jest/test-result" "^28.1.1" graceful-fs "^4.2.9" - jest-haste-map "^28.1.0" + jest-haste-map "^28.1.1" slash "^3.0.0" -"@jest/transform@^28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-28.1.0.tgz" - integrity sha512-omy2xe5WxlAfqmsTjTPxw+iXRTRnf+NtX0ToG+4S0tABeb4KsKmPUHq5UBuwunHg3tJRwgEQhEp0M/8oiatLEA== +"@jest/transform@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.1.tgz#83541f2a3f612077c8501f49cc4e205d4e4a6b27" + integrity sha512-PkfaTUuvjUarl1EDr5ZQcCA++oXkFCP9QFUkG0yVKVmNObjhrqDy0kbMpMebfHWm3CCDHjYNem9eUSH8suVNHQ== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^28.1.0" + "@jest/types" "^28.1.1" "@jridgewell/trace-mapping" "^0.3.7" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.2.9" - jest-haste-map "^28.1.0" + jest-haste-map "^28.1.1" jest-regex-util "^28.0.2" - jest-util "^28.1.0" + jest-util "^28.1.1" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -627,6 +831,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^28.1.1": + version "28.1.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.1.tgz#d059bbc80e6da6eda9f081f293299348bd78ee0b" + integrity sha512-vRXVqSg1VhDnB8bWcmvLzmg0Bt9CRKVgHPXqYwvWMX3TvAjeO+nRuK6+VdTKCtWOvYlmkF/HqNAL/z+N3B53Kw== + dependencies: + "@jest/schemas" "^28.0.2" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.3.0": version "0.3.1" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz" @@ -667,10 +883,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@mdn/browser-compat-data@5.0.3": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.0.3.tgz#14e8fc3d56518b70f2e9d5679eb68f0d209fb6f8" - integrity sha512-u5E323ARwywaNGxDnxBSKoNYr9tbBqRbPiTevfB34KBvNwqGhYWx2Bp3vnxbS1QrcQWZSz7/m43tUujUOvmWeA== +"@mdn/browser-compat-data@5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.1.3.tgz#0208d47fc2d823bde07e0cc6f9b16799a5a9dbb0" + integrity sha512-5RqTIOtj/8m2yfiAT7fRtVwqDzpHkliozS86puqlceAFFuUQpYn17nIXTZ7hPVyhQ8wacCW42crCBdwscNBjFw== "@next/eslint-plugin-next@12.1.6": version "12.1.6" @@ -705,55 +921,55 @@ resolved "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.1.tgz" integrity sha512-FFTlPOWZX1kDj9xCAsRzH5xEJfawg1lNoYAA+ecOWJMHOfiZYb1uXOI3ne9U4UILSEPwfE68p3T9wUHwIQfR0g== -"@sapphire/shapeshift@^2.0.0": - version "2.2.0" - resolved "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-2.2.0.tgz" - integrity sha512-UEnKgMlQyI0yY/q+lCMX0VJft9y86IsesgbIQj6e62FBYSaMVr+IaMNpi4z45Q14VnuMACbK0yrbHISNqgUYcQ== +"@sapphire/shapeshift@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@sapphire/shapeshift/-/shapeshift-3.1.0.tgz#e4719fd03ab1d3ea01170f9c37b9cb64f5794759" + integrity sha512-PkxFXd3QJ1qAPS05Dy2UkVGYPm/asF1Ugt2Xyzmv4DHzO3+G7l+873C4XFFcJ9M5Je+eCMC7SSifgPTSur5QuA== -"@sentry/core@7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@sentry/core/-/core-7.0.0.tgz" - integrity sha512-Wl7MjmahLhuzzByYiWaYTeHKQfF6usnMp+rTTYTBbneuM4MD7TikRt6ybgnxqyqR7nI7ADH/U8OljtiqwnsOcw== +"@sentry/core@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.3.1.tgz#69a0cbb8678e44516f8faae0b13aaaa086183c96" + integrity sha512-UYPNDluFtj5w6alh+SjSm9p6kxReKVcKEE+EPE4o44pdowjZ1BFTUY6ipMJhtIiDPr/51eeP8TuYdOAbo0Z9Pg== dependencies: - "@sentry/hub" "7.0.0" - "@sentry/types" "7.0.0" - "@sentry/utils" "7.0.0" + "@sentry/hub" "7.3.1" + "@sentry/types" "7.3.1" + "@sentry/utils" "7.3.1" tslib "^1.9.3" -"@sentry/hub@7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@sentry/hub/-/hub-7.0.0.tgz" - integrity sha512-my4s+SPZiL6BKOK89YNk74QFRejlwVKKSetzz+Wr1cxDLbGXOIHS3uRJlagqOpfthhD1dq8m3WBQnabPf5JlHQ== +"@sentry/hub@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-7.3.1.tgz#9a0e0570631997b58484a97b6a1d3aa7368121ec" + integrity sha512-TFewt7zDvVLLrCfKkmvJEO4GPw3oPvR2t+606Z4S2gKiekGXKFGX4YC5u0ytoPfi+NMadXz4HivfrPuxrMy1JQ== dependencies: - "@sentry/types" "7.0.0" - "@sentry/utils" "7.0.0" + "@sentry/types" "7.3.1" + "@sentry/utils" "7.3.1" tslib "^1.9.3" -"@sentry/node@7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@sentry/node/-/node-7.0.0.tgz" - integrity sha512-YfmPldRH2oO3OCsu5kqnzVbeMa2Tr9Qf4t8iy7AuYKPok5ossIeZCBzjTBRw/g0wJL+I5WsxHVrt2YUuLGYBSQ== +"@sentry/node@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.3.1.tgz#4c6a9ed35a7113530fe2a53dfb1ed97aaf67729a" + integrity sha512-nuv/TZToJgRAIR4o/cpxhd3zYN5cSbXgctygDlaPSZmAdJeuuivy8Qy2ZJn37COA60yoAKRhGFBr38YBw8GQ6Q== dependencies: - "@sentry/core" "7.0.0" - "@sentry/hub" "7.0.0" - "@sentry/types" "7.0.0" - "@sentry/utils" "7.0.0" + "@sentry/core" "7.3.1" + "@sentry/hub" "7.3.1" + "@sentry/types" "7.3.1" + "@sentry/utils" "7.3.1" cookie "^0.4.1" https-proxy-agent "^5.0.0" lru_map "^0.3.3" tslib "^1.9.3" -"@sentry/types@7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@sentry/types/-/types-7.0.0.tgz" - integrity sha512-im6iugKKyeOwHWiS3u+S+Ox4F6aJQ2fe76rzTDTlzdCPol4xEqYnB2kujGVVnDYrODR+qVb24ua3OsxXxwzppA== +"@sentry/types@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.3.1.tgz#c6eaabf0ab2cc778474ba68d3d8331ec839cda6f" + integrity sha512-EZaRLJ8G2aSb7Vlpe1TR9LRZKY5gNpufuu8OXVrnTHiEMLHNUv1NrLQevy2m1SQUkg43oLdTyMKdM+nTESIC+A== -"@sentry/utils@7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@sentry/utils/-/utils-7.0.0.tgz" - integrity sha512-wmZNwzl1F/xCvaGX0TLz0+M+mZP8kn5woF770o2eUgXGURIuNsnSd0Vfi0nHuBJfngVeI/3+ofOJ9MH4Co4lIw== +"@sentry/utils@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.3.1.tgz#f0d3644e9040d8654c452b01c3ba76df448d27f2" + integrity sha512-lrwbyajioWeG/CTFCzRh0Pu+zd/P+A+ASToEy2WIQsRbUORZrOmHSVzP0KR+TSZE0TMNrwxUwjSYZoyWCfZ1wQ== dependencies: - "@sentry/types" "7.0.0" + "@sentry/types" "7.3.1" tslib "^1.9.3" "@sinclair/typebox@^0.23.3": @@ -909,13 +1125,13 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@28.1.0": - version "28.1.0" - resolved "https://registry.npmjs.org/@types/jest/-/jest-28.1.0.tgz" - integrity sha512-ITfF6JJIl9zbEi2k6NmhNE/BiDqfsI/ceqfvdaWaPbcrCpYyyRq4KtDQIWh6vQUru6SqwppODiom/Zhid+np6A== +"@types/jest@28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.3.tgz#52f3f3e50ce59191ff5fbb1084896cc0cf30c9ce" + integrity sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw== dependencies: - jest-matcher-utils "^27.0.0" - pretty-format "^27.0.0" + jest-matcher-utils "^28.0.0" + pretty-format "^28.0.0" "@types/json-schema@^7.0.9": version "7.0.11" @@ -949,11 +1165,16 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@17.0.39": +"@types/node@*": version "17.0.39" resolved "https://registry.npmjs.org/@types/node/-/node-17.0.39.tgz" integrity sha512-JDU3YLlnPK3WDao6/DlXLOgSNpG13ct+CwIO17V8q0/9fWJyeMJJ/VyZ1lv8kDprihvZMydzVwf0tQOqGiY2Nw== +"@types/node@18.0.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a" + integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz" @@ -1001,19 +1222,19 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@5.23.0": - version "5.23.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.23.0.tgz" - integrity sha512-hEcSmG4XodSLiAp1uxv/OQSGsDY6QN3TcRU32gANp+19wGE1QQZLRS8/GV58VRUoXhnkuJ3ZxNQ3T6Z6zM59DA== +"@typescript-eslint/eslint-plugin@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.1.tgz#fdf59c905354139046b41b3ed95d1609913d0758" + integrity sha512-6dM5NKT57ZduNnJfpY81Phe9nc9wolnMCnknb1im6brWi1RYv84nbMS3olJa27B6+irUVV1X/Wb+Am0FjJdGFw== dependencies: - "@typescript-eslint/scope-manager" "5.23.0" - "@typescript-eslint/type-utils" "5.23.0" - "@typescript-eslint/utils" "5.23.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/type-utils" "5.27.1" + "@typescript-eslint/utils" "5.27.1" + debug "^4.3.4" functional-red-black-tree "^1.0.1" - ignore "^5.1.8" + ignore "^5.2.0" regexpp "^3.2.0" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" "@typescript-eslint/experimental-utils@^5.3.0": @@ -1023,15 +1244,15 @@ dependencies: "@typescript-eslint/utils" "5.27.0" -"@typescript-eslint/parser@5.23.0": - version "5.23.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.23.0.tgz" - integrity sha512-V06cYUkqcGqpFjb8ttVgzNF53tgbB/KoQT/iB++DOIExKmzI9vBJKjZKt/6FuV9c+zrDsvJKbJ2DOCYwX91cbw== +"@typescript-eslint/parser@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.27.1.tgz#3a4dcaa67e45e0427b6ca7bb7165122c8b569639" + integrity sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ== dependencies: - "@typescript-eslint/scope-manager" "5.23.0" - "@typescript-eslint/types" "5.23.0" - "@typescript-eslint/typescript-estree" "5.23.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" + debug "^4.3.4" "@typescript-eslint/scope-manager@5.23.0": version "5.23.0" @@ -1049,13 +1270,21 @@ "@typescript-eslint/types" "5.27.0" "@typescript-eslint/visitor-keys" "5.27.0" -"@typescript-eslint/type-utils@5.23.0": - version "5.23.0" - resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.23.0.tgz" - integrity sha512-iuI05JsJl/SUnOTXA9f4oI+/4qS/Zcgk+s2ir+lRmXI+80D8GaGwoUqs4p+X+4AxDolPpEpVUdlEH4ADxFy4gw== +"@typescript-eslint/scope-manager@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz#4d1504392d01fe5f76f4a5825991ec78b7b7894d" + integrity sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg== dependencies: - "@typescript-eslint/utils" "5.23.0" - debug "^4.3.2" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" + +"@typescript-eslint/type-utils@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.27.1.tgz#369f695199f74c1876e395ebea202582eb1d4166" + integrity sha512-+UC1vVUWaDHRnC2cQrCJ4QtVjpjjCgjNFpg8b03nERmkHv9JV9X5M19D7UFMd+/G7T/sgFwX2pGmWK38rqyvXw== + dependencies: + "@typescript-eslint/utils" "5.27.1" + debug "^4.3.4" tsutils "^3.21.0" "@typescript-eslint/types@5.23.0": @@ -1068,6 +1297,11 @@ resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.0.tgz" integrity sha512-lY6C7oGm9a/GWhmUDOs3xAVRz4ty/XKlQ2fOLr8GAIryGn0+UBOoJDWyHer3UgrHkenorwvBnphhP+zPmzmw0A== +"@typescript-eslint/types@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.27.1.tgz#34e3e629501349d38be6ae97841298c03a6ffbf1" + integrity sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg== + "@typescript-eslint/typescript-estree@5.23.0": version "5.23.0" resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.23.0.tgz" @@ -1094,17 +1328,18 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.23.0", "@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.13.0": - version "5.23.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.23.0.tgz" - integrity sha512-dbgaKN21drqpkbbedGMNPCtRPZo1IOUr5EI9Jrrh99r5UW5Q0dz46RKXeSBoPV+56R6dFKpbrdhgUNSJsDDRZA== +"@typescript-eslint/typescript-estree@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz#7621ee78607331821c16fffc21fc7a452d7bc808" + integrity sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw== dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.23.0" - "@typescript-eslint/types" "5.23.0" - "@typescript-eslint/typescript-estree" "5.23.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" "@typescript-eslint/utils@5.27.0": version "5.27.0" @@ -1118,6 +1353,30 @@ eslint-scope "^5.1.1" eslint-utils "^3.0.0" +"@typescript-eslint/utils@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.27.1.tgz#b4678b68a94bc3b85bf08f243812a6868ac5128f" + integrity sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.13.0": + version "5.23.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.23.0.tgz" + integrity sha512-dbgaKN21drqpkbbedGMNPCtRPZo1IOUr5EI9Jrrh99r5UW5Q0dz46RKXeSBoPV+56R6dFKpbrdhgUNSJsDDRZA== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.23.0" + "@typescript-eslint/types" "5.23.0" + "@typescript-eslint/typescript-estree" "5.23.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + "@typescript-eslint/visitor-keys@5.23.0": version "5.23.0" resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.23.0.tgz" @@ -1134,6 +1393,14 @@ "@typescript-eslint/types" "5.27.0" eslint-visitor-keys "^3.3.0" +"@typescript-eslint/visitor-keys@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz#05a62666f2a89769dac2e6baa48f74e8472983af" + integrity sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ== + dependencies: + "@typescript-eslint/types" "5.27.1" + eslint-visitor-keys "^3.3.0" + abbrev@1: version "1.1.1" resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" @@ -1276,6 +1543,17 @@ array-includes@^3.1.3, array-includes@^3.1.4: get-intrinsic "^1.1.1" is-string "^1.0.7" +array-includes@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" + integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + get-intrinsic "^1.1.1" + is-string "^1.0.7" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" @@ -1291,9 +1569,9 @@ array.prototype.flat@^1.2.5: es-abstract "^1.19.2" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.2.5: +array.prototype.flatmap@^1.3.0: version "1.3.0" - resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f" integrity sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg== dependencies: call-bind "^1.0.2" @@ -1326,15 +1604,15 @@ axobject-query@^2.2.0: resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== -babel-jest@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.0.tgz" - integrity sha512-zNKk0yhDZ6QUwfxh9k07GII6siNGMJWVUU49gmFj5gfdqDKLqa2RArXOF2CODp4Dr7dLxN2cvAV+667dGJ4b4w== +babel-jest@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.1.tgz#2a3a4ae50964695b2d694ccffe4bec537c5a3586" + integrity sha512-MEt0263viUdAkTq5D7upHPNxvt4n9uLUGa6pPz3WviNBMtOmStb1lIXS3QobnoqM+qnH+vr4EKlvhe8QcmxIYw== dependencies: - "@jest/transform" "^28.1.0" + "@jest/transform" "^28.1.1" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^28.0.2" + babel-preset-jest "^28.1.1" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -1350,10 +1628,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^28.0.2: - version "28.0.2" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.0.2.tgz" - integrity sha512-Kizhn/ZL+68ZQHxSnHyuvJv8IchXD62KQxV77TBDV/xoBFBOfgRAk97GNs6hXdTTCiVES9nB2I6+7MXXrk5llQ== +babel-plugin-jest-hoist@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.1.tgz#5e055cdcc47894f28341f87f5e35aad2df680b11" + integrity sha512-NovGCy5Hn25uMJSAU8FaHqzs13cFoOI4lhIujiepssjCKRsAo3TA734RDWSGxuFTsUJXerYOqQQodlxgmtqbzw== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -1378,12 +1656,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^28.0.2: - version "28.0.2" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.0.2.tgz" - integrity sha512-sYzXIdgIXXroJTFeB3S6sNDWtlJ2dllCdTEsnZ65ACrMojj3hVNFRmnJ1HZtomGi+Be7aqpY/HJ92fr8OhKVkQ== +babel-preset-jest@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.1.tgz#5b6e5e69f963eb2d70f739c607b8f723c0ee75e4" + integrity sha512-FCq9Oud0ReTeWtcneYf/48981aTfXYuB9gbU4rBNNJVBSQ6ssv7E6v/qvbBxtOWwZFXjLZwpg+W3q7J6vhH25g== dependencies: - babel-plugin-jest-hoist "^28.0.2" + babel-plugin-jest-hoist "^28.1.1" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: @@ -1460,7 +1738,7 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -bson@^4.6.2: +bson@^4.6.2, bson@^4.6.3: version "4.6.4" resolved "https://registry.npmjs.org/bson/-/bson-4.6.4.tgz" integrity sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ== @@ -1678,6 +1956,11 @@ colorette@^2.0.16: resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== +colorette@^2.0.17: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" @@ -1867,15 +2150,10 @@ detect-newline@^3.0.0: resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -diff-sequences@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz" - integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== - -diff-sequences@^28.0.2: - version "28.0.2" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz" - integrity sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ== +diff-sequences@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" + integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== diff@^4.0.1: version "4.0.2" @@ -1889,30 +2167,25 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -discord-api-types@^0.30.0: - version "0.30.0" - resolved "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.30.0.tgz" - integrity sha512-wYst0jrT8EJs2tVlwUTQ2xT0oWMjUrRMpFTkNY3NMleWyQNHgWaKhqFfxdLPdC2im9IuR5EsxcEgjhf/npeftw== +discord-api-types@^0.33.3: + version "0.33.3" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.33.3.tgz#882c39331871f619035917a1f80d486f605b3e7a" + integrity sha512-P3A1RJXKEDmGPHrFTN5+gYLsBPGUVGj+D3+fa3m0K/umc+LMfqGuEad+p7cNq7ry/icReVhS3bz9jvBvne/BRA== -discord-api-types@^0.31.1: - version "0.31.2" - resolved "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.31.2.tgz" - integrity sha512-gpzXTvFVg7AjKVVJFH0oJGC0q0tO34iJGSHZNz9u3aqLxlD6LfxEs9wWVVikJqn9gra940oUTaPFizCkRDcEiA== - -discord.js@13.7.0: - version "13.7.0" - resolved "https://registry.npmjs.org/discord.js/-/discord.js-13.7.0.tgz" - integrity sha512-iV/An3FEB/CiBGdjWHRtgskM4UuWPq5vjhjKsrQhdVU16dbKrBxA+eIV2HWA07B3tXUGM6eco1wkr42gxxV1BA== +discord.js@13.8.1: + version "13.8.1" + resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.8.1.tgz#37627154a887ac395ca72dd814bc7366776bb90a" + integrity sha512-jOsD+4tEZWWx0RHVyH+FBcqoTrsL+d5Mm5p+ULQOdU0qSaxhLNkWYig+yDHNZoND7nlkXX3qi+BW+gO5erWylg== dependencies: - "@discordjs/builders" "^0.13.0" - "@discordjs/collection" "^0.6.0" + "@discordjs/builders" "^0.14.0" + "@discordjs/collection" "^0.7.0" "@sapphire/async-queue" "^1.3.1" "@types/node-fetch" "^2.6.1" "@types/ws" "^8.5.3" - discord-api-types "^0.30.0" + discord-api-types "^0.33.3" form-data "^4.0.0" node-fetch "^2.6.1" - ws "^8.6.0" + ws "^8.7.0" doctrine@^2.1.0: version "2.1.0" @@ -2099,37 +2372,37 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-galex@4.1.3: - version "4.1.3" - resolved "https://registry.npmjs.org/eslint-config-galex/-/eslint-config-galex-4.1.3.tgz" - integrity sha512-Y6rUIpATN0hjXeqKAB9qufWSk4IY/QM9Yr9tGktLBLLH5jgRAikaGehBME5a/yJYIDm4JH++sIbVuPZdx9Z8VQ== +eslint-config-galex@4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/eslint-config-galex/-/eslint-config-galex-4.1.4.tgz#341eb4b1ef0e0c3322d52a96d4de2ed24a3dca85" + integrity sha512-ct43ELyNeM/1OjuHZl/iTgjOMgkfKqa9ds1Aibe8i9Vt0sgKHBIIvtvf9VbUrqeUiYjlzJtvBcJkQfktWuJE4A== dependencies: - "@babel/core" "7.17.10" - "@babel/eslint-parser" "7.17.0" - "@babel/preset-react" "7.16.7" + "@babel/core" "7.18.2" + "@babel/eslint-parser" "7.18.2" + "@babel/preset-react" "7.17.12" "@next/eslint-plugin-next" "12.1.6" - "@typescript-eslint/eslint-plugin" "5.23.0" - "@typescript-eslint/parser" "5.23.0" + "@typescript-eslint/eslint-plugin" "5.27.1" + "@typescript-eslint/parser" "5.27.1" confusing-browser-globals "1.0.11" eslint-config-prettier "8.5.0" eslint-import-resolver-jsconfig "1.1.0" eslint-import-resolver-node "0.3.6" eslint-import-resolver-typescript "2.7.1" eslint-plugin-import "2.26.0" - eslint-plugin-jest "26.1.5" - eslint-plugin-jest-dom "4.0.1" + eslint-plugin-jest "26.5.3" + eslint-plugin-jest-dom "4.0.2" eslint-plugin-jest-formatting "3.1.0" eslint-plugin-jsx-a11y "6.5.1" eslint-plugin-promise "6.0.0" - eslint-plugin-react "7.29.4" + eslint-plugin-react "7.30.0" eslint-plugin-react-hooks "4.5.0" eslint-plugin-sonarjs "0.13.0" - eslint-plugin-storybook "0.5.11" - eslint-plugin-testing-library "5.5.0" + eslint-plugin-storybook "0.5.12" + eslint-plugin-testing-library "5.5.1" eslint-plugin-unicorn "42.0.0" lodash.merge "4.6.2" read-pkg-up "7.0.1" - typescript "4.6.4" + typescript "4.7.3" eslint-config-prettier@8.5.0: version "8.5.0" @@ -2191,10 +2464,10 @@ eslint-plugin-import@2.26.0: resolve "^1.22.0" tsconfig-paths "^3.14.1" -eslint-plugin-jest-dom@4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-4.0.1.tgz" - integrity sha512-9aUaX4AtlFBziLqKSjc7DKHQ/y1T32qNapG3uSeLDMJYKswASoQLJWOfLIE+zEHKvCNzNIz8T7282tQkuu0TKQ== +eslint-plugin-jest-dom@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-4.0.2.tgz#9d3e2f51055f74c74e745d89c4b1a9781e0ec7a9" + integrity sha512-Jo51Atwyo2TdcUncjmU+UQeSTKh3sc2LF/M5i/R3nTU0Djw9V65KGJisdm/RtuKhy2KH/r7eQ1n6kwYFPNdHlA== dependencies: "@babel/runtime" "^7.16.3" "@testing-library/dom" "^8.11.1" @@ -2205,10 +2478,10 @@ eslint-plugin-jest-formatting@3.1.0: resolved "https://registry.npmjs.org/eslint-plugin-jest-formatting/-/eslint-plugin-jest-formatting-3.1.0.tgz" integrity sha512-XyysraZ1JSgGbLSDxjj5HzKKh0glgWf+7CkqxbTqb7zEhW7X2WHo5SBQ8cGhnszKN+2Lj3/oevBlHNbHezoc/A== -eslint-plugin-jest@26.1.5: - version "26.1.5" - resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.1.5.tgz" - integrity sha512-su89aDuljL9bTjEufTXmKUMSFe2kZUL9bi7+woq+C2ukHZordhtfPm4Vg+tdioHBaKf8v3/FXW9uV0ksqhYGFw== +eslint-plugin-jest@26.5.3: + version "26.5.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-26.5.3.tgz#a3ceeaf4a757878342b8b00eca92379b246e5505" + integrity sha512-sICclUqJQnR1bFRZGLN2jnSVsYOsmPYYnroGCIMVSvTS3y8XR3yjzy1EcTQmk6typ5pRgyIWzbjqxK6cZHEZuQ== dependencies: "@typescript-eslint/utils" "^5.10.0" @@ -2240,44 +2513,44 @@ eslint-plugin-react-hooks@4.5.0: resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.5.0.tgz" integrity sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw== -eslint-plugin-react@7.29.4: - version "7.29.4" - resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz" - integrity sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ== +eslint-plugin-react@7.30.0: + version "7.30.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.30.0.tgz#8e7b1b2934b8426ac067a0febade1b13bd7064e3" + integrity sha512-RgwH7hjW48BleKsYyHK5vUAvxtE9SMPDKmcPRQgtRCYaZA0XQPt5FSkrU3nhz5ifzMZcA8opwmRJ2cmOO8tr5A== dependencies: - array-includes "^3.1.4" - array.prototype.flatmap "^1.2.5" + array-includes "^3.1.5" + array.prototype.flatmap "^1.3.0" doctrine "^2.1.0" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" object.entries "^1.1.5" object.fromentries "^2.0.5" - object.hasown "^1.1.0" + object.hasown "^1.1.1" object.values "^1.1.5" prop-types "^15.8.1" resolve "^2.0.0-next.3" semver "^6.3.0" - string.prototype.matchall "^4.0.6" + string.prototype.matchall "^4.0.7" eslint-plugin-sonarjs@0.13.0: version "0.13.0" resolved "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.13.0.tgz" integrity sha512-t3m7ta0EspzDxSOZh3cEOJIJVZgN/TlJYaBGnQlK6W/PZNbWep8q4RQskkJkA7/zwNpX0BaoEOSUUrqaADVoqA== -eslint-plugin-storybook@0.5.11: - version "0.5.11" - resolved "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.5.11.tgz" - integrity sha512-uNQn9zjH/+UiFxRC4hd69DnxOu4RoRbr76+yc3Bp/vIEo/n2K+NdQGh3zK9JLdMoctFAbzMfoKi9FQbZVZsCvQ== +eslint-plugin-storybook@0.5.12: + version "0.5.12" + resolved "https://registry.yarnpkg.com/eslint-plugin-storybook/-/eslint-plugin-storybook-0.5.12.tgz#b84d38400b91a9abdf15cd2c81644bff27861a96" + integrity sha512-ojuNKnrZFrQpm5N5Lp8UR0VEn4HtLjlNn6nxQAYlmTsEXNigtId1XPuMbXAsvFcEmv3RTb5l+9tZgkhSURfACg== dependencies: "@storybook/csf" "^0.0.1" "@typescript-eslint/experimental-utils" "^5.3.0" requireindex "^1.1.0" -eslint-plugin-testing-library@5.5.0: - version "5.5.0" - resolved "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.5.0.tgz" - integrity sha512-eWQ19l6uWL7LW8oeMyQVSGjVYFnBqk7DMHjadm0yOHBvX3Xi9OBrsNuxoAMdX4r7wlQ5WWpW46d+CB6FWFL/PQ== +eslint-plugin-testing-library@5.5.1: + version "5.5.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.5.1.tgz#6fe602f9082a421b471bbae8aed692e26fe981b3" + integrity sha512-plLEkkbAKBjPxsLj7x4jNapcHAg2ernkQlKKrN2I8NrQwPISZHyCUNvg5Hv3EDqOQReToQb5bnqXYbkijJPE/g== dependencies: "@typescript-eslint/utils" "^5.13.0" @@ -2334,10 +2607,10 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@8.16.0: - version "8.16.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-8.16.0.tgz" - integrity sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA== +eslint@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.18.0.tgz#78d565d16c993d0b73968c523c0446b13da784fd" + integrity sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA== dependencies: "@eslint/eslintrc" "^1.3.0" "@humanwhocodes/config-array" "^0.9.2" @@ -2453,16 +2726,16 @@ exit@^0.1.2: resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/expect/-/expect-28.1.0.tgz" - integrity sha512-qFXKl8Pmxk8TBGfaFKRtcQjfXEnKAs+dmlxdwvukJZorwrAabT7M3h8oLOG01I2utEhkmUTi17CHaPBovZsKdw== +expect@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.1.tgz#ca6fff65f6517cf7220c2e805a49c19aea30b420" + integrity sha512-/AANEwGL0tWBwzLNOvO0yUdy2D52jVdNXppOqswC49sxMN2cPWsGCQdzuIf9tj6hHoBQzNvx75JUYuQAckPo3w== dependencies: - "@jest/expect-utils" "^28.1.0" + "@jest/expect-utils" "^28.1.1" jest-get-type "^28.0.2" - jest-matcher-utils "^28.1.0" - jest-message-util "^28.1.0" - jest-util "^28.1.0" + jest-matcher-utils "^28.1.1" + jest-message-util "^28.1.1" + jest-util "^28.1.1" fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" @@ -2870,7 +3143,7 @@ ignore-by-default@^1.0.1: resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz" integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= -ignore@^5.1.8, ignore@^5.2.0: +ignore@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== @@ -3181,205 +3454,180 @@ jest-changed-files@^28.0.2: execa "^5.0.0" throat "^6.0.1" -jest-circus@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.0.tgz" - integrity sha512-rNYfqfLC0L0zQKRKsg4n4J+W1A2fbyGH7Ss/kDIocp9KXD9iaL111glsLu7+Z7FHuZxwzInMDXq+N1ZIBkI/TQ== +jest-circus@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.1.tgz#3d27da6a974d85a466dc0cdc6ddeb58daaa57bb4" + integrity sha512-75+BBVTsL4+p2w198DQpCeyh1RdaS2lhEG87HkaFX/UG0gJExVq2skG2pT7XZEGBubNj2CytcWSPan4QEPNosw== dependencies: - "@jest/environment" "^28.1.0" - "@jest/expect" "^28.1.0" - "@jest/test-result" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/environment" "^28.1.1" + "@jest/expect" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/types" "^28.1.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^28.1.0" - jest-matcher-utils "^28.1.0" - jest-message-util "^28.1.0" - jest-runtime "^28.1.0" - jest-snapshot "^28.1.0" - jest-util "^28.1.0" - pretty-format "^28.1.0" + jest-each "^28.1.1" + jest-matcher-utils "^28.1.1" + jest-message-util "^28.1.1" + jest-runtime "^28.1.1" + jest-snapshot "^28.1.1" + jest-util "^28.1.1" + pretty-format "^28.1.1" slash "^3.0.0" stack-utils "^2.0.3" throat "^6.0.1" -jest-cli@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.0.tgz" - integrity sha512-fDJRt6WPRriHrBsvvgb93OxgajHHsJbk4jZxiPqmZbMDRcHskfJBBfTyjFko0jjfprP544hOktdSi9HVgl4VUQ== +jest-cli@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.1.tgz#23ddfde8940e1818585ae4a568877b33b0e51cfe" + integrity sha512-+sUfVbJqb1OjBZ0OdBbI6OWfYM1i7bSfzYy6gze1F1w3OKWq8ZTEKkZ8a7ZQPq6G/G1qMh/uKqpdWhgl11NFQQ== dependencies: - "@jest/core" "^28.1.0" - "@jest/test-result" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/core" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/types" "^28.1.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^28.1.0" - jest-util "^28.1.0" - jest-validate "^28.1.0" + jest-config "^28.1.1" + jest-util "^28.1.1" + jest-validate "^28.1.1" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-28.1.0.tgz" - integrity sha512-aOV80E9LeWrmflp7hfZNn/zGA4QKv/xsn2w8QCBP0t0+YqObuCWTSgNbHJ0j9YsTuCO08ZR/wsvlxqqHX20iUA== +jest-config@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.1.tgz#e90b97b984f14a6c24a221859e81b258990fce2f" + integrity sha512-tASynMhS+jVV85zKvjfbJ8nUyJS/jUSYZ5KQxLUN2ZCvcQc/OmhQl2j6VEL3ezQkNofxn5pQ3SPYWPHb0unTZA== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^28.1.0" - "@jest/types" "^28.1.0" - babel-jest "^28.1.0" + "@jest/test-sequencer" "^28.1.1" + "@jest/types" "^28.1.1" + babel-jest "^28.1.1" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^28.1.0" - jest-environment-node "^28.1.0" + jest-circus "^28.1.1" + jest-environment-node "^28.1.1" jest-get-type "^28.0.2" jest-regex-util "^28.0.2" - jest-resolve "^28.1.0" - jest-runner "^28.1.0" - jest-util "^28.1.0" - jest-validate "^28.1.0" + jest-resolve "^28.1.1" + jest-runner "^28.1.1" + jest-util "^28.1.1" + jest-validate "^28.1.1" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^28.1.0" + pretty-format "^28.1.1" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz" - integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== - dependencies: - chalk "^4.0.0" - diff-sequences "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-diff@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz" - integrity sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA== +jest-diff@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-28.1.1.tgz#1a3eedfd81ae79810931c63a1d0f201b9120106c" + integrity sha512-/MUUxeR2fHbqHoMMiffe/Afm+U8U4olFRJ0hiVG2lZatPJcnGxx292ustVu7bULhjV65IYMxRdploAKLbcrsyg== dependencies: chalk "^4.0.0" - diff-sequences "^28.0.2" + diff-sequences "^28.1.1" jest-get-type "^28.0.2" - pretty-format "^28.1.0" + pretty-format "^28.1.1" -jest-docblock@^28.0.2: - version "28.0.2" - resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.0.2.tgz" - integrity sha512-FH10WWw5NxLoeSdQlJwu+MTiv60aXV/t8KEwIRGEv74WARE1cXIqh1vGdy2CraHuWOOrnzTWj/azQKqW4fO7xg== +jest-docblock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" + integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== dependencies: detect-newline "^3.0.0" -jest-each@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-28.1.0.tgz" - integrity sha512-a/XX02xF5NTspceMpHujmOexvJ4GftpYXqr6HhhmKmExtMXsyIN/fvanQlt/BcgFoRKN4OCXxLQKth9/n6OPFg== +jest-each@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.1.tgz#ba5238dacf4f31d9fe23ddc2c44c01e7c23885c4" + integrity sha512-A042rqh17ZvEhRceDMi784ppoXR7MWGDEKTXEZXb4svt0eShMZvijGxzKsx+yIjeE8QYmHPrnHiTSQVhN4nqaw== dependencies: - "@jest/types" "^28.1.0" + "@jest/types" "^28.1.1" chalk "^4.0.0" jest-get-type "^28.0.2" - jest-util "^28.1.0" - pretty-format "^28.1.0" + jest-util "^28.1.1" + pretty-format "^28.1.1" -jest-environment-node@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.0.tgz" - integrity sha512-gBLZNiyrPw9CSMlTXF1yJhaBgWDPVvH0Pq6bOEwGMXaYNzhzhw2kA/OijNF8egbCgDS0/veRv97249x2CX+udQ== +jest-environment-node@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.1.tgz#1c86c59003a7d319fa06ea3b1bbda6c193715c67" + integrity sha512-2aV/eeY/WNgUUJrrkDJ3cFEigjC5fqT1+fCclrY6paqJ5zVPoM//sHmfgUUp7WLYxIdbPwMiVIzejpN56MxnNA== dependencies: - "@jest/environment" "^28.1.0" - "@jest/fake-timers" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/environment" "^28.1.1" + "@jest/fake-timers" "^28.1.1" + "@jest/types" "^28.1.1" "@types/node" "*" - jest-mock "^28.1.0" - jest-util "^28.1.0" - -jest-get-type@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz" - integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + jest-mock "^28.1.1" + jest-util "^28.1.1" jest-get-type@^28.0.2: version "28.0.2" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz" integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== -jest-haste-map@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.0.tgz" - integrity sha512-xyZ9sXV8PtKi6NCrJlmq53PyNVHzxmcfXNVvIRHpHmh1j/HChC4pwKgyjj7Z9us19JMw8PpQTJsFWOsIfT93Dw== +jest-haste-map@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.1.tgz#471685f1acd365a9394745bb97c8fc16289adca3" + integrity sha512-ZrRSE2o3Ezh7sb1KmeLEZRZ4mgufbrMwolcFHNRSjKZhpLa8TdooXOOFlSwoUzlbVs1t0l7upVRW2K7RWGHzbQ== dependencies: - "@jest/types" "^28.1.0" + "@jest/types" "^28.1.1" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" jest-regex-util "^28.0.2" - jest-util "^28.1.0" - jest-worker "^28.1.0" + jest-util "^28.1.1" + jest-worker "^28.1.1" micromatch "^4.0.4" - walker "^1.0.7" + walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.0.tgz" - integrity sha512-uIJDQbxwEL2AMMs2xjhZl2hw8s77c3wrPaQ9v6tXJLGaaQ+4QrNJH5vuw7hA7w/uGT/iJ42a83opAqxGHeyRIA== +jest-leak-detector@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.1.tgz#537f37afd610a4b3f4cab15e06baf60484548efb" + integrity sha512-4jvs8V8kLbAaotE+wFR7vfUGf603cwYtFf1/PYEsyX2BAjSzj8hQSVTP6OWzseTl0xL6dyHuKs2JAks7Pfubmw== dependencies: jest-get-type "^28.0.2" - pretty-format "^28.1.0" + pretty-format "^28.1.1" -jest-matcher-utils@^27.0.0: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz" - integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== +jest-matcher-utils@^28.0.0, jest-matcher-utils@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.1.tgz#a7c4653c2b782ec96796eb3088060720f1e29304" + integrity sha512-NPJPRWrbmR2nAJ+1nmnfcKKzSwgfaciCCrYZzVnNoxVoyusYWIjkBMNvu0RHJe7dNj4hH3uZOPZsQA+xAYWqsw== dependencies: chalk "^4.0.0" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-matcher-utils@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz" - integrity sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ== - dependencies: - chalk "^4.0.0" - jest-diff "^28.1.0" + jest-diff "^28.1.1" jest-get-type "^28.0.2" - pretty-format "^28.1.0" + pretty-format "^28.1.1" -jest-message-util@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.0.tgz" - integrity sha512-RpA8mpaJ/B2HphDMiDlrAZdDytkmwFqgjDZovM21F35lHGeUeCvYmm6W+sbQ0ydaLpg5bFAUuWG1cjqOl8vqrw== +jest-message-util@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.1.tgz#60aa0b475cfc08c8a9363ed2fb9108514dd9ab89" + integrity sha512-xoDOOT66fLfmTRiqkoLIU7v42mal/SqwDKvfmfiWAdJMSJiU+ozgluO7KbvoAgiwIrrGZsV7viETjc8GNrA/IQ== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^28.1.0" + "@jest/types" "^28.1.1" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^28.1.0" + pretty-format "^28.1.1" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.0.tgz" - integrity sha512-H7BrhggNn77WhdL7O1apG0Q/iwl0Bdd5E1ydhCJzL3oBLh/UYxAwR3EJLsBZ9XA3ZU4PA3UNw4tQjduBTCTmLw== +jest-mock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.1.tgz#37903d269427fa1ef5b2447be874e1c62a39a371" + integrity sha512-bDCb0FjfsmKweAvE09dZT59IMkzgN0fYBH6t5S45NoJfd2DHkS3ySG2K+hucortryhO3fVuXdlxWcbtIuV/Skw== dependencies: - "@jest/types" "^28.1.0" + "@jest/types" "^28.1.1" "@types/node" "*" jest-pnp-resolver@^1.2.2: @@ -3392,114 +3640,114 @@ jest-regex-util@^28.0.2: resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== -jest-resolve-dependencies@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.0.tgz" - integrity sha512-Ue1VYoSZquPwEvng7Uefw8RmZR+me/1kr30H2jMINjGeHgeO/JgrR6wxj2ofkJ7KSAA11W3cOrhNCbj5Dqqd9g== +jest-resolve-dependencies@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.1.tgz#3dffaaa56f4b41bc6b61053899d1756401763a27" + integrity sha512-p8Y150xYJth4EXhOuB8FzmS9r8IGLEioiaetgdNGb9VHka4fl0zqWlVe4v7mSkYOuEUg2uB61iE+zySDgrOmgQ== dependencies: jest-regex-util "^28.0.2" - jest-snapshot "^28.1.0" + jest-snapshot "^28.1.1" -jest-resolve@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.0.tgz" - integrity sha512-vvfN7+tPNnnhDvISuzD1P+CRVP8cK0FHXRwPAcdDaQv4zgvwvag2n55/h5VjYcM5UJG7L4TwE5tZlzcI0X2Lhw== +jest-resolve@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.1.tgz#bc2eaf384abdcc1aaf3ba7c50d1adf01e59095e5" + integrity sha512-/d1UbyUkf9nvsgdBildLe6LAD4DalgkgZcKd0nZ8XUGPyA/7fsnaQIlKVnDiuUXv/IeZhPEDrRJubVSulxrShA== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^28.1.0" + jest-haste-map "^28.1.1" jest-pnp-resolver "^1.2.2" - jest-util "^28.1.0" - jest-validate "^28.1.0" + jest-util "^28.1.1" + jest-validate "^28.1.1" resolve "^1.20.0" resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.0.tgz" - integrity sha512-FBpmuh1HB2dsLklAlRdOxNTTHKFR6G1Qmd80pVDvwbZXTriqjWqjei5DKFC1UlM732KjYcE6yuCdiF0WUCOS2w== +jest-runner@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.1.tgz#9ecdb3f27a00059986797aa6b012ba8306aa436c" + integrity sha512-W5oFUiDBgTsCloTAj6q95wEvYDB0pxIhY6bc5F26OucnwBN+K58xGTGbliSMI4ChQal5eANDF+xvELaYkJxTmA== dependencies: - "@jest/console" "^28.1.0" - "@jest/environment" "^28.1.0" - "@jest/test-result" "^28.1.0" - "@jest/transform" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/console" "^28.1.1" + "@jest/environment" "^28.1.1" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.1" + "@jest/types" "^28.1.1" "@types/node" "*" chalk "^4.0.0" emittery "^0.10.2" graceful-fs "^4.2.9" - jest-docblock "^28.0.2" - jest-environment-node "^28.1.0" - jest-haste-map "^28.1.0" - jest-leak-detector "^28.1.0" - jest-message-util "^28.1.0" - jest-resolve "^28.1.0" - jest-runtime "^28.1.0" - jest-util "^28.1.0" - jest-watcher "^28.1.0" - jest-worker "^28.1.0" + jest-docblock "^28.1.1" + jest-environment-node "^28.1.1" + jest-haste-map "^28.1.1" + jest-leak-detector "^28.1.1" + jest-message-util "^28.1.1" + jest-resolve "^28.1.1" + jest-runtime "^28.1.1" + jest-util "^28.1.1" + jest-watcher "^28.1.1" + jest-worker "^28.1.1" source-map-support "0.5.13" throat "^6.0.1" -jest-runtime@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.0.tgz" - integrity sha512-wNYDiwhdH/TV3agaIyVF0lsJ33MhyujOe+lNTUiolqKt8pchy1Hq4+tDMGbtD5P/oNLA3zYrpx73T9dMTOCAcg== +jest-runtime@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.1.tgz#569e1dc3c36c6c4c0b29516c1c49b6ad580abdaf" + integrity sha512-J89qEJWW0leOsqyi0D9zHpFEYHwwafFdS9xgvhFHtIdRghbadodI0eA+DrthK/1PebBv3Px8mFSMGKrtaVnleg== dependencies: - "@jest/environment" "^28.1.0" - "@jest/fake-timers" "^28.1.0" - "@jest/globals" "^28.1.0" + "@jest/environment" "^28.1.1" + "@jest/fake-timers" "^28.1.1" + "@jest/globals" "^28.1.1" "@jest/source-map" "^28.0.2" - "@jest/test-result" "^28.1.0" - "@jest/transform" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/test-result" "^28.1.1" + "@jest/transform" "^28.1.1" + "@jest/types" "^28.1.1" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" execa "^5.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^28.1.0" - jest-message-util "^28.1.0" - jest-mock "^28.1.0" + jest-haste-map "^28.1.1" + jest-message-util "^28.1.1" + jest-mock "^28.1.1" jest-regex-util "^28.0.2" - jest-resolve "^28.1.0" - jest-snapshot "^28.1.0" - jest-util "^28.1.0" + jest-resolve "^28.1.1" + jest-snapshot "^28.1.1" + jest-util "^28.1.1" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.0.tgz" - integrity sha512-ex49M2ZrZsUyQLpLGxQtDbahvgBjlLPgklkqGM0hq/F7W/f8DyqZxVHjdy19QKBm4O93eDp+H5S23EiTbbUmHw== +jest-snapshot@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.1.tgz#ab825c16c8d8b5e883bd57eee6ca8748c42ab848" + integrity sha512-1KjqHJ98adRcbIdMizjF5DipwZFbvxym/kFO4g4fVZCZRxH/dqV8TiBFCa6rqic3p0karsy8RWS1y4E07b7P0A== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^28.1.0" - "@jest/transform" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/expect-utils" "^28.1.1" + "@jest/transform" "^28.1.1" + "@jest/types" "^28.1.1" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^28.1.0" + expect "^28.1.1" graceful-fs "^4.2.9" - jest-diff "^28.1.0" + jest-diff "^28.1.1" jest-get-type "^28.0.2" - jest-haste-map "^28.1.0" - jest-matcher-utils "^28.1.0" - jest-message-util "^28.1.0" - jest-util "^28.1.0" + jest-haste-map "^28.1.1" + jest-matcher-utils "^28.1.1" + jest-message-util "^28.1.1" + jest-util "^28.1.1" natural-compare "^1.4.0" - pretty-format "^28.1.0" + pretty-format "^28.1.1" semver "^7.3.5" -jest-util@^28.0.0, jest-util@^28.1.0: +jest-util@^28.0.0: version "28.1.0" resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.0.tgz" integrity sha512-qYdCKD77k4Hwkose2YBEqQk7PzUf/NSE+rutzceduFveQREeH6b+89Dc9+wjX9dAwHcgdx4yedGA3FQlU/qCTA== @@ -3511,49 +3759,62 @@ jest-util@^28.0.0, jest-util@^28.1.0: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.0.tgz" - integrity sha512-Lly7CJYih3vQBfjLeANGgBSBJ7pEa18cxpQfQEq2go2xyEzehnHfQTjoUia8xUv4x4J80XKFIDwJJThXtRFQXQ== +jest-util@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.1.tgz#ff39e436a1aca397c0ab998db5a51ae2b7080d05" + integrity sha512-FktOu7ca1DZSyhPAxgxB6hfh2+9zMoJ7aEQA759Z6p45NuO8mWcqujH+UdHlCm/V6JTWwDztM2ITCzU1ijJAfw== dependencies: - "@jest/types" "^28.1.0" + "@jest/types" "^28.1.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.1.tgz#59b7b339b3c85b5144bd0c06ad3600f503a4acc8" + integrity sha512-Kpf6gcClqFCIZ4ti5++XemYJWUPCFUW+N2gknn+KgnDf549iLul3cBuKVe1YcWRlaF8tZV8eJCap0eECOEE3Ug== + dependencies: + "@jest/types" "^28.1.1" camelcase "^6.2.0" chalk "^4.0.0" jest-get-type "^28.0.2" leven "^3.1.0" - pretty-format "^28.1.0" + pretty-format "^28.1.1" -jest-watcher@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.0.tgz" - integrity sha512-tNHMtfLE8Njcr2IRS+5rXYA4BhU90gAOwI9frTGOqd+jX0P/Au/JfRSNqsf5nUTcWdbVYuLxS1KjnzILSoR5hA== +jest-watcher@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.1.tgz#533597fb3bfefd52b5cd115cd916cffd237fb60c" + integrity sha512-RQIpeZ8EIJMxbQrXpJQYIIlubBnB9imEHsxxE41f54ZwcqWLysL/A0ZcdMirf+XsMn3xfphVQVV4EW0/p7i7Ug== dependencies: - "@jest/test-result" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/test-result" "^28.1.1" + "@jest/types" "^28.1.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.10.2" - jest-util "^28.1.0" + jest-util "^28.1.1" string-length "^4.0.1" -jest-worker@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.0.tgz" - integrity sha512-ZHwM6mNwaWBR52Snff8ZvsCTqQsvhCxP/bT1I6T6DAnb6ygkshsyLQIMxFwHpYxht0HOoqt23JlC01viI7T03A== +jest-worker@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.1.tgz#3480c73247171dfd01eda77200f0063ab6a3bf28" + integrity sha512-Au7slXB08C6h+xbJPp7VIb6U0XX5Kc9uel/WFc6/rcTzGiaVCBRngBExSYuXSLFPULPSYU3cJ3ybS988lNFQhQ== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/jest/-/jest-28.1.0.tgz" - integrity sha512-TZR+tHxopPhzw3c3560IJXZWLNHgpcz1Zh0w5A65vynLGNcg/5pZ+VildAd7+XGOu6jd58XMY/HNn0IkZIXVXg== +jest@28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.1.tgz#3c39a3a09791e16e9ef283597d24ab19a0df701e" + integrity sha512-qw9YHBnjt6TCbIDMPMpJZqf9E12rh6869iZaN08/vpOGgHJSAaLLUn6H8W3IAEuy34Ls3rct064mZLETkxJ2XA== dependencies: - "@jest/core" "^28.1.0" + "@jest/core" "^28.1.1" + "@jest/types" "^28.1.1" import-local "^3.0.2" - jest-cli "^28.1.0" + jest-cli "^28.1.1" "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -3625,6 +3886,11 @@ kareem@2.3.5: resolved "https://registry.npmjs.org/kareem/-/kareem-2.3.5.tgz" integrity sha512-qxCyQtp3ioawkiRNQr/v8xw9KIviMSSNmy+63Wubj7KmMn3g7noRXIZB4vPCAP+ETi2SR8eH6CvmlKZuGpoHOg== +kareem@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.4.1.tgz#7d81ec518204a48c1cb16554af126806c3cd82b0" + integrity sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA== + keyv@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz" @@ -3679,13 +3945,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -lint-staged@13.0.0: - version "13.0.0" - resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-13.0.0.tgz" - integrity sha512-vWban5utFt78VZohbosUxNIa46KKJ+KOQTDWTQ8oSl1DLEEVl9zhUtaQbiiydAmx+h2wKJK2d0+iMaRmknuWRQ== +lint-staged@13.0.3: + version "13.0.3" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.0.3.tgz#d7cdf03a3830b327a2b63c6aec953d71d9dc48c6" + integrity sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug== dependencies: cli-truncate "^3.1.0" - colorette "^2.0.16" + colorette "^2.0.17" commander "^9.3.0" debug "^4.3.4" execa "^6.1.0" @@ -3694,7 +3960,7 @@ lint-staged@13.0.0: micromatch "^4.0.5" normalize-path "^3.0.0" object-inspect "^1.12.2" - pidtree "^0.5.0" + pidtree "^0.6.0" string-argv "^0.3.1" yaml "^2.1.1" @@ -3897,7 +4163,19 @@ mongodb@4.5.0: optionalDependencies: saslprep "^1.0.3" -mongoose@*, mongoose@6.3.5: +mongodb@4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.7.0.tgz#99f7323271d93659067695b60e7b4efee2de9bf0" + integrity sha512-HhVar6hsUeMAVlIbwQwWtV36iyjKd9qdhY+s4wcU8K6TOj4Q331iiMy+FoPuxEntDIijTYWivwFJkLv8q/ZgvA== + dependencies: + bson "^4.6.3" + denque "^2.0.1" + mongodb-connection-string-url "^2.5.2" + socks "^2.6.2" + optionalDependencies: + saslprep "^1.0.3" + +mongoose@*: version "6.3.5" resolved "https://registry.npmjs.org/mongoose/-/mongoose-6.3.5.tgz" integrity sha512-Ho3b/MK3lFyb87NjzyVwrjCqQ5DuLsIPSMFYDuZjaIJNhZfHNPQIcUDR1RLZ0/l+uznwo0VBu3WSwdu8EfAZTA== @@ -3910,6 +4188,19 @@ mongoose@*, mongoose@6.3.5: ms "2.1.3" sift "16.0.0" +mongoose@6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-6.4.1.tgz#e50e1a92ccf7764f2cc57b5a801a52918c7c3e72" + integrity sha512-6a3UmHaC2BYdxZT7qqwORqbxDfAa5HaRMidkA8Ll4Rupnl6R8vRu5Av13jx4DaxgJBpPDo4/K9AXxb+OGSD+5w== + dependencies: + bson "^4.6.2" + kareem "2.4.1" + mongodb "4.7.0" + mpath "0.9.0" + mquery "4.0.3" + ms "2.1.3" + sift "16.0.0" + mpath@0.9.0: version "0.9.0" resolved "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz" @@ -3954,7 +4245,7 @@ node-domexception@^1.0.0: resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== -node-fetch@*, node-fetch@3.2.5: +node-fetch@*: version "3.2.5" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.5.tgz" integrity sha512-u7zCHdJp8JXBwF09mMfo2CL6kp37TslDl1KP3hRGTlCInBtag+UO3LGVy+NF0VzvnL3PVMpA2hXh1EtECFnyhQ== @@ -3963,6 +4254,15 @@ node-fetch@*, node-fetch@3.2.5: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" +node-fetch@3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.6.tgz#6d4627181697a9d9674aae0d61548e0d629b31b9" + integrity sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-fetch@^2.6.1: version "2.6.6" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz" @@ -3988,10 +4288,10 @@ node-releases@^2.0.3: resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz" integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== -nodemon@2.0.16: - version "2.0.16" - resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.16.tgz" - integrity sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w== +nodemon@2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.18.tgz#0f5a3aa7b4587f2626e6f01369deba89cb0462a2" + integrity sha512-uAvrKipi2zAz8E7nkSz4qW4F4zd5fs2wNGsTx+xXlP8KXqd9ucE0vY9wankOsPboeDyuUGN9vsXGV1pLn80l/A== dependencies: chokidar "^3.5.2" debug "^3.2.7" @@ -4095,9 +4395,9 @@ object.fromentries@^2.0.5: define-properties "^1.1.3" es-abstract "^1.19.1" -object.hasown@^1.1.0: +object.hasown@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.1.tgz#ad1eecc60d03f49460600430d97f23882cf592a3" integrity sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A== dependencies: define-properties "^1.1.4" @@ -4267,10 +4567,10 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pidtree@^0.5.0: - version "0.5.0" - resolved "https://registry.npmjs.org/pidtree/-/pidtree-0.5.0.tgz" - integrity sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA== +pidtree@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== pirates@^4.0.4: version "4.0.5" @@ -4299,12 +4599,12 @@ prepend-http@^2.0.0: resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier@2.6.2: - version "2.6.2" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz" - integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== +prettier@2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== -pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: +pretty-format@^27.0.2: version "27.5.1" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== @@ -4313,10 +4613,10 @@ pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^28.1.0: - version "28.1.0" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz" - integrity sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q== +pretty-format@^28.0.0, pretty-format@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.1.tgz#f731530394e0f7fcd95aba6b43c50e02d86b95cb" + integrity sha512-wwJbVTGFHeucr5Jw2bQ9P+VYHyLdAqedFLEkdQUVaBF/eiidDwH5OpilINq4mEfhbCjLnirt6HTTDhv1HaTIQw== dependencies: "@jest/schemas" "^28.0.2" ansi-regex "^5.0.1" @@ -4781,9 +5081,9 @@ string-width@^5.0.0: is-fullwidth-code-point "^4.0.0" strip-ansi "^7.0.1" -string.prototype.matchall@^4.0.6: +string.prototype.matchall@^4.0.7: version "4.0.7" - resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d" integrity sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg== dependencies: call-bind "^1.0.2" @@ -4971,10 +5271,10 @@ tr46@~0.0.3: resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= -ts-jest@28.0.4: - version "28.0.4" - resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.4.tgz" - integrity sha512-S6uRDDdCJBvnZqyGjB4VCnwbQrbgdL8WPeP4jevVSpYsBaeGRQAIS08o3Svav2Ex+oXwLgJ/m7F24TNq62kA1A== +ts-jest@28.0.5: + version "28.0.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.5.tgz#31776f768fba6dfc8c061d488840ed0c8eeac8b9" + integrity sha512-Sx9FyP9pCY7pUzQpy4FgRZf2bhHY3za576HMKJFs+OnQ9jS96Du5vNsDKkyedQkik+sEabbKAnCliv9BEsHZgQ== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" @@ -4983,17 +5283,17 @@ ts-jest@28.0.4: lodash.memoize "4.x" make-error "1.x" semver "7.x" - yargs-parser "^20.x" + yargs-parser "^21.0.1" ts-mixer@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.1.tgz" integrity sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg== -ts-node@10.8.0: - version "10.8.0" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz" - integrity sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA== +ts-node@10.8.1: + version "10.8.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.1.tgz#ea2bd3459011b52699d7e88daa55a45a1af4f066" + integrity sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g== dependencies: "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" @@ -5029,9 +5329,9 @@ tslib@^2.1.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== -tslib@^2.3.1: +tslib@^2.4.0: version "2.4.0" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== tsutils@^3.21.0: @@ -5080,15 +5380,15 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.6.4: - version "4.6.4" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz" - integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== +typescript@4.7.3: + version "4.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" + integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== -typescript@4.7.2: - version "4.7.2" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz" - integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A== +typescript@4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== unbox-primitive@^1.0.2: version "1.0.2" @@ -5173,9 +5473,9 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -walker@^1.0.7: +walker@^1.0.8: version "1.0.8" - resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: makeerror "1.0.12" @@ -5282,9 +5582,9 @@ write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@^8.6.0: +ws@^8.7.0: version "8.7.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.7.0.tgz" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.7.0.tgz#eaf9d874b433aa00c0e0d8752532444875db3957" integrity sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg== xdg-basedir@^4.0.0: @@ -5307,12 +5607,7 @@ yaml@^2.1.1: resolved "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz" integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw== -yargs-parser@^20.x: - version "20.2.9" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-parser@^21.0.0: +yargs-parser@^21.0.0, yargs-parser@^21.0.1: version "21.0.1" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz" integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==