Merge pull request #54 from Trivernis/develop

Beta v0.11.0
pull/110/head^2
Trivernis 6 years ago committed by GitHub
commit e52197ce17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,21 @@ All notable changes to the discord bot will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed
- template Files to name `template.yaml`
- loading template file form CommandModule property `templateFile` to loading the `template.yaml` file from the `_templateDir` property (still supporting loading form templateFile)
- ExtendedRichEmbed checks if fields are empty again after replacing values
### Added
- `.template` to commands as a template for a command module with help comments
- *METADATA* property to `template.yaml` files that is used as an anchor for shared command metadata (like `category`)
- `CommandModule` **Misc** with command that are not really fitting into any other module
- option to query this CHANGELOG with `_changes [version]` and `_versions` in the `CommandModule` **Info**
### Removed
- `ExtendedRichEmbed.addNonemptyField` because the overide of `.addField` does the same
## [0.10.1] - 2019-03-03
### Changed
- Bugfix on RichEmbed not returning itself on addField and setDescription because of method overide

@ -86,6 +86,7 @@ class Bot {
messageHandler: this.messageHandler,
config: config
});
await this.messageHandler.registerCommandModule(require('./lib/commands/MiscCommands').module, {});
this.registerEvents();
}

@ -187,13 +187,17 @@ class CommandModule {
}
/**
* Loads a template for the object property templateFile or the given argument file.
* Loads a template for the module from the this._templateDir directory.
* Loads the template from this.templateFile if the attribute exists.
* @param dir {String} Overides the this._templateDir with this directory.
* @returns {Promise<void>}
* @private
*/
async _loadTemplate(file) {
let templateString = await fsx.readFile(this.templateFile || file, {encoding: 'utf-8'});
this._logger.silly(`Loaded Template file ${this.templateFile || file}`);
async _loadTemplate(dir) {
if (!this.templateFile)
this.templateFile = (dir || this._templateDir) + '/template.yaml';
let templateString = await fsx.readFile(this.templateFile, {encoding: 'utf-8'});
this._logger.silly(`Loaded Template file ${this.templateFile}`);
this.template = yaml.safeLoad(templateString);
}
@ -219,18 +223,6 @@ class ExtendedRichEmbed extends Discord.RichEmbed {
this.setTimestamp();
}
/**
* Adds a Field when a name is given or adds a blank Field otherwise
* @param name {String}
* @param content {String}
* @returns {ExtendedRichEmbed}
*/
addNonemptyField(name, content) {
if (name && name.length > 0 && content && content.length > 0)
this.addField(name, content);
return this;
}
/**
* Adds the fields defined in the fields JSON
* @param fields {JSON}
@ -238,7 +230,7 @@ class ExtendedRichEmbed extends Discord.RichEmbed {
*/
addFields(fields) {
for (let [name, value] of Object.entries(fields))
this.addNonemptyField(name, value);
this.addField(name, value);
return this;
}
@ -252,7 +244,8 @@ class ExtendedRichEmbed extends Discord.RichEmbed {
croppedValue = value.substring(0, 1024);
if (croppedValue.length < value.length)
croppedValue = croppedValue.replace(/\n.*$/g, '');
super.setDescription(croppedValue);
if (croppedValue && croppedValue.replace(/\n/g, '').length > 0)
super.setDescription(croppedValue);
return this;
}
@ -267,7 +260,10 @@ class ExtendedRichEmbed extends Discord.RichEmbed {
croppedValue = value.substring(0, 1024);
if (croppedValue.length < value.length)
croppedValue = croppedValue.replace(/\n.*$/g, '');
super.addField(name, croppedValue);
if (name && croppedValue
&& croppedValue.replace(/\n/g, '').length > 0 && name.replace(/\n/g, '').length > 0)
super.addField(name, croppedValue);
return this;
}
}

@ -0,0 +1,45 @@
/* template index.js. Doesn't implement actual commands */
const cmdLib = require('../../CommandLib'); // required for command objects
/**
* A description what the command module includes and why. Doesn't need to list commands but explains
* category of the defined commands aswell as the scope.
*/
class TemplateCommandModule extends cmdLib.CommandModule {
/**
* @param opts {Object} properties: --- define the properties the opts object needs aswell as the type
* bot - the instance of the bot
*/
constructor(opts) {
super(cmdLib.CommandScopes.Global); // call constructor of superclass with the scope of the module
this._templateDir = __dirname; // define the current directory as directory for the template.yaml file
this._bot = opts.bot; // define opts attributes as private properties of the module class
}
/**
* Defines and registers commands to the commandHandler.
* @param commandHandler {CommandHandler}
*/
async register(commandHandler) {
await this._loadTemplate(); // loads the template file to the property this.template.
let templateCommand = new cmdLib.Command( // create a new instance of Command
this.template.template_command, // pass the template to the constructor
new cmdLib.Answer(() => { // pass a new instance of Answer to the constructor
/* Command Logic */
return this.template.response.not_implemented; // this command just returns the answer not_implemented
})
);
// register the commands on the commandHandler
commandHandler.registerCommand(templateCommand); // register the command to the handler
}
}
// set the export properties
Object.assign(exports, {
module: TemplateCommandModule // Export the commandModule as module property. This is the default.
});

