Skip to content

Commit

Permalink
Added discord-bot workspace
Browse files Browse the repository at this point in the history
Signed-off-by: Magic <magicoflolis@tuta.io>
  • Loading branch information
magicoflolis committed Jul 28, 2023
1 parent 3e16c5c commit d19ed9b
Show file tree
Hide file tree
Showing 13 changed files with 445 additions and 19 deletions.
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,11 @@ Connectors allow SquadJS to communicate with external resources.

```json
"connectors": {
"discord": "Discord Login Token",
"discord": {
"clientID": "",
"guidID": "",
"token": ""
},
},
```
Connectors should be named, for example the above is named `discord`, and should have the associated config against it. Configs can be specified by name in plugin options. Should a connector not be needed by any plugin then the default values can be left or you can remove it from your config file.
Expand All @@ -137,7 +141,11 @@ See below for more details on connectors and their associated config.
Connects to Discord via `discord.js`.

```json
"discord": "Discord Login Token",
"discord": {
"clientID": "",
"guidID": "",
"token": ""
}
```
Requires a Discord bot login token.

Expand Down
6 changes: 5 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
]
},
"connectors": {
"discord": "Discord Login Token",
"discord": {
"clientID": "",
"guidID": "",
"token": ""
},
"awnAPI": {
"orgID": "YourOrgID",
"creds": {
Expand Down
17 changes: 17 additions & 0 deletions discord-bot/global-commands/ping.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SlashCommandBuilder } from 'discord.js';

const delay = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};

export default {
data: new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'),
async execute(interaction, server = null) {
await interaction.reply({
content: server === null ? 'Bot: Pong!\nSquad: Not connected' : 'Bot: Pong!\nSquad: Pong!',
ephemeral: true
});
await delay(4000);
await interaction.deleteReply();
}
};
233 changes: 233 additions & 0 deletions discord-bot/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { Client, Collection, Events, GatewayIntentBits, REST, Routes } from 'discord.js';
import Logger from 'core/logger';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

/**
* Object is Null
* @param {Object} obj - Object
* @returns {boolean} Returns if statement true or false
*/
const isNull = (obj) => {
return Object.is(obj, null) || Object.is(obj, undefined);
};

/**
* Object is Blank
* @param {(Object|Object[]|string)} obj - Array, Set, Object or String
* @returns {boolean} Returns if statement true or false
*/
const isBlank = (obj) => {
return (
(typeof obj === 'string' && Object.is(obj.trim(), '')) ||
(obj instanceof Set && Object.is(obj.size, 0)) ||
(Array.isArray(obj) && Object.is(obj.length, 0)) ||
(obj instanceof Object &&
typeof obj.entries !== 'function' &&
Object.is(Object.keys(obj).length, 0))
);
};

/**
* Object is Empty
* @param {(Object|Object[]|string)} obj - Array, object or string
* @returns {boolean} Returns if statement true or false
*/
const isEmpty = (obj) => {
return isNull(obj) || isBlank(obj);
};

export default class DiscordBot {
constructor(connectorConfig, server) {
this.client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});

this.token = connectorConfig;
this.clientId = null;
this.guildId = null;
this.server = isNull(server) ? null : server;
this.commands = [];
this.localCommands = [];
this.globalCommands = [];
this.client.commandIndex = new Collection();

if (typeof connectorConfig === 'object') {
this.token = connectorConfig.token;
this.clientId = connectorConfig.clientID;
this.guildId = connectorConfig.guildID;
}

this.client.on(Events.Warn, (info) => {
Logger.verbose('DiscordJS', 1, info);
});
}

auth() {
return new Promise((resolve, reject) => {
Logger.verbose('DiscordJS', 1, 'Logging in...');
this.client.once(Events.ClientReady, (c) => {
Logger.verbose('DiscordJS', 1, `Logged in as ${c.user.tag}`);
resolve(c);
});
this.client.on(Events.Error, reject);
this.client.login(this.token);
});
}

