diff --git a/.gitignore b/.gitignore index 15bdc1627..ebd701cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ npm-debug.log* config.json music images +runtime/databases # Runtime data pids diff --git a/CHANGELOG.md b/CHANGELOG.md index 04614b67e..909899bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# Gamma phase +(Gamma is a real thing in software development by the way, it's a synonym for RC (Release Candidate)) +## 2.0.0-gamma.1 +Added a timeout feature. +**GET HYPED FOR MUSIC STREAMING!** +Changed `versionchecker.js` ability to check for beta versions to gamma version checking. +Moved to LevelDB instead of Redis for handling permission storage and handling timeouts. + +# Beta phase ## 2.0.0-beta.5 Fixes several problems caused by 2.0.0-beta.4 Added an *incomplete* server defaulting system. @@ -27,3 +36,6 @@ Bot is also compliant with the [Discord bot best practises](https://github.com/m ## 2.0.0-beta.1 Initial release of DougBot 2.0, featuring new permission system and stability improvements. + +# Alpha phase +Nothing too interesting lol. diff --git a/DougBot.js b/DougBot.js index a75415d56..a2ae74cda 100644 --- a/DougBot.js +++ b/DougBot.js @@ -14,6 +14,7 @@ var forever = require("forever"); var DebugMode; var VerboseLog; var Defaulting = require("./runtime/serverdefaulting.js"); +var TimeOut = require("./runtime/timingout.js"); // Declare if debug mode or verbose logging is needed if (ConfigFile.bot_settings.debug_mode === true){ @@ -134,11 +135,23 @@ bot.on("message", function(msg) { if (DebugMode === true){ DebugLogger.debug("DEBUG MODE LOG: Execution of command allowed."); } + if (Commands[command].timeout){ + TimeOut.timeoutCheck(command, msg.channel.server.id, function(reply){ + if (reply === "yes"){ + bot.sendMessage(msg.channel, "Sorry, this command is on cooldown."); + return; + }});} if (!Commands[command].nsfw){ if (DebugMode === true){ DebugLogger.debug("DEBUG MODE LOG: Safe for work command executed."); } Commands[command].fn(bot, msg, suffix); + if (Commands[command].timeout){ + TimeOut.timeoutSet(command, msg.channel.server.id, Commands[command].timeout, function(reply, err){ + if (err){ + Logger.error("Resetting timeout failed!"); + } + });} return; } else { Permissions.GetNSFW(msg.channel, function (err, reply){ @@ -173,7 +186,7 @@ bot.on("message", function(msg) { return; } }); - } else { + } else { Permissions.GetLevel(0, msg.author.id, function (err, level){ // Value of 0 is acting as a placeholder, because in DM's only global permissions apply. if (DebugMode === true){ DebugLogger.debug("DEBUG MODE LOG: DM command detected, getting global perms."); diff --git a/TODO.md b/TODO.md index 0ee1c069d..c672401f4 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,6 @@ Reintroduce a timeout feature, but better. *(RC 1)* Music streaming, maybe rewrite to discord.io or discordie to make this easier. *(Proposed)* -Move to MongoDB instead of Redis. *(RC 1)* -Make command prefixes dynamic in their length requirement. *(Beta 5)* +~~Move to MongoDB instead of Redis.~~ **(Scrapped)** Make `memes.json` update automatically with new memes from Imgflip. *(RC 2)* `++remindme`. *(RC 1)* -OOTB experience improvements. *(Beta 5)* ??? diff --git a/package.json b/package.json index 894522592..064b89d3d 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,11 @@ { "name": "DougleyBot", - "version": "2.0.0-beta.5", + "version": "2.0.0-gamma-1", "description": "A Discord bot", "readme": "README.md", - "maintainers": ["Perpetucake"], + "maintainers": [ + "Perpetucake" + ], "author": "Remco Jongschaap (SteamingMutt@users.noreply.github.com)", "repository": { "type": "git", @@ -11,18 +13,19 @@ }, "license": "GPL-3.0", "dependencies": { - "discord.js": "^5.0.0", - "winston": "^1.1.1", "cleverbot-node": "0.2.x", - "request": "^2.61.0", - "xml2js": "*", - "leetspeak": "*", - "imgflipper": "*", "csgo-market": "*", - "unirest": "*", - "youtube-node": "1.2.x", + "discord.js": "hydrabolt/discord.js#0921484ef6775691f0b5fcd17623f6b08837c0a9", "forever": "*", - "redis": "*" + "imgflipper": "*", + "leetspeak": "*", + "leveldown": "^1.4.3", + "levelup": "^1.3.1", + "request": "^2.61.0", + "unirest": "*", + "winston": "^1.1.1", + "xml2js": "*", + "youtube-node": "1.2.x" }, "main": "DougBot.js" } diff --git a/runtime/commands.js b/runtime/commands.js index 494ea104d..0dd797d6d 100644 --- a/runtime/commands.js +++ b/runtime/commands.js @@ -13,7 +13,8 @@ var ConfigFile = require("../config.json"), VerboseLog, DebugLogger = require("./logger.js").DebugModeLog, Defaulting = require("./serverdefaulting.js"), - VerboseLogger = require("./logger.js").VerboseModeLog; + VerboseLogger = require("./logger.js").VerboseModeLog, + DJ = require("./djlogic.js"); if (ConfigFile.bot_settings.verbose_logging === true){ VerboseLog = true; @@ -27,6 +28,7 @@ Commands.ping = { name: "ping", help: "I'll reply to you with pong!", level: 0, + timeout: 10, fn: function(bot, msg){ if (VerboseLog === true) { VerboseLogger.debug("VERBOSE LOG: Ping is being executed."); @@ -34,6 +36,51 @@ Commands.ping = { bot.sendMessage(msg.channel, "Pong!"); }}; +Commands["join-voice"] = { + name: "join-voice", + help: "I'll join a voice channel!", + level: 3, + fn: function(bot, msg){ + if (VerboseLog === true) { + VerboseLogger.debug("VERBOSE LOG: JoinVoice is being executed."); + } + DJ.joinVoice(bot, msg); +}}; + +Commands.play = { + name: "play", + help: "I'll play a weblink containing music!", + usage: "", + level: 2, + fn: function(bot, msg){ + if (VerboseLog === true) { + VerboseLogger.debug("VERBOSE LOG: Play is being executed."); + } + DJ.playMusicURL(bot, msg); +}}; + +Commands.stop = { + name: "stop", + help: "I'll stop playing music.", + level: 3, + fn: function(bot, msg){ + if (VerboseLog === true) { + VerboseLogger.debug("VERBOSE LOG: Stop is being executed."); + } + DJ.stopPlaying(msg); +}}; + +Commands["leave-voice"] = { + name: "stop", + help: "I'll stop playing music.", + level: 3, + fn: function(bot, msg){ + if (VerboseLog === true) { + VerboseLogger.debug("VERBOSE LOG: LeaveVoice is being executed."); + } + DJ.leaveVoice(bot, msg); +}}; + Commands.setstatus = { name: "setstatus", help: "This will change my current status to something else", @@ -777,8 +824,9 @@ Commands.imglist = { } var fs = require("fs"); var path = require("path"); + var ext = [".jpg", ".jpeg", ".gif", ".png"]; var imgArray = []; - fs.readdir(imgDirectory, function(err, dirContents) { + fs.readdir("./images", function(err, dirContents) { for (var i = 0; i < dirContents.length; i++) { for (var o = 0; o < ext.length; o++) { if (path.extname(dirContents[i]) === ext[o]) { diff --git a/runtime/djlogic.js b/runtime/djlogic.js new file mode 100644 index 000000000..2d14ff5a3 --- /dev/null +++ b/runtime/djlogic.js @@ -0,0 +1,62 @@ +var Discord = require("discord.js"), + bot = new Discord.Client(), + request = require("request"), + boundChannel = false, + stream = false, + vol = 0.25; + +exports.joinVoice = function(bot, message){ + if (boundChannel) return; + var channelToJoin = spliceArguments(message.content)[1]; + for (var channel of message.channel.server.channels) { + if (channel instanceof Discord.VoiceChannel) { + if (!channelToJoin || channel.name === channelToJoin) { + boundChannel = message.channel; + bot.reply(message, `Binding to text channel <#${boundChannel.id}> and voice channel **${channel.name}** \`(${channel.id})\``); + bot.joinVoiceChannel(channel); + break; + } + } + } +}; + +exports.playMusicURL = function(bot, message){ + var url = message.content.split(" ")[1]; + bot.voiceConnection.playFile(url,{ + volume : 0.25, + stereo : true + }); + bot.reply(message, "Now playing " + url); + bot.voiceConnection.emit("end"); + bot.sendMessage("Stream has ended"); +}; + +exports.stopPlaying = function(message){ + if (!message.channel.equals(boundChannel)) return; + stopped(); + stream = false; +}; + +exports.leaveVoice = function(bot, message){ + if (!message.channel.equals(boundChannel)) return; + if (!boundChannel) + bot.sendMessage(message, "Can't leave what I'm not in!"); + if (!boundChannel) return; + bot.reply(message, `Unbinding from <#${boundChannel.id}> and destroying voice connection`); + bot.leaveVoiceChannel(); + boundChannel = false; + stream = false; + return; +}; + +function spliceArguments(message, after) { + after = after || 1; + var rest = message.split(' '); + var removed = rest.splice(0, after); + return [removed.join(' '), rest.join(' ')]; +} + +function stopped(){ + if (bot.internal.voiceConnection) bot.internal.voiceConnection.stopPlaying(); + boundChannel.sendMessage("Stream has ended"); +} diff --git a/runtime/dummychecking.js b/runtime/dummychecking.js index de758e098..f9bab4d55 100644 --- a/runtime/dummychecking.js +++ b/runtime/dummychecking.js @@ -1 +1,3 @@ -//TODO: Actually make functions here, lmao +exports.checkConfig = function(){ + // TODO: Finish this function :) +}; diff --git a/runtime/permissions.js b/runtime/permissions.js index 10a12b613..d551974fb 100644 --- a/runtime/permissions.js +++ b/runtime/permissions.js @@ -1,12 +1,8 @@ -var ConfigFile = require("../config.json"); -var Redis = require("redis"); -var Logger = require("./logger.js").Logger; +var ConfigFile = require("../config.json"), + LevelUP = require("levelup"), + Logger = require("./logger.js").Logger; -if (ConfigFile.redis.url && !ConfigFile.redis.host) { - var RedisServer = Redis.createClient(ConfigFile.redis.url); -} else { - var RedisServer = Redis.createClient(ConfigFile.redis.port, ConfigFile.redis.host); -} +var db = LevelUP('./runtime/databases/discord_permissions'); exports.GetLevel = function(sum, user, callback){ if (user === ConfigFile.permissions.masterUser){ @@ -21,54 +17,58 @@ exports.GetLevel = function(sum, user, callback){ if (user == ConfigFile.permissions.level3){ return callback (null, 3); // Hardcoded reply if user has a global permission } - // Else, connect to the Redis server and fetch the user level - RedisServer.get("auth_level:" + sum, function(err, reply) { + // Else, connect to LevelUP and fetch the user level + db.get("auth_level:" + sum, function(err, value) { if (err) { - return callback(err, -1); - } - if (reply) { - return callback(null, parseInt(reply)); - } else { - callback(null, 0); // Return 0 if no value present in Redis + if (err.notFound) { + callback(null, 0); // Return 0 if no value present in LevelUP + return; + } else { + Logger.error("LevelUP error! " + err); + callback(err, -1); + }} + if (value) { + return callback(null, parseInt(value)); } }); }; exports.GetNSFW = function(channel, callback){ - RedisServer.get("auth_nsfw:" + channel, function(err, reply) { + db.get("auth_nsfw:" + channel, function(err, value) { if (err) { - return callback(err, -1); - } - if (reply) { - return callback(null, reply); + if (err.notFound) { + callback(null, "off"); // Return 0 if no value present in LevelUP + return; } else { - callback(null, "off"); + Logger.error("LevelUP error! " + err); + callback(err, -1); + }} + if (value) { + return callback(null, value); } }); }; exports.SetLevel = function(sum, level, callback){ - RedisServer.set("auth_level:" + sum, parseInt(level), function(err, reply) { + db.put("auth_level:" + sum, parseInt(level), function(err) { if (err) { + Logger.error("LevelUP error! " + err); callback(err, -1); } - if (reply) { + if (!err) { callback(null, parseInt(level)); - } else { - return callback(null, 0); } }); }; exports.SetNSFW = function(channel, allow, callback){ - RedisServer.set("auth_nsfw:" + channel, allow, function(err, reply) { + db.put("auth_nsfw:" + channel, allow, function(err) { if (err) { + Logger.error("LevelUP error! " + err); callback(err, -1); } - if (reply) { + if (!err) { callback(null, allow); - } else { - return callback(null, null); - } + } }); }; diff --git a/runtime/serverdefaulting.js b/runtime/serverdefaulting.js index 527af4917..4194c6bdb 100644 --- a/runtime/serverdefaulting.js +++ b/runtime/serverdefaulting.js @@ -53,7 +53,7 @@ exports.fetch = function(callback){ // TODO: This is the most ridicolous method callback(ServerID); }; -exports.check = function(callback){ +exports.check = function(callback){ // TODO: Make more reliable, meant to fetch servers in which bot is the owner var oldServers = []; for (var owner in bot.servers) { oldServers[owner] = bot.servers[owner]; diff --git a/runtime/timingout.js b/runtime/timingout.js new file mode 100644 index 000000000..79e981616 --- /dev/null +++ b/runtime/timingout.js @@ -0,0 +1,39 @@ +var LevelUP = require("levelup"), + Logger = require("./logger.js").Logger; +var db = LevelUP('./runtime/databases/discord_timeouts'); + +exports.timeoutCheck = function(command, channel, callback){ + var d = new Date(), + CurrentTime = Date.UTC(); + db.get("timeout_till:" + command + channel , function(err, value) { + if (err){ + if (err.notFound) { + callback(null, "no"); // Return 0 if no value present in LevelUP + return; + } else { + Logger.error("LevelUP error! " + err); + return callback(err, -1); + }} else if (value < CurrentTime){ + db.del("timeout_till:" + command + channel, function(err){ + if (err){ + Logger.error("LevelUP error! " + err); + } else { + return callback(null, "yes"); + }}); + } else { + return callback(null, "no"); + } + }); +}; + +exports.timeoutSet = function(command, channel, timeout, callback){ + var till = (Date.UTC() + (timeout * 1000)); + db.put("timeout_till:" + command + channel, till, function(err) { + if (err){ + Logger.error("LevelUP error! " + err); + return callback(err, -1); + } else { + return callback(null, "okay"); + } + }); +}; diff --git a/runtime/versionchecker.js b/runtime/versionchecker.js index 7a33a7c82..031ae4950 100644 --- a/runtime/versionchecker.js +++ b/runtime/versionchecker.js @@ -14,7 +14,7 @@ exports.getCurrentVersion = function() { return version.join("."); }; exports.getCurrentMajor = function() { return version[0]; }; exports.getCurrentMinor = function() { return version[1]; }; exports.getCurrentPatch = function() { return version[2]; }; -exports.getCurrentBeta = function() { return version[3]; }; +exports.getCurrentGamma = function() { return version[3]; }; exports.getLatestVersion = function(callback) { @@ -56,7 +56,7 @@ exports.getLatestPatch = function(callback) { }); }; -exports.getLatestBeta = function(callback) { +exports.getLatestGamma = function(callback) { this.getLatest(function(err, latest) { if (err) { return callback(err, null); } // error handle return callback(null, parseInt(latest.split("-")[0])); @@ -82,7 +82,7 @@ exports.getStatus = function(callback) { var majorDiff = parseInt(latest[0]) - parseInt(version[0]); var minorDiff = parseInt(latest[1]) - parseInt(version[1]); var patchDiff = parseInt(latest[2]) - parseInt(version[2]); - var betaDiff = parseInt(latest[3]) - parseInt(version[3]); + var gammaDiff = parseInt(latest[3]) - parseInt(version[3]); // check for major updates if (majorDiff < 0) { @@ -105,10 +105,10 @@ exports.getStatus = function(callback) { return callback(null, "Bot is " + Math.abs(patchDiff) + " patch versions behind. (current: " + version.join(".") + ", latest: " + latest.join(".") + ")"); } - if (betaDiff < 0) { - return callback(null, "Bot is " + Math.abs(betaDiff) + " beta versions ahead! (current: " + version.join(".") + ", latest: " + latest.join(".") + ")"); - } else if (betaDiff > 0) { - return callback(null, "Bot is " + Math.abs(betaDiff) + " beta versions behind. (current: " + version.join(".") + ", latest: " + latest.join(".") + ")"); + if (gammaDiff < 0) { + return callback(null, "Bot is " + Math.abs(gammaDiff) + " gamma versions ahead! (current: " + version.join(".") + ", latest: " + latest.join(".") + ")"); + } else if (gammaDiff > 0) { + return callback(null, "Bot is " + Math.abs(gammaDiff) + " gamma versions behind. (current: " + version.join(".") + ", latest: " + latest.join(".") + ")"); } // up to date :)