"license": "Apache-2.0",
"dependencies": {
"@discordjs/builders": "^1.4.0",
+ "@discordjs/core": "^0.3.0",
"@discordjs/rest": "^1.5.0",
- "discord-api-types": "^0.37.25",
"events": "^3.3.0",
"glob-regex": "^0.3.2",
+ "js-logger": "^1.6.1",
"nats": "^2.10.2",
"ping": "^0.4.2",
- "source-map-support": "^0.5.21"
+ "source-map-support": "^0.5.21",
+ "typed-emitter": "^2.1.0"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/ping": "^0.4.1",
+ "discord-api-types": "^0.37.25",
"type-fest": "^3.5.0",
"typescript": "^4.9.4"
},
ApplicationCommandType,
InteractionResponseType,
} from "discord-api-types/v10";
-import { CommandBuilder, HandlerFn } from "../handler";
+import { CommandBuilder, HandlerFn } from "../sys/handler";
import { promise } from "ping";
type Messages = {
+++ /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";
-import { REST } from "@discordjs/rest";
-
-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>}`]: [
- ExtractEvent<GatewayDispatchPayload, P>["d"]
- ];
-};
-
-export class EventClient extends BaseEventEmitter {
- public transport: Transport;
- // constructs
- constructor(private rest: REST) {
- super();
- this.transport = new Transport(this, rest);
- }
-
- 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 ".";
-import { APIInteractionResponse } from "discord-api-types/v10";
-
-export type HandlerFunction<Args extends unknown[]> = (
- ...args: [...Args, ...[resolve?: (data: APIInteractionResponse) => 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,
- respond: (data: APIInteractionResponse) => void,
- ...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";
-import { REST } from "@discordjs/rest";
-import {
- APIInteractionResponse,
- GatewayDispatchPayload,
- GatewayInteractionCreateDispatch,
- Routes,
-} from "discord-api-types/v10";
-import { CamelCase } from "type-fest";
-
-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, private rest: REST) {}
-
- 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: GatewayDispatchPayload = JSON.parse(string);
- let respond: (repond: APIInteractionResponse) => void | null = null;
- const camelCased = d.t.toLowerCase().replace(/_([a-z])/g, function (g) {
- return g[1].toUpperCase();
- }) as CamelCase<`${typeof d.t}`>;
-
- if (camelCased === "integrationCreate") {
- let interaction = d.d as GatewayInteractionCreateDispatch["d"];
- respond = (respond: APIInteractionResponse) => {
- if (data.reply) {
- data.respond(Buffer.from(JSON.stringify(respond), "utf-8"));
- } else {
- this.rest.post(
- Routes.webhook(interaction.channel_id, interaction.token),
- { body: respond }
- );
- }
- };
- console.log("expecting reply.");
- }
-
- this.emitter.emit(camelCased, respond, d.d as any);
- }
- };
- 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 ".";
-
-/**
- * Simple wrapper around the SlashCommandBuilder provided by Discord.js
- */
-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,
- Routes,
-} from "discord-api-types/v10";
-
-export * from "./builder";
-
-export type PromiseLike<T> = T | Promise<T>;
-/**
- * A simple function that executes a slash command.
- */
-export type HandlerFn = (
- data: APIApplicationCommandInteraction
-) => PromiseLike<APIInteractionResponse>;
-
-export type Command = {
- json: RESTPostAPIChatInputApplicationCommandsJSONBody;
- handler: HandlerFn;
-};
-
-/**
- * Register all the commands to discord
- * @param commands List of commands to register
- * @param rest Rest api instance
- * @param applicationId Current application id
- */
-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,
- });
- }
-};
-
-/**
- * Creates a new handler to handle the slash commands.
- * @param commands List of commands to handle
- * @returns Handler function
- */
-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: APIInteractionResponse) => 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);
- }
- }
- };
-};
-import { EventClient } from "./events/index";
-import { buildHandler } from "./handler";
+import { Client } from "./sys/events";
+import { buildHandler } from "./sys/handler";
import { commands } from "./commands";
-import { rest } from "./rest";
/**
- * We instanciate our nova broken client.
+ * We instanciate our nova broker client.
*/
-const emitter = new EventClient(rest);
-
-// We register our slash command handler.
-emitter.on("interactionCreate", buildHandler(commands));
-
-// We connect ourselves to the nova nats broker.
-emitter
- .start({
+const emitter = new Client({
+ transport: {
additionalEvents: [],
nats: {
servers: ["localhost:4222"],
},
queue: "nova-worker-common",
- })
- .catch(console.log);
+ },
+ rest: {
+ api: "http://localhost:8090/api",
+ },
+});
+
+// We register our slash command handler.
+emitter.on("interactionCreate", buildHandler(commands));
+
+// Simple message handler
+emitter.on("messageCreate", (message) => {
+ if (message.content === "~ping") {
+ message.client.channels.createMessage(message.channel_id, {
+ content: `Bonjour! <@${message.author.id}>`,
+ });
+ }
+});
+
+// We connect ourselves to the nova nats broker.
+emitter.start().catch(console.log);
+import { REST } from "@discordjs/rest";
import { commands } from "./commands";
-import { registerCommands } from "./handler";
-import { rest } from "./rest";
+import { registerCommands } from "./sys/handler";
+const rest = new REST({
+ version: "10",
+ headers: { Authorization: "" },
+ api: "http://localhost:8090/api",
+ }).setToken("_");
+
+
/**
* We register the commands with discord
*/
+++ /dev/null
-require('source-map-support').install();
-import { REST } from "@discordjs/rest";
-
-/**
- * Rest client used to communicate with discord
- */
-export const rest = new REST({
- version: "10",
- headers: { Authorization: "" },
- api: "http://localhost:8090/api",
-}).setToken("_");
--- /dev/null
+import {
+ APIApplicationCommandPermissionsConstant,
+ APIInteractionResponse,
+ APIInteractionResponseCallbackData,
+ GatewayDispatchPayload,
+ GatewayInteractionCreateDispatchData,
+} from "discord-api-types/v10";
+import { Transport, TransportOptions } from "./transport";
+import { CamelCase, PascalCase } from "type-fest";
+import { REST, RESTOptions } from "@discordjs/rest";
+import { EventEmitter } from "stream";
+import TypedEmitter from "typed-emitter";
+
+import { API } from "@discordjs/core";
+
+/**
+ * Maps an event name (O['t']) and a Union O and extracts alla the union members that have a matching O['t']
+ * Example:
+ * type Variant1 = { t: 'type1', myProperty: 1 };
+ * type Variant2 = { t: 'type2', anotherProperty: 2 };
+ * type ExampleUnion = Variant1 | Variant2;
+ *
+ * let variant1: ExtractVariant<ExampleUnion, 'type1'>; // Type of variant1 is Variant1
+ * let variant2: ExtractVariant<ExampleUnion, 'type2'>; // Type of variant2 is Variant2
+ *
+ */
+type ExtractVariant<O extends { t: string }, U extends O["t"]> = Extract<
+ O & { t: Exclude<O["t"], Exclude<O["t"], U>> },
+ { t: U }
+>;
+
+/**
+ * Add intrisics properties to the event, such as `client` and `rest`
+ */
+export type WithIntrisics<T> = T & { client: Client };
+export type EventName = keyof EventsHandlerArguments;
+/**
+ * Reprends a handler function with one argument
+ */
+export type HandlerFunction<Arg extends unknown[]> = (
+ ...args: Arg
+) => unknown | Promise<unknown>;
+
+export type EventTypes = {
+ [P in GatewayDispatchPayload["t"]]: WithIntrisics<
+ ExtractVariant<GatewayDispatchPayload, P>["d"]
+ >;
+};
+
+/**
+ * Maps all events from GatewayDispatchPayload['t'] (GatewayDispatchEvents) and maps them to a camelcase event name
+ * Also reteives the type of the event using ExtractEvent
+ */
+export type EventsHandlerArguments = {
+ [P in keyof EventTypes as `${CamelCase<P>}`]: HandlerFunction<
+ [EventTypes[P]]
+ >;
+} & {
+ interactionCreate: HandlerFunction<
+ [
+ WithIntrisics<GatewayInteractionCreateDispatchData>,
+ (interactionCreate: APIInteractionResponseCallbackData) => void
+ ]
+ >;
+};
+
+/**
+ * Defines all the 'on...' functions on the client
+ * This is implemented by a Proxy
+ */
+export type EventsFunctions = {
+ [P in keyof EventsHandlerArguments as P extends string
+ ? `on${PascalCase<P>}`
+ : never]: (fn: EventsHandlerArguments[P]) => Client;
+};
+
+/**
+ * Defines all the methods known to be implemented
+ */
+interface ClientFunctions
+ extends EventsFunctions,
+ TypedEmitter<EventsHandlerArguments>,
+ API {}
+
+/**
+ * The real extended class is an EventEmitter.
+ */
+const UndefinedClient: { new (): ClientFunctions } = EventEmitter as any;
+
+/**
+ * nova.js client
+ *
+ * Used to interact with nova, emits events from nova
+ * Example:
+ * client.on('messageCreate', (message) => { console.log('Message received', message.content) });
+ * client.on('interactionCreate', (message) => { });
+ */
+export class Client extends UndefinedClient {
+ private readonly transport: Transport;
+ private readonly api: API;
+ public readonly rest: REST;
+
+ constructor(options: {
+ rest?: Partial<RESTOptions>;
+ transport: TransportOptions;
+ }) {
+ super();
+ this.rest = new REST(options.rest);
+ this.transport = new Transport(this, options.transport);
+ this.api = new API(this.rest);
+
+ // This is safe because this event is emitted by the EventEmitter itself.
+ this.on("newListener" as any, (event: EventName) => {
+ this.transport.subscribe(event);
+ });
+
+ // Using a proxy to provide the 'on...' functionality
+ return new Proxy(this, {
+ get(self, symbol) {
+ let name = symbol.toString();
+ if (name.startsWith("on") && name.length > 2) {
+ // Get the event name
+ let eventName = [name[2].toLowerCase(), name.slice(3)].join(
+ ""
+ ) as EventName;
+ return (fn: EventsHandlerArguments[typeof eventName]) =>
+ self.on(eventName, fn);
+ }
+
+ if (self.api[symbol] && self[symbol]) {
+ return self.api[symbol];
+ }
+
+ return self[symbol];
+ },
+ });
+ }
+
+ public start() {
+ return this.transport.start();
+ }
+}
--- /dev/null
+export * from "./client";
--- /dev/null
+import { connect, ConnectionOptions, NatsConnection, Subscription } from "nats";
+import {
+ Client,
+ EventName,
+ EventTypes,
+ EventsHandlerArguments,
+ WithIntrisics,
+} from ".";
+import globRegex from "glob-regex";
+import {
+ APIInteraction,
+ APIInteractionResponse,
+ APIInteractionResponseCallbackData,
+ GatewayDispatchPayload,
+ GatewayInteractionCreateDispatch,
+ Routes,
+} from "discord-api-types/v10";
+import { CamelCase } from "type-fest";
+
+/**
+ * Options for the nats transport layer
+ */
+export type TransportOptions = {
+ additionalEvents?: (keyof EventsHandlerArguments)[];
+ nats?: ConnectionOptions;
+ queue: string;
+};
+
+/**
+ * Transport implements all the communication to Nova using Nats
+ */
+export class Transport {
+ // Nats connection
+ private nats: NatsConnection | null = null;
+ // Current subscriptions
+ private subscriptions: Map<string, Subscription> = new Map();
+ // Current subscribed events
+ private events: Set<EventName> = new Set();
+
+ // Creats a new Transport instance.
+ constructor(
+ private emitter: Client,
+ private config: Partial<TransportOptions>
+ ) {}
+
+ /**
+ * Starts a new nats client.
+ */
+ public async start() {
+ this.nats = await connect(this.config?.nats);
+
+ for (let eventName of this.events) {
+ await this.subscribe(eventName);
+ }
+ if (this.config.additionalEvents) {
+ for (let eventName of this.config.additionalEvents) {
+ await this.subscribe(eventName);
+ }
+ }
+ }
+
+ /**
+ * Subscribe to a new topic
+ * @param event Event to subscribe to
+ * @returns
+ */
+ public async subscribe(event: EventName) {
+ // If nats is not connected, we simply request to subscribe to it at startup
+ if (!this.nats) {
+ console.log("Requesting event " + event);
+ this.events.add(event);
+ return;
+ }
+
+ // Since the event names used by this library are camelCase'd we need to
+ // re-transform it to the UPPER_CASE used by nova.
+ let dashed = event.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
+ // Construct the topic name used by nova.
+ // This **is going to change** as we implement the caching component.
+ let topic = `nova.cache.dispatch.${dashed.toUpperCase()}`;
+
+ // To avoid having multiple subscriptions covering this event
+ // we check if each of our subscriptions covers this scope.
+ let isAlreadyPresent = [...this.subscriptions.keys()].reduce(
+ (previous, current) => {
+ if (previous) return previous;
+ let regex = globRegex(current);
+
+ return regex.test(topic);
+ },
+ false
+ );
+
+ // We abord the subscriptions if it's already covered.
+ if (isAlreadyPresent) {
+ console.warn("nats subscription already covered.");
+ return;
+ }
+
+ // We remove all the subscriptions that are covered by out current subsciptions.
+ let regex = globRegex(topic);
+ [...this.subscriptions.keys()].map((key) => {
+ if (regex.test(key)) {
+ let subsciption = this.subscriptions.get(key);
+ if (!subsciption) {
+ return;
+ }
+
+ console.log(`unsubscribing from ${key}`);
+ subsciption.unsubscribe();
+ }
+ });
+
+ this._subscriptionTask(topic);
+ }
+
+ // Task that monitors the subscription
+ // It also listens for a subscription end.
+ private async _subscriptionTask(topic: string) {
+ if (!this.nats) {
+ throw new Error("nats connection is not started");
+ }
+
+ console.log(`subscribing to ${topic}`);
+ // Create the nats subscription
+ let subscription = this.nats.subscribe(topic, { queue: this.config.queue });
+ this.subscriptions.set(topic, subscription);
+ // Handle each event in the subscription stream.
+ for await (let publish of subscription) {
+ try {
+ // Decode the payload
+ let event: GatewayDispatchPayload = JSON.parse(
+ Buffer.from(publish.data).toString("utf-8")
+ );
+ // Transform the event name to a camclCased name
+ const camelCasedName = event.t
+ .toLowerCase()
+ .replace(/_([a-z])/g, function (g) {
+ return g[1].toUpperCase();
+ }) as CamelCase<typeof event.t>;
+
+ // Since an interaction need a reponse,
+ // we need to handle the case where nova is not configured
+ // with a webhook endpoint, hence we need to use a post request
+ // against webhook execute endpoint with the interaction data.
+ if (event.t === "INTERACTION_CREATE") {
+ let interaction = event.d;
+ let respond = async (respond: APIInteractionResponseCallbackData) => {
+ if (publish.reply) {
+ publish.respond(Buffer.from(JSON.stringify(respond), "utf-8"));
+ } else {
+ await this.emitter.interactions.reply(
+ interaction.id,
+ interaction.token,
+ respond
+ );
+ }
+ };
+ // Emit the
+ this.emitter.emit(
+ camelCasedName,
+ { ...event.d, client: this.emitter },
+ respond
+ );
+ } else {
+ // Typescript refuses to infer this, whyyy
+ this.emitter.emit(camelCasedName, {
+ ...event.d,
+ client: this.emitter,
+ } as any);
+ }
+ } catch (e) {}
+ }
+ }
+}
--- /dev/null
+import { SlashCommandBuilder } from "@discordjs/builders";
+import { Command, HandlerFn } from ".";
+
+/**
+ * Simple wrapper around the SlashCommandBuilder provided by Discord.js
+ */
+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,
+ APIInteractionResponseCallbackData,
+ InteractionType,
+ RESTPostAPIApplicationCommandsJSONBody,
+ RESTPostAPIChatInputApplicationCommandsJSONBody,
+ Routes,
+} from "discord-api-types/v10";
+
+export * from "./builder";
+
+export type PromiseLike<T> = T | Promise<T>;
+/**
+ * A simple function that executes a slash command.
+ */
+export type HandlerFn = (
+ data: APIApplicationCommandInteraction
+) => PromiseLike<APIInteractionResponseCallbackData>;
+
+export type Command = {
+ json: RESTPostAPIChatInputApplicationCommandsJSONBody;
+ handler: HandlerFn;
+};
+
+/**
+ * Register all the commands to discord
+ * @param commands List of commands to register
+ * @param rest Rest api instance
+ * @param applicationId Current application id
+ */
+export const registerCommands = async (
+ commands: Iterable<Command>,
+ rest: REST,
+ applicationId: string
+) => {
+ await rest.post(Routes.applicationCommands(applicationId), {
+ body: [...commands].map(
+ (x) => x.json
+ ) as RESTPostAPIApplicationCommandsJSONBody[],
+ });
+};
+
+/**
+ * Creates a new handler to handle the slash commands.
+ * @param commands List of commands to handle
+ * @returns Handler function
+ */
+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: APIInteractionResponseCallbackData) => 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);
+ }
+ }
+ };
+};
"target": "ES5",
"sourceMap": true,
"moduleResolution": "node",
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "incremental": true
},
"exclude": ["node_modules"]
resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.3.0.tgz#65bf9674db72f38c25212be562bb28fa0dba6aa3"
integrity sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg==
+"@discordjs/core@^0.3.0":
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/@discordjs/core/-/core-0.3.0.tgz#799ab3ad38dc2c037ac4567a3948e47b93ea2d31"
+ integrity sha512-yp3H+NnaZ/sg0Z4PIVgfH4Ab88MwkXxnzwG5lNxjYiwp7+8qkYjb726qf3OxZOhFXQFt7gYaAaBevHgmPz9vjA==
+ dependencies:
+ "@discordjs/rest" "^1.5.0"
+ "@discordjs/ws" "^0.6.0"
+ "@vladfrangu/async_event_emitter" "^2.1.2"
+ discord-api-types "^0.37.23"
+
"@discordjs/rest@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-1.5.0.tgz#dc15474ab98cf6f31291bf61bbc72bcf4f30cea2"
resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-0.1.0.tgz#e42ca1bf407bc6d9adf252877d1b206e32ba369a"
integrity sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ==
+"@discordjs/ws@^0.6.0":
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/@discordjs/ws/-/ws-0.6.0.tgz#bd479c65c469552530cc0c54908d6c4722731272"
+ integrity sha512-CGXmDnskIWyitBejQG07ao6MTkxK5OnFJQLan/cNIoMF61mlbTXpcKGnyrD7dhE7B/q2MEWPKfpuYXtFBog1cg==
+ dependencies:
+ "@discordjs/collection" "^1.3.0"
+ "@discordjs/rest" "^1.5.0"
+ "@discordjs/util" "^0.1.0"
+ "@sapphire/async-queue" "^1.5.0"
+ "@types/ws" "^8.5.3"
+ "@vladfrangu/async_event_emitter" "^2.1.2"
+ discord-api-types "^0.37.23"
+ tslib "^2.4.1"
+ ws "^8.11.0"
+
"@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"
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==
-"@types/node@^18.11.18":
+"@types/node@*", "@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==
resolved "https://registry.yarnpkg.com/@types/ping/-/ping-0.4.1.tgz#98f9c20be196ca6c3c2639528b7e1827b17e84be"
integrity sha512-q/D+xQvoqrHvntvz2A0Pb0ImYwnN3zakluUp8O2qoogGoVMVbdY2K/ulxHcCh9TzYzVoojayHBa9gYQDIZ4v0A==
+"@types/ws@^8.5.3":
+ version "8.5.4"
+ resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5"
+ integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==
+ dependencies:
+ "@types/node" "*"
+
+"@vladfrangu/async_event_emitter@^2.1.2":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/@vladfrangu/async_event_emitter/-/async_event_emitter-2.1.2.tgz#85f04380d474edd1e1e5bd380dee04bc933fe995"
+ integrity sha512-s/L3xf+BPb8EUOcVeqQ1pUwZNaWGl0br/GCCsdui5DTqn8bpGGu1S2JRTO3cyAytWObieetgTzOPuDN8nwNtGA==
+
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+js-logger@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/js-logger/-/js-logger-1.6.1.tgz#8f09671b515e4a6f31dced8fdb8923432e2c60af"
+ integrity sha512-yTgMCPXVjhmg28CuUH8CKjU+cIKL/G+zTu4Fn4lQxs8mRFH/03QTNvEFngcxfg/gRDiQAOoyCKmMTOm9ayOzXA==
+
lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
dependencies:
readable-stream "^3.6.0"
+rxjs@^7.5.2:
+ version "7.8.0"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
+ integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==
+ dependencies:
+ tslib "^2.1.0"
+
safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.2.tgz#3e4e4bb8daffb24435f6980b15204cb5b287e016"
integrity sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A==
-tslib@^2.4.1:
+tslib@^2.1.0, 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==
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.5.0.tgz#df7b2ef54ea775163c56d087b33e901ce9d657f7"
integrity sha512-bI3zRmZC8K0tUz1HjbIOAGQwR2CoPQG68N5IF7gm0LBl8QSNXzkmaWnkWccCUL5uG9mCsp4sBwC8SBrNSISWew==
+typed-emitter@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-2.1.0.tgz#ca78e3d8ef1476f228f548d62e04e3d4d3fd77fb"
+ integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==
+ optionalDependencies:
+ rxjs "^7.5.2"
+
typescript@^4.9.4:
version "4.9.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78"
version "3.2.1"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
+
+ws@^8.11.0:
+ version "8.11.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
+ integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==