PageRenderTime 66ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 2ms

/master.js

https://github.com/licson0729/SO-ChatBot
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

  1. var IO = window.IO = {
  2. //event handling
  3. events : {},
  4. preventDefault : false,
  5. //register for an event
  6. register : function ( name, fun, thisArg ) {
  7. if ( !this.events[name] ) {
  8. this.events[ name ] = [];
  9. }
  10. this.events[ name ].push({
  11. fun : fun,
  12. thisArg : thisArg,
  13. args : Array.prototype.slice.call( arguments, 3 )
  14. });
  15. return this;
  16. },
  17. unregister : function ( name, fun ) {
  18. if ( !this.events[name] ) {
  19. return this;
  20. }
  21. this.events[ name ] = this.events[ name ].filter(function ( obj ) {
  22. return obj.fun !== fun;
  23. });
  24. return this;
  25. },
  26. //fire event!
  27. fire : function ( name ) {
  28. this.preventDefault = false;
  29. if ( !this.events[name] ) {
  30. return;
  31. }
  32. var args = Array.prototype.slice.call( arguments, 1 ),
  33. that = this;
  34. this.events[ name ].forEach( fireEvent );
  35. function fireEvent( evt ) {
  36. var call = evt.fun.apply( evt.thisArg, evt.args.concat(args) );
  37. that.preventDefault = call === false;
  38. }
  39. },
  40. urlstringify : (function () {
  41. //simple types, for which toString does the job
  42. //used in singularStringify
  43. var simplies = { number : true, string : true, boolean : true };
  44. var singularStringify = function ( thing ) {
  45. if ( typeof thing in simplies ) {
  46. return encodeURIComponent( thing.toString() );
  47. }
  48. return '';
  49. };
  50. var arrayStringify = function ( key, array ) {
  51. key = singularStringify( key );
  52. return array.map(function ( val ) {
  53. return pair( key, val, true );
  54. }).join( '&' );
  55. };
  56. //returns a key=value pair. pass in dontStringifyKey so that, well, the
  57. // key won't be stringified (used in arrayStringify)
  58. var pair = function ( key, val, dontStringifyKey ) {
  59. if ( !dontStringifyKey ) {
  60. key = singularStringify( key );
  61. }
  62. return key + '=' + singularStringify( val );
  63. };
  64. return function ( obj ) {
  65. return Object.keys( obj ).map(function ( key ) {
  66. var val = obj[ key ];
  67. if ( Array.isArray(val) ) {
  68. return arrayStringify( key, val );
  69. }
  70. else {
  71. return pair( key, val );
  72. }
  73. }).join( '&' );
  74. };
  75. }()),
  76. loadScript : function ( url, cb ) {
  77. var script = document.createElement( 'script' );
  78. script.src = url;
  79. script.onload = cb;
  80. document.head.appendChild( script );
  81. }
  82. };
  83. IO.decodehtmlEntities = (function (){
  84. var entities; //will be filled in the following line
  85. 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":"♦"};
  86. /*
  87. & -all entities start with &
  88. (
  89. # -charcode entities also have a #
  90. x? -hex charcodes
  91. )?
  92. [\w;] -now the entity (alphanumeric, separated by ;)
  93. +? -capture em until there aint no more (don't get the trailing ;)
  94. ; -trailing ;
  95. */
  96. var entityRegex = /&(#x?)?[\w;]+?;/g;
  97. var replaceEntities = function ( entities ) {
  98. //remove the & and split into each separate entity
  99. return entities.slice( 1 ).split( ';' ).map( decodeEntity ).join( '' );
  100. };
  101. var decodeEntity = function ( entity ) {
  102. //starts with a #, it's charcode
  103. if ( entity[0] === '#' ) {
  104. return decodeCharcodeEntity( entity );
  105. }
  106. return entities[ entity ] || entity;
  107. };
  108. var decodeCharcodeEntity = function ( entity ) {
  109. //remove the # prefix
  110. entity = entity.slice( 1 );
  111. var cc;
  112. //hex entities
  113. if ( entity[0] === 'x' ) {
  114. cc = parseInt( entity.slice(1), 16 );
  115. }
  116. //decimal entities
  117. else {
  118. cc = parseInt( entity, 10 );
  119. }
  120. return String.fromCharCode( cc );
  121. };
  122. return function ( html ) {
  123. return html.replace( entityRegex, replaceEntities );
  124. };
  125. }());
  126. //build IO.in and IO.out
  127. [ 'in', 'out' ].forEach(function ( dir ) {
  128. var fullName = dir + 'put';
  129. IO[ dir ] = {
  130. buffer : [],
  131. receive : function ( obj ) {
  132. IO.fire( 'receive' + fullName, obj );
  133. if ( IO.preventDefault ) {
  134. return this;
  135. }
  136. this.buffer.push( obj );
  137. return this;
  138. },
  139. //unload the next item in the buffer
  140. tick : function () {
  141. if ( this.buffer.length ) {
  142. IO.fire( fullName, this.buffer.shift() );
  143. }
  144. return this;
  145. },
  146. //unload everything in the buffer
  147. flush : function () {
  148. IO.fire( 'before' + fullName );
  149. if ( !this.buffer.length ) {
  150. return this;
  151. }
  152. var i = this.buffer.length;
  153. while( i --> 0 ) {
  154. this.tick();
  155. }
  156. IO.fire( 'after' + fullName );
  157. this.buffer = [];
  158. return this;
  159. }
  160. };
  161. });
  162. IO.xhr = function ( params ) {
  163. //merge in the defaults
  164. params = Object.merge({
  165. method : 'GET',
  166. headers : {},
  167. complete : function (){}
  168. }, params );
  169. params.headers = Object.merge({
  170. 'Content-Type' : 'application/x-www-form-urlencoded'
  171. }, params.headers );
  172. //if the data is an object, and not a fakey String object, dress it up
  173. if ( typeof params.data === 'object' && !params.data.charAt ) {
  174. params.data = IO.urlstringify( params.data );
  175. }
  176. var xhr = new XMLHttpRequest();
  177. xhr.open( params.method, params.url );
  178. xhr.addEventListener( 'readystatechange', function () {
  179. if ( xhr.readyState === 4 ) {
  180. params.complete.call(
  181. params.thisArg, xhr.responseText, xhr
  182. );
  183. }
  184. });
  185. Object.keys( params.headers ).forEach(function ( header ) {
  186. xhr.setRequestHeader( header, params.headers[header] );
  187. });
  188. xhr.send( params.data );
  189. return xhr;
  190. };
  191. IO.jsonp = function ( opts ) {
  192. opts.data = opts.data || {};
  193. opts.jsonpName = opts.jsonpName || 'jsonp';
  194. var script = document.createElement( 'script' ),
  195. semiRandom;
  196. do {
  197. semiRandom = 'IO' + ( Date.now() * Math.ceil(Math.random()) );
  198. } while ( window[semiRandom] );
  199. //this is the callback function, called from the "jsonp file"
  200. window[ semiRandom ] = function () {
  201. opts.fun.apply( opts.thisArg, arguments );
  202. //cleanup
  203. delete window[ semiRandom ];
  204. script.parentNode.removeChild( script );
  205. };
  206. //add the jsonp parameter to the data we're sending
  207. opts.data[ opts.jsonpName ] = semiRandom;
  208. //start preparing the url to be sent
  209. if ( opts.url.indexOf('?') === -1 ) {
  210. opts.url += '?';
  211. }
  212. //append the data to be sent, in string form, to the url
  213. opts.url += this.urlstringify( opts.data );
  214. script.src = opts.url;
  215. document.head.appendChild( script );
  216. };
  217. //generic, pre-made calls to be used inside commands
  218. IO.jsonp.ddg = function ( query, cb ) {
  219. IO.jsonp({
  220. url : 'http://api.duckduckgo.com/',
  221. jsonpName : 'callback',
  222. data : {
  223. format : 'json',
  224. q : query
  225. },
  226. fun : cb
  227. });
  228. };
  229. IO.jsonp.google = function ( query, cb ) {
  230. IO.jsonp({
  231. url : 'http://ajax.googleapis.com/ajax/services/search/web',
  232. jsonpName : 'callback',
  233. data : {
  234. v : '1.0',
  235. q : query
  236. },
  237. fun : cb
  238. });
  239. };
  240. ;
  241. (function () {
  242. "use strict";
  243. var bot = window.bot = {
  244. invocationPattern : '!!',
  245. commandRegex : /^\/\s*([\w\-]+)(?:\s(.+))?$/,
  246. commands : {}, //will be filled as needed
  247. commandDictionary : null, //it's null at this point, won't be for long
  248. listeners : [],
  249. info : {
  250. invoked : 0,
  251. learned : 0,
  252. forgotten : 0,
  253. start : new Date,
  254. },
  255. parseMessage : function ( msgObj ) {
  256. if ( !this.validateMessage(msgObj) ) {
  257. bot.log( msgObj, 'parseMessage invalid' );
  258. return;
  259. }
  260. var msg = this.prepareMessage( msgObj ),
  261. id = msg.get( 'user_id' );
  262. bot.log( msg, 'parseMessage valid' );
  263. if ( this.banlist.contains(id) ) {
  264. bot.log( msgObj, 'parseMessage banned' );
  265. //tell the user he's banned only if he hasn't already been told
  266. if ( !this.banlist[id].told ) {
  267. msg.reply( 'You iz in mindjail' );
  268. this.banlist[ id ].told = true;
  269. }
  270. return;
  271. }
  272. try {
  273. //it wants to execute some code
  274. if ( msg.startsWith('>') ) {
  275. this.eval( msg );
  276. }
  277. //it's a command
  278. else if ( msg.startsWith('/') ) {
  279. this.parseCommand( msg );
  280. }
  281. //see if some hobo listener wants this
  282. else if ( !this.callListeners(msg) ) {
  283. //no listener fancied the message. this is the last frontier,
  284. // so just give up in a classy, dignified way
  285. msg.reply(
  286. 'Y U NO MAEK SENSE!? Could not understand `' + msg + '`' );
  287. }
  288. }
  289. catch ( e ) {
  290. var err = 'Could not process input. Error: ' + e.message;
  291. if ( e.lineNumber ) {
  292. err += ' on line ' + e.lineNumber;
  293. }
  294. //column isn't part of ordinary errors, it's set in custom ones
  295. if ( e.column ) {
  296. err += ' on column ' + e.column;
  297. }
  298. msg.directreply( err );
  299. //make sure we have it somewhere
  300. console.dir( e );
  301. }
  302. finally {
  303. this.info.invoked += 1;
  304. }
  305. },
  306. prepareMessage : function ( msgObj ) {
  307. msgObj = this.adapter.transform( msgObj );
  308. var msg = IO.decodehtmlEntities( msgObj.content );
  309. return this.Message(
  310. msg.slice( this.invocationPattern.length ).trim(),
  311. msgObj );
  312. },
  313. parseCommand : function ( msg ) {
  314. bot.log( msg, 'parseCommand input' );
  315. var commandParts = this.commandRegex.exec( msg );
  316. if ( !commandParts ) {
  317. msg.reply( 'Invalid command ' + msg );
  318. return;
  319. }
  320. bot.log( commandParts, 'parseCommand matched' );
  321. var commandName = commandParts[ 1 ].toLowerCase(),
  322. cmdObj = this.getCommand( commandName );
  323. if ( this.personality.check(commandName) ) {
  324. this.personality.command();
  325. }
  326. //see if there was some error fetching the command
  327. if ( cmdObj.error ) {
  328. msg.reply( cmdObj.error );
  329. return;
  330. }
  331. if ( !cmdObj.canUse(msg.get('user_id')) ) {
  332. msg.reply([
  333. 'You do not have permission to use the command ' + commandName,
  334. "I'm afraid I can't let you do that, " + msg.get('user_name')
  335. ].random());
  336. return;
  337. }
  338. bot.log( cmdObj, 'parseCommand calling' );
  339. var args = this.Message(
  340. msg.replace(/^\/\s*/, '').slice( commandName.length ).trim(),
  341. msg.get()
  342. ),
  343. //it always amazed me how, in dynamic systems, the trigger of the
  344. // actions is always a small, nearly unidentifiable line
  345. //this line right here activates a command
  346. res = cmdObj.exec( args );
  347. if ( res ) {
  348. msg.reply( res );
  349. }
  350. },
  351. validateMessage : function ( msgObj ) {
  352. var msg = msgObj.content.trim();
  353. //all we really care about
  354. return msg.startsWith( this.invocationPattern );
  355. },
  356. addCommand : function ( cmd ) {
  357. if ( !cmd.exec || !cmd.del ) {
  358. cmd = this.Command( cmd );
  359. }
  360. if ( cmd.learned ) {
  361. this.info.learned += 1;
  362. }
  363. cmd.invoked = 0;
  364. this.commands[ cmd.name ] = cmd;
  365. this.commandDictionary.trie.add( cmd.name );
  366. },
  367. //gee, I wonder what this will return?
  368. commandExists : function ( cmdName ) {
  369. return this.commands.hasOwnProperty( cmdName );
  370. },
  371. //if a command named cmdName exists, it returns that command object
  372. //otherwise, it returns an object with an error message property
  373. getCommand : function ( cmdName ) {
  374. if ( this.commandExists(cmdName) ) {
  375. return this.commands[ cmdName ];
  376. }
  377. //set the error margin according to the length
  378. this.commandDictionary.maxCost = Math.floor(
  379. cmdName.length / 5 + 1 );
  380. var msg = 'Command ' + cmdName + ' does not exist.',
  381. //find commands resembling the one the user entered
  382. guesses = this.commandDictionary.search( cmdName );
  383. //resembling command(s) found, add them to the error message
  384. if ( guesses.length ) {
  385. msg += ' Did you mean: ' + guesses.join( ', ' );
  386. }
  387. return { error : msg };
  388. },
  389. //the function women think is lacking in men
  390. listen : function ( regex, fun, thisArg ) {
  391. if ( Array.isArray(regex) ) {
  392. regex.forEach(function ( reg ) {
  393. this.listen( reg, fun, thisArg );
  394. }, this);
  395. }
  396. else {
  397. this.listeners.push({
  398. pattern : regex,
  399. fun : fun,
  400. thisArg: thisArg
  401. });
  402. }
  403. },
  404. callListeners : function ( msg ) {
  405. var fired = false;
  406. this.listeners.forEach(function ( listener ) {
  407. var match = msg.exec( listener.pattern ), resp;
  408. if ( match ) {
  409. resp = listener.fun.call( listener.thisArg, msg );
  410. bot.log( match, resp );
  411. if ( resp ) {
  412. msg.reply( resp );
  413. }
  414. fired = resp !== false;
  415. }
  416. });
  417. return fired;
  418. },
  419. stoplog : false,
  420. log : function () {
  421. if ( !this.stoplog ) {
  422. console.log.apply( console, arguments );
  423. }
  424. },
  425. stop : function () {
  426. this.stopped = true;
  427. },
  428. continue : function () {
  429. this.stopped = false;
  430. }
  431. };
  432. //execute arbitrary js code in a relatively safe environment
  433. bot.eval = (function () {
  434. window.URL = window.URL || window.webkitURL || window.mozURL || null;
  435. //translation tool: https://tinker.io/b2ff5
  436. var worker_code = atob( 'dmFyIGdsb2JhbCA9IHRoaXM7CgovKm1vc3QgZXh0cmEgZnVuY3Rpb25zIGNvdWxkIGJlIHBvc3NpYmx5IHVuc2FmZSovCnZhciB3aGl0ZXkgPSB7CgknQXJyYXknICAgICAgICAgICAgICA6IDEsCgknQm9vbGVhbicgICAgICAgICAgICA6IDEsCgknRGF0ZScgICAgICAgICAgICAgICA6IDEsCgknRXJyb3InICAgICAgICAgICAgICA6IDEsCgknRXZhbEVycm9yJyAgICAgICAgICA6IDEsCgknRnVuY3Rpb24nICAgICAgICAgICA6IDEsCgknSW5maW5pdHknICAgICAgICAgICA6IDEsCgknSlNPTicgICAgICAgICAgICAgICA6IDEsCgknTWF0aCcgICAgICAgICAgICAgICA6IDEsCgknTmFOJyAgICAgICAgICAgICAgICA6IDEsCgknTnVtYmVyJyAgICAgICAgICAgICA6IDEsCgknT2JqZWN0JyAgICAgICAgICAgICA6IDEsCgknUmFuZ2VFcnJvcicgICAgICAgICA6IDEsCgknUmVmZXJlbmNlRXJyb3InICAgICA6IDEsCgknUmVnRXhwJyAgICAgICAgICAgICA6IDEsCgknU3RyaW5nJyAgICAgICAgICAgICA6IDEsCgknU3ludGF4RXJyb3InICAgICAgICA6IDEsCgknVHlwZUVycm9yJyAgICAgICAgICA6IDEsCgknVVJJRXJyb3InICAgICAgICAgICA6IDEsCgknYXRvYicgICAgICAgICAgICAgICA6IDEsCgknYnRvYScgICAgICAgICAgICAgICA6IDEsCgknZGVjb2RlVVJJJyAgICAgICAgICA6IDEsCgknZGVjb2RlVVJJQ29tcG9uZW50JyA6IDEsCgknZW5jb2RlVVJJJyAgICAgICAgICA6IDEsCgknZW5jb2RlVVJJQ29tcG9uZW50JyA6IDEsCgknZXZhbCcgICAgICAgICAgICAgICA6IDEsCgknZ2xvYmFsJyAgICAgICAgICAgICA6IDEsCgknaXNGaW5pdGUnICAgICAgICAgICA6IDEsCgknaXNOYU4nICAgICAgICAgICAgICA6IDEsCgknb25tZXNzYWdlJyAgICAgICAgICA6IDEsCgkncGFyc2VGbG9hdCcgICAgICAgICA6IDEsCgkncGFyc2VJbnQnICAgICAgICAgICA6IDEsCgkncG9zdE1lc3NhZ2UnICAgICAgICA6IDEsCgknc2VsZicgICAgICAgICAgICAgICA6IDEsCgkndW5kZWZpbmVkJyAgICAgICAgICA6IDEsCgknd2hpdGV5JyAgICAgICAgICAgICA6IDEsCgoJLyogdHlwZWQgYXJyYXlzIGFuZCBzaGl0ICovCgknQXJyYXlCdWZmZXInICAgICAgIDogMSwKCSdCbG9iJyAgICAgICAgICAgICAgOiAxLAoJJ0Zsb2F0MzJBcnJheScgICAgICA6IDEsCgknRmxvYXQ2NEFycmF5JyAgICAgIDogMSwKCSdJbnQ4QXJyYXknICAgICAgICAgOiAxLAoJJ0ludDE2QXJyYXknICAgICAgICA6IDEsCgknSW50MzJBcnJheScgICAgICAgIDogMSwKCSdVaW50OEFycmF5JyAgICAgICAgOiAxLAoJJ1VpbnQxNkFycmF5JyAgICAgICA6IDEsCgknVWludDMyQXJyYXknICAgICAgIDogMSwKCSdVaW50OENsYW1wZWRBcnJheScgOiAxLAoKCS8qCgl0aGVzZSBwcm9wZXJ0aWVzIGFsbG93IEZGIHRvIGZ1bmN0aW9uLiB3aXRob3V0IHRoZW0sIGEgZnVja2Zlc3Qgb2YKCWluZXhwbGljYWJsZSBlcnJvcnMgZW51c2VzLiB0b29rIG1lIGFib3V0IDQgaG91cnMgdG8gdHJhY2sgdGhlc2UgZnVja2VycwoJZG93bi4KCWZ1Y2sgaGVsbCBpdCBpc24ndCBmdXR1cmUtcHJvb2YsIGJ1dCB0aGUgZXJyb3JzIHRocm93biBhcmUgdW5jYXRjaGFibGUKCWFuZCB1bnRyYWNhYmxlLiBzbyBhIGhlYWRzLXVwLiBlbmpveSwgZnV0dXJlLW1lIQoJKi8KCSdET01FeGNlcHRpb24nIDogMSwKCSdFdmVudCcgICAgICAgIDogMSwKCSdNZXNzYWdlRXZlbnQnIDogMQp9OwoKWyBnbG9iYWwsIGdsb2JhbC5fX3Byb3RvX18gXS5mb3JFYWNoKGZ1bmN0aW9uICggb2JqICkgewoJT2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMoIG9iaiApLmZvckVhY2goZnVuY3Rpb24oIHByb3AgKSB7CgkJaWYoICF3aGl0ZXkuaGFzT3duUHJvcGVydHkoIHByb3AgKSApIHsKCQkJZGVsZXRlIG9ialsgcHJvcCBdOwoJCX0KCX0pOwp9KTsKCk9iamVjdC5kZWZpbmVQcm9wZXJ0eSggQXJyYXkucHJvdG90eXBlLCAnam9pbicsIHsKCXdyaXRhYmxlOiBmYWxzZSwKCWNvbmZpZ3VyYWJsZTogZmFsc2UsCgllbnVtcmFibGU6IGZhbHNlLAoKCXZhbHVlOiAoZnVuY3Rpb24gKCBvbGQgKSB7CgkJcmV0dXJuIGZ1bmN0aW9uICggYXJnICkgewoJCQlpZiAoIHRoaXMubGVuZ3RoID4gNTAwIHx8IChhcmcgJiYgYXJnLmxlbmd0aCA+IDUwMCkgKSB7CgkJCQl0aHJvdyAnRXhjZXB0aW9uOiB0b28gbWFueSBpdGVtcyc7CgkJCX0KCgkJCXJldHVybiBvbGQuYXBwbHkoIHRoaXMsIGFyZ3VtZW50cyApOwoJCX07Cgl9KCBBcnJheS5wcm90b3R5cGUuam9pbiApKQp9KTsKCihmdW5jdGlvbigpewoJInVzZSBzdHJpY3QiOwoKCXZhciBjb25zb2xlID0gewoJCV9pdGVtcyA6IFtdLAoJCWxvZyA6IGZ1bmN0aW9uKCkgewoJCQljb25zb2xlLl9pdGVtcy5wdXNoLmFwcGx5KCBjb25zb2xlLl9pdGVtcywgYXJndW1lbnRzICk7CgkJfQoJfTsKCXZhciBwID0gY29uc29sZS5sb2cuYmluZCggY29uc29sZSApOwoKCWZ1bmN0aW9uIGV4ZWMgKCBjb2RlICkgewoJCXZhciByZXN1bHQ7CgkJdHJ5IHsKCQkJcmVzdWx0ID0gZXZhbCggJyJ1c2Ugc3RyaWN0Ijt1bmRlZmluZWQ7XG4nICsgY29kZSApOwoJCX0KCQljYXRjaCAoIGUgKSB7CgkJCXJlc3VsdCA9IGUudG9TdHJpbmcoKTsKCQl9CgoJCXJldHVybiByZXN1bHQ7Cgl9CgoJZ2xvYmFsLm9ubWVzc2FnZSA9IGZ1bmN0aW9uICggZXZlbnQgKSB7CgkJdmFyIGpzb25TdHJpbmdpZnkgPSBKU09OLnN0cmluZ2lmeSwgLypiYWNrdXAqLwoJCQlyZXN1bHQgPSBleGVjKCBldmVudC5kYXRhICk7CgoJCS8qSlNPTiBkb2VzIG5vdCBsaWtlIGFueSBvZiB0aGUgZm9sbG93aW5nKi8KCQl2YXIgc3RydW5nID0gewoJCQlGdW5jdGlvbiAgOiB0cnVlLCBFcnJvciAgOiB0cnVlLAoJCQlVbmRlZmluZWQgOiB0cnVlLCBSZWdFeHAgOiB0cnVlCgkJfTsKCQl2YXIgc2hvdWxkX3N0cmluZyA9IGZ1bmN0aW9uICggdmFsdWUgKSB7CgkJCXZhciB0eXBlID0gKCB7fSApLnRvU3RyaW5nLmNhbGwoIHZhbHVlICkuc2xpY2UoIDgsIC0xICk7CgoJCQlpZiAoIHR5cGUgaW4gc3RydW5nICkgewoJCQkJcmV0dXJuIHRydWU7CgkJCX0KCQkJLypuZWl0aGVyIGRvZXMgaXQgZmVlbCBjb21wYXNzaW9uYXRlIGFib3V0IE5hTiBvciBJbmZpbml0eSovCgkJCXJldHVybiBpc05hTiggdmFsdWUgKSB8fCAhaXNGaW5pdGUoIHZhbHVlICk7CgkJfTsKCgkJdmFyIHJldml2ZXIgPSBmdW5jdGlvbiAoIGtleSwgdmFsdWUgKSB7CgkJCXZhciBvdXRwdXQ7CgoJCQlpZiAoIHNob3VsZF9zdHJpbmcodmFsdWUpICkgewoJCQkJb3V0cHV0ID0gJycgKyB2YWx1ZTsKCQkJfQoJCQllbHNlIHsKCQkJCW91dHB1dCA9IHZhbHVlOwoJCQl9CgoJCQlyZXR1cm4gb3V0cHV0OwoJCX07CgoJCXBvc3RNZXNzYWdlKHsKCQkJYW5zd2VyIDoganNvblN0cmluZ2lmeSggcmVzdWx0LCByZXZpdmVyICksCgkJCWxvZyAgICA6IGpzb25TdHJpbmdpZnkoIGNvbnNvbGUuX2l0ZW1zLCByZXZpdmVyICkuc2xpY2UoIDEsIC0xICkKCQl9KTsKCX07Cn0pKCk7Cg==' );
  437. var blob = new Blob( [worker_code], { type : 'application/javascript' } ),
  438. code_url = window.URL.createObjectURL( blob );
  439. return function ( msg ) {
  440. var timeout,
  441. worker = new Worker( code_url );
  442. worker.onmessage = function ( evt ) {
  443. finish( dressUpAnswer(evt.data) );
  444. };
  445. worker.onerror = function ( error ) {
  446. finish( error.toString() );
  447. };
  448. //and it all boils down to this...
  449. worker.postMessage( msg.content.replace(/^>/, '') );
  450. timeout = window.setTimeout(function() {
  451. finish( 'Maximum execution time exceeded' );
  452. }, 100 );
  453. function finish ( result ) {
  454. clearTimeout( timeout );
  455. worker.terminate();
  456. msg.directreply( result );
  457. }
  458. };
  459. function dressUpAnswer ( answerObj ) {
  460. console.log( answerObj, 'eval answerObj' );
  461. var answer = answerObj.answer,
  462. log = answerObj.log,
  463. result;
  464. result = snipAndCodify( answer );
  465. if ( log && log.length ) {
  466. result += ' Logged: ' + snipAndCodify( log );
  467. }
  468. return result;
  469. }
  470. function snipAndCodify ( str ) {
  471. var ret;
  472. if ( str.length > 400 ) {
  473. ret = '`' + str.slice(0, 400) + '` (snip)';
  474. }
  475. else {
  476. ret = '`' + str +'`';
  477. }
  478. return ret;
  479. }
  480. }());
  481. bot.banlist = JSON.parse( localStorage.bot_ban || '{}' );
  482. if ( Array.isArray(bot.banlist) ) {
  483. bot.banlist = bot.banlist.reduce(function ( ret, id ) {
  484. ret[ id ] = { told : false };
  485. return ret;
  486. }, {});
  487. }
  488. bot.banlist.contains = function ( id ) {
  489. return this.hasOwnProperty( id );
  490. };
  491. bot.banlist.add = function ( id ) {
  492. this[ id ] = { told : false };
  493. this.save();
  494. };
  495. bot.banlist.remove = function ( id ) {
  496. if ( this.contains(id) ) {
  497. delete this[ id ];
  498. this.save();
  499. }
  500. };
  501. bot.banlist.save = function () {
  502. //JSON.stringify ignores functions
  503. localStorage.bot_ban = JSON.stringify( this );
  504. };
  505. //some sort of pseudo constructor
  506. bot.Command = function ( cmd ) {
  507. cmd.name = cmd.name.toLowerCase();
  508. cmd.permissions = cmd.permissions || {};
  509. cmd.permissions.use = cmd.permissions.use || 'ALL';
  510. cmd.permissions.del = cmd.permissions.del || 'NONE';
  511. cmd.description = cmd.description || '';
  512. cmd.creator = cmd.creator || 'God';
  513. cmd.invoked = 0;
  514. //make canUse and canDel
  515. [ 'Use', 'Del' ].forEach(function ( perm ) {
  516. var low = perm.toLowerCase();
  517. cmd[ 'can' + perm ] = function ( usrid ) {
  518. var canDo = this.permissions[ low ];
  519. return canDo === 'ALL' || canDo !== 'NONE' &&
  520. canDo.indexOf( usrid ) > -1;
  521. };
  522. });
  523. cmd.exec = function () {
  524. this.invoked += 1;
  525. return this.fun.apply( this.thisArg, arguments );
  526. };
  527. cmd.del = function () {
  528. bot.info.forgotten += 1;
  529. delete bot.commands[ cmd.name ];
  530. };
  531. return cmd;
  532. };
  533. //a normally priviliged command which can be executed if enough people use it
  534. bot.CommunityCommand = function ( command, req ) {
  535. var cmd = this.Command( command ),
  536. used = {},
  537. old_execute = cmd.exec,
  538. old_canUse = cmd.canUse;
  539. req = req || 2;
  540. cmd.canUse = function () {
  541. return true;
  542. };
  543. cmd.exec = function ( msg ) {
  544. var err = register( msg.get('user_id') );
  545. if ( err ) {
  546. bot.log( err );
  547. return err;
  548. }
  549. return old_execute.apply( cmd, arguments );
  550. };
  551. return cmd;
  552. //once again, a switched return statement truthy means a message, falsy
  553. // means to go on ahead
  554. function register ( usrid ) {
  555. if ( old_canUse.call(cmd, usrid) ) {
  556. return false;
  557. }
  558. clean();
  559. var count = Object.keys( used ).length,
  560. needed = req - count;
  561. bot.log( used, count, req );
  562. if ( usrid in used ) {
  563. return 'Already registered; still need {0} more'.supplant( needed );
  564. }
  565. else if ( needed > 0 ) {
  566. used[ usrid ] = new Date;
  567. return 'Registered; need {0} more to execute'.supplant( needed-1 );
  568. }
  569. bot.log( 'should execute' );
  570. return false; //huzzah!
  571. }
  572. function clean () {
  573. var tenMinsAgo = new Date;
  574. tenMinsAgo.setMinutes( tenMinsAgo.getMinutes() - 10 );
  575. Object.keys( used ).reduce( rm, used );
  576. function rm ( ret, key ) {
  577. if ( ret[key] < tenMinsAgo ) {
  578. delete ret[ key ];
  579. }
  580. return ret;
  581. }
  582. }
  583. };
  584. bot.Message = function ( text, msgObj ) {
  585. //"casting" to object so that it can be extended with cool stuff and
  586. // still be treated like a string
  587. var ret = Object( text );
  588. ret.content = text;
  589. var deliciousObject = {
  590. send : function ( resp ) {
  591. bot.adapter.out.add( resp, msgObj.room_id );
  592. },
  593. reply : function ( resp ) {
  594. var prefix = bot.adapter.reply( msgObj.user_name );
  595. this.send( prefix + ' ' + resp );
  596. },
  597. directreply : function ( resp ) {
  598. var prefix = bot.adapter.directreply( msgObj.message_id );
  599. this.send( prefix + ' ' + resp );
  600. },
  601. //parse() parses the original message
  602. //parse( true ) also turns every match result to a Message
  603. //parse( msgToParse ) parses msgToParse
  604. //parse( msgToParse, true ) combination of the above
  605. parse : function ( msg, map ) {
  606. if ( !!msg === msg ) {
  607. map = msg;
  608. msg = text;
  609. }
  610. var parsed = bot.parseCommandArgs( msg || text );
  611. if ( !map ) {
  612. return parsed;
  613. }
  614. return parsed.map(function ( part ) {
  615. return bot.Message( part, msgObj );
  616. });
  617. },
  618. //execute a regexp against the text, saving it inside the object
  619. exec : function ( regexp ) {
  620. var match = regexp.exec( text );
  621. this.matches = match ? match : [];
  622. return match;
  623. },
  624. findUserid : function ( username ) {
  625. var users = [].slice.call( document
  626. .getElementById( 'sidebar' )
  627. .getElementsByClassName( 'user-container' )
  628. );
  629. //grab a list of user ids
  630. var ids = users.map(function ( container ) {
  631. return container.id.match( /\d+/ )[ 0 ];
  632. });
  633. //and a list of their names
  634. var names = users.map(function ( container ) {
  635. return container.getElementsByTagName( 'img' )[ 0 ]
  636. .title.toLowerCase().replace( /\s/g, '' );
  637. });
  638. var idx = names.indexOf(
  639. username.toString().toLowerCase().replace( /\s/g, '' ) );
  640. if ( idx < 0 ) {
  641. return -1;
  642. }
  643. return Number( ids[idx] );
  644. }.memoize(),
  645. codify : bot.adapter.codify.bind( bot.adapter ),
  646. escape : bot.adapter.escape.bind( bot.adapter ),
  647. link : bot.adapter.link.bind( bot.adapter ),
  648. //retrieve a value from the original message object, or if no argument
  649. // provided, the msgObj itself
  650. get : function ( what ) {
  651. if ( !what ) {
  652. return msgObj;
  653. }
  654. return msgObj[ what ];
  655. },
  656. set : function ( what, val ) {
  657. return msgObj[ what ] = val;
  658. }
  659. };
  660. Object.keys( deliciousObject ).forEach(function ( key ) {
  661. ret[ key ] = deliciousObject[ key ];
  662. });
  663. return ret;
  664. };
  665. bot.owners = [
  666. 94197, //Andy E
  667. 154112, //Simon Sarris
  668. 170224, //Ivo Wetzel
  669. 263525, //dystroy
  670. 322395, //Loktar
  671. 363815, //Ryan Kinal
  672. 401137, //Amaan Cheval
  673. 418183, //Octavian Damiean
  674. 419970, //Raynos
  675. 561731, //Neal
  676. 617762, //Zirak
  677. 727208, //tereško
  678. 809950, //GNi33
  679. 829835, //rlemon
  680. 851498, //Florian Margaine
  681. 855760, //Darkyen
  682. 995876, //Esailija
  683. 1078067, //copy
  684. 1216976, //SomeKittens
  685. 1386166, //phenomnomnominal
  686. 1386886, //jAndy
  687. 1839506 //SO ChatBot
  688. ];
  689. bot.isOwner = function ( usrid ) {
  690. return this.owners.indexOf( usrid ) > -1;
  691. };
  692. IO.register( 'input', bot.parseMessage, bot );
  693. bot.beatInterval = 5000; //once every 5 seconds is Good Enough ™
  694. (function beat () {
  695. bot.beat = setTimeout(function () {
  696. IO.fire( 'heartbeat' );
  697. beat();
  698. }, bot.beatInterval );
  699. }())
  700. //small utility functions
  701. Object.merge = function () {
  702. return [].reduce.call( arguments, function ( ret, merger ) {
  703. Object.keys( merger ).forEach(function ( key ) {
  704. ret[ key ] = merger[ key ];
  705. });
  706. return ret;
  707. }, {} );
  708. };
  709. String.prototype.indexesOf = function ( str, fromIndex ) {
  710. //since we also use index to tell indexOf from where to begin, and since
  711. // telling it to begin from where it found the match will cause it to just
  712. // match it again and again, inside the indexOf we do `index + 1`
  713. // to compensate for that 1, we need to subtract 1 from the original
  714. // starting position
  715. var index = ( fromIndex || 0 ) - 1,
  716. ret = [];
  717. while ( (index = this.indexOf(str, index + 1)) > -1 ) {
  718. ret.push( index );
  719. }
  720. return ret;
  721. };
  722. String.prototype.startsWith = function ( str ) {
  723. return this.indexOf( str ) === 0;
  724. };
  725. //SO chat uses an unfiltered for...in to iterate over an array somewhere, so
  726. // that I have to use Object.defineProperty to make these non-enumerable
  727. Object.defineProperty( Array.prototype, 'invoke', {
  728. value : function ( funName ) {
  729. var args = [].slice.call( arguments, 1 );
  730. return this.map( invoke );
  731. function invoke ( item, index ) {
  732. var res = item;
  733. if ( item[funName] && item[funName].apply ) {
  734. res = item[ funName ].apply( item, args );
  735. }
  736. return res;
  737. }
  738. },
  739. configurable : true,
  740. writable : true
  741. });
  742. //fuck you readability
  743. //left this comment as company for future viewers with their new riddle
  744. Object.defineProperty( Array.prototype, 'first', {
  745. value : function ( fun ) {
  746. return this.some(function ( item ) {
  747. return fun.apply( null, arguments ) && ( (fun = item) || true );
  748. }) ? fun : null;
  749. },
  750. configurable : true,
  751. writable : true
  752. });
  753. Object.defineProperty( Array.prototype, 'random', {
  754. value : function () {
  755. return this[ Math.floor(Math.random() * this.length) ];
  756. },
  757. configurable : true,
  758. writable : true
  759. });
  760. Function.prototype.memoize = function () {
  761. var cache = Object.create( null ), fun = this;
  762. return function memoized ( hash ) {
  763. if ( hash in cache ) {
  764. return cache[ hash ];
  765. }
  766. var res = fun.apply( null, arguments );
  767. cache[ hash ] = res;
  768. return res;
  769. };
  770. };
  771. //async memoizer
  772. Function.prototype.memoizeAsync = function ( hasher ) {
  773. var cache = Object.create( null ), fun = this,
  774. hasher = hasher || function (x) { return x; };
  775. return function memoized () {
  776. var args = [].slice.call( arguments ),
  777. cb = args.pop(), //HEAVY assumption that cb is always passed last
  778. hash = hasher.apply( null, arguments );
  779. if ( hash in cache ) {
  780. cb.apply( null, cache[hash] );
  781. return;
  782. }
  783. //push the callback to the to-be-passed arguments
  784. args.push( resultFun );
  785. fun.apply( this, args );
  786. function resultFun () {
  787. cache[ hash ] = arguments;
  788. cb.apply( null, arguments );
  789. }
  790. };
  791. };
  792. //returns the number with at most `places` digits after the dot
  793. //examples:
  794. // 1.337.maxDecimal(1) === 1.3
  795. //
  796. //steps:
  797. // floor(1.337 * 10e0) = 13
  798. // 13 / 10e0 = 1.3
  799. Number.prototype.maxDecimal = function ( places ) {
  800. var exponent = Math.pow( 10, places );
  801. return Math.floor( this * exponent ) / exponent;
  802. };
  803. //receives an (ordered) array of numbers, denoting ranges, returns the first
  804. // range it falls between. I suck at explaining, so:
  805. // 4..fallsAfter( [1, 2, 5] ) === 2
  806. // 4..fallsAfter( [0, 3] ) === 3
  807. Number.prototype.fallsAfter = function ( ranges ) {
  808. ranges = ranges.slice();
  809. var min = ranges.shift(), max,
  810. n = this.valueOf();
  811. for ( var i = 0, l = ranges.length; i < l; i++ ) {
  812. max = ranges[ i ];
  813. if ( n < max ) {
  814. break;
  815. }
  816. min = max;
  817. }
  818. return min <= n ? min : null;
  819. };
  820. //calculates a:b to string form
  821. Math.ratio = function ( a, b ) {
  822. a = Number( a );
  823. b = Number( b );
  824. var gcd = this.gcd( a, b );
  825. return ( a / gcd ) + ':' + ( b / gcd );
  826. };
  827. //Euclidean gcd
  828. Math.gcd = function ( a, b ) {
  829. if ( !b ) {
  830. return a;
  831. }
  832. return this.gcd( b, a % b );
  833. };
  834. Math.rand = function ( min, max ) {
  835. //rand() === rand( 0, 9 )
  836. if ( !min ) {
  837. min = 0;
  838. max = 9;
  839. }
  840. //rand( max ) === rand( 0, max )
  841. else if ( !max ) {
  842. max = min;
  843. min = 0;
  844. }
  845. return Math.floor( Math.random() * (max - min + 1) ) + min;
  846. };
  847. //Crockford's supplant
  848. String.prototype.supplant = function ( arg ) {
  849. //if it's an object, use that. otherwise, use the arguments list.
  850. var obj = (
  851. Object(arg) === arg ?
  852. arg : arguments );
  853. return this.replace( /\{([^\}]+)\}/g, replace );
  854. function replace ( $0, $1 ) {
  855. return obj.hasOwnProperty( $1 ) ?
  856. obj[ $1 ] :
  857. $0;
  858. }
  859. };
  860. //I got annoyed that RegExps don't automagically turn into correct shit when
  861. // JSON-ing them. so HERE.
  862. Object.defineProperty( RegExp.prototype, 'toJSON', {
  863. value : function () {
  864. return this.toString();
  865. },
  866. configurable : true,
  867. writable : true
  868. });
  869. //not the most efficient thing, but who cares. formats the difference between
  870. // two dates
  871. Date.timeSince = function ( d0, d1 ) {
  872. d1 = d1 || (new Date);
  873. var ms = d1 - d0,
  874. delay, interval;
  875. var delays = [
  876. {
  877. delta : 3.1536e+10,
  878. suffix : 'year'
  879. },
  880. {
  881. delta : 2.592e+9,
  882. suffix : 'month'
  883. },
  884. {
  885. delta : 8.64e+7,
  886. suffix : 'day'
  887. },
  888. {
  889. delta : 3.6e+6,
  890. suffix : 'hour'
  891. },
  892. {
  893. delta : 6e+4,
  894. suffix : 'minute'
  895. },
  896. {
  897. delta : 1000,
  898. suffix : 'second'
  899. }
  900. //anything else is ms
  901. ];
  902. while ( delay = delays.shift() ) {
  903. if ( ms >= delay.delta ) {
  904. return format( ms / delay.delta, delay.suffix );
  905. }
  906. }
  907. return format( ms, 'millisecond' );
  908. function format ( interval, suffix ) {
  909. interval = Math.floor( interval );
  910. suffix += interval === 1 ? '' : 's';
  911. return interval + ' ' + suffix;
  912. }
  913. };
  914. (function () {
  915. "use strict";
  916. var target;
  917. if ( typeof bot !== 'undefined' ) {
  918. target = bot;
  919. }
  920. else if ( typeof exports !== 'undefined' ) {
  921. target = exports;
  922. }
  923. else {
  924. target = window;
  925. }
  926. target.parseCommandArgs = (function () {
  927. //the different states, not nearly enough to represent a female humanoid
  928. //you know you're building something fancy when it has constants with
  929. // undescores in their name
  930. var S_DATA = 0,
  931. S_SINGLE_QUOTE = 1,
  932. S_DOUBLE_QUOTE = 2,
  933. S_NEW = 3;
  934. //and constants representing constant special chars (why aren't I special? ;_;)
  935. var CH_SINGLE_QUOTE = '\'',
  936. CH_DOUBLE_QUOTE = '\"';
  937. /*
  938. the "scheme" roughly looks like this:
  939. args -> arg <sep> arg <sep> arg ... | Ø
  940. arg -> singleQuotedString | doubleQuotedString | string | Ø
  941. singleQuotedString -> 'string'
  942. doubleQuotedString -> "string"
  943. string -> char char char ... | Ø
  944. char -> anyCharacter | <escaper>anyCharacter | Ø
  945. Ø is the empty string
  946. */
  947. //the bad boy in the hood
  948. //I dunno what kind of parser this is, so I can't flaunt it or taunt with it,
  949. // but it was fun to make
  950. var parser = {
  951. parse : function ( source, sep, esc ) {
  952. //initializations are safe fun for the whole family!
  953. //later-edit: the above comment is one of the weirdest I've ever
  954. // written
  955. this.source = source;
  956. this.pos = 0;
  957. this.length = source.length;
  958. this.state = S_DATA;
  959. this.lookahead = '';
  960. this.escaper = esc || '~';
  961. this.separator = sep || ' ';
  962. var args = this.tokenize();
  963. //oh noez! errorz!
  964. if ( this.state !== S_DATA ) {
  965. this.throwFinishError();
  966. }
  967. return args;
  968. },
  969. tokenize : function () {
  970. var arg, ret = [];
  971. //let the parsing commence!
  972. while ( this.pos < this.length ) {
  973. arg = this.nextArg();
  974. //only add the next arg if it's actually something
  975. if ( arg ) {
  976. ret.push( arg );
  977. }
  978. }
  979. return ret;
  980. },
  981. //fetches the next argument (see the "scheme" at the top)
  982. nextArg : function () {
  983. var lexeme = '', ch;
  984. this.state = S_DATA;
  985. while ( true ) {
  986. ch = this.nextChar();
  987. if ( ch === null || this.state === S_NEW ) {
  988. break;
  989. }
  990. lexeme += ch;
  991. }
  992. return lexeme;
  993. },
  994. nextChar : function ( escape ) {
  995. var ch = this.lookahead = this.source[ this.pos ];
  996. this.pos++;
  997. if ( !ch ) {
  998. return null;
  999. }
  1000. if ( escape ) {
  1001. return ch;
  1002. }
  1003. //l'escaping!
  1004. else if ( ch === this.escaper ) {
  1005. return this.nextChar( true );
  1006. }
  1007. //encountered a separator and you're in data-mode!? ay digity!
  1008. else if ( ch === this.separator && this.state === S_DATA ) {
  1009. this.state = S_NEW;
  1010. return ch;
  1011. }
  1012. return this.string();
  1013. },
  1014. //IM IN YO STRINGZ EATING YO CHARS
  1015. // a.k.a string handling starts roughly here
  1016. string : function () {
  1017. var ch = this.lookahead;
  1018. //single quotes are teh rulez
  1019. if ( ch === CH_SINGLE_QUOTE ) {
  1020. return this.singleQuotedString();
  1021. }
  1022. //exactly the same, just with double-quotes, which aren't quite as teh
  1023. // rulez
  1024. else if ( ch === CH_DOUBLE_QUOTE ) {
  1025. return this.doubleQuotedString();
  1026. }
  1027. return ch;
  1028. },
  1029. singleQuotedString : function () {
  1030. //we're already inside a double-quoted string, it's just another
  1031. // char for us
  1032. if ( this.state === S_DOUBLE_QUOTE ) {
  1033. return this.lookahead;
  1034. }
  1035. //start your stringines!
  1036. else if ( this.state !== S_SINGLE_QUOTE ) {
  1037. this.state = S_SINGLE_QUOTE;
  1038. }
  1039. //end your stringiness!
  1040. else {
  1041. this.state = S_DATA;
  1042. }
  1043. return this.nextChar();
  1044. },
  1045. doubleQuotedString : function () {
  1046. if ( this.state === S_SINGLE_QUOTE ) {
  1047. return this.lookahead;
  1048. }
  1049. else if ( this.state !== S_DOUBLE_QUOTE ) {
  1050. this.state = S_DOUBLE_QUOTE;
  1051. }
  1052. else {
  1053. this.state = S_DATA;
  1054. }
  1055. return this.nextChar();
  1056. },
  1057. throwFinishError : function () {
  1058. var errMsg = '';
  1059. if ( this.state === S_SINGLE_QUOTE ) {
  1060. errMsg = 'Expected ' + CH_SINGLE_QUOTE;
  1061. }
  1062. else if ( this.state === S_DOUBLE_QUOTE ) {
  1063. errMsg = 'Expected ' + CH_DOUBLE_QUOTE;
  1064. }
  1065. var up = new Error( 'Unexpected end of input: ' + errMsg );
  1066. up.column = this.pos;
  1067. throw up; //problem?
  1068. }
  1069. };
  1070. return function () {
  1071. return parser.parse.apply( parser, arguments );
  1072. };
  1073. }());
  1074. }());
  1075. //a Trie suggestion dictionary, made by Esailija (small fixes by God)
  1076. // http://stackoverflow.com/users/995876/esailija
  1077. //used in the "command not found" message to show you closest commands
  1078. var SuggestionDictionary = (function () {
  1079. function TrieNode() {
  1080. this.word = null;
  1081. this.children = {};
  1082. }
  1083. TrieNode.prototype.add = function( word ) {
  1084. var node = this, char, i = 0;
  1085. while( char = word.charAt(i++) ) {
  1086. if( !(char in node.children) ) {
  1087. node.children[ char ] = new TrieNode();
  1088. }
  1089. node = node.children[ char ];
  1090. }
  1091. node.word = word;
  1092. };
  1093. //Having a small maxCost will increase performance greatly, experiment with
  1094. //values of 1-3
  1095. function SuggestionDictionary ( maxCost ) {
  1096. if( !(this instanceof SuggestionDictionary) ) {
  1097. throw new TypeError( "Illegal function call" );
  1098. }
  1099. maxCost = Number( maxCost );
  1100. if( isNaN( maxCost ) || maxCost < 1 ) {
  1101. throw new TypeError( "maxCost must be an integer > 1 " );
  1102. }
  1103. this.maxCost = maxCost;
  1104. this.trie = new TrieNode();
  1105. }
  1106. SuggestionDictionary.prototype = {
  1107. constructor: SuggestionDictionary,
  1108. build : function ( words ) {
  1109. if( !Array.isArray( words ) ) {
  1110. throw new TypeError( "Cannot build a dictionary from "+words );
  1111. }
  1112. this.trie = new TrieNode();
  1113. words.forEach(function ( word ) {
  1114. this.trie.add( word );
  1115. }, this);
  1116. },
  1117. __sortfn : function ( a, b ) {
  1118. return a[1] - b[1];
  1119. },
  1120. search : function ( word ) {
  1121. word = word.valueOf();
  1122. var r;
  1123. if( typeof word !== "string" ) {
  1124. throw new TypeError( "Cannot search " + word );
  1125. }
  1126. if( this.trie === undefined ) {
  1127. throw new TypeError( "Cannot search, dictionary isn't built yet" );
  1128. }
  1129. r = search( word, this.maxCost, this.trie );
  1130. //r will be array of arrays:
  1131. //["word", cost], ["word2", cost2], ["word3", cost3] , ..
  1132. r.sort( this.__sortfn ); //Sort the results in order of least cost
  1133. return r.map(function ( subarr ) {
  1134. return subarr[ 0 ];
  1135. });
  1136. }
  1137. };
  1138. function range ( x, y ) {
  1139. var r = [], i, l, start;
  1140. if( y === undefined ) {
  1141. start = 0;
  1142. l = x;
  1143. }
  1144. else {
  1145. start = x;
  1146. l = y-start;
  1147. }
  1148. for( i = 0; i < l; ++i ) {
  1149. r[i] = start++;
  1150. }
  1151. return r;
  1152. }
  1153. function search ( word, maxCost, trie ) {
  1154. var results = [],
  1155. currentRow = range( word.length + 1 );
  1156. Object.keys( trie.children ).forEach(function ( letter ) {
  1157. searchRecursive(
  1158. trie.children[letter], letter, word,
  1159. currentRow, results, maxCost );
  1160. });
  1161. return results;
  1162. }
  1163. function searchRecursive ( node, letter, word, previousRow, results, maxCost ) {
  1164. var columns = word.length + 1,
  1165. currentRow = [ previousRow[0] + 1 ],
  1166. i, insertCost, deleteCost, replaceCost, last;
  1167. for( i = 1; i < columns; ++i ) {
  1168. insertCost = currentRow[ i-1 ] + 1;
  1169. deleteCost = previousRow[ i ] + 1;
  1170. if( word.charAt(i-1) !== letter ) {
  1171. replaceCost = previousRow[ i-1 ]+1;
  1172. }
  1173. else {
  1174. replaceCost = previousRow[ i-1 ];
  1175. }
  1176. currentRow.push( Math.min(insertCost, deleteCost, replaceCost) );
  1177. }
  1178. last = currentRow[ currentRow.length-1 ];
  1179. if( last <= maxCost && node.word !== null ) {
  1180. results.push( [node.word, last] );
  1181. }
  1182. if( Math.min.apply(Math, currentRow) <= maxCost ) {
  1183. Object.keys( node.children ).forEach(function ( letter ) {
  1184. searchRecursive(
  1185. node.children[letter], letter, word,
  1186. currentRow, results, maxCost );
  1187. });
  1188. }
  1189. }
  1190. return SuggestionDictionary;
  1191. }());
  1192. bot.commandDictionary = new SuggestionDictionary( 3 );
  1193. (function () {
  1194. "use strict";
  1195. var commands = {
  1196. help : function ( args ) {
  1197. if ( args && args.length ) {
  1198. var cmd = bot.getCommand( args );
  1199. if ( cmd.error ) {
  1200. return cmd.error;
  1201. }
  1202. var desc = cmd.description || 'No info is available';
  1203. return args + ': ' + desc;
  1204. }
  1205. return (
  1206. 'https://github.com/Zirak/SO-ChatBot/wiki/' +
  1207. 'Interacting-with-the-bot'
  1208. );
  1209. },
  1210. listen : function ( msg ) {
  1211. return bot.callListeners( msg );
  1212. },
  1213. eval : function ( msg ) {
  1214. return bot.eval( msg );
  1215. },
  1216. live : function () {
  1217. if ( !bot.stopped ) {
  1218. return 'I\'m not dead! Honest!';
  1219. }
  1220. bot.continue();
  1221. return 'And on this day, you shall paint eggs for a giant bunny.';
  1222. },
  1223. die : function () {
  1224. if ( bot.stopped ) {
  1225. return 'Kill me once, shame on you, kill me twice...';
  1226. }
  1227. bot.stop();
  1228. return 'You killed me!';
  1229. },
  1230. refresh : function() {
  1231. window.location.reload();
  1232. },
  1233. forget : function ( args ) {
  1234. var name = args.toLowerCase(),
  1235. cmd = bot.getCommand( name );
  1236. if ( cmd.error ) {
  1237. return cmd.error;
  1238. }
  1239. if ( !cmd.canDel(args.get('user_id')) ) {
  1240. return 'You are not authorized to delete the command ' + args;
  1241. }
  1242. cmd.del();
  1243. return 'Command ' + name + ' forgotten.';
  1244. },
  1245. ban : function ( args ) {
  1246. var ret = [];
  1247. if ( args.content ) {
  1248. args.parse().forEach( ban );
  1249. }
  1250. else {
  1251. ret = Object.keys( bot.banlist ).filter( Number );
  1252. }
  1253. return ret.join( ' ' ) || 'Nothing to show/do.';
  1254. function ban ( usrid ) {
  1255. var id = Number( usrid ),
  1256. msg;
  1257. if ( isNaN(id) ) {
  1258. id = args.findUserid( usrid.replace(/^@/, '') );
  1259. }
  1260. if ( id < 0 ) {
  1261. msg = 'Cannot find user {0}.';
  1262. }
  1263. else if ( bot.isOwner(id) ) {
  1264. msg = 'Cannot mindjail owner {0}.';
  1265. }
  1266. else if ( bot.banlist.contains(id) ) {
  1267. msg = 'User {0} already in mindjail.';
  1268. }
  1269. else {
  1270. bot.banlist.add( id );
  1271. msg = 'User {0} added to mindjail.';
  1272. }
  1273. ret.push( msg.supplant(usrid) );
  1274. }
  1275. },
  1276. unban : function ( args ) {
  1277. var ret = [];
  1278. args.parse().forEach( unban );
  1279. return ret.join( ' ' );
  1280. function unban ( usrid ) {
  1281. var id = Number( usrid ),
  1282. msg;
  1283. if ( isNaN(id) ) {
  1284. id = args.findUserid( usrid.replace(/^@/, '') );
  1285. }
  1286. if ( id < 0 ) {
  1287. msg = 'Cannot find user {0}.'
  1288. }
  1289. else if ( !bot.banlist.contains(id) ) {
  1290. msg = 'User {0} isn\'t in mindjail.';
  1291. }
  1292. else {
  1293. bot.banlist.remove( id );
  1294. msg = 'User {0} freed from mindjail!';
  1295. }
  1296. ret.push( msg.supplant(usrid) );
  1297. }
  1298. },
  1299. //a lesson on semi-bad practices and laziness
  1300. //chapter III
  1301. info : function ( args ) {
  1302. if ( args.content ) {
  1303. return commandFormat( args.content );
  1304. }
  1305. var info = bot.info;
  1306. return timeFormat() + ', ' + statsFormat();
  1307. function commandFormat ( commandName ) {
  1308. var cmd = bot.getCommand( commandName );
  1309. if ( cmd.error ) {
  1310. return cmd.error;
  1311. }
  1312. var ret = 'Command {name}, created by {creator}'.supplant( cmd );
  1313. if ( cmd.date ) {
  1314. ret += ' on ' + cmd.date.toUTCString();
  1315. }
  1316. if ( cmd.invoked ) {
  1317. ret += ', invoked ' + cmd.invoked + ' times';
  1318. }
  1319. else {
  1320. ret += ' but hasn\'t been used yet';
  1321. }
  1322. return ret;
  1323. }
  1324. function timeFormat () {
  1325. var format = 'I awoke on {0} (that\'s about {1} ago)',
  1326. awoke = info.start.toUTCString(),
  1327. ago = Date.timeSince( info.start );
  1328. return format.supplant( awoke, ago );
  1329. }
  1330. function statsFormat () {
  1331. var ret = [],
  1332. but = ''; //you'll see in a few lines
  1333. if ( info.invoked ) {
  1334. ret.push( 'got invoked ' + info.invoked + ' times' );
  1335. }
  1336. if ( info.learned ) {
  1337. but = 'but ';
  1338. ret.push( 'learned ' + info.learned + ' commands' );
  1339. }
  1340. if ( info.forgotten ) {
  1341. ret.push( but + 'forgotten ' + info.forgotten + ' commands' );
  1342. }
  1343. if ( Math.random() < 0.15 ) {
  1344. ret.push( 'teleported ' + Math.rand(100) + ' goats' );
  1345. }
  1346. return ret.join( ', ' ) || 'haven\'t done anything yet!';
  1347. }
  1348. },
  1349. jquery : function jquery ( args ) {
  1350. //check to see if more than one thing is requested
  1351. var parsed = args.parse( true );
  1352. if ( parsed.length > 1 ) {
  1353. return parsed.map( jquery ).join( ' ' );
  1354. }
  1355. var props = args.trim().replace( /^\$/, 'jQuery' ),
  1356. parts = props.split( '.' ), exists = false,
  1357. url = props, msg;
  1358. //parts will contain two likely components, depending on the input
  1359. // jQuery.fn.prop - parts[0] = jQuery, parts[1] = prop
  1360. // jQuery.prop - parts[0] = jQuery, parts[1] = prop
  1361. // prop - parts[0] = prop
  1362. //
  1363. //jQuery API urls works like this:
  1364. // if it's on the jQuery object, then the url is /jQuery.property
  1365. // if it's on the proto, then the url is /property
  1366. //
  1367. //so, the mapping goes like this:
  1368. // jQuery.fn.prop => prop
  1369. // jQuery.prop => jQuery.prop if it's on jQuery
  1370. // prop => prop if it's on jQuery.prototype,
  1371. // jQuery.prop if it's on jQuery
  1372. bot.log( props, parts, '/jquery input' );
  1373. //user gave something like jQuery.fn.prop, turn that to just prop
  1374. // jQuery.fn.prop => prop
  1375. if ( parts.length === 3 ) {
  1376. parts = [ parts[2] ];
  1377. }
  1378. //check to see if it's a property on the jQuery object itself
  1379. // jQuery.prop => jQuery.prop
  1380. if ( parts[0] === 'jQuery' && jQuery[parts[1]] ) {
  1381. exists = true;
  1382. }
  1383. //user wants something on the prototype?
  1384. // prop => prop
  1385. else if ( parts.length === 1 && jQuery.prototype[parts[0]] ) {
  1386. url = parts[ 0 ];
  1387. exists = true;
  1388. }
  1389. //user just wanted a property? maybe.
  1390. // prop => jQuery.prop
  1391. else if ( jQuery[parts[0]] ) {
  1392. url = 'jQuery.' + parts[0];
  1393. exists = true;
  1394. }
  1395. if ( exists ) {
  1396. msg = 'http://api.jquery.com/' + url;
  1397. }
  1398. else {
  1399. msg = 'http://api.jquery.com/?s=' + encodeURIComponent( args );
  1400. }
  1401. bot.log( msg, '/jquery link' );
  1402. return msg;
  1403. },
  1404. choose : function ( args ) {
  1405. var opts = args.parse().filter( conjunctions ),
  1406. rnd = Math.random(),
  1407. len = opts.length;
  1408. bot.log( opts, rnd, '/choose input' );
  1409. //10% chance to get a "none-of-the-above"
  1410. if ( rnd < 0.1 ) {
  1411. return len === 2 ? 'Neither' : 'None of the above';
  1412. }
  1413. //15% chance to get "all-of-the-above"
  1414. // (the first 10% are covered in the previous option)
  1415. else if ( rnd < 0.25 ) {
  1416. return len === 2 ? 'Both!' : 'All of the above';
  1417. }
  1418. return opts[ Math.floor(Math.random() * len) ];
  1419. //TODO: add support for words like "and", e.g.
  1420. // skip and jump or cry and die
  1421. // =>
  1422. // "skip and jump", "cry and die"
  1423. function conjunctions ( word ) {
  1424. return word !== 'or';
  1425. }
  1426. },
  1427. user : function ( args ) {
  1428. var props = args.parse(),
  1429. usrid = props[ 0 ] || args.get( 'user_id' ),
  1430. id = usrid;
  1431. //check for searching by username
  1432. if ( !(/^\d+$/.test(usrid)) ) {
  1433. id = args.findUserid( usrid );
  1434. if ( id < 0 ) {
  1435. return 'Can\'t find user ' + usrid + ' in this chatroom.';
  1436. }
  1437. }
  1438. args.directreply( 'http://stackoverflow.com/users/' + id );
  1439. },
  1440. listcommands : function ( args ) {
  1441. var commands = Object.keys( bot.commands ),
  1442. valid = /^(\d+|$)/.test( args.content ),
  1443. page = Number( args.content ) || 0,
  1444. pageSize = 50,
  1445. total = Math.ceil( Math.max(0, commands.length) / pageSize ) - 1;
  1446. if ( page > total || !valid ) {
  1447. return [
  1448. args.codify( 'StackOverflow: Could not access page' ),
  1449. 'This unicorn has killed itself because of you',
  1450. 'Accordion to recent surveys, you suck'
  1451. ].random();
  1452. }
  1453. var start = page * pageSize,
  1454. end = start + pageSize,
  1455. ret = commands.slice( start, end ).join( ', ' );
  1456. return ret + ' (page {0}/{1})'.supplant( page, total );;
  1457. },
  1458. purgecommands : function ( args ) {
  1459. var id = args.get( 'user_id' );
  1460. Object.keys( bot.commands ).map( mapper ).forEach( del );
  1461. return 'The deed has been done.';
  1462. function mapper ( cmdName ) {
  1463. return bot.commands[ cmdName ];
  1464. }
  1465. function del ( cmd ) {
  1466. if ( cmd.learned && cmd.canDel(id) ) {
  1467. cmd.del();
  1468. }
  1469. }
  1470. }
  1471. };
  1472. commands.define = (function () {
  1473. var cache = Object.create( null );
  1474. //cb is for internal usage by other commands/listeners
  1475. return function ( args, cb ) {
  1476. //we already defined it, grab from memory
  1477. //unless you have alzheimer
  1478. //in which case, you have bigger problems
  1479. if ( cache[args] ) {
  1480. return finish( cache[args] );
  1481. }
  1482. IO.jsonp.ddg( 'define ' + args.toString(), finishCall );
  1483. //the duck talked back! either the xhr is complete, or the hallucinations
  1484. // are back
  1485. function finishCall ( resp ) {
  1486. var url = resp.AbstractURL,
  1487. def = resp.AbstractText;
  1488. bot.log( url, def, '/define finishCall input' );
  1489. //Webster returns the definition as
  1490. // wordName definition: the actual definition
  1491. // instead of just the actual definition
  1492. if ( resp.AbstractSource === 'Merriam-Webster' ) {
  1493. def = def.replace( args + ' definition: ', '' );
  1494. bot.log( def, '/define finishCall webster' );
  1495. }
  1496. if ( !def ) {
  1497. def = 'Could not find definition for ' + args +
  1498. '. Trying Urban Dictionary';
  1499. bot.getCommand( 'urban' ).exec( args );
  1500. }
  1501. else {
  1502. def = args + ': ' + def; //problem?
  1503. //the chat treats ( as a special character, so we escape!
  1504. def += ' [\\(source\\)](' + url + ')';
  1505. //add to cache
  1506. cache[ args ] = def;
  1507. }
  1508. bot.log( def, '/define finishCall output' );
  1509. finish( def );
  1510. }
  1511. function finish ( def ) {
  1512. if ( cb && cb.call ) {
  1513. cb( def );
  1514. }
  1515. else {
  1516. args.directreply( def );
  1517. }
  1518. }
  1519. };
  1520. }());
  1521. commands.define.async = true;
  1522. //cb is for internal usage by other commands/listeners
  1523. commands.norris = function ( args, cb ) {
  1524. var chucky = 'http://api.icndb.com/jokes/random';
  1525. IO.jsonp({
  1526. url : chucky,
  1527. fun : finishCall,
  1528. jsonpName : 'callback'
  1529. });
  1530. function finishCall ( resp ) {
  1531. var msg;
  1532. if ( resp.type !== 'success' ) {
  1533. msg = 'Chuck Norris is too awesome for this API. Try again.';
  1534. }
  1535. else {
  1536. msg = IO.decodehtmlEntities( resp.value.joke );

Large files files are truncated, but you can click here to view the full file