├── .env.example ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc.js ├── .vscode └── settings.json ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── heroku.yml ├── meta └── logo.png ├── package.json ├── src ├── bot.ts ├── db.ts ├── entities │ ├── HelpThread.ts │ ├── Rep.ts │ └── Snippet.ts ├── env.ts ├── index.ts ├── log.ts ├── modules │ ├── autorole.ts │ ├── etc.ts │ ├── handbook.ts │ ├── help.ts │ ├── helpForum.ts │ ├── mod.ts │ ├── playground.ts │ ├── rep.ts │ ├── snippet.ts │ └── twoslash.ts └── util │ ├── codeBlocks.ts │ ├── customCommand.ts │ ├── getReferencedMessage.ts │ ├── getTypeScriptModule.ts │ ├── limitedSizeMap.ts │ ├── send.ts │ └── sendPaginatedMessage.ts ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | TOKEN=YOUR_DISCORD_BOT_TOKEN 2 | BOT_ADMINS=123,456 3 | 4 | # can have multiple seperated by , 5 | AUTOROLE=MSG_ID:ROLE_ID:EMOJI:AUTOREMOVE 6 | # Another example: AUTOROLE=738932146978160661:728202487672078368:❌:false,738932146978160661:738936540465725520:✅:true 7 | 8 | DATABASE_URL="postgres://tscbot:tscbot@localhost:5432/tscbot" 9 | 10 | # Role given to trusted members, not full moderators, but can use some commands which 11 | # are not given to all server members. 12 | TRUSTED_ROLE_ID= 13 | 14 | RULES_CHANNEL= 15 | 16 | ROLES_CHANNEL= 17 | 18 | HOW_TO_GET_HELP_CHANNEL= 19 | HOW_TO_GIVE_HELP_CHANNEL= 20 | 21 | HELP_FORUM_CHANNEL= 22 | HELP_REQUESTS_CHANNEL= 23 | HELP_FORUM_OPEN_TAG=Open 24 | HELP_FORUM_RESOLVED_TAG=Resolved 25 | 26 | # Time in milliseconds before !helper can be run 27 | TIME_BEFORE_HELPER_PING= 28 | 29 | SUGGESTIONS_CHANNEL= 30 | 31 | LOG_CHANNEL= 32 | 33 | # PREFIXES=!,t! 34 | 35 | # REP_EMOJI=⭐ 36 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | IMAGE_NAME: bot 9 | 10 | jobs: 11 | main: 12 | name: main 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: install node v16 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: 16 20 | 21 | - name: yarn install 22 | run: yarn install 23 | - name: build 24 | run: yarn run build 25 | - name: lint 26 | run: yarn run lint 27 | 28 | - name: Set up Docker Buildx 29 | if: contains(github.ref, 'master') 30 | uses: docker/setup-buildx-action@v1 31 | 32 | - name: Login to Github Container Registry 33 | if: contains(github.ref, 'master') 34 | uses: docker/login-action@v1 35 | with: 36 | registry: ghcr.io 37 | username: ${{ github.repository_owner }} 38 | password: ${{ secrets.GHCR_TOKEN }} 39 | 40 | - name: Build and push 41 | if: contains(github.ref, 'master') 42 | uses: docker/build-push-action@v2 43 | with: 44 | context: . 45 | file: ./Dockerfile 46 | push: true 47 | cache-from: type=registry,ref=ghcr.io/typescript-community/bot:latest 48 | cache-to: type=inline 49 | tags: | 50 | ghcr.io/typescript-community/bot:latest 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | dist/ 64 | 65 | .idea 66 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * Include parentheses around a sole arrow function parameter. 4 | * 5 | * avoid - Omit parens when possible. Example: `x => x` 6 | * always - Always include parens. Example: `(x) => x` 7 | */ 8 | arrowParens: 'avoid', 9 | 10 | /** 11 | * Print spaces between brackets. 12 | * 13 | * Type: boolean 14 | */ 15 | bracketSpacing: true, 16 | 17 | /** 18 | * Print (to stderr) where a cursor at the given position would move to after formatting. 19 | * This option cannot be used with --range-start and --range-end. 20 | * 21 | * Type: integer 22 | */ 23 | cursorOffset: -1, 24 | 25 | /** 26 | * Which end of line characters to apply. 27 | * 28 | * auto - Maintain existing (mixed values within one file are normalised by looking at what's used after the first line) 29 | * lf - Line Feed only (\n), common on Linux and macOS as well as inside git repos 30 | * crlf - Carriage Return + Line Feed characters (\r\n), common on Windows 31 | * cr - Carriage Return character only (\r), used very rarely 32 | */ 33 | endOfLine: 'auto', 34 | 35 | /** 36 | * How to wrap prose. 37 | * 38 | * always - Wrap prose if it exceeds the print width. 39 | * never - Do not wrap prose. 40 | * preserve - Wrap prose as-is. 41 | */ 42 | proseWrap: 'preserve', 43 | 44 | /** 45 | * Change when properties in objects are quoted. 46 | * 47 | * as-needed - Only add quotes around object properties where required. 48 | * consistent - If at least one property in an object requires quotes, quote all properties. 49 | * preserve - Respect the input use of quotes in object properties. 50 | */ 51 | quoteProps: 'as-needed', 52 | 53 | /** 54 | * Print semicolons. 55 | * 56 | * Type: boolean 57 | */ 58 | semi: true, 59 | 60 | /** 61 | * Use single quotes instead of double quotes. 62 | * 63 | * Type: boolean 64 | */ 65 | singleQuote: true, 66 | 67 | /** 68 | * Number of spaces per indentation level. 69 | * 70 | * Type: integer 71 | */ 72 | tabWidth: 4, 73 | 74 | /** 75 | * Print trailing commas wherever possible when multi-line. 76 | * 77 | * none - No trailing commas. 78 | * es5 - Trailing commas where valid in ES5 (objects, arrays, etc.) 79 | * all - Trailing commas wherever possible (including function arguments). 80 | */ 81 | trailingComma: 'all', 82 | 83 | /** 84 | * Indent with tabs instead of spaces. 85 | * 86 | * Type: boolean 87 | */ 88 | useTabs: true, 89 | }; 90 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "cSpell.words": [ 4 | "algoliasearch", 5 | "autorole", 6 | "Cooldown", 7 | "leaderboard", 8 | "twoslash", 9 | "twoslasher" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2022-12-16 2 | 3 | - Remove `!close`, update `!helper` to include thread tags. 4 | 5 | # 2022-11-19 6 | 7 | - Removed `HELP_CATEGORY`, `GENERAL_HELP_CHANNEL` environment variables. 8 | - Added `HELP_FORUM_CHANNEL`, `HELP_REQUESTS_CHANNEL` environment variables. 9 | - Updated how to get help and how to give help channel content to not use embeds. 10 | - Updated to Discord.js 14, removed Cookiecord to prevent future delays in updating versions. 11 | - The bot will now react on the configured autorole messages to indicate available roles. 12 | - Unhandled rejections will now only be ignored if `NODE_ENV` is set to `production`. 13 | - Removed admin `checkThreads` command as using it would result in the bot checking for closed threads twice as often until restarted. 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.18.1-alpine 2 | WORKDIR /usr/src/app 3 | 4 | COPY yarn.lock ./ 5 | COPY package.json ./ 6 | 7 | RUN yarn 8 | 9 | COPY . . 10 | 11 | RUN yarn build 12 | 13 | CMD [ "node", "dist/index.js" ] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 TypeScript Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | logo 2 | 3 | # TypeScript Community Bot 4 | 5 | _A utility bot built for the [TypeScript Community Discord Server](https://discord.gg/typescript)._ 6 | 7 | chat on Discord 8 | 9 | # Contributing 10 | 11 | See the [documentation](https://cookiecord.js.org/) for the framework we use. 12 | 13 | We also have a docker-compose.yml for development, along with a .env.example. 14 | 15 | **A quick note about the help channel system:** Please only use it if you have a large server (10k+ members) as it will likely inconvenience your members rather than benefit them. We used a static channel system (#help-1 and #help-2) up until around 9,000 members, when we started to see issues arising (many questions being asked on top of each other without answers). 16 | 17 | ## Thanks! 18 | 19 | - [ckie](https://github.com/ckiee) for writing the base for the bot! 20 | - [Python Discord](https://github.com/python-discord) for heavily influencing our help channel system. 21 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Development compose file 2 | version: '3' 3 | 4 | services: 5 | bot: 6 | image: node:16 7 | command: yarn start 8 | depends_on: 9 | - postgres 10 | volumes: 11 | - .:/srv/bot:rw 12 | working_dir: /srv/bot 13 | environment: 14 | - NODE_ENV=development 15 | - DATABASE_URL=postgres://tscbot:tscbot@postgres:5432/tscbot 16 | env_file: 17 | - .env 18 | 19 | postgres: 20 | image: postgres 21 | restart: always 22 | environment: 23 | POSTGRES_USER: 'tscbot' 24 | POSTGRES_DB: 'tscbot' 25 | POSTGRES_PASSWORD: tscbot 26 | PGPORT: 5432 27 | volumes: 28 | - 'postgres_data:/postgres/data' 29 | ports: 30 | - 5432:5432 31 | 32 | volumes: 33 | postgres_data: 34 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | worker: Dockerfile 4 | -------------------------------------------------------------------------------- /meta/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typescript-community/community-bot/02b8dc2d422298f13800763b55c28a7091d93cd3/meta/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsc-bot", 3 | "version": "0.0.1", 4 | "description": "Typescript Community Bot", 5 | "main": "dist/index.js", 6 | "dependencies": { 7 | "@typescript/twoslash": "^3.2.1", 8 | "algoliasearch": "^4.14.2", 9 | "discord.js": "^14.6.0", 10 | "dotenv-safe": "^8.2.0", 11 | "html-entities": "^2.3.3", 12 | "lz-string": "^1.4.4", 13 | "npm-registry-fetch": "^14.0.2", 14 | "parse-duration": "^1.0.2", 15 | "pg": "^8.8.0", 16 | "prettier": "^2.7.1", 17 | "pretty-ms": "^8.0.0", 18 | "tar": "^6.1.12", 19 | "typeorm": "^0.3.10", 20 | "undici": "^5.12.0" 21 | }, 22 | "devDependencies": { 23 | "@types/dotenv-safe": "8.1.2", 24 | "@types/lz-string": "1.3.34", 25 | "@types/node": "16.18.3", 26 | "@types/npm-registry-fetch": "8.0.4", 27 | "@types/prettier": "2.7.1", 28 | "@types/tar": "6.1.3", 29 | "@types/ws": "8.5.3", 30 | "husky": "^8.0.2", 31 | "pretty-quick": "^3.1.3", 32 | "ts-node-dev": "^2.0.0", 33 | "typescript": "^4.9.3" 34 | }, 35 | "scripts": { 36 | "start": "ts-node-dev --respawn src", 37 | "build": "tsc", 38 | "lint": "prettier --check \"src/**/*.ts\"", 39 | "lint:fix": "prettier \"src/**/*.ts\" --write ", 40 | "prepare": "husky install" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/bot.ts: -------------------------------------------------------------------------------- 1 | import { Message, Client, User, GuildMember } from 'discord.js'; 2 | import { botAdmins, prefixes, trustedRoleId } from './env'; 3 | 4 | export interface CommandRegistration { 5 | aliases: string[]; 6 | description?: string; 7 | listener: (msg: Message, content: string) => Promise; 8 | } 9 | 10 | interface Command { 11 | admin: boolean; 12 | aliases: string[]; 13 | description?: string; 14 | listener: (msg: Message, content: string) => Promise; 15 | } 16 | 17 | export class Bot { 18 | commands = new Map(); 19 | 20 | constructor(public client: Client) { 21 | client.on('messageCreate', msg => { 22 | const triggerWithPrefix = msg.content.split(/\s/)[0]; 23 | const matchingPrefix = prefixes.find(p => 24 | triggerWithPrefix.startsWith(p), 25 | ); 26 | if (matchingPrefix) { 27 | const content = msg.content 28 | .substring(triggerWithPrefix.length + 1) 29 | .trim(); 30 | 31 | const command = this.getByTrigger( 32 | triggerWithPrefix.substring(matchingPrefix.length), 33 | ); 34 | 35 | if (!command || (command.admin && !this.isAdmin(msg.author))) { 36 | return; 37 | } 38 | command.listener(msg, content).catch(err => { 39 | this.client.emit('error', err); 40 | }); 41 | } 42 | }); 43 | } 44 | 45 | registerCommand(registration: CommandRegistration) { 46 | const command: Command = { 47 | ...registration, 48 | admin: false, 49 | }; 50 | for (const a of command.aliases) { 51 | this.commands.set(a, command); 52 | } 53 | } 54 | 55 | registerAdminCommand(registration: CommandRegistration) { 56 | const command: Command = { 57 | ...registration, 58 | admin: true, 59 | }; 60 | for (const a of command.aliases) { 61 | this.commands.set(a, command); 62 | } 63 | } 64 | 65 | getByTrigger(trigger: string): Command | undefined { 66 | return this.commands.get(trigger); 67 | } 68 | 69 | isMod(member: GuildMember | null) { 70 | return member?.permissions.has('ManageMessages') ?? false; 71 | } 72 | 73 | isAdmin(user: User) { 74 | return botAdmins.includes(user.id); 75 | } 76 | 77 | isTrusted(msg: Message) { 78 | if (!msg.guild || !msg.member || !msg.channel.isTextBased()) { 79 | return false; 80 | } 81 | 82 | if ( 83 | !msg.member.roles.cache.has(trustedRoleId) && 84 | !msg.member.permissions.has('ManageMessages') 85 | ) { 86 | return false; 87 | } 88 | 89 | return true; 90 | } 91 | 92 | async getTargetUser(msg: Message): Promise { 93 | const query = msg.content.split(/\s/)[1]; 94 | 95 | const mentioned = msg.mentions.members?.first()?.user; 96 | if (mentioned) return mentioned; 97 | 98 | if (!query) return; 99 | 100 | // Search by ID 101 | const queriedUser = await this.client.users 102 | .fetch(query) 103 | .catch(() => undefined); 104 | if (queriedUser) return queriedUser; 105 | 106 | // Search by name, likely a better way to do this... 107 | for (const user of this.client.users.cache.values()) { 108 | if (user.tag === query || user.username === query) { 109 | return user; 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/db.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from 'typeorm'; 2 | import { dbUrl } from './env'; 3 | import { Rep } from './entities/Rep'; 4 | import { HelpThread } from './entities/HelpThread'; 5 | import { Snippet } from './entities/Snippet'; 6 | 7 | let db: DataSource | undefined; 8 | export async function getDB() { 9 | if (db) return db; 10 | 11 | // Require ssl in production 12 | const extraOpts = 13 | process.env.NODE_ENV === 'production' 14 | ? { 15 | ssl: true, 16 | extra: { 17 | ssl: { 18 | rejectUnauthorized: false, 19 | }, 20 | }, 21 | } 22 | : {}; 23 | 24 | db = new DataSource({ 25 | type: 'postgres', 26 | url: dbUrl, 27 | synchronize: true, 28 | logging: false, 29 | entities: [Rep, HelpThread, Snippet], 30 | ...extraOpts, 31 | }); 32 | await db.initialize(); 33 | console.log('Connected to DB'); 34 | return db; 35 | } 36 | -------------------------------------------------------------------------------- /src/entities/HelpThread.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class HelpThread extends BaseEntity { 5 | @PrimaryColumn() 6 | threadId!: string; 7 | 8 | @Column() 9 | ownerId!: string; 10 | 11 | // When @helper was last pinged 12 | @Column({ nullable: true }) 13 | helperTimestamp?: string; 14 | 15 | /** 16 | * When the title was last set, exists only for backwards compat 17 | * @deprecated 18 | */ 19 | @Column({ nullable: true }) 20 | titleSetTimestamp?: string; 21 | 22 | /** 23 | * The id of the original message, exists only for backwards compat 24 | * @deprecated 25 | */ 26 | @Column({ nullable: true }) 27 | origMessageId?: string; 28 | } 29 | -------------------------------------------------------------------------------- /src/entities/Rep.ts: -------------------------------------------------------------------------------- 1 | import { Entity, BaseEntity, PrimaryColumn, Column } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Rep extends BaseEntity { 5 | @PrimaryColumn() 6 | messageId!: string; 7 | 8 | @Column() 9 | date!: string; 10 | 11 | @Column() 12 | channel!: string; 13 | 14 | @Column() 15 | amount!: number; 16 | 17 | @Column() 18 | recipient!: string; 19 | 20 | @Column() 21 | initialGiver!: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/entities/Snippet.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Snippet extends BaseEntity { 5 | @PrimaryColumn() 6 | id!: string; 7 | 8 | @Column() 9 | owner!: string; 10 | 11 | @Column() 12 | uses!: number; 13 | 14 | @Column({ nullable: true }) 15 | content?: string; 16 | 17 | @Column({ nullable: true }) 18 | title?: string; 19 | 20 | @Column({ nullable: true }) 21 | description?: string; 22 | 23 | @Column({ nullable: true }) 24 | color?: number; 25 | 26 | @Column({ nullable: true }) 27 | image?: string; 28 | 29 | @Column({ nullable: true }) 30 | url?: string; 31 | } 32 | -------------------------------------------------------------------------------- /src/env.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv-safe'; 2 | dotenv.config(); 3 | 4 | export const token = process.env.TOKEN!; 5 | export const botAdmins = process.env.BOT_ADMINS!.split(','); 6 | 7 | export const autorole = process.env.AUTOROLE!.split(',').map(x => { 8 | const [msgID, roleID, emoji, autoRemove] = x.split(':'); 9 | return { 10 | msgID, 11 | roleID, 12 | emoji, 13 | autoRemove: autoRemove == 'true', 14 | }; 15 | }); 16 | 17 | export const dbUrl = process.env.DATABASE_URL!; 18 | 19 | export const howToGetHelpChannel = process.env.HOW_TO_GET_HELP_CHANNEL!; 20 | export const howToGiveHelpChannel = process.env.HOW_TO_GIVE_HELP_CHANNEL!; 21 | export const helpForumChannel = process.env.HELP_FORUM_CHANNEL!; 22 | export const helpRequestsChannel = process.env.HELP_REQUESTS_CHANNEL!; 23 | 24 | export const helpForumOpenTagName = process.env.HELP_FORUM_OPEN_TAG!; 25 | export const helpForumResolvedTagName = process.env.HELP_FORUM_RESOLVED_TAG!; 26 | 27 | export const trustedRoleId = process.env.TRUSTED_ROLE_ID!; 28 | 29 | export const rulesChannelId = process.env.RULES_CHANNEL!; 30 | 31 | export const rolesChannelId = process.env.ROLES_CHANNEL!; 32 | 33 | export const TS_BLUE = '#007ACC'; 34 | export const GREEN = '#3ba55d'; 35 | // Picked from Discord's blockquote line 36 | export const BLOCKQUOTE_GREY = '#4f545c'; 37 | 38 | export const timeBeforeHelperPing = parseInt( 39 | process.env.TIME_BEFORE_HELPER_PING!, 40 | ); 41 | 42 | export const suggestionsChannelId = process.env.SUGGESTIONS_CHANNEL!; 43 | 44 | export const logChannelId = process.env.LOG_CHANNEL!; 45 | 46 | export const prefixes = (process.env.PREFIXES ?? '!').split(','); 47 | 48 | export const repEmoji = process.env.REP_EMOJI ?? '⭐'; 49 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Client, GatewayIntentBits, Partials } from 'discord.js'; 2 | import { Bot } from './bot'; 3 | import { getDB } from './db'; 4 | import { token } from './env'; 5 | import { hookLog } from './log'; 6 | 7 | import { autoroleModule } from './modules/autorole'; 8 | import { etcModule } from './modules/etc'; 9 | import { handbookModule } from './modules/handbook'; 10 | import { helpModule } from './modules/help'; 11 | import { modModule } from './modules/mod'; 12 | import { playgroundModule } from './modules/playground'; 13 | import { repModule } from './modules/rep'; 14 | import { twoslashModule } from './modules/twoslash'; 15 | import { snippetModule } from './modules/snippet'; 16 | import { helpForumModule } from './modules/helpForum'; 17 | 18 | const client = new Client({ 19 | partials: [ 20 | Partials.Reaction, 21 | Partials.Message, 22 | Partials.User, 23 | Partials.Channel, 24 | ], 25 | allowedMentions: { 26 | parse: ['users', 'roles'], 27 | }, 28 | intents: [ 29 | GatewayIntentBits.Guilds, 30 | GatewayIntentBits.GuildMessages, 31 | GatewayIntentBits.GuildMembers, 32 | GatewayIntentBits.GuildMessageReactions, 33 | GatewayIntentBits.DirectMessages, 34 | GatewayIntentBits.MessageContent, 35 | ], 36 | }).setMaxListeners(Infinity); 37 | 38 | getDB().then(() => client.login(token)); 39 | 40 | client.on('ready', async () => { 41 | const bot = new Bot(client); 42 | console.log(`Logged in as ${client.user?.tag}`); 43 | await hookLog(client); 44 | 45 | for (const mod of [ 46 | autoroleModule, 47 | etcModule, 48 | helpForumModule, 49 | playgroundModule, 50 | repModule, 51 | twoslashModule, 52 | helpModule, 53 | snippetModule, 54 | handbookModule, 55 | modModule, 56 | ]) { 57 | await mod(bot); 58 | } 59 | }); 60 | 61 | client.on('error', error => { 62 | console.error(error); 63 | }); 64 | 65 | if (process.env.NODE_ENV === 'production') { 66 | process.on('unhandledRejection', e => { 67 | console.error('Unhandled rejection', e); 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /src/log.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseGuildTextChannel, 3 | Client, 4 | GuildMember, 5 | TextChannel, 6 | ThreadChannel, 7 | User, 8 | } from 'discord.js'; 9 | import { inspect } from 'util'; 10 | import { logChannelId } from './env'; 11 | 12 | const logDebounceTime = 5000; 13 | const logMaxLength = 2000; 14 | 15 | export async function hookLog(client: Client) { 16 | const guild = client.guilds.cache.get( 17 | (await client.guilds.fetch()).first()!.id, 18 | )!; 19 | const channel = (await guild.channels.fetch(logChannelId)) as TextChannel; 20 | let curLogText = ''; 21 | let timeout: NodeJS.Timeout | null = null; 22 | const origLog = console.log; 23 | console.log = (...args) => { 24 | origLog(...args); 25 | postLog(args); 26 | }; 27 | const origError = console.error; 28 | console.error = (...args) => { 29 | origError(...args); 30 | postLog(['[ERROR]', ...args]); 31 | }; 32 | console.log('Writing logs to', channel); 33 | function argToString(arg: unknown) { 34 | if (typeof arg === 'string') return arg; 35 | return inspect(arg); 36 | } 37 | function postLog(args: unknown[]) { 38 | curLogText += `[${new Date().toISOString()}] ${args 39 | .map(argToString) 40 | .join(' ')}\n`; 41 | if (timeout) clearTimeout(timeout); 42 | while (curLogText.length > logMaxLength) { 43 | const initial = 44 | curLogText.match(/^[^]{0,2000}\n/g)?.[0] ?? 45 | curLogText.slice(0, 2000); 46 | curLogText = curLogText.slice(initial.length); 47 | postCodeblock(initial); 48 | } 49 | if (curLogText.trim().length) 50 | timeout = setTimeout(() => { 51 | postCodeblock(curLogText); 52 | curLogText = ''; 53 | }, logDebounceTime); 54 | } 55 | async function postCodeblock(content: string) { 56 | channel.send(`\`\`\`accesslog\n${content}\n\`\`\``); 57 | } 58 | } 59 | 60 | function defineCustomUtilInspect( 61 | Cls: { prototype: T }, 62 | cb: (value: T) => string, 63 | ) { 64 | // @ts-ignore 65 | Cls.prototype[inspect.custom] = function () { 66 | return cb(this); 67 | }; 68 | } 69 | 70 | const inspectUser = (user: User) => 71 | `@${user.username}#${user.discriminator}/${user.id}`; 72 | defineCustomUtilInspect(User, inspectUser); 73 | defineCustomUtilInspect(GuildMember, member => inspectUser(member.user)); 74 | 75 | const channels: Array<{ prototype: { name: string; id: string } }> = [ 76 | BaseGuildTextChannel, 77 | ThreadChannel, 78 | ]; 79 | 80 | for (const ctor of channels) { 81 | defineCustomUtilInspect(ctor, channel => `#${channel.name}/${channel.id}`); 82 | } 83 | -------------------------------------------------------------------------------- /src/modules/autorole.ts: -------------------------------------------------------------------------------- 1 | import { Bot } from '../bot'; 2 | import { autorole, rolesChannelId } from '../env'; 3 | 4 | export async function autoroleModule({ client }: Bot) { 5 | const channel = await client.channels.fetch(rolesChannelId); 6 | if (!channel?.isTextBased()) { 7 | console.error( 8 | `Roles channel (${rolesChannelId}) does not exist or is not text based.`, 9 | ); 10 | return; 11 | } 12 | 13 | for (const ar of autorole) { 14 | const msg = await channel.messages.fetch(ar.msgID); 15 | if (!msg) { 16 | console.error(`Role message does not exist for ${ar.msgID}`); 17 | } 18 | await msg?.react(ar.emoji); 19 | } 20 | 21 | client.on('messageReactionAdd', async (reaction, user) => { 22 | if (user.id == client.user.id) return; 23 | if (reaction.partial) await reaction.fetch(); 24 | for (const ar of autorole) { 25 | const msg = reaction.message; 26 | if ( 27 | ar.emoji !== reaction.emoji.toString() || 28 | ar.msgID !== msg.id || 29 | !msg.guild 30 | ) 31 | continue; 32 | if (ar.autoRemove) await reaction.users.remove(user.id); 33 | const member = await msg.guild.members.fetch(user.id); 34 | await member.roles.add(ar.roleID); 35 | console.log('Gave role', ar.roleID, 'to', member); 36 | if (!reaction.users.cache.has(client.user.id)) { 37 | await msg.react(reaction.emoji); 38 | } 39 | } 40 | }); 41 | 42 | client.on('messageReactionRemove', async (reaction, user) => { 43 | if (user.id == client.user.id) return; 44 | if (reaction.partial) await reaction.fetch(); 45 | for (const ar of autorole) { 46 | const msg = reaction.message; 47 | if ( 48 | ar.emoji !== reaction.emoji.toString() || 49 | ar.msgID !== msg.id || 50 | ar.autoRemove || 51 | !msg.guild 52 | ) 53 | continue; 54 | const member = await msg.guild.members.fetch(user.id); 55 | await member.roles.remove(ar.roleID); 56 | console.log('Removed role', ar.roleID, 'from', member); 57 | } 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /src/modules/etc.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ThreadAutoArchiveDuration, 3 | Message, 4 | MessageReaction, 5 | User, 6 | ThreadChannel, 7 | } from 'discord.js'; 8 | import { Bot } from '../bot'; 9 | import { suggestionsChannelId } from '../env'; 10 | import { 11 | clearMessageOwnership, 12 | DELETE_EMOJI, 13 | ownsBotMessage, 14 | } from '../util/send'; 15 | 16 | const emojiRegex = /<:\w+?:(\d+?)>|(\p{Emoji_Presentation})/gu; 17 | 18 | const defaultPollEmojis = ['✅', '❌', '🤷']; 19 | 20 | export function etcModule(bot: Bot) { 21 | bot.registerCommand({ 22 | aliases: ['ping'], 23 | description: 'See if the bot is alive', 24 | async listener(msg) { 25 | await msg.channel.send('pong. :ping_pong:'); 26 | }, 27 | }); 28 | 29 | bot.client.on('messageCreate', async msg => { 30 | if (msg.author.bot || !msg.content.toLowerCase().startsWith('poll:')) 31 | return; 32 | let emojis = [ 33 | ...new Set( 34 | [...msg.content.matchAll(emojiRegex)].map(x => x[1] ?? x[2]), 35 | ), 36 | ]; 37 | if (!emojis.length) emojis = defaultPollEmojis; 38 | for (const emoji of emojis) await msg.react(emoji); 39 | }); 40 | 41 | bot.client.on('messageCreate', async msg => { 42 | if (msg.author.bot || msg.channelId !== suggestionsChannelId) return; 43 | // First 50 characters of the first line of the content (without cutting off a word) 44 | const title = 45 | msg.content 46 | .split('\n')[0] 47 | .split(/(^.{0,50}\b)/) 48 | .find(x => x) ?? 'Suggestion'; 49 | await msg.startThread({ 50 | name: title, 51 | autoArchiveDuration: ThreadAutoArchiveDuration.OneDay, 52 | }); 53 | for (let emoji of defaultPollEmojis) await msg.react(emoji); 54 | }); 55 | 56 | bot.client.on('threadUpdate', async thread => { 57 | if ( 58 | thread.parentId !== suggestionsChannelId || 59 | !((await thread.fetch()) as ThreadChannel).archived 60 | ) 61 | return; 62 | const channel = thread.parent!; 63 | let lastMessage = null; 64 | let suggestion: Message; 65 | while (!suggestion!) { 66 | const msgs = await channel.messages.fetch({ 67 | before: lastMessage ?? undefined, 68 | limit: 5, 69 | }); 70 | suggestion = msgs.find(msg => msg.thread?.id === thread.id)!; 71 | lastMessage = msgs.last()!.id as string; 72 | } 73 | const pollingResults = defaultPollEmojis.map(emoji => { 74 | const reactions = suggestion.reactions.resolve(emoji); 75 | // Subtract the bot's vote 76 | const count = (reactions?.count ?? 0) - 1; 77 | return [emoji, count] as const; 78 | }); 79 | const pollingResultStr = pollingResults 80 | .sort((a, b) => b[1] - a[1]) 81 | .map(([emoji, count]) => `${count} ${emoji}`) 82 | .join(' '); 83 | await suggestion.reply({ 84 | content: `Polling finished; result: ${pollingResultStr}`, 85 | }); 86 | }); 87 | 88 | bot.client.on('messageReactionAdd', async (reaction, member) => { 89 | if (reaction.partial) return; 90 | 91 | if ((await reaction.message.fetch()).author.id !== bot.client.user.id) 92 | return; 93 | if (reaction.emoji.name !== DELETE_EMOJI) return; 94 | if (member.id === bot.client.user.id) return; 95 | 96 | if (ownsBotMessage(reaction.message, member.id)) { 97 | clearMessageOwnership(reaction.message); 98 | await reaction.message.delete(); 99 | } else { 100 | await reaction.users.remove(member.id); 101 | } 102 | }); 103 | 104 | bot.registerAdminCommand({ 105 | aliases: ['kill'], 106 | async listener(msg) { 107 | const confirm = '✅'; 108 | const confirmationMessage = await msg.channel.send('Confirm?'); 109 | confirmationMessage.react(confirm); 110 | const reactionFilter = (reaction: MessageReaction, user: User) => 111 | reaction.emoji.name === confirm && user.id === msg.author.id; 112 | const proceed = await confirmationMessage 113 | .awaitReactions({ 114 | filter: reactionFilter, 115 | max: 1, 116 | time: 10 * 1000, 117 | errors: ['time'], 118 | }) 119 | .then(() => true) 120 | .catch(() => false); 121 | await confirmationMessage.delete(); 122 | if (!proceed) return; 123 | await msg.react('☠️'); 124 | process.stdout.write(` 125 | ,--. 126 | { } 127 | K, } 128 | / ~Y\` 129 | , / / 130 | {_'-K.__/ 131 | \`/-.__L._ 132 | / ' /\`\\_} 133 | / ' / 134 | ____ / ' / 135 | ,-'~~~~ ~~/ ' /_ 136 | ,' \`\`~~~ ', 137 | ( Y 138 | { I 139 | { - \`, 140 | | ', ) 141 | | | ,..__ __. Y 142 | | .,_./ Y ' / ^Y J )| 143 | \ |' / | | || Killed by @${msg.author.tag}/${msg.author.id} 144 | \ L_/ . _ (_,.'( 145 | \, , ^^""' / | ) 146 | \_ \ /,L] / 147 | '-_~-, \` \` ./\` 148 | \`'{_ ) 149 | ^^\..___,.--\` 150 | `); 151 | process.exit(1); 152 | }, 153 | }); 154 | } 155 | -------------------------------------------------------------------------------- /src/modules/handbook.ts: -------------------------------------------------------------------------------- 1 | import { EmbedBuilder } from 'discord.js'; 2 | import algoliasearch from 'algoliasearch/lite'; 3 | import { sendWithMessageOwnership } from '../util/send'; 4 | import { TS_BLUE } from '../env'; 5 | import { decode } from 'html-entities'; 6 | import { Bot } from '../bot'; 7 | 8 | const ALGOLIA_APP_ID = 'BGCDYOIYZ5'; 9 | const ALGOLIA_API_KEY = '37ee06fa68db6aef451a490df6df7c60'; 10 | const ALGOLIA_INDEX_NAME = 'typescriptlang'; 11 | 12 | const algolia = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_API_KEY, {}); 13 | 14 | type AlgoliaResult = { 15 | hierarchy: Record; 16 | url: string; 17 | }; 18 | 19 | const HANDBOOK_EMBED = new EmbedBuilder() 20 | .setColor(TS_BLUE) 21 | .setTitle('The TypeScript Handbook') 22 | .setURL('https://www.typescriptlang.org/docs/handbook/intro.html') 23 | .setFooter({ text: 'You can search with `!handbook `' }); 24 | 25 | export async function handbookModule(bot: Bot) { 26 | bot.registerCommand({ 27 | aliases: ['handbook', 'hb'], 28 | description: 'Search the TypeScript Handbook', 29 | async listener(msg, content) { 30 | if (!content) { 31 | return await sendWithMessageOwnership(msg, { 32 | embeds: [HANDBOOK_EMBED], 33 | }); 34 | } 35 | 36 | console.log('Searching algolia for', [content]); 37 | const data = await algolia.search([ 38 | { 39 | indexName: ALGOLIA_INDEX_NAME, 40 | query: content, 41 | params: { 42 | offset: 0, 43 | length: 1, 44 | }, 45 | }, 46 | ]); 47 | console.log('Algolia response:', data); 48 | const hit = data.results[0].hits[0]; 49 | if (!hit) 50 | return await sendWithMessageOwnership( 51 | msg, 52 | ':x: No results found for that query', 53 | ); 54 | const hierarchyParts = [0, 1, 2, 3, 4, 5, 6] 55 | .map(i => hit.hierarchy[`lvl${i}`]) 56 | .filter(x => x); 57 | const embed = new EmbedBuilder() 58 | .setColor(TS_BLUE) 59 | .setTitle(decode(hierarchyParts[hierarchyParts.length - 1])) 60 | .setAuthor({ 61 | name: decode(hierarchyParts.slice(0, -1).join(' / ')), 62 | }) 63 | .setURL(hit.url); 64 | await sendWithMessageOwnership(msg, { embeds: [embed] }); 65 | }, 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /src/modules/help.ts: -------------------------------------------------------------------------------- 1 | import { EmbedBuilder } from 'discord.js'; 2 | import { Bot, CommandRegistration } from '../bot'; 3 | import { Snippet } from '../entities/Snippet'; 4 | import { sendWithMessageOwnership } from '../util/send'; 5 | 6 | function getCategoryHelp(cat: string, commands: Iterable) { 7 | const out: string[] = []; 8 | 9 | for (const cmd of new Set(commands)) { 10 | if (!cmd.description) continue; 11 | const [cat2, description] = splitCategoryDescription(cmd.description); 12 | if (cat !== cat2) continue; 13 | out.push(`\`${cmd.aliases[0]}\` ► ${description}`); 14 | } 15 | 16 | return out.join('\n'); 17 | } 18 | 19 | function splitCategoryDescription(description: string): [string, string] { 20 | const split = description.split(': ', 2); 21 | if (split.length !== 2) { 22 | return ['Misc', description]; 23 | } 24 | return split as [string, string]; 25 | } 26 | 27 | function getCommandCategories(commands: Iterable) { 28 | const categories = new Set(); 29 | 30 | for (const cmd of commands) { 31 | categories.add(splitCategoryDescription(cmd.description ?? '')[0]); 32 | } 33 | 34 | return [...categories].sort((a, b) => a.localeCompare(b)); 35 | } 36 | 37 | export function helpModule(bot: Bot) { 38 | bot.registerCommand({ 39 | aliases: ['help', 'commands', 'h'], 40 | description: "Sends what you're looking at right now", 41 | async listener(msg) { 42 | const cmdTrigger = msg.content.split(/\s/)[1]; 43 | 44 | if (!msg.guild) return; 45 | 46 | if (!cmdTrigger) { 47 | const embed = new EmbedBuilder() 48 | .setAuthor({ 49 | name: msg.guild.name, 50 | iconURL: msg.guild.iconURL() || undefined, 51 | }) 52 | .setTitle('Bot Usage') 53 | .setDescription( 54 | `Hello ${msg.author.username}! Here is a list of all commands in me! To get detailed description on any specific command, do \`help \``, 55 | ); 56 | 57 | for (const cat of getCommandCategories(bot.commands.values())) { 58 | embed.addFields({ 59 | name: `**${cat} Commands:**`, 60 | value: getCategoryHelp(cat, bot.commands.values()), 61 | }); 62 | } 63 | 64 | embed 65 | .setFooter({ 66 | text: bot.client.user.username, 67 | iconURL: bot.client.user.displayAvatarURL(), 68 | }) 69 | .setTimestamp(); 70 | 71 | return await sendWithMessageOwnership(msg, { embeds: [embed] }); 72 | } 73 | 74 | let cmd: { description?: string; aliases?: string[] } = 75 | bot.getByTrigger(cmdTrigger) || {}; 76 | 77 | if (!cmd.description && cmdTrigger.includes(':')) { 78 | const snippet = await Snippet.findOne({ 79 | where: { title: cmdTrigger }, 80 | }); 81 | if (snippet) 82 | cmd = { 83 | description: `A custom snippet created by <@${snippet.owner}>`, 84 | }; 85 | else 86 | cmd = { 87 | description: 88 | 'Run the first snippet that matches that pattern', 89 | }; 90 | } 91 | 92 | if (!cmd.description) 93 | return await sendWithMessageOwnership( 94 | msg, 95 | `:x: Command not found`, 96 | ); 97 | 98 | const embed = new EmbedBuilder().setTitle( 99 | `\`${cmdTrigger}\` Usage`, 100 | ); 101 | // Get rid of duplicates, this can happen if someone adds the method name as an alias 102 | const triggers = new Set(cmd.aliases ?? [cmdTrigger]); 103 | if (triggers.size > 1) { 104 | embed.addFields({ 105 | name: 'Aliases', 106 | value: Array.from(triggers, t => `\`${t}\``).join(', '), 107 | }); 108 | } 109 | embed.addFields({ 110 | name: 'Description', 111 | value: `*${ 112 | splitCategoryDescription(cmd.description ?? '')[1] 113 | }*`, 114 | }); 115 | 116 | await sendWithMessageOwnership(msg, { embeds: [embed] }); 117 | }, 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /src/modules/helpForum.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChannelType, 3 | ThreadChannel, 4 | TextChannel, 5 | Channel, 6 | ForumChannel, 7 | Message, 8 | } from 'discord.js'; 9 | import { Bot } from '../bot'; 10 | import { HelpThread } from '../entities/HelpThread'; 11 | import { 12 | helpForumChannel, 13 | helpForumOpenTagName, 14 | helpForumResolvedTagName, 15 | helpRequestsChannel, 16 | howToGetHelpChannel, 17 | howToGiveHelpChannel, 18 | rolesChannelId, 19 | timeBeforeHelperPing, 20 | trustedRoleId, 21 | } from '../env'; 22 | import { sendWithMessageOwnership } from '../util/send'; 23 | 24 | const MAX_TAG_COUNT = 5; 25 | 26 | // Use a non-breaking space to force Discord to leave empty lines alone 27 | const postGuidelines = (here = true) => 28 | listify(` 29 | **How To Get Help** 30 | - Create a new post ${ 31 | here ? 'here' : `in <#${helpForumChannel}>` 32 | } with your question. 33 | - It's always ok to just ask your question; you don't need permission. 34 | - Someone will (hopefully!) come along and help you. 35 | - When your question is resolved, type \`!resolved\`. 36 | \u200b 37 | **How To Get Better Help** 38 | - Explain what you want to happen and why… 39 | - …and what actually happens, and your best guess at why. 40 | - Include a short code sample and any error messages you got. 41 | - Text is better than screenshots. Start code blocks with \`\`\`ts. 42 | - If possible, create a minimal reproduction in the TypeScript Playground: . 43 | - Send the full link in its own message; do not use a link shortener. 44 | - For more tips, check out StackOverflow's guide on asking good questions: 45 | \u200b 46 | **If You Haven't Gotten Help** 47 | Usually someone will try to answer and help solve the issue within a few hours. If not, and if you have followed the bullets above, you can ping helpers by running !helper. 48 | `); 49 | 50 | const howToGiveHelp = listify(` 51 | **How To Give Help** 52 | - The channel sidebar on the left will list posts you have joined. 53 | - You can scroll through the channel to see all recent questions. 54 | 55 | **How To Give *Better* Help** 56 | - Get yourself the <@&${trustedRoleId}> role at <#${rolesChannelId}> 57 | - (If you don't like the pings, you can disable role mentions for the server.) 58 | - As a <@&${trustedRoleId}>, you can: 59 | - React to a help post to add tags. 60 | - If a post appears to be resolved, run \`!resolved\` to mark it as such. 61 | - *Only do this if the asker has indicated that their question has been resolved.* 62 | - Conversely, you can run \`!reopen\` if the asker has follow-up questions. 63 | 64 | **Useful Snippets** 65 | - \`!screenshot\` — for if an asker posts a screenshot of code 66 | - \`!ask\` — for if an asker only posts "can I get help?" 67 | `); 68 | 69 | const helperResolve = (owner: string, helper: string) => ` 70 | <@${owner}> 71 | Because your issue seemed to be resolved, this post was marked as resolved by <@${helper}>. 72 | If your issue is not resolved, **you can reopen this post by running \`!reopen\`**. 73 | *If you have a different question, make a new post in <#${helpForumChannel}>.* 74 | `; 75 | 76 | export async function helpForumModule(bot: Bot) { 77 | const channel = await bot.client.guilds.cache 78 | .first() 79 | ?.channels.fetch(helpForumChannel)!; 80 | if (channel?.type !== ChannelType.GuildForum) { 81 | console.error(`Expected ${helpForumChannel} to be a forum channel.`); 82 | return; 83 | } 84 | const forumChannel = channel; 85 | const openTag = getTag(forumChannel, helpForumOpenTagName); 86 | const resolvedTag = getTag(forumChannel, helpForumResolvedTagName); 87 | 88 | const helpRequestChannel = await bot.client.guilds.cache 89 | .first() 90 | ?.channels.fetch(helpRequestsChannel)!; 91 | if (!helpRequestChannel?.isTextBased()) { 92 | console.error(`Expected ${helpRequestChannel} to be a text channel.`); 93 | return; 94 | } 95 | 96 | await forumChannel.setTopic(postGuidelines()); 97 | 98 | bot.client.on('threadCreate', async thread => { 99 | const owner = await thread.fetchOwner(); 100 | if (!owner?.user || !isHelpThread(thread)) return; 101 | console.log( 102 | 'Received new question from', 103 | owner.user.tag, 104 | 'in thread', 105 | thread.id, 106 | ); 107 | 108 | await HelpThread.create({ 109 | threadId: thread.id, 110 | ownerId: owner.user.id, 111 | }).save(); 112 | 113 | await setStatus(thread, openTag); 114 | }); 115 | 116 | bot.client.on('threadDelete', async thread => { 117 | if (!isHelpThread(thread)) return; 118 | await HelpThread.delete({ 119 | threadId: thread.id, 120 | }); 121 | }); 122 | 123 | bot.registerCommand({ 124 | aliases: ['helper', 'helpers'], 125 | description: 'Help System: Ping the @Helper role from a help post', 126 | async listener(msg, comment) { 127 | if (!isHelpThread(msg.channel)) { 128 | return sendWithMessageOwnership( 129 | msg, 130 | ':warning: You may only ping helpers from a help post', 131 | ); 132 | } 133 | 134 | const thread = msg.channel; 135 | const threadData = await getHelpThread(thread.id); 136 | 137 | // Ensure the user has permission to ping helpers 138 | const isAsker = msg.author.id === threadData.ownerId; 139 | const isTrusted = bot.isTrusted(msg); 140 | 141 | if (!isAsker && !isTrusted) { 142 | return sendWithMessageOwnership( 143 | msg, 144 | ':warning: Only the asker can ping helpers', 145 | ); 146 | } 147 | 148 | const askTime = thread.createdTimestamp; 149 | const pingAllowedAfter = 150 | +(threadData.helperTimestamp ?? askTime ?? Date.now()) + 151 | timeBeforeHelperPing; 152 | 153 | // Ensure they've waited long enough 154 | // Trusted members (who aren't the asker) are allowed to disregard the timeout 155 | if (isAsker && Date.now() < pingAllowedAfter) { 156 | return sendWithMessageOwnership( 157 | msg, 158 | `:warning: Please wait a bit longer. You can ping helpers .`, 161 | ); 162 | } 163 | 164 | const tagStrings = thread.appliedTags.flatMap(t => { 165 | const tag = forumChannel.availableTags.find(at => at.id === t); 166 | if (!tag) return []; 167 | if (!tag.emoji) return tag.name; 168 | 169 | const emoji = tag.emoji.id 170 | ? `<:${tag.emoji.name}:${tag.emoji.id}>` 171 | : tag.emoji.name; 172 | return `${emoji} ${tag.name}`; 173 | }); 174 | const tags = tagStrings ? `(${tagStrings.join(', ')})` : ''; 175 | 176 | // The beacons are lit, Gondor calls for aid 177 | await Promise.all([ 178 | helpRequestChannel.send( 179 | `<@&${trustedRoleId}> ${msg.channel} ${tags} ${ 180 | isTrusted ? comment : '' 181 | }`, 182 | ), 183 | msg.react('✅'), 184 | HelpThread.update(thread.id, { 185 | helperTimestamp: Date.now().toString(), 186 | }), 187 | ]); 188 | }, 189 | }); 190 | 191 | bot.registerCommand({ 192 | aliases: ['resolved', 'resolve', 'close', 'closed', 'done', 'solved'], 193 | description: 'Help System: Mark a post as resolved', 194 | async listener(msg) { 195 | changeStatus(msg, true); 196 | }, 197 | }); 198 | 199 | bot.registerCommand({ 200 | aliases: ['reopen', 'open', 'unresolved', 'unresolve'], 201 | description: 'Help System: Reopen a resolved post', 202 | async listener(msg) { 203 | changeStatus(msg, false); 204 | }, 205 | }); 206 | 207 | bot.client.on('messageReactionAdd', async reaction => { 208 | const message = reaction.message; 209 | const thread = await message.channel.fetch(); 210 | if (!isHelpThread(thread)) { 211 | return; 212 | } 213 | const initial = await thread.fetchStarterMessage(); 214 | if (initial?.id !== message.id) return; 215 | const tag = forumChannel.availableTags.find( 216 | t => 217 | t.emoji && 218 | !t.moderated && 219 | t.emoji.id === reaction.emoji.id && 220 | t.emoji.name === reaction.emoji.name, 221 | ); 222 | if (!tag) return; 223 | if (thread.appliedTags.length < MAX_TAG_COUNT) { 224 | await thread.setAppliedTags([...thread.appliedTags, tag.id]); 225 | } 226 | await reaction.remove(); 227 | }); 228 | 229 | async function changeStatus(msg: Message, resolved: boolean) { 230 | const thread = msg.channel; 231 | if (!isHelpThread(thread)) { 232 | return sendWithMessageOwnership( 233 | msg, 234 | ':warning: Can only be run in a help post', 235 | ); 236 | } 237 | 238 | const threadData = await getHelpThread(thread.id); 239 | const isAsker = msg.author.id === threadData.ownerId; 240 | const isTrusted = bot.isTrusted(msg); 241 | 242 | if (!isAsker && !isTrusted) { 243 | return sendWithMessageOwnership( 244 | msg, 245 | ':warning: Only the asker can change the status of a help post', 246 | ); 247 | } 248 | 249 | await setStatus(thread, resolved ? resolvedTag : openTag); 250 | await msg.react('✅'); 251 | 252 | if (resolved && !isAsker) { 253 | await thread.send(helperResolve(thread.ownerId!, msg.author.id)); 254 | } 255 | } 256 | 257 | bot.registerAdminCommand({ 258 | aliases: ['htgh'], 259 | async listener(msg) { 260 | if ( 261 | msg.channel.id !== howToGetHelpChannel && 262 | msg.channel.id !== howToGiveHelpChannel 263 | ) { 264 | return; 265 | } 266 | (await msg.channel.messages.fetch()).forEach(x => x.delete()); 267 | const message = 268 | msg.channel.id === howToGetHelpChannel 269 | ? postGuidelines(false) 270 | : howToGiveHelp; 271 | // Force a blank line at the beginning of the message for compact-mode users 272 | msg.channel.send(`** **\n` + message.trim()); 273 | }, 274 | }); 275 | 276 | async function getHelpThread(threadId: string) { 277 | const threadData = await HelpThread.findOneBy({ threadId }); 278 | 279 | if (!threadData) { 280 | // Thread was created when the bot was down. 281 | const thread = await forumChannel.threads.fetch(threadId); 282 | if (!thread) { 283 | throw new Error('Not a forum thread ID'); 284 | } 285 | return await HelpThread.create({ 286 | threadId, 287 | ownerId: thread.ownerId!, 288 | }).save(); 289 | } 290 | 291 | return threadData; 292 | } 293 | 294 | function isHelpThread( 295 | channel: ThreadChannel | Channel, 296 | ): channel is ThreadChannel & { parent: TextChannel } { 297 | return ( 298 | channel instanceof ThreadChannel && 299 | channel.parent?.id === forumChannel.id 300 | ); 301 | } 302 | 303 | function getTag(channel: ForumChannel, name: string) { 304 | const tag = channel.availableTags.find(x => x.name === name); 305 | if (!tag) throw new Error(`Could not find tag ${name}`); 306 | return tag.id; 307 | } 308 | 309 | async function setStatus(thread: ThreadChannel, tag: string) { 310 | let tags = thread.appliedTags.filter( 311 | x => x !== openTag && x !== resolvedTag, 312 | ); 313 | if (tags.length === MAX_TAG_COUNT) { 314 | tags = tags.slice(0, -1); 315 | } 316 | await thread.setAppliedTags([tag, ...tags]); 317 | } 318 | } 319 | 320 | function listify(text: string) { 321 | // A zero-width space (necessary to prevent discord from trimming the leading whitespace), followed by a three non-breaking spaces. 322 | const indent = '\u200b\u00a0\u00a0\u00a0'; 323 | const bullet = '•'; 324 | return text.replace(/^(\s*)-/gm, `$1${bullet}`).replace(/\t/g, indent); 325 | } 326 | -------------------------------------------------------------------------------- /src/modules/mod.ts: -------------------------------------------------------------------------------- 1 | import { Message, Snowflake, User } from 'discord.js'; 2 | import { Bot } from '../bot'; 3 | import { rulesChannelId } from '../env'; 4 | 5 | // Most job posts are in this format: 6 | // > [FOR HIRE][REMOTE][SOMETHING ELSE] 7 | // > Hi, I'm ponyman6000. Hire me! 8 | const jobPostRegex = /^(?:\[[A-Z /-]+\]\s*){2,}\n/i; 9 | 10 | const SPAM_CHANNEL_THRESHOLD = 3; 11 | const SPAM_MAX_TIME = 5000; 12 | 13 | interface RecentMessageInfo { 14 | author: User; 15 | firstPost: Date; 16 | channels: Set; 17 | messages: Message[]; 18 | } 19 | const recentMessages = new Map(); 20 | const CLEANUP_RECENT_MESSAGES_MAP_INTERVAL = 10000; 21 | 22 | export function modModule({ client }: Bot) { 23 | client.on('messageCreate', async msg => { 24 | if (msg.author.bot || !jobPostRegex.test(msg.content)) return; 25 | await msg.delete(); 26 | await msg.channel.send( 27 | `${msg.author} We don't do job posts here; see <#${rulesChannelId}>`, 28 | ); 29 | console.log('Deleted job post message from', msg.author); 30 | }); 31 | 32 | client.on('messageCreate', async msg => { 33 | if (!msg.guild || msg.author.id === client.user.id) return; 34 | const messageIdentifier = msg.content.trim().toLowerCase(); 35 | if (!messageIdentifier) return; 36 | let recentMessageInfo = recentMessages.get(messageIdentifier); 37 | if ( 38 | recentMessageInfo && 39 | recentMessageInfo.author.id === msg.author.id && 40 | +recentMessageInfo.firstPost + SPAM_MAX_TIME > Date.now() 41 | ) { 42 | recentMessageInfo.channels.add(msg.channel.id); 43 | recentMessageInfo.messages.push(msg); 44 | if (recentMessageInfo.channels.size >= SPAM_CHANNEL_THRESHOLD) { 45 | await Promise.all([ 46 | msg.guild.members 47 | .fetch(msg.author) 48 | .then(member => void member.kick('Spam')), 49 | ...recentMessageInfo.messages.map(msg => void msg.delete()), 50 | ]); 51 | console.log( 52 | 'Kicked', 53 | msg.author, 54 | 'for spam and deleted', 55 | recentMessageInfo.messages.length, 56 | 'identical messages', 57 | ); 58 | } 59 | } else { 60 | recentMessages.set(messageIdentifier, { 61 | author: msg.author, 62 | firstPost: new Date(), 63 | channels: new Set([msg.channel.id]), 64 | messages: [msg], 65 | }); 66 | } 67 | }); 68 | } 69 | 70 | setInterval(() => { 71 | for (const [messageText, recentMessageInfo] of recentMessages) { 72 | if (+recentMessageInfo.firstPost + SPAM_MAX_TIME < Date.now()) { 73 | recentMessages.delete(messageText); 74 | } 75 | } 76 | }, CLEANUP_RECENT_MESSAGES_MAP_INTERVAL); 77 | -------------------------------------------------------------------------------- /src/modules/playground.ts: -------------------------------------------------------------------------------- 1 | import { EmbedBuilder, Message, User } from 'discord.js'; 2 | import { 3 | compressToEncodedURIComponent, 4 | decompressFromEncodedURIComponent, 5 | } from 'lz-string'; 6 | import { format } from 'prettier'; 7 | import { URLSearchParams } from 'url'; 8 | import { TS_BLUE } from '../env'; 9 | import { 10 | makeCodeBlock, 11 | findCode, 12 | matchPlaygroundLink, 13 | PlaygroundLinkMatch, 14 | } from '../util/codeBlocks'; 15 | import { LimitedSizeMap } from '../util/limitedSizeMap'; 16 | import { addMessageOwnership, sendWithMessageOwnership } from '../util/send'; 17 | import { fetch } from 'undici'; 18 | import { Bot } from '../bot'; 19 | 20 | const PLAYGROUND_BASE = 'https://www.typescriptlang.org/play/#code/'; 21 | const LINK_SHORTENER_ENDPOINT = 'https://tsplay.dev/api/short'; 22 | const MAX_EMBED_LENGTH = 512; 23 | const DEFAULT_EMBED_LENGTH = 256; 24 | 25 | export async function playgroundModule(bot: Bot) { 26 | const editedLongLink = new LimitedSizeMap(1000); 27 | 28 | bot.registerCommand({ 29 | aliases: ['playground', 'pg', 'playg'], 30 | description: 'Shorten a TypeScript playground link', 31 | async listener(msg, content) { 32 | console.log('Playground', msg.content); 33 | 34 | let code: string | undefined = content; 35 | 36 | if (!code) { 37 | code = await findCode(msg, true); 38 | if (!code) 39 | return sendWithMessageOwnership( 40 | msg, 41 | ":warning: couldn't find a codeblock!", 42 | ); 43 | } 44 | const embed = new EmbedBuilder() 45 | .setURL(PLAYGROUND_BASE + compressToEncodedURIComponent(code)) 46 | .setTitle('View in Playground') 47 | .setColor(TS_BLUE); 48 | await sendWithMessageOwnership(msg, { embeds: [embed] }); 49 | }, 50 | }); 51 | 52 | bot.client.on('messageCreate', async msg => { 53 | if (msg.author.bot) return; 54 | if (msg.content[0] === '!') return; 55 | const exec = matchPlaygroundLink(msg.content); 56 | if (!exec) return; 57 | const embed = createPlaygroundEmbed(msg.author, exec); 58 | if (exec.isWholeMatch) { 59 | // Message only contained the link 60 | await sendWithMessageOwnership(msg, { 61 | embeds: [embed], 62 | }); 63 | await msg.delete(); 64 | } else { 65 | // Message also contained other characters 66 | const botMsg = await msg.channel.send({ 67 | embeds: [embed], 68 | content: `${msg.author} Here's a shortened URL of your playground link! You can remove the full link from your message.`, 69 | }); 70 | editedLongLink.set(msg.id, botMsg); 71 | await addMessageOwnership(botMsg, msg.author); 72 | } 73 | }); 74 | 75 | bot.client.on('messageCreate', async msg => { 76 | const attachment = msg.attachments.find(a => a.name === 'message.txt'); 77 | if (msg.author.bot || !attachment) return; 78 | const content = await fetch(attachment.url).then(r => r.text()); 79 | const exec = matchPlaygroundLink(content); 80 | // By default, if you write a message in the box and then paste a long 81 | // playground link, it will only put the paste in message.txt and will 82 | // put the rest of the message in msg.content 83 | if (!exec?.isWholeMatch) return; 84 | const shortenedUrl = await shortenPlaygroundLink(exec.url); 85 | const embed = createPlaygroundEmbed(msg.author, exec, shortenedUrl); 86 | await sendWithMessageOwnership(msg, { 87 | embeds: [embed], 88 | }); 89 | if (!msg.content) await msg.delete(); 90 | }); 91 | 92 | bot.client.on('messageUpdate', async (_oldMsg, msg) => { 93 | if (msg.partial) msg = await msg.fetch(); 94 | const exec = matchPlaygroundLink(msg.content); 95 | if (msg.author.bot || !editedLongLink.has(msg.id) || exec) return; 96 | const botMsg = editedLongLink.get(msg.id); 97 | // Edit the message to only have the embed and not the "please edit your message" message 98 | await botMsg?.edit({ 99 | content: '', 100 | embeds: [botMsg.embeds[0]], 101 | }); 102 | editedLongLink.delete(msg.id); 103 | }); 104 | } 105 | 106 | // Take care when messing with the truncation, it's extremely finnicky 107 | function createPlaygroundEmbed( 108 | author: User, 109 | { url: _url, query, code, isEscaped }: PlaygroundLinkMatch, 110 | url: string = _url, 111 | ) { 112 | const embed = new EmbedBuilder() 113 | .setColor(TS_BLUE) 114 | .setTitle('Playground Link') 115 | .setAuthor({ name: author.tag, iconURL: author.displayAvatarURL() }) 116 | .setURL(url); 117 | 118 | const unzipped = decompressFromEncodedURIComponent(code); 119 | if (!unzipped) return embed; 120 | 121 | // Without 'normalized' you can't get consistent lengths across platforms 122 | // Matters because the playground uses the line breaks of whoever created it 123 | const lines = unzipped.split(/\r\n|\r|\n/); 124 | const normalized = lines.join('\n'); 125 | 126 | const lengths = lines.map(l => l.length); 127 | const cum = lengths.slice(1).reduce((acc, len, i) => { 128 | acc.push(len + acc[i] + '\n'.length); 129 | return acc; 130 | }, lengths.slice(0, 1)); 131 | const lineIndices = [0].concat(cum); 132 | 133 | // Note: lines are 1-indexed 134 | let { startLine, endLine } = getSelectionQueryParams(query, lines.length); 135 | 136 | const startChar = startLine ? lineIndices[startLine - 1] : 0; 137 | const cutoff = endLine 138 | ? Math.min(lineIndices[endLine], startChar + MAX_EMBED_LENGTH) 139 | : startChar + DEFAULT_EMBED_LENGTH; 140 | // End of the line containing the cutoff 141 | const endChar = lineIndices.find(len => len >= cutoff) ?? normalized.length; 142 | 143 | let pretty; 144 | try { 145 | // Make lines as short as reasonably possible, so they fit in the embed. 146 | // We pass prettier the full string, but only format part of it, so we can 147 | // calculate where the endChar is post-formatting. 148 | pretty = format(normalized, { 149 | parser: 'typescript', 150 | printWidth: 55, 151 | tabWidth: 2, 152 | semi: false, 153 | bracketSpacing: false, 154 | arrowParens: 'avoid', 155 | rangeStart: startChar, 156 | rangeEnd: endChar, 157 | }); 158 | } catch (e) { 159 | // Likely a syntax error 160 | pretty = normalized; 161 | } 162 | const prettyEndChar = pretty.length - (normalized.length - endChar); 163 | const formattedSection = pretty.slice(startChar, prettyEndChar); 164 | const content = 165 | (startChar === 0 ? '' : '...\n') + 166 | formattedSection.replace(/^\s*\n|\n\s*$/g, '') + 167 | (prettyEndChar === pretty.length ? '' : '\n...'); 168 | 169 | if (!isEscaped) { 170 | embed.setDescription('**Preview:**' + makeCodeBlock(content)); 171 | if (!startLine && !endLine) { 172 | embed.setFooter({ 173 | text: 'You can choose specific lines to embed by selecting them before copying the link.', 174 | }); 175 | } 176 | } 177 | 178 | return embed; 179 | } 180 | 181 | async function shortenPlaygroundLink(url: string) { 182 | const response = await fetch(LINK_SHORTENER_ENDPOINT, { 183 | method: 'post', 184 | body: JSON.stringify({ url, createdOn: 'api', expires: false }), 185 | headers: { 186 | 'Content-Type': 'application/json', 187 | }, 188 | }); 189 | const { shortened } = (await response.json()) as any; 190 | if (typeof shortened !== 'string') 191 | throw new Error('Received invalid api response from link shortener'); 192 | return shortened; 193 | } 194 | 195 | // Sometimes the cursor is at the start of the selection, and other times 196 | // it's at the end of the selection; we don't care which, only that the 197 | // lower one always comes first. Also, parameter validation happens here 198 | function getSelectionQueryParams(query: string, numLines: number) { 199 | const params = new URLSearchParams(query); 200 | 201 | const [startLine, endLine] = ['pln', 'ssl'] 202 | // @ts-expect-error parseInt(null) is okay here since we check for NaN 203 | .map(name => parseInt(params.get(name))) 204 | .map(n => (!Number.isNaN(n) && 1 <= n && n <= numLines ? n : undefined)) 205 | .sort((a, b) => (a ?? Infinity) - (b ?? Infinity)); 206 | 207 | return { startLine, endLine }; 208 | } 209 | -------------------------------------------------------------------------------- /src/modules/rep.ts: -------------------------------------------------------------------------------- 1 | import { Message, EmbedBuilder } from 'discord.js'; 2 | import { repEmoji, TS_BLUE } from '../env'; 3 | 4 | import { Rep } from '../entities/Rep'; 5 | import { sendPaginatedMessage } from '../util/sendPaginatedMessage'; 6 | import { getMessageOwner, sendWithMessageOwnership } from '../util/send'; 7 | import { Bot } from '../bot'; 8 | 9 | // The Chinese is outside the group on purpose, because CJK languages don't have word bounds. Therefore we only look for key characters 10 | 11 | const thanksRegex = 12 | /* cspell:disable-next-line */ 13 | /\b(?:thank|thanks|thx|cheers|thanx|thnks|ty|tysm|tks|tkx|danke|merci|gracias|grazie|xiexie)\b|谢/i; 14 | 15 | const removedReactions = new Set(); 16 | 17 | export function repModule(bot: Bot) { 18 | const { client } = bot; 19 | 20 | function giveRep( 21 | msg: Pick, 22 | { recipient, initialGiver }: Pick, 23 | ) { 24 | console.log('Creating a Rep with recipient', recipient); 25 | 26 | return Rep.create({ 27 | messageId: msg.id, 28 | channel: msg.channelId, 29 | amount: 1, 30 | recipient, 31 | initialGiver, 32 | date: new Date().toISOString(), 33 | }).save(); 34 | } 35 | 36 | async function onThank(msg: Message, force = false) { 37 | // Check for thanks messages 38 | const isThanks = thanksRegex.test(msg.content); 39 | if (msg.author.bot || (!isThanks && !force) || !msg.guild) return; 40 | 41 | const mentionUsers = msg.mentions.users.filter( 42 | user => user.id !== msg.member?.id && user.id !== client.user.id, 43 | ); 44 | if (mentionUsers.size !== 1) return; 45 | 46 | const recipient = mentionUsers.first()!; 47 | 48 | await giveRep(msg, { 49 | recipient: recipient.id, 50 | initialGiver: msg.author.id, 51 | }); 52 | 53 | await msg.react(repEmoji); 54 | } 55 | 56 | client.on('messageCreate', msg => onThank(msg)); 57 | 58 | client.on('messageReactionAdd', async (reaction, user) => { 59 | if ( 60 | !reaction.message.guild || 61 | user.id === client.user.id || 62 | (reaction.emoji.id ?? reaction.emoji.name) !== repEmoji 63 | ) { 64 | return; 65 | } 66 | 67 | const msg = reaction.message; 68 | const author = (await msg.fetch()).author; 69 | 70 | console.log('Received rep reaction on', msg.id); 71 | 72 | if (user.id === author.id) { 73 | return removeReaction(); 74 | } 75 | 76 | console.log('Querying database for existing Rep'); 77 | 78 | let existingRep = await Rep.findOne({ where: { messageId: msg.id } }); 79 | 80 | if (existingRep) { 81 | console.log('Found existing Rep', existingRep); 82 | if (user.id === existingRep.recipient) { 83 | console.log('User is recipient; removing reaction'); 84 | return removeReaction(); 85 | } 86 | console.log('Existing amount is', existingRep.amount); 87 | existingRep.amount++; 88 | existingRep.save(); 89 | console.log('Incremented amount to', existingRep.amount); 90 | return; 91 | } 92 | 93 | let recipient = author.id; 94 | 95 | if (recipient == client.user.id) { 96 | console.log('Recipient is bot; checking for message ownership'); 97 | let altRecipient = getMessageOwner(msg); 98 | if (!altRecipient) { 99 | console.log('No message owner recorded; removing reaction'); 100 | return removeReaction(); 101 | } 102 | console.log('Message owner is', altRecipient); 103 | recipient = altRecipient; 104 | } 105 | 106 | await giveRep(msg, { 107 | recipient, 108 | initialGiver: user.id, 109 | }); 110 | 111 | async function removeReaction() { 112 | removedReactions.add([msg.id, user.id].toString()); 113 | await reaction.users.remove(user.id); 114 | } 115 | }); 116 | 117 | client.on('messageReactionRemove', async (reaction, user) => { 118 | if ( 119 | !reaction.message.guild || 120 | (reaction.emoji.id ?? reaction.emoji.name) !== repEmoji || 121 | removedReactions.delete([reaction.message.id, user.id].toString()) 122 | ) 123 | return; 124 | 125 | let rep = await Rep.findOne({ 126 | where: { 127 | messageId: reaction.message.id, 128 | }, 129 | }); 130 | 131 | if (!rep) return; 132 | 133 | rep.amount -= 1; 134 | await rep.save(); 135 | 136 | console.log( 137 | 'Decremented rep amount to', 138 | rep.amount, 139 | 'for message', 140 | rep.messageId, 141 | ); 142 | }); 143 | 144 | client.on('messageDelete', async msg => { 145 | await Rep.delete(msg.id); 146 | }); 147 | 148 | bot.registerCommand({ 149 | aliases: ['rep'], 150 | description: 'Reputation: Give a different user some reputation points', 151 | async listener(msg) { 152 | const targetMember = msg.content.split(/\s/)[1]; 153 | 154 | if (targetMember.match(/\d+/)) { 155 | const user = await client.users 156 | .fetch(targetMember) 157 | .catch(() => null); 158 | 159 | if (user) { 160 | if (user.id === msg.author.id) { 161 | await msg.react('🤡'); 162 | return; 163 | } 164 | await giveRep(msg, { 165 | recipient: targetMember, 166 | initialGiver: msg.author.id, 167 | }); 168 | await msg.react(repEmoji); 169 | return; 170 | } 171 | } 172 | 173 | await onThank(msg, true); 174 | }, 175 | }); 176 | 177 | bot.registerCommand({ 178 | aliases: ['history'], 179 | description: "Reputation: View a user's reputation history", 180 | async listener(msg) { 181 | if (!msg.member) return; 182 | let user = await bot.getTargetUser(msg); 183 | 184 | if (!user) { 185 | await sendWithMessageOwnership( 186 | msg, 187 | 'Unable to find user to give rep', 188 | ); 189 | return; 190 | } 191 | 192 | const records = (await Rep.find({ where: { recipient: user.id } })) 193 | .reverse() 194 | .filter(x => x.amount > 0) 195 | .map(rg => { 196 | const emoji = 197 | msg.guild!.emojis.resolve(repEmoji) ?? repEmoji; 198 | const messageLink = `https://discord.com/channels/${ 199 | msg.guild!.id 200 | }/${rg.channel}/${rg.messageId}`; 201 | return `**${ 202 | rg.amount 203 | } ${emoji}** on [message](${messageLink}) (<@${ 204 | rg.initialGiver 205 | }>${rg.amount > 1 ? ' et al.' : ''}) at `; 208 | }); 209 | if (!records.length) records.push('[no reputation history]'); 210 | const recordsPerPage = 10; 211 | const pages = records 212 | .reduce((acc, cur, index) => { 213 | const curChunk = Math.floor(index / recordsPerPage); 214 | acc[curChunk] ??= []; 215 | acc[curChunk].push(cur); 216 | return acc; 217 | }, []) 218 | .map(page => page.join('\n')); 219 | const embed = new EmbedBuilder().setColor(TS_BLUE).setAuthor({ 220 | name: user.tag, 221 | iconURL: user.displayAvatarURL(), 222 | }); 223 | await sendPaginatedMessage( 224 | embed, 225 | pages, 226 | msg.member, 227 | msg.channel, 228 | 300000, 229 | ); 230 | }, 231 | }); 232 | 233 | bot.registerCommand({ 234 | aliases: ['leaderboard', 'lb'], 235 | description: 'Reputation: See who has the most reputation', 236 | async listener(msg) { 237 | const periods = { 238 | 'rolling-hour': ['(past hour)', Date.now() - 60 * 60 * 1000], 239 | 'rolling-day': ['(past day)', Date.now() - 24 * 60 * 60 * 1000], 240 | 'rolling-month': [ 241 | '(past 30 days)', 242 | Date.now() - 30 * 24 * 60 * 60 * 1000, 243 | ], 244 | 'rolling-year': [ 245 | '(past year)', 246 | Date.now() - 365 * 24 * 60 * 60 * 1000, 247 | ], 248 | day: [ 249 | '(today)', 250 | +new Date( 251 | new Date().getFullYear(), 252 | new Date().getMonth(), 253 | new Date().getDate(), 254 | ), 255 | ], 256 | month: [ 257 | '(this month)', 258 | +new Date(new Date().getFullYear(), new Date().getMonth()), 259 | ], 260 | year: ['(this year)', +new Date(new Date().getFullYear())], 261 | all: ['(all time)', 0], 262 | } as const; 263 | 264 | const period = msg.content.split(/\s/)[1] || 'rolling-month'; 265 | 266 | if (!(period in periods)) 267 | return await sendWithMessageOwnership( 268 | msg, 269 | `:x: Invalid period (expected one of ${Object.keys(periods) 270 | .map(x => `\`${x}\``) 271 | .join(', ')})`, 272 | ); 273 | const [text, dateMin] = periods[period as keyof typeof periods]; 274 | const topEmojis = [ 275 | ':first_place:', 276 | ':second_place:', 277 | ':third_place:', 278 | ]; 279 | const query = Rep.createQueryBuilder() 280 | .where(`date > '${new Date(dateMin).toISOString()}'`) 281 | .select(['recipient', 'SUM(amount)', 'MAX(date)']) 282 | .groupBy('recipient') 283 | .orderBy('SUM(amount)', 'DESC') 284 | .limit(10); 285 | const data = (await query.getRawMany()) as { 286 | recipient: string; 287 | sum: number; 288 | }[]; 289 | const embed = new EmbedBuilder() 290 | .setColor(TS_BLUE) 291 | .setTitle(`Top 10 Reputation ${text}`) 292 | .setDescription( 293 | data 294 | .map( 295 | (x, index) => 296 | `${ 297 | topEmojis[index] || ':white_small_square:' 298 | } **<@${x.recipient}>** with **${ 299 | x.sum 300 | }** points.`, 301 | ) 302 | .join('\n'), 303 | ); 304 | await msg.channel.send({ embeds: [embed] }); 305 | }, 306 | }); 307 | } 308 | -------------------------------------------------------------------------------- /src/modules/snippet.ts: -------------------------------------------------------------------------------- 1 | import { EmbedBuilder, TextChannel, User } from 'discord.js'; 2 | import { Snippet } from '../entities/Snippet'; 3 | import { BLOCKQUOTE_GREY } from '../env'; 4 | import { sendWithMessageOwnership } from '../util/send'; 5 | import { getReferencedMessage } from '../util/getReferencedMessage'; 6 | import { splitCustomCommand } from '../util/customCommand'; 7 | import { Bot } from '../bot'; 8 | 9 | // https://stackoverflow.com/a/3809435 10 | const LINK_REGEX = 11 | /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/; 12 | 13 | const DISCORD_MESSAGE_LINK_REGEX_ANCHORED = 14 | /^https:\/\/discord.com\/channels\/(\d+)\/(\d+)\/(\d+)$/; 15 | 16 | export function snippetModule(bot: Bot) { 17 | bot.client.on('messageCreate', async msg => { 18 | const commandData = await splitCustomCommand(bot, msg); 19 | if (!commandData) return; 20 | const { command } = commandData; 21 | 22 | if (command.includes('*') && !command.includes(':')) return; 23 | 24 | const [match] = await interpretSpecifier(msg.author, command, 1); 25 | 26 | if (!match) return; 27 | 28 | // We already know there's a snippet under this id from the search 29 | const snippet = (await Snippet.findOneBy({ id: match.id }))!; 30 | 31 | await addSnippetUses(match.id); 32 | const onDelete = () => addSnippetUses(match.id, -1); 33 | 34 | if (snippet.content) { 35 | await sendWithMessageOwnership(msg, snippet.content, onDelete); 36 | return; 37 | } 38 | 39 | const owner = await bot.client.users.fetch(snippet.owner); 40 | const embed = new EmbedBuilder({ 41 | ...snippet, 42 | // image is in an incompatible format, so we have to set it later 43 | image: undefined, 44 | }); 45 | if (match.id.includes(':')) 46 | embed.setAuthor({ 47 | name: owner.tag, 48 | iconURL: owner.displayAvatarURL(), 49 | }); 50 | if (snippet.image) embed.setImage(snippet.image); 51 | await sendWithMessageOwnership(msg, { embeds: [embed] }, onDelete); 52 | }); 53 | 54 | bot.registerCommand({ 55 | description: 'Snippet: List snippets matching an optional filter', 56 | aliases: ['listSnippets', 'snippets', 'snips'], 57 | async listener(msg, specifier) { 58 | const limit = 20; 59 | const matches = await interpretSpecifier( 60 | msg.author, 61 | specifier || '*', 62 | limit + 1, 63 | ); 64 | await sendWithMessageOwnership(msg, { 65 | embeds: [ 66 | new EmbedBuilder() 67 | .setColor(BLOCKQUOTE_GREY) 68 | .setTitle( 69 | `${ 70 | matches.length > limit 71 | ? `${limit}+` 72 | : matches.length 73 | } Matches Found`, 74 | ) 75 | .setDescription( 76 | matches 77 | .slice(0, limit) 78 | .map( 79 | s => 80 | `- \`${s.id}\` with **${s.uses}** uses`, 81 | ) 82 | .join('\n'), 83 | ), 84 | ], 85 | }); 86 | }, 87 | }); 88 | 89 | bot.registerCommand({ 90 | description: 'Snippet: Create or edit a snippet', 91 | aliases: ['snip', 'snippet', 'createSnippet'], 92 | async listener(msg, content) { 93 | if (!msg.member) return; 94 | 95 | const [name, ...parts] = content.split(' '); 96 | const source = parts.join(' '); 97 | 98 | const linkedMessage = await getMessageFromLink(source); 99 | 100 | if (!name) { 101 | return await sendWithMessageOwnership( 102 | msg, 103 | ':x: You have to supply a name for the command', 104 | ); 105 | } 106 | 107 | const id = name.startsWith('!') 108 | ? `${sanitizeIdPart(name.slice(1))}` 109 | : `${sanitizeIdPart(msg.author.username)}:${sanitizeIdPart( 110 | name, 111 | )}`; 112 | const existingSnippet = await Snippet.findOneBy({ id }); 113 | 114 | if (!id.includes(':') && !bot.isMod(msg.member)) 115 | return await sendWithMessageOwnership( 116 | msg, 117 | ":x: You don't have permission to create a global snippet", 118 | ); 119 | 120 | if ( 121 | !bot.isMod(msg.member) && 122 | existingSnippet && 123 | existingSnippet.owner !== msg.author.id 124 | ) 125 | return await sendWithMessageOwnership( 126 | msg, 127 | ":x: Cannot edit another user's snippet", 128 | ); 129 | 130 | const title = `\`!${id}\`: `; 131 | const base = { 132 | id, 133 | uses: existingSnippet?.uses ?? 0, 134 | owner: msg.author.id, 135 | title, 136 | }; 137 | let data: Partial | undefined; 138 | 139 | if (source && !linkedMessage) { 140 | const referencedSnippet = await Snippet.findOneBy({ 141 | id: source, 142 | }); 143 | if (!referencedSnippet) 144 | return await sendWithMessageOwnership( 145 | msg, 146 | ':x: Second argument must be a valid discord message link or snippet id', 147 | ); 148 | data = { 149 | ...referencedSnippet, 150 | ...base, 151 | title: 152 | base.title + 153 | referencedSnippet.title 154 | ?.split(': ') 155 | .slice(1) 156 | .join(': '), 157 | }; 158 | } else { 159 | const sourceMessage = 160 | linkedMessage ?? (await getReferencedMessage(msg)); 161 | if (!sourceMessage) 162 | return await sendWithMessageOwnership( 163 | msg, 164 | ':x: You have to reply or link to a comment to make it a snippet', 165 | ); 166 | 167 | const description = sourceMessage.content; 168 | const referencedEmbed = sourceMessage.embeds[0]; 169 | 170 | if (LINK_REGEX.exec(description)?.[0] === description) 171 | data = { 172 | ...base, 173 | content: description, 174 | }; 175 | else if (description) 176 | data = { 177 | ...base, 178 | description, 179 | color: parseInt(BLOCKQUOTE_GREY.slice(1), 16), 180 | }; 181 | else if (referencedEmbed) 182 | data = { 183 | ...base, 184 | title: title + (referencedEmbed.title || ''), 185 | description: referencedEmbed.description!, 186 | color: referencedEmbed.color!, 187 | image: referencedEmbed.image?.url, 188 | url: referencedEmbed.url!, 189 | }; 190 | } 191 | 192 | if (!data) { 193 | return await sendWithMessageOwnership( 194 | msg, 195 | ':x: Cannot generate a snippet from that message', 196 | ); 197 | } 198 | 199 | await existingSnippet?.remove(); 200 | await Snippet.create(data).save(); 201 | const verb = existingSnippet ? 'Edited' : 'Created'; 202 | await sendWithMessageOwnership( 203 | msg, 204 | `:white_check_mark: ${verb} snippet \`${id}\``, 205 | ); 206 | console.log(`${verb} snippet ${id} for`, msg.author); 207 | }, 208 | }); 209 | 210 | bot.registerCommand({ 211 | description: 'Snippet: Delete a snippet you own', 212 | aliases: ['deleteSnip'], 213 | async listener(msg, id) { 214 | if (!msg.member) return; 215 | const snippet = await Snippet.findOneBy({ id }); 216 | if (!snippet) 217 | return await sendWithMessageOwnership( 218 | msg, 219 | ':x: No snippet found with that id', 220 | ); 221 | if (!bot.isMod(msg.member) && snippet.owner !== msg.author.id) 222 | return await sendWithMessageOwnership( 223 | msg, 224 | ":x: Cannot delete another user's snippet", 225 | ); 226 | await snippet.remove(); 227 | console.log(`Deleted snippet ${id} for`, msg.author); 228 | sendWithMessageOwnership(msg, ':white_check_mark: Deleted snippet'); 229 | }, 230 | }); 231 | 232 | async function getMessageFromLink(messageLink: string | undefined) { 233 | const messageLinkExec = DISCORD_MESSAGE_LINK_REGEX_ANCHORED.exec( 234 | messageLink ?? '', 235 | ); 236 | if (!messageLinkExec) return; 237 | const guild = bot.client.guilds.cache.get(messageLinkExec[1]); 238 | const channel = guild?.channels.cache.get(messageLinkExec[2]); 239 | if (!channel || !(channel instanceof TextChannel)) return; 240 | const message = await channel.messages.fetch(messageLinkExec[3]); 241 | return message; 242 | } 243 | } 244 | 245 | const sanitizeIdPart = (part: string) => 246 | part.toLowerCase().replace(/[^\w-]/g, ''); 247 | 248 | const interpretSpecifier = async ( 249 | sender: User, 250 | specifier: string, 251 | limit: number, 252 | ): Promise => { 253 | specifier = specifier.replace(/\\/g, ''); 254 | if (/[^\w:*%-]/.test(specifier)) return []; 255 | // `%` is SQL's /.*/g for LIKE 256 | specifier = specifier.replace(/\*/g, '%'); 257 | const baseQuery = () => 258 | Snippet.createQueryBuilder() 259 | .select(['id', 'owner', 'uses']) 260 | .where('id like :specifier') 261 | .orderBy('uses', 'DESC') 262 | .setParameters({ specifier: specifier, owner: sender.id }) 263 | .limit(limit); 264 | if (specifier.startsWith(':')) { 265 | specifier = '%' + specifier; 266 | const matches = await baseQuery() 267 | .andWhere('owner = :owner') 268 | .getRawMany(); 269 | if (matches.length) return matches; 270 | } 271 | return await baseQuery().getRawMany(); 272 | }; 273 | 274 | const addSnippetUses = async (id: string, amount = 1) => { 275 | await Snippet.createQueryBuilder() 276 | .update() 277 | .where('id = :id') 278 | .set({ uses: () => 'uses + :amount' }) 279 | .setParameters({ id, amount }) 280 | .execute(); 281 | }; 282 | 283 | type SnippetInfo = Pick; 284 | -------------------------------------------------------------------------------- /src/modules/twoslash.ts: -------------------------------------------------------------------------------- 1 | import { Message } from 'discord.js'; 2 | import { twoslasher, TwoSlashReturn } from '@typescript/twoslash'; 3 | import { ScriptTarget, type CompilerOptions } from 'typescript'; 4 | import { makeCodeBlock, findCode } from '../util/codeBlocks'; 5 | import { sendWithMessageOwnership } from '../util/send'; 6 | import { getTypeScriptModule, TypeScript } from '../util/getTypeScriptModule'; 7 | import { splitCustomCommand } from '../util/customCommand'; 8 | import { Bot } from '../bot'; 9 | 10 | const defaultCompilerOptions: CompilerOptions = { 11 | target: ScriptTarget.ESNext, 12 | }; 13 | 14 | // Preload typescript@latest 15 | getTypeScriptModule('latest'); 16 | 17 | // Remove `@noErrorTruncation` from the source; this can cause lag/crashes for large errors 18 | function redactNoErrorTruncation(code: string) { 19 | return code.replace(/@noErrorTruncation/g, ''); 20 | } 21 | 22 | export function twoslashModule(bot: Bot) { 23 | bot.registerCommand({ 24 | description: 25 | 'Twoslash: Run twoslash on the latest codeblock, optionally returning the quick infos of specified symbols. You can use ts@4.8.3 or ts@next to run a specific version.', 26 | aliases: ['twoslash', 'ts'], 27 | async listener(msg, content) { 28 | await twoslash(msg, 'latest', content); 29 | }, 30 | }); 31 | 32 | bot.client.on('messageCreate', async msg => { 33 | const commandData = await splitCustomCommand(bot, msg); 34 | if (!commandData) return; 35 | const { command, args } = commandData; 36 | if (!command.startsWith('ts@') && !command.startsWith('twoslash@')) 37 | return; 38 | const version = command.split('@').slice(1).join('@'); 39 | await twoslash(msg, version, args); 40 | }); 41 | 42 | bot.client.on('messageCreate', async msg => { 43 | const match = msg.content.match( 44 | /^```(?:ts |typescript )?twoslash\n([\s\S]+)```$/im, 45 | ); 46 | if (!msg.author.bot && match) { 47 | await twoslashBlock( 48 | msg, 49 | match[1], 50 | (await getTypeScriptModule('latest'))!, 51 | ); 52 | await msg.delete(); 53 | } 54 | }); 55 | } 56 | 57 | async function twoslash(msg: Message, version: string, content: string) { 58 | const tsModule = await getTypeScriptModule(version); 59 | 60 | if (!tsModule) 61 | return await sendWithMessageOwnership( 62 | msg, 63 | ':x: Could not find that version of TypeScript', 64 | ); 65 | 66 | const code = await findCode(msg); 67 | 68 | if (!code) 69 | return await sendWithMessageOwnership( 70 | msg, 71 | `:warning: could not find any TypeScript codeblocks in the past 10 messages`, 72 | ); 73 | 74 | if (!content) return await twoslashBlock(msg, code, tsModule); 75 | 76 | if (!/^\s*([_$a-zA-Z][_$0-9a-zA-Z]*\b\s*)+/.test(content)) { 77 | return sendWithMessageOwnership( 78 | msg, 79 | 'You need to give me a valid symbol name to look for!', 80 | ); 81 | } 82 | 83 | const symbols = [...new Set(content.trim().split(/\s+/g))]; 84 | 85 | let ret: TwoSlashReturn; 86 | try { 87 | ret = twoslasher(redactNoErrorTruncation(code), 'ts', { 88 | tsModule, 89 | defaultCompilerOptions, 90 | defaultOptions: { noErrorValidation: true }, 91 | }); 92 | } catch (e) { 93 | if (!(e instanceof Error)) throw e; 94 | return await sendWithMessageOwnership(msg, `:x: ${e.message}`); 95 | } 96 | 97 | const blocks = []; 98 | 99 | for (const symbol of symbols) { 100 | const block = []; 101 | const matches: Record> = {}; 102 | for (const quickInfo of ret.staticQuickInfos) { 103 | if (quickInfo.targetString !== symbol) continue; 104 | (matches[quickInfo.text] = 105 | matches[quickInfo.text] ?? new Set()).add( 106 | `${quickInfo.line + 1}:${quickInfo.character + 1}`, 107 | ); 108 | } 109 | if (!Object.entries(matches).length) 110 | block.push(`/* No symbol named \`${symbol}\` found */`); 111 | for (const [info, locSet] of Object.entries(matches)) { 112 | block.push(`${info} /* ${[...locSet].join(', ')} */`); 113 | } 114 | blocks.push(block); 115 | } 116 | 117 | await sendWithMessageOwnership( 118 | msg, 119 | blocks.map(block => makeCodeBlock(block.join('\n'))).join(''), 120 | ); 121 | } 122 | 123 | async function twoslashBlock(msg: Message, code: string, tsModule: TypeScript) { 124 | let ret: TwoSlashReturn; 125 | try { 126 | ret = twoslasher(redactNoErrorTruncation(code), 'ts', { 127 | tsModule, 128 | defaultCompilerOptions, 129 | defaultOptions: { 130 | noErrorValidation: true, 131 | noStaticSemanticInfo: false, 132 | }, 133 | }); 134 | } catch (e) { 135 | if (!(e instanceof Error)) throw e; 136 | return await sendWithMessageOwnership(msg, `:x: ${e.message}`); 137 | } 138 | 139 | const resultLines: string[] = []; 140 | const twoslashLines = ret.code.split('\n'); 141 | 142 | twoslashLines.forEach((line, index) => { 143 | resultLines.push(line); 144 | 145 | const lineErrors = ret.errors.filter(e => e.line === index); 146 | const lineQueries = ret.queries.filter(e => e.line === index + 1); 147 | 148 | if (lineErrors.length + lineQueries.length === 0) return; 149 | 150 | if (lineErrors.length) { 151 | // Make sure all lines of errors start with a comment 152 | const errors = lineErrors.map( 153 | e => '// ' + e.renderedMessage.split('\n').join('\n// '), 154 | ); 155 | 156 | // Points to errors, e.g. ' ^^^^ ^^^^^' 157 | const hats = lineErrors 158 | // only those with a valid span 159 | .filter(x => x.character != null && x.length != null) 160 | // map to [start, end (non-inclusive)] 161 | .map( 162 | error => 163 | [ 164 | error.character!, 165 | error.character! + error.length!, 166 | ] as const, 167 | ) 168 | // sort by start, ascending 169 | .sort((a, b) => a[0] - b[0]) 170 | // fix overlapping ranges 171 | .map((cur, i, a) => { 172 | let prev = a[i - 1]; 173 | if (!prev) return cur; 174 | return [ 175 | Math.max(prev[1], cur[0]), 176 | Math.max(prev[1], cur[1]), 177 | ] as const; 178 | }) 179 | // remove empty ranges 180 | .filter(([start, end]) => start < end) 181 | // map each to hats 182 | .map(([start, end], i, a) => { 183 | let prevEnd = a[i - 1]?.[1] ?? 0; 184 | return ( 185 | ' '.repeat(start - prevEnd) + '^'.repeat(end - start) 186 | ); 187 | }) 188 | // join the resulting strings 189 | .join(''); 190 | 191 | if (hats.length > 0) { 192 | resultLines.push('//' + hats.slice(2)); 193 | } 194 | 195 | resultLines.push(...errors); 196 | } 197 | 198 | // Inline queries for showing the LSP lookup for a token 199 | if (lineQueries.length) { 200 | let queryComment = '//'; 201 | lineQueries.forEach(q => { 202 | const spaceBefore = q.offset - queryComment.length; 203 | queryComment += ' '.repeat(spaceBefore); 204 | queryComment += '^? - '; 205 | queryComment += 206 | q.text?.replace(/\n/g, '\n//' + ' '.repeat(spaceBefore)) || 207 | ''; 208 | }); 209 | resultLines.push(queryComment); 210 | } 211 | }); 212 | 213 | const output = resultLines.join('\n'); 214 | return sendWithMessageOwnership(msg, makeCodeBlock(output) + '\n'); 215 | } 216 | -------------------------------------------------------------------------------- /src/util/codeBlocks.ts: -------------------------------------------------------------------------------- 1 | import { Message, MessageType } from 'discord.js'; 2 | import { decompressFromEncodedURIComponent } from 'lz-string'; 3 | import { getReferencedMessage } from './getReferencedMessage'; 4 | 5 | const CODEBLOCK_REGEX = /```(?:ts|typescript|js|javascript)?\n([\s\S]+)```/; 6 | 7 | const PLAYGROUND_REGEX = 8 | /?/; 9 | 10 | export type PlaygroundLinkMatch = { 11 | url: string; 12 | query: string; 13 | code: string; 14 | isWholeMatch: boolean; 15 | /* Is the url wrapped in < > ? */ 16 | isEscaped: boolean; 17 | }; 18 | export function matchPlaygroundLink( 19 | msg: string, 20 | ): PlaygroundLinkMatch | undefined { 21 | const match = msg.match(PLAYGROUND_REGEX); 22 | if (!match) return; 23 | const [possiblyEscapedUrl, url, query, code] = match; 24 | const isWholeMatch = msg === possiblyEscapedUrl; 25 | const isEscaped = possiblyEscapedUrl.length === url.length + 2; 26 | return { url, query, code, isWholeMatch, isEscaped }; 27 | } 28 | 29 | export async function findCode(message: Message, ignoreLinks = false) { 30 | const codeInMessage = await findCodeInMessage(message, ignoreLinks); 31 | if (codeInMessage) return codeInMessage; 32 | const referencedMessage = await getReferencedMessage(message); 33 | if (referencedMessage) { 34 | const codeInReferencedMessage = await findCodeInMessage( 35 | referencedMessage, 36 | ignoreLinks, 37 | ); 38 | if (codeInReferencedMessage) return codeInReferencedMessage; 39 | } 40 | const msgs = await message.channel.messages.fetch({ limit: 10 }); 41 | 42 | for (const msg of msgs.values()) { 43 | const code = await findCodeInMessage(msg, ignoreLinks); 44 | if (code) return code; 45 | } 46 | } 47 | 48 | // Two possibilities: 49 | // 1: Normal code block annotated with ts from a non-bot 50 | // 2: Link to TS playground. This can be either from a bot or a normal user 51 | // since we shorten playground links on their own and delete the message. 52 | async function findCodeInMessage(msg: Message, ignoreLinks = false) { 53 | if (msg.type === MessageType.ThreadStarterMessage) { 54 | msg = await msg.fetchReference(); 55 | } 56 | const { author, content, embeds } = msg; 57 | if (!author.bot) { 58 | const match = content.match(CODEBLOCK_REGEX); 59 | if (match && match[1].length) { 60 | return match[1]; 61 | } 62 | } 63 | 64 | if (ignoreLinks) return; 65 | 66 | const codeSources = [content, ...embeds.map(({ url }) => url)]; 67 | 68 | for (const code of codeSources) { 69 | const match = code && matchPlaygroundLink(code); 70 | if (match) { 71 | return decompressFromEncodedURIComponent(match.code); 72 | } 73 | } 74 | } 75 | 76 | const CODEBLOCK = '```'; 77 | // 2048 is the most characters Discord allows in a message/embed 78 | const MAX_CODE_LENGTH = 2048 - `${CODEBLOCK}ts\n${CODEBLOCK}`.length; 79 | 80 | export function makeCodeBlock(code: string) { 81 | return `${CODEBLOCK}ts\n${truncate( 82 | escapeCode(code), 83 | MAX_CODE_LENGTH, 84 | )}${CODEBLOCK}`; 85 | } 86 | 87 | // Note: If str.length === cutoff, the string fits! No need to truncate. 88 | // (This is an easy off-by-one error to make) 89 | export function truncate(str: string, max: number) { 90 | return str.length <= max ? str : str.slice(0, max - 1) + '…'; 91 | } 92 | 93 | // Custom escape function instead of using discord.js Util.escapeCodeBlock because this 94 | // produces better results with template literal types. Discord's markdown handling is pretty 95 | // bad. It doesn't properly handle escaping back ticks, so we instead insert zero width spaces 96 | // so that users cannot escape our code block. 97 | function escapeCode(code: string) { 98 | return code.replace(/`(?=`)/g, '`\u200B'); 99 | } 100 | -------------------------------------------------------------------------------- /src/util/customCommand.ts: -------------------------------------------------------------------------------- 1 | import { Message } from 'discord.js'; 2 | import { Bot } from '../bot'; 3 | import { prefixes } from '../env'; 4 | 5 | export async function splitCustomCommand(bot: Bot, msg: Message) { 6 | const [commandPart, ...argParts] = msg.content.split(' '); 7 | const matchingPrefix = prefixes.find(x => msg.content.startsWith(x)); 8 | if (!matchingPrefix) return; 9 | let command = commandPart.slice(matchingPrefix.length); 10 | if (bot.getByTrigger(command)) return; 11 | if (!command) return; 12 | return { 13 | command, 14 | args: argParts.join(' '), 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/util/getReferencedMessage.ts: -------------------------------------------------------------------------------- 1 | import { Message } from 'discord.js'; 2 | 3 | export async function getReferencedMessage(msg: Message) { 4 | if (!msg.reference?.messageId) return null; 5 | const message = await msg.channel.messages.fetch(msg.reference.messageId); 6 | return message; 7 | } 8 | -------------------------------------------------------------------------------- /src/util/getTypeScriptModule.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'npm-registry-fetch'; 2 | import { promises as fs } from 'fs'; 3 | import tar from 'tar'; 4 | import path from 'path'; 5 | import os from 'os'; 6 | import { once } from 'events'; 7 | 8 | export type TypeScript = typeof import('typescript'); 9 | 10 | const moduleMemo = new Map>(); 11 | 12 | // Refresh package data after 1 hour 13 | const tsPackageDataTimeout = 1000 * 60 * 60; 14 | 15 | let tsPackageData: 16 | | { 17 | 'dist-tags': Record; 18 | versions: Record< 19 | string, 20 | { 21 | dist: { 22 | tarball: string; 23 | }; 24 | } 25 | >; 26 | } 27 | | undefined; 28 | 29 | export async function getTypeScriptModule( 30 | version: string | null, 31 | ): Promise { 32 | console.log(`Downloading typescript@${version}`); 33 | 34 | version = await resolveVersion(version); 35 | 36 | if (!version) { 37 | console.log(`typescript@${version} does not exist`); 38 | return null; 39 | } 40 | 41 | const memoModule = moduleMemo.get(version); 42 | if (memoModule) return memoModule; 43 | 44 | const tsPromise = (async () => { 45 | const directory = await fs.mkdtemp( 46 | path.join(os.tmpdir(), `tsbot-typescript-${version}-`), 47 | ); 48 | const tarballUrl = tsPackageData!.versions[version].dist.tarball; 49 | const tarballResponse = await fetch(tarballUrl); 50 | 51 | await once( 52 | tarballResponse.body.pipe(tar.extract({ cwd: directory })), 53 | 'finish', 54 | ); 55 | 56 | const ts: TypeScript = require(path.join(directory, 'package')); 57 | 58 | return ts; 59 | })(); 60 | 61 | moduleMemo.set(version, tsPromise); 62 | 63 | return await tsPromise; 64 | } 65 | 66 | async function resolveVersion(version: string | null): Promise { 67 | version ??= 'latest'; 68 | version = version.toLowerCase(); 69 | if (version === 'nightly') version = 'next'; 70 | tsPackageData ??= await getPackageData(); 71 | if (version in tsPackageData['dist-tags']) 72 | version = tsPackageData['dist-tags'][version]; 73 | if (version in tsPackageData.versions) return version; 74 | return null; 75 | } 76 | 77 | async function getPackageData() { 78 | tsPackageData = (await fetch.json('/typescript')) as any; 79 | setTimeout(() => (tsPackageData = undefined), tsPackageDataTimeout); 80 | return tsPackageData!; 81 | } 82 | -------------------------------------------------------------------------------- /src/util/limitedSizeMap.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A FIFO style limited size map, used to prevent memory from growing unboundedly when tracking messages. 3 | */ 4 | export class LimitedSizeMap extends Map implements Map { 5 | private _maxSize: number; 6 | 7 | constructor(maxSize: number) { 8 | super(); 9 | this._maxSize = maxSize; 10 | } 11 | 12 | set(key: K, val: V) { 13 | super.set(key, val); 14 | 15 | if (this.size > this._maxSize) { 16 | // Keys returns an iterable in insertion order, so this removes the oldest entry from the map. 17 | this.delete(this.keys().next().value); 18 | } 19 | 20 | return this; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/util/send.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Message, 3 | MessageCreateOptions, 4 | MessagePayload, 5 | PartialMessage, 6 | User, 7 | } from 'discord.js'; 8 | import { LimitedSizeMap } from './limitedSizeMap'; 9 | 10 | const messageToUserId = new LimitedSizeMap< 11 | string, 12 | { owner: string; onDelete?: () => void } 13 | >(1000); 14 | 15 | export const DELETE_EMOJI = '🗑️'; 16 | 17 | export async function sendWithMessageOwnership( 18 | message: Message, 19 | toSend: string | MessagePayload | MessageCreateOptions, 20 | onDelete?: () => void, 21 | ) { 22 | const sent = await message.channel.send(toSend); 23 | await addMessageOwnership(sent, message.author, onDelete); 24 | } 25 | 26 | export async function addMessageOwnership( 27 | message: Message, 28 | user: User, 29 | onDelete?: () => void, 30 | ) { 31 | await message.react(DELETE_EMOJI); 32 | 33 | messageToUserId.set(message.id, { owner: user.id, onDelete }); 34 | } 35 | 36 | export function getMessageOwner(message: Message | PartialMessage) { 37 | return messageToUserId.get(message.id)?.owner; 38 | } 39 | 40 | export function ownsBotMessage( 41 | message: Message | PartialMessage, 42 | userId: string, 43 | ) { 44 | return messageToUserId.get(message.id)?.owner === userId; 45 | } 46 | 47 | export function clearMessageOwnership(message: Message | PartialMessage) { 48 | messageToUserId.get(message.id)?.onDelete?.(); 49 | messageToUserId.delete(message.id); 50 | } 51 | -------------------------------------------------------------------------------- /src/util/sendPaginatedMessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GuildMember, 3 | EmbedBuilder, 4 | MessageReaction, 5 | TextBasedChannel, 6 | User, 7 | } from 'discord.js'; 8 | 9 | const emojis = { 10 | back: '◀', 11 | first: '⏮', 12 | last: '⏭', 13 | next: '▶', 14 | stop: '⏹', 15 | }; 16 | 17 | export async function sendPaginatedMessage( 18 | embed: EmbedBuilder, 19 | pages: string[], 20 | member: GuildMember, 21 | channel: TextBasedChannel, 22 | timeout: number = 100000, 23 | ) { 24 | let curPage = 0; 25 | const message = await channel.send({ 26 | embeds: [ 27 | embed 28 | .setDescription(pages[curPage]) 29 | .setFooter({ text: `Page ${curPage + 1} of ${pages.length}` }), 30 | ], 31 | }); 32 | if (pages.length === 1) return; 33 | 34 | await message.react(emojis.first); 35 | await message.react(emojis.back); 36 | await message.react(emojis.stop); 37 | await message.react(emojis.next); 38 | await message.react(emojis.last); 39 | 40 | const collector = message.createReactionCollector({ 41 | filter: (_reaction, user) => 42 | user.id === member.id && user.id !== message.author.id, 43 | time: timeout, 44 | }); 45 | 46 | collector.on('collect', async (reaction: MessageReaction, user: User) => { 47 | await reaction.users.remove(user); 48 | 49 | switch (reaction.emoji.toString()) { 50 | case emojis.first: 51 | curPage = 0; 52 | break; 53 | case emojis.last: 54 | curPage = pages.length - 1; 55 | break; 56 | case emojis.stop: 57 | await message.reactions.removeAll(); 58 | break; 59 | case emojis.back: 60 | curPage--; 61 | if (curPage < 0) curPage = pages.length - 1; 62 | break; 63 | case emojis.next: 64 | curPage++; 65 | if (curPage > pages.length - 1) curPage = 0; 66 | break; 67 | } 68 | 69 | await message.edit({ 70 | embeds: [ 71 | embed.setDescription(pages[curPage]).setFooter({ 72 | text: `Page ${curPage + 1} of ${pages.length}`, 73 | }), 74 | ], 75 | }); 76 | }); 77 | 78 | collector.on('end', () => { 79 | message.reactions.removeAll(); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "Node16", 5 | "lib": ["ES2020"], 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "types": ["discord.js", "dotenv-safe"], 9 | 10 | "strict": true, 11 | "esModuleInterop": true, 12 | 13 | "noUnusedParameters": true, 14 | "noUnusedLocals": true, 15 | "skipLibCheck": true, 16 | 17 | "experimentalDecorators": true, 18 | "emitDecoratorMetadata": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@algolia/cache-browser-local-storage@4.14.2": 6 | version "4.14.2" 7 | resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.14.2.tgz#d5b1b90130ca87c6321de876e167df9ec6524936" 8 | integrity sha512-FRweBkK/ywO+GKYfAWbrepewQsPTIEirhi1BdykX9mxvBPtGNKccYAxvGdDCumU1jL4r3cayio4psfzKMejBlA== 9 | dependencies: 10 | "@algolia/cache-common" "4.14.2" 11 | 12 | "@algolia/cache-common@4.14.2": 13 | version "4.14.2" 14 | resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.14.2.tgz#b946b6103c922f0c06006fb6929163ed2c67d598" 15 | integrity sha512-SbvAlG9VqNanCErr44q6lEKD2qoK4XtFNx9Qn8FK26ePCI8I9yU7pYB+eM/cZdS9SzQCRJBbHUumVr4bsQ4uxg== 16 | 17 | "@algolia/cache-in-memory@4.14.2": 18 | version "4.14.2" 19 | resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.14.2.tgz#88e4a21474f9ac05331c2fa3ceb929684a395a24" 20 | integrity sha512-HrOukWoop9XB/VFojPv1R5SVXowgI56T9pmezd/djh2JnVN/vXswhXV51RKy4nCpqxyHt/aGFSq2qkDvj6KiuQ== 21 | dependencies: 22 | "@algolia/cache-common" "4.14.2" 23 | 24 | "@algolia/client-account@4.14.2": 25 | version "4.14.2" 26 | resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.14.2.tgz#b76ac1ba9ea71e8c3f77a1805b48350dc0728a16" 27 | integrity sha512-WHtriQqGyibbb/Rx71YY43T0cXqyelEU0lB2QMBRXvD2X0iyeGl4qMxocgEIcbHyK7uqE7hKgjT8aBrHqhgc1w== 28 | dependencies: 29 | "@algolia/client-common" "4.14.2" 30 | "@algolia/client-search" "4.14.2" 31 | "@algolia/transporter" "4.14.2" 32 | 33 | "@algolia/client-analytics@4.14.2": 34 | version "4.14.2" 35 | resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.14.2.tgz#ca04dcaf9a78ee5c92c5cb5e9c74cf031eb2f1fb" 36 | integrity sha512-yBvBv2mw+HX5a+aeR0dkvUbFZsiC4FKSnfqk9rrfX+QrlNOKEhCG0tJzjiOggRW4EcNqRmaTULIYvIzQVL2KYQ== 37 | dependencies: 38 | "@algolia/client-common" "4.14.2" 39 | "@algolia/client-search" "4.14.2" 40 | "@algolia/requester-common" "4.14.2" 41 | "@algolia/transporter" "4.14.2" 42 | 43 | "@algolia/client-common@4.14.2": 44 | version "4.14.2" 45 | resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.14.2.tgz#e1324e167ffa8af60f3e8bcd122110fd0bfd1300" 46 | integrity sha512-43o4fslNLcktgtDMVaT5XwlzsDPzlqvqesRi4MjQz2x4/Sxm7zYg5LRYFol1BIhG6EwxKvSUq8HcC/KxJu3J0Q== 47 | dependencies: 48 | "@algolia/requester-common" "4.14.2" 49 | "@algolia/transporter" "4.14.2" 50 | 51 | "@algolia/client-personalization@4.14.2": 52 | version "4.14.2" 53 | resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.14.2.tgz#656bbb6157a3dd1a4be7de65e457fda136c404ec" 54 | integrity sha512-ACCoLi0cL8CBZ1W/2juehSltrw2iqsQBnfiu/Rbl9W2yE6o2ZUb97+sqN/jBqYNQBS+o0ekTMKNkQjHHAcEXNw== 55 | dependencies: 56 | "@algolia/client-common" "4.14.2" 57 | "@algolia/requester-common" "4.14.2" 58 | "@algolia/transporter" "4.14.2" 59 | 60 | "@algolia/client-search@4.14.2": 61 | version "4.14.2" 62 | resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.14.2.tgz#357bdb7e640163f0e33bad231dfcc21f67dc2e92" 63 | integrity sha512-L5zScdOmcZ6NGiVbLKTvP02UbxZ0njd5Vq9nJAmPFtjffUSOGEp11BmD2oMJ5QvARgx2XbX4KzTTNS5ECYIMWw== 64 | dependencies: 65 | "@algolia/client-common" "4.14.2" 66 | "@algolia/requester-common" "4.14.2" 67 | "@algolia/transporter" "4.14.2" 68 | 69 | "@algolia/logger-common@4.14.2": 70 | version "4.14.2" 71 | resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.14.2.tgz#b74b3a92431f92665519d95942c246793ec390ee" 72 | integrity sha512-/JGlYvdV++IcMHBnVFsqEisTiOeEr6cUJtpjz8zc0A9c31JrtLm318Njc72p14Pnkw3A/5lHHh+QxpJ6WFTmsA== 73 | 74 | "@algolia/logger-console@4.14.2": 75 | version "4.14.2" 76 | resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.14.2.tgz#ec49cb47408f5811d4792598683923a800abce7b" 77 | integrity sha512-8S2PlpdshbkwlLCSAB5f8c91xyc84VM9Ar9EdfE9UmX+NrKNYnWR1maXXVDQQoto07G1Ol/tYFnFVhUZq0xV/g== 78 | dependencies: 79 | "@algolia/logger-common" "4.14.2" 80 | 81 | "@algolia/requester-browser-xhr@4.14.2": 82 | version "4.14.2" 83 | resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.14.2.tgz#a2cd4d9d8d90d53109cc7f3682dc6ebf20f798f2" 84 | integrity sha512-CEh//xYz/WfxHFh7pcMjQNWgpl4wFB85lUMRyVwaDPibNzQRVcV33YS+63fShFWc2+42YEipFGH2iPzlpszmDw== 85 | dependencies: 86 | "@algolia/requester-common" "4.14.2" 87 | 88 | "@algolia/requester-common@4.14.2": 89 | version "4.14.2" 90 | resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.14.2.tgz#bc4e9e5ee16c953c0ecacbfb334a33c30c28b1a1" 91 | integrity sha512-73YQsBOKa5fvVV3My7iZHu1sUqmjjfs9TteFWwPwDmnad7T0VTCopttcsM3OjLxZFtBnX61Xxl2T2gmG2O4ehg== 92 | 93 | "@algolia/requester-node-http@4.14.2": 94 | version "4.14.2" 95 | resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.14.2.tgz#7c1223a1785decaab1def64c83dade6bea45e115" 96 | integrity sha512-oDbb02kd1o5GTEld4pETlPZLY0e+gOSWjWMJHWTgDXbv9rm/o2cF7japO6Vj1ENnrqWvLBmW1OzV9g6FUFhFXg== 97 | dependencies: 98 | "@algolia/requester-common" "4.14.2" 99 | 100 | "@algolia/transporter@4.14.2": 101 | version "4.14.2" 102 | resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.14.2.tgz#77c069047fb1a4359ee6a51f51829508e44a1e3d" 103 | integrity sha512-t89dfQb2T9MFQHidjHcfhh6iGMNwvuKUvojAj+JsrHAGbuSy7yE4BylhLX6R0Q1xYRoC4Vvv+O5qIw/LdnQfsQ== 104 | dependencies: 105 | "@algolia/cache-common" "4.14.2" 106 | "@algolia/logger-common" "4.14.2" 107 | "@algolia/requester-common" "4.14.2" 108 | 109 | "@cspotcode/source-map-support@^0.8.0": 110 | version "0.8.1" 111 | resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" 112 | integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== 113 | dependencies: 114 | "@jridgewell/trace-mapping" "0.3.9" 115 | 116 | "@discordjs/builders@^1.3.0": 117 | version "1.3.0" 118 | resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.3.0.tgz#20d4e3fbcd734ce2468df10407c7c6df22c9f77e" 119 | integrity sha512-Pvca6Nw8Hp+n3N+Wp17xjygXmMvggbh5ywUsOYE2Et4xkwwVRwgzxDJiMUuYapPtnYt4w/8aKlf5khc8ipLvhg== 120 | dependencies: 121 | "@discordjs/util" "^0.1.0" 122 | "@sapphire/shapeshift" "^3.7.0" 123 | discord-api-types "^0.37.12" 124 | fast-deep-equal "^3.1.3" 125 | ts-mixer "^6.0.1" 126 | tslib "^2.4.0" 127 | 128 | "@discordjs/collection@^1.2.0": 129 | version "1.2.0" 130 | resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.2.0.tgz#5cad4bb47521c6f0abd175bf55c84528d1ac94f7" 131 | integrity sha512-VvrrtGb7vbfPHzbhGq9qZB5o8FOB+kfazrxdt0OtxzSkoBuw9dURMkCwWizZ00+rDpiK2HmLHBZX+y6JsG9khw== 132 | 133 | "@discordjs/rest@^1.3.0": 134 | version "1.3.0" 135 | resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-1.3.0.tgz#26dd146eeda0c19a3076b3872a00ac9f323763c8" 136 | integrity sha512-U6X5J+r/MxYpPTlHFuPxXEf92aKsBaD2teBC7sWkKILIr30O8c9+XshfL7KFBCavnAqS/qE+PF9fgRilO3N44g== 137 | dependencies: 138 | "@discordjs/collection" "^1.2.0" 139 | "@discordjs/util" "^0.1.0" 140 | "@sapphire/async-queue" "^1.5.0" 141 | "@sapphire/snowflake" "^3.2.2" 142 | discord-api-types "^0.37.12" 143 | file-type "^18.0.0" 144 | tslib "^2.4.0" 145 | undici "^5.11.0" 146 | 147 | "@discordjs/util@^0.1.0": 148 | version "0.1.0" 149 | resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-0.1.0.tgz#e42ca1bf407bc6d9adf252877d1b206e32ba369a" 150 | integrity sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ== 151 | 152 | "@jridgewell/resolve-uri@^3.0.3": 153 | version "3.1.0" 154 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" 155 | integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== 156 | 157 | "@jridgewell/sourcemap-codec@^1.4.10": 158 | version "1.4.14" 159 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" 160 | integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== 161 | 162 | "@jridgewell/trace-mapping@0.3.9": 163 | version "0.3.9" 164 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" 165 | integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== 166 | dependencies: 167 | "@jridgewell/resolve-uri" "^3.0.3" 168 | "@jridgewell/sourcemap-codec" "^1.4.10" 169 | 170 | "@npmcli/fs@^3.1.0": 171 | version "3.1.0" 172 | resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.0.tgz#233d43a25a91d68c3a863ba0da6a3f00924a173e" 173 | integrity sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w== 174 | dependencies: 175 | semver "^7.3.5" 176 | 177 | "@sapphire/async-queue@^1.5.0": 178 | version "1.5.0" 179 | resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.5.0.tgz#2f255a3f186635c4fb5a2381e375d3dfbc5312d8" 180 | integrity sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA== 181 | 182 | "@sapphire/shapeshift@^3.7.0": 183 | version "3.7.0" 184 | resolved "https://registry.yarnpkg.com/@sapphire/shapeshift/-/shapeshift-3.7.0.tgz#488cf06857be75826292dac451c13eeb0143602d" 185 | integrity sha512-A6vI1zJoxhjWo4grsxpBRBgk96SqSdjLX5WlzKp9H+bJbkM07mvwcbtbVAmUZHbi/OG3HLfiZ1rlw4BhH6tsBQ== 186 | dependencies: 187 | fast-deep-equal "^3.1.3" 188 | lodash.uniqwith "^4.5.0" 189 | 190 | "@sapphire/snowflake@^3.2.2": 191 | version "3.2.2" 192 | resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.2.2.tgz#faacdc1b5f7c43145a71eddba917de2b707ef780" 193 | integrity sha512-ula2O0kpSZtX9rKXNeQMrHwNd7E4jPDJYUXmEGTFdMRfyfMw+FPyh04oKMjAiDuOi64bYgVkOV3MjK+loImFhQ== 194 | 195 | "@sqltools/formatter@^1.2.2": 196 | version "1.2.5" 197 | resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" 198 | integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== 199 | 200 | "@tokenizer/token@^0.3.0": 201 | version "0.3.0" 202 | resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" 203 | integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== 204 | 205 | "@tootallnate/once@2": 206 | version "2.0.0" 207 | resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" 208 | integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== 209 | 210 | "@tsconfig/node10@^1.0.7": 211 | version "1.0.9" 212 | resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" 213 | integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== 214 | 215 | "@tsconfig/node12@^1.0.7": 216 | version "1.0.11" 217 | resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" 218 | integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== 219 | 220 | "@tsconfig/node14@^1.0.0": 221 | version "1.0.3" 222 | resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" 223 | integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== 224 | 225 | "@tsconfig/node16@^1.0.2": 226 | version "1.0.3" 227 | resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" 228 | integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== 229 | 230 | "@types/dotenv-safe@8.1.2": 231 | version "8.1.2" 232 | resolved "https://registry.yarnpkg.com/@types/dotenv-safe/-/dotenv-safe-8.1.2.tgz#72f126969f445af5654efd3167deabc2cbc6c24e" 233 | integrity sha512-R/B/wIMda6lRE2P1H0vwSoJsV78IOkhccE4vIvmKZQNXOIjiU0QyJsUXwSotBxOPZFZ/oOnjCa3+kK5kVJwGyw== 234 | dependencies: 235 | dotenv "^8.2.0" 236 | 237 | "@types/lz-string@1.3.34": 238 | version "1.3.34" 239 | resolved "https://registry.yarnpkg.com/@types/lz-string/-/lz-string-1.3.34.tgz#69bfadde419314b4a374bf2c8e58659c035ed0a5" 240 | integrity sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow== 241 | 242 | "@types/minimatch@^3.0.3": 243 | version "3.0.5" 244 | resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" 245 | integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== 246 | 247 | "@types/node-fetch@*": 248 | version "2.6.2" 249 | resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" 250 | integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== 251 | dependencies: 252 | "@types/node" "*" 253 | form-data "^3.0.0" 254 | 255 | "@types/node@*": 256 | version "18.11.9" 257 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" 258 | integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== 259 | 260 | "@types/node@16.18.3": 261 | version "16.18.3" 262 | resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc" 263 | integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg== 264 | 265 | "@types/npm-package-arg@*": 266 | version "6.1.1" 267 | resolved "https://registry.yarnpkg.com/@types/npm-package-arg/-/npm-package-arg-6.1.1.tgz#9e2d8adc04d39824a3d9f36f738010a3f7da3c1a" 268 | integrity sha512-452/1Kp9IdM/oR10AyqAgZOxUt7eLbm+EMJ194L6oarMYdZNiFIFAOJ7IIr0OrZXTySgfHjJezh2oiyk2kc3ag== 269 | 270 | "@types/npm-registry-fetch@8.0.4": 271 | version "8.0.4" 272 | resolved "https://registry.yarnpkg.com/@types/npm-registry-fetch/-/npm-registry-fetch-8.0.4.tgz#77b2737cde22314ccda1dfdb9568fd7769e95b90" 273 | integrity sha512-R9yEj6+NDmXLpKNS19cIaMyaHfV0aHjy/1qbo8K9jiHyjyaYg0CEmuOV/L0Q91DZDi3SuxlYY+2XYwh9TbB+eQ== 274 | dependencies: 275 | "@types/node" "*" 276 | "@types/node-fetch" "*" 277 | "@types/npm-package-arg" "*" 278 | "@types/npmlog" "*" 279 | "@types/ssri" "*" 280 | 281 | "@types/npmlog@*": 282 | version "4.1.4" 283 | resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.4.tgz#30eb872153c7ead3e8688c476054ddca004115f6" 284 | integrity sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ== 285 | 286 | "@types/prettier@2.7.1": 287 | version "2.7.1" 288 | resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" 289 | integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== 290 | 291 | "@types/ssri@*": 292 | version "7.1.1" 293 | resolved "https://registry.yarnpkg.com/@types/ssri/-/ssri-7.1.1.tgz#2a2c94abf0d3a8c3b07bb4ff08142dd571407bb5" 294 | integrity sha512-DPP/jkDaqGiyU75MyMURxLWyYLwKSjnAuGe9ZCsLp9QZOpXmDfuevk769F0BS86TmRuD5krnp06qw9nSoNO+0g== 295 | dependencies: 296 | "@types/node" "*" 297 | 298 | "@types/strip-bom@^3.0.0": 299 | version "3.0.0" 300 | resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" 301 | integrity sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ== 302 | 303 | "@types/strip-json-comments@0.0.30": 304 | version "0.0.30" 305 | resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" 306 | integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== 307 | 308 | "@types/tar@6.1.3": 309 | version "6.1.3" 310 | resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.3.tgz#46a2ce7617950c4852dfd7e9cd41aa8161b9d750" 311 | integrity sha512-YzDOr5kdAeqS8dcO6NTTHTMJ44MUCBDoLEIyPtwEn7PssKqUYL49R1iCVJPeiPzPlKi6DbH33eZkpeJ27e4vHg== 312 | dependencies: 313 | "@types/node" "*" 314 | minipass "^3.3.5" 315 | 316 | "@types/ws@8.5.3", "@types/ws@^8.5.3": 317 | version "8.5.3" 318 | resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" 319 | integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== 320 | dependencies: 321 | "@types/node" "*" 322 | 323 | "@typescript/twoslash@^3.2.1": 324 | version "3.2.1" 325 | resolved "https://registry.yarnpkg.com/@typescript/twoslash/-/twoslash-3.2.1.tgz#7ba5fb2a230925ccd773967b02daf38efc1a3377" 326 | integrity sha512-tS4gLwOe1WCDspqBXhQCb2ESUqzEd1tOkmKpiZ1O+W1x+9l+9njETuXFkLErtH9is/uD1GSvClDjk/tEOJktjQ== 327 | dependencies: 328 | "@typescript/vfs" "1.4.0" 329 | debug "^4.1.1" 330 | lz-string "^1.4.4" 331 | 332 | "@typescript/vfs@1.4.0": 333 | version "1.4.0" 334 | resolved "https://registry.yarnpkg.com/@typescript/vfs/-/vfs-1.4.0.tgz#2d22985c7666c9d4ce26eb025405e6f156aa32b0" 335 | integrity sha512-Pood7yv5YWMIX+yCHo176OnF8WUlKGImFG7XlsuH14Zb1YN5+dYD3uUtS7lqZtsH7tAveNUi2NzdpQCN0yRbaw== 336 | dependencies: 337 | debug "^4.1.1" 338 | 339 | acorn-walk@^8.1.1: 340 | version "8.2.0" 341 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" 342 | integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== 343 | 344 | acorn@^8.4.1: 345 | version "8.8.1" 346 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" 347 | integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== 348 | 349 | agent-base@6, agent-base@^6.0.2: 350 | version "6.0.2" 351 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" 352 | integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== 353 | dependencies: 354 | debug "4" 355 | 356 | agentkeepalive@^4.2.1: 357 | version "4.2.1" 358 | resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" 359 | integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== 360 | dependencies: 361 | debug "^4.1.0" 362 | depd "^1.1.2" 363 | humanize-ms "^1.2.1" 364 | 365 | aggregate-error@^3.0.0: 366 | version "3.1.0" 367 | resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" 368 | integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== 369 | dependencies: 370 | clean-stack "^2.0.0" 371 | indent-string "^4.0.0" 372 | 373 | algoliasearch@^4.14.2: 374 | version "4.14.2" 375 | resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.14.2.tgz#63f142583bfc3a9bd3cd4a1b098bf6fe58e56f6c" 376 | integrity sha512-ngbEQonGEmf8dyEh5f+uOIihv4176dgbuOZspiuhmTTBRBuzWu3KCGHre6uHj5YyuC7pNvQGzB6ZNJyZi0z+Sg== 377 | dependencies: 378 | "@algolia/cache-browser-local-storage" "4.14.2" 379 | "@algolia/cache-common" "4.14.2" 380 | "@algolia/cache-in-memory" "4.14.2" 381 | "@algolia/client-account" "4.14.2" 382 | "@algolia/client-analytics" "4.14.2" 383 | "@algolia/client-common" "4.14.2" 384 | "@algolia/client-personalization" "4.14.2" 385 | "@algolia/client-search" "4.14.2" 386 | "@algolia/logger-common" "4.14.2" 387 | "@algolia/logger-console" "4.14.2" 388 | "@algolia/requester-browser-xhr" "4.14.2" 389 | "@algolia/requester-common" "4.14.2" 390 | "@algolia/requester-node-http" "4.14.2" 391 | "@algolia/transporter" "4.14.2" 392 | 393 | ansi-regex@^5.0.1: 394 | version "5.0.1" 395 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 396 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 397 | 398 | ansi-styles@^4.0.0, ansi-styles@^4.1.0: 399 | version "4.3.0" 400 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 401 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 402 | dependencies: 403 | color-convert "^2.0.1" 404 | 405 | any-promise@^1.0.0: 406 | version "1.3.0" 407 | resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" 408 | integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== 409 | 410 | anymatch@~3.1.2: 411 | version "3.1.2" 412 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 413 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 414 | dependencies: 415 | normalize-path "^3.0.0" 416 | picomatch "^2.0.4" 417 | 418 | app-root-path@^3.0.0: 419 | version "3.1.0" 420 | resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" 421 | integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== 422 | 423 | arg@^4.1.0: 424 | version "4.1.3" 425 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 426 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 427 | 428 | argparse@^2.0.1: 429 | version "2.0.1" 430 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" 431 | integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 432 | 433 | array-differ@^3.0.0: 434 | version "3.0.0" 435 | resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" 436 | integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== 437 | 438 | array-union@^2.1.0: 439 | version "2.1.0" 440 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 441 | integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 442 | 443 | arrify@^2.0.1: 444 | version "2.0.1" 445 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" 446 | integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== 447 | 448 | asynckit@^0.4.0: 449 | version "0.4.0" 450 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 451 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 452 | 453 | balanced-match@^1.0.0: 454 | version "1.0.2" 455 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 456 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 457 | 458 | base64-js@^1.3.1: 459 | version "1.5.1" 460 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 461 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 462 | 463 | binary-extensions@^2.0.0: 464 | version "2.2.0" 465 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 466 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 467 | 468 | brace-expansion@^1.1.7: 469 | version "1.1.11" 470 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 471 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 472 | dependencies: 473 | balanced-match "^1.0.0" 474 | concat-map "0.0.1" 475 | 476 | brace-expansion@^2.0.1: 477 | version "2.0.1" 478 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" 479 | integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== 480 | dependencies: 481 | balanced-match "^1.0.0" 482 | 483 | braces@~3.0.2: 484 | version "3.0.2" 485 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 486 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 487 | dependencies: 488 | fill-range "^7.0.1" 489 | 490 | buffer-from@^1.0.0: 491 | version "1.1.2" 492 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 493 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 494 | 495 | buffer-writer@2.0.0: 496 | version "2.0.0" 497 | resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" 498 | integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== 499 | 500 | buffer@^6.0.3: 501 | version "6.0.3" 502 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" 503 | integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== 504 | dependencies: 505 | base64-js "^1.3.1" 506 | ieee754 "^1.2.1" 507 | 508 | builtins@^5.0.0: 509 | version "5.0.1" 510 | resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" 511 | integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== 512 | dependencies: 513 | semver "^7.0.0" 514 | 515 | busboy@^1.6.0: 516 | version "1.6.0" 517 | resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" 518 | integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== 519 | dependencies: 520 | streamsearch "^1.1.0" 521 | 522 | cacache@^17.0.0: 523 | version "17.0.2" 524 | resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.0.2.tgz#ff2bd029bf45099b3fe711f56fbf138b846c8d6d" 525 | integrity sha512-rYUs2x4OjSgCQND7nTrh21AHIBFgd7s/ctAYvU3a8u+nK+R5YaX/SFPDYz4Azz7SGL6+6L9ZZWI4Kawpb7grzQ== 526 | dependencies: 527 | "@npmcli/fs" "^3.1.0" 528 | fs-minipass "^2.1.0" 529 | glob "^8.0.1" 530 | lru-cache "^7.7.1" 531 | minipass "^3.1.6" 532 | minipass-collect "^1.0.2" 533 | minipass-flush "^1.0.5" 534 | minipass-pipeline "^1.2.4" 535 | p-map "^4.0.0" 536 | promise-inflight "^1.0.1" 537 | ssri "^10.0.0" 538 | tar "^6.1.11" 539 | unique-filename "^3.0.0" 540 | 541 | chalk@^3.0.0: 542 | version "3.0.0" 543 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" 544 | integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== 545 | dependencies: 546 | ansi-styles "^4.1.0" 547 | supports-color "^7.1.0" 548 | 549 | chalk@^4.0.0, chalk@^4.1.0: 550 | version "4.1.2" 551 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 552 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 553 | dependencies: 554 | ansi-styles "^4.1.0" 555 | supports-color "^7.1.0" 556 | 557 | chokidar@^3.5.1: 558 | version "3.5.3" 559 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" 560 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== 561 | dependencies: 562 | anymatch "~3.1.2" 563 | braces "~3.0.2" 564 | glob-parent "~5.1.2" 565 | is-binary-path "~2.1.0" 566 | is-glob "~4.0.1" 567 | normalize-path "~3.0.0" 568 | readdirp "~3.6.0" 569 | optionalDependencies: 570 | fsevents "~2.3.2" 571 | 572 | chownr@^2.0.0: 573 | version "2.0.0" 574 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" 575 | integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== 576 | 577 | clean-stack@^2.0.0: 578 | version "2.2.0" 579 | resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" 580 | integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== 581 | 582 | cli-highlight@^2.1.11: 583 | version "2.1.11" 584 | resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" 585 | integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== 586 | dependencies: 587 | chalk "^4.0.0" 588 | highlight.js "^10.7.1" 589 | mz "^2.4.0" 590 | parse5 "^5.1.1" 591 | parse5-htmlparser2-tree-adapter "^6.0.0" 592 | yargs "^16.0.0" 593 | 594 | cliui@^7.0.2: 595 | version "7.0.4" 596 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" 597 | integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== 598 | dependencies: 599 | string-width "^4.2.0" 600 | strip-ansi "^6.0.0" 601 | wrap-ansi "^7.0.0" 602 | 603 | cliui@^8.0.1: 604 | version "8.0.1" 605 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" 606 | integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== 607 | dependencies: 608 | string-width "^4.2.0" 609 | strip-ansi "^6.0.1" 610 | wrap-ansi "^7.0.0" 611 | 612 | color-convert@^2.0.1: 613 | version "2.0.1" 614 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 615 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 616 | dependencies: 617 | color-name "~1.1.4" 618 | 619 | color-name@~1.1.4: 620 | version "1.1.4" 621 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 622 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 623 | 624 | combined-stream@^1.0.8: 625 | version "1.0.8" 626 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 627 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 628 | dependencies: 629 | delayed-stream "~1.0.0" 630 | 631 | concat-map@0.0.1: 632 | version "0.0.1" 633 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 634 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 635 | 636 | create-require@^1.1.0: 637 | version "1.1.1" 638 | resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" 639 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 640 | 641 | cross-spawn@^7.0.0: 642 | version "7.0.3" 643 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 644 | integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== 645 | dependencies: 646 | path-key "^3.1.0" 647 | shebang-command "^2.0.0" 648 | which "^2.0.1" 649 | 650 | date-fns@^2.28.0: 651 | version "2.29.3" 652 | resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" 653 | integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== 654 | 655 | debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3: 656 | version "4.3.4" 657 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 658 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 659 | dependencies: 660 | ms "2.1.2" 661 | 662 | delayed-stream@~1.0.0: 663 | version "1.0.0" 664 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 665 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== 666 | 667 | depd@^1.1.2: 668 | version "1.1.2" 669 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 670 | integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== 671 | 672 | diff@^4.0.1: 673 | version "4.0.2" 674 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 675 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 676 | 677 | discord-api-types@^0.37.12: 678 | version "0.37.18" 679 | resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.18.tgz#1f0ca95cea4b2380ba77623a62b1d285f1237d7a" 680 | integrity sha512-mJ+9C8gmG5csssVZPH06Y8IGiJykljFyZc6n6F+T3vKo6yNBI5TtLIbwt6t9hJzsR5f1ITzRZ6cuPrTvRCUxqA== 681 | 682 | discord.js@^14.6.0: 683 | version "14.6.0" 684 | resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-14.6.0.tgz#896f7540d4c6f190dffe91d2f3e9a9f7cbc8bd26" 685 | integrity sha512-On1K7xpJZRe0KsziIaDih2ksYPhgxym/ZqV45i1f3yig4vUotikqs7qp5oXiTzQ/UTiNRCixUWFTh7vA1YBCqw== 686 | dependencies: 687 | "@discordjs/builders" "^1.3.0" 688 | "@discordjs/collection" "^1.2.0" 689 | "@discordjs/rest" "^1.3.0" 690 | "@discordjs/util" "^0.1.0" 691 | "@sapphire/snowflake" "^3.2.2" 692 | "@types/ws" "^8.5.3" 693 | discord-api-types "^0.37.12" 694 | fast-deep-equal "^3.1.3" 695 | lodash.snakecase "^4.1.1" 696 | tslib "^2.4.0" 697 | undici "^5.11.0" 698 | ws "^8.9.0" 699 | 700 | dotenv-safe@^8.2.0: 701 | version "8.2.0" 702 | resolved "https://registry.yarnpkg.com/dotenv-safe/-/dotenv-safe-8.2.0.tgz#8d548c7318a62c09a66c4dc8c31864cc007c78ba" 703 | integrity sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA== 704 | dependencies: 705 | dotenv "^8.2.0" 706 | 707 | dotenv@^16.0.0: 708 | version "16.0.3" 709 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" 710 | integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== 711 | 712 | dotenv@^8.2.0: 713 | version "8.6.0" 714 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" 715 | integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== 716 | 717 | dynamic-dedupe@^0.3.0: 718 | version "0.3.0" 719 | resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1" 720 | integrity sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ== 721 | dependencies: 722 | xtend "^4.0.0" 723 | 724 | emoji-regex@^8.0.0: 725 | version "8.0.0" 726 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 727 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 728 | 729 | encoding@^0.1.13: 730 | version "0.1.13" 731 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" 732 | integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== 733 | dependencies: 734 | iconv-lite "^0.6.2" 735 | 736 | end-of-stream@^1.1.0: 737 | version "1.4.4" 738 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 739 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 740 | dependencies: 741 | once "^1.4.0" 742 | 743 | err-code@^2.0.2: 744 | version "2.0.3" 745 | resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" 746 | integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== 747 | 748 | escalade@^3.1.1: 749 | version "3.1.1" 750 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 751 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 752 | 753 | execa@^4.0.0: 754 | version "4.1.0" 755 | resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" 756 | integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== 757 | dependencies: 758 | cross-spawn "^7.0.0" 759 | get-stream "^5.0.0" 760 | human-signals "^1.1.1" 761 | is-stream "^2.0.0" 762 | merge-stream "^2.0.0" 763 | npm-run-path "^4.0.0" 764 | onetime "^5.1.0" 765 | signal-exit "^3.0.2" 766 | strip-final-newline "^2.0.0" 767 | 768 | fast-deep-equal@^3.1.3: 769 | version "3.1.3" 770 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 771 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 772 | 773 | file-type@^18.0.0: 774 | version "18.0.0" 775 | resolved "https://registry.yarnpkg.com/file-type/-/file-type-18.0.0.tgz#7a39378f8657ddc02807a0c62cb77cb4dc318197" 776 | integrity sha512-jjMwFpnW8PKofLE/4ohlhqwDk5k0NC6iy0UHAJFKoY1fQeGMN0GDdLgHQrvCbSpMwbqzoCZhRI5dETCZna5qVA== 777 | dependencies: 778 | readable-web-to-node-stream "^3.0.2" 779 | strtok3 "^7.0.0" 780 | token-types "^5.0.1" 781 | 782 | fill-range@^7.0.1: 783 | version "7.0.1" 784 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 785 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 786 | dependencies: 787 | to-regex-range "^5.0.1" 788 | 789 | find-up@^4.1.0: 790 | version "4.1.0" 791 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" 792 | integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== 793 | dependencies: 794 | locate-path "^5.0.0" 795 | path-exists "^4.0.0" 796 | 797 | form-data@^3.0.0: 798 | version "3.0.1" 799 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" 800 | integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== 801 | dependencies: 802 | asynckit "^0.4.0" 803 | combined-stream "^1.0.8" 804 | mime-types "^2.1.12" 805 | 806 | fs-minipass@^2.0.0, fs-minipass@^2.1.0: 807 | version "2.1.0" 808 | resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" 809 | integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== 810 | dependencies: 811 | minipass "^3.0.0" 812 | 813 | fs.realpath@^1.0.0: 814 | version "1.0.0" 815 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 816 | integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 817 | 818 | fsevents@~2.3.2: 819 | version "2.3.2" 820 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 821 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 822 | 823 | function-bind@^1.1.1: 824 | version "1.1.1" 825 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 826 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 827 | 828 | get-caller-file@^2.0.5: 829 | version "2.0.5" 830 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 831 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 832 | 833 | get-stream@^5.0.0: 834 | version "5.2.0" 835 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" 836 | integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== 837 | dependencies: 838 | pump "^3.0.0" 839 | 840 | glob-parent@~5.1.2: 841 | version "5.1.2" 842 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 843 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 844 | dependencies: 845 | is-glob "^4.0.1" 846 | 847 | glob@^7.1.3, glob@^7.2.0: 848 | version "7.2.3" 849 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" 850 | integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== 851 | dependencies: 852 | fs.realpath "^1.0.0" 853 | inflight "^1.0.4" 854 | inherits "2" 855 | minimatch "^3.1.1" 856 | once "^1.3.0" 857 | path-is-absolute "^1.0.0" 858 | 859 | glob@^8.0.1: 860 | version "8.0.3" 861 | resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" 862 | integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== 863 | dependencies: 864 | fs.realpath "^1.0.0" 865 | inflight "^1.0.4" 866 | inherits "2" 867 | minimatch "^5.0.1" 868 | once "^1.3.0" 869 | 870 | has-flag@^4.0.0: 871 | version "4.0.0" 872 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 873 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 874 | 875 | has@^1.0.3: 876 | version "1.0.3" 877 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 878 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 879 | dependencies: 880 | function-bind "^1.1.1" 881 | 882 | highlight.js@^10.7.1: 883 | version "10.7.3" 884 | resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" 885 | integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== 886 | 887 | hosted-git-info@^6.0.0: 888 | version "6.1.1" 889 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-6.1.1.tgz#629442c7889a69c05de604d52996b74fe6f26d58" 890 | integrity sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w== 891 | dependencies: 892 | lru-cache "^7.5.1" 893 | 894 | html-entities@^2.3.3: 895 | version "2.3.3" 896 | resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" 897 | integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== 898 | 899 | http-cache-semantics@^4.1.0: 900 | version "4.1.0" 901 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" 902 | integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== 903 | 904 | http-proxy-agent@^5.0.0: 905 | version "5.0.0" 906 | resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" 907 | integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== 908 | dependencies: 909 | "@tootallnate/once" "2" 910 | agent-base "6" 911 | debug "4" 912 | 913 | https-proxy-agent@^5.0.0: 914 | version "5.0.1" 915 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" 916 | integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== 917 | dependencies: 918 | agent-base "6" 919 | debug "4" 920 | 921 | human-signals@^1.1.1: 922 | version "1.1.1" 923 | resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" 924 | integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== 925 | 926 | humanize-ms@^1.2.1: 927 | version "1.2.1" 928 | resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" 929 | integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== 930 | dependencies: 931 | ms "^2.0.0" 932 | 933 | husky@^8.0.2: 934 | version "8.0.2" 935 | resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.2.tgz#5816a60db02650f1f22c8b69b928fd6bcd77a236" 936 | integrity sha512-Tkv80jtvbnkK3mYWxPZePGFpQ/tT3HNSs/sasF9P2YfkMezDl3ON37YN6jUUI4eTg5LcyVynlb6r4eyvOmspvg== 937 | 938 | iconv-lite@^0.6.2: 939 | version "0.6.3" 940 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" 941 | integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== 942 | dependencies: 943 | safer-buffer ">= 2.1.2 < 3.0.0" 944 | 945 | ieee754@^1.2.1: 946 | version "1.2.1" 947 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 948 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 949 | 950 | ignore@^5.1.4: 951 | version "5.2.0" 952 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" 953 | integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== 954 | 955 | imurmurhash@^0.1.4: 956 | version "0.1.4" 957 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 958 | integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== 959 | 960 | indent-string@^4.0.0: 961 | version "4.0.0" 962 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" 963 | integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== 964 | 965 | inflight@^1.0.4: 966 | version "1.0.6" 967 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 968 | integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== 969 | dependencies: 970 | once "^1.3.0" 971 | wrappy "1" 972 | 973 | inherits@2, inherits@^2.0.1, inherits@^2.0.3: 974 | version "2.0.4" 975 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 976 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 977 | 978 | ip@^2.0.0: 979 | version "2.0.0" 980 | resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" 981 | integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== 982 | 983 | is-binary-path@~2.1.0: 984 | version "2.1.0" 985 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 986 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 987 | dependencies: 988 | binary-extensions "^2.0.0" 989 | 990 | is-core-module@^2.9.0: 991 | version "2.11.0" 992 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" 993 | integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== 994 | dependencies: 995 | has "^1.0.3" 996 | 997 | is-extglob@^2.1.1: 998 | version "2.1.1" 999 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 1000 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 1001 | 1002 | is-fullwidth-code-point@^3.0.0: 1003 | version "3.0.0" 1004 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 1005 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 1006 | 1007 | is-glob@^4.0.1, is-glob@~4.0.1: 1008 | version "4.0.3" 1009 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 1010 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 1011 | dependencies: 1012 | is-extglob "^2.1.1" 1013 | 1014 | is-lambda@^1.0.1: 1015 | version "1.0.1" 1016 | resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" 1017 | integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== 1018 | 1019 | is-number@^7.0.0: 1020 | version "7.0.0" 1021 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 1022 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 1023 | 1024 | is-stream@^2.0.0: 1025 | version "2.0.1" 1026 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" 1027 | integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== 1028 | 1029 | isexe@^2.0.0: 1030 | version "2.0.0" 1031 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 1032 | integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== 1033 | 1034 | js-yaml@^4.1.0: 1035 | version "4.1.0" 1036 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" 1037 | integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== 1038 | dependencies: 1039 | argparse "^2.0.1" 1040 | 1041 | jsonparse@^1.3.1: 1042 | version "1.3.1" 1043 | resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" 1044 | integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== 1045 | 1046 | locate-path@^5.0.0: 1047 | version "5.0.0" 1048 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" 1049 | integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== 1050 | dependencies: 1051 | p-locate "^4.1.0" 1052 | 1053 | lodash.snakecase@^4.1.1: 1054 | version "4.1.1" 1055 | resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" 1056 | integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== 1057 | 1058 | lodash.uniqwith@^4.5.0: 1059 | version "4.5.0" 1060 | resolved "https://registry.yarnpkg.com/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz#7a0cbf65f43b5928625a9d4d0dc54b18cadc7ef3" 1061 | integrity sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q== 1062 | 1063 | lru-cache@^6.0.0: 1064 | version "6.0.0" 1065 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 1066 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 1067 | dependencies: 1068 | yallist "^4.0.0" 1069 | 1070 | lru-cache@^7.5.1, lru-cache@^7.7.1: 1071 | version "7.14.1" 1072 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.1.tgz#8da8d2f5f59827edb388e63e459ac23d6d408fea" 1073 | integrity sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA== 1074 | 1075 | lz-string@^1.4.4: 1076 | version "1.4.4" 1077 | resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" 1078 | integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ== 1079 | 1080 | make-error@^1.1.1: 1081 | version "1.3.6" 1082 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 1083 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 1084 | 1085 | make-fetch-happen@^11.0.0: 1086 | version "11.0.1" 1087 | resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-11.0.1.tgz#b3c51663d018d9e11d57fdd4393a4c5a1a7d56eb" 1088 | integrity sha512-clv3IblugXn2CDUmqFhNzii3rjKa46u5wNeivc+QlLXkGI5FjLX3rGboo+y2kwf1pd8W0iDiC384cemeDtw9kw== 1089 | dependencies: 1090 | agentkeepalive "^4.2.1" 1091 | cacache "^17.0.0" 1092 | http-cache-semantics "^4.1.0" 1093 | http-proxy-agent "^5.0.0" 1094 | https-proxy-agent "^5.0.0" 1095 | is-lambda "^1.0.1" 1096 | lru-cache "^7.7.1" 1097 | minipass "^3.1.6" 1098 | minipass-collect "^1.0.2" 1099 | minipass-fetch "^3.0.0" 1100 | minipass-flush "^1.0.5" 1101 | minipass-pipeline "^1.2.4" 1102 | negotiator "^0.6.3" 1103 | promise-retry "^2.0.1" 1104 | socks-proxy-agent "^7.0.0" 1105 | ssri "^10.0.0" 1106 | 1107 | merge-stream@^2.0.0: 1108 | version "2.0.0" 1109 | resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" 1110 | integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== 1111 | 1112 | mime-db@1.52.0: 1113 | version "1.52.0" 1114 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 1115 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 1116 | 1117 | mime-types@^2.1.12: 1118 | version "2.1.35" 1119 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 1120 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 1121 | dependencies: 1122 | mime-db "1.52.0" 1123 | 1124 | mimic-fn@^2.1.0: 1125 | version "2.1.0" 1126 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" 1127 | integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== 1128 | 1129 | minimatch@^3.0.4, minimatch@^3.1.1: 1130 | version "3.1.2" 1131 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 1132 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 1133 | dependencies: 1134 | brace-expansion "^1.1.7" 1135 | 1136 | minimatch@^5.0.1: 1137 | version "5.1.0" 1138 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" 1139 | integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== 1140 | dependencies: 1141 | brace-expansion "^2.0.1" 1142 | 1143 | minimist@^1.2.6: 1144 | version "1.2.7" 1145 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" 1146 | integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== 1147 | 1148 | minipass-collect@^1.0.2: 1149 | version "1.0.2" 1150 | resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" 1151 | integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== 1152 | dependencies: 1153 | minipass "^3.0.0" 1154 | 1155 | minipass-fetch@^3.0.0: 1156 | version "3.0.0" 1157 | resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.0.tgz#02481219ddbd3d30eb0e354016f680b10c6f2bcb" 1158 | integrity sha512-NSx3k5gR4Q5Ts2poCM/19d45VwhVLBtJZ6ypYcthj2BwmDx/e7lW8Aadnyt3edd2W0ecb+b0o7FYLRYE2AGcQg== 1159 | dependencies: 1160 | minipass "^3.1.6" 1161 | minipass-sized "^1.0.3" 1162 | minizlib "^2.1.2" 1163 | optionalDependencies: 1164 | encoding "^0.1.13" 1165 | 1166 | minipass-flush@^1.0.5: 1167 | version "1.0.5" 1168 | resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" 1169 | integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== 1170 | dependencies: 1171 | minipass "^3.0.0" 1172 | 1173 | minipass-json-stream@^1.0.1: 1174 | version "1.0.1" 1175 | resolved "https://registry.yarnpkg.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" 1176 | integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== 1177 | dependencies: 1178 | jsonparse "^1.3.1" 1179 | minipass "^3.0.0" 1180 | 1181 | minipass-pipeline@^1.2.4: 1182 | version "1.2.4" 1183 | resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" 1184 | integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== 1185 | dependencies: 1186 | minipass "^3.0.0" 1187 | 1188 | minipass-sized@^1.0.3: 1189 | version "1.0.3" 1190 | resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" 1191 | integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== 1192 | dependencies: 1193 | minipass "^3.0.0" 1194 | 1195 | minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: 1196 | version "3.3.4" 1197 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" 1198 | integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== 1199 | dependencies: 1200 | yallist "^4.0.0" 1201 | 1202 | minipass@^3.3.5: 1203 | version "3.3.5" 1204 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.5.tgz#6da7e53a48db8a856eeb9153d85b230a2119e819" 1205 | integrity sha512-rQ/p+KfKBkeNwo04U15i+hOwoVBVmekmm/HcfTkTN2t9pbQKCMm4eN5gFeqgrrSp/kH/7BYYhTIHOxGqzbBPaA== 1206 | dependencies: 1207 | yallist "^4.0.0" 1208 | 1209 | minizlib@^2.1.1, minizlib@^2.1.2: 1210 | version "2.1.2" 1211 | resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" 1212 | integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== 1213 | dependencies: 1214 | minipass "^3.0.0" 1215 | yallist "^4.0.0" 1216 | 1217 | mkdirp@^1.0.3, mkdirp@^1.0.4: 1218 | version "1.0.4" 1219 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" 1220 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== 1221 | 1222 | mri@^1.1.5: 1223 | version "1.2.0" 1224 | resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" 1225 | integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== 1226 | 1227 | ms@2.1.2: 1228 | version "2.1.2" 1229 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 1230 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 1231 | 1232 | ms@^2.0.0: 1233 | version "2.1.3" 1234 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 1235 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 1236 | 1237 | multimatch@^4.0.0: 1238 | version "4.0.0" 1239 | resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3" 1240 | integrity sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ== 1241 | dependencies: 1242 | "@types/minimatch" "^3.0.3" 1243 | array-differ "^3.0.0" 1244 | array-union "^2.1.0" 1245 | arrify "^2.0.1" 1246 | minimatch "^3.0.4" 1247 | 1248 | mz@^2.4.0: 1249 | version "2.7.0" 1250 | resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" 1251 | integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== 1252 | dependencies: 1253 | any-promise "^1.0.0" 1254 | object-assign "^4.0.1" 1255 | thenify-all "^1.0.0" 1256 | 1257 | negotiator@^0.6.3: 1258 | version "0.6.3" 1259 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" 1260 | integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== 1261 | 1262 | normalize-path@^3.0.0, normalize-path@~3.0.0: 1263 | version "3.0.0" 1264 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 1265 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 1266 | 1267 | npm-package-arg@^10.0.0: 1268 | version "10.0.0" 1269 | resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-10.0.0.tgz#a34f4a4208a937074b1fff0943a684fbacc83977" 1270 | integrity sha512-7dkh8mRp7s0KwVHKIVJnFCJQ2B34gOGnzgBjDGyprycmARq/82SX/lhilQ95ZuacP/G/1gsS345iAkKmxWBQ2Q== 1271 | dependencies: 1272 | hosted-git-info "^6.0.0" 1273 | proc-log "^3.0.0" 1274 | semver "^7.3.5" 1275 | validate-npm-package-name "^5.0.0" 1276 | 1277 | npm-registry-fetch@^14.0.2: 1278 | version "14.0.2" 1279 | resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-14.0.2.tgz#f637630d9005aeebe4d7411226fb11fa1628c5e8" 1280 | integrity sha512-TMenrMagFA9KF81E2bkS5XRyzERK4KXu70vgXt5+i8FcrFeLNgNsc6e5hekTqjDwPDkL3HGn/holWcXDMfnFgw== 1281 | dependencies: 1282 | make-fetch-happen "^11.0.0" 1283 | minipass "^3.1.6" 1284 | minipass-fetch "^3.0.0" 1285 | minipass-json-stream "^1.0.1" 1286 | minizlib "^2.1.2" 1287 | npm-package-arg "^10.0.0" 1288 | proc-log "^3.0.0" 1289 | 1290 | npm-run-path@^4.0.0: 1291 | version "4.0.1" 1292 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" 1293 | integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== 1294 | dependencies: 1295 | path-key "^3.0.0" 1296 | 1297 | object-assign@^4.0.1: 1298 | version "4.1.1" 1299 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 1300 | integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== 1301 | 1302 | once@^1.3.0, once@^1.3.1, once@^1.4.0: 1303 | version "1.4.0" 1304 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 1305 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 1306 | dependencies: 1307 | wrappy "1" 1308 | 1309 | onetime@^5.1.0: 1310 | version "5.1.2" 1311 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" 1312 | integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== 1313 | dependencies: 1314 | mimic-fn "^2.1.0" 1315 | 1316 | p-limit@^2.2.0: 1317 | version "2.3.0" 1318 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" 1319 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== 1320 | dependencies: 1321 | p-try "^2.0.0" 1322 | 1323 | p-locate@^4.1.0: 1324 | version "4.1.0" 1325 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" 1326 | integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== 1327 | dependencies: 1328 | p-limit "^2.2.0" 1329 | 1330 | p-map@^4.0.0: 1331 | version "4.0.0" 1332 | resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" 1333 | integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== 1334 | dependencies: 1335 | aggregate-error "^3.0.0" 1336 | 1337 | p-try@^2.0.0: 1338 | version "2.2.0" 1339 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 1340 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 1341 | 1342 | packet-reader@1.0.0: 1343 | version "1.0.0" 1344 | resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" 1345 | integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== 1346 | 1347 | parse-duration@^1.0.2: 1348 | version "1.0.2" 1349 | resolved "https://registry.yarnpkg.com/parse-duration/-/parse-duration-1.0.2.tgz#b9aa7d3a1363cc7e8845bea8fd3baf8a11df5805" 1350 | integrity sha512-Dg27N6mfok+ow1a2rj/nRjtCfaKrHUZV2SJpEn/s8GaVUSlf4GGRCRP1c13Hj+wfPKVMrFDqLMLITkYKgKxyyg== 1351 | 1352 | parse-ms@^3.0.0: 1353 | version "3.0.0" 1354 | resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-3.0.0.tgz#3ea24a934913345fcc3656deda72df921da3a70e" 1355 | integrity sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw== 1356 | 1357 | parse5-htmlparser2-tree-adapter@^6.0.0: 1358 | version "6.0.1" 1359 | resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" 1360 | integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== 1361 | dependencies: 1362 | parse5 "^6.0.1" 1363 | 1364 | parse5@^5.1.1: 1365 | version "5.1.1" 1366 | resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" 1367 | integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== 1368 | 1369 | parse5@^6.0.1: 1370 | version "6.0.1" 1371 | resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" 1372 | integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== 1373 | 1374 | path-exists@^4.0.0: 1375 | version "4.0.0" 1376 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 1377 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 1378 | 1379 | path-is-absolute@^1.0.0: 1380 | version "1.0.1" 1381 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 1382 | integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== 1383 | 1384 | path-key@^3.0.0, path-key@^3.1.0: 1385 | version "3.1.1" 1386 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" 1387 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== 1388 | 1389 | path-parse@^1.0.7: 1390 | version "1.0.7" 1391 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 1392 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 1393 | 1394 | peek-readable@^5.0.0: 1395 | version "5.0.0" 1396 | resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.0.0.tgz#7ead2aff25dc40458c60347ea76cfdfd63efdfec" 1397 | integrity sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A== 1398 | 1399 | pg-connection-string@^2.5.0: 1400 | version "2.5.0" 1401 | resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" 1402 | integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== 1403 | 1404 | pg-int8@1.0.1: 1405 | version "1.0.1" 1406 | resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" 1407 | integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== 1408 | 1409 | pg-pool@^3.5.2: 1410 | version "3.5.2" 1411 | resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.5.2.tgz#ed1bed1fb8d79f1c6fd5fb1c99e990fbf9ddf178" 1412 | integrity sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w== 1413 | 1414 | pg-protocol@^1.5.0: 1415 | version "1.5.0" 1416 | resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0" 1417 | integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== 1418 | 1419 | pg-types@^2.1.0: 1420 | version "2.2.0" 1421 | resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" 1422 | integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== 1423 | dependencies: 1424 | pg-int8 "1.0.1" 1425 | postgres-array "~2.0.0" 1426 | postgres-bytea "~1.0.0" 1427 | postgres-date "~1.0.4" 1428 | postgres-interval "^1.1.0" 1429 | 1430 | pg@^8.8.0: 1431 | version "8.8.0" 1432 | resolved "https://registry.yarnpkg.com/pg/-/pg-8.8.0.tgz#a77f41f9d9ede7009abfca54667c775a240da686" 1433 | integrity sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw== 1434 | dependencies: 1435 | buffer-writer "2.0.0" 1436 | packet-reader "1.0.0" 1437 | pg-connection-string "^2.5.0" 1438 | pg-pool "^3.5.2" 1439 | pg-protocol "^1.5.0" 1440 | pg-types "^2.1.0" 1441 | pgpass "1.x" 1442 | 1443 | pgpass@1.x: 1444 | version "1.0.5" 1445 | resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" 1446 | integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== 1447 | dependencies: 1448 | split2 "^4.1.0" 1449 | 1450 | picomatch@^2.0.4, picomatch@^2.2.1: 1451 | version "2.3.1" 1452 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 1453 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 1454 | 1455 | postgres-array@~2.0.0: 1456 | version "2.0.0" 1457 | resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" 1458 | integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== 1459 | 1460 | postgres-bytea@~1.0.0: 1461 | version "1.0.0" 1462 | resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" 1463 | integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== 1464 | 1465 | postgres-date@~1.0.4: 1466 | version "1.0.7" 1467 | resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" 1468 | integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== 1469 | 1470 | postgres-interval@^1.1.0: 1471 | version "1.2.0" 1472 | resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" 1473 | integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== 1474 | dependencies: 1475 | xtend "^4.0.0" 1476 | 1477 | prettier@^2.7.1: 1478 | version "2.7.1" 1479 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" 1480 | integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== 1481 | 1482 | pretty-ms@^8.0.0: 1483 | version "8.0.0" 1484 | resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-8.0.0.tgz#a35563b2a02df01e595538f86d7de54ca23194a3" 1485 | integrity sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q== 1486 | dependencies: 1487 | parse-ms "^3.0.0" 1488 | 1489 | pretty-quick@^3.1.3: 1490 | version "3.1.3" 1491 | resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-3.1.3.tgz#15281108c0ddf446675157ca40240099157b638e" 1492 | integrity sha512-kOCi2FJabvuh1as9enxYmrnBC6tVMoVOenMaBqRfsvBHB0cbpYHjdQEpSglpASDFEXVwplpcGR4CLEaisYAFcA== 1493 | dependencies: 1494 | chalk "^3.0.0" 1495 | execa "^4.0.0" 1496 | find-up "^4.1.0" 1497 | ignore "^5.1.4" 1498 | mri "^1.1.5" 1499 | multimatch "^4.0.0" 1500 | 1501 | proc-log@^3.0.0: 1502 | version "3.0.0" 1503 | resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8" 1504 | integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A== 1505 | 1506 | promise-inflight@^1.0.1: 1507 | version "1.0.1" 1508 | resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" 1509 | integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== 1510 | 1511 | promise-retry@^2.0.1: 1512 | version "2.0.1" 1513 | resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" 1514 | integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== 1515 | dependencies: 1516 | err-code "^2.0.2" 1517 | retry "^0.12.0" 1518 | 1519 | pump@^3.0.0: 1520 | version "3.0.0" 1521 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 1522 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 1523 | dependencies: 1524 | end-of-stream "^1.1.0" 1525 | once "^1.3.1" 1526 | 1527 | readable-stream@^3.6.0: 1528 | version "3.6.0" 1529 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 1530 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 1531 | dependencies: 1532 | inherits "^2.0.3" 1533 | string_decoder "^1.1.1" 1534 | util-deprecate "^1.0.1" 1535 | 1536 | readable-web-to-node-stream@^3.0.2: 1537 | version "3.0.2" 1538 | resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" 1539 | integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== 1540 | dependencies: 1541 | readable-stream "^3.6.0" 1542 | 1543 | readdirp@~3.6.0: 1544 | version "3.6.0" 1545 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 1546 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 1547 | dependencies: 1548 | picomatch "^2.2.1" 1549 | 1550 | reflect-metadata@^0.1.13: 1551 | version "0.1.13" 1552 | resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" 1553 | integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== 1554 | 1555 | require-directory@^2.1.1: 1556 | version "2.1.1" 1557 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 1558 | integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== 1559 | 1560 | resolve@^1.0.0: 1561 | version "1.22.1" 1562 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" 1563 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== 1564 | dependencies: 1565 | is-core-module "^2.9.0" 1566 | path-parse "^1.0.7" 1567 | supports-preserve-symlinks-flag "^1.0.0" 1568 | 1569 | retry@^0.12.0: 1570 | version "0.12.0" 1571 | resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" 1572 | integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== 1573 | 1574 | rimraf@^2.6.1: 1575 | version "2.7.1" 1576 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" 1577 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== 1578 | dependencies: 1579 | glob "^7.1.3" 1580 | 1581 | safe-buffer@^5.0.1, safe-buffer@~5.2.0: 1582 | version "5.2.1" 1583 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 1584 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 1585 | 1586 | "safer-buffer@>= 2.1.2 < 3.0.0": 1587 | version "2.1.2" 1588 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1589 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 1590 | 1591 | sax@>=0.6.0: 1592 | version "1.2.4" 1593 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 1594 | integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== 1595 | 1596 | semver@^7.0.0, semver@^7.3.5: 1597 | version "7.3.8" 1598 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" 1599 | integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== 1600 | dependencies: 1601 | lru-cache "^6.0.0" 1602 | 1603 | sha.js@^2.4.11: 1604 | version "2.4.11" 1605 | resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" 1606 | integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== 1607 | dependencies: 1608 | inherits "^2.0.1" 1609 | safe-buffer "^5.0.1" 1610 | 1611 | shebang-command@^2.0.0: 1612 | version "2.0.0" 1613 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" 1614 | integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== 1615 | dependencies: 1616 | shebang-regex "^3.0.0" 1617 | 1618 | shebang-regex@^3.0.0: 1619 | version "3.0.0" 1620 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 1621 | integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 1622 | 1623 | signal-exit@^3.0.2: 1624 | version "3.0.7" 1625 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" 1626 | integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== 1627 | 1628 | smart-buffer@^4.2.0: 1629 | version "4.2.0" 1630 | resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" 1631 | integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== 1632 | 1633 | socks-proxy-agent@^7.0.0: 1634 | version "7.0.0" 1635 | resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" 1636 | integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== 1637 | dependencies: 1638 | agent-base "^6.0.2" 1639 | debug "^4.3.3" 1640 | socks "^2.6.2" 1641 | 1642 | socks@^2.6.2: 1643 | version "2.7.1" 1644 | resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" 1645 | integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== 1646 | dependencies: 1647 | ip "^2.0.0" 1648 | smart-buffer "^4.2.0" 1649 | 1650 | source-map-support@^0.5.12: 1651 | version "0.5.21" 1652 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 1653 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 1654 | dependencies: 1655 | buffer-from "^1.0.0" 1656 | source-map "^0.6.0" 1657 | 1658 | source-map@^0.6.0: 1659 | version "0.6.1" 1660 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 1661 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 1662 | 1663 | split2@^4.1.0: 1664 | version "4.1.0" 1665 | resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" 1666 | integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== 1667 | 1668 | ssri@^10.0.0: 1669 | version "10.0.0" 1670 | resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.0.tgz#1e34554cbbc4728f5290674264e21b64aaf27ca7" 1671 | integrity sha512-64ghGOpqW0k+jh7m5jndBGdVEoPikWwGQmBNN5ks6jyUSMymzHDTlnNHOvzp+6MmHOljr2MokUzvRksnTwG0Iw== 1672 | dependencies: 1673 | minipass "^3.1.1" 1674 | 1675 | streamsearch@^1.1.0: 1676 | version "1.1.0" 1677 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" 1678 | integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== 1679 | 1680 | string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: 1681 | version "4.2.3" 1682 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 1683 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 1684 | dependencies: 1685 | emoji-regex "^8.0.0" 1686 | is-fullwidth-code-point "^3.0.0" 1687 | strip-ansi "^6.0.1" 1688 | 1689 | string_decoder@^1.1.1: 1690 | version "1.3.0" 1691 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 1692 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 1693 | dependencies: 1694 | safe-buffer "~5.2.0" 1695 | 1696 | strip-ansi@^6.0.0, strip-ansi@^6.0.1: 1697 | version "6.0.1" 1698 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 1699 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 1700 | dependencies: 1701 | ansi-regex "^5.0.1" 1702 | 1703 | strip-bom@^3.0.0: 1704 | version "3.0.0" 1705 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 1706 | integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== 1707 | 1708 | strip-final-newline@^2.0.0: 1709 | version "2.0.0" 1710 | resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" 1711 | integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== 1712 | 1713 | strip-json-comments@^2.0.0: 1714 | version "2.0.1" 1715 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1716 | integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== 1717 | 1718 | strtok3@^7.0.0: 1719 | version "7.0.0" 1720 | resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-7.0.0.tgz#868c428b4ade64a8fd8fee7364256001c1a4cbe5" 1721 | integrity sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ== 1722 | dependencies: 1723 | "@tokenizer/token" "^0.3.0" 1724 | peek-readable "^5.0.0" 1725 | 1726 | supports-color@^7.1.0: 1727 | version "7.2.0" 1728 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 1729 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 1730 | dependencies: 1731 | has-flag "^4.0.0" 1732 | 1733 | supports-preserve-symlinks-flag@^1.0.0: 1734 | version "1.0.0" 1735 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 1736 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 1737 | 1738 | tar@^6.1.11, tar@^6.1.12: 1739 | version "6.1.12" 1740 | resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.12.tgz#3b742fb05669b55671fb769ab67a7791ea1a62e6" 1741 | integrity sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw== 1742 | dependencies: 1743 | chownr "^2.0.0" 1744 | fs-minipass "^2.0.0" 1745 | minipass "^3.0.0" 1746 | minizlib "^2.1.1" 1747 | mkdirp "^1.0.3" 1748 | yallist "^4.0.0" 1749 | 1750 | thenify-all@^1.0.0: 1751 | version "1.6.0" 1752 | resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" 1753 | integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== 1754 | dependencies: 1755 | thenify ">= 3.1.0 < 4" 1756 | 1757 | "thenify@>= 3.1.0 < 4": 1758 | version "3.3.1" 1759 | resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" 1760 | integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== 1761 | dependencies: 1762 | any-promise "^1.0.0" 1763 | 1764 | to-regex-range@^5.0.1: 1765 | version "5.0.1" 1766 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1767 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1768 | dependencies: 1769 | is-number "^7.0.0" 1770 | 1771 | token-types@^5.0.1: 1772 | version "5.0.1" 1773 | resolved "https://registry.yarnpkg.com/token-types/-/token-types-5.0.1.tgz#aa9d9e6b23c420a675e55413b180635b86a093b4" 1774 | integrity sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg== 1775 | dependencies: 1776 | "@tokenizer/token" "^0.3.0" 1777 | ieee754 "^1.2.1" 1778 | 1779 | tree-kill@^1.2.2: 1780 | version "1.2.2" 1781 | resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" 1782 | integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== 1783 | 1784 | ts-mixer@^6.0.1: 1785 | version "6.0.2" 1786 | resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.2.tgz#3e4e4bb8daffb24435f6980b15204cb5b287e016" 1787 | integrity sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A== 1788 | 1789 | ts-node-dev@^2.0.0: 1790 | version "2.0.0" 1791 | resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-2.0.0.tgz#bdd53e17ab3b5d822ef519928dc6b4a7e0f13065" 1792 | integrity sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w== 1793 | dependencies: 1794 | chokidar "^3.5.1" 1795 | dynamic-dedupe "^0.3.0" 1796 | minimist "^1.2.6" 1797 | mkdirp "^1.0.4" 1798 | resolve "^1.0.0" 1799 | rimraf "^2.6.1" 1800 | source-map-support "^0.5.12" 1801 | tree-kill "^1.2.2" 1802 | ts-node "^10.4.0" 1803 | tsconfig "^7.0.0" 1804 | 1805 | ts-node@^10.4.0: 1806 | version "10.9.1" 1807 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" 1808 | integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== 1809 | dependencies: 1810 | "@cspotcode/source-map-support" "^0.8.0" 1811 | "@tsconfig/node10" "^1.0.7" 1812 | "@tsconfig/node12" "^1.0.7" 1813 | "@tsconfig/node14" "^1.0.0" 1814 | "@tsconfig/node16" "^1.0.2" 1815 | acorn "^8.4.1" 1816 | acorn-walk "^8.1.1" 1817 | arg "^4.1.0" 1818 | create-require "^1.1.0" 1819 | diff "^4.0.1" 1820 | make-error "^1.1.1" 1821 | v8-compile-cache-lib "^3.0.1" 1822 | yn "3.1.1" 1823 | 1824 | tsconfig@^7.0.0: 1825 | version "7.0.0" 1826 | resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" 1827 | integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== 1828 | dependencies: 1829 | "@types/strip-bom" "^3.0.0" 1830 | "@types/strip-json-comments" "0.0.30" 1831 | strip-bom "^3.0.0" 1832 | strip-json-comments "^2.0.0" 1833 | 1834 | tslib@^2.3.1, tslib@^2.4.0: 1835 | version "2.4.1" 1836 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" 1837 | integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== 1838 | 1839 | typeorm@^0.3.10: 1840 | version "0.3.10" 1841 | resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.10.tgz#aa2857fd4b078c912ca693b7eee01b6535704458" 1842 | integrity sha512-VMKiM84EpJQ+Mz9xDIPqnfplWhyUy1d8ccaKdMY9obifxJOTFnv8GYVyPsGwG8Lk7Nb8MlttHyHWENGAhBA3WA== 1843 | dependencies: 1844 | "@sqltools/formatter" "^1.2.2" 1845 | app-root-path "^3.0.0" 1846 | buffer "^6.0.3" 1847 | chalk "^4.1.0" 1848 | cli-highlight "^2.1.11" 1849 | date-fns "^2.28.0" 1850 | debug "^4.3.3" 1851 | dotenv "^16.0.0" 1852 | glob "^7.2.0" 1853 | js-yaml "^4.1.0" 1854 | mkdirp "^1.0.4" 1855 | reflect-metadata "^0.1.13" 1856 | sha.js "^2.4.11" 1857 | tslib "^2.3.1" 1858 | uuid "^8.3.2" 1859 | xml2js "^0.4.23" 1860 | yargs "^17.3.1" 1861 | 1862 | typescript@^4.9.3: 1863 | version "4.9.3" 1864 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" 1865 | integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA== 1866 | 1867 | undici@^5.11.0, undici@^5.12.0: 1868 | version "5.12.0" 1869 | resolved "https://registry.yarnpkg.com/undici/-/undici-5.12.0.tgz#c758ffa704fbcd40d506e4948860ccaf4099f531" 1870 | integrity sha512-zMLamCG62PGjd9HHMpo05bSLvvwWOZgGeiWlN/vlqu3+lRo3elxktVGEyLMX+IO7c2eflLjcW74AlkhEZm15mg== 1871 | dependencies: 1872 | busboy "^1.6.0" 1873 | 1874 | unique-filename@^3.0.0: 1875 | version "3.0.0" 1876 | resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea" 1877 | integrity sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g== 1878 | dependencies: 1879 | unique-slug "^4.0.0" 1880 | 1881 | unique-slug@^4.0.0: 1882 | version "4.0.0" 1883 | resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz#6bae6bb16be91351badd24cdce741f892a6532e3" 1884 | integrity sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ== 1885 | dependencies: 1886 | imurmurhash "^0.1.4" 1887 | 1888 | util-deprecate@^1.0.1: 1889 | version "1.0.2" 1890 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 1891 | integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== 1892 | 1893 | uuid@^8.3.2: 1894 | version "8.3.2" 1895 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" 1896 | integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== 1897 | 1898 | v8-compile-cache-lib@^3.0.1: 1899 | version "3.0.1" 1900 | resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" 1901 | integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== 1902 | 1903 | validate-npm-package-name@^5.0.0: 1904 | version "5.0.0" 1905 | resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz#f16afd48318e6f90a1ec101377fa0384cfc8c713" 1906 | integrity sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ== 1907 | dependencies: 1908 | builtins "^5.0.0" 1909 | 1910 | which@^2.0.1: 1911 | version "2.0.2" 1912 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 1913 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 1914 | dependencies: 1915 | isexe "^2.0.0" 1916 | 1917 | wrap-ansi@^7.0.0: 1918 | version "7.0.0" 1919 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 1920 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 1921 | dependencies: 1922 | ansi-styles "^4.0.0" 1923 | string-width "^4.1.0" 1924 | strip-ansi "^6.0.0" 1925 | 1926 | wrappy@1: 1927 | version "1.0.2" 1928 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1929 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 1930 | 1931 | ws@^8.9.0: 1932 | version "8.11.0" 1933 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" 1934 | integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== 1935 | 1936 | xml2js@^0.4.23: 1937 | version "0.4.23" 1938 | resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" 1939 | integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== 1940 | dependencies: 1941 | sax ">=0.6.0" 1942 | xmlbuilder "~11.0.0" 1943 | 1944 | xmlbuilder@~11.0.0: 1945 | version "11.0.1" 1946 | resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" 1947 | integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== 1948 | 1949 | xtend@^4.0.0: 1950 | version "4.0.2" 1951 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 1952 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 1953 | 1954 | y18n@^5.0.5: 1955 | version "5.0.8" 1956 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" 1957 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== 1958 | 1959 | yallist@^4.0.0: 1960 | version "4.0.0" 1961 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 1962 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 1963 | 1964 | yargs-parser@^20.2.2: 1965 | version "20.2.9" 1966 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" 1967 | integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== 1968 | 1969 | yargs-parser@^21.1.1: 1970 | version "21.1.1" 1971 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" 1972 | integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== 1973 | 1974 | yargs@^16.0.0: 1975 | version "16.2.0" 1976 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" 1977 | integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== 1978 | dependencies: 1979 | cliui "^7.0.2" 1980 | escalade "^3.1.1" 1981 | get-caller-file "^2.0.5" 1982 | require-directory "^2.1.1" 1983 | string-width "^4.2.0" 1984 | y18n "^5.0.5" 1985 | yargs-parser "^20.2.2" 1986 | 1987 | yargs@^17.3.1: 1988 | version "17.6.2" 1989 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" 1990 | integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== 1991 | dependencies: 1992 | cliui "^8.0.1" 1993 | escalade "^3.1.1" 1994 | get-caller-file "^2.0.5" 1995 | require-directory "^2.1.1" 1996 | string-width "^4.2.3" 1997 | y18n "^5.0.5" 1998 | yargs-parser "^21.1.1" 1999 | 2000 | yn@3.1.1: 2001 | version "3.1.1" 2002 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 2003 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 2004 | --------------------------------------------------------------------------------