Added Servercommands

- added commands for music functionalities
pull/51/head
Trivernis 6 years ago
parent a425607a6b
commit 556c3a5e7f

@ -72,7 +72,11 @@ class Bot {
await this.messageHandler await this.messageHandler
.registerCommandModule(require('./lib/commands/UtilityCommands').module, {bot: this, logger: logger, config: config}); .registerCommandModule(require('./lib/commands/UtilityCommands').module, {bot: this, logger: logger, config: config});
await this.messageHandler await this.messageHandler
.registerCommandModule(require('./lib/commands/InfoCommands').module, {client: this.client}); .registerCommandModule(require('./lib/commands/InfoCommands').module, {client: this.client, messageHandler: this.messageHandler});
await this.messageHandler
.registerCommandModule(require('./lib/commands/MusicCommands').module, {getGuildHandler: (g) => {
return this.getGuildHandler(g, prefix);
}, logger: logger})
//this.registerCommands(); //this.registerCommands();
this.registerCallbacks(); this.registerCallbacks();
cmd.init(prefix); cmd.init(prefix);

@ -46,12 +46,13 @@ class Command {
*/ */
constructor(template, answer) { constructor(template, answer) {
this.name = template.name; this.name = template.name;
this.prefix = '';
this.description = template.description; this.description = template.description;
this.args = template.args || []; this.args = template.args || [];
this.permission = template.permission; this.permission = template.permission;
this.category = template.category || 'Other'; this.category = template.category || 'Other';
this.usage = template.usage || this.usage = template.usage ||
`\`${this.name} [${this.args.join('][')}\``.replace('[]', ''); `${this.name} [${this.args.join('][')}]`.replace('[]', '');
this.answObj = answer; this.answObj = answer;
if (!template.name) if (!template.name)
throw new Error("Template doesn't define a name."); throw new Error("Template doesn't define a name.");
@ -76,7 +77,7 @@ class Command {
get help() { get help() {
return new ExtendedRichEmbed(`Help for ${this.name}`) return new ExtendedRichEmbed(`Help for ${this.name}`)
.addFields({ .addFields({
'Usage': this.usage, 'Usage': `\`${this.prefix}${this.usage}\``,
'Description': this.description, 'Description': this.description,
'Permission Role': this.permission 'Permission Role': this.permission
}); });
@ -109,13 +110,16 @@ class CommandHandler {
if (commandName.indexOf(this.prefix) >= 0) { if (commandName.indexOf(this.prefix) >= 0) {
commandName = commandName.replace(this.prefix, ''); commandName = commandName.replace(this.prefix, '');
let argsString = commandMessage.replace(/^\S+/, ''); let argsString = commandMessage.replace(/^\S+/, '');
argsString = argsString
.replace(/^\s+/, '') // leading whitespace
.replace(/\s+$/, ''); // trailing whitespace
let args = argsString.match(/\S+/g); let args = argsString.match(/\S+/g);
let command = this.commands[commandName]; let command = this.commands[commandName];
if (command) { if (command) {
let kwargs = {}; let kwargs = {};
if (args) if (args)
for (let i = 0; i < Math.min(command.kwargs, args.length); i++) for (let i = 0; i < Math.min(command.args.length, args.length); i++)
kwargs[command.kwargs[i]] = args[i]; kwargs[command.args[i]] = args[i];
return command.answer(message, kwargs, argsString); return command.answer(message, kwargs, argsString);
} else { } else {
return false; return false;
@ -131,7 +135,9 @@ class CommandHandler {
* @param command {Command} * @param command {Command}
*/ */
registerCommand(command) { registerCommand(command) {
command.prefix = this.prefix;
this.commands[command.name] = command; this.commands[command.name] = command;
return this;
} }
} }
@ -184,19 +190,23 @@ class ExtendedRichEmbed extends Discord.RichEmbed {
* Adds a Field when a name is given or adds a blank Field otherwise * Adds a Field when a name is given or adds a blank Field otherwise
* @param name {String} * @param name {String}
* @param content {String} * @param content {String}
* @returns {ExtendedRichEmbed}
*/ */
addNonemptyField(name, content) { addNonemptyField(name, content) {
if (name && name.length > 0 && content) if (name && name.length > 0 && content)
this.addField(name, content); this.addField(name, content);
return this;
} }
/** /**
* Adds the fields defined in the fields JSON * Adds the fields defined in the fields JSON
* @param fields {JSON} * @param fields {JSON}
* @returns {ExtendedRichEmbed}
*/ */
addFields(fields) { addFields(fields) {
for (let [name, value] of Object.entries(fields)) for (let [name, value] of Object.entries(fields))
this.addNonemptyField(name, value); this.addNonemptyField(name, value);
return this;
} }
} }

@ -7,9 +7,9 @@ class MessageHandler {
/** /**
* Message Handler to handle messages. Listens on the * Message Handler to handle messages. Listens on the
* client message event. * _client message event.
* @param client {Discord.Client} * @param client {Discord.Client}
* @param logger {winston.logger} * @param logger {winston._logger}
*/ */
constructor (client, logger) { constructor (client, logger) {
this.logger = logger; this.logger = logger;
@ -100,7 +100,7 @@ class MessageHandler {
*/ */
async _executeCommandSequence(cmdSequence, message) { async _executeCommandSequence(cmdSequence, message) {
this.logger.debug(`Executing command sequence: ${JSON.stringify(cmdSequence)} ...`); this.logger.debug(`Executing command sequence: ${JSON.stringify(cmdSequence)} ...`);
let scopeCmdHandler = this._getScopeHandler(message); let scopeCmdHandler = this.getScopeHandler(message);
await Promise.all(cmdSequence.map((sq) => promiseWaterfall(sq.map((cmd) => async () => { await Promise.all(cmdSequence.map((sq) => promiseWaterfall(sq.map((cmd) => async () => {
try { try {
this.logger.debug(`Executing command ${cmd}`); this.logger.debug(`Executing command ${cmd}`);
@ -124,7 +124,7 @@ class MessageHandler {
* @param message * @param message
* @private * @private
*/ */
_getScopeHandler(message) { getScopeHandler(message) {
if (message && message.guild) if (message && message.guild)
return this.guildCmdHandler; return this.guildCmdHandler;
else else

@ -3,8 +3,8 @@ anime_search:
permission: all permission: all
usage: anime [search query] usage: anime [search query]
description: > description: >
Searches AniList.co for the anime Title and returns information about Searches AniList.co for the anime title and returns information about
it if there is an result. it if there is a result.
category: AniList category: AniList
response: response:
not_found: > not_found: >
@ -15,8 +15,8 @@ manga_search:
permission: all permission: all
usage: manga [search query] usage: manga [search query]
description: > description: >
Searches AniList.co for the manga Title and returns information about Searches AniList.co for the manga title and returns information about
it if there is an result. it if there is a result.
category: AniList category: AniList
response: response:
not_found: > not_found: >

@ -32,3 +32,12 @@ guilds:
Answers with the number of guilds the bot has joined Answers with the number of guilds the bot has joined
permission: owner permission: owner
category: Info category: Info
help:
name: help
description: >
Shows help for bot ocmmands.
permission: all
category: Info
args:
- command

@ -4,7 +4,7 @@ const cmdLib = require('../../CommandLib'),
/** /**
* Info commands provide information about the bot. These informations are * Info commands provide information about the bot. These informations are
* not process specific but access the discord client instance of the bot. * not process specific but access the discord _client instance of the bot.
*/ */
class InfoCommandModule extends cmdLib.CommandModule { class InfoCommandModule extends cmdLib.CommandModule {
@ -12,11 +12,33 @@ class InfoCommandModule extends cmdLib.CommandModule {
/** /**
* @param opts {Object} properties: * @param opts {Object} properties:
* client - the instance of the discord client. * client - the instance of the discord client.
* messageHandler - the instance of the Message Handler
*/ */
constructor(opts) { constructor(opts) {
super(cmdLib.CommandScopes.Global); super(cmdLib.CommandScopes.Global);
this.templateFile = location + '/InfoCommandsTemplate.yaml'; this.templateFile = location + '/InfoCommandsTemplate.yaml';
this.client = opts.client; this._client = opts.client;
this._messageHandler = opts.messageHandler;
}
_createHelpEmbed(commands, msg, prefix) {
let helpEmbed = new cmdLib.ExtendedRichEmbed('Commands')
.setDescription('Create a sequence of commands with `;` and `&&`.');
let categories = [];
let catCommands = {};
Object.entries(commands).sort().forEach(([key, value]) => {
if (!categories.includes(value.category)) {
categories.push(value.category);
catCommands[value.category] = `\`${prefix}${key}\` \t`;
} else {
catCommands[value.category] += `\`${prefix}${key}\` \t`;
}
});
for (let cat of categories)
helpEmbed.addField(cat, catCommands[cat]);
helpEmbed.setFooter(prefix + 'help [command] for more info to each command');
return helpEmbed;
} }
async register(commandHandler) { async register(commandHandler) {
@ -34,14 +56,14 @@ class InfoCommandModule extends cmdLib.CommandModule {
let ping = new cmdLib.Command( let ping = new cmdLib.Command(
this.template.ping, this.template.ping,
new cmdLib.Answer(() => { new cmdLib.Answer(() => {
return `Current average ping: \`${this.client.ping} ms\``; return `Current average ping: \`${this._client.ping} ms\``;
}) })
); );
let uptime = new cmdLib.Command( let uptime = new cmdLib.Command(
this.template.uptime, this.template.uptime,
new cmdLib.Answer(() => { new cmdLib.Answer(() => {
let uptime = utils.getSplitDuration(this.client.uptime); let uptime = utils.getSplitDuration(this._client.uptime);
return new cmdLib.ExtendedRichEmbed('Uptime').setDescription(` return new cmdLib.ExtendedRichEmbed('Uptime').setDescription(`
**${uptime.days}** days **${uptime.days}** days
**${uptime.hours}** hours **${uptime.hours}** hours
@ -55,15 +77,33 @@ class InfoCommandModule extends cmdLib.CommandModule {
let guilds = new cmdLib.Command( let guilds = new cmdLib.Command(
this.template.guilds, this.template.guilds,
new cmdLib.Answer(() => { new cmdLib.Answer(() => {
return `Number of guilds: \`${this.client.guilds.size}\``; return `Number of guilds: \`${this._client.guilds.size}\``;
})
);
let help = new cmdLib.Command(
this.template.help,
new cmdLib.Answer((m, k) => {
let globH = this._messageHandler.globalCmdHandler;
let scopeH = this._messageHandler.getScopeHandler(m);
if (k.command) {
k.command = k.command.replace(globH.prefix, '');
let commandInstance = globH.commands[k.command] || scopeH.commands[k.command];
return commandInstance.help;
} else {
let commandObj = {...globH.commands, ...scopeH.commands};
return this._createHelpEmbed(commandObj, m, globH.prefix);
}
}) })
); );
// register commands // register commands
commandHandler.registerCommand(about); commandHandler
commandHandler.registerCommand(ping); .registerCommand(about)
commandHandler.registerCommand(uptime); .registerCommand(ping)
commandHandler.registerCommand(guilds); .registerCommand(uptime)
.registerCommand(guilds)
.registerCommand(help);
} }
} }

@ -0,0 +1,178 @@
play:
name: play
description: >
Adds the url to the YouTube video or YouTube playlist into the queue.
permission: all
category: Music
args:
- url
response:
success: >
Added URL to the media queue.
failure: >
Failed adding the URL to the media queue.
url_invalid: >
The URL you provided is not a valid YouTube video or Playlist URL.
no_url: >
You need to provide an URL to a YouTube viceo or Playlist.
no_voicechannel: >
You need to join a VoiceChannel to request media playback.
play_next:
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:
success: >
Added URL as next media to the media queue.
failure: >
Failed adding the URL to the media queue.
url_invalid: >
The URL you provided is not a valid YouTube video or Playlist URL.
no_url: >
You need to provide an URL to a YouTube viceo or Playlist.
no_voicechannel: >
You need to join a VoiceChannel to request media playback.
join:
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:
name: stop
description: >
Stops the media playback and leaves the VoiceChannel.
permission: dj
category: Music
response:
success: >
Stopped music playback.
not_playing: >
I'm not playing music at the moment. What do you want me to stop?
pause:
name: pause
description: >
Pauses the media playback.
permission: all
category: Music
response:
success: >
Paused playback.
not_playing: >
I'm not playing music at the moment.
resume:
name: resume
description: >
Resumes the media playback.
permission: all
category: Music
response:
success: >
Resumed playback.
not_playing: >
I'm not playing music at the moment.
skip:
name: skip
description: >
Skips the currently playing song.
permission: dj
category: Music
response:
success: >
Skipped to the next song.
not_playing: >
I'm not playing music at the moment.
clear:
name: clear
description: >
Clears the media queue.
permission: dj
category: Music
response:
success: >
The media queue has been cleared.
media_queue:
name: queue
descriptions: >
Shows the next ten songs in the media queue.
permission: all
category: Music
media_current:
name: np
description: >
Shows the currently playing song.
permission: all
category: Music
response:
not_playing: >
I'm not playing music at the moment.
shuffle:
name: shuffle
description: >
Shuffles the media queue
permission: all
category: Music
response:
success: >
The queue has been shuffled.
toggle_repeat:
name: repeat
description: >
Toggles listening o repeat.
permission: all
category: Music
response:
repeat_true: >
Listening on repeat now!
repeat_false: >
Not listening on repeat anymore.
save_media:
name: savemedia
description: >
Saves the YouTube URL with a specific name.
permission: dj
category: Music
args:
- url
usage: savemedia [url] [name...]
delete_media:
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:
name: savedmedia
description: >
Shows all saved YouTube URLs.
permission: all
category: Music
response:
no_saved: >
There are no saved YouTube URLs :(

@ -0,0 +1,285 @@
const cmdLib = require('../../CommandLib'),
utils = require('../../utils'),
location = './lib/commands/MusicCommands';
/**
* Music commands provide commands to control the bots music functions.
* These commands are for server music functionalities.
*/
class MusicCommandModule extends cmdLib.CommandModule {
/**
* @param opts {Object} properties:
* getGuildHandler - a function to get the guild handler for a guild.
* logger - the logger instance
*/
constructor(opts) {
super(cmdLib.CommandScopes.Guild);
this.templateFile = location + '/MusicCommandsTemplate.yaml';
this._getGuildHandler = opts.getGuildHandler;
this._logger = opts.logger;
}
/**
* Connects to a voice-channel if not connected and plays the url
* @param gh {guilding.GuildHandler}
* @param vc {Discord.VoiceChannel}
* @param url {String} The url to the YouTube media
* @param next {Boolean} Should the song be played next
* @returns {Promise<void>}
* @private
*/
async _connectAndPlay(gh, vc, url, next) {
if (!gh.dj.connected) {
await gh.dj.connect(vc);
await gh.dj.playYouTube(url, next);
} else {
await gh.dj.playYouTube(url, next);
}
}
/**
* The play function for the music commands play and playnext
* @param m {Discord.Message}
* @param k {Object} kwargs
* @param s {String} argsString
* @param t {Object} template
* @param n {Boolean} play next
* @returns {Promise<*>}
* @private
*/
async _playFunction(m, k, s, t, n) {
let gh = await this._getGuildHandler(m.guild);
let vc = gh.dj.voiceChannel || m.member.voiceChannel;
let url = k['url'];
if (!vc)
return t.response.no_voicechannel;
if (!url)
return t.response.no_url;
if (!utils.YouTube.isValidEntityUrl(url)) {
url = s;
let row = await gh.db.get('SELECT url FROM playlists WHERE name = ?', [url]);
if (!row) {
this._logger.debug('Got invalid url for play command.');
return t.response.url_invalid;
} else {
await this._connectAndPlay(gh, vc, row.url, n);
return t.response.success;
}
} else {
await this._connectAndPlay(gh, vc, url, n);
return t.response.success;
}
}
async register(commandHandler) {
await this._loadTemplate();
let play = new cmdLib.Command(
this.template.play,
new cmdLib.Answer(async (m, k, s) => {
return await this._playFunction(m, k, s, this.template.play, false);
})
);
let playNext = new cmdLib.Command(
this.template.play_next,
new cmdLib.Answer(async (m, k, s) => {
return await this._playFunction(m, k, s, this.template.play_next, true);
})
);
let join = new cmdLib.Command(
this.template.join,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
if (m.member.voiceChannel)
await gh.dj.connect(m.member.voiceChannel);
else
return this.template.join.response.no_voicechannel;
})
);
let stop = new cmdLib.Command(
this.template.stop,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
if (gh.dj.connected) {
gh.dj.stop();
return this.template.stop.success;
} else {
return this.template.stop.not_playing;
}
})
);
let pause = new cmdLib.Command(
this.template.pause,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
if (gh.dj.playing) {
gh.dj.pause();
return this.template.pause.response.success;
} else {
return this.template.pause.response.not_playing;
}
})
);
let resume = new cmdLib.Command(
this.template.resume,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
if (gh.dj.playing) {
gh.dj.resume();
return this.template.resume.response.success;
} else {
return this.template.resume.response.not_playing;
}
})
);
let skip = new cmdLib.Command(
this.template.skip,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
if (gh.dj.playing) {
gh.dj.skip();
return this.template.skip.response.success;
} else {
return this.template.skip.response.not_playing;
}
})
);
let clear = new cmdLib.Command(
this.template.clear,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
gh.dj.clear();
return this.template.clear.response.success;
})
);
let mediaQueue = new cmdLib.Command(
this.template.media_queue,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
this._logger.debug(`Found ${gh.dj.queue.length} songs.`);
let description = '';
for (let i = 0; i < Math.min(gh.dj.queue.length, 9); i++) {
let entry = gh.dj.queue[i];
description += `[${entry.title}](${entry.url})\n`;
}
return new cmdLib.ExtendedRichEmbed(`${gh.dj.queue.length} songs in queue`)
.setDescription(description);
})
);
let mediaCurrent = new cmdLib.Command(
this.template.media_current,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
let song = gh.dj.song;
if (song)
return new cmdLib.ExtendedRichEmbed('Now playing:')
.setDescription(`[${song.title}](${song.url})`)
.setImage(utils.YouTube.getVideoThumbnailUrlFromUrl(song.url))
.setColor(0x00aaff);
else
return this.template.media_current.response.not_playing;
})
);
let shuffle = new cmdLib.Command(
this.template.shuffle,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
gh.dj.shuffle();
return this.template.shuffle.response.success;
})
);
let toggleRepeat = new cmdLib.Command(
this.template.toggle_repeat,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
gh.dj.repeat = !gh.dj.repeat;
return gh.dj.repeat?
this.template.toggle_repeat.response.repeat_true :
this.template.toggle_repeat.response.repeat_false;
})
);
let saveMedia = new cmdLib.Command(
this.template.save_media,
new cmdLib.Answer(async (m, k, s) => {
let gh = await this._getGuildHandler(m.guild);
let saveName = s.replace(k.url + ' ', '');
let row = await gh.db
.get('SELECT COUNT(*) count FROM playlists WHERE name = ?', [saveName]);
if (!row || row.count === 0)
await gh.db.run('INSERT INTO playlists (name, url) VALUES (?, ?)',
[saveName, k.url]);
else
await gh.db.run('UPDATE playlists SET url = ? WHERE name = ?',
[k.url, saveName]);
return `Saved song/playlist as ${saveName}`;
})
);
let deleteMedia = new cmdLib.Command(
this.template.delete_media,
new cmdLib.Answer(async (m, k, s) => {
let gh = await this._getGuildHandler(m.guild);
if (!s) {
return this.template.delete_media.response.no_name;
} else {
await gh.db.run('DELETE FROM playlists WHERE name = ?', [s]);
return `Deleted ${s} from saved media`;
}
})
);
let savedMedia = new cmdLib.Command(
this.template.saved_media,
new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild);
let response = '';
let rows = await gh.db.all('SELECT name, url FROM playlists');
for (let row of rows)
response += `[${row.name}](${row.url})\n`;
if (rows.length === 0)
return this.template.saved_media.response.no_saved;
else
return new cmdLib.ExtendedRichEmbed('Saved Songs and Playlists')
.setDescription(response)
.setFooter(`Play a saved entry with play [Entryname]`);
})
);
// register commands
commandHandler
.registerCommand(play)
.registerCommand(playNext)
.registerCommand(join)
.registerCommand(stop)
.registerCommand(pause)
.registerCommand(resume)
.registerCommand(skip)
.registerCommand(clear)
.registerCommand(mediaQueue)
.registerCommand(mediaCurrent)
.registerCommand(shuffle)
.registerCommand(toggleRepeat)
.registerCommand(saveMedia)
.registerCommand(deleteMedia)
.registerCommand(savedMedia);
}
}
Object.assign(exports, {
'module': MusicCommandModule
});

@ -14,14 +14,14 @@ add_presence:
usage: addpresence [presence] usage: addpresence [presence]
rotate_presence: rotate_presence:
name: rotate_presence name: rotatepresence
description: > description: >
Forces a presence rotation Forces a presence rotation
permission: owner permission: owner
category: Utility category: Utility
create_user: create_user:
name: create_user name: createuser
description: > description: >
Creates a user for the webinterface. Creates a user for the webinterface.
permission: owner permission: owner

Loading…
Cancel
Save