@ -0,0 +1,16 @@
# see yaml references (learnxinyminutes.com/docs/yaml/)
METADATA: &METADATA
category: template # [optional if defined in commands]
permission: all # [optional if defined in commands]
template_command:
<<: *METADATA # include the predefined metadata for the command
name: templateCommand # [required] the name of the command for execution
usage: _templateCommand [templateArg] # [optional] overides the default help that generates from name and args
permission: owner # [optional if in METADATA] overiedes the metadata value for permission
description: > # [required] the description entry for the command help.
A template for a command
response: # [optional] predefine responses that can be used in the command logic
not_implemented: >
This command is not implemented.

@ -1,6 +1,5 @@
const cmdLib = require('../../CommandLib'),
anilistApi = require('../../api/AnilistApi'),
location = './lib/commands/AnilistApiCommands';
anilistApi = require('../../api/AnilistApi');
/**
* The AniList commands are all commands that interact with the anilist api.
@ -169,7 +168,7 @@ class AniListCommandModule extends cmdLib.CommandModule {
constructor() {
super(cmdLib.CommandScopes.Global);
this.templateFile = location + '/AniListCommandsTemplate.yaml';
this._templateDir = __dirname;
this.template = null;
}

@ -1,58 +1,57 @@
METADATA: &METADATA
category: AniList
permission: all
anime_search:
<<: *METADATA
name: alAnime
permission: all
usage: alAnime [search query]
description: >
Searches [AniList.co](https://anilist.co) for the anime *title* or *id* and returns information about
it if there is a result. The staff members are not included because the message would grow too big.
category: AniList
response:
not_found: >
I couldn't find the anime you were searching for :(
anime_staff_search:
<<: *METADATA
name: alAnimeStaff
permission: all
usage: alAnimeStaff [search query]
description: >
Searches [AniList.co](https://anilist.co) for the anime *title* or *id* and returns all staff members.
category: AniList
response:
not_found: >
I couldn't find the anime you were searching for :(
manga_search:
<<: *METADATA
name: alManga
permission: all
usage: alManga [search query]
description: >
Searches [AniList.co](https://anilist.co) for the manga *title* or *id* and returns information about
it if there is a result.
category: AniList
response:
not_found: >
I couldn't find the manga you were searching for :(
staff_search:
<<: *METADATA
name: alStaff
permission: all
usage: alStaff [search query]
description: >
Searches [AniList.co](https://anilist.co) for the staff member *name* or *id* and returns information about
the member aswell as roles in media.
category: AniList
response:
not_found: >
I couldn't find the staff member you were searching for :(
character_search:
<<: *METADATA
name: alCharacter
permission: all
usage: alCharacter [search query]
description: >
Searches [AniList.co](https://anilist.co) for the character *name* or *id* and returns information about
the character aswell as media roles.
category: AniList
response:
not_found: >
I couldn't find the character member you were searching for :(

@ -1,6 +1,6 @@
const cmdLib = require('../../CommandLib'),
utils = require('../../utils'),
location = './lib/commands/InfoCommands';
fsx = require('fs-extra'),
utils = require('../../utils');
/**
* Info commands provide information about the bot. These informations are
@ -16,7 +16,7 @@ class InfoCommandModule extends cmdLib.CommandModule {
*/
constructor(opts) {
super(cmdLib.CommandScopes.Global);
this.templateFile = location + '/InfoCommandsTemplate.yaml';
this._templateDir = __dirname;
this._client = opts.client;
this._messageHandler = opts.messageHandler;
}
@ -43,8 +43,58 @@ class InfoCommandModule extends cmdLib.CommandModule {
return helpEmbed;
}
async _loadChangelog() {
try {
let changelog = (await fsx.readFile('CHANGELOG.md', {encoding: 'utf-8'})).replace(/\r\n/g, '\n');
let entries = changelog.split(/\n## /);
let changes = {};
let latestVersion = null;
this._logger.debug(`Found ${entries.length} changelog entries`);
for (let entry of entries) {
let title = '';
let version = '';
let date = '';
let titleMatch = entry.match(/^.*?\n/g);
if (titleMatch && titleMatch.length > 0)
title = titleMatch[0].replace(/\n/, '');
let versionMatch = title.match(/\[.*?]/);
if (versionMatch && versionMatch.length > 0)
version = versionMatch[0].replace(/^\[|]$/g, '');
if (!latestVersion && version && version.length > 0)
latestVersion = version;
let dateMatch = title.match(/\d{4}-\d{2}-\d{2}/);
if (version && version.length > 0) {
changes[version] = {
date: date,
title: title,
segments: {}
};
if (dateMatch && dateMatch.length > 0)
date = dateMatch[0];
let segments = entry.replace(title.replace(/\n/, ''), '').split(/\n### /);
for (let segment of segments) {
let segmentTitle = '';
let titleMatch = segment.match(/^.*?\n/);
if (titleMatch && titleMatch.length > 0)
segmentTitle = titleMatch[0].replace(/\n/, '');
changes[version].segments[segmentTitle] = segment.replace(segmentTitle, '');
}
}
}
changes.latest = changes[latestVersion];
this._changes = changes;
} catch (err) {
this._logger.warn(err.message);
this._logger.debug(err.stack);
}
}
async register(commandHandler) {
await this._loadTemplate();
await this._loadChangelog();
let about = new cmdLib.Command(
this.template.about,
@ -99,13 +149,52 @@ class InfoCommandModule extends cmdLib.CommandModule {
})
);
let changes = new cmdLib.Command(
this.template.changes,
new cmdLib.Answer((m, k) => {
try {
if (!k.version)
return new cmdLib.ExtendedRichEmbed(this._changes.latest.title)
.addFields(this._changes.latest.segments)
.setColor(this.template.changes.embed_color)
.attachFile('CHANGELOG.md');
else
return new cmdLib.ExtendedRichEmbed(this._changes[k.version].title)
.addFields(this._changes[k.version].segments)
.setColor(this.template.changes.embed_color)
.attachFile('CHANGELOG.md');
} catch (err) {
this._logger.verbose(err.message);
this._logger.silly(err.stack);
return this.template.changes.response.not_found;
}
})
);
let versions = new cmdLib.Command(
this.template.versions,
new cmdLib.Answer(() => {
try {
return new cmdLib.ExtendedRichEmbed('CHANGELOG.md Versions')
.setDescription(Object.keys(this._changes).join('\n'))
.setColor(this.template.versions.embed_color);
} catch (err) {
this._logger.verbose(err.message);
this._logger.silly(err.stack);
return this.template.versions.response.not_found;
}
})
);
// register commands
commandHandler
.registerCommand(about)
.registerCommand(ping)
.registerCommand(uptime)
.registerCommand(guilds)
.registerCommand(help);
.registerCommand(help)
.registerCommand(changes)
.registerCommand(versions);
}
}

