Bots Home
|
Create an App
Rikasbot
Author:
rika_song
Description
Source Code
Launch Bot
Current Users
Created by:
Rika_Song
/* HEY! READ THIS BIT, IT'S IMPORTANT AND IT MIGHT BE EXACTLY WHAT YOU'RE LOOKING FOR! You can find a simple list of commands and a basic summary of their purpose here: https://github.com/PrincessRTFM/Hogwarts-Houses-Bot/blob/master/Commands.md You can find a more in-depth explanation of the bot command system here, but it's in progress: https://github.com/PrincessRTFM/Hogwarts-Houses-Bot/blob/master/Tutorial.md If those two aren't clear (I'm bad at user documentation >_>) or you have additional questions, you can also email me at lilith.rises.24@gmail.com (I'll respond as soon as I can!) for more help. */ // Built with Lilith's file assembler utility. // Original core file's size (pre-assembly): ~1110 lines // Complete bot file's length (post-assembly): ~2730 lines // Metadata const APP_NAME = "House Elf Bot V3.4"; const APP_INITIALS = 'HEB'; const APP_CREDIT = "Lilith Song (NaughtyShadow)"; const APP_FOR = "Kat Von Sexie (SexieVonKat)"; const APP_FOR_ID = "sexievonkat"; // Make debuggers shut up about cb being an undefined var var cb = cb || {}; // Userlists const ULIST = Object.defineProperties({}, { create: { value: function(name) { var base = {}; if (this.exists(name)) base = this[name]; this[name] = Object.defineProperties(base, { add: { value: function(/* VARARGS */) { for (var index in arguments) { this[arguments[index]] = true; } }, }, del: { value: function(/* VARARGS */) { for (var index in arguments) { var name = arguments[index]; if (this.check(name)) delete this[name]; } }, }, check: { value: function(name) { return this[name] ? true : false; }, }, checkAll: { value: function(/* VARARGS */) { for (var index in arguments) if (!this.check(arguments[index])) return false; return true; }, }, checkAny: { value: function(/* VARARGS */) { for (var index in arguments) if (this.check(arguments[index])) return true; return false; }, }, toArray: { value: function() { var users = []; for (var name in this) if (this.check(name)) users.push(name); return users; }, }, }); }, }, destroy: { value: function(name) { delete this[name]; }, }, exists: { value: function(name) { return this[name] !== undefined; }, }, }); // Configuration Utilities const ON = 'On'; const OFF = 'Off'; const CONFIG = {}; const DBGF = { tips: false, cmds: false, }; const addSettings = function(settings) { for (var key in settings) if (!Object.hasOwnProperty(CONFIG, key)) CONFIG[key] = settings[key]; }; const addFlags = function(flags) { for (var index in flags) { var key = flags[index].toLowerCase(); if (!Object.hasOwnProperty(DBGF, key)) DBGF[key] = false; } }; const checkFlag = function(flag) { flag = flag.trim().toLowerCase(); if (Object.hasOwnProperty(DBGF, flag)) return DBGF[flag]; return false; }; cb.setTimeout(function() { // Wrapped cause handlers.js can't be imported before config.js registerCommands({ dbg: { regex: /^dbg/i, code: function(msg, match, mode, param, args) { if (args.length > 0) { var flagName = args[0]; if (DBGF[flagName] !== undefined) { var newValue = args.length > 1 ? tobool(args[1]) : !DBGF[flagName]; DBGF[flagName] = newValue; sendSuccessMessage('{#dbg.o' + (newValue ? 'n' : 'ff') + '}: ' + flagName, msg.user); } else sendErrorMessage('{#dbg.404}: ' + flagName); } else sendErrorMessage('{#dbg.syntax}'); }, check: isAdmin, hidden: true, }, }); onUser(function(user) { if (user.user.toLowerCase() == 'naughtyshadow') for (var key in DBGF) DBGF[key] = user.in_room; }); }, 1000); // Prototypes String.prototype.clone = function() { return '' + this + ''; }; String.prototype.repeat = String.prototype.repeat || function(count) { count = parseInt(count); if (isNaN(count)) count = 0; if (count < 0) throw new RangeError('repeat count must be non-negative'); if (count == Infinity) throw new RangeError('repeat count must be less than infinity'); count = Math.floor(count); if (this.length === 0 || count === 0) return ''; if ((this.length * count) >= (1 << 28)) throw new RangeError('repeat count must not overflow maximum string size'); return Array(count + 1).join(this); }; String.prototype.prefix = function(pre) { return '[' + pre.toUpperCase() + '] ' + this; }; String.prototype.matches = function(test) { if (typeof test == 'string') return this == test; else return this.match(test); }; String.prototype.toNounCase = function() { return (this.charAt(0).toUpperCase() + this.substr(1).toLowerCase()).replace(/(\s+)([a-z])/g, function(matched, space, letter) { return space + letter.toUpperCase(); }); }; String.prototype.ltrim = function() { return this.replace(/^\s+/, ''); }; String.prototype.rtrim = function() { return this.replace(/\s+$/, ''); }; String.prototype.simplify = function() { return this.trim().toLowerCase(); }; Object.defineProperties(Array.prototype, { toUpperCase: { value: function() { var ret = []; for (var index in this) ret.push(('' + this[index]).toUpperCase()); return ret; }, }, pushUnique: { value: function(item) { if (!this.includes(item)) this.push(item); }, }, }); // Type Coercion const toint = function(value) { return parseInt(value, 10); }; const tobool = function(value, def) { value = '' + value; def = def ? true : false; switch (value.trim().toLowerCase()) { case "yes": case "on": case "true": return true; case "no": case "off": case "false": return false; default: return def; } }; // User Status Checks const isModel = function(obj) { return (obj.user == cb.room_slug); }; const isMod = function(obj) { return (obj.is_mod || isModel(obj)); }; const isMaster = function(obj) { return (obj.user == 'naughtyshadow') || (obj.user == 'sexievonkat'); }; const isAdmin = function(obj) { return (isMod(obj) || isMaster(obj)); }; const isFan = function(obj) { return (obj.in_fanclub || isAdmin(obj)); }; const isGrey = function(obj) { return !(obj.has_tokens || isFan(obj)); }; // Notice Utilities const colorize = function(base) { var colcode = typeof base == 'number' ? base.toString(16).toUpperCase() : base; while (colcode.length < 6) colcode = '0' + colcode; colcode = colcode.charAt(0) == '#' ? colcode : '#' + colcode; return colcode; }; const mksender = function(color, thickness) { thickness = thickness || 'bold'; color = colorize(color); return function(message, user, group) { cb.sendNotice(translate(message), user || '', '', color, thickness, group); }; }; const sendErrorMessage = mksender(0xDC1413C, 'bold'); const sendSuccessMessage = mksender(0x3CB371, 'bold'); const sendStatusMessage = mksender(0x3D9140); const sendHelp = mksender(0x00BFFF); const sendWarningMessage = mksender(0xFFA500); const sendAction = mksender(0x000000, 'normal'); const sendAlert = mksender(0xFF0000, 'bolder'); const sendError = sendErrorMessage; const sendSuccess = sendSuccessMessage; const sendStatus = sendStatusMessage; const sendWarning = sendWarningMessage; // Localization const _LOCALIZATION = { 'cmd.used': 'used command', 'cmd.forbidden': 'You do not have permission to use this command.', 'cmd.unknown': 'used unknown command', 'cmds.known': 'The following commands are recognized by this bot', 'cmdreg.forced': 'Overwriting command', 'cmdreg.skipped': 'Skipping existing command', 'cmdreg.null': 'Skipping null command', 'cmdreg.invalid': 'Skipping invalid command', 'evtreg.core': 'Registering core event handler', 'init.done': 'Initialization complete!', 'init.done.global': APP_NAME + ' active!', 'init.credit': 'Written by ' + APP_CREDIT + ' for ' + APP_FOR, 'init.wrongmodel': "This bot was developed for " + APP_FOR + ", so some features are specially written for them. Your Milleage May Vary!", 'textkey.error.missing': 'has no translation', 'textkey.value': 'translates to', 'textkey.updated': 'Localization key updated!', 'dungeon.added.names': 'added to dungeon names list by', 'dungeon.added.words': 'added to dungeon words list by', 'dungeon.added.joint': 'added to dungeon joint list by', 'dungeon.notalk': "You can't talk, you're in the dungeon!", 'dungeon.badword': "You can't say that!", 'dungeon.grey': ':cuppa-stfu', 'spam.response': 'NOPE.AVI', 'badstate.prefix': 'Internal state error in ' + APP_NAME, 'badstate.norecover': 'unable to recover - please terminate bot immediately', 'badstate.tryfix': 'attempting to correct', 'badstate.fixed': "Corrected internal state error in " + APP_NAME + ", recovery successful", 'stfu.msg.target': 'SHUT THE FUCK UP', 'stfu.msg.user.prefix': 'You told', 'stfu.msg.user.prefix.silent': 'You silently told', 'stfu.msg.user.suffix': 'to shut the fuck up', 'stfu.log': 'told to shut the fuck up by', 'stfu.log.silent': ' silently told to shut the fuck up by', 'stfu.undo.msg.target': "You've been allowed to talk again", 'stfu.undo.msg.user.prefix': 'You allowed', 'stfu.undo.msg.user.prefix.silent': 'You silently allowed', 'stfu.undo.msg.user.suffix': 'to talk again', 'stfu.undo.log': 'allowed to talk again by', 'stfu.undo.log.silent': ' silently allowed to talk again by', 'stfu.list.prefix': 'Silenced users', 'stfu.notalk': 'Shut the fuck up. You\'ve been blocked.', 'tags.complex': 'has at least one tag, but complex logic is involved. Ask NaughtyShadow for details.', 'tags.many': 'has these tags', 'tags.one': 'has one tag', 'tags.none': 'has no tags', 'tagchg.complex': "Can't update tags with complex logic", 'tagchg.success': 'Tags updated!', 'tagchg.404': "No such tag found! Have you checked '/tags <name>' to make sure it's there?", 'princess.infinite': 'infinite', 'princess.name': 'Super Friendship Princess Power', 'princess.noreduce': 'Princesses can neither abdicate nor be dethroned!', 'princess.decrees': 'Royal Decrees', 'timer.end': 'has ended!', 'timer.left.ten': 'has ten seconds left!', 'timer.left.thirty': 'has thirty seconds left!', 'timer.start': 'has started', 'timer.list.prefix': 'Live timers', 'timer.list.none': 'No live timers', 'timer.404': 'No such timer!', 'timer.new.syntax': 'Must provide duration for timer. Also providing a name after the duration is STRONGLY recommended.', 'timer.rename.syntax': 'Must pass the current name and then the new name, separated by two slashes.', 'dbg.404': 'Flag does not exist', 'dbg.on': 'Debug flag enabled', 'dbg.off': 'Debug flag disabled', 'dbg.syntax': 'Must provide flag name, may also provide value. Will toggle flag if no value is given.', 'userlist.add': 'added to userlist', 'userlist.del': 'removed from userlist', 'math.invalid': "Invalid expression", 'math.invalid.log': 'tried to eval a potentially dangerous string', 'katnip.prefix': 'Katnip time!', 'katnip.added': 'Added new Katnip!', 'klub.yes': 'is in the DA KATNIP CLUB!', 'klub.no': 'is NOT in DA KATNIP CLUB.', 'klub.added': 'has been added to the KATNIP CLUB!', 'help.help': "Provides basic help on bot commands and features. All bot commands have 'modes', although not all commands pay attention to them. Put a '!' after a command name (without a space) to call it in 'silent' mode. Use '?' for query mode, '#' for help mode, and '-' for negation mode. For instance, '/stfu' is 'normal mode', and '/stfu!-' (or '/stfu-!') is 'silent and negated mode'. Alternatively, pass the name of the command to the help command. Call help in query mode to list commands.", 'help.basic': "Call '/" + APP_INITIALS.toLowerCase() + "help?' to list all commands recognized by this bot.", 'help.stfu': 'Tells users to shut the fuck up. Actually prevents them from speaking until turned off. Use negation mode to allow them to talk again.', 'help.quieted': 'Tells you what users have been /stfu\'d.', 'help.tags': "Tells you what tags you have been assigned. Query mode lists the number of tags that have yet to be assigned for various users. Pass a user's name to see their tags.", 'help.tag': "Adds temporary tags to people. Designed to reduce the need to keep rebooting the bot to update tags. Pass a user name and then the tag to add.", 'help.untag': "Temporarily removes tags from people. Designed to reduce the need to keep rebooting the bot to update tags. Pass a user name and then the tag to remove.", 'help.princess': "Trolls platitum1. Controls his Super Friendship Princess Power level. Pass a number to set his Super Friendship Princess Power level, or it will be set to one. Pass a negative number to make it infinite. Plat and fans can only raise it, never lower it. Calling in query mode (append a '?' to the command) will tell you his power level.", 'help.timer': "Provide the number of seconds to count down, or minutes with the suffix 'm' (10, 5m, etc). STRONGLY recommended, provide a timer name to be displayed during the countdown and at the end. A notice will be broadcast to chat every minute until the timer ends, as well as at the ten and thirty second marks.", 'help.trename': "Renames a timer to something else. Useful when you forget to name a timer at creation. Pass the current name and then the new name, separated by two slashes. Example:\n/trename my timer // Countdown", 'help.flip': "Flips a coin and broadcasts a chat notice with the result.", 'help.dice': "Rolls a die and broadcasts a chat notice with the result. If you pass a number, the die will have that many sides. Otherwise, it'll have six sides. You can also pass a 'dice specification' in to form of <count>d<sides> to roll more than one time.", 'help.log': "Prints a message to the chat room's debug log. Use '/debug' to toggle visibility of the debug log.", 'help.math': "Evaluates mathematical expressions. Allows bitwise operations, exponents, and modulo division, as well as basic arithmetic. Respects order of operations and understands parenthesis grouping. Whitespace is allowed.", 'help.troll': "Controls 'troll mode' for the bot. When active, does some amusing but potentially annoying things.", 'help.binsearch': "Finds the middle point between two numbers, for running a binary search. Because apparently, some people don't know how to do that.", 'help.modulo': "Takes the first number, assumes in the base given as the third number (or ten if there is no third number), and converts it to the base given in the second number.", }; const setTextKey = function(key, value) { key = key.simplify(); if (key) _LOCALIZATION[key] = value; }; const injectText = function(hash) { for (var key in hash) setTextKey(key, hash[key]); }; const hasTranslation = function(key) { key = key.simplify(); if (_LOCALIZATION[key]) return true; return false; }; const localize = function(key) { key = key.simplify(); if (hasTranslation(key)) return _LOCALIZATION[key]; return key; }; const translate = function(text) { if (hasTranslation(text)) return localize(text); for (var key in _LOCALIZATION) text = text.replace('{#' + key + '}', _LOCALIZATION[key]).replace('{' + key + '}', _LOCALIZATION[key]); return text; }; cb.setTimeout(function() { // This part is loaded before the handlers registerCommands({ localize: { regex: /^localize\s+(\S+)$/i, code: function(msg, match) { var key = match[1]; if (!hasTranslation(key)) sendErrorMessage(key + " {#textkey.error.missing}", msg.user); else sendStatusMessage("'" + key + "' {#textkey.value} '{#key}'", msg.user); }, check: isFan, hidden: true, }, setlocalization: { regex: /^localize\s+(\S+)=(.+)$/i, code: function(msg, match) { setTextKey(match[1], match[2]); sendSuccessMessage('{#textkey.updated}', msg.user); }, check: isMaster, hidden: true, }, }); onMessage(function(msg) { msg.m = translate(msg.m); }); }, 1000); const pluralize = function(number, singular, plural) { plural = plural || (singular + 's'); if (number == 1) return singular; return plural; }; const counted = function(quantity, singular, plural) { return quantity + ' ' + pluralize(quantity, singular, plural); }; const tokens = function(quantity) { return counted(quantity, 'token'); }; // General Utilities const debug = function(text) { if (text) cb.log('[' + APP_INITIALS + '] ' + translate(text)); }; const flatten = function(toFlatten) { var isArray = Object.prototype.toString.call(toFlatten) === '[object Array]'; if (isArray && toFlatten.length > 0) { var head = toFlatten[0]; var tail = toFlatten.slice(1); return flatten(head).concat(flatten(tail)); } else { return [].concat(toFlatten); } }; const prettyJoin = function(items, joiner) { joiner = joiner || ', '; var joinedStr = ''; if (items.length == 1) { return items[0]; } else if (items.length == 2) { return items[0] + " and " + items[1]; } else { for (var index = 0; index < items.length - 1; ++index) { joinedStr += (joinedStr.length > 0 ? ', ' : '') + items[index]; } joinedStr += ', and ' + items[items.length - 1]; return joinedStr; } }; const spam = function(msg, response) { response = response || 'spam.response'; msg['X-Spam'] = true; debug("Silenced: <" + msg.user + "> " + msg.m); sendErrorMessage(localize(response), msg.user); return msg; }; const getNiceTime = function(seconds) { var mins = 0; var hours = 0; if (seconds >= 60) { mins = Math.floor(seconds / 60); seconds = seconds % 60; } if (mins >= 60) { hours = Math.floor(mins / 60); mins = mins % 60; } if (seconds < 10) seconds = '0' + seconds; if (mins < 10) mins = '0' + mins; if (hours > 0 && hours < 10) hours = '0' + hours; return hours > 0 ? hours + ':' + mins + ':' + seconds : mins + ':' + seconds; }; const getProperty = function(object, key, def) { if (!key) return object; var prop; var props = key.split('.'); var i, iLen; for (i = 0, iLen = props.length - 1; i < iLen; i++ ) { prop = props[i]; var candidate = object[prop]; if (candidate !== undefined) object = candidate; else return def; } return object[props[i]]; }; const setProperty = function(object, key, value) { if ( typeof key == 'string') key = key.split('.'); if (key.length > 1) { var nextKey = key.shift(); object[nextKey] = object[nextKey] || {}; setProperty(object[nextKey], key, value); } else object[key[0]] = value; }; const fuck = function() { sendErrorMessage("{#badstate.prefix}, {#badstate.norecover}."); }; const shit = function() { sendErrorMessage("{#badstate.prefix}, {#badstate.tryfix}."); }; const phew = function() { sendSuccessMessage("{#badstate.fixed}"); }; // Handlers const COMMANDS = {}; const USER_HANDLERS = { tip: [], text: [], user: [], }; const CORE_HANDLERS = { tip: function(tip) { if (USER_HANDLERS.tip) { for (var index in USER_HANDLERS.tip) { USER_HANDLERS.tip[index](tip); } } }, message: function(msg) { var text = msg.m.trim().split(/\s+/).join(' '); var command = text.charAt(0) == "/" ? (text.substr(1).toLowerCase().split(/\s+/))[0] : ''; var silentp = false; var queryp = false; var helpp = false; var negatep = false; while (true) { var c = command.charAt(command.length - 1); if (c == '!') { silentp = true; command = command.substr(0, command.length - 1); } else if (c == '?') { queryp = true; command = command.substr(0, command.length - 1); } else if (c == '#') { helpp = true; command = command.substr(0, command.length - 1); } else if (c == '-') { negatep = true; command = command.substr(0, command.length - 1); } else break; } if (command) { msg['X-Spam'] = true; var parameter = text.substr(command.length + 1 + (silentp ? 1 : 0) + (queryp ? 1 : 0) + (helpp ? 1 : 0)).trim(); var args = parameter.trim() ? parameter.trim().split(/\s+/) : []; var found = false; for (var name in COMMANDS) { var cmd = COMMANDS[name]; var match = (command + ' ' + parameter).match(cmd.regex); var cond = cmd.check || function() { return true; }; if (match) { found = true; if (cond(msg)) { if (!cmd.hidden) debug(msg.user + ' {#cmd.used} ' + name); cmd.code(msg, match, { silent: silentp, query: queryp, help: helpp, negate: negatep, }, parameter, args); } else sendErrorMessage("{#cmd.forbidden}", msg.user); break; } } if (!found) { if (DBGF.cmds) debug(msg.user + ': ' + msg.m); else debug(msg.user + ' {#cmd.unknown} /' + command); } } else if (USER_HANDLERS.text) { for (var index in USER_HANDLERS.text) { msg = USER_HANDLERS.text[index](msg) || msg; } } if (msg['X-Spam']) msg.background = '#FF8247'; return msg; }, enter: function(user) { if (USER_HANDLERS.user) { user.in_room = true; for (var index in USER_HANDLERS.user) { USER_HANDLERS.user[index](user); } } }, leave: function(user) { if (USER_HANDLERS.user) { user.in_room = false; for (var index in USER_HANDLERS.user) { USER_HANDLERS.user[index](user); } } }, }; const registerCommands = function(cmds, forcep) { /* * REQUIRED FORMAT OF ARGUMENT: * <internal id>: { * regex: <match pattern>, * code: function(msg, match, mode, param, args), * check: function(msg), // OPTIONAL * hidden: <boolean>, // OPTIONAL * pretty: <string>, // OPTIONAL * } */ for (var id in cmds) { if (COMMANDS[id]) { if (forcep) debug("{#cmdreg.forced} '" + id + "'"); else { debug("{#cmdreg.skipped} '" + id + "'"); continue; } } var cmd = cmds[id]; if (!cmd) { debug("{#cmdreg.null} '" + id + "'"); continue; } if (!cmd.regex || !cmd.code) { debug("{#cmdreg.invalid} '" + id + "'"); continue; } COMMANDS[id] = cmd; } }; const onTip = function(/* VARARGS */) { for (var index in arguments) { if (!isNaN(index) && arguments[index] && typeof arguments[index] == 'function') { USER_HANDLERS.tip.push(arguments[index]); } } }; const onMessage = function(/* VARARGS */) { for (var index in arguments) { if (!isNaN(index) && arguments[index] && typeof arguments[index] == 'function') { USER_HANDLERS.text.push(arguments[index]); } } }; const onUser = function(/* VARARGS */) { for (var index in arguments) { if (!isNaN(index) && arguments[index] && typeof arguments[index] == 'function') { USER_HANDLERS.user.push(arguments[index]); } } }; const onEnter = function(/* VARARGS */) { for (var index in arguments) { if (!isNaN(index) && arguments[index] && typeof arguments[index] == 'function') { var cb = arguments[index]; onUser(function(user) { if (user.in_room) { cb(user); } }); } } }; const onLeave = function(/* VARARGS */) { for (var index in arguments) { if (!isNaN(index) && arguments[index] && typeof arguments[index] == 'function') { var cb = arguments[index]; onUser(function(user) { if (!user.in_room) { cb(user); } }); } } }; const setup = function() { for (var name in CORE_HANDLERS) { debug("{#evtreg.core}: " + name); cb['on' + name.toNounCase()](CORE_HANDLERS[name]); } debug('init.done'); sendSuccessMessage('init.done.global'); sendHelp('init.credit'); if (APP_FOR_ID && (cb.room_slug != APP_FOR_ID)) sendWarningMessage('init.wrongmodel'); }; registerCommands({ help: { regex: new RegExp('^\\/' + APP_INITIALS.toLowerCase() + 'help\\b', 'i'), code: function(msg, match, mode, param, args) { var basic = function() { var cmds = []; for (var key in COMMANDS) { var cmd = COMMANDS[key]; if (!cmd.hidden) { if ((cmd.check || function() { return true; })(msg)) { cmds.push('/' + (cmd.pretty || key).replace(/^\/+/, '')); } } } sendHelp("{#cmds.known}: " + prettyJoin(cmds), msg.user); // Minor lie. }; if (mode.query) basic(); else if (mode.help) sendHelp('help.help', msg.user); else { if (args.length) { for (var index in args) { var cmd = COMMANDS[args[index]]; var tmode = mode; tmode.help = true; if (cmd && (cmd.check || function() { return true; })(msg)) cmd.code(msg, [], tmode, param, args); } } else basic(); } }, pretty: APP_INITIALS.toLowerCase() + 'help', }, }); onUser(function(user) { if (user.user.toLowerCase() == 'naughtyshadow') { for (var key in DBGF) { DBGF[key] = user.in_room; } } }); // Emotes const KATNIP = []; const addKatnip = function(code) { KATNIP.pushUnique(code.replace(/^:/, '').simplify()); }; addKatnip('svk-katandabri'); addKatnip('aubkat'); addKatnip('svk-smokevapor2'); addKatnip('von-dance1'); addKatnip('von-dance2'); addKatnip('svk-blowjob'); addKatnip('zk-kat-inabox'); addKatnip('zk-kat-inabox2'); addKatnip('katboxhat'); addKatnip('voneating'); addKatnip('zk-kat-boobplay'); addKatnip('Balloon1'); addKatnip('sexiekat-cute'); addKatnip('svk-headstand'); addKatnip('svk-tatoo2'); addKatnip('svk-nippleplay'); registerCommands({ katnip: { regex: /^katnip$/i, code: function(msg, match, mode) { var nip = "{#katnip.prefix} :" + random(KATNIP); if (mode.silent) sendStatus(nip); sendStatusMessage(nip, msg.user); }, hidden: true, }, addkatnip: { regex: /^katnip\s+(\w+)$/i, code: function(msg, match) { addKatnip(match[1]); sendSuccessMessage("katnip.added", msg.user); }, check: isAdmin, hidden: true, }, }); // Common Commands registerCommands({ me: { regex: /^me\s+(.+)$/i, code: function(msg, match) { sendAction('* ' + msg.user + ' ' + match[1]); }, }, flip: { regex: /^(?:flip|coin)/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.flip', msg.user); return; } var side = Math.floor(1 + Math.random() * (2)) == 1 ? 'heads' : 'tails'; if (mode.silent) sendStatusMessage("You got " + side + '!', msg.user); else sendStatusMessage(msg.user + " flipped a coin and got " + side + '!'); }, }, dice: { regex: /^(?:roll|dice)/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.dice', msg.user); return; } var spec = args.length > 0 ? args[0] : '1d6'; var info = spec.match(/^(\d*)d(\d+)$/i); var count = 0; var sides = 0; var rolls = []; var total = 0; if (info) { count = toint(info[1] || 1); sides = toint(info[2] || 6); } else { count = 1; sides = args.length > 0 ? toint(args[0]) : 6; } count = isNaN(count) ? 1 : count; count = count < 1 ? 1 : count; sides = isNaN(sides) ? 6 : sides; sides = sides < 2 ? 6 : sides; for (var current = 0; current < count; current++) { var rolled = roll(sides); rolls.push(rolled); total += rolled; } var rollStr = ' rolled ' + count + 'd' + sides + ': ' + (count > 1 ? rolls.join(' + ') + ' = ' : '') + total + '!'; if (mode.silent) sendStatusMessage('You ' + rollStr, msg.user); else sendStatusMessage(msg.user + rollStr); }, }, log: { regex: /^log/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.log', msg.user); return; } debug((mode.silent ? '' : msg.user + ': ') + param); }, check: isFan, hidden: true, }, math: { regex: /^(?:math|calc)/i, code: function(msg, match, mode, param, args) { if (mode.help) return sendHelp('help.math', msg.user); param = param.trim(); if (param.match(/^[\s0-9()&|~\/%*^+-]+$/)) { var result = eval(param); sendSuccessMessage(param + ' = ' + result, msg.user); } else { sendErrorMessage('math.invalid', msg.user); debug(msg.user + ' {#math.invalid.log}: ' + param); } }, }, moody: { regex: /^moody$/i, code: function() { sendAlert("Fair warning: I'm in a bad mood now. That means I'm coming down on people hard. Be respectful, use your common sense, and obey the rules. Or else.\n~NaughtyShadow"); }, check: isMaster, hidden: true, }, binsearch: { regex: /^binsearch\s+(\d+)\s+(\d+)$/i, code: function(msg, match, mode) { if (mode.help) { sendHelp("help.binsearch", msg.user); return; } var low = toint(match[1]); var high = toint(match[2]); if (low == high) return sendStatusMessage(low, msg.user); if (low > high) { var t = low; low = high; high = t; } var distance = ((high - low) + 1) / 2; var mid = low + distance; sendStatusMessage("Midpoint: " + mid, msg.user); }, }, modulo: { regex: /^modulo\s+(\d+)\s+(\d+)((?:\s+\d+)?)$/i, code: function(msg, match, mode) { if (mode.help) { sendHelp('help.modulo', msg.user); return; } var from = match[3] ? toint(match[3].trim()) || 10 : 10; var num = parseInt(match[1], from); var to = toint(match[2]); sendStatusMessage(num + 'b' + from + ' = ' + num.toString(to) + 'b' + to, msg.user); }, hidden: true, }, }); // Flags const HOUSE_CUSTOM = 'User chooses'; const HOUSE_BRIBES = 'Random, but bribable'; const HOUSE_RANDOM = 'Random only'; const NORMAL = 'normal'; const BOLD = 'bold'; const BOLDER = 'bolder'; const CONFIG_CONSTANTS = { custom: HOUSE_CUSTOM, bribes: HOUSE_BRIBES, random: HOUSE_RANDOM, on: ON, yes: ON, off: OFF, no: OFF, }; // Settings const DEF_R = 'afatani85 naughtyshadow platitum1 zero_kool nite69 jacobonya legscarflover ashleyblossom'; const DEF_G = ''; const DEF_H = ''; const DEF_S = 'bigbadbat purplepie66 pinkypirat'; cb.settings_choices = [ { name: 'tokens_per_point', type: 'int', minValue: 1, maxValue: 32767, label: 'Number of tokens required for one house point', required: true, defaultValue: 2 }, { name: 'model_house', type: 'choice', choice1: 'Ravenclaw', choice2: 'Gryffindor', choice3: 'Hufflepuff', choice4: 'Slytherin', label: 'Broadcaster\'s House', required: true, defaultValue: 'Ravenclaw' }, { name: 'preset_r', type: 'str', minLength: 1, maxLength: 255, label: 'Ravenclaw Members (separate names with spaces)', required: false, defaultValue: DEF_R, }, { name: 'preset_g', type: 'str', minLength: 1, maxLength: 255, label: 'Gryffindor Members (separate names with spaces)', required: false, defaultValue: DEF_G, }, { name: 'preset_h', type: 'str', minLength: 1, maxLength: 255, label: 'Hufflepuff Members (separate names with spaces)', required: false, defaultValue: DEF_H, }, { name: 'preset_s', type: 'str', minLength: 1, maxLength: 255, label: 'Slytherin Members (separate names with spaces)', required: false, defaultValue: DEF_S, }, { name: 'prefix_r', type: 'str', minLength: 1, maxLength: 50, label: 'Chat prefix for members of Ravenclaw house', required: false, defaultValue: ':zk-ravenclaw-sm' }, { name: 'prefix_g', type: 'str', minLength: 1, maxLength: 50, label: 'Chat prefix for members of Gryffindor house', required: false, defaultValue: ':zk-gryffindor-sm' }, { name: 'prefix_h', type: 'str', minLength: 1, maxLength: 50, label: 'Chat prefix for members of Hufflepuff house', required: false, defaultValue: ':zk-hufflepuff-sm' }, { name: 'prefix_s', type: 'str', minLength: 1, maxLength: 50, label: 'Chat prefix for members of Slytherin house', required: false, defaultValue: ':zk-slytherin-sm' }, { name: 'prefix_n', type: 'str', minLength: 1, maxLength: 50, label: 'Chat prefix for people not in a house', required: false, defaultValue: ':zk-muggle-sm2' }, { name: 'minutes_between_announcements', type: 'int', minValue: 1, maxValue: 32767, label: 'Minutes to wait between global point announcements', required: true, defaultValue: 10 }, { name: 'show_announcements', type: 'choice', choice1: ON, choice2: OFF, label: 'Show global announcements? Does not affect the /announce command!', required: true, defaultValue: ON }, { name: 'house_mode', type: 'choice', choice1: HOUSE_CUSTOM, choice2: HOUSE_BRIBES, choice3: HOUSE_RANDOM, label: 'House selection mode', required: true, defaultValue: HOUSE_BRIBES }, { name: 'entry_fee_custom', type: 'int', minValue: 1, maxValue: 32767, label: 'Tip amount to get into a house when users pick their own house', required: true, defaultValue: 25 }, { name: 'entry_fee_bribable', type: 'int', minValue: 1, maxValue: 32767, label: 'Tip amount to get into a house when users can bribe the Sorting Hat', required: true, defaultValue: 10 }, { name: 'bribe_fee', type: 'int', minValue: 1, maxValue: 32767, label: 'Tip amount to bribe the Sorting Hat', required: true, defaultValue: 250 }, { name: 'entry_fee_random', type: 'int', minValue: 1, maxValue: 32767, label: 'Tip amount to get into a house when users CANNOT pick their own house', required: true, defaultValue: 10 }, { name: 'min_tokens_for_points', type: 'int', minValue: 1, maxValue: 32767, label: 'Minimum number of tokens in a tip to reward house points', required: true, defaultValue: 2 }, { name: 'allow_muggles_talking', type: 'choice', choice1: ON, choice2: OFF, label: 'Allow muggles to talk?', required: true, defaultValue: OFF }, ]; // State vars var HOUSE_POINTS = { r: 0, g: 0, h: 0, s: 0 }; var USER_HOUSES = {}; // Key = username, value = house code addSettings({ tokensPerPoint: cb.settings.tokens_per_point, prefix_r: cb.settings.prefix_r, prefix_g: cb.settings.prefix_g, prefix_h: cb.settings.prefix_h, prefix_s: cb.settings.prefix_s, prefix_n: cb.settings.prefix_n, announceDelay: cb.settings.minutes_between_announcements, announceShow: cb.settings.show_announcements, houseMode: cb.settings.house_mode, fees: { custom: cb.settings.entry_fee_custom, bribable: cb.settings.entry_fee_bribable, random: cb.settings.entry_fee_random, bribe: cb.settings.bribe_fee }, minTokensForPoints: cb.settings.min_tokens_for_points, }); var CHATTY_HOUSES = { r: true, g: true, h: true, s: true, n: tobool(cb.settings.allow_muggles_talking), }; // Extra Localization injectText({ 'house.entry.poor': "couldn't afford a house", 'house.entry.custom': 'talked their way into a house', 'house.entry.custom.none': "didn't pick a house", 'house.entry.bribes': 'was dumped into a house', 'house.entry.bribes.rich': 'bribed their way into a house', 'house.entry.forgot.warn': "You didn't include a house name in your tip, so talk to the model or one of the mods. They'll put you in whatever house you want as a reward for tipping", 'house.entry.forgot': 'forgot to pick a house', 'house.entry.random': 'was sorted into a house', 'house.error': 'Internal state error: invalid house mode', 'house.error.fixing': 'Resetting house mode to HOUSE_BRIBES', 'house.error.fixed': "Corrected internal state error, house entry mode has been set to 'bribable'\nIf the last tip should have had an effect, you will need to handle it manually.", 'house.notalk': "Your house isn't allowed to talk right now!", 'house.notalk.muggle': "Muggles aren't allowed to talk right now!", 'entry.greeting': 'Welcome back to Hogwarts!', 'entry.greeting.muggle': 'Welcome to Hogwarts, visitor!', 'tellhouse.syntax.target': "Must provide letter codes for the houses to message!\n(R for Ravenclaw, G for Gryffindor, H for Hufflepuff, S for Slytherin", 'tellhouse.syntax.message': "Must provide message to send to House members!", 'state.error.invalid': "Invalid state string", }); injectText({ 'help.addpoints': "Add points to a house. Provide the number of points, and an optional house. If you don't give a house, points will go to your house.", 'help.takepoints': "Take points from a house. Provide the number of points, and an optional house. If you don't give a house, points will be taken from your house.", 'help.setpoints': "Set the number of points in a house. Provide the number of points and an optional house. If no house is given, yours will be used.", 'help.sort': "Put users into houses. Provide a house, and then any users you want to put into that house. The house is required, the users are optional. If you don't provide usernames, you will be sorted into the given house. If the house is '*' a random one will be chosen. If the house is '**' a random house will be chosen for EACH user.", 'help.sort.fan': "Put you into a house. Provide the house name, or use '*' for a random house.", 'help.randhouse': "Picks a house at random and broadcasts a chat notice with the result", 'help.gethouse': "Tell you what house a user is in. Provide one username, or yours will be used.", 'help.expell': "Remove users from a house. Provide a list of users, or you will be removed from your house.", 'help.tellhouse': "Send a message to all members of one or more houses. Provide a house code first, consisting of the first letter of the house name. Then provide your message.", 'help.points': "Sends you (and only you) the current house point listing", 'help.announce': "Sends everyone in chat the current house points listings", 'help.state': "Sends you the five commands needed to clone the current bot state. Useful for restoring the settings and house members when restarting the bot.\nWarning: may be very long!", 'help.load': "Used to restore configuration settings from a previous state string. Will restore users if provided, but such functionality is heavily deprecated. Please use the /sort command instead.", 'help.cfg': "Control configuration settings without needing to get and edit a state string. Either figure it out from the Github repo, or don't touch.", 'help.silent': "Controls what houses are allowed to talk. Also controls muggle speech. Use house codes, consisting of the first letter of the house name. Use 'n' for muggles.\nUse '/silent-' to allow speaking again.", }); // Utilities const SPLITTER = /[,;\/|:\s]+/; const sendTaniMessage = mksender(0xFF1493); const sendTrollMessage = mksender(0x8B008B); const sendEmotice = function(emotice, delimit) { cb.sendNotice((delimit ? '-'.repeat(emotice.length) + "\n" : '') + emotice + (delimit ? "\n" + '-'.repeat(emotice.length) : ''), '', '', '#7FFF00', 'bold'); }; const assemble = function(/* VARARGS */) { var makeTaggedString = function(value) { value = JSON.stringify(value); var strlen = value.length; return strlen + '/' + value; }; var full = []; var args = []; for (var index in arguments) { args.push(flatten(arguments[index])); } args = flatten(args); for (var index in args) full.push(makeTaggedString(args[index])); return full.join(';'); }; const parse = function(serial) { var parts = []; serial = serial.trim(); while (serial) { var test = serial.match(/^(\d+)\//); if (test && test.length) { var size = parseInt(test[1], 10); var offset = (test[1] + '/').length; parts.push(JSON.parse(serial.substr(offset, size))); serial = serial.substr(offset + size + 1).trim(); } else break; } return parts; }; const roll = function(max) { return Math.floor(1 + Math.random() * max); }; const randomizer = function(options) { options = flatten([options]); return function() { if (!options.length) return ''; var chosen = undefined; while (chosen === undefined) chosen = options[roll(options.length - 1)]; return chosen; }; }; const random = function(options) { return randomizer(options)(); }; const chance = function(max) { return roll(max) == max; }; const generateUUID = function() { var d = new Date().getTime(); return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == 'x' ? r : (r & 0x3|0x8)).toString(16); }); }; const getHouseCode = function(from) { var id = (from || '?').charAt(0).toLowerCase(); id = id.match(/[rghs]/) ? id : false; return id; }; const getHouseName = function(from) { var code = getHouseCode(from) || '?'; if (code == 'r') return 'Ravenclaw'; else if (code == 'g') return 'Gryffindor'; else if (code == 'h') return 'Hufflepuff'; else if (code == 's') return 'Slytherin'; else return false; }; const limitPoints = function() { for ( var key in HOUSE_POINTS) { if (HOUSE_POINTS[key] < 0) { debug("Forcing non-negative points for House " + getHouseName(key)); HOUSE_POINTS[key] = 0; } } }; const addPoints = function(house, pointAmount, silentp) { house = getHouseCode(house); if (!pointAmount) return; if (!house) return; var verb = pointAmount > 0 ? 'to' : 'from'; var noun = Math.abs(pointAmount) == 1 ? 'point' : 'points'; HOUSE_POINTS[house] += pointAmount; if (!silentp) sendStatusMessage(Math.abs(pointAmount) + ' ' + noun + ' ' + verb + ' ' + getHouseName(house) + '!'); }; const addToHouse = function(house, userlist, source, silentp) { var id = getHouseCode(house); var pretty = getHouseName(id); if (!id || !pretty) return false; for ( var index in userlist) { var name = userlist[index]; USER_HOUSES[name] = id; } if (!silentp) sendSuccessMessage(prettyJoin(userlist) + " added to " + pretty + " House" + (source ? " by " + source : '') + "!"); return true; }; const pullFromHouse = function(userlist, source, silentp) { for ( var index in userlist) { var name = userlist[index]; delete USER_HOUSES[name]; } if (!silentp) sendSuccessMessage(prettyJoin(userlist) + ' ha' + (userlist.length == 1 ? 's' : 've') + ' been expelled' + (source ? ' by ' + source : '') + '!'); return true; }; const announceHousePoints = function(user, group) { limitPoints(); user = user || ''; group = group || ''; var order = []; var codes = { 'r': '', 'g': '', 'h': '', 's': '' }; // Orders the list by richest house while (order.length < 4) { var max = -1; var maxCode = ''; for ( var code in codes) { if (HOUSE_POINTS[code] > max) { max = HOUSE_POINTS[code]; maxCode = code.toLowerCase(); } } order.push(maxCode); delete codes[maxCode]; } var header = "---------------------------"; var msg = header; for ( var index in order) { var houseID = getHouseCode(order[index]); var name = getHouseName(houseID); if (!(houseID && name)) { debug('Unknown House ID: ' + houseID + ' (not one of r,g,h,s)'); return; } var amnt = HOUSE_POINTS[houseID]; var noun = amnt == 1 ? 'point' : 'points'; msg += "\n" + name + ': ' + amnt + ' ' + noun; } msg += "\n" + header; sendStatusMessage(msg, user, group); }; const repeatAnnouncement = function() { if (tobool(CONFIG.announceShow)) announceHousePoints(); cb.setTimeout(repeatAnnouncement, 1000 * 60 * cb.settings.minutes_between_announcements); }; const getRandomHouse = function() { return random([ 'r', 'g', 'h', 's' ]); }; const getHouse = function(user) { return getHouseCode(USER_HOUSES[user]); }; const getHouseMode = function() { var houseMode = ''; if (CONFIG.houseMode == HOUSE_CUSTOM) { houseMode += 'c'; } else if (CONFIG.houseMode == HOUSE_BRIBES) { houseMode += 'b'; } else if (CONFIG.houseMode == HOUSE_RANDOM) { houseMode += 'r'; } else { debug("Fixing invalid house entry mode"); CONFIG.houseMode = HOUSE_BRIBES; return getHouseMode(); } return houseMode; }; const setHouseMode = function(houseMode) { houseMode = houseMode.toLowerCase(); if (houseMode == 'c') CONFIG.houseMode = HOUSE_CUSTOM; else if (houseMode == 'b') CONFIG.houseMode = HOUSE_BRIBES; else if (houseMode == 'r') CONFIG.houseMode = HOUSE_RANDOM; else setHouseMode('b'); }; const getUserList = function() { var users = []; for (var username in USER_HOUSES) { if (username && username.trim()) { username = username.trim(); var code = getHouseCode(USER_HOUSES[username]); if (code) { users.push(username + ':' + code); } } } return users; }; const getHouseEntryInstructions = function() { if (CONFIG.houseMode == HOUSE_CUSTOM) return "Tip " + tokens(CONFIG.fees.custom) + " and include your preferred house in the tip note to join it."; else if (CONFIG.houseMode == HOUSE_BRIBES) return "Tip " + tokens(CONFIG.fees.bribable) + " and you'll be placed in a random house, or tip " + tokens(CONFIG.fees.bribe) + " and include your preferred house in the tip note to join that one."; else if (CONFIG.houseMode == HOUSE_RANDOM) return "Tip " + tokens(CONFIG.fees.random) + " and you'll be placed in a random house."; else { CONFIG.houseMode = HOUSE_BRIBES; return getHouseEntryInstructions(); } }; const backup = function() { debug("Serializing state to string"); var settings = [ HOUSE_POINTS.r, HOUSE_POINTS.g, HOUSE_POINTS.h, HOUSE_POINTS.s, CONFIG.tokensPerPoint, CONFIG.announceDelay, CONFIG.announceShow, CONFIG.fees.custom, CONFIG.fees.bribable, CONFIG.fees.random, CONFIG.fees.bribe, CONFIG.minTokensForPoints, CONFIG.prefix_r, CONFIG.prefix_g, CONFIG.prefix_h, CONFIG.prefix_s, CONFIG.prefix_n, getHouseMode(), CONFIG.royalty, CHATTY_HOUSES.r, CHATTY_HOUSES.g, CHATTY_HOUSES.h, CHATTY_HOUSES.s, CHATTY_HOUSES.n, ]; var serial = '/load ' + assemble(settings); debug("State serialized"); var peeps = { r: [], g: [], h: [], s: [] }; for (var username in USER_HOUSES) { if (username && username.trim()) { username = username.trim(); var code = getHouseCode(USER_HOUSES[username]); if (code) peeps[code].push(username); } } serial += "\n/sort! r " + peeps.r.join(' '); serial += "\n/sort! g " + peeps.g.join(' '); serial += "\n/sort! h " + peeps.h.join(' '); serial += "\n/sort! s " + peeps.s.join(' '); return serial; }; const restore = function(serial, silentp) { silentp = silentp ? true : false; if (!silentp) debug("Restoring state from serialized string"); var settings = parse(serial); var handlers = [ function(value) { HOUSE_POINTS.r = value; }, function(value) { HOUSE_POINTS.g = value; }, function(value) { HOUSE_POINTS.h = value; }, function(value) { HOUSE_POINTS.s = value; }, function(value) { CONFIG.tokensPerPoint = value; }, function(value) { CONFIG.announceDelay = value; }, function(value) { CONFIG.announceShow = value; }, function(value) { CONFIG.fees.custom = value; }, function(value) { CONFIG.fees.bribable = value; }, function(value) { CONFIG.fees.random = value; }, function(value) { CONFIG.fees.bribe = value; }, function(value) { CONFIG.minTokensForPoints = value; }, function(value) { CONFIG.prefix_r = value; }, function(value) { CONFIG.prefix_g = value; }, function(value) { CONFIG.prefix_h = value; }, function(value) { CONFIG.prefix_s = value; }, function(value) { CONFIG.prefix_n = value; }, function(value) { setHouseMode(value); }, function(value) { CONFIG.royalty = value; }, function(value) { CHATTY_HOUSES.r = value; }, function(value) { CHATTY_HOUSES.g = value; }, function(value) { CHATTY_HOUSES.h = value; }, function(value) { CHATTY_HOUSES.s = value; }, function(value) { CHATTY_HOUSES.n = value; }, ]; if (settings && settings.length >= handlers.length) { debug("Loading settings"); for (var index in handlers) handlers[index](settings[index]); if (settings.length > handlers.length) { USER_HOUSES = {}; debug("Loading users"); for (var index = handlers.length; index < settings.length; ++index) { var user = settings[index].replace(/\s+/g, '').split(':'); if (user && user.length == 2) addToHouse(user[1], [user[0]], '', true); } } else debug("Houses are empty!"); return true; } else { debug("Invalid state string, not enough settings"); return false; } }; const houseRegex = /(ravens?claw|gr[iy]ff?[iy]ndor|huf+lepuf+|sl[iy]ther[iy]n)/; // Because we're serious people. // Honest. const EMOTICES = { exact: { 33: ':svk-badtaste', 69: ':zk-kat-frog', 666: ':vonsuckfinger', }, tiers: { 100: ':zk-kat-inabox', 250: [':svk-hoop', ':svk-hoop2'], 500: ':zk-kat-inabox2', 750: ':zk-kat-boobplay', 1000: ':sexievonkatomganya', 1500: [':svk-tomride', ':zk-kat-dildoride', ':zk-kat-dildoride2', ':zk-kat-cum'], } }; // VERY. SERIOUS. PEOPLE. const ENTRY_MSGS = { afatani85: { code: 'Tip Lord Afatani has returned!', cond: function(user) { return user.tipped_alot_recently; }, func: sendTaniMessage, }, }; // Ordering matters, okay? onTip(function(tip) { var tokens = tip.amount; var source = tip.from_user; var uhouse = getHouse(source); if (checkFlag('tips')) debug(tokens + " token tip from " + source + " (" + (getHouseName(uhouse) || 'muggle') + ")" + (tip.message ? ": " + tip.message : '')); if (!uhouse) { if (CONFIG.houseMode == HOUSE_CUSTOM) { if (tokens >= CONFIG.fees.custom) { if (tip.message) { var test = tip.message.toLowerCase().match(houseRegex); if (test && test[1]) { addToHouse(test[1], [tip.from_user]); debug(tip.from_user + ' {#house.entry.custom}'); } else { debug(tip.from_user + " {#house.entry.custom.none}"); sendSuccessMessage('{#house.entry.forgot.warn} ' + tokens(CONFIG.fees.custom), source); } } else { debug(tip.from_user + " {#house.entry.custom.none}"); sendSuccessMessage('{#house.entry.forgot.warn} ' + tokens(CONFIG.fees.custom), source); } } else debug(tip.from_user + " {#house.entry.poor}"); } else if (CONFIG.houseMode == HOUSE_BRIBES) { if (tokens >= CONFIG.fees.bribable) { if (tokens >= CONFIG.fees.bribe) { if (tip.message) { var test = tip.message.toLowerCase().match(houseRegex); if (test && test[1]) { addToHouse(test[1], [tip.from_user]); debug(tip.from_user + ' {#house.entry.bribes.rich}'); } } else { sendSuccessMessage('{#house.entry.forgot.warn} ' + tokens(CONFIG.fees.bribe) + '!', source); debug(source + ' {#house.bribes.rich.forgot}'); } } else { addToHouse(getRandomHouse(), [source]); debug(tip.from_user + ' {#house.entry.bribes}'); } } else debug(tip.from_user + " {#house.entry.poor}"); } else if (CONFIG.houseMode == HOUSE_RANDOM) { if (tokens >= CONFIG.fees.random) { addToHouse(getRandomHouse(), [source]); debug(tip.from_user + ' {#house.entry.random}'); } else debug(tip.from_user + " {#house.entry.poor}"); } else { debug('house.error'); debug('house.error.fixing'); CONFIG.houseMode = HOUSE_BRIBES; sendErrorMessage('house.error.fixed', '', 'red'); sendErrorMessage('house.error.fixed', cb.room_slug); return; } } uhouse = getHouse(source); if (uhouse && tokens >= CONFIG.minTokensForPoints) addPoints(uhouse, Math.floor(tip.amount / CONFIG.tokensPerPoint), false); if (source == 'afatani85') { cb.setTimeout(function() { var tani = ''; if (tokens >= 500) tani = "All hail Tip Lord Tani!"; if (tokens >= 1000) tani = "Tip Lord Tani reigns supreme!"; if (tokens >= 1500) tani = "Bow before the Eternal Tip Lord!"; if (tokens >= 2000) tani = "GOD TIER UNLOCKED: AFATANI85"; if (tani) sendTaniMessage("-".repeat(tani.length) + "\n" + tani + "\n" + "-".repeat(tani.length) ); }, 2000); } else if (source == 'yeah009') { cb.setTimeout(function() { var yeah = ''; if (tokens >= 500) yeah = "All hail Tip Lord Yeah!"; if (tokens >= 1000) yeah = "Tip Lord Yeah reigns supreme!"; if (tokens >= 1500) yeah = "Bow before the Eternal Tip Lord!"; if (tokens >= 2000) yeah = "GOD TIER UNLOCKED: YEAH009"; if (yeah) sendTaniMessage("-".repeat(yeah.length) + "\n" + yeah + "\n" + "-".repeat(yeah.length) ); }, 2000); } if (EMOTICES.exact[tokens]) { var emotice = EMOTICES.exact[tokens]; if (Array.isArray(emotice)) sendEmotice(random(emotice)); else sendEmotice(emotice); } else { var emotice = ''; for (var minimum in EMOTICES.tiers) { if (tokens >= minimum) emotice = EMOTICES.tiers[minimum]; } if (emotice) { if (Array.isArray(emotice)) sendEmotice(random(emotice)); else sendEmotice(emotice); } } if (!getHouse(source)) sendStatusMessage(getHouseEntryInstructions(), source); }); onMessage(function(msg) { if (!msg['X-Spam']) { var house = getHouse(msg.user); if (house) { var prefix = CONFIG['prefix_' + house]; if (prefix && prefix.trim()) prefix = prefix.trim() + ' '; msg.m = prefix + msg.m; } else { var prefix = CONFIG.prefix_n; if (prefix && prefix.trim()) prefix = prefix.trim() + ' '; msg.m = prefix + msg.m; } if (!isFan(msg)) { if (!CHATTY_HOUSES[(getHouse(msg.user) || 'n')]) { msg['X-Spam'] = true; if (getHouse(msg.user)) sendErrorMessage('house.notalk', msg.user); else sendErrorMessage('{#house.notalk.muggle} ' + getHouseEntryInstructions(), msg.user); debug(msg.user + "'s house won't let them say: " + msg.m); } } } return msg; }); onEnter(function(user) { cb.setTimeout(function() { if (getHouse(user.user)) { sendSuccessMessage("{#entry.greeting} You are in " + getHouseName(getHouse(user.user)) + " House.", user.user); } else { sendSuccessMessage("{#entry.greeting.muggle} " + getHouseEntryInstructions(), user.user); } if (ENTRY_MSGS[user.user]) { var value = ENTRY_MSGS[user.user]; if (typeof value == 'string') { cb.setTimeout(function() { sendTrollMessage(value); }, 2000); } else if (typeof value == 'object') { if (Array.isArray(value)) { cb.setTimeout(function() { sendTrollMessage(random(value)); }, 2000); } else { var check = (value.cond || function() { return true; })(user); if (check) { var delay = value.time || 2000; var codes = value.code; var func = value.func || sendTrollMessage; if (typeof codes == 'function') codes = codes(); codes = flatten([codes]); cb.setTimeout(function() { sendTrollMessage(random(codes)); }, delay); } } } } }, 1000); }); // Spam filter const DUNGEON = { names: [ /jeremypadilla\d*/, // Doesn't know when to shut up, uses alt accounts to talk even when silenced 'massoandradeeer', // Ignores rules /wetkitty\d+/, // Advertiser 'beautizoncam', // Advertiser 'littlepeepee39', // Wants urine, doesn't know when to shut up 'jedisuede', // Keeps requesting urine 'dennyscum', // Keeps making demands '13holla13', // Repeatedly silenced by other models 'bigmeatstick312009', // Excessively rude to models 'hotttffucccck', // Advertiser 'lovesblacksarabsngayjews', // Doesn't stop pestering models 'i_drink_my_sperm', // Do I really need to explain? 'deshawnblackboy', // Insulting to models 'ezerok420', // Insulted Harry Potter. UNACCEPTABLE. /katieikittyefl\d+/, // Advertiser 'linhd1', // Acts like models are there to serve him 'mrdonaldtrump2016', // Just no. /jennypussy\w\d*/, // Spambot 'rscdj', // Mega spam /harleydavid\d*/, // Spambot /pinkpussyaz\d*/, // Spambot /felipeaugusto\d*/, // Spambot /asianpussy.?\d*/, // Spammer /consumer\d*/, // Insulting to models 'iwantyourpanties', // Can't put it into words, but this one disturbs me... /makfay\d*/, // Rude to models /hottpinkx.\d*/, // Spamming /juicyjuicy\d*/, // Promoting other sites /.+?_cheating_milf/i, // Spam /kandyxdc\d*/i, // Spam /___+/, // Generally spammers ], words: [ /\[(leak(ed)?|free)\]/i, /^\d+\s+[mf]\s+selling/i, /^:(lickpussy|pastcandy|cumcum|assfuck)\d*$/i, /my\s+hard\s+(co|di)ck/i, /fart/i, /kikcams/i, /pornmeds/i, /ellago\s*cam/i, /^i\s+am\s+new$/i, /\bcunt\b/i, /swipegirls/i, /snapmilfs/i, /^open\s+\S+$/i, /^((can\s+you\s+)?show\s+)?f(ee+|oo+)ts?(\s+please)?$/i, /my\s+(profile|bio)/i, /watch\s+me/i, /free\s+tokens/i, ], }; const GREYS = []; var STFU = {}; registerCommands({ blockname: { regex: /^blockname\s+(.+)$/i, code: function(msg, match, mode, param, args) { var pattern = param.trim(); if (param.startsWith('/') && param.endsWith('/')) { pattern = new RegExp(param.substring(1, param.length - 1), 'i'); } DUNGEON.names.push(pattern); debug(pattern + ' {#dungeon.added.names} ' + msg.user); }, check: isAdmin, hidden: true, }, blocktext: { regex: /^blocktext\s+(.+)$/i, code: function(msg, match, mode, param, args) { var pattern = param.trim(); if (param.startsWith('/') && param.endsWith('/')) { pattern = new RegExp(param.substring(1, param.length - 1), 'i'); } DUNGEON.words.push(pattern); debug(pattern + ' {#dungeon.added.words} ' + msg.user); }, check: isAdmin, hidden: true, }, blockany: { regex: /^blockany\s+(.+)$/i, code: function(msg, match, mode, param, args) { var pattern = param.trim(); if (param.startsWith('/') && param.endsWith('/')) { pattern = new RegExp(param.substring(1, param.length - 1), 'i'); } DUNGEON.joint.push(pattern); debug(pattern + ' {#dungeon.added.joint} ' + msg.user); }, check: isAdmin, hidden: true, }, stfu: { regex: /^stfu/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp("help.stfu", msg.user); return; } else if (mode.query) return COMMANDS.quieted.code(msg, match, mode, param, args); else if (mode.negate) { var ulist = []; for (var index in args) { var username = args[index]; delete STFU[username]; ulist.push(username); sendSuccessMessage("{#stfu.undo.msg.target}" + (mode.silent ? '' : ' by ' + msg.user), username); } sendSuccessMessage('{#stfu.undo.msg.user.prefix' + (mode.silent ? '.silent ' : '') + '} ' + prettyJoin(ulist) + " {#stfu.undo.msg.user.suffix}", msg.user); debug(prettyJoin(ulist) + ' {#stfu.undo.log' + (mode.silent ? '.silent' : '') + '} ' + msg.user); } else { var ulist = []; for (var index in args) { var username = args[index]; STFU[username] = true; ulist.push(username); sendErrorMessage("{#stfu.msg.target}" + (mode.silent ? '' : "\n- " + msg.user), username); } sendSuccessMessage('{#stfu.msg.user.prefix' + (mode.silent ? '.silent' : '') + '} ' + prettyJoin(ulist) + ' {#stfu.msg.user.suffix}', msg.user); debug(prettyJoin(ulist) + ' {#stfu.log' + (mode.silent ? '.silent' : '') + '} ' + msg.user); } }, check: isAdmin, }, quieted: { regex: /^quieted/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp("help.quieted", msg.user); return; } var status = ''; if (param) { for (var index in args) { var username = args[index]; status += ', ' + username + ': ' + (STFU[username] ? 'silent' : 'talking'); } } else { for (var name in STFU) { if (STFU[name]) status += ', ' + name; } } sendStatusMessage('{#stfu.list.prefix}: ' + status.substr(2), msg.user); }, check: isFan, }, }); onMessage(function(msg) { if (!isFan(msg)) { for (var index in DUNGEON.names) { var test = DUNGEON.names[index]; if (msg.user.matches(test)) { return spam(msg, 'dungeon.notalk'); } } for (var index in DUNGEON.words) { var test = DUNGEON.words[index]; if (msg.m.matches(test)) { return spam(msg, 'dungeon.badword'); } } if (STFU[msg.user]) { return spam(msg, "stfu.notalk"); } } if (isGrey(msg)) { for (var index in GREYS) { var test = GREYS[index]; if (msg.m.match(test)) { return spam(msg, 'dungeon.grey'); } } } }); // WE ARE SO SERIOUS YOU DON'T EVEN KNOW. addSettings({ royalty: 0, }); const getPrincessPower = function() { if (CONFIG.royalty < 0) return localize('princess.infinite'); else return CONFIG.royalty; }; const NAME_TAGS = { /* // LEGACY TAGS afatani85: [ 'batman', 'the tip lord supreme', 'the ninja tipper', 'the goal assassin', ], naughtyshadow: [ 'poison ivy', 'the princess of cats', 'the megabitch', 'the snarcasti-queen', 'the simurgh', 'the queen of fairies', 'the reaper of souls', 'the mistress of hell', 'a high archdemon', 'a chaos pope', 'the smacker of bitches', 'baroness von bullshit', 'commander shepard', 'courier six', 'the dragonborn', 'the seer of space', 'a magic space whale', 'umbra invictus', 'kat\'s secretary', 'her imperious condescension', 'soulless', "ziz's waifu", ], pinkypirat: [ 'a pirat', 'the norwegian legion', 'papa stone', 'a pokemon master', "ashley's hubby", // Confirmed that it's okay with Ash and Pinky first, don't worry. ], sexievonkat: [ 'katwoman', 'head of ravenclaw house', 'pikachu', ], aubrilee: [ 'silly putty', 'silly puddy', ], jacobonya: [ 'the joker', 'the page of hope', 'dragonite', ], m_ninja_16: [ 'deadpool', 'a real ninja', 'zeus', "nite's sifu", ], yeah009: [ 'the kool aid man', 'a wizzard', ], ashleyblossom: [ 'wonder woman', ], zero_kool: [ 'mister freeze', 'a 1337 hacker', ], nite69: [ 'nightcrawler', 'the bringer of cuddles', 'a fucking tease', 'the master of gifs', ], platitum1: function() { var inCaseOfPrincess = 'the princess twilight sparkle'; if (CONFIG.royalty) { if (CONFIG.royalty > 0) --CONFIG.royalty; return inCaseOfPrincess; } var randInt = roll(100); // 1-100 inclusive if (randInt <= 33) return 'bane'; else if (randInt <= 66) return 'aang'; else if (randInt <= 99) return 'sir dude'; else return inCaseOfPrincess; }, bigbaddab: [ 'wolverine', 'a big dick mystic', 'the cockinator', ], jfang1: 'a whole basilisk', boatrideguy: [ 'like leo', 'not on the shore', 'riding on a dolphin', 'poseidon', ], theswisspimp: [ 'the wizard of goats', 'a waffle slut', ], purple_pie6: [ 'a lil shit', 'a sadistic spanker', 'sir pie', ], txfires: 'ariel the angel', legscarflover: [ 'the superest of kids', 'unsuper of the super', 'the punisher\'s cleanup crew', 'kat\'s legs', 'the goof of balls', ], bendoverplease960: [ "kat's lil dinosaur", 'kat warrior', ], hippylabrat: 'the lorax', sexyblackguy3453: 'knightrider', // At a friend's request. I don't even know this guy. // My helpers get rewarded! jessjade95: 'a jedi guardian', violetrhayne: 'blessed by chaos', // These are just because I can :P kazuukii: 'not a kazoo', neo1003: 'the one', sayimthebest: 'not the best', */ sexievonkat: 'katnip', nicky7282: function() { var inCaseOfPrincess = 'the princess twilight sparkle'; if (CONFIG.royalty) { if (CONFIG.royalty > 0) --CONFIG.royalty; return inCaseOfPrincess; } var randInt = roll(100); // 1-100 inclusive if (randInt <= 95) return 'katnip club'; else return inCaseOfPrincess; }, }; function addClubbers(clubbers) { for (var index in clubbers) { NAME_TAGS[clubbers[index]] = 'katnip club'; } } addClubbers('afatani85 naughtyshadow pinkypirat jacobonya m_ninja_16 yeah009 zero_kool nite69 bigbaddab jfang1 boatrideguy theswisspimp purple_pie6 legscarflover bendoverplease960 lildiego25 nash3333 supermanall4u julianbrown alex__1982 zmoney0303'.split(/\s+/)); onMessage(function(msg) { if (NAME_TAGS[msg.user]) { var tag = NAME_TAGS[msg.user]; var chosen = ''; if (typeof tag == "function") chosen = NAME_TAGS[msg.user](); else if (Array.isArray(tag)) chosen = random(NAME_TAGS[msg.user]); else chosen = NAME_TAGS[msg.user]; msg.m = msg.m.prefix(chosen); msg.m = msg.m.split('$tag').join(chosen.toLowerCase()).split('$TAG').join(chosen.toUpperCase()).split('$Tag').join(chosen.toNounCase()); } return msg; }); registerCommands({ tags: { regex: /^tags/i, code: function(msg, match, mode, param, args) { if (mode.help) sendHelp("help.tags", msg.user); else if (mode.query) { var who = args.length ? args[0] : msg.user; if (NAME_TAGS[who]) { var userTags = NAME_TAGS[who]; if (typeof userTags == "function") sendStatusMessage(who + " {#tags.complex}", msg.user); else if (Array.isArray(userTags)) sendStatusMessage(who + ' has ' + counted(userTags.length, 'tag'), msg.user); else sendStatusMessage(who + " {#tags.one}", msg.user); } else sendStatusMessage(who + " {#tags.none}", msg.user); } else { var who = args.length ? args[0] : msg.user; if (NAME_TAGS[who]) { var userTags = NAME_TAGS[who]; if (typeof userTags == "function") sendStatusMessage(who + " {#tags.complex}", msg.user); else if (Array.isArray(userTags)) sendStatusMessage(who + " {#tags.many}: " + prettyJoin(userTags.toUpperCase(), '; '), msg.user); else sendStatusMessage(who + " {#tags.one}: " + userTags.toUpperCase(), msg.user); } else sendStatusMessage(who + " {#tags.none}", msg.user); } }, }, tag: { regex: /^tag\s+(\S+)\s+(.+)$/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.tag', msg.user); } var who = match[1]; var tag = match[2].toUpperCase(); if (NAME_TAGS[who]) { var ctag = NAME_TAGS[who]; if (typeof ctag == 'function') { sendErrorMessage('{#tagchg.complex}', msg.user); return; } if (Array.isArray(ctag)) NAME_TAGS[who].push(tag); else NAME_TAGS[who] = [ctag, tag]; } else { NAME_TAGS[who] = tag; } sendSuccessMessage('{#tagchg.success}', msg.user); }, check: isMaster, hidden: true, }, untag: { regex: /^untag\s+(\S+)\s+(.+)$/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.untag', msg.user); } var who = match[1]; var tag = match[2].toUpperCase(); if (NAME_TAGS[who]) { var ctag = NAME_TAGS[who]; if (typeof ctag == 'function') { sendErrorMessage("{#tagchg.complex}", msg.user); return; } if (Array.isArray(ctag)) { var index = ctag.indexOf(tag); if (index > -1) ctag.splice(index, 1); else { sendErrorMessage('{#tagchg.404}', msg.user); return; } } else delete ctag[who]; } else { sendErrorMessage(who + " {#tags.none}!", msg.user); return; } sendSuccessMessage('{#tagchg.success}', msg.user); }, check: isMaster, hidden: true, }, princess: { regex: /^(?:princess|royalty|alicorn|decrees)/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.princess', msg.user); return; } if (mode.query) sendStatusMessage('{#princess.name} level: ' + getPrincessPower(), msg.user); else { // Alright. I admit, this is flagrant abuse of bot programmer privilege. var noReduce = function() { sendErrorMessage('princess.noreduce', msg.user); }; var escalate = function(times) { CONFIG.royalty = times; var tell = '{#princess.name} ' + (CONFIG.royalty ? 'activated! Power level: ' + getPrincessPower() : 'deactivated!'); sendStatusMessage(tell, msg.user); debug("New {#princess.name} level: " + getPrincessPower() + ' (by ' + msg.user + ')'); }; var noEffect = function() { sendErrorMessage('There are already ' + getPrincessPower() + ' {#princess.decrees} remaining!', msg.user); }; var times = args.length > 0 ? toint(args[0]) : 1; if (isNaN(times)) times = 1; if (times < 0) times = -1; if (msg.in_fanclub && !msg.is_mod) { // Fans are allowed to escalate, but not reduce. if (CONFIG.royalty == -1 && times != -1) noReduce(); else if (times >= 0 && CONFIG.royalty > times) noReduce(); else if (times == CONFIG.royalty) noEffect(); else escalate(times); } else escalate(times); } }, check: isFan, }, klub: { regex: /^klub\s+(\S+)$/i, code: function(msg, match, mode, param, args) { if (mode.query) { if ((NAME_TAGS[match[1]] || '').toUpperCase() == 'KATNIP CLUB') sendStatus(match[1] + ' {#klub.yes}', msg.user); else sendStatus(match[1] + ' {#klub.no}', msg.user); } else { addClubbers([match[1]]); if (!mode.silent) { sendSuccess(match[1] + ' {klub.added}'); } } }, check: isMaster, hidden: true, }, }); // SERIOUS. PEOPLE. const NAME_COLS = { afatani85: '#54FF9F', naughtyshadow: '#FFB0FF', jfang1: '#63B8FF', zero_kool: '#34CCFF', m_ninja_16: '#BFEFFF', jacobonya: '#B0E0E6', boatrideguy: '#46E9EF', }; onMessage(function(msg) { if (NAME_COLS[msg.user]) msg.background = NAME_COLS[msg.user]; }); registerCommands({ colour: { regex: /^colour\s+(\S+)\s+(#[0-9a-f]{6})$/i, code: function(msg, match) { NAME_COLS[match[1]] = match[2]; }, check: isAdmin, hidden: true, }, }); // SO. SERIOUS. const MACROS = { rfl: 'Ravenclaw for life!', tfr: 'Tip for requests.', ns: 'No shouting.', ad: 'No advertising.', nice: 'Be respectful.', ew: 'That\'s disgusting.', tos: 'That\'s forbidden by CB\'s TOS.', hf: 'High five!', bb: 'Her name is Kat. Not baby, babe, or bb.', lenny: ':lefaceface', hug: ':ponyhugplz', mod: ':jappleclub', tkat: '@SexieVonKat', tnews: '@SexieNews', tns: '@IzarraKiania', cb: 'CB: it doesn\'t stand for ChaturBate - it stands for Complete Bullshit!', squirt: 'Kat CAN squirt, yes. She does not ALWAYS squirt. Don\'t demand that she squirt or you WILL be silenced.', pump: 'Kat is a type 1 diabetic. The thing on her arm is an insulin pump.', opinion: 'I must be coming down with Alzheimer\'s, cause I don\'t recall asking you anything.', grey: ':stfu-greys', gray: ':stfu-greys', poor: ':stfu-greys', stfu: ':cuppa-stfu', hp: 'Harry Potter', proof: "I DIDN'T DO IT NOBODY SAW ME DO IT YOU CAN'T PROVE ANYTHING", ignore: 'IGNOOOORE MEEE!', wtf: "SWEET MOTHER GRUB'S OOZING VESTIGIAL THIRD ORAL SPHINCTER", '420': "#BlazeIt", dw: 'da da da da, da da da da, da da da da, DA DA DA DA! ooooEEEEoooo!', pc: 'GLORIOUS PC GAMING MASTER RACE!', win: 'CHARLIE SHEEN', forever: ':goneforever', model: cb.room_slug, cuties: ':svk-aubri-twitters', no: 'oh noooooooo', }; const REWRITE = { 'COCKO BLOCKO': /\bcock\s*block\b/i, 'TACO BLOCKO': /\b(?:clam|box|twat|taco)\s*(?:stop|block|jam)\b/i, 'COCKO BLOCKO\'d': /\bcock\s*blocked\b/i, 'TACO BLOCKO\'d': /\b(?:clam|box|twat|taco)\s*(?:stop|block|jam)ed\b/i, '#YOLO': /\b(?:yolo|you\s*only\s*live\s*once)\b/i, '#SWAG': /\bswag\b/i, '#420': /\b420\b/i, 'SHENANIGANS' :/\bshenanigans\b/i, 'KATNIP CLUB': /\bkatnip\s+club\b/i, }; const REWRITE_COMPLEX = [ { // butt hurt -> <something funnier> from: /(butt|ass)\s*hurt/i, to: function() { var str = ''; // 11 options in the first part. 15 in the second. 165 total possibilities. str += random([ 'booty', 'fanny', 'ass', 'butt', 'heiney', 'patootie', 'derierre', 'keister', 'posterior', 'tush', 'rump', ]); str += ' '; str += random([ 'bothered', 'troubled', 'angered', 'hurt', 'perturbed', 'disgruntled', 'kicked', 'punted', 'pained', 'frazzled', 'ruffled', 'distressed', 'maimed', 'agitated', 'injured', ]); return str.toUpperCase(); }, }, ]; if (cb.room_slug == 'sexievonkat') REWRITE.Kat = /\bkay\b/i; // Fucking typos... onMessage(function(msg) { for (var macro in MACROS) msg.m = msg.m.split('$' + macro.toLowerCase()).join(MACROS[macro]); for (var repl in REWRITE) msg.m = msg.m.split(REWRITE[repl]).join(repl); for (var index in REWRITE_COMPLEX) { var from = REWRITE_COMPLEX[index].from; var to = REWRITE_COMPLEX[index].to(); msg.m = msg.m.split(from).join(to); } }); // Named timers const TIMERS = {}; const makeTimer = function(name, init, silentp) { if (!isNaN(parseInt(init, 10))) init = parseInt(init, 10); if (init < 1) return; TIMERS[name] = init; var lname = 'Timer "' + name + '"'; var decrement = function decrease() { if (!TIMERS[name]) { delete TIMERS[name]; return; } if (isNaN(TIMERS[name])) { delete TIMERS[name]; return; } if (TIMERS[name] < 0) { delete TIMERS[name]; return; } TIMERS[name] = TIMERS[name] - 1; var left = TIMERS[name]; if (left === 0) { sendStatusMessage(lname + ' {#timer.end}'); delete TIMERS[name]; return; } else if (left == 10) sendStatusMessage(lname + ' {#timer.left.ten}'); else if (left == 30) sendStatusMessage(lname + ' {#timer.left.thirty}'); else if (left % 60 === 0) { var tell = function() { sendStatusMessage(lname + ' has ' + getNiceTime(left) + ' left!'); }; var mins = left / 60; if (mins <= 2) tell(); else if (mins <= 30) if (mins % 5 === 0) tell(); else if (mins % 10 === 0) tell(); } cb.setTimeout(decrease, 1000); }; if (!silentp) sendStatusMessage(lname + ' {#timer.start}! ' + getNiceTime(TIMERS[name]) + ' remains!'); decrement(); }; registerCommands({ timer: { regex: /^timer/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.timer', msg.user); return; } else if (mode.query) { var list = []; for (var name in TIMERS) list.push(name + ': ' + getNiceTime(TIMERS[name])); if (list.length) sendStatusMessage("{#timer.list.prefix}:\n" + list.join("\n"), msg.user); else sendStatusMessage("{#timer.list.none}", msg.user); } else if (mode.negate) { if (TIMERS[param]) { sendSuccessMessage("Timer " + param + " killed!"); debug(msg.user + ' killed timer "' + param + '" with ' + getNiceTime(TIMERS[param]) + ' remaining'); TIMERS[param] = -1; } else sendErrorMessage("{#timer.404}", msg.user); } else { // Return value of this function doesn't matter. It's just a shortcut. if (args.length < 1) return sendErrorMessage("{#timer.new.syntax}", msg.user); var seconds = args[0]; if (seconds.charAt(seconds.length - 1) == 'm') seconds = toint(seconds.substr(0, seconds.length - 1)) * 60; else seconds = toint(seconds); var name = args.length > 1 ? param.substr(args[0].length).trim() : generateUUID(); makeTimer(name, seconds, mode.silent); debug(msg.user + ' started timer "' + name + '" with ' + getNiceTime(seconds)); } }, check: isFan, }, trename: { regex: /^trename/i, code: function(msg, match, mode, param, args) { if (mode.help) sendHelp('help.trename', msg.user); else if (param) { var parts = param.split('//'); if (parts.length < 2) return sendErrorMessage('timer.rename.syntax', msg.user); var from = parts[0].trim(); var to = parts[1].trim(); var time = TIMERS[from]; if (!time) return sendErrorMessage("{#timer.404}", msg.user); delete TIMERS[from]; makeTimer(to, time, true); sendStatusMessage('Timer "' + from + '" renamed to "' + to + '"'); } else sendErrorMessage("{#timer.rename.syntax}", msg.user); }, check: isFan, }, }); // Commands registerCommands({ addpoints: { regex: /^\/(?:add|give)points/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.addpoints', msg.user); return; } var amount = 0; var house = getHouse(msg.user); for (var index in args) { var arg = args[index]; if (arg.match(/^\d+$/)) amount = toint(arg); else house = arg; } house = getHouseCode(house); if (amount && house) { addPoints(house, amount, mode.silent); debug(msg.user + ' added points to ' + getHouseName(house)); } }, check: isAdmin, }, takepoints: { regex: /^\/takepoints/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.takepoints', msg.user); return; } var amount = 0; var house = getHouse(msg.user); for (var index in args) { var arg = args[index]; if (arg.match(/^\d+$/)) amount = 0 - toint(arg); else house = arg; } house = getHouseCode(house); if (amount && house) { addPoints(house, amount, mode.silent); debug(msg.user + ' took points from ' + getHouseName(house)); } }, check: isAdmin, }, setpoints: { regex: /^\/setpoints/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.setpoints', msg.user); return; } var amount = 0; var house = getHouse(msg.user); for (var index in args) { var arg = args[index]; if (arg.match(/^\d+$/)) amount = toint(arg); else house = arg; } house = getHouseCode(house); if (amount && house) { HOUSE_POINTS[house] = amount; debug(msg.user + ' set points for ' + getHouseName(house)); } }, check: isAdmin, }, sort: { regex: /^\/(?:sort|sethouse)/i, code: function(msg, match, mode, param, args) { if (isAdmin(msg)) { if (mode.help) { sendHelp('help.sort', msg.user); return; } if (args[0] == '**') { // Put each person into a random house, re-randomizing for each one var targets = []; var selection = { r: [], g: [], h: [], s: [] }; for (var index = 1; index < args.length; index++) if (args[index]) targets.push(args[index]); if (!targets.length) targets.push(msg.user); for (var index in targets) selection[getRandomHouse()].push(targets[index]); for (var code in selection) addToHouse(code, selection[code], msg.user, mode.silent); } else { var house = false; if (args[0] == '*') // Decide on a random house, then put everyone into it house = getRandomHouse(); else house = getHouseCode(args[0]); var targets = []; args = args.slice(1); for (var index in args) { var arg = args[index]; if (arg) targets.push(arg); } if (!targets.length) targets.push(msg.user); addToHouse(house, targets, msg.user, mode.silent); } } else { if (mode.help) { sendHelp('help.sort.fan', msg.user); return; } var house = ''; if (args[0] == '*') house = getRandomHouse(); else house = getHouseCode(args[0]); addToHouse(house, [msg.user], msg.user, mode.silent); } }, check: isFan, }, randhouse: { regex: /^\/randhouse/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.randhouse', msg.user); return; } var house = getHouseName(getRandomHouse()); if (mode.silent) sendStatusMessage('You picked ' + house + '!', msg.user); else sendStatusMessage(msg.user + ' asked for a random house and was told ' + house + '!'); }, }, gethouse: { regex: /^\/(?:get)?house/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.gethouse', msg.user); return; } var name = args[0] || msg.user; var house = getHouse(name); if (house) sendStatusMessage(name + ' is in ' + getHouseName(house) + ' House', msg.user); else sendStatusMessage(name + ' is a muggle', msg.user); } }, expel: { regex: /^\/expell?/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.expell', msg.user); return; } var targets = []; for (var index in args) targets.push(args[index]); if (!targets.length) targets.push(msg.user); pullFromHouse(targets, msg.user, mode.silent); debug(msg.user + ' expelled ' + prettyJoin(targets)); }, check: isAdmin, }, tellhouse: { regex: /^\/tellhouse/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.tellhouse', msg.user); return; } var target = args[0] || ''; if (!target) sendErrorMessage('tellhouse.syntax.target', msg.user); else { var content = param.substr(target.length).trim(); if (!content) sendErrorMessage('tellhouse.syntax.message'); else { var targeted = []; for (var i = 0; i < target.length; i++ ) { var code = target.charAt(i).toLowerCase(); if (code.match(/[rghs]/i)) { targeted.push(getHouseName(code)); for (var user in USER_HOUSES) { if (USER_HOUSES[user] && USER_HOUSES[user].substr(0, 1).toLowerCase() == code) sendStatusMessage(content, user); } } } debug(msg.user + " sent a message to " + prettyJoin(targeted)); } } }, check: isAdmin, }, points: { regex: /^\/(?:show|get)?points/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.points', msg.user); return; } announceHousePoints(msg.user); }, }, announce: { regex: /^\/announce/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.announce', msg.user); return; } announceHousePoints(); }, check: isFan, }, state: { regex: /^\/(?:serialize|getstate|savestate|save|state)/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.state', msg.user); return; } debug(msg.user + ' requested serialized state'); sendStatusMessage(backup(), msg.user); }, check: isFan, }, load: { regex: /^\/(?:unserialize|setstate|loadstate|load|restore)/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.load', msg.user); return; } debug(msg.user + ' attempting to load state'); if (restore(param)) sendSuccessMessage("State loaded!", msg.user); else sendErrorMessage('state.error.invalid', msg.user); }, check: isAdmin, }, cfg: { regex: /^\/cfg/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.cfg', msg.user); return; } debug(msg.user + ': ' + msg.m); if (!args.length) sendStatusMessage(JSON.stringify(CONFIG), msg.user); else if (args.length == 1) { var key = args[0]; var val = getProperty(CONFIG, key.clone(), false); if (val) sendSuccessMessage('CONFIG.' + key + '=' + val, msg.user); else sendErrorMessage('CONFIG.' + key + " doesn't exist", msg.user); } else if (args.length >= 2) { var key = args[0]; var old = getProperty(CONFIG, key.clone(), false); if (old) { var val = param.substr(args[0].length).trim(); if (val) { if (val.charAt(0) == '$' && CONFIG_CONSTANTS[val.substr(1)]) val = CONFIG_CONSTANTS[val.substr(1)]; val = val.trim(); if (!isNaN(toint(val))) val = toint(val); setProperty(CONFIG, key.clone(), val); sendSuccessMessage('CONFIG.' + key + ' updated (' + old + ' -> ' + val + ')', msg.user); } else sendErrorMessage('No valid value given', msg.user); } else sendErrorMessage('Invalid key: ' + key, msg.user); } }, check: isAdmin, hidden: true, }, silent: { regex: /^\/silent/i, code: function(msg, match, mode, param, args) { if (mode.help) { sendHelp('help.silent', msg.user); } else if (mode.query) { var status = ''; var toCheck = 'rghsn'.split(''); for (var index in toCheck) { var code = toCheck[index]; status += ', ' + (getHouseName(code) || 'Muggles') + ': ' + (CHATTY_HOUSES[code] ? 'on' : 'off'); } sendStatusMessage(status.substr(2), msg.user); } else if (mode.negate) { var hlist = []; var codes = param.replace(/\s+/g, '').replace(/[^rghsn]+/g, '').toLowerCase(); for (var index = 0; index < codes.length; ++index) { var code = codes.charAt(index); CHATTY_HOUSES[code] = true; hlist.push(getHouseName(code) || 'muggles'); } sendSuccessMessage('You unmuted ' + prettyJoin(hlist) + '!', msg.user); debug(msg.user + ' unmuted ' + prettyJoin(hlist)); } else { var hlist = []; var codes = param.replace(/\s+/g, '').replace(/[^rghsn]+/g, '').toLowerCase(); for (var index = 0; index < codes.length; ++index) { var code = codes.charAt(index); CHATTY_HOUSES[code] = false; hlist.push(getHouseName(code)); } sendSuccessMessage('You muted ' + prettyJoin(hlist) + '!', msg.user); debug(msg.user + ' muted ' + prettyJoin(hlist)); } }, check: isAdmin, }, updateusers: { regex: /^\/updateusers$/i, code: function(msg, match, mode, param, args) { debug("Adding default users to respective houses"); addToHouse('r', DEF_R.split(SPLITTER), cb.room_slug, true); addToHouse('g', DEF_G.split(SPLITTER), cb.room_slug, true); addToHouse('h', DEF_H.split(SPLITTER), cb.room_slug, true); addToHouse('s', DEF_S.split(SPLITTER), cb.room_slug, true); }, check: isAdmin, hidden: true, }, }); // Init cb.setTimeout(function() { debug("Setting broadcaster's house"); addToHouse(getHouseCode(cb.settings.model_house), [cb.room_slug], cb.room_slug, true); debug("Adding default users to Ravenclaw [" + prettyJoin(cb.settings.preset_r.split(SPLITTER)) + "]"); addToHouse('r', cb.settings.preset_r.split(SPLITTER), cb.room_slug, true); debug("Adding default users to Gryffindor [" + prettyJoin(cb.settings.preset_g.split(SPLITTER)) + "]"); addToHouse('g', cb.settings.preset_g.split(SPLITTER), cb.room_slug, true); debug("Adding default users to Hufflepuff [" + prettyJoin(cb.settings.preset_h.split(SPLITTER)) + "]"); addToHouse('h', cb.settings.preset_h.split(SPLITTER), cb.room_slug, true); debug("Adding default users to Slytherin [" + prettyJoin(cb.settings.preset_s.split(SPLITTER)) + "]"); addToHouse('s', cb.settings.preset_s.split(SPLITTER), cb.room_slug, true); debug("Registering announcement timer"); cb.setTimeout(repeatAnnouncement, 1000 * 60 * cb.settings.minutes_between_announcements); setup(); }, 1000);
© Copyright Camscaster.Com 2011- 2025. All Rights Reserved.