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"), const Discord = require("discord.js"),
fs = require('fs-extra'), fs = require('fs-extra'),
logger = require('./lib/logging').getLogger(), logger = require('./lib/logging').getLogger(),
msgLib = require('./lib/MessageLib'),
cmd = require("./lib/cmd"), cmd = require("./lib/cmd"),
guilding = require('./lib/guilding'), guilding = require('./lib/guilding'),
utils = require('./lib/utils'), utils = require('./lib/utils'),
@ -21,6 +22,7 @@ class Bot {
this.rotator = null; this.rotator = null;
this.maindb = null; this.maindb = null;
this.presences = []; this.presences = [];
this.messageHandler = new msgLib.MessageHandler(this.client, logger);
this.guildHandlers = []; this.guildHandlers = [];
this.userRates = {}; this.userRates = {};
@ -65,7 +67,11 @@ class Bot {
if (config.webservice && config.webservice.enabled) if (config.webservice && config.webservice.enabled)
await this.initializeWebserver(); await this.initializeWebserver();
logger.verbose('Registering commands'); 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(); this.registerCallbacks();
cmd.init(prefix); cmd.init(prefix);
} }
@ -166,7 +172,6 @@ class Bot {
registerCommands() { registerCommands() {
cmd.registerUtilityCommands(prefix, this); cmd.registerUtilityCommands(prefix, this);
cmd.registerInfoCommands(prefix, this); cmd.registerInfoCommands(prefix, this);
cmd.registerAnilistApiCommands(prefix);
} }
/** /**
@ -206,6 +211,7 @@ class Bot {
}); });
}); });
/*
this.client.on('message', async (msg) => { this.client.on('message', async (msg) => {
try { try {
if (msg.author === this.client.user) { if (msg.author === this.client.user) {
@ -235,7 +241,7 @@ class Bot {
logger.error(err.message); logger.error(err.message);
logger.debug(err.stack); logger.debug(err.stack);
} }
}); });*/
this.client.on('voiceStateUpdate', async (oldMember, newMember) => { this.client.on('voiceStateUpdate', async (oldMember, newMember) => {
let gh = await this.getGuildHandler(newMember.guild, prefix); 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 = { const scopes = {
'Global': 0, 'Global': 0,
@ -9,30 +12,29 @@ const scopes = {
class Answer { class Answer {
/** /**
* Creates an new Answer object with func as answer logic. * Creates an new Answer object with _func as answer logic.
* @param func * @param func
*/ */
constructor(func) { constructor(func) {
this.func = func; this._func = func;
} }
/** /**
* Evaluates the answer string for the answer object. * Evaluates the answer string for the answer object.
* If the logic function returns a promise all nested promises get resolved.
* @param message * @param message
* @param kwargs * @param kwargs
* @param argsString * @param argsString
* @returns {Promise<*>} * @returns {Promise<*>}
*/ */
async evaluate(message, kwargs, argsString) { async evaluate(message, kwargs, argsString) {
let result = this.func(message, kwargs, argsString); let result = this._func(message, kwargs, argsString);
switch (result.constructor.name) { if (result instanceof Promise)
case 'Promise': return await utils.resolveNestedPromise(result);
return await this.evaluate(await result); else
default:
return result; return result;
} }
} }
}
class Command { class Command {
@ -69,7 +71,7 @@ class Command {
/** /**
* Returns rich help embed for this command. * Returns rich help embed for this command.
* @returns {Discord.RichEmbed} * @returns {*|Discord.RichEmbed}
*/ */
get help() { get help() {
return new ExtendedRichEmbed(`Help for ${this.name}`) return new ExtendedRichEmbed(`Help for ${this.name}`)
@ -102,20 +104,25 @@ class CommandHandler {
*/ */
handleCommand(commandMessage, message) { handleCommand(commandMessage, message) {
let commandName = commandMessage.match(/^\S+/); let commandName = commandMessage.match(/^\S+/);
if (commandName.indexOf(this.prefix) > 0) { if (commandName.length > 0)
commandName = commandName.replace(this.prefix); commandName = commandName[0];
if (commandName.indexOf(this.prefix) >= 0) {
commandName = commandName.replace(this.prefix, '');
let argsString = commandMessage.replace(/^\S+/, ''); let argsString = commandMessage.replace(/^\S+/, '');
let args = argsString(/\S+/g); let args = argsString.match(/\S+/g);
let command = this.commands[commandName]; let command = this.commands[commandName];
if (command) {
let kwargs = {}; let kwargs = {};
if (args)
for (let i = 0; i < Math.min(command.kwargs, args.length); i++) for (let i = 0; i < Math.min(command.kwargs, args.length); i++)
kwargs[command.kwargs[i]] = args[i]; kwargs[command.kwargs[i]] = args[i];
return command.answer(message, kwargs, argsString); return command.answer(message, kwargs, argsString);
} else { } else {
return false; return false;
} }
} else {
return false;
}
} }
/** /**
@ -126,16 +133,39 @@ class CommandHandler {
registerCommand(name, command) { registerCommand(name, command) {
this.commands[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. * Loads a template for the object property templateFile or the given argument file.
* @param commandMap {Map} * @returns {Promise<void>}
* @private
*/ */
registerCommands(commandMap) { async _loadTemplate(file) {
for (let [name, cmd] in commandMap) let templateString = await fsx.readFile(this.templateFile || file, {encoding: 'utf-8'});
this.commands[name] = cmd; 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 { class ExtendedRichEmbed extends Discord.RichEmbed {
@ -165,7 +195,7 @@ class ExtendedRichEmbed extends Discord.RichEmbed {
* @param fields {JSON} * @param fields {JSON}
*/ */
addFields(fields) { addFields(fields) {
for (let [name, value] in Object.entries(fields)) for (let [name, value] of Object.entries(fields))
this.addNonemptyField(name, value); this.addNonemptyField(name, value);
} }
} }
@ -176,6 +206,7 @@ Object.assign(exports, {
Answer: Answer, Answer: Answer,
Command: Command, Command: Command,
CommandHandler: CommandHandler, CommandHandler: CommandHandler,
CommandModule: CommandModule,
ExtendedRichEmbed: ExtendedRichEmbed, ExtendedRichEmbed: ExtendedRichEmbed,
CommandScopes: scopes CommandScopes: scopes
}); });

@ -1,4 +1,4 @@
const cmdLib = require('CommandLib'), const cmdLib = require('./CommandLib'),
config = require('../config.json'), config = require('../config.json'),
Discord = require('discord.js'), Discord = require('discord.js'),
promiseWaterfall = require('promise-waterfall'); 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. * Registering event handlers.
* @private * @private
*/ */
_registerEvents() { _registerEvents() {
this.logger.debug('Registering message event...');
this.discordClient.on('message', async (msg) => { 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); 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 * @private
*/ */
_parseSyntax(message) { _parseSyntax(message) {
this.logger.debug('Parsing command sequence...');
let commandSequence = []; let commandSequence = [];
let content = message.content; let content = message.content;
let strings = content.match(/".+?"/g); let strings = content.match(/".+?"/g);
@ -68,7 +87,7 @@ class MessageHandler {
let independentCommands = content // independent command sequende with ; let independentCommands = content // independent command sequende with ;
.split(/(?<!\\);/g) .split(/(?<!\\);/g)
.map(x => x.replace(/^ +/, '')); .map(x => x.replace(/^ +/, ''));
for (let indepCommand in independentCommands) for (let indepCommand of independentCommands)
commandSequence.push(indepCommand commandSequence.push(indepCommand
.split(/(?<!\\)&&/g) // dependend sequence with && (like unix) .split(/(?<!\\)&&/g) // dependend sequence with && (like unix)
.map(x => x.replace(/^ +/, '')) .map(x => x.replace(/^ +/, ''))
@ -80,18 +99,24 @@ class MessageHandler {
* Executes a sequence of commands * Executes a sequence of commands
*/ */
async _executeCommandSequence(cmdSequence, message) { async _executeCommandSequence(cmdSequence, message) {
let scopeCmdHandler = this._getScopeHandlers(message); this.logger.debug(`Executing command sequence: ${JSON.stringify(cmdSequence)} ...`);
await Promise.all(cmdSequence.map(async (sq) => { let scopeCmdHandler = this._getScopeHandler(message);
return await promiseWaterfall(sq.map(async (cmd) => { 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 globalResult = await this.globalCmdHandler.handleCommand(cmd, message);
let scopeResult = await scopeCmdHandler.handleCommand(cmd, message); let scopeResult = await scopeCmdHandler.handleCommand(cmd, message);
this.logger.debug(`globalResult: ${globalResult}, scopeResult: ${scopeResult}`);
if (scopeResult) if (scopeResult)
this._answerMessage(message, scopeResult); this._answerMessage(message, scopeResult);
else if (globalResult) else if (globalResult)
this._answerMessage(message, globalResult); this._answerMessage(message, globalResult);
})); } catch (err) {
})); this.logger.verbose(err.message);
this.logger.silly(err.stack);
}
}))));
} }
/** /**
@ -100,7 +125,7 @@ class MessageHandler {
* @private * @private
*/ */
_getScopeHandler(message) { _getScopeHandler(message) {
if (message.guild) if (message && message.guild)
return this.guildCmdHandler; return this.guildCmdHandler;
else else
return this.userCmdHandler; return this.userCmdHandler;
@ -113,6 +138,7 @@ class MessageHandler {
* @private * @private
*/ */
_answerMessage(message, answer) { _answerMessage(message, answer) {
this.logger.debug(`Sending answer ${answer}`);
if (answer) if (answer)
if (answer instanceof Discord.RichEmbed) if (answer instanceof Discord.RichEmbed)
message.channel.send('', answer); message.channel.send('', answer);
@ -120,3 +146,7 @@ class MessageHandler {
message.channel.send(answer); message.channel.send(answer);
} }
} }
Object.assign(exports, {
MessageHandler: MessageHandler
});

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

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

@ -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; 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 */ /* Classes */
exports.YouTube = class { exports.YouTube = class {

@ -23,7 +23,7 @@ exports.WebServer = class {
this.app = express(); this.app = express();
this.server = null; this.server = null;
this.port = port; 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 = {}; this.root = {};
} }

Loading…
Cancel
Save