]> git.puffer.fish Git - matthieu/gru.git/commitdiff
first commit
authorMatthieuCoder <matthieu@matthieu-dev.xyz>
Tue, 3 Jan 2023 10:21:17 +0000 (14:21 +0400)
committerMatthieuCoder <matthieu@matthieu-dev.xyz>
Tue, 3 Jan 2023 10:21:17 +0000 (14:21 +0400)
16 files changed:
.gitignore [new file with mode: 0644]
LICENCE [new file with mode: 0644]
package.json [new file with mode: 0644]
src/commands/index.ts [new file with mode: 0644]
src/commands/ping.ts [new file with mode: 0644]
src/events/client.ts [new file with mode: 0644]
src/events/event-emitter.ts [new file with mode: 0644]
src/events/index.ts [new file with mode: 0644]
src/events/transport.ts [new file with mode: 0644]
src/handler/builder.ts [new file with mode: 0644]
src/handler/index.ts [new file with mode: 0644]
src/index.ts [new file with mode: 0644]
src/register.ts [new file with mode: 0644]
src/rest.ts [new file with mode: 0644]
tsconfig.json [new file with mode: 0644]
yarn.lock [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b2de364
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..5564701
--- /dev/null
@@ -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 (file)
index 0000000..366e781
--- /dev/null
@@ -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 (file)
index 0000000..9bf6bc0
--- /dev/null
@@ -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<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();
diff --git a/src/events/client.ts b/src/events/client.ts
new file mode 100644 (file)
index 0000000..b4045a0
--- /dev/null
@@ -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<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);
+  }
+}
diff --git a/src/events/event-emitter.ts b/src/events/event-emitter.ts
new file mode 100644 (file)
index 0000000..8a106c9
--- /dev/null
@@ -0,0 +1,51 @@
+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();
+  }
+}
diff --git a/src/events/index.ts b/src/events/index.ts
new file mode 100644 (file)
index 0000000..5ec7692
--- /dev/null
@@ -0,0 +1 @@
+export * from "./client";
diff --git a/src/events/transport.ts b/src/events/transport.ts
new file mode 100644 (file)
index 0000000..4f2abd6
--- /dev/null
@@ -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<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);
+  }
+}
diff --git a/src/handler/builder.ts b/src/handler/builder.ts
new file mode 100644 (file)
index 0000000..148542a
--- /dev/null
@@ -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 (file)
index 0000000..8126717
--- /dev/null
@@ -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<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);
+      }
+    }
+  };
+};
diff --git a/src/index.ts b/src/index.ts
new file mode 100644 (file)
index 0000000..eb8707f
--- /dev/null
@@ -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 = <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);
diff --git a/src/register.ts b/src/register.ts
new file mode 100644 (file)
index 0000000..4672585
--- /dev/null
@@ -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 (file)
index 0000000..9bee0cb
--- /dev/null
@@ -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 (file)
index 0000000..5781751
--- /dev/null
@@ -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 (file)
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==