Started Reimplementing Commands

- reimplemented utils
- reimplemented info
pull/51/head
Trivernis 6 years ago
parent 08dcf2f084
commit c4f3635dd6

@ -1,6 +1,7 @@
const Discord = require("discord.js"),
fs = require('fs-extra'),
logger = require('./lib/logging').getLogger(),
msgLib = require('./lib/MessageLib'),
cmd = require("./lib/cmd"),
guilding = require('./lib/guilding'),
utils = require('./lib/utils'),
@ -21,6 +22,7 @@ class Bot {
this.rotator = null;
this.maindb = null;
this.presences = [];
this.messageHandler = new msgLib.MessageHandler(this.client, logger);
this.guildHandlers = [];
this.userRates = {};
@ -65,7 +67,11 @@ class Bot {
if (config.webservice && config.webservice.enabled)
await this.initializeWebserver();
logger.verbose('Registering commands');
this.registerCommands();
await this.messageHandler
.registerCommandModule(require('./lib/commands/AnilistApiCommands').module, {});
await this.messageHandler
.registerCommandModule(require('./lib/commands/UtilityCommands').module, {bot: this, logger: logger, config: config});
//this.registerCommands();
this.registerCallbacks();
cmd.init(prefix);
}
@ -166,7 +172,6 @@ class Bot {
registerCommands() {
cmd.registerUtilityCommands(prefix, this);
cmd.registerInfoCommands(prefix, this);
cmd.registerAnilistApiCommands(prefix);
}
/**
@ -206,6 +211,7 @@ class Bot {
});
});
/*
this.client.on('message', async (msg) => {
try {
if (msg.author === this.client.user) {
@ -235,7 +241,7 @@ class Bot {
logger.error(err.message);
logger.debug(err.stack);
}
});
});*/
this.client.on('voiceStateUpdate', async (oldMember, newMember) => {
let gh = await this.getGuildHandler(newMember.guild, prefix);

@ -1,4 +1,7 @@
const Discord = require('discord.js');
const Discord = require('discord.js'),
yaml = require('js-yaml'),
fsx = require('fs-extra'),
utils = require('./utils');
const scopes = {
'Global': 0,
@ -9,30 +12,29 @@ const scopes = {
class Answer {
/**
* Creates an new Answer object with func as answer logic.
* Creates an new Answer object with _func as answer logic.
* @param func
*/
constructor(func) {
this.func = func;
this._func = func;
}
/**
* Evaluates the answer string for the answer object.
* If the logic function returns a promise all nested promises get resolved.
* @param message
* @param kwargs
* @param argsString
* @returns {Promise<*>}
*/
async evaluate(message, kwargs, argsString) {
let result = this.func(message, kwargs, argsString);
switch (result.constructor.name) {
case 'Promise':
return await this.evaluate(await result);
default:
let result = this._func(message, kwargs, argsString);
if (result instanceof Promise)
return await utils.resolveNestedPromise(result);
else
return result;
}
}
}
class Command {
@ -69,7 +71,7 @@ class Command {
/**
* Returns rich help embed for this command.
* @returns {Discord.RichEmbed}
* @returns {*|Discord.RichEmbed}
*/
get help() {
return new ExtendedRichEmbed(`Help for ${this.name}`)
@ -102,20 +104,25 @@ class CommandHandler {
*/
handleCommand(commandMessage, message) {
let commandName = commandMessage.match(/^\S+/);
if (commandName.indexOf(this.prefix) > 0) {
commandName = commandName.replace(this.prefix);
if (commandName.length > 0)
commandName = commandName[0];
if (commandName.indexOf(this.prefix) >= 0) {
commandName = commandName.replace(this.prefix, '');
let argsString = commandMessage.replace(/^\S+/, '');
let args = argsString(/\S+/g);
let args = argsString.match(/\S+/g);
let command = this.commands[commandName];
if (command) {
let kwargs = {};
if (args)
for (let i = 0; i < Math.min(command.kwargs, args.length); i++)
kwargs[command.kwargs[i]] = args[i];
return command.answer(message, kwargs, argsString);
} else {
return false;
}
} else {
return false;
}
}
/**
@ -126,16 +133,39 @@ class CommandHandler {
registerCommand(name, command) {
this.commands[name] = command;
}
}
/**
* @abstract
*/
class CommandModule {
/**
* Initializes a CommandModule instance.
* @param scope
*/
constructor(scope) {
this.scope = scope;
}
/**
* Registers a map of commands containing of the name and the command.
* @param commandMap {Map}
* Loads a template for the object property templateFile or the given argument file.
* @returns {Promise<void>}
* @private
*/
registerCommands(commandMap) {
for (let [name, cmd] in commandMap)
this.commands[name] = cmd;
async _loadTemplate(file) {
let templateString = await fsx.readFile(this.templateFile || file, {encoding: 'utf-8'});
this.template = yaml.safeLoad(templateString);
}
/**
* Registering commands after loading a template
* @param commandHandler {CommandHandler}
* @returns {Promise<void>}
*/
async register(commandHandler) { // eslint-disable-line no-unused-vars
}
}
class ExtendedRichEmbed extends Discord.RichEmbed {
@ -165,7 +195,7 @@ class ExtendedRichEmbed extends Discord.RichEmbed {
* @param fields {JSON}
*/
addFields(fields) {
for (let [name, value] in Object.entries(fields))
for (let [name, value] of Object.entries(fields))
this.addNonemptyField(name, value);
}
}
@ -176,6 +206,7 @@ Object.assign(exports, {
Answer: Answer,
Command: Command,
CommandHandler: CommandHandler,
CommandModule: CommandModule,
ExtendedRichEmbed: ExtendedRichEmbed,
CommandScopes: scopes
});

@ -1,4 +1,4 @@
const cmdLib = require('CommandLib'),
const cmdLib = require('./CommandLib'),
config = require('../config.json'),
Discord = require('discord.js'),
promiseWaterfall = require('promise-waterfall');
@ -39,14 +39,32 @@ class MessageHandler {
}
}
/**
* Registers a command module to a command handler.
* @param CommandModule {cmdLib.CommandModule}
* @param options {Object} Options passed to the module constructor.
* @returns {Promise<void>}
*/
async registerCommandModule(CommandModule, options) {
this.logger.info(`Registering command module ${CommandModule.name}...`);
let cmdModule = new CommandModule(options);
await cmdModule.register(this.getHandler(cmdModule.scope));
}
/**
* Registering event handlers.
* @private
*/
_registerEvents() {
this.logger.debug('Registering message event...');
this.discordClient.on('message', async (msg) => {
this.logger.debug(`<${msg.channel.name || 'PRIVATE'}> ${msg.author.name}: ${msg.content}`);
if (msg.author !== this.discordClient.user) {
let sequence = this._parseSyntax(msg);
await this._executeCommandSequence(sequence);
this.logger.debug(`Syntax parsing returned: ${JSON.stringify(sequence)}`);
await this._executeCommandSequence(sequence, msg);
this.logger.debug('Executed command sequence');
}
});
}
@ -57,6 +75,7 @@ class MessageHandler {
* @private
*/
_parseSyntax(message) {
this.logger.debug('Parsing command sequence...');
let commandSequence = [];
let content = message.content;
let strings = content.match(/".+?"/g);
@ -68,7 +87,7 @@ class MessageHandler {
let independentCommands = content // independent command sequende with ;
.split(/(?<!\\);/g)
.map(x => x.replace(/^ +/, ''));
for (let indepCommand in independentCommands)
for (let indepCommand of independentCommands)
commandSequence.push(indepCommand
.split(/(?<!\\)&&/g) // dependend sequence with && (like unix)
.map(x => x.replace(/^ +/, ''))
@ -80,18 +99,24 @@ class MessageHandler {
* Executes a sequence of commands
*/
async _executeCommandSequence(cmdSequence, message) {
let scopeCmdHandler = this._getScopeHandlers(message);
await Promise.all(cmdSequence.map(async (sq) => {
return await promiseWaterfall(sq.map(async (cmd) => {
this.logger.debug(`Executing command sequence: ${JSON.stringify(cmdSequence)} ...`);
let scopeCmdHandler = this._getScopeHandler(message);
await Promise.all(cmdSequence.map((sq) => promiseWaterfall(sq.map((cmd) => async () => {
try {
this.logger.debug(`Executing command ${cmd}`);
let globalResult = await this.globalCmdHandler.handleCommand(cmd, message);
let scopeResult = await scopeCmdHandler.handleCommand(cmd, message);
this.logger.debug(`globalResult: ${globalResult}, scopeResult: ${scopeResult}`);
if (scopeResult)
this._answerMessage(message, scopeResult);
else if (globalResult)
this._answerMessage(message, globalResult);
}));
}));
} catch (err) {
this.logger.verbose(err.message);
this.logger.silly(err.stack);
}
}))));
}
/**
@ -100,7 +125,7 @@ class MessageHandler {
* @private
*/
_getScopeHandler(message) {
if (message.guild)
if (message && message.guild)
return this.guildCmdHandler;
else
return this.userCmdHandler;
@ -113,6 +138,7 @@ class MessageHandler {
* @private
*/
_answerMessage(message, answer) {
this.logger.debug(`Sending answer ${answer}`);
if (answer)
if (answer instanceof Discord.RichEmbed)
message.channel.send('', answer);
@ -120,3 +146,7 @@ class MessageHandler {
message.channel.send(answer);
}
}
Object.assign(exports, {
MessageHandler: MessageHandler
});

@ -1,6 +1,6 @@
const fetch = require('node-fetch'),
fsx = require('fs-extra'),
queryPath = './lib/graphql/AnilistApi',
queryPath = './lib/api/graphql/AnilistApi',
alApiEndpoint = 'https://graphql.anilist.co';
/**

@ -1,8 +1,6 @@
const cmdLib = require('../../CommandLib'),
anilistApi = require('../../api/AnilistApi'),
yaml = require('js-yaml'),
fsx = require('fs-extra'),
templateFile = 'AniListCommandsTemplate.yaml';
location = './lib/commands/AnilistApiCommands';
class RichMediaInfo extends cmdLib.ExtendedRichEmbed {
@ -19,7 +17,7 @@ class RichMediaInfo extends cmdLib.ExtendedRichEmbed {
.setFooter('Provided by AniList.co');
let fields = {
'Genres': mediaInfo.genres.join(' '),
'Studios': mediaInfo.studios.studioList.map(x => `[${x.name}](${x.siteUrl})`),
'Studios': mediaInfo.studios? mediaInfo.studios.studioList.map(x => `[${x.name}](${x.siteUrl})`) : null,
'Scoring': `**AverageScore**: ${mediaInfo.averageScore}\n**Favourites**${mediaInfo.favourites}`,
'Episodes': mediaInfo.episodes,
'Duration': null,
@ -43,55 +41,48 @@ class RichMediaInfo extends cmdLib.ExtendedRichEmbed {
// -- initialize -- //
let template = null;
/**
* Initializes the module.
* @returns {Promise<void>}
* Implementing the AniList commands module.
*/
async function init() {
let templateString = await fsx.readFile(templateFile, {encoding: 'utf-8'});
template = yaml.safeLoad(templateString);
class AniListCommandModule extends cmdLib.CommandModule {
constructor() {
super(cmdLib.CommandScopes.Global);
this.templateFile = location + '/AniListCommandsTemplate.yaml';
this.template = null;
}
/**
* Registers the commands to the CommandHandler.
* @param commandHandler {cmdLib.CommandHandler}
* @returns {Promise<void>}
*/
async function register(commandHandler) {
// creating commands
async register(commandHandler) {
await this._loadTemplate();
let animeSearch = new cmdLib.Command(
template.anime_search,
this.template.anime_search,
new cmdLib.Answer(async (m, k, s) => {
try {
let animeData = await anilistApi.searchAnimeByName(s);
return new RichMediaInfo(animeData);
} catch (err) {
return template.anime_search.not_found;
return this.template.anime_search.not_found;
}
}));
let mangaSearch = new cmdLib.Command(
template.manga_search,
this.template.manga_search,
new cmdLib.Answer(async (m, k, s) => {
try {
let mangaData = await anilistApi.searchMangaByName(s);
return new RichMediaInfo(mangaData);
} catch (err) {
return template.manga_search.not_found;
return this.template.manga_search.not_found;
}
})
);
// registering commands
commandHandler.registerCommand(template.anime_search.name, animeSearch);
commandHandler.registerCommand(template.manga_search.name, mangaSearch);
commandHandler.registerCommand(this.template.anime_search.name, animeSearch);
commandHandler.registerCommand(this.template.manga_search.name, mangaSearch);
}
}
// -- exports -- //
Object.assign(exports, {
init: init,
register: register
'module': AniListCommandModule
});

@ -0,0 +1,34 @@
about:
name: about
description: >
Shows information about this Discord Bot.
permission: all
category: Info
response:
about_icon: |
This icon war created by [blackrose14344](https://www.deviantart.com/blackrose14344).
[Original](https://www.deviantart.com/blackrose14344/art/2B-Chi-B-685771489)
about_creator: |
This bot was created by Trivernis.
More about this bot [here](https://github.com/Trivernis/discordbot.js).
ping:
name: ping
description: >
Answers with the current average ping of the bot.
permission: all
category: Info
uptime:
name: uptime
description: >
Answers with the uptime of the bot.
permission: all
category: Info
guilds:
name: guilds
description: >
Answers with the number of guilds the bot has joined
permission: owner
category: Info

@ -0,0 +1,44 @@
shutdown:
name: shutdown
description: >
Shuts down the bot.
permission: owner
category: Utility
add_presence:
name: addpresence
description: >
Adds a Rich Presence to the bot.
permission: owner
category: Utility
usage: addpresence [presence]
rotate_presence:
name: rotate_presence
description: >
Forces a presence rotation
permission: owner
category: Utility
create_user:
name: create_user
description: >
Creates a user for the webinterface.
permission: owner
category: Utility
args:
- username
- password
- scope
bugreport:
name: bug
description: >
Get information about where to report bugs.
permission: all
category: Utility
response:
title: >
You want to report a bug?
bug_report: >
Please report your bugs [here](https://github.com/Trivernis/discordbot.js/issues)

@ -115,6 +115,18 @@ exports.getSplitDuration = function (duration) {
return retObj;
};
/**
* Resolves a nested promise by resolving it iterative.
* @param promise
* @returns {Promise<*>}
*/
exports.resolveNestedPromise = async function(promise) {
let result = await promise;
while (result instanceof Promise)
result = await result; // eslint-disable-line no-await-in-loop
return result;
};
/* Classes */
exports.YouTube = class {

@ -23,7 +23,7 @@ exports.WebServer = class {
this.app = express();
this.server = null;
this.port = port;
this.schema = buildSchema(fs.readFileSync('./lib/graphql/schema.gql', 'utf-8'));
this.schema = buildSchema(fs.readFileSync('./lib/api/graphql/schema.gql', 'utf-8'));
this.root = {};
}

Loading…
Cancel
Save