"use strict"; /* Avoid editing this file to prevent erasure of edits by future updates. See customchatcommands.example.js for a bit more info. */ const C = require("cli-color"); const ent = require("html-entities").AllHtmlEntities; const utils = require("./utils.js"); const strings = require("./strings.js"); const api = require("./api.js"); var chatCommands = {}; /* Define command aliases here as: alias: commandName */ var aliases = { kill: "exit", closepoll: "endpoll", ec: "emotecount", uec: "useremotecount", ud: "urbandictionary", seen: "lastseen", ask: "8ball", accept: "acceptduel", decline: "declineduel", choose: "pick", lang: "language", ipban: "ban" } module.exports = {}; function createCommands(bot) { if (bot.handlingChatCommands) return false; bot.logger.debug(strings.format(bot, "COMMAND_CREATING")); /* Command object definition. Receives data as a key/value object of multiple variables and a function which is executed when the command is called. Key name and cmdName MUST be lowercase. data - Object. cmdName - String. MUST match the name of the command's key in the commands object. Ignoring this will cause issues. minRank - Float. Target rank to compare the user's rank to. Uses rankMatch. rankMatch - String, but only <=, ==, or >=. Defines how minRank will be evaluated. If <=, the command will only exec for users with a rank less than or equal to minRank, and so on. userCooldown - Integer. Amount of time in milliseconds before the same user can use this command again (each command has their own independent cooldown; overlaps with cmdCooldown) cmdCooldown - Integer. Amount of time in milliseconds before anyone can use this command again. Differs from userCooldown in that the cooldown is not for each single user independently, but for everyone. isActive - Boolean. Determines if the command is active at start. Can be enabled later through the enable command. requiredChannelPerms - Array. List of channel perms (see bottom of bot.js for descriptions on these permissions) required for the command to run. Example: ["chat", "seeplaylist"] allowRankChange - Boolean. Determines if the command's rank may be changed or overridden, especially with the setrank command. canBeUsedInPM - Boolean. Determines if the command can be used within a private message. fn - Function. The action performed when the command is called. Failure to assign all of these values correctly will result in a broken command which will not run until fixed and the bot is restarted. Broken commands will be error logged and cannot be enabled. */ /** * Command object definition. Receives data as a key/value object of multiple variables and a function which is executed when the command is called. * @constructor * @param {Object} data Object of command properties. See comment in file for more info * @param {Function} fn Command action */ function Command(data, fn) { this.cmdName = data.cmdName; this.minRank = parseFloat(data.minRank); this.rankMatch = data.rankMatch; this.userCooldown = parseInt(data.userCooldown); this.cmdCooldown = parseInt(data.cmdCooldown); this.isActive = utils.parseBool(data.isActive); this.requiredChannelPerms = data.requiredChannelPerms; this.allowRankChange = utils.parseBool(data.allowRankChange); this.canBeUsedInPM = utils.parseBool(data.canBeUsedInPM); this.fn = fn; this.broken = false; this.defaultRank = this.minRank; this.defaultRankMatch = this.rankMatch; this.defaultCmdCooldown = this.cmdCooldown; this.defaultUserCooldown = this.userCooldown; this.defaultActiveState = this.isActive; if (!Array.isArray(this.requiredChannelPerms) || typeof this.cmdName !== "string" || typeof this.rankMatch !== "string" || this.cmdName === "" || !isValidRankMatch(this.rankMatch) || null === this.allowRankChange || null === this.isActive || null === this.canBeUsedInPM || isNaN(this.minRank) || isNaN(this.userCooldown) || isNaN(this.cmdCooldown) || this.cmdName.toLowerCase() !== this.cmdName || typeof fn !== "function") { bot.logger.error(strings.format(bot, "COMMAND_INVALID", [this.cmdName])); this.isActive = false; this.broken = true; } else if (!this.isActive) { bot.logger.warn(strings.format(bot, "COMMAND_INACTIVE", [this.cmdName])); } } Command.prototype.lastUse = 0; //Put commands below as a key/value pair. //Template: /* "": new Command({ cmdName: "", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 2000, cmdCooldown: 500, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { //do something }) */ var commands = { "8ball": new Command({ cmdName: "8ball", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 30000, cmdCooldown: 500, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { if (message === "") return false; var answers = ["It is certain", "It is decidedly so", "Without a doubt", "Yes - definitely", "You may rely on it", "As I see it, yes", "Most likely", "Outlook good", "Signs point to yes", "Yes", "Ask again later", "Better not tell you now", "Cannot predict now", "Don't count on it", "My reply is no", "My sources say no", "Outlook not so good", "Very doubtful", "Never", "Of course not", "Of course!"]; var answer = answers[Math.floor(Math.random() * answers.length)]; bot.sendChatMsg(strings.format(bot, "CHAT_EIGHTBALL", [message, answer])); return true; }), "about": new Command({ cmdName: "about", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 120000, cmdCooldown: 60000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { bot.sendChatMsg(bot.botName + " v" + bot.version + " :: written by biggles- :: https://github.com/deerfarce/ChozoBot", false, false); return true; }), "acceptduel": new Command({ cmdName: "acceptduel", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { let duel = bot.getUserDuel(user.name, false); if (duel && duel[0] !== user.name) { bot.getUserDuel(user.name, true); return bot.commenceDuel(duel); } return false; }), /* "addfromplaylist": new Command({ cmdName: "addfromplaylist", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 20000, cmdCooldown: 10000, isActive: false, requiredChannelPerms: ["playlistadd"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { Add videos using the youtubeplaylist API method }), */ "allow": new Command({ cmdName: "allow", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 500, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { let target = message.split(" ")[0]; if (utils.isValidUserName(target) && callerOverTargetRank(user, target)) { let success = bot.allowUser(target); if (success) bot.sendPM(user.name, target + " allowed."); } return false; }), "anagram": new Command({ cmdName: "anagram", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 3000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { var msg = message.trim(); if (7<=msg.length && msg.length<=30) { api.APIcall(bot, "anagram", msg, null, function(status, data, ok) { if (!ok || !data) return false; bot.sendChatMsg(strings.format(bot, "ANAGRAM_RESULT", [msg, ent.decode(utils.removeExcessWhitespace(utils.removeHtmlTags(data[1])))])); }); return true; } else { bot.sendChatMsg(strings.format(bot, "ANAGRAM_BAD_LENGTH", [7, 30])); } return false; }), "ban": new Command({ cmdName: "ban", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: ["ban", "chat"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { return disciplineUser(user, cmd, "ipban", message.trim()); }), "blacklist": new Command({ cmdName: "blacklist", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["playlistdelete"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { let spl = message.split(" "), result = false, op = "add", name = ""; if (spl.length >= 2 && spl[0].toLowerCase() === "/remove" && utils.isValidUserName(spl[1])) { result = bot.setUserBlacklistState(spl[1], false); name = spl[1]; op = "remove"; } else if (spl[0] && utils.isValidUserName(spl[0])) { result = bot.setUserBlacklistState(spl[0], true); name = spl[0]; } if (result) { if (op === "add") { bot.sendPM(user.name, strings.format(bot, "BLACKLIST_SUCCESS", [name])); bot.logger.mod(user.name + " blacklisted " + name); bot.purgeUser(name); } else { bot.sendPM(user.name, strings.format(bot, "BLACKLIST_REMOVE_SUCCESS", [name])); bot.logger.mod(user.name + " removed " + name + " from the blacklist."); } } else { if (op === "add") bot.sendPM(user.name, strings.format(bot, "BLACKLIST_FAIL", [name])); else bot.sendPM(user.name, strings.format(bot, "BLACKLIST_REMOVE_FAIL", [name])); } return result; }), "blacklistvid": new Command({ cmdName: "blacklistvid", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["playlistdelete", "seeplaylist"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { let spl = message.split(" "), state = true, result = false, media = null; if (spl[0]) { if (spl[0].toLowerCase() === "/remove") { spl.splice(0,1); state = false; if (spl.length <= 0) return false; } let match = spl[0].match(/^(\w\w)\:([\w\/\.\:\-]+?)$/i); if (match) { media = {id: match[2], type: match[1].toLowerCase()}; result = bot.setMediaBlacklistState(media, state); } else { let pos = parseInt(spl[0]); if (!isNaN(pos)) { pos--; let pl = bot.CHANNEL.playlist; if (pos < 0 || pos >= pl.length) return false; media = {id: pl[pos].media.id, type: pl[pos].media.type.toLowerCase()}; result = bot.setMediaBlacklistState(pl[pos], state); } } } else { let cm = bot.CHANNEL.currentMedia; if (cm) { media = {id: cm.id, type: cm.type}; result = bot.setMediaBlacklistState(cm, true); } } let shortMedia = ""; if (media) shortMedia = utils.formatLink(media.id, media.type, true); if (result) { if (state && media) { bot.deleteVideoAndDupes(media); bot.sendPM(user.name, strings.format(bot, "BLACKLIST_SUCCESS", [shortMedia])); bot.logger.mod(user.name + " blacklisted " + shortMedia); } else if (!state) { bot.sendPM(user.name, strings.format(bot, "BLACKLIST_REMOVE_SUCCESS", [shortMedia])); bot.logger.mod(user.name + " removed " + shortMedia + " from the blacklist."); } } else if (shortMedia !== "") { if (state) { bot.sendPM(user.name, strings.format(bot, "BLACKLIST_FAIL", [shortMedia])); } else { bot.sendPM(user.name, strings.format(bot, "BLACKLIST_REMOVE_FAIL", [shortMedia])); } } return result; }), "bump": new Command({ cmdName: "bump", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 10000, cmdCooldown: 5000, isActive: true, requiredChannelPerms: ["seeplaylist", "playlistmove"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { const BUMP_CAP = bot.cfg.media.bumpCap; const BUMP_COOLDOWN = bot.cfg.media.bumpCooldown; let playlist = bot.CHANNEL.playlist; let explicitPos = false; let spl = message.split(" "); let vidObj = null; let bumpPos = BUMP_CAP; let getPos = true; if (message.trim() !== "") { //See if the very first arg is a number let _pos = parseInt(spl[0]); if (!isNaN(_pos)) { //If it's a number, then check if it's a valid name as some people have //numeric usernames if (utils.isValidUserName(spl[0])) { //Find the user's video vidObj = findLastMedia(spl[0]); //If no video found, interpret as a self bump to the given position if (!vidObj) { vidObj = findLastMedia(user.name); bumpPos = _pos; explicitPos = true; getPos = false; } } } else if (utils.isValidUserName(spl[0])) { vidObj = findLastMedia(spl[0]); } if (getPos && spl[1]) { let _pos = parseInt(spl[1]); if (isNaN(_pos) || _pos > playlist.length) { bot.sendPM(user.name, "bump: Invalid position given. Usage: " + bot.trigger + this.cmdName + " [user] [position]"); return false; } else { explicitPos = true; bumpPos = _pos; } } } else { vidObj = findLastMedia(user.name); } if (bumpPos < BUMP_CAP) bumpPos = BUMP_CAP; //Decrement because we're expecting users to provide indexes based on //how they see the playlist on CyTube (1-based index) //..but also another time because we want to put it after a video bumpPos -= 2; if (!vidObj || bumpPos + 2 >= playlist.length || bumpPos < 0) return false; const TARGETUSER = vidObj.media.queueby; if (bot.disallowed(TARGETUSER)) { bot.sendPM(user.name, TARGETUSER + " is disallowed."); return false; } if (bot.bumpStats.users.hasOwnProperty(TARGETUSER)) { let timeSinceLast = Date.now() - bot.bumpStats.users[TARGETUSER]; if (timeSinceLast < BUMP_COOLDOWN) { bot.sendPM(user.name, TARGETUSER + " was last bumped " + timeSinceLast/1000 + " seconds ago. You can try bumping this user again in " + ((BUMP_COOLDOWN - timeSinceLast)/1000) + " seconds."); return false; } } let bumpReference = playlist[bumpPos], lastBumped = bot.bumpStats.lastBumpedUIDs; if (lastBumped.length > 0) { let last_index = lastBumped.length-1; for (;last_index >= 0;last_index--) { let index = bot.getMediaIndex(lastBumped[last_index]); if (!~index || index < BUMP_CAP - 1) { lastBumped.splice(last_index, 1); continue; } else if (~index && index >= BUMP_CAP - 1 && index < playlist.length) bumpReference = playlist[index]; break; } } if (!bumpReference || bumpReference.uid === vidObj.media.uid) return false; if (!explicitPos && vidObj.media.media.seconds > 600) { bot.sendPM(user.name, "Video Over 10 minutes - please specify a position to bump to or "+bot.trigger+"vidya if video games") return false; } bot.bumpStats.users[TARGETUSER] = Date.now(); bot.logger.mod(strings.format(bot, "BUMP_LOG", [ "BUMP", utils.formatLink(vidObj.media.media.id, vidObj.media.media.type, true), TARGETUSER, user.name, (vidObj.index+1), (bumpPos+2) ])); bot.bumpStats.lastBumpedUIDs.push(vidObj.media.uid); bot.bumpStats.bumpingUIDs.push(vidObj.media.uid); bot.moveMedia(vidObj.media.uid, bumpReference.uid); if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.bump_stats) { let column = "others"; if (TARGETUSER === user.name) column = "self"; bot.db.run("bumpCount", [user.name, column]); } return true; }), "cleanemotedb": new Command({ cmdName: "cleanemotedb", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 60000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.emote_data) { bot.db.run("getStoredEmotes", null, function(res) { if (res && res.rowCount > 0) { let i = 0, rows = res.rows, unused = []; for (;i < rows.length; i++) { if (!bot.emoteExists(rows[i])) { unused.push(rows[i]); } } if (unused.length <= 0) { bot.sendChatMsg(strings.format(bot, "DB_EMOTES_CLEANED_NONE", [user.name])); } else { bot.db.run("cleanUnusedEmotes", [unused], function() { bot.sendChatMsg(strings.format(bot, "DB_EMOTES_CLEANED", [user.name])); }); } } }); return true; } return false; }), "clearcooldownoverrides": new Command({ cmdName: "clearcooldownoverrides", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { var spl = message.split(" "); if (spl.length >= 1 && spl[0].trim() !== "") { const COMMAND = resolveCmdName(spl[0]); if (COMMAND) { COMMAND.userCooldown = COMMAND.defaultUserCooldown; COMMAND.cmdCooldown = COMMAND.defaultCmdCooldown; delete bot.settings.cmdCooldownOverrides[COMMAND.cmdName]; delete bot.settings.userCooldownOverrides[COMMAND.cmdName]; bot.writeSettings(); bot.sendPM(user.name, "Cooldowns have been reset for " + COMMAND.cmdName + "."); bot.logger.mod("Cooldowns for " + COMMAND.cmdName + " reset by " + user.name); return true; } else { bot.sendPM(user.name, "That command does not exist."); } } else { bot.sendPM(user.name, "You must provide a command name."); } return false; }), "clearrankoverrides": new Command({ cmdName: "clearrankoverrides", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { var spl = message.split(" "); return resetRank(spl[0], user); }), "clearemotecount": new Command({ cmdName: "clearemotecount", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 60000, cmdCooldown: 500, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.emote_data) { bot.db.run("deleteUserEmotes", [user.name], function() { bot.sendPM(user.name, strings.format(bot, "DB_EMOTES_ERASED", [bot.trigger])); }); return true; } return false; }), "clearquotes": new Command({ cmdName: "clearquotes", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 60000, cmdCooldown: 500, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.chat) { bot.db.run("deleteUserChat", [user.name], function() { bot.sendPM(user.name, strings.format(bot, "DB_QUOTES_ERASED", [bot.trigger])); }) return true; } return false; }), "comment": new Command({ cmdName: "comment", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 3000, cmdCooldown: 3000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { function extractComment(comments) { let comment = comments.length > 1 ? comments[Math.floor(Math.random() * comments.length)] : comments[0]; if (comment.snippet && comment.snippet.topLevelComment && comment.snippet.topLevelComment.snippet) { let cchild = comment.snippet.topLevelComment.snippet; return { author: ent.decode(utils.removeExcessWhitespace(utils.removeHtmlTags(cchild.authorDisplayName))), text: ent.decode(utils.removeExcessWhitespace(utils.removeHtmlTags(cchild.textOriginal))) }; } return null; } var currentMedia = bot.CHANNEL.currentMedia; if (!currentMedia || currentMedia.type !== "yt") { bot.sendPM(user.name, "A YouTube video must be playing to get a random comment."); return false; } let cvd = bot.currentVideoData, authorCode = bot.cfg.chat.filters.commentAuthor, ID = currentMedia.id; if (ID === cvd.id && (cvd.comments !== null || cvd.commentsDisabled)) { if (!cvd.comments) return false; if (cvd.commentsDisabled) { bot.sendChatMsg(strings.format(bot, "API_YT_COMMENTSDISABLED")); return true; } else if (cvd.comments.length <= 0) { bot.sendChatMsg(strings.format(bot, "API_YT_NOCOMMENTS")); return true; } else { let comment = extractComment(cvd.comments); if (!comment) return false; bot.sendChatMsg("["+authorCode+"]**<" + comment.author + ">**[/"+authorCode+"] " + comment.text); return true; } } else { if (bot.gettingComments) { bot.sendPM(user.name, "The bot is currently getting the list of comments. Try again in a few seconds. If this persists, this is an issue."); return false; } bot.gettingComments = true; api.APIcall(bot, "youtubecomments", ID, bot.cfg.api.youtube_key, function (status, data) { cvd.id = ID; if (!status) { bot.gettingComments = false; return false; } if (status === true && data && data.items) { cvd.comments = data.items; bot.gettingComments = false; if (data.items.length < 1) { return bot.sendChatMsg(strings.format(bot, "API_YT_NOCOMMENTS")); } let comment = extractComment(data.items); if (!comment) return true; bot.sendChatMsg("["+authorCode+"]**<" + comment.author + ">**[/"+authorCode+"] " + comment.text); return true; } else if (!data || (data && (!data.items || (data.error && data.error.errors)))) { if (status === 403) { if (data && data.error && data.error.errors && data.error.errors[0]["reason"]) { var reason = data.error.errors[0]["reason"]; if (reason === "commentsDisabled") { cvd.commentsDisabled = true; bot.gettingComments = false; return bot.sendChatMsg(strings.format(bot, "API_YT_COMMENTSDISABLED")); } else { bot.gettingComments = false; return bot.logger.error(strings.format(bot, "API_YT_ERROR", ["youtubecomments", status, reason])); } } } bot.gettingComments = false; return bot.sendPM(user.name, "Error getting comments. Is a YouTube video playing?"); } }); } return true; }), "cooldown": new Command({ cmdName: "cooldown", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 2000, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { const help = ()=>{ bot.sendPM(user.name, bot.trigger + this.cmdName + " commandname (global,cmd)|user duration: Sets the cooldown duration for a given command."); } let spl = message.split(" "); if (spl.length > 0) { let cmdName = spl[0].toLowerCase(); if (cmdName === "") { help(); return false; } const COMMAND = resolveCmdName(cmdName); if (COMMAND) { if (COMMAND.broken) { bot.sendPM(user.name, "That command is broken. Tell the bot maintainer."); return false; } if (spl.length > 2) { let type = spl[1].toLowerCase(); let duration = parseInt(spl[2]); if (isNaN(duration)) { help(); return false; } else if (duration < 0) { bot.sendPM(user.name, "Invalid duration. Must be 0 or higher."); return false; } let overrides = null, old_cooldown = -1; if (type === "global" || type === "cmd") { old_cooldown = COMMAND.cmdCooldown; COMMAND.cmdCooldown = duration; bot.sendPM(user.name, "Set the global cooldown for " + cmdName + " to " + duration/1000 + " seconds."); overrides = bot.settings.cmdCooldownOverrides; type = "cmd"; //simplify for later because we allow different inputs here } else if (type === "user") { old_cooldown = COMMAND.userCooldown; COMMAND.userCooldown = duration; bot.sendPM(user.name, "Set the user cooldown for " + cmdName + " to " + duration/1000 + " seconds."); overrides = bot.settings.userCooldownOverrides; } else { help(); return false; } if (overrides) { if ((type === "cmd" && duration === COMMAND.defaultCmdCooldown) || (type === "user" && duration === COMMAND.defaultUserCooldown)) { delete overrides[COMMAND.cmdName]; } else { overrides[COMMAND.cmdName] = duration; } bot.logger.mod(user.name + "changed the cooldown ("+type+") for " + COMMAND.cmdName + ": " + old_cooldown + " => " + duration); bot.writeSettings(); } return true; } else { help(); return false; } } else { bot.sendPM(user.name, "That command, " + cmdName + ", doesn't exist."); return false; } } return false; }), "declineduel": new Command({ cmdName: "declineduel", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { let duel = bot.getUserDuel(user.name, true); if (duel && duel[1] === user.name) { bot.sendChatMsg(strings.format(bot, "DUEL_DECLINE", [user.name, duel[0], ""])); return true; } return false; }), "deletepoll": new Command({ cmdName: "deletepoll", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 10000, cmdCooldown: 5000, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.saved_polls) { let name = message.trim(); if (name.length < 1 || name.length > 30) { bot.sendPM(user.name, "Poll name must be 1-30 characters."); } else { bot.db.run("deletePoll", [user.name, name], function(res) { if (res.rowCount <= 0) { bot.sendPM(user.name, "That poll either does not exist, or was not saved by you."); } else { bot.sendPM(user.name, "Poll deleted: " + name); } }) return true; } } return false; }), "disable": new Command({ cmdName: "disable", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { return disable(bot, message.split(" ")[0].toLowerCase(), user.name); }), "disallow": new Command({ cmdName: "disallow", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 500, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { let target = message.split(" ")[0]; if (utils.isValidUserName(target) && callerOverTargetRank(user, target)) { let success = bot.disallowUser(target); if (success) bot.sendPM(user.name, target + " disallowed."); } return false; }), "duel": new Command({ cmdName: "duel", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 5000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { let target = message.split(" ")[0].toLowerCase(); var invalidNames = ["", bot.username.toLowerCase(), user.name.toLowerCase()]; if (~invalidNames.indexOf(target) || bot.userIsBot(target) || bot.disallowed(target)) return false; let targetUser = bot.getUser(target); if (targetUser && targetUser.rank >= bot.RANKS.USER) { let callerDuel = bot.getUserDuel(user.name); if (!callerDuel) { let targetDuel = bot.getUserDuel(targetUser.name); if (!targetDuel) { let timeout = setTimeout(function() { let duel = bot.getUserDuel(user.name, true); if (duel) bot.sendChatMsg(strings.format(bot, "DUEL_EXPIRED", [user.name, targetUser.name])); }, 120000); bot.duels.push([user.name, targetUser.name, timeout]); bot.sendChatMsg(strings.format(bot, "DUEL_BEGIN", [targetUser.name, user.name, bot.trigger, "acceptduel", "declineduel"])) } else { bot.sendPM(user.name, strings.format(bot, "DUEL_PM_INDUEL")); } } else { if (callerDuel[0] === user.name) { bot.sendPM(user.name, strings.format(bot, "DUEL_PM_CALLERWAITING", [callerDuel[1]])); } else { bot.sendPM(user.name, strings.format(bot, "DUEL_PM_TARGETWAITING", [callerDuel[0]])); } } return true; } return false; }), "duelrecord": new Command({ cmdName: "duelrecord", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 5000, cmdCooldown: 500, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { if (bot.cfg.db.useTables.users && bot.cfg.db.useTables.duel_stats) { bot.db.run("getDuelRecord", [user.name], function(res) { let wins = 0, losses = 0, winRate = "0%"; if (res && res.rowCount > 0) { let row = res.rows[0]; wins = row.wins; losses = row.losses; if (wins > 0) winRate = (100 * wins / (wins + losses)).toFixed(2) + "%"; } bot.sendChatMsg(strings.format(bot, "DUEL_RECORD", [user.name, wins, losses, winRate])); }); return true; } else { bot.sendPM(user.name, "The duel statistics database table is currently disabled."); } return false; }), "echo": new Command({ cmdName: "echo", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 500, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { bot.sendChatMsg(message, false, false); return true; }), "emotecount": new Command({ cmdName: "emotecount", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 5000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.emote_data) { var spl = message.split(" "); if (spl[0] !== "" && bot.emoteExists(spl[0])) { bot.db.run("getEmoteTotalCount", [spl[0]], function(res) { if (res && res.rowCount > 0 && res.rows[0].sum != null) { var row = res.rows[0]; bot.sendChatMsg(strings.format(bot, "CHAT_EC_USED", [spl[0], row.sum, (row.sum == 1 ? "time" : "times")])); } else { bot.sendChatMsg(strings.format(bot, "CHAT_EC_NOTUSED", [spl[0]])); } }); return true; } } return false; }), "enable": new Command({ cmdName: "enable", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { return enable(bot, message.split(" ")[0].toLowerCase(), user.name); }), "endpoll": new Command({ cmdName: "endpoll", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 1000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["pollctl"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { bot.socket.emit("closePoll"); bot.logger.mod(strings.format(bot, "POLL_CLOSED_CMD", [user.name])); return true; }), "exit": new Command({ cmdName: "exit", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { bot.handlingChatCommands = false; bot.kill("exit issued via chat by " + user.name, 1000, 3); return true; }), "flatskiprate": new Command({ cmdName: "flatskiprate", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 5000, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { let skipopts = bot.getOpt("flatSkiprate", { managing: false, target: -1, original_rate: -1 } ), spl = message.split(" "), help = bot.trigger + this.cmdName + " skip_target|off - Enables flat skiprate with skip_target (number above 0) as the desired amount of skips, or disables if \"off\" is given."; if (spl.length >= 1) { if (spl[0].toLowerCase() === "off") { if (skipopts.managing) { skipopts.managing = false; skipopts.original_rate = -1; bot.setOpt("flatSkiprate", skipopts); let msg = "No longer managing flat skiprate."; if (skipopts.original_rate < 0) { msg += " However, there was no valid original skiprate stored. It has been set to 50%."; bot.setChannelOpts({voteskip_ratio: 0.5}); } else { bot.setChannelOpts({voteskip_ratio: skipopts.original_rate}); } bot.sendPM(user.name, msg); bot.logger.mod(user.name + " disabled automatic flat skiprate."); return true; } else { bot.sendPM(user.name, "Automatic flat skiprate is currently disabled. Provide the desired amount of skips instead to enable it."); } } else { let target = parseInt(spl[0]); if (isNaN(target) || target <= 0) { bot.sendPM(user.name, help); } else { let was_managing = skipopts.managing, same_target = skipopts.target === target; skipopts.managing = true; if (skipopts.original_rate < 0) skipopts.original_rate = bot.CHANNEL.opts.voteskip_ratio; skipopts.target = target; bot.setOpt("flatSkiprate", skipopts); let success = bot.setFlatSkiprate(); if (success) { if (was_managing) { if (same_target) { bot.sendPM(user.name, "Already managing flat skiprate with that target."); } else { bot.sendPM(user.name, "Already managing flat skiprate, but the target amount has been changed."); bot.logger.mod(user.name + " changed flat skiprate target to " + target); } } else { bot.sendPM(user.name, "Now managing flat skiprate with a target skip amount of " + target); bot.logger.mod(user.name + " enabled flat skiprate with a target of " + target); } } else { bot.sendPM(user.name, "Unable to begin managing skiprate. Check if I'm a mod."); } return success; } } } else { bot.sendPM(user.name, help); } return false; }), "getchanlog": new Command({ cmdName: "getchanlog", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 60000, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { bot.readChanLog(); return true; }), "gettimeban": new Command({ cmdName: "gettimeban", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 500, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { let spl = message.split(" "); if (spl.length <= 0 || !utils.isValidUserName(spl[0])) return false; let timeban = bot.userIsTimebanned(spl[0]), timeLeft = (timeban ? (timeban.unbanTime - Date.now()) / 1000 : 0); if (timeban) { if (timeLeft < 60) { bot.sendPM(user.name, strings.format(bot, "TIMEBAN_SOON", [spl[0]])); } else { bot.sendPM(user.name, strings.format(bot, "TIMEBAN_TIME", [spl[0], utils.secsToTime(timeLeft, true)])); } return true; } else { bot.sendPM(user.name, strings.format(bot, "TIMEBAN_NONE", [spl[0]])); } return false; }), "img": new Command({ cmdName: "img", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 30000, cmdCooldown: 5000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { let code = bot.cfg.chat.filters.img, spoilerTagOpener = bot.cfg.chat.filters.spoilerTagOpener, spoilerTagCloser = bot.cfg.chat.filters.spoilerTagCloser; if (!code || code === "") { bot.sendPM(user.name, "The chatfilter for " + this.cmdName + " is not set up yet. Tell an admin."); return false; } let spl = message.split(" "); if (spl.length <= 0) return false; let spoiler = false; if (spl[0].toLowerCase() === "spoiler") { spoiler = true; spl.shift(); if (!spoilerTagOpener || !spoilerTagCloser || spoilerTagOpener === "" || spoilerTagCloser === "") { bot.sendPM(user.name, "The chatfilter for spoilers is not set up yet. Tell an admin."); return false; } } let link = utils.parseImageLink(spl[0]); if (link) { let final = link + "." + code; if (spoiler) { final = spoilerTagOpener + final + spoilerTagCloser; } if (final.length > 320) { bot.sendPM(user.name, "That link is too long, especially with the filters."); return false; } bot.sendChatMsg(final); return true; } return false; }), "isblacklisted": new Command({ cmdName: "isblacklisted", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 500, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { let spl = message.split(" "); if (spl.length <= 0) return false; if (utils.isValidUserName(spl[0])) { let isBlacklisted = ~bot.settings.userBlacklist.indexOf(spl[0].toLowerCase()); if (isBlacklisted) { bot.sendPM(user.name, spl[0] + " is a blacklisted user."); } else { bot.sendPM(user.name, spl[0] + " is not blacklisted."); } return true; } else { let match = spl[0].match(/^(\w\w)\:([\w\/\.\:]+?)$/i); if (match) { let media = {id: match[2], type: match[1].toLowerCase()}; if (bot.mediaIsBlacklisted(media)) { bot.sendPM(user.name, spl[0] + " is blacklisted media."); } else { bot.sendPM(user.name, spl[0] + " is not blacklisted."); } return true; } } return false; }), "kick": new Command({ cmdName: "kick", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: ["kick", "chat"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { return disciplineUser(user, cmd, "kick", message.trim()); }), /* TODO: make sure you can't traverse paths with this "language": new Command({ cmdName: "language", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 6000, cmdCooldown: 2000, isActive: false, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { let lang = message.toLowerCase().split(" "); if (!lang[0] || ((lang[0].length < 2 || lang[0].length > 3) && lang[0] !== "reset")) { bot.sendPM(user.name, "Invalid language. Must be 2-3 chars."); } else { bot.pendingLanguageChange = lang[0]; return true; } return false; }), */ "lastseen": new Command({ cmdName: "lastseen", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 2000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (!bot.cfg.db.useTables.users) return false; let out = (msg) => { if (opts.isPM) bot.sendPM(user.name, msg); else bot.sendChatMsg(msg); } var spl = message.split(" "); if (utils.isValidUserName(spl[0])) { var _user = bot.getUser(spl[0]); if (_user) { out(strings.format(bot, "CHAT_LS_INROOM", [_user.name])); return true; } else { bot.db.run("getLastSeen", [spl[0]], function(res) { if (res && res.rowCount > 0 && res.rows[0].last_seen != null) { var row = res.rows[0]; out(strings.format(bot, "CHAT_LS_LASTSEEN", [row.uname, utils.getUTCTimeStringFromDate(row.last_seen)])); } else { out(strings.format(bot, "CHAT_LS_NOTSEEN", [spl[0]])); } }); return true; } } return false; }), "loadpoll": new Command({ cmdName: "loadpoll", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 20000, cmdCooldown: 10000, isActive: true, requiredChannelPerms: ["pollctl"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.saved_polls) { let name = message.trim(); let first = name.split(" ")[0], timeout = 0; if (first && first.toLowerCase().indexOf("time:") === 0) { name = name.substr(first.length+1); let time = first.substr(5); if (!~time.indexOf(":")) { let parsed = parseInt(time); if (!isNaN(parsed)) timeout = parsed; } else { timeout = utils.timecodeToSecs(time); } } if (name.length >= 1 && name.length <= 30) { bot.db.run("getPoll", [name], function(res) { if (res && res.rows.length > 0) { let row = res.rows[0], opts = null; try { opts = JSON.parse(row.options); } catch (e) { bot.sendPM(user.name, "That poll was found, but its options are malformed."); } if (!opts) return false; let poll = { title: row.title, opts: opts, obscured: row.obscured } if (timeout > 0) poll.timeout = Math.min(86400, timeout); bot.openPoll(poll); } else { bot.sendPM(user.name, "No poll found with the name: " + name); } }) return true; } else { bot.sendPM(user.name, "Poll name must be 1-30 characters."); } } return false; }), "memoryusage": new Command({ cmdName: "memoryusage", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 500, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { bot.sendChatMsg(strings.format(bot, "MEMORY_USAGE", [(process.memoryUsage().heapUsed / 1024), "KB"])); }), "modmsg": new Command({ cmdName: "modmsg", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 8000, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { bot.broadcastModPM(message); return true; }), "mute": new Command({ cmdName: "mute", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 1000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (!bot.getOpt("muted", false)) { bot.logger.mod(strings.format(bot, "BOT_MUTED", [user.name])); return bot.setOpt("muted", true); } return false; }), "nameban": new Command({ cmdName: "nameban", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: ["ban", "chat"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { return disciplineUser(user, cmd, "ban", message.trim()); }), "pick": new Command({ cmdName: "pick", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 5000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { let choices = message.split(";"), i = 0, filtered = []; for (;i < choices.length; i++) { if (choices[i].length > 0) filtered.push(choices[i]); } if (filtered.length <= 1) { bot.sendPM(user.name, "You must have at least two things to pick from! Separate choices with a ;"); return false; } else { let choice = filtered[Math.floor(Math.random() * filtered.length)]; bot.sendChatMsg(user.name + ": " + choice.trim()); return true; } return false; }), "purge": new Command({ cmdName: "purge", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 10000, isActive: true, requiredChannelPerms: ["playlistdelete"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { let spl = message.split(" "); let out = (msg) => { if (opts.isPM) bot.sendPM(user.name, msg); else bot.sendChatMsg(msg); } if (utils.isValidUserName(spl[0])) { let result = bot.purgeUser(spl[0], true); if (result) { out(strings.format(bot, "USER_PURGED", [spl[0]])); } return result; } return false; }), "quote": new Command({ cmdName: "quote", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 20000, cmdCooldown: 10000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.chat) { var spl = message.split(" "); if (spl[0].toLowerCase() === bot.username.toLowerCase() || !utils.isValidUserName(spl[0]) || bot.userIsBot(spl[0]) || bot.getSavedUserData(spl[0]).quoteExempt) return false; bot.db.run("getRandomChat", [spl[0]], function(res) { if (res && res.rowCount > 0) { var row = res.rows[0]; if (/^\/me /.test(row.msg)) bot.sendChatMsg(strings.format(bot, "CHAT_QUOTE_ME", [utils.getUTCDateStringFromDate(row.time), row.uname, row.msg.substr(4)])); else bot.sendChatMsg(strings.format(bot, "CHAT_QUOTE", [utils.getUTCDateStringFromDate(row.time), row.uname, row.msg])); } }); return true; } return false; }), "quotes": new Command({ cmdName: "quotes", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 500, cmdCooldown: 0, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { var opt = "quoteExempt"; var exempt = bot.getSavedUserData(user.name)[opt]; var spl = message.toLowerCase().split(" "); if (spl[0] === "on" && exempt) { bot.setUserDataOpt(user.name, opt, false); bot.sendPM(user.name, strings.format(bot, "QUOTE_ON_PM", [bot.trigger])); } else if (spl[0] === "off" && !exempt) { bot.setUserDataOpt(user.name, opt, true); bot.sendPM(user.name, strings.format(bot, "QUOTE_OFF_PM", [bot.trigger])); } else { bot.sendPM(user.name, strings.format(bot, "QUOTE_NO_ARG_PM", [exempt ? "exempt" : "not exempt", bot.trigger, this.cmdName])); } return true; }), "restart": new Command({ cmdName: "restart", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { bot.handlingChatCommands = false; bot.kill("restart issued via chat by " + user.name, 2000, 0); return true; }), "roll": new Command({ cmdName: "roll", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 10000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { var splitmsg = message.trim().split(" "); var sndmsg = ""; var dicereg = /^(\d{1,2})?d(\d{1,3})(?:(\+|\-)(\d{1,3}))?/i; if (splitmsg[0] && dicereg.test(splitmsg[0])) { var d, rolls, sides, op, offset, sum; d = splitmsg[0].match(dicereg); if (!d) return false; rolls = parseInt(d[1]); sides = parseInt(d[2]); op = d[3]; offset = parseInt(d[4]); if (isNaN(offset)) offset = 0; if (isNaN(rolls)) rolls = 1; else if (rolls > 15 || rolls < 1) rolls = 2; if (sides > 999 || sides < 2) sides = 6; sum = 0; var eachRoll = []; var max = rolls * sides; sndmsg = user.name + ' rolled ' + rolls + "d" + sides; if (op && offset) { sndmsg += op + offset; } sndmsg += ": "; for (var i = rolls; i >= 1; i--) { var roll = Math.floor((Math.random() * sides) + 1); sum += roll; eachRoll.push(roll); } if (rolls > 1 && !offset && sum === max) { sndmsg += '!! ' + sndmsg + 'PERFECT ' + sum.toString() + ' !!'; } else { if (op === "+") { sum += offset; } else if (op === "-") { sum -= offset; } sndmsg += sum.toString(); if (rolls > 1) sndmsg += " {" + eachRoll.join(",") + "}"; } } else { var roll = 2; var combos = ['DUBS: ', 'TRIPS: ', 'QUADS: ', 'QUINTS!!: ', 'SEXTS!!: ', 'SEPTS!!: ', 'OCTS!!!: ', 'NONS!!!!:', 'DECS!!!! HOLY SHIT: ']; var dig = parseInt(splitmsg[0]); if (!isNaN(dig) && dig > 0 && dig < 11) roll = dig; let bestRoll = {num: "", checkem: -1}; let DoRoll = function() { let rollnum = Math.floor(Math.random() * Math.pow(10, roll)).toString(); rollnum = "0".repeat(roll - rollnum.length) + rollnum; let j = 0, i, repeatcheck = rollnum[rollnum.length - 1]; for (i = (rollnum.length - 1); i > -1; i--) { if (rollnum[i] === repeatcheck) j++; else break; } if (j > bestRoll.checkem) bestRoll = {num: rollnum, checkem: j}; else if (j === bestRoll.checkem && rollnum > bestRoll.num) bestRoll.num = rollnum } DoRoll(); let luck = bot.settings.lucky[user.name]; if (luck) { delete bot.settings.lucky[user.name]; bot.writeSettings(); while (luck > 0) { DoRoll(); luck--; } } if (bestRoll.checkem > 1 && combos[bestRoll.checkem - 2] !== undefined) { sndmsg = '!! ' + user.name + ' rolled ' + combos[bestRoll.checkem - 2] + bestRoll.num + ' !!'; //var comboEmotes = ["/go", "/slowgo", "/gigago", "/wow2", "/push", "/praise", "/stop", "/wut2", "/alarm /cantwakeup /alarm2"]; //if (comboEmotes[bestRoll.checkem - 2] !== undefined) sndmsg += ' ' + comboEmotes[bestRoll.checkem - 2]; } else { sndmsg = user.name + ' rolled: ' + bestRoll.num; } } if (sndmsg !== "") { bot.sendChatMsg(sndmsg); return true; } return false; }), "roomtime": new Command({ cmdName: "roomtime", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 20000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users) { let out = (msg) => { if (opts.isPM) bot.sendPM(user.name, msg); else bot.sendChatMsg(msg); } let spl = message.split(" "), username = user.name; if (spl.length >= 1 && utils.isValidUserName(spl[0])) { username = spl[0]; } bot.updateUserRoomTime(username, true, function() { bot.db.run("getUserRoomTime", [username], function(res) { if (res && res.rows && res.rows[0]) { var rows = res.rows[0]; if (rows.hasOwnProperty("first_seen") && rows.hasOwnProperty("room_time") && rows.hasOwnProperty("afk_time")) { if (rows.room_time > 0) { let first_seen = utils.getUTCTimeStringFromDate(rows.first_seen), total_time = utils.secsToTime(Math.floor(rows.room_time), true), active_time = utils.secsToTime(Math.floor(rows.room_time - rows.afk_time), true), percent_active = (((rows.room_time - rows.afk_time) / rows.room_time)*100).toFixed(2); out(strings.format(bot, "CHAT_ROOMTIME", [username, first_seen, total_time, active_time, percent_active])); } else { let first_seen = utils.getUTCTimeStringFromDate(rows.first_seen); out(strings.format(bot, "CHAT_ROOMTIME_ONLYSEEN", [username, first_seen])); } } } else { out(strings.format(bot, "CHAT_LS_NOTSEEN", [username])); } }); }); return true; } return false; }), "savepoll": new Command({ cmdName: "savepoll", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 10000, cmdCooldown: 5000, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.saved_polls) { let poll = bot.getCurrentPollFrame(), name = message.trim(), first = name.split(" ")[0]; if (!poll) { bot.sendPM(user.name, strings.format(bot, "SAVEPOLL_ERR_NOACTIVE")); } else if (first && first.toLowerCase().indexOf("time:") === 0) { bot.sendPM(user.name, strings.format(bot, "SAVEPOLL_ERR_STARTSWITHTIME")); } else if (name.length < 1 || name.length > 30) { bot.sendPM(user.name, strings.format(bot, "SAVEPOLL_ERR_NAMELENGTH", [1,30])); } else if (poll.opts.length > 10) { bot.sendPM(user.name, strings.format(bot, "SAVEPOLL_ERR_OPTLENGTH", [10])); } else { bot.db.run("insertPoll", [user.name, name, poll.title, poll.obscured, JSON.stringify(poll.opts)], function(res) { if (res.rowCount <= 0) { bot.sendPM(user.name, strings.format(bot, "SAVEPOLL_ERR_NOTUNIQUE")); } else { bot.sendPM(user.name, strings.format(bot, "SAVEPOLL_SUCCESS", [name])); } }) return true; } } return false; }), "seekto": new Command({ cmdName: "seekto", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 6000, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (!bot.leader && !bot.checkChannelPermission("leaderctl")) { bot.sendPM(user.name, strings.format(bot, "LEADER_NOPERM")); return false; } let cm = bot.CHANNEL.currentMedia; if (!cm) return false; let spl = message.split(" "); if (spl.length > 0 && spl[0]) { bot.seek.autoUnassign = false; let time = utils.timecodeToSecs(spl[0]); if (time < 0) { bot.sendPM(user.name, strings.format(bot, "TIMECODE_BADFORMAT")); return false; } else if (time > cm.seconds) { bot.sendPM(user.name, strings.format(bot, "SEEK_TOOFAR")); return false; } else if (bot.leader) { bot.startLeadTimer(); bot.sendVideoUpdate(time); return true; } else { bot.seek.time = time; bot.seek.autoUnassign = true; bot.assignLeader(bot.username); return true; } } return false; }), "selfpurge": new Command({ cmdName: "selfpurge", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 3600000, cmdCooldown: 10000, isActive: false, requiredChannelPerms: ["seeplaylist", "playlistdelete"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (msg.trim() !== "") { bot.sendPM(user.name, strings.format(bot, "SELFPURGE_NOARG", [bot.trigger])); return false; } let currentVidIsUsers = false, pl = bot.CHANNEL.playlist, name = user.name.toLowerCase(); if (pl.length <= 0) { return false; } let finalize = function(message) { bot.sendChatMsg(strings.format(bot, "USER_PURGED", [user.name])); bot.sendPM(user.name, message); return true; } let currentVid = bot.getMedia(bot.CHANNEL.currentUID); if (currentVid && currentVid.queueby.toLowerCase() === name) currentVidIsUsers = true; if (currentVidIsUsers) { let i = 0; for (;i < pl.length; i++) { if (pl[i].uid !== bot.CHANNEL.currentUID && pl[i].queueby.toLowerCase() === name) bot.deleteVideo(pl[i].uid); } return finalize(strings.format(bot, "SELFPURGE_SEMISUCCESS")); } else { bot.sendChatMsg("/clean " + user.name, false, true); return finalize(strings.format(bot, "SELFPURGE_SUCCESS")); } return false; }), "selfremove": new Command({ cmdName: "selfremove", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 40000, cmdCooldown: 20000, isActive: true, requiredChannelPerms: ["seeplaylist", "playlistdelete"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { let splitmsg = message.trim().split(" "); if (splitmsg.length <= 0) { bot.sendPM(user.name, strings.format(bot, "SELFREMOVE_USAGE", [bot.trigger, this.cmdName])); return false; } let video = false, pos = -1, pl = bot.CHANNEL.playlist, name = user.name.toLowerCase(); var findVideo = function(dir) { let action = function(idx) { if (pl[idx].queueby.toLowerCase() === name) { pos = idx; return pl[idx]; } return -1; } if (dir === "descending") { let i = 0; for (; i < pl.length; i++) { let media = action(i); if (~media) return media; } } else if (dir === "ascending") { var i = pl.length - 1; for (; i >= 0; i--) { let media = action(i); if (~media) return media; } } return false; } if (splitmsg[0].toLowerCase() === "first") { video = findVideo("descending"); } else if (splitmsg[0].toLowerCase() === "last") { video = findVideo("ascending"); } else { pos = parseInt(splitmsg[0]); if (isNaN(pos) || pos - 1 < 0 || pos - 1 >= pl.length) { bot.sendPM(user.name, strings.format(bot, "PLAYLIST_INVALID_POSITION")); return false; } video = pl[pos - 1]; } if (!video) { bot.sendPM(user.name, strings.format(bot, "PLAYLIST_VIDEONOTFOUND")); return false; } if (video.queueby.toLowerCase() === name) { if (video.uid === bot.CHANNEL.currentUID) { bot.sendPM(user.name, strings.format(bot, "SELFREMOVE_ERR_ACTIVE")); } else { bot.sendPM(user.name, strings.format(bot, "SELFREMOVE_SUCCESS", [pos])); bot.deleteVideo(video.uid); return true; } } else { bot.sendPM(user.name, strings.format(bot, "SELFREMOVE_ERR_NOTYOURS", [pos])); } return false; }), "setrank": new Command({ cmdName: "setrank", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { var spl = message.split(" "); return setRank(spl[0], spl[1], user); }), "setrankmatch": new Command({ cmdName: "setrankmatch", minRank: bot.RANKS.ADMIN, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { var spl = message.split(" "); return setRankMatch(spl[0], spl[1], user); }), "shuffle": new Command({ cmdName: "shuffle", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 2000, isActive: true, requiredChannelPerms: ["playlistshuffle"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { if (message.trim() === "") { bot.socket.emit("shufflePlaylist"); return true; } else { bot.sendPM(user.name, strings.format(bot, "SHUFFLE_ERR", [bot.trigger])); } return false; }), "shuffleuser": new Command({ cmdName: "shuffleuser", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 20000, isActive: true, requiredChannelPerms: ["seeplaylist", "playlistmove"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { let TARGETUSER = message.split(" ")[0]; if (!utils.isValidUserName(TARGETUSER)) { bot.sendPM(user.name, "$shuffleuser -- Disperses a user's videos throughout the playlist."); return false; } let userVideos = bot.getUserVideos(TARGETUSER); let pl = bot.CHANNEL.playlist; if (userVideos.length <= 0) { bot.sendPM(user.name, "No videos found for " + TARGETUSER); return false; } if (pl.length <= userVideos.length) { bot.sendPM(user.name, "Playlist must have at least two contributors to use this command."); return false; } if (pl.length <= 5) { bot.sendPM(user.name, "Playlist must have more than 5 videos."); return false; } var moveVid = function(i) { if (i < userVideos.length) { //keep checking playlist status since it can always update during this var retry, after; if (userVideos[i].uid !== bot.CHANNEL.currentUID) { do { retry = false; if (userVideos[i].uid !== bot.CHANNEL.currentUID) { let active = bot.getMediaIndex(bot.CHANNEL.currentUID); let newPos = Math.max(5, Math.floor(Math.random() * pl.length - active) + active); after = pl[newPos].uid; if (after === userVideos[i].uid) retry = true; } else { after = -1; } } while (retry); bot.logger.debug("selected UID " + userVideos[i].uid + ", active UID is " + bot.CHANNEL.currentUID); if (after >= 0) { bot.moveMedia(userVideos[i].uid, after); } } let fn = ()=>{moveVid(i+1)}; bot.actionQueue.enqueue([this, fn, []]); } }; moveVid(0); }), "su-allow": new Command({ cmdName: "su-allow", minRank: bot.RANKS.OWNER, rankMatch: ">=", userCooldown: 0, cmdCooldown: 500, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { let target = message.split(" ")[0]; if (!utils.isValidUserName(target)) return false; let success = bot.allowUser(target); if (success) bot.sendPM(user.name, target + " allowed."); return success; }), "su-clearquotes": new Command({ cmdName: "su-clearquotes", minRank: bot.RANKS.OWNER, rankMatch: ">=", userCooldown: 10000, cmdCooldown: 500, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.chat) { let spl = message.split(" "); if (utils.isValidUserName(spl[0])) { bot.db.run("deleteUserChat", [spl[0]], function() { bot.sendPM(user.name, strings.format(bot, "DB_QUOTES_ERASED_OTHER", [spl[0]])); }); return true; } else { bot.sendPM(user.name, strings.format(bot, "INVALID_USERNAME")); } } return false; }), "su-disallow": new Command({ cmdName: "su-disallow", minRank: bot.RANKS.OWNER, rankMatch: ">=", userCooldown: 0, cmdCooldown: 500, isActive: true, requiredChannelPerms: [], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { let target = message.split(" ")[0]; if (!utils.isValidUserName(target)) return false; let success = bot.disallowUser(target); if (success) bot.sendPM(user.name, target + " disallowed."); return success; }), "timeban": new Command({ cmdName: "timeban", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 0, isActive: true, requiredChannelPerms: ["ban", "chat"], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (bot.rank < 3) return false; message = message.toLowerCase().trim(); let space = message.indexOf(" "); if (!~space) { bot.sendPM(user.name, "timeban [username] [length] -- Valid time notation examples (any numbers can be used): 1y1d1h1m1s, 1m, 30s"); return false; } let name = message.substr(0,space), time = utils.removeExcessWhitespace(message.substr(space+1)), regex = new RegExp(/(\d+[ydhms])/gi); if (!utils.isValidUserName(name)) return false; if (!regex.test(time)) { bot.sendPM(user.name, "You must specify a time! Valid (any numbers can be used): 1y1d1h1m1s"); return false; } function strToSecs(str) { let time = parseInt(str.substr(0,str.length-1)), type = str.substr(-1,1); if (!isNaN(time)) { switch (type) { case "s": return time; case "m": return time*60; case "h": return time*60*60; case "d": return time*24*60*60; //who cares about leap years here case "y": return time*365*24*60*60; default: return 0; } } } let matches = time.match(regex), banTime = 0, i = 0; if (!matches) { bot.sendPM(user.name, "You must specify a time! Valid (any numbers can be used): 1y1d1h1m1s"); return false; } for (;i < matches.length;i++) { if (matches[i] && matches[i] !== "") { banTime += strToSecs(matches[i]); } } if (banTime > Number.MAX_SAFE_INTEGER || banTime === Infinity) banTime = Number.MAX_SAFE_INTEGER; if (banTime <= 60) banTime = 60; if (disciplineUser(user, cmd, "ipban", name + " [" + utils.getUTCDateStringFromDate() + "] Banned for " + utils.secsToTime(banTime, true))) { bot.timeBan(name, banTime); bot.logger.mod("TIMEBAN: " + name + " banned by " + user.name + " for " + banTime + " seconds"); bot.sendPM(user.name, "Timebanned " + name + " for " + utils.secsToTime(banTime, true)); return true; } return false; }), "top5emotes": new Command({ cmdName: "top5emotes", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 20000, cmdCooldown: 15000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.emote_data) { bot.db.run("getTopFiveEmotes", null, function(res) { if (res && res.rowCount > 0) { var txt = "Top " + res.rowCount + " emotes: "; for (var i = 0; i < res.rows.length; i++) { var row = res.rows[i]; if (row.emote.length > 40) row.emote = row.emote.substr(0,40) + "..."; txt += row.emote + ` (${row.sum})`; if (i < res.rows.length - 1) txt += ", "; } bot.sendChatMsg(txt); } }); return true; } return false; }), "top5emoteusers": new Command({ cmdName: "top5emoteusers", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 20000, cmdCooldown: 15000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.emote_data) { bot.db.run("getTopFiveEmoteUsers", null, function(res) { if (res && res.rowCount > 0) { var txt = "Top " + res.rowCount + " emote users: "; for (var i = 0; i < res.rows.length; i++) { var row = res.rows[i]; txt += row.uname + ` (${row.sum})`; if (i < res.rows.length - 1) txt += ", "; } bot.sendChatMsg(txt); } }); return true; } return false; }), "unban": new Command({ cmdName: "unban", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["ban"], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { let spl = message.split(" "); if (spl.length <= 0 || !utils.isValidUserName(spl[0])) return false; if (bot.userIsTimebanned(spl[0])) { bot.sendPM(user.name, strings.format(bot, "UNBAN_FAIL_TIMEBANNED", [spl[0], bot.trigger])); return false; } return bot.unbanUser(spl[0]); }), "untimeban": new Command({ cmdName: "untimeban", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["ban"], allowRankChange: false, canBeUsedInPM: true }, function (cmd, user, message, opts) { let spl = message.split(" "); if (spl.length <= 0 || !utils.isValidUserName(spl[0])) return false; return bot.unbanUser(spl[0]); }), "unmute": new Command({ cmdName: "unmute", minRank: bot.RANKS.MOD, rankMatch: ">=", userCooldown: 0, cmdCooldown: 1000, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: true }, function (cmd, user, message, opts) { if (bot.getOpt("muted", false)) { bot.logger.mod(strings.format(bot, "BOT_UNMUTED", [user.name])); return bot.setOpt("muted", false); } return false; }), "uptime": new Command({ cmdName: "uptime", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 2000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { bot.sendChatMsg(strings.format(bot, "CHAT_UPTIME", [utils.secsToTime(Math.floor((Date.now() - bot.started)/1000))])); return true; }), "urbandictionary": new Command({ cmdName: "urbandictionary", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 20000, cmdCooldown: 10000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { if (message.trim() === "") return false; api.APIcall(bot, "urbandictionary", message, null, function(status, data) { if (!data || !status) return false; let noResponse = false; if (data.list && data.list.length >= 1) { function getDefinition() { let i = 0; for (;i < data.list.length;i++) { if (data.list[i].hasOwnProperty("definition") && data.list[i].word.toLowerCase() === message.toLowerCase()) return ent.decode(utils.removeExcessWhitespace(data.list[i].definition)); } return null; } let def = getDefinition(); if (def) { def = def.replace(/[\[\]]+/g, ""); bot.sendChatMsg(strings.format(bot, "API_PLAIN_RESPONSE", ["ud", def])); } else { noResponse = true; } } else { noResponse = true; } if (noResponse) { bot.sendChatMsg(strings.format(bot, "API_PLAIN_RESPONSE", ["ud", "No definition found."])); } }); return true; }), "useremotecount": new Command({ cmdName: "useremotecount", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 5000, cmdCooldown: 1000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { if (bot.db && bot.cfg.db.useTables.users && bot.cfg.db.useTables.emote_data) { var spl = message.split(" "); if (utils.isValidUserName(spl[0])) { if (bot.getSavedUserData(spl[0]).quoteExempt || spl[0].toLowerCase() === bot.username.toLowerCase()) { bot.sendChatMsg(strings.format(bot, "TARGETUSER_EXEMPT")); return true; } else { if (spl.length > 1 && bot.emoteExists(spl[1])) { bot.db.run("getUserEmoteCount", [spl[0], spl[1]], function(res) { if (res && res.rowCount > 0) { var row = res.rows[0]; bot.sendChatMsg(strings.format(bot, "CHAT_UEC_USED", [row.uname, spl[1], row.count, (row.count == 1 ? "time" : "times")])); } else { bot.sendChatMsg(strings.format(bot, "CHAT_UEC_NOTUSED", [spl[0], spl[1]])); } }); return true; } else { bot.db.run("getUserTotalEmoteCount", [spl[0]], function(res) { if (res && res.rowCount > 0 && res.rows[0].sum != null) { var row = res.rows[0]; bot.sendChatMsg(strings.format(bot, "CHAT_UEC_USEDTOTAL", [row.uname, row.sum, (row.sum == 1 ? "emote" : "emotes")])); } else { bot.sendChatMsg(strings.format(bot, "CHAT_UEC_NONEUSED", [spl[0]])); } }); return true; } } } } return false; }), "vidstats": new Command({ cmdName: "vidstats", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 0, cmdCooldown: 6000, isActive: true, requiredChannelPerms: [], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { let cm = bot.CHANNEL.currentMedia; if (!cm || cm.type !== "yt") { bot.sendPM(user.name, "A YouTube video must be playing to get statistics."); return false; } let cvd = bot.currentVideoData, ID = cm.id; if (cvd.id === ID && (cvd.views > -1 || cvd.noStats || cvd.ratingsDisabled)) { let sndmsg = (cvd.views).toLocaleString("en") + " views. "; if (bot.cfg.chat.roomHasSSC) sndmsg = "ssc:#1c8a1a " + sndmsg; if (cvd.ratingsDisabled) sndmsg += "Ratings are disabled."; else { let total = cvd.likes+cvd.dislikes, likePercent = (total <= 0) ? "0" : ((cvd.likes/total)*100).toFixed(2); sndmsg += cvd.likes + " likes, " + cvd.dislikes + " dislikes (" + likePercent + "% positive)"; } bot.sendChatMsg(sndmsg); return true; } else { if (bot.gettingVideoMeta) { bot.sendPM(user.name, "The bot is currently getting the video stats. Try again in a few seconds. If this persists, this is an issue."); return false; } bot.gettingVideoMeta = true; api.APIcall(bot, "youtubestatistics", ID, bot.cfg.api.youtube_key, function (status, data, ok) { cvd.id = ID; if (!status) { bot.gettingVideoMeta = false; return false; } if (ok && data && data.items) { if (data.items.length < 1 || !data.items[0].hasOwnProperty("statistics")) { cvd.noStats = true; bot.gettingVideoMeta = false; bot.sendChatMsg("No stats found for this video."); return; } let stats = data.items[0]["statistics"]; cvd.views = parseInt(stats.viewCount); let sndmsg = (cvd.views).toLocaleString("en") + " views. "; if (bot.cfg.chat.roomHasSSC) sndmsg = "ssc:#1c8a1a " + sndmsg; if (!stats.hasOwnProperty("likeCount") || !stats.hasOwnProperty("dislikeCount")) { cvd.ratingsDisabled = true; sndmsg += "Ratings are disabled."; } else { var likes = parseInt(stats.likeCount), dislikes = parseInt(stats.dislikeCount), total = likes+dislikes, likePercent = (total <= 0) ? "0" : ((likes/total)*100).toFixed(2); cvd.likes = likes; cvd.dislikes = dislikes; sndmsg += likes + " likes, " + dislikes + " dislikes (" + likePercent + "% positive)"; } bot.gettingVideoMeta = false; bot.sendChatMsg(sndmsg); return; } else if (!data || (data && (!data.items || (data.error && data.error.errors)))) { bot.gettingVideoMeta = false; return bot.sendPM(user.name, "Error getting video statistics. Is a YouTube video playing?"); } }); } return true; }), "wolfram": new Command({ cmdName: "wolfram", minRank: bot.RANKS.USER, rankMatch: ">=", userCooldown: 10000, cmdCooldown: 2000, isActive: true, requiredChannelPerms: ["chat"], allowRankChange: true, canBeUsedInPM: false }, function (cmd, user, message, opts) { let apiKey = bot.cfg.api.wolfram_key; if (apiKey.trim() === "") return false; api.APIcall(bot, "wolfram", message, apiKey, function(status, data) { if (!status || !data) return false; switch (status) { case -3: bot.logger.error("wolfram error: " + data); break; case -2: bot.sendPM(user.name, "[wolfram] "+data); break; case -1: return false; case 200: bot.sendChatMsg(strings.format(bot, "API_WR_RESPONSE", [user.name, data])); break; case 501: bot.sendChatMsg(strings.format(bot, "API_WR_RESPONSE", [user.name, "No data found with that query."])); break; default: return false; } }); }) } //LOCAL FUNCTIONS function disciplineUser(caller, cmdName, method, message) { var spl = message.split(" "), targetname = spl.splice(0, 1)[0]; spl = spl.join(" ").trim(); let lower = targetname.toLowerCase(); if (lower === caller.name.toLowerCase() || lower === bot.username.toLowerCase() || targetname === "") return false; var usr = bot.getUser(targetname); if (!callerOverTargetRank(caller, targetname)) return false; if (/^(w?range)/i.test(spl)) spl = " " + spl; if (usr) bot.sendChatMsg("/" + method + " " + usr.name + " " + spl, true, true); else if (method !== "kick") bot.sendChatMsg("/" + method + " " + targetname + " " + spl, true, true); bot.logger.mod(strings.format(bot, "DISCIPLINE_LOG", [caller.name, cmdName, (usr ? usr.name : targetname), (spl !== "" ? spl : "")])); return true; } function resolveCmdName(cmd) { if (typeof cmd === "string") { cmd = cmd.toLowerCase(); if (aliases.hasOwnProperty(cmd) && !chatCommands.hasOwnProperty(cmd)) return commands[aliases[cmd]]; return commands[cmd]; } else if (cmd instanceof Command) { return cmd; } return null; } function resetRank(_command, caller) { if (!caller) caller = {name: "[none]"}; var command = resolveCmdName(_command); if (!command) return false; return (setRank(command, command.defaultRank, caller, true) && setRankMatch(command, command.defaultRankMatch, caller, true)); } function setRank(_command, rank, caller, force) { var command = resolveCmdName(_command); if (!command) return false; if (!command.allowRankChange && !force) { bot.sendPM(caller.name, "That command cannot have its rank changed."); return false; } else if (command.broken) { bot.sendPM(caller.name, "That command is broken. Tell the bot maintainer."); return false; } var passed = false; var oldrank = command.minRank; if (!isNaN(parseFloat(rank))) rank = parseFloat(rank); if (typeof rank === "string") { var rankKeys = Object.keys(bot.RANKS); var ranks = {}; var input = rank.toLowerCase(); for (var i = 0; i < rankKeys.length; i++) { ranks[rankKeys[i].toLowerCase()] = rankKeys[i]; } if (ranks.hasOwnProperty(input)) { var newrank = bot.RANKS[ranks[input]]; if (typeof newrank === "number") { command.minRank = newrank; passed = true; } } } else if (typeof rank === "number") { command.minRank = rank; passed = true; } if (passed) { let overrides = bot.settings.minRankOverrides; if (command.minRank === command.defaultRank) delete overrides[command.cmdName]; else overrides[command.cmdName] = command.minRank; bot.writeSettings(); bot.logger.mod(strings.format(bot, "COMMAND_RANK_CHANGED", [command.cmdName, oldrank, rank, utils.colorUsername(bot, caller)])); } return passed; } function setRankMatch(_command, rankMatch, caller, force) { var command = resolveCmdName(_command); if (!command) return false; if (!command.allowRankChange && !force) { bot.sendPM(caller.name, "That command cannot have its rank changed."); return false; } else if (command.broken) { bot.sendPM(caller.name, "That command is broken. Tell the bot maintainer."); return false; } else if (!isValidRankMatch(rankMatch)) { bot.sendPM(caller.name, "Invalid input. Comparison operator can either be <=, ==, or >=."); return false; } var oldRankMatch = command.rankMatch; command.rankMatch = rankMatch; let overrides = bot.settings.rankMatchOverrides; if (command.rankMatch === command.defaultRankMatch) delete overrides[command.cmdName]; else overrides[command.cmdName] = command.rankMatch; bot.writeSettings(); bot.logger.mod(strings.format(bot, "COMMAND_RANKMATCH_CHANGED", [command.cmdName, oldRankMatch, rankMatch, utils.colorUsername(bot, caller)])) return true; } function findLastMedia(name) { name = name.toLowerCase(); let playlist = bot.CHANNEL.playlist; let i = playlist.length-1; for (;i >= 0;i--) { if (playlist[i].queueby.toLowerCase() === name) { return {media:playlist[i], index:i}; } } return null; } function callerOverTargetRank(caller, targetname) { let target = bot.getUser(targetname); if (target) { return caller.rank > target.rank && bot.rank > target.rank; } else if (!bot.first.grabbedChannelRanks) { let rank = bot.getChanRank(targetname); return caller.rank > rank && bot.rank > rank; } return false; } //END FUNCTIONS & COMMANDS //Revisit this area because it's a bit messy module.exports["Command"] = Command; var custCmds = {commands:{},aliases:{}}; var missingCustCmdsFile = false; try { let filename = "./customchatcommands"; if (bot.cfg.advanced.useChannelCustomCommands) filename += "-" + bot.CHANNEL.room; filename += ".js"; var cmds = require(filename).getCommands(bot); custCmds.commands = cmds.commands; custCmds.aliases = cmds.aliases; } catch (e) { if (e.code === "MODULE_NOT_FOUND") missingCustCmdsFile = true; //bot.logger.error(strings.format(bot, "CUSTOMCOMMANDS_NOT_FOUND")); else bot.logger.error(strings.format(bot, "CUSTOMCOMMANDS_LOAD_ERROR", [e.stack])); } let custList = bot.cfg.advanced.customCommandsToLoad, i = 0; for (;i < custList.length;i++) { if (custList[i] === bot.CHANNEL.room && bot.cfg.advanced.useChannelCustomCommands) continue; try { let filename = "./customchatcommands-" + custList[i] + ".js"; let cmds = require(filename).getCommands(bot); for (var cmd in cmds.commands) { custCmds.commands[cmd] = cmds.commands[cmd]; } for (var alias in cmds.aliases) { custCmds.aliases[alias] = cmds.aliases[alias]; } } catch (e) { if (e.code === "MODULE_NOT_FOUND") { bot.logger.error("Could not find customchatcommands-" + custList[i] + ".js!"); } else bot.logger.error(strings.format(bot, "CUSTOMCOMMANDS_LOAD_ERROR", [e.stack])); } } if (utils.isObject(custCmds.commands)) { let numCmds = 0; for (let i in custCmds.commands) { if (commands.hasOwnProperty(i)) { bot.logger.warn(strings.format(bot, "CUSTOMCOMMANDS_OVERWRITE", [i])); } commands[i] = custCmds.commands[i]; numCmds++; } if (utils.isObject(custCmds.aliases)) { for (let i in custCmds.aliases) { if (aliases.hasOwnProperty(i)) { bot.logger.warn(strings.format(bot, "CUSTOMCOMMANDS_ALIASES_OVERWRITE", [i])); } aliases[i] = custCmds.aliases[i]; } } else { bot.logger.error(strings.format(bot, "CUSTOMCOMMANDS_ALIASES_NOT_OBJ")); } bot.logger.info("Found " + numCmds + " custom commands."); } else { bot.logger.error(strings.format(bot, "CUSTOMCOMMANDS_CMDS_NOT_OBJ")); } bot.logger.debug(strings.format(bot, "DBG_CMD_CHECKOVERRIDE")); for (let i in commands) { i = i.toLowerCase(); let COMMAND = commands[i]; if (i !== COMMAND.cmdName) { COMMAND.isActive = false; COMMAND.broken = true; bot.logger.error(strings.format(bot, "COMMAND_INVALID_UNEQUAL_ID", [i])); continue; } //bot.logger.debug(strings.format(bot, "DBG_CMD_SETCDPROP", [i])); bot.userCooldowns[i] = {}; if (bot.settings.cmdStateOverrides.hasOwnProperty(i)) { let newState = bot.settings.cmdStateOverrides[i]; if (newState !== COMMAND.defaultActiveState) { bot.logger.debug(strings.format(bot, "DBG_CMD_FOUNDSTATEOVERRIDE", [ i, COMMAND.defaultActiveState, newState ])); COMMAND.isActive = newState; } else { delete bot.settings.cmdStateOverrides[i]; } } if (bot.settings.minRankOverrides.hasOwnProperty(i)) { if (!COMMAND.allowRankChange) { bot.logger.debug(strings.format(bot, "DBG_CMD_FOUNDRANKOVERRIDE_NOTALLOWED", [i])); } else { var newrank = bot.settings.minRankOverrides[i]; if (newrank !== COMMAND.defaultRank) { bot.logger.debug(strings.format(bot, "DBG_CMD_FOUNDRANKOVERRIDE", [ i, COMMAND.minRank, newrank ])); COMMAND.minRank = newrank; } else { delete bot.settings.minRankOverrides[i]; } } } if (bot.settings.rankMatchOverrides.hasOwnProperty(i)) { if (!COMMAND.allowRankChange) { bot.logger.debug(strings.format(bot, "DBG_CMD_FOUNDRANKMATCHOVERRIDE_NOTALLOWED", [i])); } else { var newrm = bot.settings.rankMatchOverrides[i]; if (newrm !== COMMAND.defaultRankMatch) { bot.logger.debug(strings.format(bot, "DBG_CMD_FOUNDRANKMATCHOVERRIDE", [ i, COMMAND.rankMatch, newrm ])); COMMAND.rankMatch = newrm; } else { delete bot.settings.rankMatchOverrides[i]; } } } if (bot.settings.userCooldownOverrides.hasOwnProperty(i)) { var newcd = bot.settings.userCooldownOverrides[i]; if (newcd >= 0 && newcd !== COMMAND.defaultUserCooldown) { bot.logger.debug(strings.format(bot, "DBG_CMD_FOUNDCDOVERRIDE", [ i, COMMAND.userCooldown, newcd ])); COMMAND.userCooldown = newcd; } else { delete bot.settings.userCooldownOverrides[i]; } } if (bot.settings.cmdCooldownOverrides.hasOwnProperty(i)) { var newcd = bot.settings.cmdCooldownOverrides[i]; if (newcd >= 0 && newcd !== COMMAND.defaultCmdCooldown) { bot.logger.debug(strings.format(bot, "DBG_CMD_FOUNDGCDOVERRIDE", [ i, COMMAND.cmdCooldown, newcd ])); COMMAND.cmdCooldown = newcd; } else { delete bot.settings.cmdCooldownOverrides[i]; } } } chatCommands = commands; bot.handlingChatCommands = true; bot.logger.verbose(strings.format(bot, "COMMAND_LISTENING")); return true; } module.exports["exec"] = function (bot, user, input, isPM) { if (bot.username === "" || bot.cfg.chat.disableAllCommands || bot.killed || !user || bot.disallowed(user.name)) return; let username = user.name; var split = input.split(" "), cmd = split.splice(0, 1)[0], givenCmd = cmd; if (cmd === "") return; if (aliases.hasOwnProperty(cmd) && !chatCommands.hasOwnProperty(cmd)) { cmd = aliases[cmd]; } cmd = cmd.toLowerCase(); if (chatCommands.hasOwnProperty(cmd)) { var command = chatCommands[cmd], now = Date.now(); if (!bot.handlingChatCommands) { bot.logger.error(strings.format(bot, "COMMAND_USED_BEFOREHANDLING", [cmd])); return; } else if (chatCommands[cmd].broken) { bot.logger.error(strings.format(bot, "COMMAND_USED_BROKEN", [cmd])); bot.sendPM(user.name, "\"" + command.cmdName + "\" is broken. Notify a bot maintainer."); return; } else if (!chatCommands[cmd].isActive) { bot.logger.warn(strings.format(bot, "COMMAND_USED_INACTIVE", [cmd])); bot.sendPM(user.name, "\"" + command.cmdName + "\" is not active."); return; } else if (isPM && !chatCommands[cmd].canBeUsedInPM) { bot.logger.warn(strings.format(bot, "COMMAND_USED_NOPM", [cmd])); bot.sendPM(user.name, "\"" + command.cmdName + "\" cannot be used in PM."); return; } if (user.rank < bot.RANKS.OWNER && bot.settings.disallow.indexOf(username) > -1) return; var reqperm = command.requiredChannelPerms; for (var i = 0; i < reqperm.length; i++) { if (!bot.checkChannelPermission(reqperm[i])) { bot.logger.error(strings.format(bot, "COMMAND_CHANPERM_FAIL", [command.cmdName, reqperm[i]])); if (user.rank >= bot.RANKS.MOD) { bot.sendPM(user.name, "Bot does not have the channel permissions required for " + command.cmdName + ": " + reqperm.join(", ")); } return; } } if ((command.rankMatch === ">=" && user.rank < command.minRank) ||(command.rankMatch === "==" && user.rank !== command.minRank) ||(command.rankMatch === "<=" && user.rank > command.minRank)) { var rankstr = command.minRank; if (bot.cfg.rankNames.hasOwnProperty(command.minRank)) rankstr += " (" + bot.cfg.rankNames[command.minRank] + ")"; else rankstr += ""; bot.sendPM(user.name, strings.format(bot, "COMMAND_REQUIRED_RANK", [ command.cmdName, rankstr, command.rankMatch ])); } else { //if bypass is either disabled, or the user is too low of a rank to bypass cooldowns... if (bot.cfg.chat.minRankToBypassCooldown < 0 || (bot.cfg.chat.minRankToBypassCooldown > -1 && user.rank < bot.cfg.chat.minRankToBypassCooldown)) { if (now - command.lastUse < command.cmdCooldown) { return bot.sendPM(user.name, strings.format(bot, "COOLDOWN_C_ACTIVE", [ command.cmdName, ((command.cmdCooldown - (now - command.lastUse)) / 1000) ])); } else if (bot.userCooldowns[cmd].hasOwnProperty(username) && now - bot.userCooldowns[cmd][username] < command.userCooldown) { return bot.sendPM(user.name, strings.format(bot, "COOLDOWN_U_ACTIVE", [ command.cmdName, ((command.userCooldown - (now - bot.userCooldowns[cmd][username])) / 1000) ])); } } let opts = { isPM: isPM } var success = chatCommands[cmd].fn(givenCmd, user, split.join(" "), opts); if (success !== false) { //yes, explicit false. undefined should succeed as default behavior chatCommands[cmd].lastUse = now; bot.userCooldowns[cmd][username] = now; } } } else { bot.logger.warn(strings.format(bot, "UNKNOWN_CHAT_COMMAND", [cmd])); } }; module.exports["init"] = createCommands; function disable(bot, cmd, username) { if (aliases.hasOwnProperty(cmd)) cmd = aliases[cmd]; if (cmd === "disable" || cmd === "enable") return false; if (chatCommands.hasOwnProperty(cmd)) { let COMMAND = chatCommands[cmd]; let strID = "COMMAND_DISABLED", success = false; if (COMMAND.isActive) { COMMAND.isActive = false; let overrides = bot.settings.cmdStateOverrides; if (!COMMAND.defaultActiveState) { delete overrides[COMMAND.cmdName]; } else { overrides[COMMAND.cmdName] = false; } bot.writeSettings(); success = true; } else { strID = "COMMAND_DISABLED_FAIL"; } let msg = strings.format(bot, strID, [cmd]); bot.logger.verbose(msg); if (username) bot.sendPM(username, msg); return success; } return false; } function enable(bot, cmd, username) { if (aliases.hasOwnProperty(cmd)) cmd = aliases[cmd]; if (chatCommands.hasOwnProperty(cmd)) { let COMMAND = chatCommands[cmd]; let msg = "", success = false; if (COMMAND.broken) { msg = strings.format(bot, "COMMAND_ENABLED_BROKEN", [cmd]); bot.logger.error(msg); } else if (!COMMAND.isActive) { COMMAND.isActive = true; let overrides = bot.settings.cmdStateOverrides; if (COMMAND.defaultActiveState) { delete overrides[COMMAND.cmdName]; } else { overrides[COMMAND.cmdName] = true; } bot.writeSettings(); msg = strings.format(bot, "COMMAND_ENABLED", [cmd]); success = true; } else { msg = strings.format(bot, "COMMAND_ENABLED_FAIL", [cmd]); } if (COMMAND.broken) bot.logger.error(msg); else bot.logger.verbose(msg); if (username) bot.sendPM(username, msg); return success; } return false; } function isValidRankMatch(rm) { return ~["<=", "==", ">="].indexOf(rm); }