diff --git a/bot.js b/bot.js index b742cde..850ec24 100644 --- a/bot.js +++ b/bot.js @@ -160,7 +160,7 @@ class Bot { */ registerCommands() { // useless test command - cmd.createGlobalCommand(prefix + 'repeatafterme', (msg, argv, args) => { + cmd.createGlobalCommand(prefix + 'say', (msg, argv, args) => { return args.join(' '); }, [], "Repeats what you say"); @@ -333,10 +333,7 @@ class Bot { let resolvedAnswer = await answer; await this.answerMessage(msg, resolvedAnswer); } else if (answer instanceof Array) { - let answerPromises = []; - for (let answerPart of answer) - answerPromises.push(async () => await this.answerMessage(msg, answerPart)); - await waterfall(answerPromises); + await waterfall(answer.map((x) => async () => await this.answerMessage(msg, x))); // execute each after another } else if ({}.toString.call(answer) === '[object Function]') { await this.answerMessage(msg, answer()); } else if (answer) { diff --git a/commands/servercommands.json b/commands/servercommands.json index 36b2efc..caa0c9e 100644 --- a/commands/servercommands.json +++ b/commands/servercommands.json @@ -5,6 +5,42 @@ "permission": "all", "description": "Shows the roles used for commands on the server.", "category": "Utility" + }, + "savecmd": { + "name": "savecmd", + "permission": "moderator", + "description": "Saves a sequence of commands under a new name. ~save [cmdsequence] [cmdname]. Semicoli must be escaped with \\ (Backslash)", + "category": "Utility" + }, + "savedcmd": { + "name": "savedcmd", + "permission": "all", + "description": "Displays the saved commands.", + "category": "Utility", + "response": { + "no_commands": "There are no saved commands." + } + }, + "deletecmd": { + "name": "deletecmd", + "permission": "moderator", + "description": "Delete a saved command.", + "args": [ + "cmdname" + ], + "category": "Utility" + }, + "execute": { + "name": "execute", + "permission": "all", + "args": [ + "cmdname" + ], + "description": "Execute saved commands.", + "category": "Utility", + "response": { + "not_found": "This command could not be found." + } } }, "music": { @@ -132,8 +168,8 @@ "repeat_false": "Not listening on repeat anymore." } }, - "save": { - "name": "save", + "savemedia": { + "name": "savemedia", "permission": "dj", "args": [ "url" @@ -141,14 +177,23 @@ "description": "Saves the YouTube song/playlist with a specific name", "category": "Music" }, - "saved": { - "name": "saved", + "savedmedia": { + "name": "savedmedia", "permission": "all", - "description": "Prints out all saved playlists.", + "description": "Prints out all saved playlists and songs.", "category": "Music", "response": { "no_saved": "There are no saved songs/playlists :(" } + }, + "deletemedia": { + "name": "deletemedia", + "permission": "dj", + "description": "Deletes a saved media entry. ~deletemedia [name]", + "category": "Music", + "response": { + "no_name": "You must provide a name for the media that shall be deleted." + } } } } diff --git a/lib/cmd.js b/lib/cmd.js index 495b323..508ff95 100644 --- a/lib/cmd.js +++ b/lib/cmd.js @@ -35,7 +35,9 @@ exports.Servant = class { } else { let helpEmbed = new Discord.RichEmbed() - .setTitle('Commands'); + .setTitle('Commands') + .setDescription('Create a sequence of commands with `;` (semicolon).') + .setTimestamp(); let globHelp = ''; Object.entries(globCommands).sort().forEach(([key, value]) => { if (value.role !== 'owner' || checkPermission(msg, 'owner')) @@ -107,12 +109,19 @@ exports.Servant = class { * @param msg * @param globResult * @param content + * @param returnFunction Boolean if the return value should be a function. + * @param fallback * @returns {*} */ - processCommand(msg, globResult, content, returnFunction) { + processCommand(msg, globResult, content, returnFunction, fallback) { let command = (content.match(/^.\w+/) || [])[0]; if (!command || !this.commands[command]) - return globResult; + if (fallback && !globResult) { + command = fallback; + content = `${fallback} ${content}`; + } else { + return globResult; + } let cmd = this.commands[command]; if (!checkPermission(msg, cmd.role)) return 'No Permission'; @@ -143,14 +152,18 @@ exports.Servant = class { let globResult = parseGlobalCommand(msg); logger.debug(`Global command result is ${globResult}`); let content = msg.content; - let commands = content.split(';').map(x => x.replace(/^ +/, '')); + let commands = content.split(/(? x.replace(/^ +/, '')); if (commands.length === 1) { return this.processCommand(msg, globResult, content); } else { let answers = []; - for (let i = 0; i < commands.length; i++) + let previousCommand = (commands[0].match(/^.\w+/) || [])[0];; + for (let i = 0; i < commands.length; i++) { answers.push(this.processCommand(msg, globResult[i], commands[i], - true)); // return function to avoid "race conditions" + true, previousCommand)); // return function to avoid "race conditions" + let commandMatch = (commands[i].match(/^.\w+/) || [])[0]; + previousCommand = this.commands[commandMatch]? commandMatch : previousCommand; + } return answers; } @@ -215,6 +228,7 @@ exports.init = function (prefix) { } else { let helpEmbed = new Discord.RichEmbed() .setTitle('Global Commands') + .setDescription('Create a sequence of commands with `;` (semicolon).') .setTimestamp(); let description = ''; Object.entries(globCommands).sort().forEach(([key, value]) => { @@ -248,7 +262,7 @@ function processCommand(cmd, msg, content, returnFunction) { */ function parseGlobalCommand(msg) { let content = msg.content; - let commands = content.split(';').map(x => x.replace(/^ +/, '')); + let commands = content.split(/(? x.replace(/^ +/, '')); if (commands.length === 1) { let command = (content.match(/^.\w+/) || [])[0]; if (!command || !globCommands[command]) @@ -260,15 +274,25 @@ function parseGlobalCommand(msg) { return processCommand(cmd, msg, content); } else { let answers = []; + let previousCommand = ''; for (let commandPart of commands) { - let command = (commandPart.match(/^.\w+/) || [])[0]; + let command = (commandPart.match(/^.\w+/) || [])[0] || previousCommand; + previousCommand = globCommands[command]? command : previousCommand; + if (!commandPart || !globCommands[command]) { + commandPart = `${previousCommand} ${commandPart}`; + command = previousCommand; + } if (command && globCommands[command]) { let cmd = globCommands[command]; if (checkPermission(msg, cmd.role)) { logger.debug(`Permission <${cmd.role}> granted for command ${command} for user <${msg.author.tag}>`); answers.push(processCommand(cmd, msg, commandPart, true)); // return an function to avoid "race conditions" + } else { + answers.push(false); } + } else { + answers.push(false); } } return answers; @@ -285,8 +309,9 @@ function checkPermission(msg, rolePerm) { return true; if (msg.author.tag === args.owner || config.owners.includes(msg.author.tag)) return true; - else - if (msg.member && rolePerm && msg.member.roles.some(role => role.name.toLowerCase() === rolePerm.toLowerCase())) + else + if (msg.member && rolePerm && rolePerm !== 'owner' && msg.member.roles + .some(role => (role.name.toLowerCase() === rolePerm.toLowerCase() || role.name.toLowerCase() === 'botcommander'))) return true; return false; diff --git a/lib/guilding.js b/lib/guilding.js index 46af1e8..0344e42 100644 --- a/lib/guilding.js +++ b/lib/guilding.js @@ -36,7 +36,7 @@ exports.GuildHandler = class { logger.debug(`Connected to the database for ${this.guild}`); await this.createTables(); // register commands - this.registerMusicCommands(); + this.registerCommands(); } /** @@ -66,6 +66,11 @@ exports.GuildHandler = class { name VARCHAR(32) UNIQUE NOT NULL, url VARCHAR(255) NOT NULL )`); + await this.db.run(`${utils.sql.tableExistCreate} commands ( + ${utils.sql.pkIdSerial}, + name VARCHAR(32) UNIQUE NOT NULL, + command VARCHAR(255) NOT NULL + )`); } /** @@ -81,10 +86,7 @@ exports.GuildHandler = class { let resolvedAnswer = await answer; await this.answerMessage(msg, resolvedAnswer); } else if (answer instanceof Array) { - let answerPromises = []; - for (let answerPart of answer) - answerPromises.push(async () => await this.answerMessage(msg, answerPart)); - await waterfall(answerPromises); + await waterfall(answer.map((x) => async () => await this.answerMessage(msg, x))); } else if ({}.toString.call(answer) === '[object Function]') { // check if the answer is of type function await this.answerMessage(msg, answer()); } else { @@ -124,7 +126,7 @@ exports.GuildHandler = class { /** * registers all music commands and initializes a dj */ - registerMusicCommands() { + registerCommands() { this.dj = new music.DJ(); let playCb = async (msg, kwargs, argv, template, next) => { @@ -261,8 +263,8 @@ exports.GuildHandler = class { } }); - // saves playlists - this.servant.createCommand(servercmd.music.save, async (msg, kwargs, argv) => { + // saves playlists and videos + this.servant.createCommand(servercmd.music.savemedia, async (msg, kwargs, argv) => { let saveName = argv.join(' '); let row = await this.db.get('SELECT COUNT(*) count FROM playlists WHERE name = ?', [saveName]); if (!row || row.count === 0) @@ -272,19 +274,75 @@ exports.GuildHandler = class { return `Saved song/playlist as ${saveName}`; }); - // saved command - prints out saved playlists - this.servant.createCommand(servercmd.music.saved, async (msg) => { + // savedmedia command - prints out saved playlists and videos + this.servant.createCommand(servercmd.music.savedmedia, async () => { let response = ''; let rows = await this.db.all('SELECT name, url FROM playlists'); for (let row of rows) response += `[${row.name}](${row.url})\n`; if (rows.length === 0) - msg.channel.send(servercmd.music.saved.response.no_saved); + return servercmd.music.savedmedia.response.no_saved; else return new Discord.RichEmbed() .setTitle('Saved Songs and Playlists') - .setDescription(response); + .setDescription(response) + .setFooter(`Play a saved entry with ${this.prefix}play [Entryname]`) + .setTimestamp(); + }); + + this.servant.createCommand(servercmd.music.deletemedia, async (msg, kwargs, argv) => { + let saveName = argv.join(' '); + if (!saveName) { + return servercmd.music.deletemedia.response.no_name; + } else { + await this.db.run('DELETE FROM playlists WHERE name = ?', [saveName]); + return `Deleted ${saveName} from saved media`; + } + }); + + // savecmd - saves a command sequence with a name + this.servant.createCommand(servercmd.utils.savecmd, async (msg, kwargs, argv) => { + let saveName = argv.pop(); + let cmdsequence = argv.join(' ').replace(/\\/g, ''); + let row = await this.db.get('SELECT COUNT(*) count FROM commands WHERE name = ?', [saveName]); + if (!row || row.count === 0) + await this.db.run('INSERT INTO commands (name, command) VALUES (?, ?)', [saveName, cmdsequence]); + else + await this.db.run('UPDATE commands SET sequence = ? WHERE name = ?', [cmdsequence, saveName]); + return `saved command sequence as ${saveName}`; + }); + + // savedcmd - prints saved commands + this.servant.createCommand(servercmd.utils.savedcmd, async () => { + let response = new Discord.RichEmbed() + .setTitle('Saved Commands') + .setFooter(`Execute a saved entry with ${this.prefix}execute [Entryname]`) + .setTimestamp(); + let rows = await this.db.all('SELECT name, command FROM commands'); + if (rows.length === 0) + return servercmd.utils.savedcmd.response.no_commands; + else + for (let row of rows) + response.addField(row.name, '`' + row.command + '`'); + return response; + }); + + // deletecmd - deletes a command from saved commands + this.servant.createCommand(servercmd.utils.deletecmd, async (msg, kwargs) => { + await this.db.run('DELETE FROM commands WHERE name = ?', [kwargs.cmdname]); + return `Deleted command ${kwargs.cmdname}`; + }); + + // execute - executes a saved command + this.servant.createCommand(servercmd.utils.execute, async (msg, kwargs) => { + let row = await this.db.get('SELECT command FROM commands WHERE name = ?', [kwargs.cmdname]); + if (row) { + msg.content = row.command; + await this.handleMessage(msg); + } else { + return servercmd.utils.execute.response.not_found; + } }); } }; diff --git a/web/http/index.pug b/web/http/index.pug index 69ede1b..145e2c6 100644 --- a/web/http/index.pug +++ b/web/http/index.pug @@ -71,7 +71,7 @@ head span#dj-queueCount | Songs in Queue span.cell - | Next + | Next span#dj-queueDisplayCount 0 | Songs: #dj-songQueue