async localCmds() {
if (isEmpty(this.clientId)) {
return;
}
if (isEmpty(this.guildId)) {
return;
}

const dir = await fs.promises.opendir(path.join(__dirname, './local-commands'));
const cmdFilenames = [];
for await (const dirent of dir) {
if (!dirent.isFile()) continue;
cmdFilenames.push(dirent.name);
}
if (isBlank(cmdFilenames)) {
return;
}
for (const cmdFilename of cmdFilenames) {
Logger.verbose('DiscordJS', 1, `Loading command file ${cmdFilename}...`);
const { default: cmdData } = await import(`./local-commands/${cmdFilename}`);
if ('data' in cmdData && 'execute' in cmdData) {
this.localCommands.push(cmdData.data.toJSON());
this.client.commandIndex.set(cmdData.data.name, cmdData);
} else {
Logger.verbose(
'Err',
1,
`The command at "./local-commands/${cmdFilename}" is missing a required "data" or "execute" property.`
);
}
}

const rest = new REST().setToken(this.token);
try {
Logger.verbose(
'DiscordJS',
1,
`Started refreshing ${this.localCommands.length} application (/) commands.`
);

const data = await rest
.put(Routes.applicationGuildCommands(this.clientId, this.guildId), {
body: this.localCommands
})
.catch((ex) => {
Logger.verbose('Err', 1, `Failed to load Routes. Reason: ${ex.message}`, ex.stack);
});

Logger.verbose(
'DiscordJS',
1,
`Successfully reloaded ${data.length} application (/) commands.`
);
} catch (ex) {
Logger.verbose('Err', 1, `Failed to load RESET. Reason: ${ex.message}`, ex.stack);
}
}

async globalCmds() {
if (isEmpty(this.clientId)) {
return;
}

const dir = await fs.promises.opendir(path.join(__dirname, './global-commands'));
const cmdFilenames = [];
for await (const dirent of dir) {
if (!dirent.isFile()) continue;
cmdFilenames.push(dirent.name);
}
if (isBlank(cmdFilenames)) {
return;
}
for (const cmdFilename of cmdFilenames) {
Logger.verbose('DiscordJS', 1, `Loading command file ${cmdFilename}...`);
const { default: cmdData } = await import(`./global-commands/${cmdFilename}`);
if ('data' in cmdData && 'execute' in cmdData) {
this.globalCommands.push(cmdData.data.toJSON());
this.client.commandIndex.set(cmdData.data.name, cmdData);
} else {
Logger.verbose(
'Err',
1,
`The command at "./global-commands/${cmdFilename}" is missing a required "data" or "execute" property.`
);
}
}

const rest = new REST().setToken(this.token);
try {
Logger.verbose(
'DiscordJS',
1,
`Started refreshing ${this.globalCommands.length} application (/) commands.`
);

const data = await rest
.put(Routes.applicationCommands(this.clientId), { body: this.globalCommands })
.catch((ex) => {
Logger.verbose('Err', 1, `Failed to load Routes. Reason: ${ex.message}`, ex.stack);
});

Logger.verbose(
'DiscordJS',
1,
`Successfully reloaded ${data.length} application (/) commands.`
);
} catch (ex) {
Logger.verbose('Err', 1, `Failed to load RESET. Reason: ${ex.message}`, ex.stack);
}
}

async getCommands() {
this.client.on(Events.InteractionCreate, async (interaction) => {
const command = interaction.client.commandIndex.get(interaction.commandName);
if (!command) {
Logger.verbose('Err', 1, `No command matching ${interaction.commandName} was found.`);
return;
}
try {
if (interaction.isChatInputCommand()) {
await command.execute(interaction, this.server);
} else if (interaction.isAutocomplete()) {
await command.autocomplete(interaction, this.server);
}
} catch (ex) {
Logger.verbose('Err', 1, `Failed to execute Interaction. Reason: ${ex.message}`, ex.stack);
if (interaction.isChatInputCommand()) {
if (interaction.replied || interaction.deferred) {
await interaction.followUp({
content: 'There was an error while executing this command!',
ephemeral: true
});
} else {
await interaction.reply({
content: 'There was an error while executing this command!',
ephemeral: true
});
}
} else if (interaction.isAutocomplete()) {
await interaction.respond([
{ name: 'There was an error while executing this command!', value: '' }
]);
}
}
});
await this.localCmds();
await this.globalCmds();
}
}
25 changes: 25 additions & 0 deletions discord-bot/local-commands/close.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';

const delay = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};

export default {
data: new SlashCommandBuilder()
.setName('close')
.setDescription('Exit node process')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.setDMPermission(false),
async execute(interaction, server) {
if (server !== null) {
await server.unwatch();
}
await interaction.reply({
content: `Shutting down discord bot${server !== null ? ' + SquadJS' : ''}...`,
ephemeral: true
});
await delay(2000);
await interaction.deleteReply();
process.exit(0);
}
};
Loading

0 comments on commit d19ed9b

Please sign in to comment.