--- /dev/null
+config/
+dist/
+bin/
+node_modules/
\ No newline at end of file
--- /dev/null
+ 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
--- /dev/null
+{
+ "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"
+ }
+}
--- /dev/null
+import { ping } from "./ping";
+
+export const commands = [ping];
--- /dev/null
+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<APIInteractionResponse> => {
+ 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();
--- /dev/null
+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<O extends GatewayDispatchPayload, U extends O["t"]> = Extract<
+ O & { t: Exclude<O["t"], Exclude<O["t"], U>> },
+ { t: U }
+>;
+
+export type Events = {
+ [P in GatewayDispatchPayload["t"] as `${CamelCase<P>}` | P]: [
+ ExtractEvent<GatewayDispatchPayload, P>["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<K extends keyof Events>(name: K, fn: HandlerFunction<Events[K]>): this {
+ this.transport.subscribe(name);
+ return super.on(name, fn);
+ }
+}
--- /dev/null
+import { EventEmitter } from "events";
+import { PascalCase } from "type-fest";
+import { Events } from ".";
+
+export type HandlerFunction<Args extends unknown[]> = (
+ ...args: [...Args, ...[resolve?: (data: object) => void]]
+) => unknown | Promise<unknown>;
+
+export type EventsFunctions = {
+ [P in keyof Events as P extends string ? `on${PascalCase<P>}` : never]: (
+ fn: HandlerFunction<Events[P]>
+ ) => BaseEventEmitter;
+};
+
+// Typings for the EventClient
+export interface BaseEventEmitter extends EventEmitter {
+ addListener<K extends keyof Events>(
+ name: K,
+ fn: HandlerFunction<Events[K]>
+ ): this;
+
+ on<K extends keyof Events>(name: K, fn: HandlerFunction<Events[K]>): this;
+
+ once<K extends keyof Events>(name: K, fn: HandlerFunction<Events[K]>): this;
+
+ off<K extends keyof Events>(name: K, fn: HandlerFunction<Events[K]>): this;
+
+ prependListener<K extends keyof Events>(
+ name: K,
+ fn: HandlerFunction<Events[K]>
+ ): this;
+
+ prependOnceListener<K extends keyof Events>(
+ name: K,
+ fn: HandlerFunction<Events[K]>
+ ): this;
+
+ removeAllListeners(eventName: keyof Events | undefined): this;
+ removeListener(eventName: keyof Events): this;
+
+ emit<T extends keyof Events>(name: T, ...args: Events[T]): boolean;
+ listenerCount(event: keyof Events): number;
+ listeners<T extends keyof Events>(event: T): HandlerFunction<Events[T]>[];
+ rawListeners: this["listeners"];
+}
+
+export class BaseEventEmitter extends EventEmitter implements BaseEventEmitter {
+ constructor() {
+ super();
+ }
+}
--- /dev/null
+export * from "./client";
--- /dev/null
+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<string, Function> = new Map();
+ private queue?: string;
+ private events: Set<string> = 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);
+ }
+}
--- /dev/null
+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 };
+ }
+}
--- /dev/null
+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<APIInteractionResponse>;
+export type PromiseLike<T> = T | Promise<T>;
+export type Command = {
+ json: RESTPostAPIChatInputApplicationCommandsJSONBody;
+ handler: HandlerFn;
+};
+
+export const registerCommands = async (
+ commands: Iterable<Command>,
+ 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<Command>) => {
+ let internal: Map<String, Command> = 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);
+ }
+ }
+ };
+};
--- /dev/null
+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 = <RESTPostAPIChannelMessageResult>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);
--- /dev/null
+import { commands } from "./commands";
+import { registerCommands } from "./handler";
+import { rest } from "./rest";
+
+registerCommands(commands, rest, "807188335717384212");
\ No newline at end of file
--- /dev/null
+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("_");
--- /dev/null
+{
+ "compilerOptions": {
+ "outDir": "dist",
+ "downlevelIteration": true,
+ "target": "ES5",
+ "sourceMap": true
+ }
+}
\ No newline at end of file
--- /dev/null
+# 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==