fix: bigbot template (#2535)

* chore: fix script extension

* chore: remove extention

* fix: at least it run can now

* chore: fix dev script

* refactor: change to use env file for config

* chore: update readme
This commit is contained in:
Jonathan Ho
2022-10-20 09:44:34 -05:00
committed by GitHub
parent 783da60779
commit cedf75f28e
21 changed files with 1298 additions and 308 deletions
+85 -3
View File
@@ -1,4 +1,86 @@
# For Prisma use. Remember to remove the `[` and `]` # For Prisma use. Remember to remove the [ and ]
DATABASE_URL=postgres://[username]:[password]@[host]:[port]/[db] DATABASE_URL=postgres:#[username]:[password]@[host]:[port]/[db]
# For other configs, update src/configs.ts # For other configs, update src/configs.ts
# General Configurations
# Whether or not this process is a local development version. Change to false for the main production bot. */
# SETUP-DD-TEMP: Change this to false in your server for production bot. Keep true in local testing.
DEVELOPMENT=true
# The server id where you develop/test the bot. */
# SETUP-DD-TEMP: Change the id to a server where you develop the bot privately.
DEV_SERVER_ID=
# The discord bot token, without the BOT prefix. */
# SETUP-DD-TEMP: Add the bot token here.
DISCORD_TOKEN=
# Bot Configurations
# The secret passcode that the bot code (event handler) is listening for. This is used to prevent someone else from trying to send malicious messages to your bot. */
# SETUP-DD-TEMP: Add a secret passcode here.
EVENT_HANDLER_AUTHORIZATION=server
# The host where the event handler will run. Must follow https:#nodejs.org/api/net.html#serverlistenoptions-callback. */
# SETUP-DD-TEMP: Set the event handler's host here.
EVENT_HANDLER_HOST=localhost
# The port where the event handler is listening for events. */
# SETUP-DD-TEMP: Set the desired port where events will be sent to be processed.
EVENT_HANDLER_PORT=8081
# The full webhook url where the bot can send errors to alert you that the bot is missing translations. */
# SETUP-DD-TEMP: Set a full discord webhook url here.
MISSING_TRANSLATION_WEBHOOK=
# The full webhook url where the bot can send errors to alert you that the bot is throwing errors. */
# SETUP-DD-TEMP: Set a full discord webhook url here.
BUGS_ERRORS_REPORT_WEBHOOK=
# Rest Proxy Configurations
# The authorization code that the REST proxy will check for to make sure the requests are coming from you. */
# SETUP-DD-TEMP: Add a secret passcode here.
REST_AUTHORIZATION=secret
# The host where the REST proxy will run. Must follow https:#nodejs.org/api/net.html#serverlistenoptions-callback. */
# SETUP-DD-TEMP: Set the REST proxy's host here.
REST_HOST=localhost
# The port that will run the REST proxy. */
# SETUP-DD-TEMP: Choose the port number here that will be used for the REST proxy.
REST_PORT=8000
# Gateway Proxy Configurations
# The amount of shards to start. Useful with multiple servers where each server is handling a portion of your bot. */
# SETUP-DD-TEMP: To start all bots, leave it as undefined. Specify he number of shards this process should handle.
TOTAL_SHARDS=
# The amount of shards to start per worker. */
# SETUP-DD-TEMP: Choose how many shards to start per worker. If you are not sure just stick to 16.
SHARDS_PER_WORKER=16
# The total amount of workers to start. Generally this should be equal to the number of cores your server has. */
# SETUP-DD-TEMP: Choose how many workers to start up. If you are not sure, check how many cores your server has.
TOTAL_WORKERS=4
# The secret passcode that the gateway is listening for. This is used to prevent someone else from trying to send malicious messages to your bot. */
# SETUP-DD-TEMP: Add a secret passcode here.
GATEWAY_AUTHORIZATION=
# The host where the gateway will run. Must follow https:#nodejs.org/api/net.html#serverlistenoptions-callback. */
# SETUP-DD-TEMP: Set the gateways's host here.
GATEWAY_HOST=localhost
# The port where the gateway will run. This is where the bot will send its messages to the gateway. */
# SETUP-DD-TEMP: Set the gateways's port here.
GATEWAY_PORT=8080
# Database Configurations
# These INFLUX configs are only if you wish to enable analytics. */
# SETUP-DD-TEMP: This is optional. If you want to build analytics, add influxdb here.
INFLUX_BUCKET=
INFLUX_ORG=
INFLUX_TOKEN=
INFLUX_URL=
+31
View File
@@ -0,0 +1,31 @@
{
"minify": true,
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"target": "es2022",
"keepClassNames": true,
"loose": true,
"minify": {
"compress": {
"unused": true
},
"mangle": true
}
},
"module": {
"type": "es6",
"strict": false,
"strictMode": true,
"lazy": false,
"noInterop": false
},
"sourceMaps": "inline"
}
+4 -8
View File
@@ -17,15 +17,11 @@ Make sure to install the latest version when you use it.
To start your bot, you will need to start a few processes. The instructions below will use `node` but you can use To start your bot, you will need to start a few processes. The instructions below will use `node` but you can use
something like `pm2` to help keep your processes alive. something like `pm2` to help keep your processes alive.
Please compile everything first with `tsc`. Please compile everything first with `npm run build`.
- Start REST - Start REST
- `ts-node src/rest/index.ts` - `npm run startr`
- Start Gateway - Start Gateway
- `ts-node src/gateway/index.ts` - `npm run startg`
- Start Bot - Start Bot
- `ts-node src/bot/index.ts` - `npm run startb`
## Improvements
- Change configs.ts file to use an .env file.
+6
View File
@@ -0,0 +1,6 @@
{
"watch": "./src/**/*.ts",
"ext ": "env,ts",
"signal": "SIGKILL",
"exec": "npm run build && node --experimental-specifier-resolution=node"
}
+961 -38
View File
File diff suppressed because it is too large Load Diff
+15 -13
View File
@@ -2,39 +2,41 @@
"name": "dd-big-bot", "name": "dd-big-bot",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"type": "module",
"scripts": { "scripts": {
"dev": "nodemon -e ts --exec 'npm run start'",
"start": "node --no-warnings dist/index.js",
"devbg": "npx prisma generate && tsc --watch", "devbg": "npx prisma generate && tsc --watch",
"fmt": "prettier -w ./src", "fmt": "prettier -w ./src",
"dg": "ts-node src/gateway/index.ts", "devg": "nodemon --ignore ./src/bot/**/* --ignore ./src/rest/**/* --ignore ./dist/**/* -e ts dist/gateway/index.js",
"dr": "ts-node src/rest/index.js", "devr": "nodemon --ignore ./src/bot/**/* --ignore ./src/gateway/**/* --ignore ./dist/**/* -e ts dist/rest/index.js",
"db": "ts-node src/bot/index.ts", "devb": "nodemon --ignore ./src/rest/**/* --ignore ./src/gateway/**/* --ignore ./dist/**/* -e ts dist/bot/index.js",
"tsc": "tsc", "type": "tsc --noEmit",
"tscw": "tsc --watch", "build": "swc src --out-dir dist",
"devr": "node dist/rest/index", "startr": "node --experimental-specifier-resolution=node dist/rest/index.js",
"devg": "node dist/gateway/index", "startg": "node --experimental-specifier-resolution=node dist/gateway/index.js",
"devb": "node dist/bot/index" "startb": "node --experimental-specifier-resolution=node dist/bot/index.js"
}, },
"dependencies": { "dependencies": {
"@influxdata/influxdb-client": "^1.29.0", "@influxdata/influxdb-client": "^1.29.0",
"@prisma/client": "^3.15.2", "@prisma/client": "^3.15.2",
"colorette": "^2.0.19", "colorette": "^2.0.19",
"discordeno": "^16.0.1", "discordeno": "^16.0.1",
"dotenv": "^16.0.3",
"express": "^4.18.1", "express": "^4.18.1",
"fastify": "^4.8.1", "fastify": "^4.8.1",
"nanoid": "^4.0.0", "nanoid": "^4.0.0",
"ts-node": "^10.9.1", "node-fetch": "^3.2.10",
"tslib": "^2.3.1",
"web-worker": "^1.2.0" "web-worker": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.57",
"@swc/core": "^1.3.9",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/node": "^17.0.23", "@types/node": "^17.0.23",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"nodemon": "^2.0.15", "nodemon": "^2.0.15",
"prettier": "2.6.2", "prettier": "2.6.2",
"prisma": "^4.2.1", "prisma": "^4.2.1",
"ts-node": "^10.9.1",
"typescript": "^4.6.3" "typescript": "^4.6.3"
}, },
"prettier": { "prettier": {
@@ -45,4 +47,4 @@
"semi": true, "semi": true,
"printWidth": 120 "printWidth": 120
} }
} }
+5 -1
View File
@@ -1,5 +1,9 @@
import { InfluxDB } from "@influxdata/influxdb-client"; import { InfluxDB } from "@influxdata/influxdb-client";
import { INFLUX_BUCKET, INFLUX_ORG, INFLUX_TOKEN, INFLUX_URL } from "./configs.js";
const INFLUX_BUCKET = process.env.INFLUX_BUCKET as string
const INFLUX_ORG = process.env.INFLUX_ORG as string
const INFLUX_TOKEN = process.env.INFLUX_TOKEN as string
const INFLUX_URL = process.env.INFLUX_URL as string
export const influxDB = INFLUX_URL && INFLUX_TOKEN ? new InfluxDB({ url: INFLUX_URL, token: INFLUX_TOKEN }) : undefined; export const influxDB = INFLUX_URL && INFLUX_TOKEN ? new InfluxDB({ url: INFLUX_URL, token: INFLUX_TOKEN }) : undefined;
export const Influx = influxDB?.getWriteApi(INFLUX_ORG, INFLUX_BUCKET); export const Influx = influxDB?.getWriteApi(INFLUX_ORG, INFLUX_BUCKET);
+7 -4
View File
@@ -1,10 +1,13 @@
import { Bot, Collection, createBot, createRestManager } from "discordeno"; import { Bot, Collection, createBot, createRestManager } from "discordeno";
import enableHelpersPlugin from "discordeno/helpers-plugin"; import enableHelpersPlugin from "discordeno/helpers-plugin";
import { createLogger } from "discordeno/logger"; import { createLogger } from "discordeno/logger";
import { DISCORD_TOKEN, INTENTS, REST_AUTHORIZATION, REST_URL } from "../configs.js"; import { INTENTS, REST_URL } from "../configs";
import { setupEventHandlers } from "./events/mod.js"; import { setupEventHandlers } from "./events/mod";
import { MessageCollector } from "./utils/collectors.js"; import { MessageCollector } from "./utils/collectors";
import { customizeInternals } from "./utils/internals/mod.js"; import { customizeInternals } from "./utils/internals/mod";
const DISCORD_TOKEN = process.env.DISCORD_TOKEN as string
const REST_AUTHORIZATION = process.env.REST_AUTHORIZATION as string
export const bot = enableHelpersPlugin( export const bot = enableHelpersPlugin(
customizeBot( customizeBot(
@@ -10,11 +10,12 @@ import {
InteractionResponseTypes, InteractionResponseTypes,
Member, Member,
Role, Role,
User, User
} from "discordeno"; } from "discordeno";
import { bot, BotWithCustomProps } from "../../bot.js"; import { bot, BotWithCustomProps } from "../../bot.js";
import COMMANDS from "../../commands/mod.js"; import COMMANDS from "../../commands/mod.js";
import { getLanguage, loadLanguage, serverLanguages, translate, translationKeys } from "../../languages/translate.js"; import { getLanguage, loadLanguage, serverLanguages, translate, translationKeys } from "../../languages/translate.js";
import { InteractionWithCustomProps } from "../../typings/discordeno.js";
import { Command, ConvertArgumentDefinitionsToArgs } from "../../utils/slash/createCommand.js"; import { Command, ConvertArgumentDefinitionsToArgs } from "../../utils/slash/createCommand.js";
function logCommand( function logCommand(
@@ -22,11 +23,10 @@ function logCommand(
type: "Failure" | "Success" | "Trigger" | "Slowmode" | "Missing" | "Inhibit", type: "Failure" | "Success" | "Trigger" | "Slowmode" | "Missing" | "Inhibit",
commandName: string, commandName: string,
) { ) {
const command = `[COMMAND: ${bgYellow(black(commandName || "Unknown"))} - ${ const command = `[COMMAND: ${bgYellow(black(commandName || "Unknown"))} - ${bgBlack(
bgBlack( ["Failure", "Slowmode", "Missing"].includes(type) ? red(type) : type === "Success" ? green(type) : white(type),
["Failure", "Slowmode", "Missing"].includes(type) ? red(type) : type === "Success" ? green(type) : white(type), )
) }]`;
}]`;
const user = bgGreen( const user = bgGreen(
black(`${info.user.username}#${info.user.discriminator.toString().padStart(4, "0")}(${info.id})`), black(`${info.user.username}#${info.user.discriminator.toString().padStart(4, "0")}(${info.id})`),
@@ -36,7 +36,7 @@ function logCommand(
bot.logger.info(`${command} by ${user} in ${guild} with MessageID: ${info.id}`); bot.logger.info(`${command} by ${user} in ${guild} with MessageID: ${info.id}`);
} }
export async function executeSlashCommand(bot: BotWithCustomProps, interaction: Interaction) { export async function executeSlashCommand(bot: BotWithCustomProps, interaction: InteractionWithCustomProps) {
const data = interaction.data; const data = interaction.data;
const name = data?.name as keyof typeof COMMANDS; const name = data?.name as keyof typeof COMMANDS;
@@ -89,7 +89,7 @@ export async function executeSlashCommand(bot: BotWithCustomProps, interaction:
} }
/** Runs the inhibitors to see if a command is allowed to run. */ /** Runs the inhibitors to see if a command is allowed to run. */
export async function commandAllowed(interaction: Interaction, command: Command<any>) { export async function commandAllowed(interaction: InteractionWithCustomProps, command: Command<any>) {
// CHECK WHETHER THE USER/GUILD IS VIP // CHECK WHETHER THE USER/GUILD IS VIP
if (command.vipOnly) { if (command.vipOnly) {
// SETUP-DD-TEMP: Check if this server/user is a vip. // SETUP-DD-TEMP: Check if this server/user is a vip.
@@ -149,21 +149,21 @@ function convertOptionValue(
option: InteractionDataOption, option: InteractionDataOption,
translateOptions?: Record<string, string>, translateOptions?: Record<string, string>,
): [ ): [
string, string,
( (
| { user: User; member: Member } | { user: User; member: Member }
| Role | Role
| { | {
id: bigint; id: bigint;
name: string; name: string;
type: ChannelTypes; type: ChannelTypes;
permissions: bigint; permissions: bigint;
} }
| boolean | boolean
| string | string
| number | number
), ),
] { ] {
// THE OPTION IS A CHANNEL // THE OPTION IS A CHANNEL
if (option.type === ApplicationCommandOptionTypes.Channel) { if (option.type === ApplicationCommandOptionTypes.Channel) {
const channel = interaction.data?.resolved?.channels?.get(BigInt(option.value as string)); const channel = interaction.data?.resolved?.channels?.get(BigInt(option.value as string));
@@ -269,7 +269,7 @@ export function optionParser(
[translateOptions?.[interaction.data.options[0].name] ?? interaction.data.options[0].name]: { [translateOptions?.[interaction.data.options[0].name] ?? interaction.data.options[0].name]: {
[ [
translateOptions?.[interaction.data.options[0]!.options![0]!.name] ?? translateOptions?.[interaction.data.options[0]!.options![0]!.name] ??
interaction.data.options[0]!.options![0]!.name interaction.data.options[0]!.options![0]!.name
]: convertedOptions, ]: convertedOptions,
}, },
}; };
@@ -1,5 +1,6 @@
import { InteractionTypes, MessageComponentTypes } from "discordeno"; import { InteractionTypes, MessageComponentTypes } from "discordeno";
import { bot } from "../../bot.js"; import { bot } from "../../bot.js";
import { InteractionWithCustomProps } from "../../typings/discordeno.js";
import { executeButtonClick } from "./button.js"; import { executeButtonClick } from "./button.js";
import { executeSlashCommand } from "./command.js"; import { executeSlashCommand } from "./command.js";
import { executeModalSubmit } from "./modal.js"; import { executeModalSubmit } from "./modal.js";
@@ -7,7 +8,7 @@ import { executeModalSubmit } from "./modal.js";
export function setInteractionCreateEvent() { export function setInteractionCreateEvent() {
bot.events.interactionCreate = async function (_, interaction) { bot.events.interactionCreate = async function (_, interaction) {
if (interaction.type === InteractionTypes.ApplicationCommand) { if (interaction.type === InteractionTypes.ApplicationCommand) {
await executeSlashCommand(bot, interaction); await executeSlashCommand(bot, interaction as InteractionWithCustomProps);
} else if (interaction.type === InteractionTypes.MessageComponent) { } else if (interaction.type === InteractionTypes.MessageComponent) {
if (!interaction.data) return; if (!interaction.data) return;
+3 -3
View File
@@ -1,6 +1,6 @@
import { setInteractionCreateEvent } from "./interactions/mod.js"; import { setInteractionCreateEvent } from "./interactions/mod";
import { setMessageCreateEvent } from "./messages/create.js"; import { setMessageCreateEvent } from "./messages/create";
import { setRawEvent } from "./raw.js"; import { setRawEvent } from "./raw";
export function setupEventHandlers() { export function setupEventHandlers() {
setInteractionCreateEvent(); setInteractionCreateEvent();
+18 -12
View File
@@ -1,18 +1,20 @@
import { DiscordGatewayPayload } from "discordeno"; import { DiscordGatewayPayload } from "discordeno";
import Embeds from "discordeno/embeds"; // ReferenceError: publishMessage is not defined
// import Embeds from "discordeno/embeds";
import dotenv from 'dotenv';
import express from "express"; import express from "express";
import { import { BOT_ID, EVENT_HANDLER_URL } from "../configs.js";
BOT_ID,
BUGS_ERRORS_REPORT_WEBHOOK,
DEVELOPMENT,
EVENT_HANDLER_AUTHORIZATION,
EVENT_HANDLER_PORT,
EVENT_HANDLER_URL,
} from "../configs.js";
import { bot } from "./bot.js"; import { bot } from "./bot.js";
import { updateDevCommands } from "./utils/slash/updateCommands.js"; import { updateDevCommands } from "./utils/slash/updateCommands.js";
import { webhookURLToIDAndToken } from "./utils/webhook.js"; import { webhookURLToIDAndToken } from "./utils/webhook.js";
dotenv.config()
const BUGS_ERRORS_REPORT_WEBHOOK = process.env.BUGS_ERRORS_REPORT_WEBHOOK as string
const DEVELOPMENT = process.env.DEVELOPMENT as string
const EVENT_HANDLER_AUTHORIZATION = process.env.EVENT_HANDLER_AUTHORIZATION as string
const EVENT_HANDLER_PORT = process.env.EVENT_HANDLER_PORT as string
process process
.on("unhandledRejection", (error) => { .on("unhandledRejection", (error) => {
const { id, token } = webhookURLToIDAndToken(BUGS_ERRORS_REPORT_WEBHOOK); const { id, token } = webhookURLToIDAndToken(BUGS_ERRORS_REPORT_WEBHOOK);
@@ -28,6 +30,8 @@ process
if (!error) return; if (!error) return;
// ReferenceError: publishMessage is not defined
/*
const embeds = new Embeds() const embeds = new Embeds()
.setDescription(["```js", error, "```"].join(`\n`)) .setDescription(["```js", error, "```"].join(`\n`))
.setTimestamp() .setTimestamp()
@@ -35,6 +39,7 @@ process
// SEND ERROR TO THE LOG CHANNEL ON THE DEV SERVER // SEND ERROR TO THE LOG CHANNEL ON THE DEV SERVER
return bot.helpers.sendWebhookMessage(bot.transformers.snowflake(id), token, { embeds }).catch(console.error); return bot.helpers.sendWebhookMessage(bot.transformers.snowflake(id), token, { embeds }).catch(console.error);
*/
}) })
.on("uncaughtException", async (error) => { .on("uncaughtException", async (error) => {
const { id, token } = webhookURLToIDAndToken(BUGS_ERRORS_REPORT_WEBHOOK); const { id, token } = webhookURLToIDAndToken(BUGS_ERRORS_REPORT_WEBHOOK);
@@ -50,13 +55,14 @@ process
if (!error) process.exit(1); if (!error) process.exit(1);
/*
const embeds = new Embeds() const embeds = new Embeds()
.setDescription(["```js", error.stack, "```"].join(`\n`)) .setDescription(["```js", error.stack, "```"].join(`\n`))
.setTimestamp() .setTimestamp()
.setFooter("Unhandled Exception Error Occurred"); .setFooter("Unhandled Exception Error Occurred");
// SEND ERROR TO THE LOG CHANNEL ON THE DEV SERVER
// SEND ERROR TO THE LOG CHANNEL ON THE DEV SERVER await bot.helpers.sendWebhookMessage(bot.transformers.snowflake(id), token, { embeds }).catch(console.error);
await bot.helpers.sendWebhookMessage(bot.transformers.snowflake(id), token, { embeds }).catch(console.error); */
process.exit(1); process.exit(1);
}); });
@@ -1,10 +1,11 @@
import Embeds from "discordeno/embeds"; import Embeds from "discordeno/embeds";
import { MISSING_TRANSLATION_WEBHOOK } from "../../configs.js";
import { bot } from "../bot.js"; import { bot } from "../bot.js";
import { webhookURLToIDAndToken } from "../utils/webhook.js"; import { webhookURLToIDAndToken } from "../utils/webhook.js";
import english from "./english.js"; import english from "./english.js";
import languages from "./languages.js"; import languages from "./languages.js";
const MISSING_TRANSLATION_WEBHOOK = process.env.MISSING_TRANSLATION_WEBHOOK as string;
/** This should hold the language names per guild id. <guildId, language> */ /** This should hold the language names per guild id. <guildId, language> */
export const serverLanguages = new Map<bigint, keyof typeof languages>(); export const serverLanguages = new Map<bigint, keyof typeof languages>();
+8 -11
View File
@@ -1,13 +1,10 @@
// This file allows you to tell typescript about any additions you have made to the internal discordeno objects. // This file allows you to tell typescript about any additions you have made to the internal discordeno objects.
import { InteractionResponse, Message } from "discordeno"; import { Interaction, InteractionResponse, Message } from "discordeno";
declare module "discordeno" { export interface InteractionWithCustomProps extends Interaction {
// We want to add something to the Interaction object. // Normally, to send a response you would have to do something like bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { type: InteractionResponseTypes.ChannelMessageWithSource, data: { content: "text here" } })
interface Interaction { // But with this reply method we added, it is as simple as interaction.reply("text here").
// Normally, to send a response you would have to do something like bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { type: InteractionResponseTypes.ChannelMessageWithSource, data: { content: "text here" } }) // Feel free to delete these comments once you have understood the concept.
// But with this reply method we added, it is as simple as interaction.reply("text here"). /** Send a reply to an interaction. */
// Feel free to delete these comments once you have understood the concept. reply(response: InteractionResponse | string): Promise<Message | undefined>;
/** Send a reply to an interaction. */ }
reply(response: InteractionResponse | string): Promise<Message | undefined>;
}
}
@@ -7,10 +7,11 @@ import {
Member, Member,
PermissionStrings, PermissionStrings,
Role, Role,
User, User
} from "discordeno"; } from "discordeno";
import english from "../../languages/english.js"; import english from "../../languages/english.js";
import { translationKeys } from "../../languages/translate.js"; import { translationKeys } from "../../languages/translate.js";
import { InteractionWithCustomProps } from "../../typings/discordeno.js";
import { PermissionLevelHandlers } from "./permLevels.js"; import { PermissionLevelHandlers } from "./permLevels.js";
export function createCommand<T extends readonly ArgumentDefinition[]>(command: Command<T>) { export function createCommand<T extends readonly ArgumentDefinition[]>(command: Command<T>) {
@@ -156,79 +157,79 @@ export type ConvertArgumentDefinitionsToArgs<T extends readonly ArgumentDefiniti
UnionToIntersection< UnionToIntersection<
{ {
[P in keyof T]: T[P] extends StringOptionalArgumentDefinition<infer N> // STRING [P in keyof T]: T[P] extends StringOptionalArgumentDefinition<infer N> // STRING
? { ? {
// @ts-ignore TODO: fix this some day // @ts-ignore TODO: fix this some day
[_ in getName<N>]?: T[P]["choices"] extends readonly { name: string; value: string }[] // @ts-ignore [_ in getName<N>]?: T[P]["choices"] extends readonly { name: string; value: string }[] // @ts-ignore
? T[P]["choices"][number]["value"] ? T[P]["choices"][number]["value"]
: string; : string;
} }
: T[P] extends StringArgumentDefinition<infer N> ? { : T[P] extends StringArgumentDefinition<infer N> ? {
// @ts-ignore TODO: fix this some day // @ts-ignore TODO: fix this some day
[_ in getName<N>]: T[P]["choices"] extends readonly { name: string; value: string }[] // @ts-ignore [_ in getName<N>]: T[P]["choices"] extends readonly { name: string; value: string }[] // @ts-ignore
? T[P]["choices"][number]["value"] ? T[P]["choices"][number]["value"]
: string; : string;
} }
// INTEGER // INTEGER
: T[P] extends IntegerOptionalArgumentDefinition<infer N> ? { : T[P] extends IntegerOptionalArgumentDefinition<infer N> ? {
[_ in getName<N>]?: T[P]["choices"] extends readonly { name: string; value: number }[] // @ts-ignore [_ in getName<N>]?: T[P]["choices"] extends readonly { name: string; value: number }[] // @ts-ignore
? T[P]["choices"][number]["value"] ? T[P]["choices"][number]["value"]
: number; : number;
} }
: T[P] extends IntegerArgumentDefinition<infer N> ? { : T[P] extends IntegerArgumentDefinition<infer N> ? {
[_ in getName<N>]: T[P]["choices"] extends readonly { name: string; value: number }[] // @ts-ignore [_ in getName<N>]: T[P]["choices"] extends readonly { name: string; value: number }[] // @ts-ignore
? T[P]["choices"][number]["value"] ? T[P]["choices"][number]["value"]
: number; : number;
} }
// BOOLEAN // BOOLEAN
: T[P] extends BooleanOptionalArgumentDefinition<infer N> ? { [_ in getName<N>]?: boolean } : T[P] extends BooleanOptionalArgumentDefinition<infer N> ? { [_ in getName<N>]?: boolean }
: T[P] extends BooleanArgumentDefinition<infer N> ? { [_ in getName<N>]: boolean } : T[P] extends BooleanArgumentDefinition<infer N> ? { [_ in getName<N>]: boolean }
// USER // USER
: T[P] extends UserOptionalArgumentDefinition<infer N> ? { : T[P] extends UserOptionalArgumentDefinition<infer N> ? {
[_ in getName<N>]?: { [_ in getName<N>]?: {
user: User; user: User;
member: Member; member: Member;
}; };
} }
: T[P] extends UserArgumentDefinition<infer N> ? { : T[P] extends UserArgumentDefinition<infer N> ? {
[_ in getName<N>]: { [_ in getName<N>]: {
user: User; user: User;
member: Member; member: Member;
}; };
} }
// CHANNEL // CHANNEL
: T[P] extends ChannelOptionalArgumentDefinition<infer N> ? { [_ in getName<N>]?: Channel } : T[P] extends ChannelOptionalArgumentDefinition<infer N> ? { [_ in getName<N>]?: Channel }
: T[P] extends ChannelArgumentDefinition<infer N> ? { [_ in getName<N>]: Channel } : T[P] extends ChannelArgumentDefinition<infer N> ? { [_ in getName<N>]: Channel }
// ROLE // ROLE
: T[P] extends RoleOptionalArgumentDefinition<infer N> ? { [_ in getName<N>]?: Role } : T[P] extends RoleOptionalArgumentDefinition<infer N> ? { [_ in getName<N>]?: Role }
: T[P] extends RoleArgumentDefinition<infer N> ? { [_ in getName<N>]: Role } : T[P] extends RoleArgumentDefinition<infer N> ? { [_ in getName<N>]: Role }
// MENTIONABLE // MENTIONABLE
: T[P] extends MentionableOptionalArgumentDefinition<infer N> ? { : T[P] extends MentionableOptionalArgumentDefinition<infer N> ? {
[_ in getName<N>]?: [_ in getName<N>]?:
| Role | Role
| { | {
user: User; user: User;
member: Member; member: Member;
}; };
} }
: T[P] extends MentionableArgumentDefinition<infer N> ? { : T[P] extends MentionableArgumentDefinition<infer N> ? {
[_ in getName<N>]: [_ in getName<N>]:
| Role | Role
| { | {
user: User; user: User;
member: Member; member: Member;
}; };
} }
// SUBCOMMAND // SUBCOMMAND
: T[P] extends SubcommandArgumentDefinition<infer N> ? { : T[P] extends SubcommandArgumentDefinition<infer N> ? {
[_ in getName<N>]?: T[P]["options"] extends readonly ArgumentDefinition[] // @ts-ignore somehow this check does not work [_ in getName<N>]?: T[P]["options"] extends readonly ArgumentDefinition[] // @ts-ignore somehow this check does not work
? ConvertArgumentDefinitionsToArgs<T[P]["options"]> ? ConvertArgumentDefinitionsToArgs<T[P]["options"]>
: {}; : {};
} }
// SUBCOMMANDGROUP // SUBCOMMANDGROUP
: T[P] extends SubcommandGroupArgumentDefinition<infer N> ? { : T[P] extends SubcommandGroupArgumentDefinition<infer N> ? {
[_ in getName<N>]?: ConvertArgumentDefinitionsToArgs<T[P]["options"]>; [_ in getName<N>]?: ConvertArgumentDefinitionsToArgs<T[P]["options"]>;
} }
: never; : never;
}[number] }[number]
> >
>; >;
@@ -244,7 +245,7 @@ export interface Command<T extends readonly ArgumentDefinition[]> {
/** The options for the command, used for both slash and message commands. */ /** The options for the command, used for both slash and message commands. */
// options?: ApplicationCommandOption[]; // options?: ApplicationCommandOption[];
options?: T; options?: T;
execute: (bot: Bot, data: Interaction, args: ConvertArgumentDefinitionsToArgs<T>) => unknown; execute: (bot: Bot, data: InteractionWithCustomProps, args: ConvertArgumentDefinitionsToArgs<T>) => unknown;
subcommands?: Record<string, Omit<Command<any>, "subcommands"> & { group?: string }>; subcommands?: Record<string, Omit<Command<any>, "subcommands"> & { group?: string }>;
/** Whether the command should have a cooldown */ /** Whether the command should have a cooldown */
cooldown?: { cooldown?: {
@@ -272,8 +273,8 @@ export interface Command<T extends readonly ArgumentDefinition[]> {
acknowledge?: boolean; acknowledge?: boolean;
permissionLevels?: permissionLevels?:
| (keyof typeof PermissionLevelHandlers)[] | (keyof typeof PermissionLevelHandlers)[]
| ((data: Interaction, command: Command<T>) => boolean | Promise<boolean>); | ((data: Interaction, command: Command<T>) => boolean | Promise<boolean>);
botServerPermissions?: PermissionStrings[]; botServerPermissions?: PermissionStrings[];
botChannelPermissions?: PermissionStrings[]; botChannelPermissions?: PermissionStrings[];
userServerPermissions?: PermissionStrings[]; userServerPermissions?: PermissionStrings[];
@@ -1,11 +1,12 @@
import { ApplicationCommandOption, ApplicationCommandTypes, Bot } from "discordeno"; import { ApplicationCommandOption, ApplicationCommandTypes, Bot } from "discordeno";
import { DEV_SERVER_ID } from "../../../configs.js";
import { prisma } from "../../../prisma.js"; import { prisma } from "../../../prisma.js";
import { bot } from "../../bot.js"; import { bot } from "../../bot.js";
import COMMANDS from "../../commands/mod.js"; import COMMANDS from "../../commands/mod.js";
import { serverLanguages, translate } from "../../languages/translate.js"; import { serverLanguages, translate } from "../../languages/translate.js";
import { ArgumentDefinition } from "./createCommand.js"; import { ArgumentDefinition } from "./createCommand.js";
const DEV_SERVER_ID = process.env.DEV_SERVER_ID as string;
export async function updateDevCommands(bot: Bot) { export async function updateDevCommands(bot: Bot) {
const cmds = Object.entries(COMMANDS) const cmds = Object.entries(COMMANDS)
// ONLY DEV COMMANDS // ONLY DEV COMMANDS
+6 -85
View File
@@ -1,54 +1,12 @@
import { getBotIdFromToken, Intents } from "discordeno"; import { getBotIdFromToken, Intents } from "discordeno";
import dotenv from 'dotenv';
// General Configurations dotenv.config()
/** Whether or not this process is a local development version. Change to false for the main production bot. */
// SETUP-DD-TEMP: Change this to false in your server for production bot. Keep true in local testing.
export const DEVELOPMENT = true;
/** The server id where you develop/test the bot. */
// SETUP-DD-TEMP: Change the id to a server where you develop the bot privately.
export const DEV_SERVER_ID: string = "";
/** The discord bot token, without the BOT prefix. */
// SETUP-DD-TEMP: Add the bot token here.
export const DISCORD_TOKEN = "";
/** The bot id, derived from the bot token. */ /** The bot id, derived from the bot token. */
export const BOT_ID = getBotIdFromToken(DISCORD_TOKEN); export const BOT_ID = getBotIdFromToken(process.env.DISCORD_TOKEN as string);
export const EVENT_HANDLER_URL = `http://${process.env.EVENT_HANDLER_HOST}:${process.env.EVENT_HANDLER_PORT}`;
// Bot Configurations export const REST_URL = `http://${process.env.REST_HOST}:${process.env.REST_PORT}`;
/** The secret passcode that the bot code (event handler) is listening for. This is used to prevent someone else from trying to send malicious messages to your bot. */ export const GATEWAY_URL = `http://${process.env.GATEWAY_HOST}:${process.env.GATEWAY_PORT}`;
// SETUP-DD-TEMP: Add a secret passcode here.
export const EVENT_HANDLER_AUTHORIZATION = "";
/** The port where the event handler is listening for events. */
// SETUP-DD-TEMP: Set the desired port where events will be sent to be processed.
export const EVENT_HANDLER_PORT = 8081;
/** The url where the bot code(event handler) will run. This is where the gateway will send its messages to. */
// SETUP-DD-TEMP: Set the bot's url here.
export const EVENT_HANDLER_URL = `http://localhost:${EVENT_HANDLER_PORT}`;
/** The full webhook url where the bot can send errors to alert you that the bot is missing translations. */
// SETUP-DD-TEMP: Set a full discord webhook url here.
export const MISSING_TRANSLATION_WEBHOOK = "";
/** The full webhook url where the bot can send errors to alert you that the bot is throwing errors. */
// SETUP-DD-TEMP: Set a full discord webhook url here.
export const BUGS_ERRORS_REPORT_WEBHOOK = "";
// Rest Proxy Configurations
/** The authorization code that the REST proxy will check for to make sure the requests are coming from you. */
// SETUP-DD-TEMP: Add a secret passcode here.
export const REST_AUTHORIZATION = "";
/** The port that will run the REST proxy. */
// SETUP-DD-TEMP: Choose the port number here that will be used for the REST proxy.
export const REST_PORT = 8000;
/** The url where requests will be sent to from the bot. */
// SETUP-DD-TEMP: Provide the url where you will host your REST proxy. If it is on the same server as others, you can use localhost but if it is on a separate server you should change this entirely.
export const REST_URL = `http://localhost:${REST_PORT}`;
// Gateway Proxy Configurations // Gateway Proxy Configurations
/** The gateway intents you would like to use. */ /** The gateway intents you would like to use. */
@@ -69,40 +27,3 @@ export const INTENTS: Intents =
Intents.GuildVoiceStates | Intents.GuildVoiceStates |
Intents.GuildWebhooks | Intents.GuildWebhooks |
Intents.Guilds; Intents.Guilds;
/** The amount of shards to start. Useful with multiple servers where each server is handling a portion of your bot. */
// SETUP-DD-TEMP: To start all bots, leave it as undefined. Specify he number of shards this process should handle.
export const TOTAL_SHARDS: number | undefined = undefined;
/** The amount of shards to start per worker. */
// SETUP-DD-TEMP: Choose how many shards to start per worker. If you are not sure just stick to 16.
export const SHARDS_PER_WORKER: number = 16;
/** The total amount of workers to start. Generally this should be equal to the number of cores your server has. */
// SETUP-DD-TEMP: Choose how many workers to start up. If you are not sure, check how many cores your server has.
export const TOTAL_WORKERS: number = 4;
/** The secret passcode that the gateway is listening for. This is used to prevent someone else from trying to send malicious messages to your bot. */
// SETUP-DD-TEMP: Add a secret passcode here.
export const GATEWAY_AUTHORIZATION = "";
/** The host where the gateway will run. Must follow https://nodejs.org/api/net.html#serverlistenoptions-callback. */
// SETUP-DD-TEMP: Set the gateways's host here.
export const GATEWAY_HOST = "localhost";
/** The port where the gateway will run. This is where the bot will send its messages to the gateway. */
// SETUP-DD-TEMP: Set the gateways's port here.
export const GATEWAY_PORT: number = 8080;
/** The url where the gateway will run. This is where the bot will send its messages to the gateway. */
// SETUP-DD-TEMP: Set the gateways's url here.
export const GATEWAY_URL = `${GATEWAY_HOST}:${GATEWAY_PORT}`;
// Database Configurations
/** These INFLUX configs are only if you wish to enable analytics. */
// SETUP-DD-TEMP: This is optional. If you want to build analytics, add influxdb here.
export const INFLUX_BUCKET = "";
export const INFLUX_ORG = "";
export const INFLUX_TOKEN = "";
export const INFLUX_URL = "";
+17 -17
View File
@@ -3,21 +3,21 @@ import { createLogger } from "discordeno/logger";
import fastify from "fastify"; import fastify from "fastify";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { Worker } from "worker_threads"; import { Worker } from "worker_threads";
import { import { EVENT_HANDLER_URL, INTENTS, REST_URL } from "../configs.js";
DISCORD_TOKEN, import { WorkerCreateData, WorkerGetShardInfo, WorkerMessage, WorkerShardInfo, WorkerShardPayload } from "./worker";
EVENT_HANDLER_AUTHORIZATION,
EVENT_HANDLER_URL, import dotenv from "dotenv";
GATEWAY_AUTHORIZATION, dotenv.config()
GATEWAY_HOST,
GATEWAY_PORT, const DISCORD_TOKEN = process.env.DISCORD_TOKEN as string
INTENTS, const EVENT_HANDLER_AUTHORIZATION = process.env.EVENT_HANDLER_AUTHORIZATION as string
REST_AUTHORIZATION, const GATEWAY_AUTHORIZATION = process.env.GATEWAY_AUTHORIZATION as string
REST_URL, const GATEWAY_HOST = process.env.GATEWAY_HOST as string
SHARDS_PER_WORKER, const GATEWAY_PORT = Number(process.env.GATEWAY_PORT as string)
TOTAL_SHARDS, const REST_AUTHORIZATION = process.env.REST_AUTHORIZATION as string
TOTAL_WORKERS, const SHARDS_PER_WORKER = Number(process.env.SHARDS_PER_WORKER as string)
} from "../configs.js"; const TOTAL_SHARDS = process.env.TOTAL_SHARDS ? Number(process.env.TOTAL_SHARDS) : undefined
import { WorkerCreateData, WorkerGetShardInfo, WorkerMessage, WorkerShardInfo, WorkerShardPayload } from "./worker.js"; const TOTAL_WORKERS = Number(process.env.TOTAL_WORKERS as string)
async function main() { async function main() {
const log = createLogger({ name: "[MANAGER]" }); const log = createLogger({ name: "[MANAGER]" });
@@ -45,7 +45,7 @@ async function main() {
shardsPerWorker: SHARDS_PER_WORKER, shardsPerWorker: SHARDS_PER_WORKER,
totalWorkers: TOTAL_WORKERS, totalWorkers: TOTAL_WORKERS,
handleDiscordPayload: () => {}, handleDiscordPayload: () => { },
tellWorkerToIdentify: async (_gateway, workerId, shardId, _bucketId) => { tellWorkerToIdentify: async (_gateway, workerId, shardId, _bucketId) => {
log.info("TELL TO IDENTIFY", { workerId, shardId, _bucketId }); log.info("TELL TO IDENTIFY", { workerId, shardId, _bucketId });
@@ -81,7 +81,7 @@ async function main() {
workerId, workerId,
}; };
const worker = new Worker("./worker.js", { const worker = new Worker("./dist/gateway/worker.js", {
workerData, workerData,
}); });
+3 -2
View File
@@ -6,11 +6,12 @@ import {
GatewayEventNames, GatewayEventNames,
Shard, Shard,
ShardSocketRequest, ShardSocketRequest,
ShardState, ShardState
} from "discordeno"; } from "discordeno";
import { createLogger } from "discordeno/logger"; import { createLogger } from "discordeno/logger";
import fetch from "node-fetch";
import { parentPort, workerData } from "worker_threads"; import { parentPort, workerData } from "worker_threads";
import { ManagerMessage } from "./index.js"; import { ManagerMessage } from "./index";
if (!parentPort) { if (!parentPort) {
throw new Error("Parent port is null"); throw new Error("Parent port is null");
+9 -2
View File
@@ -1,9 +1,15 @@
import { Point } from "@influxdata/influxdb-client"; import { Point } from "@influxdata/influxdb-client";
import { BASE_URL, createRestManager } from "discordeno"; import { BASE_URL, createRestManager } from "discordeno";
import express, { Request, Response } from "express"; import express, { Request, Response } from "express";
import { Influx } from "../analytics";
import { REST_URL } from '../configs';
import { Influx } from "../analytics.js"; import dotenv from "dotenv";
import { DISCORD_TOKEN, REST_AUTHORIZATION, REST_PORT, REST_URL } from "../configs.js"; dotenv.config()
const DISCORD_TOKEN = process.env.DISCORD_TOKEN as string
const REST_AUTHORIZATION = process.env.REST_AUTHORIZATION as string
const REST_PORT = process.env.REST_PORT as string
const rest = createRestManager({ const rest = createRestManager({
token: DISCORD_TOKEN, token: DISCORD_TOKEN,
@@ -55,6 +61,7 @@ if (Influx) {
}, 30000); }, 30000);
} }
//@ts-ignore
rest.convertRestError = (errorStack, data) => { rest.convertRestError = (errorStack, data) => {
if (!data) return { message: errorStack.message }; if (!data) return { message: errorStack.message };
return { ...data, message: errorStack.message }; return { ...data, message: errorStack.message };
+12 -5
View File
@@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es2020", "target": "es2020",
"module": "CommonJS", "module": "es2020",
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"outDir": "./dist", "outDir": "./dist",
@@ -10,7 +10,6 @@
"importHelpers": true, "importHelpers": true,
"allowUnusedLabels": false, "allowUnusedLabels": false,
"noImplicitOverride": true, "noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
@@ -20,7 +19,15 @@
"useUnknownInCatchVariables": false, "useUnknownInCatchVariables": false,
"allowUnreachableCode": false, "allowUnreachableCode": false,
"skipLibCheck": true, "skipLibCheck": true,
"moduleResolution": "node" "moduleResolution": "node",
}, },
"include": ["./src/**/*", ".env"] "include": [
} "./src/**/*",
".env"
],
"ts-node": {
"esm": true,
"experimentalSpecifierResolution": "node",
"swc": true
}
}