Skip to content

Commit

Permalink
Feature: Slash Commands and Context Menu Commands (#109)
Browse files Browse the repository at this point in the history
* refactor(messageCreate): move commands handling into specific file

* feat(commands): move all commands to slash commands

Parallel to a huge refactor for the reply to interactions, all commands are now processed as slash commands (except for the pin command which became a message context menu command now). This means we do not need messageCreate event anymore (nor do we need the intents related to content).
Changes on the config to remove unnecessary emotes as well.

* fix(db): migrations always run into error when run on existing db

* refactor(events): apply same strategy of loading commands to events

Adapt helper to log information as well.
Fix dockerfile warning with stage names

* fix: apply sonarcloud feedbacks, new linter

* fix(datadrop): avoid nested template literal

* fix: no need for eslint mentions anymore

* refactor(owner commands): destructuring config to get ownerIds directly

* fix(CommandHandler): first check for deferred interaction before checking if it's replied already or not

* fix(Event): type-safety for events

* style: fix linting issues

* fix(datadrop): runtime issue due to complex infered types

* refactor(deploy-commands): protocol file:/// also works on linux machines

* fix(pin): remove commented description setter

There is no description on Context Menu Commands

* refactor(deploy-commands): divide script in different paths related to args

* refactor(deploy-commands): separate behaviour in fully async functions, rework flow to shutdown client after script ran entirely

* style: lint scripts and src files

* fix(docker): properly write logs to a file in the volume

* style(Dockerfile): better lisibility and less layers
  • Loading branch information
HunteRoi authored Oct 30, 2024
1 parent 1b0315f commit efe18fd
Show file tree
Hide file tree
Showing 47 changed files with 1,987 additions and 1,718 deletions.
32 changes: 0 additions & 32 deletions .eslintrc.json

This file was deleted.

4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ typings/
# Optional npm cache directory
.npm

# eslint
.eslintcache
.eslintrc.json

# Optional REPL history
.node_repl_history

Expand Down
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"recommendations": [
"ms-azuretools.vscode-docker",
"dbaeumer.vscode-eslint",
"gamunu.vscode-yarn",
"biomejs.biome",
]
}
3 changes: 0 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
"files.exclude": {
"node_modules": true
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"npm.packageManager": "yarn",
"files.eol": "\n",
"editor.insertSpaces": true,
Expand Down
24 changes: 14 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
FROM node:lts-alpine AS BUILD
FROM node:lts-alpine AS build

WORKDIR /app

RUN apk --no-cache add zip

COPY . .
RUN yarn install --frozen-lockfile
RUN yarn env-gen
RUN yarn build
RUN yarn install --production
RUN zip -r app.zip ./node_modules ./build ./yarn.lock ./.env
RUN yarn install --frozen-lockfile \
&& yarn env-gen \
&& yarn build \
&& yarn install --production \
&& zip -r app.zip ./node_modules ./build ./yarn.lock ./.env ./entrypoint.sh

# ------------------------------------------------------------
FROM node:lts-alpine AS APP
FROM node:lts-alpine AS app

WORKDIR /app

RUN apk --no-cache add unzip

COPY --from=BUILD /app/app.zip .
RUN unzip app.zip && rm app.zip && mv ./build/* . && rm -rf ./build
COPY --from=build /app/app.zip .
RUN unzip app.zip \
&& rm app.zip \
&& mv ./build/* . \
&& rm -rf ./build \
&& chmod +x ./entrypoint.sh

CMD ["sh", "-c", "sleep 2 && node ./index.js"]
ENTRYPOINT ["./entrypoint.sh"]
24 changes: 24 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "https://biomejs.dev/schemas/1.6.4/schema.json",
"organizeImports": {
"enabled": true
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "master"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 4,
"lineEnding": "lf"
}
}
4 changes: 1 addition & 3 deletions config.development.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
],
"minLevel": "debug",
"includeTimestamp": true,
"prefix": "dab!",
"botName": "Yoda Beta",
"botId": "703031563062870107",
"communitymanagerRoleid": "1028257512383848478",
"adminRoleid": "1028257512358674472",
"delegatesRoleid": "1028257512358674468",
Expand Down Expand Up @@ -143,8 +143,6 @@
"roleid": "1028257512312557591",
"emote": "📢"
},
"ok_hand": "👌",
"warning": "⚠️",
"communicationServiceOptions": {
"mailData": {
"from": "[email protected]",
Expand Down
4 changes: 1 addition & 3 deletions config.production.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
],
"minLevel": "info",
"includeTimestamp": true,
"prefix": "da!",
"botName": "Yoda",
"botId": "454768256364969985",
"communitymanagerRoleid": "288659580064366592",
"adminRoleid": "360850813914185738",
"delegatesRoleid": "288659613732306944",
Expand Down Expand Up @@ -151,8 +151,6 @@
"roleid": "364008970966269952",
"emote": "📢"
},
"ok_hand": "👌",
"warning": "⚠️",
"communicationServiceOptions": {
"mailData": {
"from": "[email protected]",
Expand Down
3 changes: 1 addition & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ x-restart-policy:
&restart-policy
restart: always

version: '3.8'
services:
database:
<<: [*env-file, *network, *restart-policy]
Expand All @@ -33,7 +32,7 @@ services:
context: .
dockerfile: ./Dockerfile
volumes:
- bot_logs:/app/logs
- bot_logs:/var/log/datadrop/
depends_on:
- database

Expand Down
12 changes: 12 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh

echo "Waiting for PostgreSQL to be ready..."
until nc -z -v -w30 database 5432
do
echo "Waiting for PostgreSQL..."
sleep 1
done

echo "PostgreSQL is up and running!"

node ./index.js | tee -a /var/log/datadrop/console.log
37 changes: 18 additions & 19 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import * as dotenv from 'dotenv';
import { GatewayIntentBits } from 'discord.js';
import { GatewayIntentBits } from "discord.js";
import * as dotenv from "dotenv";

import { DatadropClient } from './src/datadrop.js';
import { readConfig } from './src/config.js';
import { Configuration } from './src/models/Configuration.js';
import { readConfig } from "./src/config.js";
import { DatadropClient } from "./src/datadrop.js";
import type { Configuration } from "./src/models/Configuration.js";

dotenv.config({ debug: Boolean(process.env.DEBUG) });
dotenv.config({ debug: Boolean(process.env.DEBUG), encoding: "utf-8" });

let client: DatadropClient;
readConfig()
.then(
async (config: Configuration) => {
client = new DatadropClient({
.then(async (config: Configuration) => {
client = new DatadropClient(
{
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.Guilds,
GatewayIntentBits.MessageContent,
GatewayIntentBits.DirectMessages
]
}, config);
GatewayIntentBits.GuildMessages,
GatewayIntentBits.DirectMessages,
],
},
config,
);

await client.start();
}
)
await client.start();
})
.catch(() => client?.stop());
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "datadrop",
"version": "1.11.1",
"version": "2.0.0",
"type": "module",
"main": "./build/index.js",
"scripts": {
"start": "docker-compose up --build",
"stop": "docker-compose down",
"deploy:commands": "tsc && node ./scripts/deploy-commands.cjs",
"build": "rm -rf build/ && tsc",
"lint": "eslint --fix --ext .ts .",
"lint": "biome check --write ./src index.ts ./scripts",
"env-gen": "node ./scripts/envgen.cjs"
},
"repository": {
Expand All @@ -26,15 +27,14 @@
"@hunteroi/discord-temp-channels": "^3.3.0",
"@hunteroi/discord-verification": "^1.5.0",
"@sendgrid/mail": "8.1.3",
"discord-sync-commands": "^0.3.0",
"discord.js": "^14.16.2",
"dotenv": "^16.4.5",
"ts-postgres": "1.3.0"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@types/node": "^20.12.7",
"@typescript-eslint/eslint-plugin": "^7.6.0",
"@typescript-eslint/parser": "^7.6.0",
"eslint": "^9.0.0",
"typescript": "^5.4.4"
}
}
120 changes: 120 additions & 0 deletions scripts/deploy-commands.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
const dotenv = require("dotenv");
const { Client, Collection, REST, Routes } = require("discord.js");
const path = require("node:path");
const fsp = require("node:fs/promises");
const synchronizeSlashCommands = require("discord-sync-commands");
const { botId: botProdId } = require("../config.production.json");
const { guildId, botId: botDevId } = require("../config.development.json");

async function deleteAllCommands(rest, applicationId, guildId) {
try {
if (guildId) {
console.log("Deleting all commands in the test guild...");
console.log("Guild ID:", guildId);
await rest.put(
Routes.applicationGuildCommands(applicationId, guildId),
{ body: [] },
);
console.log(
"Successfully deleted all application commands from the test guild.",
);
} else {
console.log("Deleting all global commands...");
await rest.put(Routes.applicationCommands(applicationId), {
body: [],
});
console.log("Successfully deleted all application commands.");
}
} catch (error) {
console.error("Error deleting commands:", error);
}
}

async function readFilesFrom(directory, callback) {
try {
const files = await fsp.readdir(directory);
for (const file of files) {
const filePath = path.join(directory, file);
const stats = await fsp.stat(filePath);
if (stats.isDirectory()) {
await readFilesFrom(filePath, callback);
} else if (stats.isFile() && file.endsWith(".js")) {
const props = await import(`file:///${filePath}`);
callback(file.replace(".js", ""), props.default);
}
}
} catch (error) {
console.error("Error reading files:", error);
}
}

async function main(args) {
if (args.includes("--deleteAll")) {
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
await deleteAllCommands(
rest,
applicationId,
args.includes("--guild") ? guildId : undefined,
);
process.exit(0);
}

if (args.includes("--guild")) {
console.log("Deploying commands to the test guild...");
console.log("Guild ID:", guildId);
}

const client = new Client({ intents: [] });
client.once("ready", () => console.log("Ready!"));
client.once("error", console.error);

const commands = new Collection();
await readFilesFrom(
path.join(__dirname, "..", "build", "src", "commands"),
(commandName, command) => {
console.log(`Reading ${commandName}...`);
commands.set(commandName, command.data.toJSON());
},
);

const slashCommands = [...commands.values()];
console.log("Total expected slash commands:", slashCommands.length);

await client.login(process.env.DISCORD_TOKEN);

await synchronizeSlashCommands(client, slashCommands, {
debug: true,
guildId: args.includes("--guild") ? guildId : undefined,
});

await client.destroy();
}

dotenv.config({ debug: Boolean(process.env.DEBUG), encoding: "utf-8" });
if (!process.env.DISCORD_TOKEN) {
console.error(
"Error: Please provide a Discord token in the environment variable DISCORD_TOKEN.",
);
process.exit(1);
}

const args = process.argv.slice(2);
if (args.includes("--help")) {
console.log(`
Usage: yarn deploy:commands [options]
Options:
--help\t\tShow this help message
--guild\t\tDeploy commands to the test guild
--deleteAll\t\tDelete all deployed commands (pair with --guild to delete commands in the test guild)
--prod\t\tDeploy commands to the production bot (don't forget to use the prod token as well from .env)
`);
process.exit(0);
}

let applicationId = botDevId;
if (args.includes("--prod")) {
console.log("Deploying commands to the production bot...");
applicationId = botProdId;
}

main(args).catch(console.error);
Loading

0 comments on commit efe18fd

Please sign in to comment.