Basic commands working

This commit is contained in:
MSWS
2023-04-08 23:38:46 -07:00
parent 0e37d76ddd
commit fb3d375421
17 changed files with 200 additions and 67 deletions

View File

@@ -40,7 +40,6 @@
"error",
4
],
"no-use-before-define": "warn",
"camelcase": "warn",
"block-scoped-var": "error",
"arrow-body-style": [
@@ -168,7 +167,6 @@
"quotes": [
"error",
"double"
],
"no-console": "warn"
]
}
}

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
node_modules/
dist/
*.js

View File

@@ -1,2 +1,3 @@
{
"TOKEN": ""
}

View File

@@ -0,0 +1,8 @@
{
"commands": [
{
"name": "foo",
"enabled": false
}
]
}

8
package-lock.json generated
View File

@@ -14,7 +14,7 @@
"@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"eslint": "^8.37.0",
"eslint-plugin-jsdoc": "^40.1.1",
"eslint-plugin-jsdoc": "^40.1.2",
"typescript": "^5.0.3"
}
},
@@ -840,9 +840,9 @@
}
},
"node_modules/eslint-plugin-jsdoc": {
"version": "40.1.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.1.1.tgz",
"integrity": "sha512-KxrQCq9pPt7LNeDBlLlnuJMpDFZnEQTs4e25NrT4u5cWmPw2P7F03F2qwPz0GMdlRZTyMOofuPAdiWytvPubvA==",
"version": "40.1.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.1.2.tgz",
"integrity": "sha512-U4Kt42OVjF0EXOWPEc8pjanT8O1ULvILwgA5k87CnhrCKG4xaJ8Sjsb6CWgDtaemOywN06u86duKU1yMaBp7IQ==",
"dev": true,
"dependencies": {
"@es-joy/jsdoccomment": "~0.37.0",

View File

@@ -1,9 +1,14 @@
{
"scripts": {
"build": "tsc",
"lint": "eslint src --ext .ts",
"start": "npm run build && node dist/index.js"
},
"devDependencies": {
"@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"eslint": "^8.37.0",
"eslint-plugin-jsdoc": "^40.1.1",
"eslint-plugin-jsdoc": "^40.1.2",
"typescript": "^5.0.3"
},
"dependencies": {

View File

@@ -8,8 +8,10 @@ import { TokenEntry } from "./data/configs/Token";
import { Client } from "discord.js";
import { MaulConfig, MaulEntry } from "./data/configs/MAUL";
import { CommandManager } from "./modules/Commands";
import { PingCommand } from "./modules/commands/PingCommand";
import { FooCommand } from "./modules/commands/FooCommand";
class Bot implements DSBot {
export class Bot implements DSBot {
maul: MaulAPI | undefined;
modules: AbstractModuleManager;
config: BotConfig;
@@ -33,6 +35,11 @@ class Bot implements DSBot {
this.maul = new MAUL(conf.TOKEN, conf.URL, conf.IP);
});
});
this.client.on("ready", () => {
console.log(`Successfully logged in as ${this.client.user?.tag}!`);
this.initModules();
});
}
initModules() {
@@ -40,8 +47,9 @@ class Bot implements DSBot {
this.modules.loadModules();
cmds.registerCommand(new PingCommand());
cmds.registerCommand(new FooCommand());
cmds.hookCommand();
}
}
new Bot();

View File

@@ -3,6 +3,7 @@ import { GuildConfig } from "./GuildConfig";
export interface BotConfig extends Config {
getGuildConfig(guildId: string): Promise<GuildConfig>;
getGuildConfigs(): Promise<GuildConfig[]>;
addGuildConfig(config: GuildConfig): void;
}

View File

@@ -1,8 +1,9 @@
import { ApplicationCommandDataResolvable, CommandInteraction, PermissionFlags, PermissionsBitField } from "discord.js";
import { ApplicationCommandData, CommandInteraction, PermissionsBitField } from "discord.js";
export abstract class Command {
abstract data: ApplicationCommandDataResolvable;
abstract data: ApplicationCommandData;
permissions: bigint = PermissionsBitField.All;
global = false;
abstract handleCommand(interaction: CommandInteraction): boolean;
}

View File

@@ -4,7 +4,7 @@ export interface ConfigEntry<T = unknown> {
onUpdate(oldValue: T, newValue: T): void;
}
export interface GuildedConfigEntry<T extends ConfigEntry> {
export interface GuildedConfigEntry extends ConfigEntry {
guildId: string;
}

View File

@@ -1,3 +1,5 @@
import { Config } from "./Config";
export type GuildConfig = Config
export interface GuildConfig extends Config {
guildId: string;
}

View File

@@ -1,9 +1,9 @@
import * as fs from "fs";
import { BotConfig } from "../api/data/BotConfig";
import { GuildConfig } from "../api/data/GuildConfig";
import { ConfigEntry } from "../api/data/Config";
import { GuildConfig } from "../api/data/GuildConfig";
export class FileConfig implements GuildConfig, BotConfig {
export class FileConfig implements BotConfig {
entries: ConfigEntry[] = [];
file: string;
@@ -11,12 +11,23 @@ export class FileConfig implements GuildConfig, BotConfig {
this.file = file;
}
getGuildConfigs(): Promise<GuildConfig[]> {
const files = fs.readdirSync("./guilds");
const configs: GuildConfig[] = [];
for (const file of files) {
const config = new FileGuildConfig(`./guilds/${file}`, file.split(".")[0]);
configs.push(config);
}
return Promise.resolve(configs);
}
addGuildConfig(config: GuildConfig): void {
throw new Error("Method not implemented.");
}
getGuildConfig(guildId: string): Promise<GuildConfig> {
const config = new FileConfig(`./guilds/${guildId}.json`);
const config = new FileGuildConfig(`./guilds/${guildId}.json`, guildId);
return Promise.resolve(config);
}
@@ -28,9 +39,10 @@ export class FileConfig implements GuildConfig, BotConfig {
async getValue(id: string | ConfigEntry): Promise<unknown> {
if (typeof id === "string")
return this.getConfigEntry(id)?.value;
for (const entry of this.entries)
{if (entry.name === id.name)
return entry.value;}
for (const entry of this.entries) {
if (entry.name === id.name)
return entry.value;
}
return undefined;
}
@@ -77,9 +89,14 @@ export class FileConfig implements GuildConfig, BotConfig {
this.entries.splice(this.entries.indexOf(entry), 1);
}
save(): Promise<void> {
// this.entries.forEach(entry => {
// entry.onUpdate();
// });
// JSON stringify entries and write to file
const data: any = {};
this.entries.forEach(entry => {
data[entry.name] = entry.value;
});
fs.writeFileSync(this.file, JSON.stringify(data));
return Promise.resolve();
}
@@ -87,3 +104,12 @@ export class FileConfig implements GuildConfig, BotConfig {
return this.entries.find(entry => entry.name === name);
}
}
export class FileGuildConfig extends FileConfig implements GuildConfig {
guildId: string;
constructor(file: string, guildId: string) {
super(file);
this.guildId = guildId;
}
}

View File

@@ -1,34 +0,0 @@
import { BotConfig, ConfigEntry } from "../api/data/BotConfig";
export class SQLConfig implements BotConfig {
entries: ConfigEntry[] = [];
constructor() {
this.entries = [];
}
getConfigEntry(name: string): ConfigEntry | undefined {
throw new Error("Method not implemented.");
}
getEntries(): Promise<ConfigEntry[]> {
throw new Error("Method not implemented.");
}
getValue(name: string): Promise<unknown> {
throw new Error("Method not implemented.");
}
load(): Promise<void> {
throw new Error("Method not implemented.");
}
removeEntry(entry: ConfigEntry): void {
throw new Error("Method not implemented.");
}
save(): Promise<void> {
throw new Error("Method not implemented.");
}
registerEntry(entry: ConfigEntry): ConfigEntry {
this.entries.push(entry);
return entry;
}
}

3
src/index.ts Normal file
View File

@@ -0,0 +1,3 @@
import { Bot } from "./Bot";
new Bot();

View File

@@ -1,27 +1,40 @@
import { DSBot } from "../api/DSBot";
import { Module } from "../api/Module";
import { Command } from "../api/data/Command";
import { PingCommand } from "./commands/PingCommand";
import { ConfigEntry, GuildedConfigEntry } from "../api/data/Config";
export class CommandManager extends Module {
name = "Commands";
commands: Map<string, Command>;
configs: Map<string, Map<string, CommandConfigEntry>> = new Map();
core = true;
commandsConfigEntry: CommandsConfig;
constructor(main: DSBot) {
super(main);
this.commandsConfigEntry = new CommandsConfig();
this.main = main;
this.commands = new Map();
this.configs = new Map();
main.client.on("interactionCreate", async (interaction) => {
if (!interaction.isCommand())
if (!interaction.isCommand() || !interaction.guildId)
return;
const command = this.commands.get(interaction.commandName);
if (!command)
return;
const config = this.configs.get(interaction.guildId)?.get(command.data.name);
if (config) {
if (!config.enabled) {
interaction.reply({ content: "This command is disabled.", ephemeral: true });
return;
}
}
if (!interaction.memberPermissions?.has(command.permissions))
return;
@@ -30,13 +43,95 @@ export class CommandManager extends Module {
}
registerCommand(command: Command): void {
console.log("Registering command: " + command.data.name);
this.commands.set(command.data.name, command);
}
onEnable(): void {
this.commands.set("ping", new PingCommand());
async hookCommand(...commands: Command[]): Promise<void> {
if (!commands || commands.length === 0) {
this.hookCommand(...this.commands.values());
return;
}
const guildRegistrations = [];
const guilds = await this.main.client.guilds.fetch();
for (const weakGuild of guilds.values()) {
console.log("Registering commands for guild: " + weakGuild.name);
if (!this.configs.has(weakGuild.id))
this.configs.set(weakGuild.id, new Map());
guildRegistrations.push(
async () => {
const guildConfig = this.configs.get(weakGuild.id);
console.log(`${weakGuild.id} config: ${JSON.stringify(guildConfig)}`);
const deepGuild = await weakGuild.fetch();
for (const cmd of commands.filter(cmd => !cmd.global)) {
if (guildConfig?.has(cmd.data.name)) {
if (!guildConfig.get(cmd.data.name)?.enabled)
continue;
}
console.log("Registering Guild Command: " + cmd.data.name);
deepGuild.commands.create(cmd.data);
}
}
);
}
console.log("Awaiting completion of guild commands...");
await Promise.all(guildRegistrations.map(fn => fn())).catch(console.error);
console.log("Completed guild commands, registering global commands...");
for (const cmd of commands.filter(cmd => cmd.global)) {
console.log("Registering global command: " + cmd.data.name);
this.main.client.application?.commands.create(cmd.data);
}
console.log("Completed global commands.");
}
async onEnable(): Promise<void> {
const guildConfigs = await this.main.config.getGuildConfigs();
const processors = [];
for (const guildConfig of guildConfigs) {
guildConfig.registerEntry(this.commandsConfigEntry);
console.log("Registered guild config: " + guildConfig.guildId);
processors.push(async () => {
const commandMap = this.configs.get(guildConfig.guildId) || new Map();
await guildConfig.load();
const commandEntries = await guildConfig.getValue(this.commandsConfigEntry) as CommandConfigEntry[];
for (const entry of commandEntries)
commandMap.set(entry.name, entry);
this.configs.set(guildConfig.guildId, commandMap);
});
}
await Promise.all(processors.map(fn => fn())).catch(console.error);
}
onDisable(): void {
this.commands.clear();
}
}
export interface CommandConfig extends GuildedConfigEntry {
commands: CommandConfigEntry[];
}
export interface CommandConfigEntry extends ConfigEntry {
name: string;
enabled: boolean;
overrides: unknown | undefined;
}
export class CommandsConfig implements ConfigEntry<CommandConfigEntry[]> {
name = "commands";
value: CommandConfigEntry[] = [];
onUpdate(oldValue: CommandConfigEntry[], newValue: CommandConfigEntry[]): void {
this.value = newValue;
}
}

View File

@@ -0,0 +1,16 @@
import { ApplicationCommandData, CommandInteraction, CacheType, ApplicationCommandType } from "discord.js";
import { Command } from "../../api/data/Command";
export class FooCommand extends Command {
data: ApplicationCommandData = {
name: "foo",
description: "Foobar",
type: ApplicationCommandType.ChatInput
};
handleCommand(interaction: CommandInteraction): boolean {
interaction.reply("Bar");
return true;
}
}

View File

@@ -1,12 +1,13 @@
import { PermissionsBitField, CommandInteraction, CacheType, ApplicationCommandDataResolvable, ApplicationCommandType } from "discord.js";
import { ApplicationCommandData, ApplicationCommandType, CommandInteraction, PermissionsBitField } from "discord.js";
import { Command } from "../../api/data/Command";
export class PingCommand extends Command {
data: ApplicationCommandDataResolvable = {
data: ApplicationCommandData = {
name: "ping",
description: "Ping pong",
description: "Ping!",
type: ApplicationCommandType.ChatInput
};
global = true;
constructor() {
super();
@@ -15,6 +16,7 @@ export class PingCommand extends Command {
handleCommand(interaction: CommandInteraction): boolean {
interaction.reply("Pong!");
return true;
}
}