@ -1,9 +1,12 @@
METADATA: &METADATA
category: Info
permission: all
about:
<<: *METADATA
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).
@ -13,32 +16,50 @@ about:
More about this bot [here](https://github.com/Trivernis/discordbot.js).
ping:
<<: *METADATA
name: ping
description: >
Answers with the current average ping of the bot.
permission: all
category: Info
uptime:
<<: *METADATA
name: uptime
description: >
Answers with the uptime of the bot.
permission: all
category: Info
guilds:
<<: *METADATA
name: guilds
description: >
Answers with the number of guilds the bot has joined
permission: owner
category: Info
help:
<<: *METADATA
name: help
description: >
Shows help for bot ocmmands.
permission: all
category: Info
embed_color: 0xffffff
args:
- command
changes:
<<: *METADATA
name: changes
description: >
Shows the changes of the current release or a specific previous.
embed_color: 0xaabbcc
args:
- version
response:
not_found: >
I could not find the changelog for the version you were looking for.
versions:
<<: *METADATA
name: versions
description: >
Shows all versions present in the CHANGELOG.
embed_color: 0xaabbcc
response:
not_found: >
I could not find any versions.

@ -0,0 +1,82 @@
/* template index.js. Doesn't implement actual commands */
const cmdLib = require('../../CommandLib');
/**
* Several commands that are that special that they can't be included in any other module.
*/
/**
* Async delay
* @param seconds {Number}
*/
function delay(seconds) {
return new Promise((resolve) => {
setTimeout(resolve, seconds * 1000);
});
}
class TemplateCommandModule extends cmdLib.CommandModule {
constructor() {
super(cmdLib.CommandScopes.Global);
this._templateDir = __dirname;
}
/**
* Defines and registers commands to the commandHandler.
* @param commandHandler {CommandHandler}
*/
async register(commandHandler) {
await this._loadTemplate();
let sayCommand = new cmdLib.Command(
this.template.say,
new cmdLib.Answer((m, k, s) => {
return s.replace(/^"|"$/g, '');
})
);
let delayCommand = new cmdLib.Command(
this.template.delay,
new cmdLib.Answer(async (m, k) => {
this._logger.silly(`Delaying for ${k.seconds} seconds`);
await delay(k.seconds);
})
);
let chooseCommand = new cmdLib.Command(
this.template.choose,
new cmdLib.Answer(async (m, k, s) => {
let options = s.split(',').map(x => {
if (x) {
let strippedValue = x.replace(/^\s+|\s+$/, '');
if (strippedValue.length === 0)
return null;
else
return strippedValue;
} else {
return null;
}
}).filter(x => x);
if (options.length === 0) {
return this.template.choose.response.no_options;
} else {
this._logger.silly(`Choosing from ${options.join(', ')}`);
let item = options[Math.floor(Math.random() * options.length)];
return `I've chosen ${item.replace(/^"|"$|^\s+|\s+$/g, '')}`;
}
})
);
/* Register commands to handler */
commandHandler
.registerCommand(sayCommand)
.registerCommand(delayCommand)
.registerCommand(chooseCommand);
}
}
Object.assign(exports, {
module: TemplateCommandModule
});

@ -0,0 +1,29 @@
METADATA: &METADATA
category: Misc
permission: all
say:
<<: *METADATA
name: say
usage: say [...message]
description: >
The bot says what you defined in the message argument
delay:
<<: *METADATA
name: delay
usage: delay
args:
- seconds
description: >
Set a delay in seconds. Useful for command sequences.
choose:
<<: *METADATA
name: choose
usage: choose [opt-1], [opt-2], ..., [opt-n]
description: >
Chooses randomly from one of the options
response:
no_options: >
You need to define options for me to choose from.

@ -1,7 +1,6 @@
const cmdLib = require('../../CommandLib'),
utils = require('../../utils'),
config = require('../../../config'),
location = './lib/commands/MusicCommands';
config = require('../../../config');
function checkPermission(msg, rolePerm) {
if (!rolePerm || ['all', 'any', 'everyone'].includes(rolePerm))
@ -27,7 +26,7 @@ class MusicCommandModule extends cmdLib.CommandModule {
*/
constructor(opts) {
super(cmdLib.CommandScopes.Guild);
this.templateFile = location + '/MusicCommandsTemplate.yaml';
this._templateDir = __dirname;
this._getGuildHandler = opts.getGuildHandler;
}

@ -1,9 +1,12 @@
METADATA: &METADATA
category: Music
permission: all
play:
<<: *METADATA
name: play
description: >
Adds the url to the YouTube video or YouTube playlist into the queue.
permission: all
category: Music
args:
- url
response:
@ -19,12 +22,11 @@ play:
You need to join a VoiceChannel to request media playback.
play_next:
<<: *METADATA
name: playnext
description: >
Adds the url to the YouTube video or YouTube playlist into the queue as
next playing song.
permission: all
category: Music
args:
- url
response:
@ -40,21 +42,19 @@ play_next:
You need to join a VoiceChannel to request media playback.
join:
<<: *METADATA
name: join
description: >
Joins the VoiceChannel you are in.
permission: all
category: Music
response:
no_voicechannel: >
You need to join a VoiceChannel for me to join.
stop:
<<: *METADATA
name: stop
description: >
Stops the media playback and leaves the VoiceChannel.
permission: all
category: Music
response:
success: >
Stopped music playback.
@ -62,11 +62,10 @@ stop:
I'm not playing music at the moment. What do you want me to stop?
pause:
<<: *METADATA
name: pause
description: >
Pauses the media playback.
permission: all
category: Music
response:
success: >
Paused playback.
@ -74,11 +73,10 @@ pause:
I'm not playing music at the moment.
resume:
<<: *METADATA
name: resume
description: >
Resumes the media playback.
permission: all
category: Music
response:
success: >
Resumed playback.
@ -86,11 +84,10 @@ resume:
I'm not playing music at the moment.
skip:
<<: *METADATA
name: skip
description: >
Skips the currently playing song.
permission: all
category: Music
response:
success: >
Skipped to the next song.
@ -98,48 +95,44 @@ skip:
I'm not playing music at the moment.
clear:
<<: *METADATA
name: clear
description: >
Clears the media queue.
permission: musicPlayer
category: Music
permission: dj
response:
success: >
The media queue has been cleared.
media_queue:
<<: *METADATA
name: queue
descriptions: >
Shows the next ten songs in the media queue.
permission: all
category: Music
media_current:
<<: *METADATA
name: np
description: >
Shows the currently playing song.
permission: all
category: Music
response:
not_playing: >
I'm not playing music at the moment.
shuffle:
<<: *METADATA
name: shuffle
description: >
Shuffles the media queue
permission: all
category: Music
response:
success: >
The queue has been shuffled.
toggle_repeat:
<<: *METADATA
name: repeat
description: >
Toggles listening o repeat.
permission: all
category: Music
response:
repeat_true: >
Listening on repeat now!
@ -147,32 +140,31 @@ toggle_repeat:
Not listening on repeat anymore.
save_media:
<<: *METADATA
name: savemedia
description: >
Saves the YouTube URL with a specific name.
permission: dj
category: Music
args:
- url
usage: savemedia [url] [name...]
delete_media:
<<: *METADATA
name: deletemedia
description: >
Deletes a saved YouTube URL from saved media.
permission: dj
category: Music
usage: deletemedia [name]
response:
no_name: >
You must provide a name for the media to delete.
saved_media:
<<: *METADATA
name: savedmedia
description: >
Shows all saved YouTube URLs.
permission: all
category: Music
response:
no_saved: >
There are no saved YouTube URLs :(

@ -1,5 +1,4 @@
const cmdLib = require('../../CommandLib'),
location = './lib/commands/ServerUtilityCommands';
const cmdLib = require('../../CommandLib');
/**
* This command module includes utility commands for the server.
@ -14,7 +13,7 @@ class ServerUtilityCommandModule extends cmdLib.CommandModule {
*/
constructor(opts) {
super(cmdLib.CommandScopes.Guild);
this.templateFile = location + '/ServerUtilityCommandsTemplate.yaml';
this._templateDir = __dirname;
this._messageHandler = opts.messageHandler;
this._getGuildHandler = opts.getGuildHandler;
this._config = opts.config;

@ -1,9 +1,13 @@
METADATA: &METADATA
category: Server Utility
permission: all
save_cmd:
<<: *METADATA
name: savecmd
description: >
Saves a sequence of commands under a new name.
permission: moderator
category: Server Utility
usage: savecmd [cmdname] [cmdsequence]
args:
- name
@ -17,30 +21,28 @@ save_cmd:
This sequence executes too long serial chains.
delete_cmd:
<<: *METADATA
name: deletecmd
description: >
Deletes a saved command.
permission: moderator
category: Server Utility
args:
- name
saved_cmd:
<<: *METADATA
name: savedcmd
description: >
Lists all saved commands.
category: Server Utility
permission: all
response:
no_commands: >
There are no saved commands.
execute:
<<: *METADATA
name: execute
description: >
Executes a saved command.
permission: all
category: Server Utility
args:
- name
response:

@ -1,5 +1,4 @@
const cmdLib = require('../../CommandLib'),
location = './lib/commands/UtilityCommands';
const cmdLib = require('../../CommandLib');
/**
* Utility commands are all commands that allow the user to control the behaviour of the
@ -18,7 +17,7 @@ class UtilityCommandModule extends cmdLib.CommandModule {
*/
constructor(opts) {
super(cmdLib.CommandScopes.User);
this.templateFile = location + '/UtilityCommandsTemplate.yaml';
this._templateDir = __dirname;
this._bot = opts.bot;
this._config = opts.config;
}

@ -1,42 +1,42 @@
METADATA: &METADATA
category: Utility
permission: owner
shutdown:
<<: *METADATA
name: shutdown
description: >
Shuts down the bot.
permission: owner
category: Utility
add_presence:
<<: *METADATA
name: addpresence
description: >
Adds a Rich Presence to the bot.
permission: owner
category: Utility
usage: addpresence [presence]
rotate_presence:
<<: *METADATA
name: rotatepresence
description: >
Forces a presence rotation
permission: owner
category: Utility
create_user:
<<: *METADATA
name: createuser
description: >
Creates a user for the webinterface.
permission: owner
category: Utility
args:
- username
- password
- scope
bugreport:
<<: *METADATA
name: bug
permission: all
description: >
Get information about where to report bugs.
permission: all
category: Utility
response:
title: >
You want to report a bug?
Loading…
Cancel
Save