From 80c28510bc446c255e15117ad206d41f5bc2b231 Mon Sep 17 00:00:00 2001 From: MatthieuCoder Date: Tue, 3 Jan 2023 14:21:17 +0400 Subject: [PATCH] first commit --- .gitignore | 4 + LICENCE | 201 +++++++++++++++++++++++++++ package.json | 28 ++++ src/commands/index.ts | 3 + src/commands/ping.ts | 86 ++++++++++++ src/events/client.ts | 33 +++++ src/events/event-emitter.ts | 51 +++++++ src/events/index.ts | 1 + src/events/transport.ts | 111 +++++++++++++++ src/handler/builder.ts | 19 +++ src/handler/index.ts | 57 ++++++++ src/index.ts | 52 +++++++ src/register.ts | 5 + src/rest.ts | 8 ++ tsconfig.json | 8 ++ yarn.lock | 270 ++++++++++++++++++++++++++++++++++++ 16 files changed, 937 insertions(+) create mode 100644 .gitignore create mode 100644 LICENCE create mode 100644 package.json create mode 100644 src/commands/index.ts create mode 100644 src/commands/ping.ts create mode 100644 src/events/client.ts create mode 100644 src/events/event-emitter.ts create mode 100644 src/events/index.ts create mode 100644 src/events/transport.ts create mode 100644 src/handler/builder.ts create mode 100644 src/handler/index.ts create mode 100644 src/index.ts create mode 100644 src/register.ts create mode 100644 src/rest.ts create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2de364 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +config/ +dist/ +bin/ +node_modules/ \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..a513140 --- /dev/null +++ b/LICENCE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021-2023 Matthieu Pignolet, Nicolas Paul, Max Charrier + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..5564701 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "@discordnova/nova-quickstart", + "version": "1.0.0", + "description": "Simple example of a nova deployment", + "main": "index.js", + "repository": "https://github.com/discordnova/nova-quickstart", + "author": "MatthieuCoder", + "license": "MIT", + "dependencies": { + "@discordjs/builders": "^1.4.0", + "@discordjs/rest": "^1.5.0", + "@types/node": "^18.11.18", + "@types/ping": "^0.4.1", + "discord-api-types": "^0.37.25", + "events": "^3.3.0", + "glob-regex": "^0.3.2", + "nats": "^2.10.2", + "ping": "^0.4.2", + "source-map-support": "^0.5.21", + "type-fest": "^3.5.0", + "typescript": "^4.9.4" + }, + "scripts": { + "build": "tsc", + "start": "tsc || true && node dist", + "register": "tsc || true && node dist/register.js" + } +} diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..366e781 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,3 @@ +import { ping } from "./ping"; + +export const commands = [ping]; diff --git a/src/commands/ping.ts b/src/commands/ping.ts new file mode 100644 index 0000000..9bf6bc0 --- /dev/null +++ b/src/commands/ping.ts @@ -0,0 +1,86 @@ +import { + APIApplicationCommandInteraction, + APIInteractionResponse, + ApplicationCommandOptionType, + ApplicationCommandType, + InteractionResponseType, +} from "discord-api-types/v10"; +import { CommandBuilder, HandlerFn } from "../handler"; +import { promise } from "ping"; + +type Messages = { + latency_message: [number]; + failure_message: [string]; +}; + +type Locales = { + [K in keyof Messages]: Partial< + Record< + APIApplicationCommandInteraction["locale"] & "_", + (...args: Messages[K]) => string + > + >; +}; + +const locales: Locales = { + latency_message: { + fr: (latency: number) => + `**Temps de complétion du ping:** \`${latency}ms\``, + _: (latency: number) => `**Ping time:** \`${latency}ms\``, + }, + failure_message: { + _: (error: string) => `**The host seems unreachable** \`\`\`${error}\`\`\``, + }, +}; + +const resolve = ( + msg: keyof Messages, + locale: APIApplicationCommandInteraction["locale"], + ...args: Messages[typeof msg] +) => (locales[msg][locale] ?? locales[msg]["_"])(args); + +const handler: HandlerFn = async ( + data: APIApplicationCommandInteraction +): Promise => { + if ( + data.data.type === ApplicationCommandType.ChatInput && + data.data.options[0].name === "host" && + data.data.options[0].type === ApplicationCommandOptionType.String + ) { + let host = data.data.options[0].value; + let response = await promise.probe(host); + + if (response.alive) { + return { + type: InteractionResponseType.ChannelMessageWithSource, + data: { + content: resolve("latency_message", data.locale, response.avg), + }, + }; + } else { + let output = response.output; + return { + type: InteractionResponseType.ChannelMessageWithSource, + data: { + content: resolve("failure_message", data.locale, output), + }, + }; + } + } +}; + +export const ping = new CommandBuilder() + .setName("ping") + .handler(handler) + .setDescription("Measure the bot network latency") + .addStringOption((option) => + option + .setName("host") + .setDescription("The host to test pings against") + .setRequired(true) + ) + .setDescriptionLocalizations({ + fr: "Mesure la latence reseau du bot", + }) + .setDMPermission(false) + .build(); diff --git a/src/events/client.ts b/src/events/client.ts new file mode 100644 index 0000000..b4045a0 --- /dev/null +++ b/src/events/client.ts @@ -0,0 +1,33 @@ +import { GatewayDispatchPayload } from "discord-api-types/v10"; +import { BaseEventEmitter, HandlerFunction } from "./event-emitter"; +import { Transport, TransportOptions } from "./transport"; +import { CamelCase } from "type-fest"; + +type ExtractEvent = Extract< + O & { t: Exclude> }, + { t: U } +>; + +export type Events = { + [P in GatewayDispatchPayload["t"] as `${CamelCase

}` | P]: [ + ExtractEvent["d"] + ]; +}; + +export class EventClient extends BaseEventEmitter { + public transport: Transport; + // constructs + constructor() { + super(); + this.transport = new Transport(this); + } + + public async start(options: TransportOptions) { + await this.transport.start(options); + } + + on(name: K, fn: HandlerFunction): this { + this.transport.subscribe(name); + return super.on(name, fn); + } +} diff --git a/src/events/event-emitter.ts b/src/events/event-emitter.ts new file mode 100644 index 0000000..8a106c9 --- /dev/null +++ b/src/events/event-emitter.ts @@ -0,0 +1,51 @@ +import { EventEmitter } from "events"; +import { PascalCase } from "type-fest"; +import { Events } from "."; + +export type HandlerFunction = ( + ...args: [...Args, ...[resolve?: (data: object) => void]] +) => unknown | Promise; + +export type EventsFunctions = { + [P in keyof Events as P extends string ? `on${PascalCase

}` : never]: ( + fn: HandlerFunction + ) => BaseEventEmitter; +}; + +// Typings for the EventClient +export interface BaseEventEmitter extends EventEmitter { + addListener( + name: K, + fn: HandlerFunction + ): this; + + on(name: K, fn: HandlerFunction): this; + + once(name: K, fn: HandlerFunction): this; + + off(name: K, fn: HandlerFunction): this; + + prependListener( + name: K, + fn: HandlerFunction + ): this; + + prependOnceListener( + name: K, + fn: HandlerFunction + ): this; + + removeAllListeners(eventName: keyof Events | undefined): this; + removeListener(eventName: keyof Events): this; + + emit(name: T, ...args: Events[T]): boolean; + listenerCount(event: keyof Events): number; + listeners(event: T): HandlerFunction[]; + rawListeners: this["listeners"]; +} + +export class BaseEventEmitter extends EventEmitter implements BaseEventEmitter { + constructor() { + super(); + } +} diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 0000000..5ec7692 --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1 @@ +export * from "./client"; diff --git a/src/events/transport.ts b/src/events/transport.ts new file mode 100644 index 0000000..4f2abd6 --- /dev/null +++ b/src/events/transport.ts @@ -0,0 +1,111 @@ +import { connect, ConnectionOptions, NatsConnection } from "nats"; +import { EventClient, Events } from "."; +import globRegex from "glob-regex"; + +export type TransportOptions = { + additionalEvents?: (keyof Events)[]; + nats?: ConnectionOptions; + queue: string; +}; +export class Transport { + private nats: NatsConnection | null = null; + private subscription: Map = new Map(); + private queue?: string; + private events: Set = new Set(); + + constructor(private emitter: EventClient) {} + + public async start(options: TransportOptions) { + this.nats = await connect(options?.nats); + this.queue = options.queue; + if (options.additionalEvents) { + options.additionalEvents.forEach((a) => this.events.add(a)); + } + + let initial_events = [...this.events]; + + for (let subscription of initial_events) { + await this.subscribe(subscription); + } + } + + public async subscribe(event: string) { + if (!this.nats) { + console.log("Requesting event " + event); + this.events.add(event); + return; + } + let dashed = event.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase()); + event = `nova.cache.dispatch.${dashed.toUpperCase()}`; + + let isAlreadyPresent = [...this.subscription.keys()].reduce( + (previous, current) => { + if (previous) return previous; + let regex = globRegex(current); + + return regex.test(event); + }, + false + ); + + if (isAlreadyPresent) { + console.warn("nats subscription already covered."); + return; + } + + let regex = globRegex(event); + [...this.subscription.keys()].map((key) => { + if (regex.test(key)) { + let v = this.subscription.get(key); + if (!v) { + return; + } + + console.log(`unsubscribing from ${key}`); + v(); + } + }); + + this._subTask(event, this.queue || "default_queue"); + } + + private async _subTask(event: string, queue: string) { + if (!this.nats) { + throw new Error("nats transporter is not started."); + } + + console.log(`subscribing to ${event}`); + let resolve: Function = () => {}; + let task = new Promise((r) => { + resolve = r; + }); + let sub = this.nats.subscribe(event, { queue: "" }); + + const fn = async () => { + for await (let data of sub) { + let string = Buffer.from(data.data).toString("utf-8"); + let d = JSON.parse(string); + let respond: Function | null = null; + + if (data.reply) { + console.log("expecting reply."); + respond = (d: object) => { + data.respond(Buffer.from(JSON.stringify(d), "utf-8")); + }; + } + const camelCased = d.t.toLowerCase().replace(/_([a-z])/g, function (g) { + return g[1].toUpperCase(); + }); + console.log("envoi de ", camelCased); + this.emitter.emit(camelCased, d.d, respond); + } + }; + this.subscription.set(event, resolve); + + await Promise.race([task, fn()]); + + console.log(`finished task for ${event}`); + sub.unsubscribe(); + this.subscription.delete(event); + } +} diff --git a/src/handler/builder.ts b/src/handler/builder.ts new file mode 100644 index 0000000..148542a --- /dev/null +++ b/src/handler/builder.ts @@ -0,0 +1,19 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { Command, HandlerFn } from "."; + +export class CommandBuilder extends SlashCommandBuilder { + private _handler: HandlerFn; + + constructor() { + super(); + } + + handler(handler: HandlerFn): this { + this._handler = handler; + return this; + } + + build(): Command { + return { json: this.toJSON(), handler: this._handler }; + } +} diff --git a/src/handler/index.ts b/src/handler/index.ts new file mode 100644 index 0000000..8126717 --- /dev/null +++ b/src/handler/index.ts @@ -0,0 +1,57 @@ +import { REST } from "@discordjs/rest"; +import { + APIApplicationCommandInteraction, + APIInteraction, + APIInteractionResponse, + InteractionType, + RESTPostAPIApplicationCommandsJSONBody, + RESTPostAPIChatInputApplicationCommandsJSONBody, + RESTPostAPIWebhookWithTokenJSONBody, + Routes, +} from "discord-api-types/v10"; +import { rest } from "../rest"; + +export * from "./builder"; + +export type HandlerFn = ( + data: APIApplicationCommandInteraction +) => PromiseLike; +export type PromiseLike = T | Promise; +export type Command = { + json: RESTPostAPIChatInputApplicationCommandsJSONBody; + handler: HandlerFn; +}; + +export const registerCommands = async ( + commands: Iterable, + rest: REST, + applicationId: string +) => { + for (const command of commands) { + await rest.post(Routes.applicationCommands(applicationId), { + body: command.json as RESTPostAPIApplicationCommandsJSONBody, + }); + } +}; + +export const buildHandler = (commands: Iterable) => { + let internal: Map = new Map(); + for (const command of commands) { + internal.set(command.json.name, command); + } + + return async (event: APIInteraction, reply?: (data: object) => void) => { + console.log("executing: ", event.data); + if (event.type === InteractionType.ApplicationCommand) { + console.log("executing: ", event.data); + let command = internal.get(event.data.name); + + if (command) { + let data = await command.handler(event); + console.log("sending reply", data); + + reply(data); + } + } + }; +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..eb8707f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,52 @@ +import { EventClient } from "./events/index"; +import { + RESTPostAPIChannelMessageResult, + Routes, +} from "discord-api-types/v10"; +import { rest } from "./rest"; +import { buildHandler } from "./handler"; +import { commands } from "./commands"; + +const emitter = new EventClient(); +emitter.on("interactionCreate", buildHandler(commands)); + +emitter.on("messageCreate", async (message) => { + console.log(message); + if (message.content.toLowerCase() == "salut") { + await rest.post(Routes.channelMessages(message.channel_id), { + body: { + content: `Salut <@${message.author.id}> :wink:`, + }, + }); + } else if (message.content.toLocaleLowerCase() == "~ping") { + let t1 = new Date().getTime(); + let sentMessage = await rest.post( + Routes.channelMessages(message.channel_id), + { + body: { + content: `Calcul du ping...`, + }, + } + ); + let time = new Date().getTime() - t1; + + await rest.patch( + Routes.channelMessage(message.channel_id, sentMessage.id), + { + body: { + content: `Le ping de <@${sentMessage.author.id}> est de \`${time}ms\``, + }, + } + ); + } +}); + +emitter + .start({ + additionalEvents: [], + nats: { + servers: ["localhost:4222"], + }, + queue: "nova-worker-common", + }) + .catch(console.log); diff --git a/src/register.ts b/src/register.ts new file mode 100644 index 0000000..4672585 --- /dev/null +++ b/src/register.ts @@ -0,0 +1,5 @@ +import { commands } from "./commands"; +import { registerCommands } from "./handler"; +import { rest } from "./rest"; + +registerCommands(commands, rest, "807188335717384212"); \ No newline at end of file diff --git a/src/rest.ts b/src/rest.ts new file mode 100644 index 0000000..9bee0cb --- /dev/null +++ b/src/rest.ts @@ -0,0 +1,8 @@ +require('source-map-support').install(); +import { REST } from "@discordjs/rest"; + +export const rest = new REST({ + version: "10", + headers: { Authorization: "" }, + api: "http://localhost:8090/api", +}).setToken("_"); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5781751 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "outDir": "dist", + "downlevelIteration": true, + "target": "ES5", + "sourceMap": true + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..c276afd --- /dev/null +++ b/yarn.lock @@ -0,0 +1,270 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@discordjs/builders@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.4.0.tgz#b951b5e6ce4e459cd06174ce50dbd51c254c1d47" + integrity sha512-nEeTCheTTDw5kO93faM1j8ZJPonAX86qpq/QVoznnSa8WWcCgJpjlu6GylfINTDW6o7zZY0my2SYdxx2mfNwGA== + dependencies: + "@discordjs/util" "^0.1.0" + "@sapphire/shapeshift" "^3.7.1" + discord-api-types "^0.37.20" + fast-deep-equal "^3.1.3" + ts-mixer "^6.0.2" + tslib "^2.4.1" + +"@discordjs/collection@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.3.0.tgz#65bf9674db72f38c25212be562bb28fa0dba6aa3" + integrity sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg== + +"@discordjs/rest@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-1.5.0.tgz#dc15474ab98cf6f31291bf61bbc72bcf4f30cea2" + integrity sha512-lXgNFqHnbmzp5u81W0+frdXN6Etf4EUi8FAPcWpSykKd8hmlWh1xy6BmE0bsJypU1pxohaA8lQCgp70NUI3uzA== + dependencies: + "@discordjs/collection" "^1.3.0" + "@discordjs/util" "^0.1.0" + "@sapphire/async-queue" "^1.5.0" + "@sapphire/snowflake" "^3.2.2" + discord-api-types "^0.37.23" + file-type "^18.0.0" + tslib "^2.4.1" + undici "^5.13.0" + +"@discordjs/util@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-0.1.0.tgz#e42ca1bf407bc6d9adf252877d1b206e32ba369a" + integrity sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ== + +"@sapphire/async-queue@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.5.0.tgz#2f255a3f186635c4fb5a2381e375d3dfbc5312d8" + integrity sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA== + +"@sapphire/shapeshift@^3.7.1": + version "3.8.1" + resolved "https://registry.yarnpkg.com/@sapphire/shapeshift/-/shapeshift-3.8.1.tgz#b98dc6a7180f9b38219267917b2e6fa33f9ec656" + integrity sha512-xG1oXXBhCjPKbxrRTlox9ddaZTvVpOhYLmKmApD/vIWOV1xEYXnpoFs68zHIZBGbqztq6FrUPNPerIrO1Hqeaw== + dependencies: + fast-deep-equal "^3.1.3" + lodash "^4.17.21" + +"@sapphire/snowflake@^3.2.2": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.4.0.tgz#25c012158a9feea2256c718985dbd6c1859a5022" + integrity sha512-zZxymtVO6zeXVMPds+6d7gv/OfnCc25M1Z+7ZLB0oPmeMTPeRWVPQSS16oDJy5ZsyCOLj7M6mbZml5gWXcVRNw== + +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + +"@types/node@^18.11.18": + version "18.11.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" + integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== + +"@types/ping@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/ping/-/ping-0.4.1.tgz#98f9c20be196ca6c3c2639528b7e1827b17e84be" + integrity sha512-q/D+xQvoqrHvntvz2A0Pb0ImYwnN3zakluUp8O2qoogGoVMVbdY2K/ulxHcCh9TzYzVoojayHBa9gYQDIZ4v0A== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +discord-api-types@^0.37.20, discord-api-types@^0.37.23, discord-api-types@^0.37.25: + version "0.37.25" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.25.tgz#bee7fd09bd4afb9d0e2983ef837ca1cad5df0e5c" + integrity sha512-aCwA2sWnL1zPQgTELkkMzQneuWyCXXUjZCUKswesiE6RDCfOfxAPXOHg6ZTlBA5layPSikGCBBRjyh8S3Wzd+A== + +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +file-type@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-18.0.0.tgz#7a39378f8657ddc02807a0c62cb77cb4dc318197" + integrity sha512-jjMwFpnW8PKofLE/4ohlhqwDk5k0NC6iy0UHAJFKoY1fQeGMN0GDdLgHQrvCbSpMwbqzoCZhRI5dETCZna5qVA== + dependencies: + readable-web-to-node-stream "^3.0.2" + strtok3 "^7.0.0" + token-types "^5.0.1" + +glob-regex@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/glob-regex/-/glob-regex-0.3.2.tgz#27348f2f60648ec32a4a53137090b9fb934f3425" + integrity sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +nats@^2.10.2: + version "2.10.2" + resolved "https://registry.yarnpkg.com/nats/-/nats-2.10.2.tgz#f60a6658fcc503d9f9064d759f68272daf229ad3" + integrity sha512-U+3bjrYT/MqEkWDoHH9ql2rm8qt4s8z49yUjvryBvjEV0GtObTqa+BUpcTK6v2SE5fv09qcP7BcfUK0k0/gB0g== + dependencies: + nkeys.js "1.0.4" + web-streams-polyfill "^3.2.1" + +nkeys.js@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/nkeys.js/-/nkeys.js-1.0.4.tgz#0902bd44129569c4bd3857ce87d8b69f1c0e7253" + integrity sha512-xeNDE6Ha5I3b3PnlHyT9AbmBxq3Vb9KHzmaI/h4IXYg0PUVZSUXNHNhTfU20oBsubw2ZdV/1AdC6hnRuMiZfMQ== + dependencies: + tweetnacl "1.0.3" + +peek-readable@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.0.0.tgz#7ead2aff25dc40458c60347ea76cfdfd63efdfec" + integrity sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A== + +ping@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/ping/-/ping-0.4.2.tgz#8ca8857f201281b5977fe765e7887d0477b5cf52" + integrity sha512-1uAw0bzHtrPbPo2s6no06oZAzY6KqKclEJR1JRZKIHKXKlPdrz9N0/1MPPB+BbrvMjN3Mk0pcod3bfLNZFRo9w== + dependencies: + q "1.x" + underscore "^1.12.0" + +q@1.x: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== + +readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-web-to-node-stream@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== + dependencies: + readable-stream "^3.6.0" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +source-map-support@^0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strtok3@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-7.0.0.tgz#868c428b4ade64a8fd8fee7364256001c1a4cbe5" + integrity sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^5.0.0" + +token-types@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-5.0.1.tgz#aa9d9e6b23c420a675e55413b180635b86a093b4" + integrity sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + +ts-mixer@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.2.tgz#3e4e4bb8daffb24435f6980b15204cb5b287e016" + integrity sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A== + +tslib@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + +tweetnacl@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + +type-fest@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.5.0.tgz#df7b2ef54ea775163c56d087b33e901ce9d657f7" + integrity sha512-bI3zRmZC8K0tUz1HjbIOAGQwR2CoPQG68N5IF7gm0LBl8QSNXzkmaWnkWccCUL5uG9mCsp4sBwC8SBrNSISWew== + +typescript@^4.9.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== + +underscore@^1.12.0: + version "1.13.6" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" + integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== + +undici@^5.13.0: + version "5.14.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.14.0.tgz#1169d0cdee06a4ffdd30810f6228d57998884d00" + integrity sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ== + dependencies: + busboy "^1.6.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +web-streams-polyfill@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== -- 2.39.5