/master.js
JavaScript | 6583 lines | 5143 code | 861 blank | 579 comment | 598 complexity | 797b0b15e3dcd4e717ba93fe5e238fd6 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- var IO = window.IO = {
- //event handling
- events : {},
- preventDefault : false,
- //register for an event
- register : function ( name, fun, thisArg ) {
- if ( !this.events[name] ) {
- this.events[ name ] = [];
- }
- this.events[ name ].push({
- fun : fun,
- thisArg : thisArg,
- args : Array.prototype.slice.call( arguments, 3 )
- });
- return this;
- },
- unregister : function ( name, fun ) {
- if ( !this.events[name] ) {
- return this;
- }
- this.events[ name ] = this.events[ name ].filter(function ( obj ) {
- return obj.fun !== fun;
- });
- return this;
- },
- //fire event!
- fire : function ( name ) {
- this.preventDefault = false;
- if ( !this.events[name] ) {
- return;
- }
- var args = Array.prototype.slice.call( arguments, 1 ),
- that = this;
- this.events[ name ].forEach( fireEvent );
- function fireEvent( evt ) {
- var call = evt.fun.apply( evt.thisArg, evt.args.concat(args) );
- that.preventDefault = call === false;
- }
- },
- urlstringify : (function () {
- //simple types, for which toString does the job
- //used in singularStringify
- var simplies = { number : true, string : true, boolean : true };
- var singularStringify = function ( thing ) {
- if ( typeof thing in simplies ) {
- return encodeURIComponent( thing.toString() );
- }
- return '';
- };
- var arrayStringify = function ( key, array ) {
- key = singularStringify( key );
- return array.map(function ( val ) {
- return pair( key, val, true );
- }).join( '&' );
- };
- //returns a key=value pair. pass in dontStringifyKey so that, well, the
- // key won't be stringified (used in arrayStringify)
- var pair = function ( key, val, dontStringifyKey ) {
- if ( !dontStringifyKey ) {
- key = singularStringify( key );
- }
- return key + '=' + singularStringify( val );
- };
- return function ( obj ) {
- return Object.keys( obj ).map(function ( key ) {
- var val = obj[ key ];
- if ( Array.isArray(val) ) {
- return arrayStringify( key, val );
- }
- else {
- return pair( key, val );
- }
- }).join( '&' );
- };
- }()),
- loadScript : function ( url, cb ) {
- var script = document.createElement( 'script' );
- script.src = url;
- script.onload = cb;
- document.head.appendChild( script );
- }
- };
- IO.decodehtmlEntities = (function (){
- var entities; //will be filled in the following line
- entities = {"quot":"\"","amp":"&","apos":"'","lt":"<","gt":">","nbsp":" ","iexcl":"¡","cent":"¢","pound":"£","curren":"¤","yen":"¥","brvbar":"¦","sect":"§","uml":"¨","copy":"©","ordf":"ª","laquo":"«","not":"¬","reg":"®","macr":"¯","deg":"°","plusmn":"±","sup2":"²","sup3":"³","acute":"´","micro":"µ","para":"¶","middot":"·","cedil":"¸","sup1":"¹","ordm":"º","raquo":"»","frac14":"¼","frac12":"½","frac34":"¾","iquest":"¿","Agrave":"À","Aacute":"Á","Acirc":"Â","Atilde":"Ã","Auml":"Ä","Aring":"Å","AElig":"Æ","Ccedil":"Ç","Egrave":"È","Eacute":"É","Ecirc":"Ê","Euml":"Ë","Igrave":"Ì","Iacute":"Í","Icirc":"Î","Iuml":"Ï","ETH":"Ð","Ntilde":"Ñ","Ograve":"Ò","Oacute":"Ó","Ocirc":"Ô","Otilde":"Õ","Ouml":"Ö","times":"×","Oslash":"Ø","Ugrave":"Ù","Uacute":"Ú","Ucirc":"Û","Uuml":"Ü","Yacute":"Ý","THORN":"Þ","szlig":"ß","agrave":"à","aacute":"á","acirc":"â","atilde":"ã","auml":"ä","aring":"å","aelig":"æ","ccedil":"ç","egrave":"è","eacute":"é","ecirc":"ê","euml":"ë","igrave":"ì","iacute":"í","icirc":"î","iuml":"ï","eth":"ð","ntilde":"ñ","ograve":"ò","oacute":"ó","ocirc":"ô","otilde":"õ","ouml":"ö","divide":"÷","oslash":"ø","ugrave":"ù","uacute":"ú","ucirc":"û","uuml":"ü","yacute":"ý","thorn":"þ","yuml":"ÿ","OElig":"Œ","oelig":"œ","Scaron":"Š","scaron":"š","Yuml":"Ÿ","fnof":"ƒ","circ":"ˆ","tilde":"˜","Alpha":"Α","Beta":"Β","Gamma":"Γ","Delta":"Δ","Epsilon":"Ε","Zeta":"Ζ","Eta":"Η","Theta":"Θ","Iota":"Ι","Kappa":"Κ","Lambda":"Λ","Mu":"Μ","Nu":"Ν","Xi":"Ξ","Omicron":"Ο","Pi":"Π","Rho":"Ρ","Sigma":"Σ","Tau":"Τ","Upsilon":"Υ","Phi":"Φ","Chi":"Χ","Psi":"Ψ","Omega":"Ω","alpha":"α","beta":"β","gamma":"γ","delta":"δ","epsilon":"ε","zeta":"ζ","eta":"η","theta":"θ","iota":"ι","kappa":"κ","lambda":"λ","mu":"μ","nu":"ν","xi":"ξ","omicron":"ο","pi":"π","rho":"ρ","sigmaf":"ς","sigma":"σ","tau":"τ","upsilon":"υ","phi":"φ","chi":"χ","psi":"ψ","omega":"ω","thetasym":"ϑ","upsih":"ϒ","piv":"ϖ","ensp":" ","emsp":" ","thinsp":" ","ndash":"–","mdash":"—","lsquo":"‘","rsquo":"’","sbquo":"‚","ldquo":"“","rdquo":"”","bdquo":"„","dagger":"†","Dagger":"‡","bull":"•","hellip":"…","permil":"‰","prime":"′","Prime":"″","lsaquo":"‹","rsaquo":"›","oline":"‾","frasl":"⁄","euro":"€","image":"ℑ","weierp":"℘","real":"ℜ","trade":"™","alefsym":"ℵ","larr":"←","uarr":"↑","rarr":"→","darr":"↓","harr":"↔","crarr":"↵","lArr":"⇐","uArr":"⇑","rArr":"⇒","dArr":"⇓","hArr":"⇔","forall":"∀","part":"∂","exist":"∃","empty":"∅","nabla":"∇","isin":"∈","notin":"∉","ni":"∋","prod":"∏","sum":"∑","minus":"−","lowast":"∗","radic":"√","prop":"∝","infin":"∞","ang":"∠","and":"∧","or":"∨","cap":"∩","cup":"∪","int":"∫","there4":"∴","sim":"∼","cong":"≅","asymp":"≈","ne":"≠","equiv":"≡","le":"≤","ge":"≥","sub":"⊂","sup":"⊃","nsub":"⊄","sube":"⊆","supe":"⊇","oplus":"⊕","otimes":"⊗","perp":"⊥","sdot":"⋅","lceil":"⌈","rceil":"⌉","lfloor":"⌊","rfloor":"⌋","lang":"〈","rang":"〉","loz":"◊","spades":"♠","clubs":"♣","hearts":"♥","diams":"♦"};
- /*
- & -all entities start with &
- (
- # -charcode entities also have a #
- x? -hex charcodes
- )?
- [\w;] -now the entity (alphanumeric, separated by ;)
- +? -capture em until there aint no more (don't get the trailing ;)
- ; -trailing ;
- */
- var entityRegex = /&(#x?)?[\w;]+?;/g;
- var replaceEntities = function ( entities ) {
- //remove the & and split into each separate entity
- return entities.slice( 1 ).split( ';' ).map( decodeEntity ).join( '' );
- };
- var decodeEntity = function ( entity ) {
- //starts with a #, it's charcode
- if ( entity[0] === '#' ) {
- return decodeCharcodeEntity( entity );
- }
- return entities[ entity ] || entity;
- };
- var decodeCharcodeEntity = function ( entity ) {
- //remove the # prefix
- entity = entity.slice( 1 );
- var cc;
- //hex entities
- if ( entity[0] === 'x' ) {
- cc = parseInt( entity.slice(1), 16 );
- }
- //decimal entities
- else {
- cc = parseInt( entity, 10 );
- }
- return String.fromCharCode( cc );
- };
- return function ( html ) {
- return html.replace( entityRegex, replaceEntities );
- };
- }());
- //build IO.in and IO.out
- [ 'in', 'out' ].forEach(function ( dir ) {
- var fullName = dir + 'put';
- IO[ dir ] = {
- buffer : [],
- receive : function ( obj ) {
- IO.fire( 'receive' + fullName, obj );
- if ( IO.preventDefault ) {
- return this;
- }
- this.buffer.push( obj );
- return this;
- },
- //unload the next item in the buffer
- tick : function () {
- if ( this.buffer.length ) {
- IO.fire( fullName, this.buffer.shift() );
- }
- return this;
- },
- //unload everything in the buffer
- flush : function () {
- IO.fire( 'before' + fullName );
- if ( !this.buffer.length ) {
- return this;
- }
- var i = this.buffer.length;
- while( i --> 0 ) {
- this.tick();
- }
- IO.fire( 'after' + fullName );
- this.buffer = [];
- return this;
- }
- };
- });
- IO.xhr = function ( params ) {
- //merge in the defaults
- params = Object.merge({
- method : 'GET',
- headers : {},
- complete : function (){}
- }, params );
- params.headers = Object.merge({
- 'Content-Type' : 'application/x-www-form-urlencoded'
- }, params.headers );
- //if the data is an object, and not a fakey String object, dress it up
- if ( typeof params.data === 'object' && !params.data.charAt ) {
- params.data = IO.urlstringify( params.data );
- }
- var xhr = new XMLHttpRequest();
- xhr.open( params.method, params.url );
- xhr.addEventListener( 'readystatechange', function () {
- if ( xhr.readyState === 4 ) {
- params.complete.call(
- params.thisArg, xhr.responseText, xhr
- );
- }
- });
- Object.keys( params.headers ).forEach(function ( header ) {
- xhr.setRequestHeader( header, params.headers[header] );
- });
- xhr.send( params.data );
- return xhr;
- };
- IO.jsonp = function ( opts ) {
- opts.data = opts.data || {};
- opts.jsonpName = opts.jsonpName || 'jsonp';
- var script = document.createElement( 'script' ),
- semiRandom;
- do {
- semiRandom = 'IO' + ( Date.now() * Math.ceil(Math.random()) );
- } while ( window[semiRandom] );
- //this is the callback function, called from the "jsonp file"
- window[ semiRandom ] = function () {
- opts.fun.apply( opts.thisArg, arguments );
- //cleanup
- delete window[ semiRandom ];
- script.parentNode.removeChild( script );
- };
- //add the jsonp parameter to the data we're sending
- opts.data[ opts.jsonpName ] = semiRandom;
- //start preparing the url to be sent
- if ( opts.url.indexOf('?') === -1 ) {
- opts.url += '?';
- }
- //append the data to be sent, in string form, to the url
- opts.url += this.urlstringify( opts.data );
- script.src = opts.url;
- document.head.appendChild( script );
- };
- //generic, pre-made calls to be used inside commands
- IO.jsonp.ddg = function ( query, cb ) {
- IO.jsonp({
- url : 'http://api.duckduckgo.com/',
- jsonpName : 'callback',
- data : {
- format : 'json',
- q : query
- },
- fun : cb
- });
- };
- IO.jsonp.google = function ( query, cb ) {
- IO.jsonp({
- url : 'http://ajax.googleapis.com/ajax/services/search/web',
- jsonpName : 'callback',
- data : {
- v : '1.0',
- q : query
- },
- fun : cb
- });
- };
- ;
- (function () {
- "use strict";
- var bot = window.bot = {
- invocationPattern : '!!',
- commandRegex : /^\/\s*([\w\-]+)(?:\s(.+))?$/,
- commands : {}, //will be filled as needed
- commandDictionary : null, //it's null at this point, won't be for long
- listeners : [],
- info : {
- invoked : 0,
- learned : 0,
- forgotten : 0,
- start : new Date,
- },
- parseMessage : function ( msgObj ) {
- if ( !this.validateMessage(msgObj) ) {
- bot.log( msgObj, 'parseMessage invalid' );
- return;
- }
- var msg = this.prepareMessage( msgObj ),
- id = msg.get( 'user_id' );
- bot.log( msg, 'parseMessage valid' );
- if ( this.banlist.contains(id) ) {
- bot.log( msgObj, 'parseMessage banned' );
- //tell the user he's banned only if he hasn't already been told
- if ( !this.banlist[id].told ) {
- msg.reply( 'You iz in mindjail' );
- this.banlist[ id ].told = true;
- }
- return;
- }
- try {
- //it wants to execute some code
- if ( msg.startsWith('>') ) {
- this.eval( msg );
- }
- //it's a command
- else if ( msg.startsWith('/') ) {
- this.parseCommand( msg );
- }
- //see if some hobo listener wants this
- else if ( !this.callListeners(msg) ) {
- //no listener fancied the message. this is the last frontier,
- // so just give up in a classy, dignified way
- msg.reply(
- 'Y U NO MAEK SENSE!? Could not understand `' + msg + '`' );
- }
- }
- catch ( e ) {
- var err = 'Could not process input. Error: ' + e.message;
- if ( e.lineNumber ) {
- err += ' on line ' + e.lineNumber;
- }
- //column isn't part of ordinary errors, it's set in custom ones
- if ( e.column ) {
- err += ' on column ' + e.column;
- }
- msg.directreply( err );
- //make sure we have it somewhere
- console.dir( e );
- }
- finally {
- this.info.invoked += 1;
- }
- },
- prepareMessage : function ( msgObj ) {
- msgObj = this.adapter.transform( msgObj );
- var msg = IO.decodehtmlEntities( msgObj.content );
- return this.Message(
- msg.slice( this.invocationPattern.length ).trim(),
- msgObj );
- },
- parseCommand : function ( msg ) {
- bot.log( msg, 'parseCommand input' );
- var commandParts = this.commandRegex.exec( msg );
- if ( !commandParts ) {
- msg.reply( 'Invalid command ' + msg );
- return;
- }
- bot.log( commandParts, 'parseCommand matched' );
- var commandName = commandParts[ 1 ].toLowerCase(),
- cmdObj = this.getCommand( commandName );
- if ( this.personality.check(commandName) ) {
- this.personality.command();
- }
- //see if there was some error fetching the command
- if ( cmdObj.error ) {
- msg.reply( cmdObj.error );
- return;
- }
- if ( !cmdObj.canUse(msg.get('user_id')) ) {
- msg.reply([
- 'You do not have permission to use the command ' + commandName,
- "I'm afraid I can't let you do that, " + msg.get('user_name')
- ].random());
- return;
- }
- bot.log( cmdObj, 'parseCommand calling' );
- var args = this.Message(
- msg.replace(/^\/\s*/, '').slice( commandName.length ).trim(),
- msg.get()
- ),
- //it always amazed me how, in dynamic systems, the trigger of the
- // actions is always a small, nearly unidentifiable line
- //this line right here activates a command
- res = cmdObj.exec( args );
- if ( res ) {
- msg.reply( res );
- }
- },
- validateMessage : function ( msgObj ) {
- var msg = msgObj.content.trim();
- //all we really care about
- return msg.startsWith( this.invocationPattern );
- },
- addCommand : function ( cmd ) {
- if ( !cmd.exec || !cmd.del ) {
- cmd = this.Command( cmd );
- }
- if ( cmd.learned ) {
- this.info.learned += 1;
- }
- cmd.invoked = 0;
- this.commands[ cmd.name ] = cmd;
- this.commandDictionary.trie.add( cmd.name );
- },
- //gee, I wonder what this will return?
- commandExists : function ( cmdName ) {
- return this.commands.hasOwnProperty( cmdName );
- },
- //if a command named cmdName exists, it returns that command object
- //otherwise, it returns an object with an error message property
- getCommand : function ( cmdName ) {
- if ( this.commandExists(cmdName) ) {
- return this.commands[ cmdName ];
- }
- //set the error margin according to the length
- this.commandDictionary.maxCost = Math.floor(
- cmdName.length / 5 + 1 );
- var msg = 'Command ' + cmdName + ' does not exist.',
- //find commands resembling the one the user entered
- guesses = this.commandDictionary.search( cmdName );
- //resembling command(s) found, add them to the error message
- if ( guesses.length ) {
- msg += ' Did you mean: ' + guesses.join( ', ' );
- }
- return { error : msg };
- },
- //the function women think is lacking in men
- listen : function ( regex, fun, thisArg ) {
- if ( Array.isArray(regex) ) {
- regex.forEach(function ( reg ) {
- this.listen( reg, fun, thisArg );
- }, this);
- }
- else {
- this.listeners.push({
- pattern : regex,
- fun : fun,
- thisArg: thisArg
- });
- }
- },
- callListeners : function ( msg ) {
- var fired = false;
- this.listeners.forEach(function ( listener ) {
- var match = msg.exec( listener.pattern ), resp;
- if ( match ) {
- resp = listener.fun.call( listener.thisArg, msg );
- bot.log( match, resp );
- if ( resp ) {
- msg.reply( resp );
- }
- fired = resp !== false;
- }
- });
- return fired;
- },
- stoplog : false,
- log : function () {
- if ( !this.stoplog ) {
- console.log.apply( console, arguments );
- }
- },
- stop : function () {
- this.stopped = true;
- },
- continue : function () {
- this.stopped = false;
- }
- };
- //execute arbitrary js code in a relatively safe environment
- bot.eval = (function () {
- window.URL = window.URL || window.webkitURL || window.mozURL || null;
- //translation tool: https://tinker.io/b2ff5
- var worker_code = atob( 'dmFyIGdsb2JhbCA9IHRoaXM7CgovKm1vc3QgZXh0cmEgZnVuY3Rpb25zIGNvdWxkIGJlIHBvc3NpYmx5IHVuc2FmZSovCnZhciB3aGl0ZXkgPSB7CgknQXJyYXknICAgICAgICAgICAgICA6IDEsCgknQm9vbGVhbicgICAgICAgICAgICA6IDEsCgknRGF0ZScgICAgICAgICAgICAgICA6IDEsCgknRXJyb3InICAgICAgICAgICAgICA6IDEsCgknRXZhbEVycm9yJyAgICAgICAgICA6IDEsCgknRnVuY3Rpb24nICAgICAgICAgICA6IDEsCgknSW5maW5pdHknICAgICAgICAgICA6IDEsCgknSlNPTicgICAgICAgICAgICAgICA6IDEsCgknTWF0aCcgICAgICAgICAgICAgICA6IDEsCgknTmFOJyAgICAgICAgICAgICAgICA6IDEsCgknTnVtYmVyJyAgICAgICAgICAgICA6IDEsCgknT2JqZWN0JyAgICAgICAgICAgICA6IDEsCgknUmFuZ2VFcnJvcicgICAgICAgICA6IDEsCgknUmVmZXJlbmNlRXJyb3InICAgICA6IDEsCgknUmVnRXhwJyAgICAgICAgICAgICA6IDEsCgknU3RyaW5nJyAgICAgICAgICAgICA6IDEsCgknU3ludGF4RXJyb3InICAgICAgICA6IDEsCgknVHlwZUVycm9yJyAgICAgICAgICA6IDEsCgknVVJJRXJyb3InICAgICAgICAgICA6IDEsCgknYXRvYicgICAgICAgICAgICAgICA6IDEsCgknYnRvYScgICAgICAgICAgICAgICA6IDEsCgknZGVjb2RlVVJJJyAgICAgICAgICA6IDEsCgknZGVjb2RlVVJJQ29tcG9uZW50JyA6IDEsCgknZW5jb2RlVVJJJyAgICAgICAgICA6IDEsCgknZW5jb2RlVVJJQ29tcG9uZW50JyA6IDEsCgknZXZhbCcgICAgICAgICAgICAgICA6IDEsCgknZ2xvYmFsJyAgICAgICAgICAgICA6IDEsCgknaXNGaW5pdGUnICAgICAgICAgICA6IDEsCgknaXNOYU4nICAgICAgICAgICAgICA6IDEsCgknb25tZXNzYWdlJyAgICAgICAgICA6IDEsCgkncGFyc2VGbG9hdCcgICAgICAgICA6IDEsCgkncGFyc2VJbnQnICAgICAgICAgICA6IDEsCgkncG9zdE1lc3NhZ2UnICAgICAgICA6IDEsCgknc2VsZicgICAgICAgICAgICAgICA6IDEsCgkndW5kZWZpbmVkJyAgICAgICAgICA6IDEsCgknd2hpdGV5JyAgICAgICAgICAgICA6IDEsCgoJLyogdHlwZWQgYXJyYXlzIGFuZCBzaGl0ICovCgknQXJyYXlCdWZmZXInICAgICAgIDogMSwKCSdCbG9iJyAgICAgICAgICAgICAgOiAxLAoJJ0Zsb2F0MzJBcnJheScgICAgICA6IDEsCgknRmxvYXQ2NEFycmF5JyAgICAgIDogMSwKCSdJbnQ4QXJyYXknICAgICAgICAgOiAxLAoJJ0ludDE2QXJyYXknICAgICAgICA6IDEsCgknSW50MzJBcnJheScgICAgICAgIDogMSwKCSdVaW50OEFycmF5JyAgICAgICAgOiAxLAoJJ1VpbnQxNkFycmF5JyAgICAgICA6IDEsCgknVWludDMyQXJyYXknICAgICAgIDogMSwKCSdVaW50OENsYW1wZWRBcnJheScgOiAxLAoKCS8qCgl0aGVzZSBwcm9wZXJ0aWVzIGFsbG93IEZGIHRvIGZ1bmN0aW9uLiB3aXRob3V0IHRoZW0sIGEgZnVja2Zlc3Qgb2YKCWluZXhwbGljYWJsZSBlcnJvcnMgZW51c2VzLiB0b29rIG1lIGFib3V0IDQgaG91cnMgdG8gdHJhY2sgdGhlc2UgZnVja2VycwoJZG93bi4KCWZ1Y2sgaGVsbCBpdCBpc24ndCBmdXR1cmUtcHJvb2YsIGJ1dCB0aGUgZXJyb3JzIHRocm93biBhcmUgdW5jYXRjaGFibGUKCWFuZCB1bnRyYWNhYmxlLiBzbyBhIGhlYWRzLXVwLiBlbmpveSwgZnV0dXJlLW1lIQoJKi8KCSdET01FeGNlcHRpb24nIDogMSwKCSdFdmVudCcgICAgICAgIDogMSwKCSdNZXNzYWdlRXZlbnQnIDogMQp9OwoKWyBnbG9iYWwsIGdsb2JhbC5fX3Byb3RvX18gXS5mb3JFYWNoKGZ1bmN0aW9uICggb2JqICkgewoJT2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMoIG9iaiApLmZvckVhY2goZnVuY3Rpb24oIHByb3AgKSB7CgkJaWYoICF3aGl0ZXkuaGFzT3duUHJvcGVydHkoIHByb3AgKSApIHsKCQkJZGVsZXRlIG9ialsgcHJvcCBdOwoJCX0KCX0pOwp9KTsKCk9iamVjdC5kZWZpbmVQcm9wZXJ0eSggQXJyYXkucHJvdG90eXBlLCAnam9pbicsIHsKCXdyaXRhYmxlOiBmYWxzZSwKCWNvbmZpZ3VyYWJsZTogZmFsc2UsCgllbnVtcmFibGU6IGZhbHNlLAoKCXZhbHVlOiAoZnVuY3Rpb24gKCBvbGQgKSB7CgkJcmV0dXJuIGZ1bmN0aW9uICggYXJnICkgewoJCQlpZiAoIHRoaXMubGVuZ3RoID4gNTAwIHx8IChhcmcgJiYgYXJnLmxlbmd0aCA+IDUwMCkgKSB7CgkJCQl0aHJvdyAnRXhjZXB0aW9uOiB0b28gbWFueSBpdGVtcyc7CgkJCX0KCgkJCXJldHVybiBvbGQuYXBwbHkoIHRoaXMsIGFyZ3VtZW50cyApOwoJCX07Cgl9KCBBcnJheS5wcm90b3R5cGUuam9pbiApKQp9KTsKCihmdW5jdGlvbigpewoJInVzZSBzdHJpY3QiOwoKCXZhciBjb25zb2xlID0gewoJCV9pdGVtcyA6IFtdLAoJCWxvZyA6IGZ1bmN0aW9uKCkgewoJCQljb25zb2xlLl9pdGVtcy5wdXNoLmFwcGx5KCBjb25zb2xlLl9pdGVtcywgYXJndW1lbnRzICk7CgkJfQoJfTsKCXZhciBwID0gY29uc29sZS5sb2cuYmluZCggY29uc29sZSApOwoKCWZ1bmN0aW9uIGV4ZWMgKCBjb2RlICkgewoJCXZhciByZXN1bHQ7CgkJdHJ5IHsKCQkJcmVzdWx0ID0gZXZhbCggJyJ1c2Ugc3RyaWN0Ijt1bmRlZmluZWQ7XG4nICsgY29kZSApOwoJCX0KCQljYXRjaCAoIGUgKSB7CgkJCXJlc3VsdCA9IGUudG9TdHJpbmcoKTsKCQl9CgoJCXJldHVybiByZXN1bHQ7Cgl9CgoJZ2xvYmFsLm9ubWVzc2FnZSA9IGZ1bmN0aW9uICggZXZlbnQgKSB7CgkJdmFyIGpzb25TdHJpbmdpZnkgPSBKU09OLnN0cmluZ2lmeSwgLypiYWNrdXAqLwoJCQlyZXN1bHQgPSBleGVjKCBldmVudC5kYXRhICk7CgoJCS8qSlNPTiBkb2VzIG5vdCBsaWtlIGFueSBvZiB0aGUgZm9sbG93aW5nKi8KCQl2YXIgc3RydW5nID0gewoJCQlGdW5jdGlvbiAgOiB0cnVlLCBFcnJvciAgOiB0cnVlLAoJCQlVbmRlZmluZWQgOiB0cnVlLCBSZWdFeHAgOiB0cnVlCgkJfTsKCQl2YXIgc2hvdWxkX3N0cmluZyA9IGZ1bmN0aW9uICggdmFsdWUgKSB7CgkJCXZhciB0eXBlID0gKCB7fSApLnRvU3RyaW5nLmNhbGwoIHZhbHVlICkuc2xpY2UoIDgsIC0xICk7CgoJCQlpZiAoIHR5cGUgaW4gc3RydW5nICkgewoJCQkJcmV0dXJuIHRydWU7CgkJCX0KCQkJLypuZWl0aGVyIGRvZXMgaXQgZmVlbCBjb21wYXNzaW9uYXRlIGFib3V0IE5hTiBvciBJbmZpbml0eSovCgkJCXJldHVybiBpc05hTiggdmFsdWUgKSB8fCAhaXNGaW5pdGUoIHZhbHVlICk7CgkJfTsKCgkJdmFyIHJldml2ZXIgPSBmdW5jdGlvbiAoIGtleSwgdmFsdWUgKSB7CgkJCXZhciBvdXRwdXQ7CgoJCQlpZiAoIHNob3VsZF9zdHJpbmcodmFsdWUpICkgewoJCQkJb3V0cHV0ID0gJycgKyB2YWx1ZTsKCQkJfQoJCQllbHNlIHsKCQkJCW91dHB1dCA9IHZhbHVlOwoJCQl9CgoJCQlyZXR1cm4gb3V0cHV0OwoJCX07CgoJCXBvc3RNZXNzYWdlKHsKCQkJYW5zd2VyIDoganNvblN0cmluZ2lmeSggcmVzdWx0LCByZXZpdmVyICksCgkJCWxvZyAgICA6IGpzb25TdHJpbmdpZnkoIGNvbnNvbGUuX2l0ZW1zLCByZXZpdmVyICkuc2xpY2UoIDEsIC0xICkKCQl9KTsKCX07Cn0pKCk7Cg==' );
- var blob = new Blob( [worker_code], { type : 'application/javascript' } ),
- code_url = window.URL.createObjectURL( blob );
- return function ( msg ) {
- var timeout,
- worker = new Worker( code_url );
- worker.onmessage = function ( evt ) {
- finish( dressUpAnswer(evt.data) );
- };
- worker.onerror = function ( error ) {
- finish( error.toString() );
- };
- //and it all boils down to this...
- worker.postMessage( msg.content.replace(/^>/, '') );
- timeout = window.setTimeout(function() {
- finish( 'Maximum execution time exceeded' );
- }, 100 );
- function finish ( result ) {
- clearTimeout( timeout );
- worker.terminate();
- msg.directreply( result );
- }
- };
- function dressUpAnswer ( answerObj ) {
- console.log( answerObj, 'eval answerObj' );
- var answer = answerObj.answer,
- log = answerObj.log,
- result;
- result = snipAndCodify( answer );
- if ( log && log.length ) {
- result += ' Logged: ' + snipAndCodify( log );
- }
- return result;
- }
- function snipAndCodify ( str ) {
- var ret;
- if ( str.length > 400 ) {
- ret = '`' + str.slice(0, 400) + '` (snip)';
- }
- else {
- ret = '`' + str +'`';
- }
- return ret;
- }
- }());
- bot.banlist = JSON.parse( localStorage.bot_ban || '{}' );
- if ( Array.isArray(bot.banlist) ) {
- bot.banlist = bot.banlist.reduce(function ( ret, id ) {
- ret[ id ] = { told : false };
- return ret;
- }, {});
- }
- bot.banlist.contains = function ( id ) {
- return this.hasOwnProperty( id );
- };
- bot.banlist.add = function ( id ) {
- this[ id ] = { told : false };
- this.save();
- };
- bot.banlist.remove = function ( id ) {
- if ( this.contains(id) ) {
- delete this[ id ];
- this.save();
- }
- };
- bot.banlist.save = function () {
- //JSON.stringify ignores functions
- localStorage.bot_ban = JSON.stringify( this );
- };
- //some sort of pseudo constructor
- bot.Command = function ( cmd ) {
- cmd.name = cmd.name.toLowerCase();
- cmd.permissions = cmd.permissions || {};
- cmd.permissions.use = cmd.permissions.use || 'ALL';
- cmd.permissions.del = cmd.permissions.del || 'NONE';
- cmd.description = cmd.description || '';
- cmd.creator = cmd.creator || 'God';
- cmd.invoked = 0;
- //make canUse and canDel
- [ 'Use', 'Del' ].forEach(function ( perm ) {
- var low = perm.toLowerCase();
- cmd[ 'can' + perm ] = function ( usrid ) {
- var canDo = this.permissions[ low ];
- return canDo === 'ALL' || canDo !== 'NONE' &&
- canDo.indexOf( usrid ) > -1;
- };
- });
- cmd.exec = function () {
- this.invoked += 1;
- return this.fun.apply( this.thisArg, arguments );
- };
- cmd.del = function () {
- bot.info.forgotten += 1;
- delete bot.commands[ cmd.name ];
- };
- return cmd;
- };
- //a normally priviliged command which can be executed if enough people use it
- bot.CommunityCommand = function ( command, req ) {
- var cmd = this.Command( command ),
- used = {},
- old_execute = cmd.exec,
- old_canUse = cmd.canUse;
- req = req || 2;
- cmd.canUse = function () {
- return true;
- };
- cmd.exec = function ( msg ) {
- var err = register( msg.get('user_id') );
- if ( err ) {
- bot.log( err );
- return err;
- }
- return old_execute.apply( cmd, arguments );
- };
- return cmd;
- //once again, a switched return statement truthy means a message, falsy
- // means to go on ahead
- function register ( usrid ) {
- if ( old_canUse.call(cmd, usrid) ) {
- return false;
- }
- clean();
- var count = Object.keys( used ).length,
- needed = req - count;
- bot.log( used, count, req );
- if ( usrid in used ) {
- return 'Already registered; still need {0} more'.supplant( needed );
- }
- else if ( needed > 0 ) {
- used[ usrid ] = new Date;
- return 'Registered; need {0} more to execute'.supplant( needed-1 );
- }
- bot.log( 'should execute' );
- return false; //huzzah!
- }
- function clean () {
- var tenMinsAgo = new Date;
- tenMinsAgo.setMinutes( tenMinsAgo.getMinutes() - 10 );
- Object.keys( used ).reduce( rm, used );
- function rm ( ret, key ) {
- if ( ret[key] < tenMinsAgo ) {
- delete ret[ key ];
- }
- return ret;
- }
- }
- };
- bot.Message = function ( text, msgObj ) {
- //"casting" to object so that it can be extended with cool stuff and
- // still be treated like a string
- var ret = Object( text );
- ret.content = text;
- var deliciousObject = {
- send : function ( resp ) {
- bot.adapter.out.add( resp, msgObj.room_id );
- },
- reply : function ( resp ) {
- var prefix = bot.adapter.reply( msgObj.user_name );
- this.send( prefix + ' ' + resp );
- },
- directreply : function ( resp ) {
- var prefix = bot.adapter.directreply( msgObj.message_id );
- this.send( prefix + ' ' + resp );
- },
- //parse() parses the original message
- //parse( true ) also turns every match result to a Message
- //parse( msgToParse ) parses msgToParse
- //parse( msgToParse, true ) combination of the above
- parse : function ( msg, map ) {
- if ( !!msg === msg ) {
- map = msg;
- msg = text;
- }
- var parsed = bot.parseCommandArgs( msg || text );
- if ( !map ) {
- return parsed;
- }
- return parsed.map(function ( part ) {
- return bot.Message( part, msgObj );
- });
- },
- //execute a regexp against the text, saving it inside the object
- exec : function ( regexp ) {
- var match = regexp.exec( text );
- this.matches = match ? match : [];
- return match;
- },
- findUserid : function ( username ) {
- var users = [].slice.call( document
- .getElementById( 'sidebar' )
- .getElementsByClassName( 'user-container' )
- );
- //grab a list of user ids
- var ids = users.map(function ( container ) {
- return container.id.match( /\d+/ )[ 0 ];
- });
- //and a list of their names
- var names = users.map(function ( container ) {
- return container.getElementsByTagName( 'img' )[ 0 ]
- .title.toLowerCase().replace( /\s/g, '' );
- });
- var idx = names.indexOf(
- username.toString().toLowerCase().replace( /\s/g, '' ) );
- if ( idx < 0 ) {
- return -1;
- }
- return Number( ids[idx] );
- }.memoize(),
- codify : bot.adapter.codify.bind( bot.adapter ),
- escape : bot.adapter.escape.bind( bot.adapter ),
- link : bot.adapter.link.bind( bot.adapter ),
- //retrieve a value from the original message object, or if no argument
- // provided, the msgObj itself
- get : function ( what ) {
- if ( !what ) {
- return msgObj;
- }
- return msgObj[ what ];
- },
- set : function ( what, val ) {
- return msgObj[ what ] = val;
- }
- };
- Object.keys( deliciousObject ).forEach(function ( key ) {
- ret[ key ] = deliciousObject[ key ];
- });
- return ret;
- };
- bot.owners = [
- 94197, //Andy E
- 154112, //Simon Sarris
- 170224, //Ivo Wetzel
- 263525, //dystroy
- 322395, //Loktar
- 363815, //Ryan Kinal
- 401137, //Amaan Cheval
- 418183, //Octavian Damiean
- 419970, //Raynos
- 561731, //Neal
- 617762, //Zirak
- 727208, //tereško
- 809950, //GNi33
- 829835, //rlemon
- 851498, //Florian Margaine
- 855760, //Darkyen
- 995876, //Esailija
- 1078067, //copy
- 1216976, //SomeKittens
- 1386166, //phenomnomnominal
- 1386886, //jAndy
- 1839506 //SO ChatBot
- ];
- bot.isOwner = function ( usrid ) {
- return this.owners.indexOf( usrid ) > -1;
- };
- IO.register( 'input', bot.parseMessage, bot );
- bot.beatInterval = 5000; //once every 5 seconds is Good Enough ™
- (function beat () {
- bot.beat = setTimeout(function () {
- IO.fire( 'heartbeat' );
- beat();
- }, bot.beatInterval );
- }())
- //small utility functions
- Object.merge = function () {
- return [].reduce.call( arguments, function ( ret, merger ) {
- Object.keys( merger ).forEach(function ( key ) {
- ret[ key ] = merger[ key ];
- });
- return ret;
- }, {} );
- };
- String.prototype.indexesOf = function ( str, fromIndex ) {
- //since we also use index to tell indexOf from where to begin, and since
- // telling it to begin from where it found the match will cause it to just
- // match it again and again, inside the indexOf we do `index + 1`
- // to compensate for that 1, we need to subtract 1 from the original
- // starting position
- var index = ( fromIndex || 0 ) - 1,
- ret = [];
- while ( (index = this.indexOf(str, index + 1)) > -1 ) {
- ret.push( index );
- }
- return ret;
- };
- String.prototype.startsWith = function ( str ) {
- return this.indexOf( str ) === 0;
- };
- //SO chat uses an unfiltered for...in to iterate over an array somewhere, so
- // that I have to use Object.defineProperty to make these non-enumerable
- Object.defineProperty( Array.prototype, 'invoke', {
- value : function ( funName ) {
- var args = [].slice.call( arguments, 1 );
- return this.map( invoke );
- function invoke ( item, index ) {
- var res = item;
- if ( item[funName] && item[funName].apply ) {
- res = item[ funName ].apply( item, args );
- }
- return res;
- }
- },
- configurable : true,
- writable : true
- });
- //fuck you readability
- //left this comment as company for future viewers with their new riddle
- Object.defineProperty( Array.prototype, 'first', {
- value : function ( fun ) {
- return this.some(function ( item ) {
- return fun.apply( null, arguments ) && ( (fun = item) || true );
- }) ? fun : null;
- },
- configurable : true,
- writable : true
- });
- Object.defineProperty( Array.prototype, 'random', {
- value : function () {
- return this[ Math.floor(Math.random() * this.length) ];
- },
- configurable : true,
- writable : true
- });
- Function.prototype.memoize = function () {
- var cache = Object.create( null ), fun = this;
- return function memoized ( hash ) {
- if ( hash in cache ) {
- return cache[ hash ];
- }
- var res = fun.apply( null, arguments );
- cache[ hash ] = res;
- return res;
- };
- };
- //async memoizer
- Function.prototype.memoizeAsync = function ( hasher ) {
- var cache = Object.create( null ), fun = this,
- hasher = hasher || function (x) { return x; };
- return function memoized () {
- var args = [].slice.call( arguments ),
- cb = args.pop(), //HEAVY assumption that cb is always passed last
- hash = hasher.apply( null, arguments );
- if ( hash in cache ) {
- cb.apply( null, cache[hash] );
- return;
- }
- //push the callback to the to-be-passed arguments
- args.push( resultFun );
- fun.apply( this, args );
- function resultFun () {
- cache[ hash ] = arguments;
- cb.apply( null, arguments );
- }
- };
- };
- //returns the number with at most `places` digits after the dot
- //examples:
- // 1.337.maxDecimal(1) === 1.3
- //
- //steps:
- // floor(1.337 * 10e0) = 13
- // 13 / 10e0 = 1.3
- Number.prototype.maxDecimal = function ( places ) {
- var exponent = Math.pow( 10, places );
- return Math.floor( this * exponent ) / exponent;
- };
- //receives an (ordered) array of numbers, denoting ranges, returns the first
- // range it falls between. I suck at explaining, so:
- // 4..fallsAfter( [1, 2, 5] ) === 2
- // 4..fallsAfter( [0, 3] ) === 3
- Number.prototype.fallsAfter = function ( ranges ) {
- ranges = ranges.slice();
- var min = ranges.shift(), max,
- n = this.valueOf();
- for ( var i = 0, l = ranges.length; i < l; i++ ) {
- max = ranges[ i ];
- if ( n < max ) {
- break;
- }
- min = max;
- }
- return min <= n ? min : null;
- };
- //calculates a:b to string form
- Math.ratio = function ( a, b ) {
- a = Number( a );
- b = Number( b );
- var gcd = this.gcd( a, b );
- return ( a / gcd ) + ':' + ( b / gcd );
- };
- //Euclidean gcd
- Math.gcd = function ( a, b ) {
- if ( !b ) {
- return a;
- }
- return this.gcd( b, a % b );
- };
- Math.rand = function ( min, max ) {
- //rand() === rand( 0, 9 )
- if ( !min ) {
- min = 0;
- max = 9;
- }
- //rand( max ) === rand( 0, max )
- else if ( !max ) {
- max = min;
- min = 0;
- }
- return Math.floor( Math.random() * (max - min + 1) ) + min;
- };
- //Crockford's supplant
- String.prototype.supplant = function ( arg ) {
- //if it's an object, use that. otherwise, use the arguments list.
- var obj = (
- Object(arg) === arg ?
- arg : arguments );
- return this.replace( /\{([^\}]+)\}/g, replace );
- function replace ( $0, $1 ) {
- return obj.hasOwnProperty( $1 ) ?
- obj[ $1 ] :
- $0;
- }
- };
- //I got annoyed that RegExps don't automagically turn into correct shit when
- // JSON-ing them. so HERE.
- Object.defineProperty( RegExp.prototype, 'toJSON', {
- value : function () {
- return this.toString();
- },
- configurable : true,
- writable : true
- });
- //not the most efficient thing, but who cares. formats the difference between
- // two dates
- Date.timeSince = function ( d0, d1 ) {
- d1 = d1 || (new Date);
- var ms = d1 - d0,
- delay, interval;
- var delays = [
- {
- delta : 3.1536e+10,
- suffix : 'year'
- },
- {
- delta : 2.592e+9,
- suffix : 'month'
- },
- {
- delta : 8.64e+7,
- suffix : 'day'
- },
- {
- delta : 3.6e+6,
- suffix : 'hour'
- },
- {
- delta : 6e+4,
- suffix : 'minute'
- },
- {
- delta : 1000,
- suffix : 'second'
- }
- //anything else is ms
- ];
- while ( delay = delays.shift() ) {
- if ( ms >= delay.delta ) {
- return format( ms / delay.delta, delay.suffix );
- }
- }
- return format( ms, 'millisecond' );
- function format ( interval, suffix ) {
- interval = Math.floor( interval );
- suffix += interval === 1 ? '' : 's';
- return interval + ' ' + suffix;
- }
- };
- (function () {
- "use strict";
- var target;
- if ( typeof bot !== 'undefined' ) {
- target = bot;
- }
- else if ( typeof exports !== 'undefined' ) {
- target = exports;
- }
- else {
- target = window;
- }
- target.parseCommandArgs = (function () {
- //the different states, not nearly enough to represent a female humanoid
- //you know you're building something fancy when it has constants with
- // undescores in their name
- var S_DATA = 0,
- S_SINGLE_QUOTE = 1,
- S_DOUBLE_QUOTE = 2,
- S_NEW = 3;
- //and constants representing constant special chars (why aren't I special? ;_;)
- var CH_SINGLE_QUOTE = '\'',
- CH_DOUBLE_QUOTE = '\"';
- /*
- the "scheme" roughly looks like this:
- args -> arg <sep> arg <sep> arg ... | Ø
- arg -> singleQuotedString | doubleQuotedString | string | Ø
- singleQuotedString -> 'string'
- doubleQuotedString -> "string"
- string -> char char char ... | Ø
- char -> anyCharacter | <escaper>anyCharacter | Ø
- Ø is the empty string
- */
- //the bad boy in the hood
- //I dunno what kind of parser this is, so I can't flaunt it or taunt with it,
- // but it was fun to make
- var parser = {
- parse : function ( source, sep, esc ) {
- //initializations are safe fun for the whole family!
- //later-edit: the above comment is one of the weirdest I've ever
- // written
- this.source = source;
- this.pos = 0;
- this.length = source.length;
- this.state = S_DATA;
- this.lookahead = '';
- this.escaper = esc || '~';
- this.separator = sep || ' ';
- var args = this.tokenize();
- //oh noez! errorz!
- if ( this.state !== S_DATA ) {
- this.throwFinishError();
- }
- return args;
- },
- tokenize : function () {
- var arg, ret = [];
- //let the parsing commence!
- while ( this.pos < this.length ) {
- arg = this.nextArg();
- //only add the next arg if it's actually something
- if ( arg ) {
- ret.push( arg );
- }
- }
- return ret;
- },
- //fetches the next argument (see the "scheme" at the top)
- nextArg : function () {
- var lexeme = '', ch;
- this.state = S_DATA;
- while ( true ) {
- ch = this.nextChar();
- if ( ch === null || this.state === S_NEW ) {
- break;
- }
- lexeme += ch;
- }
- return lexeme;
- },
- nextChar : function ( escape ) {
- var ch = this.lookahead = this.source[ this.pos ];
- this.pos++;
- if ( !ch ) {
- return null;
- }
- if ( escape ) {
- return ch;
- }
- //l'escaping!
- else if ( ch === this.escaper ) {
- return this.nextChar( true );
- }
- //encountered a separator and you're in data-mode!? ay digity!
- else if ( ch === this.separator && this.state === S_DATA ) {
- this.state = S_NEW;
- return ch;
- }
- return this.string();
- },
- //IM IN YO STRINGZ EATING YO CHARS
- // a.k.a string handling starts roughly here
- string : function () {
- var ch = this.lookahead;
- //single quotes are teh rulez
- if ( ch === CH_SINGLE_QUOTE ) {
- return this.singleQuotedString();
- }
- //exactly the same, just with double-quotes, which aren't quite as teh
- // rulez
- else if ( ch === CH_DOUBLE_QUOTE ) {
- return this.doubleQuotedString();
- }
- return ch;
- },
- singleQuotedString : function () {
- //we're already inside a double-quoted string, it's just another
- // char for us
- if ( this.state === S_DOUBLE_QUOTE ) {
- return this.lookahead;
- }
- //start your stringines!
- else if ( this.state !== S_SINGLE_QUOTE ) {
- this.state = S_SINGLE_QUOTE;
- }
- //end your stringiness!
- else {
- this.state = S_DATA;
- }
- return this.nextChar();
- },
- doubleQuotedString : function () {
- if ( this.state === S_SINGLE_QUOTE ) {
- return this.lookahead;
- }
- else if ( this.state !== S_DOUBLE_QUOTE ) {
- this.state = S_DOUBLE_QUOTE;
- }
- else {
- this.state = S_DATA;
- }
- return this.nextChar();
- },
- throwFinishError : function () {
- var errMsg = '';
- if ( this.state === S_SINGLE_QUOTE ) {
- errMsg = 'Expected ' + CH_SINGLE_QUOTE;
- }
- else if ( this.state === S_DOUBLE_QUOTE ) {
- errMsg = 'Expected ' + CH_DOUBLE_QUOTE;
- }
- var up = new Error( 'Unexpected end of input: ' + errMsg );
- up.column = this.pos;
- throw up; //problem?
- }
- };
- return function () {
- return parser.parse.apply( parser, arguments );
- };
- }());
- }());
- //a Trie suggestion dictionary, made by Esailija (small fixes by God)
- // http://stackoverflow.com/users/995876/esailija
- //used in the "command not found" message to show you closest commands
- var SuggestionDictionary = (function () {
- function TrieNode() {
- this.word = null;
- this.children = {};
- }
- TrieNode.prototype.add = function( word ) {
- var node = this, char, i = 0;
- while( char = word.charAt(i++) ) {
- if( !(char in node.children) ) {
- node.children[ char ] = new TrieNode();
- }
- node = node.children[ char ];
- }
- node.word = word;
- };
- //Having a small maxCost will increase performance greatly, experiment with
- //values of 1-3
- function SuggestionDictionary ( maxCost ) {
- if( !(this instanceof SuggestionDictionary) ) {
- throw new TypeError( "Illegal function call" );
- }
- maxCost = Number( maxCost );
- if( isNaN( maxCost ) || maxCost < 1 ) {
- throw new TypeError( "maxCost must be an integer > 1 " );
- }
- this.maxCost = maxCost;
- this.trie = new TrieNode();
- }
- SuggestionDictionary.prototype = {
- constructor: SuggestionDictionary,
- build : function ( words ) {
- if( !Array.isArray( words ) ) {
- throw new TypeError( "Cannot build a dictionary from "+words );
- }
- this.trie = new TrieNode();
- words.forEach(function ( word ) {
- this.trie.add( word );
- }, this);
- },
- __sortfn : function ( a, b ) {
- return a[1] - b[1];
- },
- search : function ( word ) {
- word = word.valueOf();
- var r;
- if( typeof word !== "string" ) {
- throw new TypeError( "Cannot search " + word );
- }
- if( this.trie === undefined ) {
- throw new TypeError( "Cannot search, dictionary isn't built yet" );
- }
- r = search( word, this.maxCost, this.trie );
- //r will be array of arrays:
- //["word", cost], ["word2", cost2], ["word3", cost3] , ..
- r.sort( this.__sortfn ); //Sort the results in order of least cost
- return r.map(function ( subarr ) {
- return subarr[ 0 ];
- });
- }
- };
- function range ( x, y ) {
- var r = [], i, l, start;
- if( y === undefined ) {
- start = 0;
- l = x;
- }
- else {
- start = x;
- l = y-start;
- }
- for( i = 0; i < l; ++i ) {
- r[i] = start++;
- }
- return r;
- }
- function search ( word, maxCost, trie ) {
- var results = [],
- currentRow = range( word.length + 1 );
- Object.keys( trie.children ).forEach(function ( letter ) {
- searchRecursive(
- trie.children[letter], letter, word,
- currentRow, results, maxCost );
- });
- return results;
- }
- function searchRecursive ( node, letter, word, previousRow, results, maxCost ) {
- var columns = word.length + 1,
- currentRow = [ previousRow[0] + 1 ],
- i, insertCost, deleteCost, replaceCost, last;
- for( i = 1; i < columns; ++i ) {
- insertCost = currentRow[ i-1 ] + 1;
- deleteCost = previousRow[ i ] + 1;
- if( word.charAt(i-1) !== letter ) {
- replaceCost = previousRow[ i-1 ]+1;
- }
- else {
- replaceCost = previousRow[ i-1 ];
- }
- currentRow.push( Math.min(insertCost, deleteCost, replaceCost) );
- }
- last = currentRow[ currentRow.length-1 ];
- if( last <= maxCost && node.word !== null ) {
- results.push( [node.word, last] );
- }
- if( Math.min.apply(Math, currentRow) <= maxCost ) {
- Object.keys( node.children ).forEach(function ( letter ) {
- searchRecursive(
- node.children[letter], letter, word,
- currentRow, results, maxCost );
- });
- }
- }
- return SuggestionDictionary;
- }());
- bot.commandDictionary = new SuggestionDictionary( 3 );
- (function () {
- "use strict";
- var commands = {
- help : function ( args ) {
- if ( args && args.length ) {
- var cmd = bot.getCommand( args );
- if ( cmd.error ) {
- return cmd.error;
- }
- var desc = cmd.description || 'No info is available';
- return args + ': ' + desc;
- }
- return (
- 'https://github.com/Zirak/SO-ChatBot/wiki/' +
- 'Interacting-with-the-bot'
- );
- },
- listen : function ( msg ) {
- return bot.callListeners( msg );
- },
- eval : function ( msg ) {
- return bot.eval( msg );
- },
- live : function () {
- if ( !bot.stopped ) {
- return 'I\'m not dead! Honest!';
- }
- bot.continue();
- return 'And on this day, you shall paint eggs for a giant bunny.';
- },
- die : function () {
- if ( bot.stopped ) {
- return 'Kill me once, shame on you, kill me twice...';
- }
- bot.stop();
- return 'You killed me!';
- },
- refresh : function() {
- window.location.reload();
- },
- forget : function ( args ) {
- var name = args.toLowerCase(),
- cmd = bot.getCommand( name );
- if ( cmd.error ) {
- return cmd.error;
- }
- if ( !cmd.canDel(args.get('user_id')) ) {
- return 'You are not authorized to delete the command ' + args;
- }
- cmd.del();
- return 'Command ' + name + ' forgotten.';
- },
- ban : function ( args ) {
- var ret = [];
- if ( args.content ) {
- args.parse().forEach( ban );
- }
- else {
- ret = Object.keys( bot.banlist ).filter( Number );
- }
- return ret.join( ' ' ) || 'Nothing to show/do.';
- function ban ( usrid ) {
- var id = Number( usrid ),
- msg;
- if ( isNaN(id) ) {
- id = args.findUserid( usrid.replace(/^@/, '') );
- }
- if ( id < 0 ) {
- msg = 'Cannot find user {0}.';
- }
- else if ( bot.isOwner(id) ) {
- msg = 'Cannot mindjail owner {0}.';
- }
- else if ( bot.banlist.contains(id) ) {
- msg = 'User {0} already in mindjail.';
- }
- else {
- bot.banlist.add( id );
- msg = 'User {0} added to mindjail.';
- }
- ret.push( msg.supplant(usrid) );
- }
- },
- unban : function ( args ) {
- var ret = [];
- args.parse().forEach( unban );
- return ret.join( ' ' );
- function unban ( usrid ) {
- var id = Number( usrid ),
- msg;
- if ( isNaN(id) ) {
- id = args.findUserid( usrid.replace(/^@/, '') );
- }
- if ( id < 0 ) {
- msg = 'Cannot find user {0}.'
- }
- else if ( !bot.banlist.contains(id) ) {
- msg = 'User {0} isn\'t in mindjail.';
- }
- else {
- bot.banlist.remove( id );
- msg = 'User {0} freed from mindjail!';
- }
- ret.push( msg.supplant(usrid) );
- }
- },
- //a lesson on semi-bad practices and laziness
- //chapter III
- info : function ( args ) {
- if ( args.content ) {
- return commandFormat( args.content );
- }
- var info = bot.info;
- return timeFormat() + ', ' + statsFormat();
- function commandFormat ( commandName ) {
- var cmd = bot.getCommand( commandName );
- if ( cmd.error ) {
- return cmd.error;
- }
- var ret = 'Command {name}, created by {creator}'.supplant( cmd );
- if ( cmd.date ) {
- ret += ' on ' + cmd.date.toUTCString();
- }
- if ( cmd.invoked ) {
- ret += ', invoked ' + cmd.invoked + ' times';
- }
- else {
- ret += ' but hasn\'t been used yet';
- }
- return ret;
- }
- function timeFormat () {
- var format = 'I awoke on {0} (that\'s about {1} ago)',
- awoke = info.start.toUTCString(),
- ago = Date.timeSince( info.start );
- return format.supplant( awoke, ago );
- }
- function statsFormat () {
- var ret = [],
- but = ''; //you'll see in a few lines
- if ( info.invoked ) {
- ret.push( 'got invoked ' + info.invoked + ' times' );
- }
- if ( info.learned ) {
- but = 'but ';
- ret.push( 'learned ' + info.learned + ' commands' );
- }
- if ( info.forgotten ) {
- ret.push( but + 'forgotten ' + info.forgotten + ' commands' );
- }
- if ( Math.random() < 0.15 ) {
- ret.push( 'teleported ' + Math.rand(100) + ' goats' );
- }
- return ret.join( ', ' ) || 'haven\'t done anything yet!';
- }
- },
- jquery : function jquery ( args ) {
- //check to see if more than one thing is requested
- var parsed = args.parse( true );
- if ( parsed.length > 1 ) {
- return parsed.map( jquery ).join( ' ' );
- }
- var props = args.trim().replace( /^\$/, 'jQuery' ),
- parts = props.split( '.' ), exists = false,
- url = props, msg;
- //parts will contain two likely components, depending on the input
- // jQuery.fn.prop - parts[0] = jQuery, parts[1] = prop
- // jQuery.prop - parts[0] = jQuery, parts[1] = prop
- // prop - parts[0] = prop
- //
- //jQuery API urls works like this:
- // if it's on the jQuery object, then the url is /jQuery.property
- // if it's on the proto, then the url is /property
- //
- //so, the mapping goes like this:
- // jQuery.fn.prop => prop
- // jQuery.prop => jQuery.prop if it's on jQuery
- // prop => prop if it's on jQuery.prototype,
- // jQuery.prop if it's on jQuery
- bot.log( props, parts, '/jquery input' );
- //user gave something like jQuery.fn.prop, turn that to just prop
- // jQuery.fn.prop => prop
- if ( parts.length === 3 ) {
- parts = [ parts[2] ];
- }
- //check to see if it's a property on the jQuery object itself
- // jQuery.prop => jQuery.prop
- if ( parts[0] === 'jQuery' && jQuery[parts[1]] ) {
- exists = true;
- }
- //user wants something on the prototype?
- // prop => prop
- else if ( parts.length === 1 && jQuery.prototype[parts[0]] ) {
- url = parts[ 0 ];
- exists = true;
- }
- //user just wanted a property? maybe.
- // prop => jQuery.prop
- else if ( jQuery[parts[0]] ) {
- url = 'jQuery.' + parts[0];
- exists = true;
- }
- if ( exists ) {
- msg = 'http://api.jquery.com/' + url;
- }
- else {
- msg = 'http://api.jquery.com/?s=' + encodeURIComponent( args );
- }
- bot.log( msg, '/jquery link' );
- return msg;
- },
- choose : function ( args ) {
- var opts = args.parse().filter( conjunctions ),
- rnd = Math.random(),
- len = opts.length;
- bot.log( opts, rnd, '/choose input' );
- //10% chance to get a "none-of-the-above"
- if ( rnd < 0.1 ) {
- return len === 2 ? 'Neither' : 'None of the above';
- }
- //15% chance to get "all-of-the-above"
- // (the first 10% are covered in the previous option)
- else if ( rnd < 0.25 ) {
- return len === 2 ? 'Both!' : 'All of the above';
- }
- return opts[ Math.floor(Math.random() * len) ];
- //TODO: add support for words like "and", e.g.
- // skip and jump or cry and die
- // =>
- // "skip and jump", "cry and die"
- function conjunctions ( word ) {
- return word !== 'or';
- }
- },
- user : function ( args ) {
- var props = args.parse(),
- usrid = props[ 0 ] || args.get( 'user_id' ),
- id = usrid;
- //check for searching by username
- if ( !(/^\d+$/.test(usrid)) ) {
- id = args.findUserid( usrid );
- if ( id < 0 ) {
- return 'Can\'t find user ' + usrid + ' in this chatroom.';
- }
- }
- args.directreply( 'http://stackoverflow.com/users/' + id );
- },
- listcommands : function ( args ) {
- var commands = Object.keys( bot.commands ),
- valid = /^(\d+|$)/.test( args.content ),
- page = Number( args.content ) || 0,
- pageSize = 50,
- total = Math.ceil( Math.max(0, commands.length) / pageSize ) - 1;
- if ( page > total || !valid ) {
- return [
- args.codify( 'StackOverflow: Could not access page' ),
- 'This unicorn has killed itself because of you',
- 'Accordion to recent surveys, you suck'
- ].random();
- }
- var start = page * pageSize,
- end = start + pageSize,
- ret = commands.slice( start, end ).join( ', ' );
- return ret + ' (page {0}/{1})'.supplant( page, total );;
- },
- purgecommands : function ( args ) {
- var id = args.get( 'user_id' );
- Object.keys( bot.commands ).map( mapper ).forEach( del );
- return 'The deed has been done.';
- function mapper ( cmdName ) {
- return bot.commands[ cmdName ];
- }
- function del ( cmd ) {
- if ( cmd.learned && cmd.canDel(id) ) {
- cmd.del();
- }
- }
- }
- };
- commands.define = (function () {
- var cache = Object.create( null );
- //cb is for internal usage by other commands/listeners
- return function ( args, cb ) {
- //we already defined it, grab from memory
- //unless you have alzheimer
- //in which case, you have bigger problems
- if ( cache[args] ) {
- return finish( cache[args] );
- }
- IO.jsonp.ddg( 'define ' + args.toString(), finishCall );
- //the duck talked back! either the xhr is complete, or the hallucinations
- // are back
- function finishCall ( resp ) {
- var url = resp.AbstractURL,
- def = resp.AbstractText;
- bot.log( url, def, '/define finishCall input' );
- //Webster returns the definition as
- // wordName definition: the actual definition
- // instead of just the actual definition
- if ( resp.AbstractSource === 'Merriam-Webster' ) {
- def = def.replace( args + ' definition: ', '' );
- bot.log( def, '/define finishCall webster' );
- }
- if ( !def ) {
- def = 'Could not find definition for ' + args +
- '. Trying Urban Dictionary';
- bot.getCommand( 'urban' ).exec( args );
- }
- else {
- def = args + ': ' + def; //problem?
- //the chat treats ( as a special character, so we escape!
- def += ' [\\(source\\)](' + url + ')';
- //add to cache
- cache[ args ] = def;
- }
- bot.log( def, '/define finishCall output' );
- finish( def );
- }
- function finish ( def ) {
- if ( cb && cb.call ) {
- cb( def );
- }
- else {
- args.directreply( def );
- }
- }
- };
- }());
- commands.define.async = true;
- //cb is for internal usage by other commands/listeners
- commands.norris = function ( args, cb ) {
- var chucky = 'http://api.icndb.com/jokes/random';
- IO.jsonp({
- url : chucky,
- fun : finishCall,
- jsonpName : 'callback'
- });
- function finishCall ( resp ) {
- var msg;
- if ( resp.type !== 'success' ) {
- msg = 'Chuck Norris is too awesome for this API. Try again.';
- }
- else {
- msg = IO.decodehtmlEntities( resp.value.joke );
- …
Large files files are truncated, but you can click here to view the full file