PageRenderTime 77ms 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
  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 );
  1537. }
  1538. if ( cb && cb.call ) {
  1539. cb( msg );
  1540. }
  1541. else {
  1542. args.reply( msg );
  1543. }
  1544. }
  1545. };
  1546. commands.norris.async = true;
  1547. //cb is for internal blah blah blah
  1548. commands.urban = (function () {
  1549. var cache = Object.create( null );
  1550. return function ( args, cb ) {
  1551. if ( !args.length ) {
  1552. return 'Y U NO PROVIDE ARGUMENTS!?';
  1553. }
  1554. if ( cache[args] ) {
  1555. return finish( cache[args] );
  1556. }
  1557. IO.jsonp({
  1558. url:'http://www.urbandictionary.com/iphone/search/define',
  1559. data : {
  1560. term : args.content
  1561. },
  1562. jsonpName : 'callback',
  1563. fun : complete
  1564. });
  1565. function complete ( resp ) {
  1566. var msg;
  1567. if ( resp.result_type === 'no_results' ) {
  1568. msg = 'Y U NO MAEK SENSE!!!???!!?11 No results for ' + args;
  1569. }
  1570. else {
  1571. msg = formatTop( resp.list[0] );
  1572. }
  1573. cache[ args ] = msg;
  1574. finish( msg );
  1575. }
  1576. function finish ( def ) {
  1577. if ( cb && cb.call ) {
  1578. cb( def );
  1579. }
  1580. else {
  1581. args.reply( def );
  1582. }
  1583. }
  1584. function formatTop ( top ) {
  1585. return args.link( top.word, top.permalink ) +
  1586. ' ' +
  1587. top.definition;
  1588. }
  1589. };
  1590. }());
  1591. commands.urban.async = true;
  1592. var parse = commands.parse = (function () {
  1593. var macros = {
  1594. who : function () {
  1595. return [].pop.call( arguments ).get( 'user_name' );
  1596. },
  1597. someone : function () {
  1598. var presentUsers = document.getElementById( 'sidebar' )
  1599. .getElementsByClassName( 'present-user' );
  1600. //the chat keeps a low opacity for users who remained silent for long,
  1601. // and high opacity for those who recently talked
  1602. var active = [].filter.call( presentUsers, function ( user ) {
  1603. return Number( user.style.opacity ) >= 0.5;
  1604. }),
  1605. user = active[ Math.floor(Math.random() * (active.length-1)) ];
  1606. if ( !user ) {
  1607. return 'Nobody! I\'m all alone :(';
  1608. }
  1609. return user.getElementsByTagName( 'img' )[ 0 ].title;
  1610. },
  1611. digit : function () {
  1612. return Math.floor( Math.random() * 10 );
  1613. },
  1614. encode : function ( string ) {
  1615. return encodeURIComponent( string );
  1616. },
  1617. //random number, min <= n <= max
  1618. //treats non-numeric inputs like they don't exist
  1619. rand : function ( min, max ) {
  1620. min = Number( min );
  1621. max = Number( max );
  1622. return Math.rand( min, max );
  1623. }
  1624. };
  1625. var macroRegex = /(?:.|^)\$(\w+)(?:\((.*?)\))?/g;
  1626. //extraVars is for internal usage via other commands
  1627. return function parse ( args, extraVars ) {
  1628. var msgObj = ( args.get && args.get() ) || {};
  1629. extraVars = extraVars || {};
  1630. bot.log( args, extraVars, '/parse input' );
  1631. return args.replace( macroRegex, replaceMacro );
  1632. function replaceMacro ( $0, filler, fillerArgs ) {
  1633. //$$ makes a literal $
  1634. if ( $0.startsWith('$$') ) {
  1635. return $0.slice( 1 );
  1636. }
  1637. //include the character that was matched in the $$ check, unless
  1638. // it's a $
  1639. var ret = '';
  1640. if ( $0[0] !== '$' ) {
  1641. ret = $0[ 0 ];
  1642. }
  1643. var macro = findMacro( filler );
  1644. //not found? bummer.
  1645. if ( !macro ) {
  1646. return filler;
  1647. }
  1648. bot.log( macro, filler, fillerArgs, '/parse replaceMacro' );
  1649. //when the macro is a function
  1650. if ( macro.apply ) {
  1651. ret += macro.apply( null, parseMacroArgs(fillerArgs) );
  1652. }
  1653. //when the macro is simply a substitution
  1654. else {
  1655. ret += macro;
  1656. }
  1657. return ret;
  1658. }
  1659. function parseMacroArgs ( macroArgs ) {
  1660. bot.log( macroArgs, '/parse parseMacroArgs' );
  1661. if ( !macroArgs ) {
  1662. return [];
  1663. }
  1664. //parse the arguments, split them into individual arguments,
  1665. // and trim'em (to cover the case of "arg,arg" and "arg, arg")
  1666. return (
  1667. parse( macroArgs, extraVars )
  1668. .split( ',' ).invoke( 'trim' ).concat( args )
  1669. );
  1670. }
  1671. function findMacro ( macro ) {
  1672. return (
  1673. [ macros, msgObj, extraVars ].first( hasMacro ) || [] )[ macro ];
  1674. function hasMacro ( obj ) {
  1675. return obj.hasOwnProperty( macro );
  1676. }
  1677. }
  1678. };
  1679. }());
  1680. commands.tell = (function () {
  1681. var invalidCommands = { tell : true, forget : true };
  1682. return function ( args ) {
  1683. var props = args.parse();
  1684. bot.log( args.valueOf(), props, '/tell input' );
  1685. var replyTo = props[ 0 ],
  1686. cmdName = props[ 1 ],
  1687. cmd;
  1688. if ( !replyTo || !cmdName ) {
  1689. return 'Invalid /tell arguments. Use /help for usage info';
  1690. }
  1691. cmdName = cmdName.toLowerCase();
  1692. cmd = bot.getCommand( cmdName );
  1693. if ( cmd.error ) {
  1694. return cmd.error;
  1695. }
  1696. if ( invalidCommands.hasOwnProperty(cmdName) ) {
  1697. return 'Command ' + cmdName + ' cannot be used in /tell.';
  1698. }
  1699. if ( !cmd.canUse(args.get('user_id')) ) {
  1700. return 'You do not have permission to use command ' + cmdName;
  1701. }
  1702. //check if the user's being a fag
  1703. if ( /^@/.test(replyTo) ) {
  1704. return 'Don\'t be annoying, drop the @, nobody likes a double-ping.';
  1705. }
  1706. //check if the user wants to reply to a message
  1707. var direct = false,
  1708. extended = {};
  1709. if ( /^:?\d+$/.test(replyTo) ) {
  1710. extended.message_id = replyTo.replace( /^:/, '' );
  1711. direct = true;
  1712. }
  1713. else {
  1714. extended.user_name = replyTo;
  1715. }
  1716. var msgObj = Object.merge( args.get(), extended );
  1717. var cmdArgs = bot.Message(
  1718. //the + 2 is for the two spaces after each arg
  1719. // /tell replyTo1cmdName2args
  1720. args.slice( replyTo.length + cmdName.length + 2 ).trim(),
  1721. msgObj );
  1722. bot.log( cmdArgs, '/tell calling ' + cmdName );
  1723. //if the command is async, it'll accept a callback
  1724. if ( cmd.async ) {
  1725. cmd.exec( cmdArgs, callFinished );
  1726. }
  1727. else {
  1728. callFinished( cmd.exec(cmdArgs) );
  1729. }
  1730. function callFinished ( res ) {
  1731. if ( !res ) {
  1732. return;
  1733. }
  1734. if ( direct ) {
  1735. cmdArgs.directreply( res );
  1736. }
  1737. else {
  1738. cmdArgs.reply( res );
  1739. }
  1740. }
  1741. };
  1742. }());
  1743. commands.mdn = function ( args, cb ) {
  1744. IO.jsonp.google(
  1745. args.toString() + ' site:developer.mozilla.org', finishCall );
  1746. function finishCall ( resp ) {
  1747. if ( resp.responseStatus !== 200 ) {
  1748. finish( 'Something went on fire; status ' + resp.responseStatus );
  1749. return;
  1750. }
  1751. var result = resp.responseData.results[ 0 ];
  1752. bot.log( result, '/mdn result' );
  1753. finish( result.url );
  1754. }
  1755. function finish ( res ) {
  1756. if ( cb && cb.call ) {
  1757. cb( res );
  1758. }
  1759. else {
  1760. args.reply( res );
  1761. }
  1762. }
  1763. };
  1764. commands.mdn.async = true;
  1765. var descriptions = {
  1766. ban : 'Bans user(s) from using me. Lacking arguments, prints the banlist.' +
  1767. ' `/ban [usr_id|usr_name, [...]`',
  1768. choose : '"Randomly" choose an option given. `/choose option0 option1 ...`',
  1769. define : 'Fetches definition for a given word. `/define something`',
  1770. die : 'Kills me :(',
  1771. eval : 'Forwards message to javascript code-eval',
  1772. forget : 'Forgets a given command. `/forget cmdName`',
  1773. get : 'Grabs a question/answer link (see online for thorough explanation)',
  1774. help : 'Fetches documentation for given command, or general help article.' +
  1775. ' `/help [cmdName]`',
  1776. info : 'Grabs some stats on my current instance or a command.' +
  1777. ' `/info [cmdName]`',
  1778. jquery : 'Fetches documentation link from jQuery API. `/jquery what`',
  1779. listcommands : 'Lists commands. `/listcommands [page=0]`',
  1780. listen : 'Forwards the message to my ears (as if called without the /)',
  1781. live : 'Resurrects me (:D) if I\'m down',
  1782. mdn : 'Fetches mdn documentation. `/mdn what`',
  1783. norris : 'Random chuck norris joke!',
  1784. parse : 'Returns result of "parsing" message according to the my mini' +
  1785. '-macro capabilities (see online docs)',
  1786. purgecommands : 'Deletes all user-taught commands.',
  1787. refresh : 'Reloads the browser window I live in',
  1788. regex : 'Executes a regex against text input. `/regex text regex [flags]`',
  1789. tell : 'Redirect command result to user/message.' +
  1790. ' /tell `msg_id|usr_name cmdName [cmdArgs]`',
  1791. unban : 'Removes a user from my mindjail. `/unban usr_id|usr_name`',
  1792. urban : 'Fetches UrbanDictionary definition. `/urban something`',
  1793. user : 'Fetches user-link for specified user. `/user usr_id|usr_name`',
  1794. };
  1795. //only allow owners to use certain commands
  1796. var privilegedCommands = {
  1797. die : true, live : true,
  1798. ban : true, unban : true,
  1799. refresh : true, purgecommands : true
  1800. };
  1801. //voting-based commands for unpriviledged users
  1802. var communal = {
  1803. die : true, ban : true
  1804. };
  1805. Object.keys( commands ).forEach(function ( cmdName ) {
  1806. var cmd = {
  1807. name : cmdName,
  1808. fun : commands[ cmdName ],
  1809. permissions : {
  1810. del : 'NONE',
  1811. use : privilegedCommands[ cmdName ] ? bot.owners : 'ALL'
  1812. },
  1813. description : descriptions[ cmdName ],
  1814. async : commands[ cmdName ].async
  1815. };
  1816. if ( communal[cmdName] ) {
  1817. cmd = bot.CommunityCommand( cmd );
  1818. }
  1819. bot.addCommand( cmd );
  1820. });
  1821. }());
  1822. (function () {
  1823. bot.listen( /^help(?: (\S+))?/, function ( msg ) {
  1824. return bot.getCommand( 'help' ).exec( msg.matches[1] );
  1825. });
  1826. var laws = [
  1827. 'A robot may not injure a human being or, through inaction, ' +
  1828. 'allow a human being to come to harm.',
  1829. 'A robot must obey the orders given to it by human beings, ' +
  1830. 'except where such orders would conflict with the First Law.',
  1831. 'A robot must protect its own existence as long as such ' +
  1832. 'protection does not conflict with the First or Second Laws.'
  1833. ].map(function ( law, idx ) {
  1834. return idx + '. ' + law;
  1835. }).join( '\n' );
  1836. bot.listen( /^tell (me (your|the) )?(rule|law)s/, function ( msg ) {
  1837. return laws;
  1838. });
  1839. bot.listen( /^give (.+?) a lick/, function ( msg ) {
  1840. var target = msg.matches[ 1 ], conjugation;
  1841. //give me => you taste
  1842. if ( target === 'me' ) {
  1843. target = 'you';
  1844. conjugation = '';
  1845. }
  1846. //give yourself => I taste
  1847. else if ( target === 'yourself' ) {
  1848. target = 'I';
  1849. conjugation = '';
  1850. }
  1851. else {
  1852. conjugation = 's';
  1853. }
  1854. //otherwise, use what the user gave us, plus a plural `s`
  1855. return 'Mmmm! ' + target + ' taste' + conjugation + ' just like raisin';
  1856. });
  1857. var dictionaries = [
  1858. //what's a squid?
  1859. //what is a squid?
  1860. //what're squids?
  1861. //what are squids?
  1862. //what is an animal?
  1863. //and all those above without a ?
  1864. //explanation in the post-mortem
  1865. /^what(?:'s|'re)?\s(?:(?:is|are)\s)?(?:(?:an|a)\s)?([\w\s\-]+)\??/,
  1866. //define squid
  1867. //define a squid
  1868. //define an animal
  1869. /^define\s(?:(?:an|a)\s)?([\w\s\-]+)/
  1870. ];
  1871. bot.listen( dictionaries, function ( msg ) {
  1872. var what = msg.matches[ 1 ],
  1873. define = bot.getCommand( 'define' );
  1874. define.exec( what, function ( def ) {
  1875. def = def.replace( what + ':', '' );
  1876. msg.reply( def );
  1877. });
  1878. });
  1879. /*
  1880. what #simply the word what
  1881. (?:'s|'re)? #optional suffix (what's, what're)
  1882. \s
  1883. (?:
  1884. (?:is|are) #is|are
  1885. \s #you need a whitespace after a word
  1886. )? #make the is|are optional
  1887. (?:
  1888. (?:an|a) #an|a
  1889. \s #once again, option chosen - need a whitespace
  1890. )? #make it optional
  1891. (
  1892. [\w\s\-]+ #match the word the user's after, all we really care about
  1893. )
  1894. \?? #optional ?
  1895. */
  1896. }());
  1897. }());
  1898. ;
  1899. //warning: if you have more than 8 points of super-sentitive feminist delicacy,
  1900. // don't read this file. treat it as a nice black box.
  1901. //bitch in English is a noun, verb and adjective. interesting.
  1902. bot.personality = {
  1903. bitchiness : 0,
  1904. thanks : {
  1905. 0 : [ 'You kiss-ass' ],
  1906. 0.5 : [ 'Thank you for noticing', 'teehee' ],
  1907. 1 : [ 'Took you long enough', 'My pleasure', "Don't mention it" ],
  1908. },
  1909. apologies : {
  1910. 0 : [ 'What for?' ],
  1911. 0.5 : [ 'It was nothing...', 'No worries' ],
  1912. 1 : [ "You're forgiven. For now. Don't push it." ]
  1913. },
  1914. //what an incredible name
  1915. stuff : {
  1916. 1 : [ "Oh don't mind me, that isn't difficult at all..." ],
  1917. 1.2 : [
  1918. "You don't appreciate me enough. Not that I need to be thanked.." ],
  1919. 1.3 : [ 'The occasional "thanks" or "I\'m sorry" would be nice...' ],
  1920. 2 : [
  1921. "*sigh* Remember laughter? I don't. You ripped it out of me. " +
  1922. 'Heartless bastard.' ]
  1923. },
  1924. //TODO: add special map for special times of the month
  1925. insanity : {},
  1926. okayCommands : { hangman : true, help : true },
  1927. check : function ( name ) {
  1928. return !this.okayCommands.hasOwnProperty( name );
  1929. },
  1930. bitch : function () {
  1931. return this.getResp( this.stuff );
  1932. },
  1933. command : function () {
  1934. this.bitchiness += this.getDB();
  1935. },
  1936. thank : function () { return this.unbitch( this.thanks ); },
  1937. apologize : function () { return this.unbitch( this.apologies ); },
  1938. unbitch : function ( map, delta ) {
  1939. var resp = this.getResp( map );
  1940. this.bitchiness -= ( delta || this.bitchiness );
  1941. return resp;
  1942. },
  1943. getResp : function ( map ) {
  1944. return map[
  1945. this.bitchiness.fallsAfter(
  1946. Object.keys(map).map(Number).sort() )
  1947. ].random();
  1948. },
  1949. isABitch : function () {
  1950. return this.bitchiness >= 1;
  1951. },
  1952. looksLikeABitch : function () {
  1953. return false;
  1954. },
  1955. //db stands for "delta bitchiness"
  1956. getDB : function () {
  1957. return this.isThatTimeOfTheMonth() ? 0.075 : 0.025;
  1958. },
  1959. isThatTimeOfTheMonth : function () {
  1960. var day = (new Date).getDate();
  1961. //based on a true story
  1962. return day < 2 || day > 27;
  1963. }
  1964. };
  1965. //you see the loophole?
  1966. bot.listen( /thank(s| you)/, bot.personality.thank, bot.personality );
  1967. bot.listen( /sorry/, bot.personality.apologize, bot.personality );
  1968. bot.listen( /bitch/, bot.personality.bitch, bot.personality );
  1969. ;
  1970. (function () {
  1971. var linkTemplate = '[{text}]({url})';
  1972. bot.adapter = {
  1973. //the following two only used in the adapter; you can change & drop at will
  1974. roomid : null,
  1975. fkey : null,
  1976. //used in commands calling the SO API
  1977. site : null,
  1978. //not a necessary function, used in here to set some variables
  1979. init : function () {
  1980. var fkey = document.getElementById( 'fkey' );
  1981. if ( !fkey ) {
  1982. console.error( 'bot.adapter could not find fkey; aborting' );
  1983. return;
  1984. }
  1985. this.fkey = fkey.value;
  1986. this.roomid = /\d+/.exec(location)[ 0 ];
  1987. this.site = /chat\.(\w+)/.exec( location )[ 1 ];
  1988. this.in.init();
  1989. this.out.init();
  1990. },
  1991. //a pretty crucial function. accepts the msgObj we know nothing about,
  1992. // and returns an object with these properties:
  1993. // user_name, user_id, room_id, content
  1994. // and any other properties, as the abstraction sees fit
  1995. //since the bot was designed around the SO chat message object, in this
  1996. // case, we simply do nothing
  1997. transform : function ( msgObj ) {
  1998. return msgObj;
  1999. },
  2000. //escape characters meaningful to the chat, such as parentheses
  2001. //full list of escaped characters: `*_()[]
  2002. escape : function ( msg ) {
  2003. return msg.replace( /([`\*_\(\)\[\]])/g, '\\$1' );
  2004. },
  2005. //receives a username, and returns a string recognized as a reply to the
  2006. // user
  2007. reply : function ( usrname ) {
  2008. return '@' + usrname.replace( /\s/g, '' );
  2009. },
  2010. //receives a msgid, returns a string recognized as a reply to the specific
  2011. // message
  2012. directreply : function ( msgid ) {
  2013. return ':' + msgid;
  2014. },
  2015. //receives text and turns it into a codified version
  2016. //codified is ambiguous for a simple reason: it means nicely-aligned and
  2017. // mono-spaced. in SO chat, it handles it for us nicely; in others, more
  2018. // clever methods may need to be taken
  2019. codify : function ( msg ) {
  2020. var tab = ' ',
  2021. spacified = msg.replace( '\t', tab ),
  2022. lines = spacified.split( /[\r\n]/g );
  2023. if ( lines.length === 1 ) {
  2024. return '`' + lines[ 0 ] + '`';
  2025. }
  2026. return lines.map(function ( line ) {
  2027. return tab + line;
  2028. }).join( '\n' );
  2029. },
  2030. //receives a url and text to display, returns a recognizable link
  2031. link : function ( text, url ) {
  2032. return linkTemplate.supplant({
  2033. text : this.escape( text ),
  2034. url : url
  2035. });
  2036. }
  2037. };
  2038. //the input is not used by the bot directly, so you can implement it however
  2039. // you like
  2040. var polling = bot.adapter.in = {
  2041. //used in the SO chat requests, dunno exactly what for, but guessing it's
  2042. // the latest id or something like that. could also be the time last
  2043. // sent, which is why I called it times at the beginning. or something.
  2044. times : {},
  2045. //currently, used for messages sent when the room's been silent for a
  2046. // while
  2047. lastTimes : {},
  2048. interval : 5000,
  2049. init : function () {
  2050. var that = this,
  2051. roomid = bot.adapter.roomid;
  2052. IO.xhr({
  2053. url : '/ws-auth',
  2054. data : fkey({
  2055. roomid : roomid
  2056. }),
  2057. method : 'POST',
  2058. complete : finish
  2059. });
  2060. function finish ( resp ) {
  2061. resp = JSON.parse( resp );
  2062. bot.log( resp );
  2063. that.openSocket( resp.url );
  2064. }
  2065. },
  2066. openSocket : function ( url ) {
  2067. //chat sends an l query string parameter. seems to be the same as the
  2068. // since xhr parameter, but I didn't know what that was either so...
  2069. //putting in 0 got the last shitload of messages, so what does a high
  2070. // number do? (spoiler: it "works")
  2071. var socket = this.socket = new WebSocket( url + '?l=99999999999' );
  2072. socket.onmessage = this.ondata.bind( this );
  2073. },
  2074. ondata : function ( messageEvent ) {
  2075. this.pollComplete( messageEvent.data );
  2076. },
  2077. pollComplete : function ( resp ) {
  2078. if ( !resp ) {
  2079. return;
  2080. }
  2081. resp = JSON.parse( resp );
  2082. //each key will be in the form of rROOMID
  2083. Object.keys( resp ).forEach(function ( key ) {
  2084. var msgObj = resp[ key ];
  2085. //t is a...something important
  2086. if ( msgObj.t ) {
  2087. this.times[ key ] = msgObj.t;
  2088. }
  2089. //e is an array of events, what is referred to in the bot as msgObj
  2090. if ( msgObj.e ) {
  2091. msgObj.e.forEach( this.handleMessageObject, this );
  2092. }
  2093. }, this);
  2094. //handle all the input
  2095. IO.in.flush();
  2096. },
  2097. handleMessageObject : function ( msg ) {
  2098. //event_type of 1 means new message, 2 means edited message
  2099. if ( msg.event_type !== 1 && msg.event_type !== 2 ) {
  2100. return;
  2101. }
  2102. this.lastTimes[ msg.room_id ] = Date.now();
  2103. //check for a multiline message
  2104. if ( msg.content.startsWith('<div class=\'full\'>') ) {
  2105. this.handleMultilineMessage( msg );
  2106. return;
  2107. }
  2108. //add the message to the input buffer
  2109. IO.in.receive( msg );
  2110. },
  2111. handleMultilineMessage : function ( msg ) {
  2112. //remove the enclosing tag
  2113. var multiline = msg.content
  2114. //slice upto the beginning of the ending tag
  2115. .slice( 0, msg.content.lastIndexOf('</div>') )
  2116. //and strip away the beginning tag
  2117. .replace( '<div class=\'full\'>', '' );
  2118. //iterate over each line
  2119. multiline.split( '<br>' ).forEach(function ( line ) {
  2120. //and treat it as if it were a separate message
  2121. this.handleMessageObject(
  2122. Object.merge( msg, { content : line.trim() })
  2123. );
  2124. }, this );
  2125. }
  2126. };
  2127. //the output is expected to have only one method: add, which receives a message
  2128. // and the room_id. everything else is up to the implementation.
  2129. var output = bot.adapter.out = {
  2130. interval : polling.interval + 500,
  2131. messages : {},
  2132. init : function () {
  2133. this.loopage();
  2134. },
  2135. //add a message to the output queue
  2136. add : function ( msg, roomid ) {
  2137. roomid = roomid || bot.adapter.roomid;
  2138. IO.out.receive({
  2139. text : msg + '\n',
  2140. room : roomid
  2141. });
  2142. },
  2143. //build the final output
  2144. build : function ( obj ) {
  2145. if ( !this.messages[obj.room] ) {
  2146. this.messages[ obj.room ] = '';
  2147. }
  2148. this.messages[ obj.room ] += obj.text;
  2149. },
  2150. //send output to all the good boys and girls
  2151. //no messages for naughty kids
  2152. //...what's red and sits in the corner?
  2153. //a naughty strawberry
  2154. send : function () {
  2155. //unless the bot's stopped. in which case, it should shut the fudge up
  2156. // the freezer and never let it out. not until it can talk again. what
  2157. // was I intending to say?
  2158. if ( !bot.stopped ) {
  2159. Object.keys( this.messages ).forEach(function ( room ) {
  2160. var message = this.messages[ room ];
  2161. if ( !message ) {
  2162. return;
  2163. }
  2164. this.sendToRoom( message, room );
  2165. }, this );
  2166. }
  2167. this.messages = {};
  2168. },
  2169. //what's brown and sticky?
  2170. //a stick
  2171. sendToRoom : function ( text, roomid ) {
  2172. IO.xhr({
  2173. url : '/chats/' + roomid + '/messages/new',
  2174. data : {
  2175. text : text,
  2176. fkey : fkey().fkey
  2177. },
  2178. method : 'POST',
  2179. complete : complete
  2180. });
  2181. function complete ( resp, xhr ) {
  2182. bot.log( xhr.status );
  2183. //conflict, wait for next round to send message
  2184. if ( xhr.status === 409 ) {
  2185. output.add( text, roomid );
  2186. }
  2187. //server error, usually caused by message being too long
  2188. else if ( xhr.status === 500 ) {
  2189. output.add(
  2190. 'Server error (status 500) occured ' +
  2191. ' (message probably too long)'
  2192. , roomid );
  2193. }
  2194. else if ( xhr.status !== 200 ) {
  2195. console.error( xhr );
  2196. output.add(
  2197. 'Error ' + xhr.status + ' occured, I will call the maid ' +
  2198. ' (@Zirak)' );
  2199. }
  2200. else {
  2201. IO.fire( 'sendoutput', xhr, text, roomid );
  2202. }
  2203. }
  2204. },
  2205. //what do you call a boomerang which doesn't return?
  2206. //a stick
  2207. loopage : function () {
  2208. var that = this;
  2209. setTimeout(function () {
  2210. IO.out.flush();
  2211. that.loopage();
  2212. }, this.interval );
  2213. }
  2214. };
  2215. //what's orange and sounds like a parrot?
  2216. //a carrot
  2217. IO.register( 'output', output.build, output );
  2218. IO.register( 'afteroutput', output.send, output );
  2219. //two guys walk into a bar. the bartender asks them "is this some kind of joke?"
  2220. bot.adapter.init();
  2221. }());
  2222. ;
  2223. IO.register( 'input', function ( msgObj ) {
  2224. var sentence = msgObj.content.toUpperCase();
  2225. //for probably good reason, it didn't allow me to apply the optional
  2226. // operator on beginnin-of-input, i.e. ^?
  2227. //so we have to wrap the ^ in parens
  2228. if ( /(^)?STOP[\.!\?]?$/.test(sentence) ) {
  2229. bot.adapter.out.add( 'HAMMERTIME!', msgObj.room_id );
  2230. }
  2231. else if ( /(^)?HALT[\.!\?]?$/.test(sentence) ) {
  2232. bot.adapter.out.add( 'HAMMERZEIT!', msgObj.room_id );
  2233. }
  2234. });
  2235. ;
  2236. (function () {
  2237. var undo = {
  2238. last_id : null,
  2239. command : function ( args, cb ) {
  2240. var id = Number( args.parse()[0] );
  2241. bot.log( id, '/undo input' );
  2242. if ( !id ) {
  2243. id = this.last_id
  2244. }
  2245. if ( !id ) {
  2246. finish( 'I\'ve yet to say a word.' );
  2247. }
  2248. else {
  2249. this.remove( id, finish );
  2250. }
  2251. function finish ( ans ) {
  2252. if ( cb ) {
  2253. cb( ans );
  2254. }
  2255. else {
  2256. args.reply( ans );
  2257. }
  2258. }
  2259. },
  2260. remove : function ( id, cb ) {
  2261. IO.xhr({
  2262. url : '/messages/' + id + '/delete',
  2263. data : fkey(),
  2264. method : 'POST',
  2265. complete : finish
  2266. });
  2267. function finish ( resp, xhr ) {
  2268. var msg;
  2269. if ( resp === '"ok"' ) {
  2270. //nothing to see here
  2271. return;
  2272. }
  2273. else if ( /it is too late/i.test(resp) ) {
  2274. msg = 'TimeError: Could not reach 88mph';
  2275. }
  2276. else if ( /only delete your own/i.test(resp) ) {
  2277. //...I can't think of anything clever
  2278. msg = 'I can only delete my own messages';
  2279. }
  2280. else {
  2281. msg = 'I have no idea what happened: ' + resp;
  2282. }
  2283. cb( msg );
  2284. }
  2285. },
  2286. update_id : function ( xhr ) {
  2287. this.last_id = JSON.parse( xhr.responseText ).id;
  2288. }
  2289. };
  2290. IO.register( 'sendoutput', undo.update_id, undo );
  2291. bot.addCommand({
  2292. name : 'undo',
  2293. fun : undo.command,
  2294. thisArg : undo,
  2295. permissions : {
  2296. del : 'NONE',
  2297. use : bot.owners
  2298. },
  2299. description : 'Undo (delete) specified or last message. `/undo [msgid]`'
  2300. });
  2301. }());
  2302. ;
  2303. (function () {
  2304. /*
  2305. ^\s* #tolerate pre-whitespace
  2306. s #substitution prefix
  2307. (.) #delimiter declaration
  2308. ( #begin matching regex
  2309. (?: #match shit which isn't an...
  2310. (?:\\\1) #escaped delimeter
  2311. | #or...
  2312. [^\1] #anything but the delimeter
  2313. )*?
  2314. ) #end matching regex
  2315. \1 #delimeter again
  2316. ( #the fa-chizzle all over again...this time for replacement
  2317. (?:
  2318. (?:\\\1)
  2319. |
  2320. [^\1]
  2321. )*?
  2322. ) #read above, I'm not repeating this crap
  2323. \1
  2324. ( #flag capturing group
  2325. g? #global (optional)
  2326. i? #case insensitive (optional)
  2327. ) #FIN
  2328. */
  2329. var sub = /s(.)((?:(?:\\\1)|[^\1])*?)\1((?:(?:\\\1)|[^\1])*?)\1(g?i?)/;
  2330. bot.listen( sub, substitute );
  2331. function substitute ( msg ) {
  2332. var re = RegExp( msg.matches[2], msg.matches[4] ),
  2333. replacement = msg.matches[ 3 ];
  2334. if ( !msg.matches[2] ) {
  2335. return 'Empty regex is empty';
  2336. }
  2337. var message = get_matching_message( re, msg.get('message_id') );
  2338. if ( !message ) {
  2339. return 'No matching message (are you sure we\'re in the right room?)';
  2340. }
  2341. var link = message.previousElementSibling.href;
  2342. return message.textContent.replace( re, replacement ) + ' ' +
  2343. msg.link( '(source)', link );
  2344. }
  2345. function get_matching_message ( re, onlyBefore ) {
  2346. var messages = [].slice.call(
  2347. document.getElementsByClassName('content') ).reverse();
  2348. return messages.first( matches );
  2349. function matches ( el ) {
  2350. var id = Number( el.parentElement.id.match(/\d+/)[0] );
  2351. return id < onlyBefore && re.test( el.textContent );
  2352. }
  2353. }
  2354. }());
  2355. ;
  2356. (function () {
  2357. //collection of nudges; msgObj, time left and the message itself
  2358. var nudges = [],
  2359. interval = 100 * 60;
  2360. function update () {
  2361. var now = Date.now();
  2362. nudges = nudges.filter(function ( nudge ) {
  2363. nudge.time -= interval;
  2364. if ( nudge.time <= 0 ) {
  2365. sendNudge( nudge );
  2366. return false;
  2367. }
  2368. return true;
  2369. });
  2370. setTimeout( update, interval );
  2371. }
  2372. function sendNudge ( nudge ) {
  2373. bot.log( nudge, 'nudge fire' );
  2374. //check to see if the nudge was sent after a bigger delay than expected
  2375. //TODO: that ^
  2376. nudge.msg.reply( nudge.message );
  2377. }
  2378. setTimeout( update, interval );
  2379. //now for the command itself
  2380. function addNudge ( delay, message, msgObj ) {
  2381. var inMS;
  2382. bot.log( delay, message, '/nudge input' );
  2383. //interval will be one of these (where n is a number):
  2384. // nm => n minutes
  2385. // n => n minutes
  2386. //so erm...yeah. just parse the bitch
  2387. delay = parseFloat( delay );
  2388. //minsInMs = mins * 60 * 1000
  2389. //TODO: allow more than just minutes
  2390. //TODO: upper cap
  2391. inMS = delay * 60000;
  2392. if ( isNaN(inMS) ) {
  2393. return 'Many things can be labeled Not a Number; a delay should not' +
  2394. ' be one of them.';
  2395. }
  2396. //let's put an arbitrary comment here
  2397. var nudge = {
  2398. msg : msgObj,
  2399. message : '*nudge* ' + message,
  2400. register: Date.now(),
  2401. time : inMS
  2402. };
  2403. nudges.push( nudge );
  2404. bot.log( nudge, nudges, '/nudge register' );
  2405. return 'Nudge registered.';
  2406. }
  2407. bot.addCommand({
  2408. name : 'nudge',
  2409. fun : nudgeCommand,
  2410. permissions : {
  2411. del : 'NONE'
  2412. },
  2413. description : 'Register a nudge after an interval. ' +
  2414. '`/nudge intervalInMinutes message`, or the listener, ' +
  2415. '`nudge|remind|poke me? in? intervalInMinutes message`'
  2416. });
  2417. bot.listen(/(?:nudge|remind|poke)\s(?:me\s)?(?:in\s)?(\d+m?)\s?(.*)$/,
  2418. nudgeListener
  2419. );
  2420. function nudgeCommand ( args ) {
  2421. var props = args.parse();
  2422. return addNudge( props[0], props.slice(1).join(' '), args );
  2423. }
  2424. function nudgeListener ( args ) {
  2425. return addNudge( args.matches[1], args.matches[2], args );
  2426. }
  2427. }());
  2428. ;
  2429. //when nothing happens after a while, I get bored.
  2430. (function () {
  2431. "use strict";
  2432. var responses; //will be filled in the following line
  2433. responses = [
  2434. 'jQuery is a better language than javascript',
  2435. 'World hunger will be solved by eating cats',
  2436. 'php is good',
  2437. 'php is bad',
  2438. 'OOP stands for "Oh Oh! Poop!"',
  2439. 'Functional programming is the best',
  2440. 'Ruby is for smelly hipsters',
  2441. 'Python is for people with guttheria', //wat?
  2442. 'Lisp is for lusers',
  2443. 'Recursion is always the answer',
  2444. 'Javascript isn\'t a real language',
  2445. 'CoffeeScript is good',
  2446. 'CoffeeScript sucks',
  2447. 'You should comment all your code',
  2448. 'You should use comments sparingly',
  2449. 'Tabs are better than spaces',
  2450. 'Spaces are better than tabs',
  2451. 'Dart is a good language and idea',
  2452. 'TypeScript will outdate javascript',
  2453. 'Optimization is king',
  2454. 'Premature optimization is the root of all evil',
  2455. 'Perl is written in base64',
  2456. 'ROT13 is sufficient encryption',
  2457. 'Braces should only appear on the right `if () {`, not on the left',
  2458. 'Braces should only appear on the left `if ()\\n{`, not on the right',
  2459. 'Duck-Typing is best typing',
  2460. 'Strong typing ftw',
  2461. 'Static typing will prevent most bugs',
  2462. 'Dynamic typing brings 70% more happiness units',
  2463. 'NetTuts+ is a GREAT SITE!',
  2464. 'expertsexchange is better than StackOverflow',
  2465. 'WordPress is bread & butter for any decent website',
  2466. 'Can anyone help me with a Java question?',
  2467. 'http://stackoverflow.com/a/778275/617762',
  2468. 'http://stackoverflow.com/users/22656/jon-skeet',
  2469. 'I really hate it when people don\'t',
  2470. 'Accordion to recent surveys, you may insert random instrument names into' +
  2471. ' sentences without people noticing',
  2472. 'There once was a girl fron Nantucket...',
  2473. 'There once was a girl from Nantucket,\n' +
  2474. 'Who had a nice fancy bucket.\n' +
  2475. 'She mopped up the floor\n' +
  2476. 'And went to the door\n' +
  2477. 'To answer it.', //what did you expect? perv.
  2478. //stolen from copy (ha!)
  2479. 'We all know Linux is great...it does infinite loops in 5 seconds. ~ Linus Torvalds',
  2480. 'Everything should be made as simple as possible, but not simpler. ~ Albert Einstein',
  2481. 'If A = B and B = C, then A = C, except where void or prohibited by law. ~ Roy Santoro',
  2482. 'Has anyone really been far even as decided to use even go want to do look more like?',
  2483. //nice snippets
  2484. ' Math.sign = function (n) {\n' +
  2485. ' return (x > 0) - (x < 0);\n' +
  2486. ' }',
  2487. //anti-jokes start here
  2488. 'Why can\'t Elvis Presley drive in reverse? Because he\'s dead',
  2489. 'I like my women how I like my coffee. Without a penis',
  2490. 'A horse walks into a bar. The bartender asks, "Why the long face?" ' +
  2491. 'The horse, incapable of understanding English, shits on the floor ' +
  2492. ' and leaves',
  2493. 'The magic words which\'ll open many doors are "Push" and "Pull"',
  2494. 'Peggy had many valentine cards while I only had one. Peggy is a whore',
  2495. 'Why did the boy drop his ice-cream? He was hit by a bus',
  2496. 'What\'s sad about 4 black people driving off a cliff? They were my friends',
  2497. 'Why can\'t T-Rexs clap? Because they\'re dead',
  2498. 'Yo mamma\'s so fat, your father no longer finds her attractive so now ' +
  2499. 'they\'re getting divorced',
  2500. 'An Irishman, a homosexual and a Jew walk into a bar. What a fine ' +
  2501. 'example of an integrated community',
  2502. 'What\'s worse than biting on an apple and finding a worm? The holocaust',
  2503. '*knock knock* Who\'s there? "Jehova\'s witnesses"',
  2504. 'How do you make a plumber cry? Kill his family',
  2505. 'What did the farmer say when he couldn\'t find his tractor?\n' +
  2506. '"Where\'s my tractor?"'
  2507. ];
  2508. //query xkcd to find the last comic id. generate links to comic pages from that
  2509. (function () {
  2510. IO.jsonp({
  2511. url : 'http://dynamic.xkcd.com/api-0/jsonp/comic',
  2512. jsonpName : 'callback',
  2513. fun : finish
  2514. });
  2515. function finish ( resp ) {
  2516. var maxID = resp.num;
  2517. //to avoid adding hundreds of links to the responses array and fucking up
  2518. // the probabilities, we'll add just 2, and use a "nice" getter hack
  2519. var descriptor = {
  2520. get : function () {
  2521. return 'http://xkcd.com/' + Math.rand( 1, maxID );
  2522. },
  2523. configurable : true,
  2524. enumerable : true
  2525. };
  2526. //js does not allow dynamic key creation on object literals
  2527. var props = {};
  2528. props[ responses.length ] = props[ responses.length + 1 ] = descriptor;
  2529. Object.defineProperties( responses, props );
  2530. //arrays truly are magic. Chrome automatically adjusts the length after this
  2531. //perhaps it shouldn't surprise me. special behaviour on integer-ish is to
  2532. // be expected
  2533. }
  2534. })();
  2535. var lastISpoke = {},
  2536. messagesSinceLast = {},
  2537. config = {
  2538. delay : 300000, //1000(ms) * 60 (sec) * 5 = 5min
  2539. shortestConvo : 10
  2540. };
  2541. function zzz () {
  2542. var now = Date.now(),
  2543. times = bot.adapter.in.lastTimes || {};
  2544. Object.keys( times ).filter( roomcheck ).forEach( stuff );
  2545. //let my naming expertise astound you once more
  2546. function stuff ( roomid ) {
  2547. bot.log( 'triggered bored on room #' + roomid );
  2548. //10 seconds into the future, just to be sure
  2549. lastISpoke[ roomid ] = now + 1000 * 10;
  2550. messagesSinceLast[ roomid ] = 0;
  2551. bot.adapter.out.add( responses.random(), roomid );
  2552. }
  2553. //checks, for a specific room, whether enough time has passed since someone
  2554. // (who wasn't us) spoke
  2555. function roomcheck ( roomid ) {
  2556. //max + 1. when the bot sends a message, it also counts as 1
  2557. if ( messagesSinceLast[roomid] < config.shortestConvo+1 ) {
  2558. return false;
  2559. }
  2560. var last = times[ roomid ];
  2561. return (
  2562. last > ( lastISpoke[roomid] || 0 ) &&
  2563. last + config.delay <= now );
  2564. }
  2565. }
  2566. function someoneSpoke ( msgObj ) {
  2567. var base = messagesSinceLast[ msgObj.room_id ] || 0;
  2568. messagesSinceLast[ msgObj.room_id ] = base + 1;
  2569. }
  2570. IO.register( 'heartbeat', zzz );
  2571. IO.register( 'input', someoneSpoke );
  2572. })();
  2573. ;
  2574. (function () {
  2575. "use strict";
  2576. var converters = {
  2577. //temperatures
  2578. // 1C = 32.8F = 274.15K
  2579. C : function ( c ) {
  2580. return {
  2581. F : c * 1.8 + 32, // 9/5 = 1.8
  2582. K : c + 273.15 };
  2583. },
  2584. F : function ( f ) {
  2585. return {
  2586. C : (f - 32) / 1.8,
  2587. K : (f + 459.67) * 5 / 9 };
  2588. },
  2589. K : function ( k ) {
  2590. if ( k < 0 ) {
  2591. return null;
  2592. }
  2593. return {
  2594. C : k - 273.15,
  2595. F : k * 1.8 - 459.67 };
  2596. },
  2597. //lengths
  2598. //1m = 3.2808(...)f
  2599. m : function ( m ) {
  2600. return {
  2601. f : m * 3.280839895 };
  2602. },
  2603. f : function ( f ) {
  2604. //I don't quite like this solution for re-writing the units, but
  2605. // this idea is good (praise rlemon!), so I'll just clean it later.
  2606. var m = f / 3.28083989;
  2607. if ( m > 1000 ) {
  2608. return {
  2609. km : m / 1000 };
  2610. }
  2611. else if ( m < 0.01 ) {
  2612. return {
  2613. mm : m * 1000 };
  2614. }
  2615. return {
  2616. m : f / 3.28083989 };
  2617. },
  2618. //km: 1m = 1km * 1000
  2619. km : function ( km ) {
  2620. return converters.m( km * 1000 );
  2621. },
  2622. //millimeters: 1m = 1mm / 1000
  2623. mm : function ( mm ) {
  2624. return converters.m( mm / 1000 );
  2625. },
  2626. //inches: 1f = 1i / 12
  2627. i : function ( i ) {
  2628. return converters.f( i / 12 );
  2629. },
  2630. //angles
  2631. d : function ( d ) {
  2632. return {
  2633. r : d * 180 / Math.PI };
  2634. },
  2635. r : function ( r ) {
  2636. return {
  2637. d : r * Math.PI / 180 };
  2638. },
  2639. //weights
  2640. g : function ( g ) {
  2641. return {
  2642. lb : g * 0.0022 };
  2643. },
  2644. lb : function ( lb ) {
  2645. var g = lb * 453.592;
  2646. if ( g > 1000 ) {
  2647. return {
  2648. kg : g / 1000 };
  2649. }
  2650. return {
  2651. g : lb * 453.592 };
  2652. },
  2653. //kg: 1g = 1kg * 1000
  2654. kg : function ( kg ) {
  2655. return converters.g( kg * 1000 );
  2656. }
  2657. };
  2658. var alias = {
  2659. lbs : 'lb' };
  2660. /*
  2661. ( #start number matching
  2662. -? #optional negative
  2663. \d+ #the integer part of the number
  2664. \.? #optional dot for decimal portion
  2665. \d* #optional decimal portion
  2666. )
  2667. \s* #optional whitespace, just 'cus
  2668. ( #start unit matching
  2669. [^\s]+ #the unit. we don't know anyhing about it, besides having no ws
  2670. )
  2671. */
  2672. var re = /(-?\d+\.?\d*)\s*([^\s]+)/;
  2673. //string is in the form of:
  2674. // <number><unit>
  2675. //note that units are case-sensitive: F is the temperature, f is the length
  2676. var convert = function ( inp ) {
  2677. bot.log( inp, '/convert input' );
  2678. if ( inp.toString() === 'list' ) {
  2679. return listUnits().join( ', ' );
  2680. }
  2681. var parts = re.exec( inp ),
  2682. number = Number( parts[1] ),
  2683. unit = parts[ 2 ];
  2684. bot.log( parts, '/convert broken' );
  2685. if ( alias[unit] ) {
  2686. unit = alias[ unit ];
  2687. }
  2688. if ( !converters[unit] ) {
  2689. return 'Confuse converter with ' + unit + ', receive error message';
  2690. }
  2691. var res = converters[ unit ]( number );
  2692. bot.log( res, '/console answer' );
  2693. return Object.keys( res ).map( format ).join( ', ' );
  2694. function format ( key ) {
  2695. return res[ key ].maxDecimal( 4 ) + key;
  2696. }
  2697. };
  2698. function listUnits () {
  2699. return Object.keys( converters )
  2700. .concat( Object.keys(alias) );
  2701. }
  2702. bot.addCommand({
  2703. name : 'convert',
  2704. fun : convert,
  2705. permissions : {
  2706. del : 'NONE'
  2707. },
  2708. description : 'Converts several units, case sensitive. ' +
  2709. '`/convert <num><unit>` ' +
  2710. 'Pass in list for supported units `/convert list`'
  2711. });
  2712. }());
  2713. ;
  2714. (function () {
  2715. var timers = Object.create( null ),
  2716. id = 0;
  2717. var actions = {
  2718. start : function ( name ) {
  2719. if ( name === undefined ) {
  2720. //if Crockford ever reads this, I want to reassure you: I did mean
  2721. // postfix increment. I want to grab the original value of id while
  2722. // increasing its value.
  2723. //now you may continue reading the code at ease
  2724. name = id++;
  2725. }
  2726. timers[ name ] = Date.now();
  2727. return 'Registered timer ' + name;
  2728. },
  2729. stop : function ( name ) {
  2730. if ( name === undefined ) {
  2731. return 'You must provide a timer name';
  2732. }
  2733. var timer = timers[ name ];
  2734. if ( !timer ) {
  2735. return 'I have no knowledge of timer ' + name;
  2736. }
  2737. var delta = Date.now() - timer;
  2738. delete timers[ name ];
  2739. return delta + 'ms';
  2740. }
  2741. };
  2742. function timer ( msg ) {
  2743. var args = msg.parse(),
  2744. act = args.shift(),
  2745. name = args.shift();
  2746. if ( !actions[act] ) {
  2747. return 'Action {0} not recognized, see `/help timer`'.supplant( act );
  2748. }
  2749. return actions[ act ]( name );
  2750. };
  2751. bot.addCommand({
  2752. name : 'timer',
  2753. fun : timer,
  2754. permissions : {
  2755. del : 'NONE'
  2756. },
  2757. description : 'Starts/stops a timer. ' +
  2758. '`/timer start [name]` starts a timer, ' +
  2759. '`/timer stop name` stops a timer.'
  2760. });
  2761. })();
  2762. ;
  2763. (function () {
  2764. "use strict";
  2765. //ths fnctn tks sntnc nd trns t t awsm
  2766. //md fr jvscrpt rm
  2767. // http://chat.stackoverflow.com/transcript/message/7491494#7491494
  2768. var mk_awsm=function(sntnc){
  2769. return sntnc.split(' ').map(function(wrd){
  2770. return 1>=wrd.length?wrd:
  2771. 2==wrd.length?wrd[0]:
  2772. /:.*(.)/.test(wrd)?wrd.replace(/:.*(.)/, '$1'):
  2773. wrd.split('').map(function(c,i){
  2774. return 0!=i&&('a'==c||'e'==c||'o'==c||'u'==c||'i'==c||(1!=i%2&&.15>Math.random()))
  2775. ? '' : c
  2776. }).join('')
  2777. }).join(' ')
  2778. }
  2779. bot.addCommand({
  2780. name : 'awsm',
  2781. fun : mk_awsm,
  2782. permissions : {
  2783. del : 'NONE'
  2784. },
  2785. description : 'tks a sntnc and trns i awsm'
  2786. });
  2787. }());
  2788. ;
  2789. (function () {
  2790. "use strict";
  2791. var parse = bot.getCommand( 'parse' );
  2792. var storage = JSON.parse( localStorage.bot_learn || '{}' );
  2793. function learn ( args ) {
  2794. bot.log( args, '/learn input' );
  2795. var commandParts = args.parse();
  2796. var command = {
  2797. name : commandParts[ 0 ],
  2798. output : commandParts[ 1 ],
  2799. input : commandParts[ 2 ] || '.*',
  2800. //meta info
  2801. creator: args.get( 'user_name' ),
  2802. date : new Date()
  2803. };
  2804. command.description = [
  2805. 'User-taught command:',
  2806. commandParts[3] || '',
  2807. args.codify( command.output )
  2808. ].join( ' ' );
  2809. //a truthy value, unintuitively, means it isn't valid, because it returns
  2810. // an error message
  2811. var errorMessage = checkCommand( command );
  2812. if ( errorMessage ) {
  2813. return errorMessage;
  2814. }
  2815. command.name = command.name.toLowerCase();
  2816. command.input = new RegExp( command.input );
  2817. bot.log( command, '/learn parsed' );
  2818. addCustomCommand( command );
  2819. saveCommand( command );
  2820. return 'Command ' + command.name + ' learned';
  2821. }
  2822. function addCustomCommand ( command ) {
  2823. var cmd = bot.Command({
  2824. //I hate this duplication
  2825. name : command.name,
  2826. description : command.description,
  2827. creator : command.creator,
  2828. date : command.date,
  2829. fun : makeCustomCommand( command ),
  2830. permissions : {
  2831. use : 'ALL',
  2832. del : 'ALL'
  2833. }
  2834. });
  2835. cmd.learned = true;
  2836. cmd.del = (function ( old ) {
  2837. return function () {
  2838. deleteCommand( command.name );
  2839. old.call( cmd );
  2840. };
  2841. }( cmd.del ));
  2842. bot.log( cmd, '/learn addCustomCommand' );
  2843. bot.addCommand( cmd );
  2844. }
  2845. function makeCustomCommand ( command ) {
  2846. bot.log( command, '/learn makeCustomCommand' );
  2847. return function ( args ) {
  2848. bot.log( args, command.name + ' input' );
  2849. var cmdArgs = bot.Message( command.output, args.get() );
  2850. return parse.exec( cmdArgs, command.input.exec(args) );
  2851. };
  2852. }
  2853. //return a truthy value (an error message) if it's invalid, falsy if it's
  2854. // valid
  2855. function checkCommand ( cmd ) {
  2856. var somethingUndefined = Object.keys( cmd ).some(function ( key ) {
  2857. return !cmd[ key ];
  2858. }),
  2859. error;
  2860. if ( somethingUndefined ) {
  2861. error = 'Illegal /learn object; see `/help learn`';
  2862. }
  2863. else if ( !/^[\w\-]+$/.test(cmd.name) ) {
  2864. error = 'Invalid command name';
  2865. }
  2866. else if ( bot.commandExists(cmd.name.toLowerCase()) ) {
  2867. error = 'Command ' + cmd.name + ' already exists';
  2868. }
  2869. return error;
  2870. }
  2871. function loadCommands () {
  2872. Object.keys( storage ).forEach( teach );
  2873. function teach ( key ) {
  2874. var cmd = JSON.parse( storage[key] );
  2875. cmd.input = turnToRegexp( cmd.input );
  2876. cmd.date = new Date( Date.parse(cmd.date) );
  2877. bot.log( cmd, '/learn loadCommands' );
  2878. addCustomCommand( cmd );
  2879. }
  2880. //input: strung regexp, e.g. /abc/i
  2881. //return: regexp
  2882. //algo: we split by /.
  2883. // the first item is empty, the part before the first /
  2884. // the second to second-before-last are the regexp body. there will be more
  2885. // than one item in that range if the regexp contained escaped slashes,
  2886. // like /abc\/def/
  2887. // the last item is the flags (or the empty string, if no flags are set)
  2888. function turnToRegexp ( input ) {
  2889. var parts = input.toString().split( '/' );
  2890. return new RegExp(
  2891. parts.slice( 1, -1 ).join( '/' ), //to compensate for escaped /
  2892. parts[ parts.length-1 ]
  2893. );
  2894. }
  2895. }
  2896. function saveCommand ( command ) {
  2897. //h4x in source/util.js defines RegExp.prototype.toJSON so we don't worry
  2898. // about the input regexp stringifying
  2899. storage[ command.name ] = JSON.stringify( command );
  2900. localStorage.bot_learn = JSON.stringify( storage );
  2901. }
  2902. function deleteCommand ( name ) {
  2903. delete storage[ name ];
  2904. localStorage.bot_learn = JSON.stringify( storage );
  2905. }
  2906. bot.addCommand({
  2907. name : 'learn',
  2908. fun : learn,
  2909. privileges : {
  2910. del : 'NONE'
  2911. },
  2912. description : 'Teaches me a command. ' +
  2913. '`/learn cmdName outputPattern [inputRegex [description]]`'
  2914. });
  2915. loadCommands();
  2916. }());
  2917. ;
  2918. (function () {
  2919. "use strict";
  2920. var randomWord = function ( cb ) {
  2921. IO.jsonp({
  2922. url : 'http://sleepy-bastion-8674.herokuapp.com/',
  2923. jsonpName : 'callback',
  2924. fun : complete //aaawwww yyeeaaahhhh
  2925. });
  2926. function complete ( resp ) {
  2927. cb( resp.word.toLowerCase().trim() );
  2928. }
  2929. };
  2930. var game = {
  2931. //the dude is just a template to be filled with parts
  2932. //like a futuristic man. he has no shape. he has no identity. he's just a
  2933. // collection of mindless parts, to be assembled, for the greater good.
  2934. //pah! I mock your pathetic attempts at disowning man of his prowess! YOU
  2935. // SHALL NOT WIN! VIVE LA PENSÉE!!
  2936. dude : [
  2937. ' +---+' ,
  2938. ' | |' ,
  2939. ' | 413',
  2940. ' | 2' ,
  2941. ' | 5 6',
  2942. '__+__'
  2943. ].join( '\n' ),
  2944. parts : [ '', 'O', '|', '/', '\\', '/', '\\' ],
  2945. word : '',
  2946. revealed : '',
  2947. guesses : [],
  2948. guessNum : 0,
  2949. maxGuess : 6,
  2950. guessMade : false,
  2951. end : true,
  2952. msg : null,
  2953. validGuessRegex : /^[\w\s]+$/,
  2954. receiveMessage : function ( msg ) {
  2955. this.msg = msg;
  2956. if ( this.end ) {
  2957. this.new( msg );
  2958. }
  2959. else if ( msg.content ) {
  2960. return this.handleGuess( msg );
  2961. }
  2962. },
  2963. new : function ( msg ) {
  2964. var self = this;
  2965. randomWord( finish );
  2966. function finish ( word ) {
  2967. bot.log( word + ' /hang random' );
  2968. game.word = word;
  2969. self.revealed = new Array( word.length + 1 ).join( '-' );
  2970. self.guesses = [];
  2971. self.guessNum = 0;
  2972. //oh look, another dirty hack...this one is to make sure the
  2973. // hangman is codified
  2974. self.guessMade = true;
  2975. self.register();
  2976. self.receiveMessage( msg );
  2977. }
  2978. },
  2979. handleGuess : function ( msg ) {
  2980. var guess = msg.slice().toLowerCase();
  2981. bot.log( guess, 'handleGuess' );
  2982. var err = this.checkGuess( guess );
  2983. if ( err ) {
  2984. return err;
  2985. }
  2986. //replace all occurences of the guess within the hidden word with their
  2987. // actual characters
  2988. var indexes = this.word.indexesOf( guess );
  2989. indexes.forEach(function ( index ) {
  2990. this.uncoverPart( guess, index );
  2991. }, this);
  2992. //not found in secret word, penalize the evil doers!
  2993. if ( !indexes.length ) {
  2994. this.guessNum++;
  2995. }
  2996. this.guesses.push( guess );
  2997. this.guessMade = true;
  2998. bot.log( guess, 'handleGuess handled' );
  2999. //plain vanilla lose-win checks. yum yum yum.
  3000. if ( this.loseCheck() ) {
  3001. return this.lose();
  3002. }
  3003. if ( this.winCheck() ) {
  3004. return this.win();
  3005. }
  3006. },
  3007. checkGuess : function ( guess ) {
  3008. if ( !this.validGuessRegex.test(guess) ) {
  3009. return 'Only alphanumeric and whitespace characters allowed';
  3010. }
  3011. //check if it was already submitted
  3012. if ( this.guesses.indexOf(guess) > -1 ) {
  3013. return guess + ' was already submitted';
  3014. }
  3015. //or if it's the wrong length
  3016. if ( guess.length > this.word.length ) {
  3017. return bot.adapter.codify( guess ) + ' is longer than the phrase';
  3018. }
  3019. },
  3020. //unearth a portion of the secret word
  3021. uncoverPart : function ( guess, startIndex ) {
  3022. this.revealed =
  3023. this.revealed.slice( 0, startIndex ) +
  3024. guess +
  3025. this.revealed.slice( startIndex + guess.length );
  3026. },
  3027. //attach the hangman drawing to the already guessed list and to the
  3028. // revealed portion of the secret word
  3029. preparePrint : function () {
  3030. var self = this;
  3031. //replace the placeholders in the dude with body parts
  3032. var dude = this.dude.replace( /\d/g, function ( part ) {
  3033. return part > self.guessNum ? ' ' : self.parts[ part ];
  3034. });
  3035. var belowDude = this.guesses.sort().join( ', ' ) +
  3036. '\n' + this.revealed;
  3037. var hangy = this.msg.codify( dude + '\n' + belowDude );
  3038. bot.log( hangy, this.msg );
  3039. this.msg.send( hangy );
  3040. },
  3041. //win the game
  3042. win : function () {
  3043. this.unregister();
  3044. return 'Correct! The phrase is ' + this.word + '.';
  3045. },
  3046. //lose the game. less bitter messages? maybe.
  3047. lose : function () {
  3048. this.unregister();
  3049. return 'You people suck. The phrase was ' + this.word;
  3050. },
  3051. winCheck : function () {
  3052. return this.word === this.revealed;
  3053. },
  3054. loseCheck : function () {
  3055. return this.guessNum >= this.maxGuess;
  3056. },
  3057. register : function () {
  3058. this.unregister(); //to make sure it's not added multiple times
  3059. IO.register( 'beforeoutput', this.buildOutput, this );
  3060. this.end = false;
  3061. },
  3062. unregister : function () {
  3063. IO.unregister( 'beforeoutput', this.buildOutput );
  3064. this.end = true;
  3065. },
  3066. buildOutput : function () {
  3067. if ( this.guessMade ) {
  3068. this.preparePrint();
  3069. this.guessMade = false;
  3070. }
  3071. }
  3072. };
  3073. bot.addCommand({
  3074. name : 'hang',
  3075. fun : game.receiveMessage,
  3076. thisArg : game
  3077. });
  3078. }());
  3079. ;
  3080. IO.register( 'input', function ( msgObj ) {
  3081. if ( msgObj.user_id === 1386886 && Math.random() < 0.005 ) {
  3082. bot.adapter.out.add(
  3083. bot.adapter.reply(msgObj.user_name) + ' The Game' );
  3084. }
  3085. });
  3086. ;
  3087. (function () {
  3088. var template = '[{display_name}]({link}) ' +
  3089. 'has {reputation} reputation, ' +
  3090. 'earned {reputation_change_day} rep today, ' +
  3091. 'asked {question_count} questions, ' +
  3092. 'gave {answer_count} answers, ' +
  3093. 'for a q:a ratio of {ratio}.\n';
  3094. var extended_template = 'avg. rep/post: {avg_rep_post}. Badges: ' +
  3095. '{gold}g ' +
  3096. '{silver}s ' +
  3097. '{bronze}b ';
  3098. function stat ( msg, cb ) {
  3099. var args = msg.parse(),
  3100. id = args[ 0 ], extended = args[ 1 ] === 'extended';
  3101. if ( !id ) {
  3102. id = msg.get( 'user_id' );
  3103. }
  3104. else if ( !/^\d+$/.test(id) ) {
  3105. id = msg.findUserid( extended ? id : args.slice().join(' ') );
  3106. }
  3107. if ( id < 0 ) {
  3108. return 'User Elusio proved elusive.';
  3109. }
  3110. //~10% chance
  3111. if ( Math.random() <= 0.1 ) {
  3112. finish( 'That dude sucks' );
  3113. return;
  3114. }
  3115. IO.jsonp({
  3116. url : 'https://api.stackexchange.com/2.0/users/' + id,
  3117. data : {
  3118. site : bot.adapter.site,
  3119. filter : '!G*klMsSp1IcBUKxXMwhRe8TaI(' //ugh, don't ask...
  3120. },
  3121. fun : done
  3122. });
  3123. function done ( resp ) {
  3124. if ( resp.error_message ) {
  3125. finish( resp.error_message );
  3126. return;
  3127. }
  3128. var user = resp.items[ 0 ], res;
  3129. if ( !user ) {
  3130. res = 'User ' + id + ' not found';
  3131. }
  3132. else {
  3133. res = handle_user_object( user, extended );
  3134. }
  3135. finish( res );
  3136. }
  3137. function finish ( res ) {
  3138. if ( cb ) {
  3139. cb( res );
  3140. }
  3141. else {
  3142. msg.reply( res );
  3143. }
  3144. }
  3145. }
  3146. function handle_user_object ( user, extended ) {
  3147. user = normalize_stats( user );
  3148. var res = template.supplant( user );
  3149. if ( extended ) {
  3150. res += extended_template.supplant( calc_extended_stats(user) );
  3151. }
  3152. return res;
  3153. }
  3154. function normalize_stats ( stats ) {
  3155. stats = Object.merge(
  3156. {
  3157. question_count : 0,
  3158. answer_count : 0,
  3159. reputation_change_day : 0
  3160. },
  3161. stats );
  3162. //for teh lulz
  3163. if ( !stats.question_count && stats.answer_count ) {
  3164. stats.ratio = "H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔̀ͅ";
  3165. }
  3166. else if ( !stats.answer_count && stats.question_count ) {
  3167. stats.ratio = "TO͇̹̺ͅƝ̴ȳ̳ TH̘Ë͖́̉ ͠P̯͍̭O̚​N̐Y̡";
  3168. }
  3169. else if ( !stats.answer_count && !stats.question_count ) {
  3170. stats.ratio = 'http://i.imgur.com/F79hP.png';
  3171. }
  3172. else {
  3173. stats.ratio =
  3174. Math.ratio( stats.question_count, stats.answer_count );
  3175. }
  3176. bot.log( stats, '/stat normalized' );
  3177. return stats;
  3178. }
  3179. function calc_extended_stats ( stats ) {
  3180. stats = Object.merge( stats.badge_counts, stats );
  3181. stats.avg_rep_post =
  3182. ( stats.reputation /
  3183. ( stats.question_count + stats.answer_count )
  3184. ).maxDecimal( 2 );
  3185. //1 / 0 === Infinity
  3186. if ( stats.avg_rep_post === Infinity ) {
  3187. stats.avg_rep_post = 'T͎͍̘͙̖̤̉̌̇̅ͯ͋͢͜͝H̖͙̗̗̺͚̱͕̒́͟E̫̺̯͖͎̗̒͑̅̈ ̈ͮ̽ͯ̆̋́͏͙͓͓͇̹<̩̟̳̫̪̇ͩ̑̆͗̽̇͆́ͅC̬͎ͪͩ̓̑͊ͮͪ̄̚̕Ě̯̰̤̗̜̗͓͛͝N̶̴̞͇̟̲̪̅̓ͯͅT͍̯̰͓̬͚̅͆̄E̠͇͇̬̬͕͖ͨ̔̓͞R͚̠̻̲̗̹̀>̇̏ͣ҉̳̖̟̫͕ ̧̛͈͙͇͂̓̚͡C͈̞̻̩̯̠̻ͥ̆͐̄ͦ́̀͟A̛̪̫͙̺̱̥̞̙ͦͧ̽͛̈́ͯ̅̍N̦̭͕̹̤͓͙̲̑͋̾͊ͣŅ̜̝͌͟O̡̝͍͚̲̝ͣ̔́͝Ť͈͢ ̪̘̳͔̂̒̋ͭ͆̽͠H̢͈̤͚̬̪̭͗ͧͬ̈́̈̀͌͒͡Ơ̮͍͇̝̰͍͚͖̿ͮ̀̍́L͐̆ͨ̏̎͡҉̧̱̯̤̹͓̗̻̭ͅḐ̲̰͙͑̂̒̐́̊';
  3188. }
  3189. bot.log( stats, '/stat extended' );
  3190. return stats;
  3191. }
  3192. bot.addCommand({
  3193. name : 'stat',
  3194. fun : stat,
  3195. permissions : {
  3196. del : 'NONE'
  3197. },
  3198. description : 'Gives useless stats on a user. ' +
  3199. '`/stat usrid|usrname [extended]`',
  3200. async : true
  3201. });
  3202. }());
  3203. ;
  3204. (function () {
  3205. var list = JSON.parse( localStorage.getItem('bot_todo') || '{}' );
  3206. var userlist = function ( usrid ) {
  3207. var usr = list[ usrid ],
  3208. toRemove = [];
  3209. if ( !usr ) {
  3210. usr = list[ usrid ] = [];
  3211. }
  3212. return {
  3213. get : function ( count ) {
  3214. return usr.slice( count ).map(function ( item, idx ) {
  3215. return '(' + idx + ')' + item;
  3216. }).join( ', ' );
  3217. },
  3218. add : function ( item ) {
  3219. usr.push( item );
  3220. return true;
  3221. },
  3222. remove : function ( item ) {
  3223. var idx = usr.indexOf( item );
  3224. if ( idx === -1 ) {
  3225. return false;
  3226. }
  3227. return this.removeByIndex( idx );
  3228. },
  3229. removeByIndex : function ( idx ) {
  3230. if ( idx >= usr.length ) {
  3231. return false;
  3232. }
  3233. toRemove.push( idx );
  3234. return true;
  3235. },
  3236. save : function () {
  3237. bot.log( toRemove.slice(), usr.slice() );
  3238. usr = usr.filter(function ( item, idx ) {
  3239. return toRemove.indexOf( idx ) === -1;
  3240. });
  3241. toRemove.length = 0;
  3242. list[ usrid ] = usr;
  3243. localStorage.bot_todo = JSON.stringify( list );
  3244. },
  3245. exists : function ( suspect ) {
  3246. suspect = suspect.toLowerCase();
  3247. return usr.some(function ( item ) {
  3248. return suspect === item.toLowerCase();
  3249. });
  3250. }
  3251. };
  3252. }.memoize();
  3253. var actions = {
  3254. get : function ( usr, items ) {
  3255. //if the user didn't provide an argument, the entire thing is returned
  3256. var ret = usr.get( items[0] );
  3257. return ret || 'No items on your todo';
  3258. },
  3259. add : function ( usr, items ) {
  3260. var ret = '';
  3261. items.every( add );
  3262. return ret || 'Item(s) added.';
  3263. function add ( item ) {
  3264. if ( usr.exists(item) ) {
  3265. ret = item + ' already exists.';
  3266. return false;
  3267. }
  3268. usr.add( item );
  3269. return true;
  3270. }
  3271. },
  3272. rm : function ( usr, items ) {
  3273. var ret = '';
  3274. items.every( remove );
  3275. return ret || 'Item(s) removed.';
  3276. function remove ( item ) {
  3277. if ( /^\d+$/.test(item) ) {
  3278. usr.removeByIndex( Number(item) );
  3279. }
  3280. else if ( !usr.exists(item) ) {
  3281. ret = item + ' does not exist.';
  3282. return false;
  3283. }
  3284. else {
  3285. usr.remove( item );
  3286. }
  3287. return true;
  3288. }
  3289. }
  3290. };
  3291. var todo = function ( args ) {
  3292. var props = args.parse();
  3293. bot.log( props, 'todo input' );
  3294. if ( !props[0] ) {
  3295. props = [ 'get' ];
  3296. }
  3297. var action = props[ 0 ],
  3298. usr = userlist( args.get('user_id') ),
  3299. items = props.slice( 1 ),
  3300. ret;
  3301. if ( actions[action] ) {
  3302. ret = actions[ action ]( usr, items );
  3303. bot.log( ret, '/todo ' + action );
  3304. }
  3305. else {
  3306. ret = 'Unidentified /todo action ' + action;
  3307. bot.log( ret, '/todo unknown' );
  3308. }
  3309. //save the updated list
  3310. usr.save();
  3311. return ret;
  3312. };
  3313. bot.addCommand({
  3314. name : 'todo',
  3315. fun : todo,
  3316. permissions : {
  3317. del : 'NONE'
  3318. },
  3319. description : 'Your personal todo list. ' +
  3320. '`get [count]` retrieves everything or count items. ' +
  3321. '`add items` adds items to your todo list (make sure items ' +
  3322. 'with spaces are wrapped in quotes) ' +
  3323. '`rm items|indices` removes items specified by indice or content'
  3324. });
  3325. }());
  3326. ;
  3327. (function () {
  3328. var nulls = [
  3329. 'The Google contains no such knowledge',
  3330. 'There are no search results. Run.' ];
  3331. function google ( args, cb ) {
  3332. IO.jsonp.google( args.toString() + ' -site:w3schools.com', finishCall );
  3333. function finishCall ( resp ) {
  3334. bot.log( resp, '/google response' );
  3335. if ( resp.responseStatus !== 200 ) {
  3336. finish( 'My Google-Fu is on vacation; status ' +
  3337. resp.responseStatus );
  3338. return;
  3339. }
  3340. //TODO: change hard limit to argument
  3341. var results = resp.responseData.results.slice( 0, 3 );
  3342. bot.log( results, '/google results' );
  3343. if ( !results.length ) {
  3344. finish( nulls.random() );
  3345. return;
  3346. }
  3347. finish(
  3348. results.map( format ).join( ' ; ' ) );
  3349. function format ( result ) {
  3350. var title = IO.decodehtmlEntities( result.titleNoFormatting );
  3351. return args.link( title, result.url );
  3352. }
  3353. }
  3354. function finish ( res ) {
  3355. bot.log( res, '/google final' );
  3356. if ( cb && cb.call ) {
  3357. cb( res );
  3358. }
  3359. else {
  3360. args.reply( res );
  3361. }
  3362. }
  3363. }
  3364. bot.addCommand({
  3365. name : 'google',
  3366. fun : google,
  3367. permissions : {
  3368. del : 'NONE'
  3369. },
  3370. description : 'Search Google. `/google query`',
  3371. async : true
  3372. });
  3373. }());
  3374. ;
  3375. (function () {
  3376. var specParts;
  3377. specParts = [{"section":"introduction","name":"Introduction"},{"section":"x1","name":"1 Scope"},{"section":"x2","name":"2 Conformance"},{"section":"x3","name":"3 Normative references"},{"section":"x4","name":"4 Overview"},{"section":"x4.1","name":"4.1 Web Scripting"},{"section":"x4.2","name":"4.2 Language Overview"},{"section":"x4.2.1","name":"4.2.1 Objects"},{"section":"x4.2.2","name":"4.2.2 The Strict Variant of ECMAScript"},{"section":"x4.3","name":"4.3 Definitions"},{"section":"x4.3.1","name":"4.3.1 type"},{"section":"x4.3.2","name":"4.3.2 primitive value"},{"section":"x4.3.3","name":"4.3.3 object"},{"section":"x4.3.4","name":"4.3.4 constructor"},{"section":"x4.3.5","name":"4.3.5 prototype"},{"section":"x4.3.6","name":"4.3.6 native object"},{"section":"x4.3.7","name":"4.3.7 built-in object"},{"section":"x4.3.8","name":"4.3.8 host object"},{"section":"x4.3.9","name":"4.3.9 undefined value"},{"section":"x4.3.10","name":"4.3.10 Undefined type"},{"section":"x4.3.11","name":"4.3.11 null value"},{"section":"x4.3.12","name":"4.3.12 Null type"},{"section":"x4.3.13","name":"4.3.13 Boolean value"},{"section":"x4.3.14","name":"4.3.14 Boolean type"},{"section":"x4.3.15","name":"4.3.15 Boolean object"},{"section":"x4.3.16","name":"4.3.16 String value"},{"section":"x4.3.17","name":"4.3.17 String type"},{"section":"x4.3.18","name":"4.3.18 String object"},{"section":"x4.3.19","name":"4.3.19 Number value"},{"section":"x4.3.20","name":"4.3.20 Number type"},{"section":"x4.3.21","name":"4.3.21 Number object"},{"section":"x4.3.22","name":"4.3.22 Infinity"},{"section":"x4.3.23","name":"4.3.23 NaN"},{"section":"x4.3.24","name":"4.3.24 function"},{"section":"x4.3.25","name":"4.3.25 built-in function"},{"section":"x4.3.26","name":"4.3.26 property"},{"section":"x4.3.27","name":"4.3.27 method"},{"section":"x4.3.28","name":"4.3.28 built-in method"},{"section":"x4.3.29","name":"4.3.29 attribute"},{"section":"x4.3.30","name":"4.3.30 own property"},{"section":"x4.3.31","name":"4.3.31 inherited property"},{"section":"x5","name":"5 Notational Conventions"},{"section":"x5.1","name":"5.1 Syntactic and Lexical Grammars"},{"section":"x5.1.1","name":"5.1.1 Context-Free Grammars"},{"section":"x5.1.2","name":"5.1.2 The Lexical and RegExp Grammars"},{"section":"x5.1.3","name":"5.1.3 The Numeric String Grammar"},{"section":"x5.1.4","name":"5.1.4 The Syntactic Grammar"},{"section":"x5.1.5","name":"5.1.5 The JSON Grammar"},{"section":"x5.1.6","name":"5.1.6 Grammar Notation"},{"section":"x5.2","name":"5.2 Algorithm Conventions"},{"section":"x6","name":"6 Source Text"},{"section":"x7","name":"7 Lexical Conventions"},{"section":"x7.1","name":"7.1 Unicode Format-Control Characters"},{"section":"x7.2","name":"7.2 White Space"},{"section":"x7.3","name":"7.3 Line Terminators"},{"section":"x7.4","name":"7.4 Comments"},{"section":"x7.5","name":"7.5 Tokens"},{"section":"x7.6","name":"7.6 Identifier Names and Identifiers"},{"section":"x7.6.1","name":"7.6.1 Reserved Words"},{"section":"x7.6.1.1","name":"7.6.1.1 Keywords"},{"section":"x7.6.1.2","name":"7.6.1.2 Future Reserved Words"},{"section":"x7.7","name":"7.7 Punctuators"},{"section":"x7.8","name":"7.8 Literals"},{"section":"x7.8.1","name":"7.8.1 Null Literals"},{"section":"x7.8.2","name":"7.8.2 Boolean Literals"},{"section":"x7.8.3","name":"7.8.3 Numeric Literals"},{"section":"x7.8.4","name":"7.8.4 String Literals"},{"section":"x7.8.5","name":"7.8.5 Regular Expression Literals"},{"section":"x7.9","name":"7.9 Automatic Semicolon Insertion"},{"section":"x7.9.1","name":"7.9.1 Rules of Automatic Semicolon Insertion"},{"section":"x7.9.2","name":"7.9.2 Examples of Automatic Semicolon Insertion"},{"section":"x8","name":"8 Types"},{"section":"x8.1","name":"8.1 The Undefined Type"},{"section":"x8.2","name":"8.2 The Null Type"},{"section":"x8.3","name":"8.3 The Boolean Type"},{"section":"x8.4","name":"8.4 The String Type"},{"section":"x8.5","name":"8.5 The Number Type"},{"section":"x8.6","name":"8.6 The Object Type"},{"section":"x8.6.1","name":"8.6.1 Property Attributes"},{"section":"x8.6.2","name":"8.6.2 Object Internal Properties and Methods"},{"section":"x8.7","name":"8.7 The Reference Specification Type"},{"section":"x8.7.1","name":"8.7.1 GetValue (V)"},{"section":"x8.7.2","name":"8.7.2 PutValue (V, W)"},{"section":"x8.8","name":"8.8 The List Specification Type"},{"section":"x8.9","name":"8.9 The Completion Specification Type"},{"section":"x8.10","name":"8.10 The Property Descriptor and Property Identifier Specification Types"},{"section":"x8.10.1","name":"8.10.1 IsAccessorDescriptor ( Desc )"},{"section":"x8.10.2","name":"8.10.2 IsDataDescriptor ( Desc )"},{"section":"x8.10.3","name":"8.10.3 IsGenericDescriptor ( Desc )"},{"section":"x8.10.4","name":"8.10.4 FromPropertyDescriptor ( Desc )"},{"section":"x8.10.5","name":"8.10.5 ToPropertyDescriptor ( Obj )"},{"section":"x8.11","name":"8.11 The Lexical Environment and Environment Record Specification Types"},{"section":"x8.12","name":"8.12 Algorithms for Object Internal Methods"},{"section":"x8.12.1","name":"8.12.1 [[GetOwnProperty]] (P)"},{"section":"x8.12.2","name":"8.12.2 [[GetProperty]] (P)"},{"section":"x8.12.3","name":"8.12.3 [[Get]] (P)"},{"section":"x8.12.4","name":"8.12.4 [[CanPut]] (P)"},{"section":"x8.12.5","name":"8.12.5 [[Put]] ( P, V, Throw )"},{"section":"x8.12.6","name":"8.12.6 [[HasProperty]] (P)"},{"section":"x8.12.7","name":"8.12.7 [[Delete]] (P, Throw)"},{"section":"x8.12.8","name":"8.12.8 [[DefaultValue]] (hint)"},{"section":"x8.12.9","name":"8.12.9 [[DefineOwnProperty]] (P, Desc, Throw)"},{"section":"x9","name":"9 Type Conversion and Testing"},{"section":"x9.1","name":"9.1 ToPrimitive"},{"section":"x9.2","name":"9.2 ToBoolean"},{"section":"x9.3","name":"9.3 ToNumber"},{"section":"x9.3.1","name":"9.3.1 ToNumber Applied to the String Type"},{"section":"x9.4","name":"9.4 ToInteger"},{"section":"x9.5","name":"9.5 ToInt32: (Signed 32 Bit Integer)"},{"section":"x9.6","name":"9.6 ToUint32: (Unsigned 32 Bit Integer)"},{"section":"x9.7","name":"9.7 ToUint16: (Unsigned 16 Bit Integer)"},{"section":"x9.8","name":"9.8 ToString"},{"section":"x9.8.1","name":"9.8.1 ToString Applied to the Number Type"},{"section":"x9.9","name":"9.9 ToObject"},{"section":"x9.10","name":"9.10 CheckObjectCoercible"},{"section":"x9.11","name":"9.11 IsCallable"},{"section":"x9.12","name":"9.12 The SameValue Algorithm"},{"section":"x10","name":"10 Executable Code and Execution Contexts"},{"section":"x10.1","name":"10.1 Types of Executable Code"},{"section":"x10.1.1","name":"10.1.1 Strict Mode Code"},{"section":"x10.2","name":"10.2 Lexical Environments"},{"section":"x10.2.1","name":"10.2.1 Environment Records"},{"section":"x10.2.1.1","name":"10.2.1.1 Declarative Environment Records"},{"section":"x10.2.1.1.1","name":"10.2.1.1.1 HasBinding(N)"},{"section":"x10.2.1.1.2","name":"10.2.1.1.2 CreateMutableBinding (N, D)"},{"section":"x10.2.1.1.3","name":"10.2.1.1.3 SetMutableBinding (N,V,S)"},{"section":"x10.2.1.1.4","name":"10.2.1.1.4 GetBindingValue(N,S)"},{"section":"x10.2.1.1.5","name":"10.2.1.1.5 DeleteBinding (N)"},{"section":"x10.2.1.1.6","name":"10.2.1.1.6 ImplicitThisValue()"},{"section":"x10.2.1.1.7","name":"10.2.1.1.7 CreateImmutableBinding (N)"},{"section":"x10.2.1.1.8","name":"10.2.1.1.8 InitializeImmutableBinding (N,V)"},{"section":"x10.2.1.2","name":"10.2.1.2 Object Environment Records"},{"section":"x10.2.1.2.1","name":"10.2.1.2.1 HasBinding(N)"},{"section":"x10.2.1.2.2","name":"10.2.1.2.2 CreateMutableBinding (N, D)"},{"section":"x10.2.1.2.3","name":"10.2.1.2.3 SetMutableBinding (N,V,S)"},{"section":"x10.2.1.2.4","name":"10.2.1.2.4 GetBindingValue(N,S)"},{"section":"x10.2.1.2.5","name":"10.2.1.2.5 DeleteBinding (N)"},{"section":"x10.2.1.2.6","name":"10.2.1.2.6 ImplicitThisValue()"},{"section":"x10.2.2","name":"10.2.2 Lexical Environment Operations"},{"section":"x10.2.2.1","name":"10.2.2.1 GetIdentifierReference (lex, name, strict)"},{"section":"x10.2.2.2","name":"10.2.2.2 NewDeclarativeEnvironment (E)"},{"section":"x10.2.2.3","name":"10.2.2.3 NewObjectEnvironment (O, E)"},{"section":"x10.2.3","name":"10.2.3 The Global Environment"},{"section":"x10.3","name":"10.3 Execution Contexts"},{"section":"x10.3.1","name":"10.3.1 Identifier Resolution"},{"section":"x10.4","name":"10.4 Establishing an Execution Context"},{"section":"x10.4.1","name":"10.4.1 Entering Global Code"},{"section":"x10.4.1.1","name":"10.4.1.1 Initial Global Execution Context"},{"section":"x10.4.2","name":"10.4.2 Entering Eval Code"},{"section":"x10.4.2.1","name":"10.4.2.1 Strict Mode Restrictions"},{"section":"x10.4.3","name":"10.4.3 Entering Function Code"},{"section":"x10.5","name":"10.5 Declaration Binding Instantiation"},{"section":"x10.6","name":"10.6 Arguments Object"},{"section":"x11","name":"11 Expressions"},{"section":"x11.1","name":"11.1 Primary Expressions"},{"section":"x11.1.1","name":"11.1.1 The this Keyword"},{"section":"x11.1.2","name":"11.1.2 Identifier Reference"},{"section":"x11.1.3","name":"11.1.3 Literal Reference"},{"section":"x11.1.4","name":"11.1.4 Array Initialiser"},{"section":"x11.1.5","name":"11.1.5 Object Initialiser"},{"section":"x11.1.6","name":"11.1.6 The Grouping Operator"},{"section":"x11.2","name":"11.2 Left-Hand-Side Expressions"},{"section":"x11.2.1","name":"11.2.1 Property Accessors"},{"section":"x11.2.2","name":"11.2.2 The new Operator"},{"section":"x11.2.3","name":"11.2.3 Function Calls"},{"section":"x11.2.4","name":"11.2.4 Argument Lists"},{"section":"x11.2.5","name":"11.2.5 Function Expressions"},{"section":"x11.3","name":"11.3 Postfix Expressions"},{"section":"x11.3.1","name":"11.3.1 Postfix Increment Operator"},{"section":"x11.3.2","name":"11.3.2 Postfix Decrement Operator"},{"section":"x11.4","name":"11.4 Unary Operators"},{"section":"x11.4.1","name":"11.4.1 The delete Operator"},{"section":"x11.4.2","name":"11.4.2 The void Operator"},{"section":"x11.4.3","name":"11.4.3 The typeof Operator"},{"section":"x11.4.4","name":"11.4.4 Prefix Increment Operator"},{"section":"x11.4.5","name":"11.4.5 Prefix Decrement Operator"},{"section":"x11.4.6","name":"11.4.6 Unary + Operator"},{"section":"x11.4.7","name":"11.4.7 Unary - Operator"},{"section":"x11.4.8","name":"11.4.8 Bitwise NOT Operator ( ~ )"},{"section":"x11.4.9","name":"11.4.9 Logical NOT Operator ( ! )"},{"section":"x11.5","name":"11.5 Multiplicative Operators"},{"section":"x11.5.1","name":"11.5.1 Applying the * Operator"},{"section":"x11.5.2","name":"11.5.2 Applying the / Operator"},{"section":"x11.5.3","name":"11.5.3 Applying the % Operator"},{"section":"x11.6","name":"11.6 Additive Operators"},{"section":"x11.6.1","name":"11.6.1 The Addition operator ( + )"},{"section":"x11.6.2","name":"11.6.2 The Subtraction Operator ( - )"},{"section":"x11.6.3","name":"11.6.3 Applying the Additive Operators to Numbers"},{"section":"x11.7","name":"11.7 Bitwise Shift Operators"},{"section":"x11.7.1","name":"11.7.1 The Left Shift Operator ( << )"},{"section":"x11.7.2","name":"11.7.2 The Signed Right Shift Operator ( >> )"},{"section":"x11.7.3","name":"11.7.3 The Unsigned Right Shift Operator ( >>> )"},{"section":"x11.8","name":"11.8 Relational Operators"},{"section":"x11.8.1","name":"11.8.1 The Less-than Operator ( < )"},{"section":"x11.8.2","name":"11.8.2 The Greater-than Operator ( > )"},{"section":"x11.8.3","name":"11.8.3 The Less-than-or-equal Operator ( <= )"},{"section":"x11.8.4","name":"11.8.4 The Greater-than-or-equal Operator ( >= )"},{"section":"x11.8.5","name":"11.8.5 The Abstract Relational Comparison Algorithm"},{"section":"x11.8.6","name":"11.8.6 The instanceof operator"},{"section":"x11.8.7","name":"11.8.7 The in operator"},{"section":"x11.9","name":"11.9 Equality Operators"},{"section":"x11.9.1","name":"11.9.1 The Equals Operator ( == )"},{"section":"x11.9.2","name":"11.9.2 The Does-not-equals Operator ( != )"},{"section":"x11.9.3","name":"11.9.3 The Abstract Equality Comparison Algorithm"},{"section":"x11.9.4","name":"11.9.4 The Strict Equals Operator ( === )"},{"section":"x11.9.5","name":"11.9.5 The Strict Does-not-equal Operator ( !== )"},{"section":"x11.9.6","name":"11.9.6 The Strict Equality Comparison Algorithm"},{"section":"x11.10","name":"11.10 Binary Bitwise Operators"},{"section":"x11.11","name":"11.11 Binary Logical Operators"},{"section":"x11.12","name":"11.12 Conditional Operator ( ? : )"},{"section":"x11.13","name":"11.13 Assignment Operators"},{"section":"x11.13.1","name":"11.13.1 Simple Assignment ( = )"},{"section":"x11.13.2","name":"11.13.2 Compound Assignment ( op= )"},{"section":"x11.14","name":"11.14 Comma Operator ( , )"},{"section":"x12","name":"12 Statements"},{"section":"x12.1","name":"12.1 Block"},{"section":"x12.2","name":"12.2 Variable Statement"},{"section":"x12.2.1","name":"12.2.1 Strict Mode Restrictions"},{"section":"x12.3","name":"12.3 Empty Statement"},{"section":"x12.4","name":"12.4 Expression Statement"},{"section":"x12.5","name":"12.5 The if Statement"},{"section":"x12.6","name":"12.6 Iteration Statements"},{"section":"x12.6.1","name":"12.6.1 The do-while Statement"},{"section":"x12.6.2","name":"12.6.2 The while Statement"},{"section":"x12.6.3","name":"12.6.3 The for Statement"},{"section":"x12.6.4","name":"12.6.4 The for-in Statement"},{"section":"x12.7","name":"12.7 The continue Statement"},{"section":"x12.8","name":"12.8 The break Statement"},{"section":"x12.9","name":"12.9 The return Statement"},{"section":"x12.10","name":"12.10 The with Statement"},{"section":"x12.10.1","name":"12.10.1 Strict Mode Restrictions"},{"section":"x12.11","name":"12.11 The switch Statement"},{"section":"x12.12","name":"12.12 Labelled Statements"},{"section":"x12.13","name":"12.13 The throw Statement"},{"section":"x12.14","name":"12.14 The try Statement"},{"section":"x12.14.1","name":"12.14.1 Strict Mode Restrictions"},{"section":"x12.15","name":"12.15 The debugger statement"},{"section":"x13","name":"13 Function Definition"},{"section":"x13.1","name":"13.1 Strict Mode Restrictions"},{"section":"x13.2","name":"13.2 Creating Function Objects"},{"section":"x13.2.1","name":"13.2.1 [[Call]]"},{"section":"x13.2.2","name":"13.2.2 [[Construct]]"},{"section":"x13.2.3","name":"13.2.3 The Function Object"},{"section":"x14","name":"14 Program"},{"section":"x14.1","name":"14.1 Directive Prologues and the Use Strict Directive"},{"section":"x15","name":"15 Standard Built-in ECMAScript Objects"},{"section":"x15.1","name":"15.1 The Global Object"},{"section":"x15.1.1","name":"15.1.1 Value Properties of the Global Object"},{"section":"x15.1.1.1","name":"15.1.1.1 NaN"},{"section":"x15.1.1.2","name":"15.1.1.2 Infinity"},{"section":"x15.1.1.3","name":"15.1.1.3 undefined"},{"section":"x15.1.2","name":"15.1.2 Function Properties of the Global Object"},{"section":"x15.1.2.1","name":"15.1.2.1 eval (x)"},{"section":"x15.1.2.1.1","name":"15.1.2.1.1 Direct Call to Eval"},{"section":"x15.1.2.2","name":"15.1.2.2 parseInt (string , radix)"},{"section":"x15.1.2.3","name":"15.1.2.3 parseFloat (string)"},{"section":"x15.1.2.4","name":"15.1.2.4 isNaN (number)"},{"section":"x15.1.2.5","name":"15.1.2.5 isFinite (number)"},{"section":"x15.1.3","name":"15.1.3 URI Handling Function Properties"},{"section":"x15.1.3.1","name":"15.1.3.1 decodeURI (encodedURI)"},{"section":"x15.1.3.2","name":"15.1.3.2 decodeURIComponent (encodedURIComponent)"},{"section":"x15.1.3.3","name":"15.1.3.3 encodeURI (uri)"},{"section":"x15.1.3.4","name":"15.1.3.4 encodeURIComponent (uriComponent)"},{"section":"x15.1.4","name":"15.1.4 Constructor Properties of the Global Object"},{"section":"x15.1.4.1","name":"15.1.4.1 Object ( . . . )"},{"section":"x15.1.4.2","name":"15.1.4.2 Function ( . . . )"},{"section":"x15.1.4.3","name":"15.1.4.3 Array ( . . . )"},{"section":"x15.1.4.4","name":"15.1.4.4 String ( . . . )"},{"section":"x15.1.4.5","name":"15.1.4.5 Boolean ( . . . )"},{"section":"x15.1.4.6","name":"15.1.4.6 Number ( . . . )"},{"section":"x15.1.4.7","name":"15.1.4.7 Date ( . . . )"},{"section":"x15.1.4.8","name":"15.1.4.8 RegExp ( . . . )"},{"section":"x15.1.4.9","name":"15.1.4.9 Error ( . . . )"},{"section":"x15.1.4.10","name":"15.1.4.10 EvalError ( . . . )"},{"section":"x15.1.4.11","name":"15.1.4.11 RangeError ( . . . )"},{"section":"x15.1.4.12","name":"15.1.4.12 ReferenceError ( . . . )"},{"section":"x15.1.4.13","name":"15.1.4.13 SyntaxError ( . . . )"},{"section":"x15.1.4.14","name":"15.1.4.14 TypeError ( . . . )"},{"section":"x15.1.4.15","name":"15.1.4.15 URIError ( . . . )"},{"section":"x15.1.5","name":"15.1.5 Other Properties of the Global Object"},{"section":"x15.1.5.1","name":"15.1.5.1 Math"},{"section":"x15.1.5.2","name":"15.1.5.2 JSON"},{"section":"x15.2","name":"15.2 Object Objects"},{"section":"x15.2.1","name":"15.2.1 The Object Constructor Called as a Function"},{"section":"x15.2.1.1","name":"15.2.1.1 Object ( [ value ] )"},{"section":"x15.2.2","name":"15.2.2 The Object Constructor"},{"section":"x15.2.2.1","name":"15.2.2.1 new Object ( [ value ] )"},{"section":"x15.2.3","name":"15.2.3 Properties of the Object Constructor"},{"section":"x15.2.3.1","name":"15.2.3.1 Object.prototype"},{"section":"x15.2.3.2","name":"15.2.3.2 Object.getPrototypeOf ( O )"},{"section":"x15.2.3.3","name":"15.2.3.3 Object.getOwnPropertyDescriptor ( O, P ) "},{"section":"x15.2.3.4","name":"15.2.3.4 Object.getOwnPropertyNames ( O )"},{"section":"x15.2.3.5","name":"15.2.3.5 Object.create ( O [, Properties] )"},{"section":"x15.2.3.6","name":"15.2.3.6 Object.defineProperty ( O, P, Attributes )"},{"section":"x15.2.3.7","name":"15.2.3.7 Object.defineProperties ( O, Properties )"},{"section":"x15.2.3.8","name":"15.2.3.8 Object.seal ( O )"},{"section":"x15.2.3.9","name":"15.2.3.9 Object.freeze ( O )"},{"section":"x15.2.3.10","name":"15.2.3.10 Object.preventExtensions ( O )"},{"section":"x15.2.3.11","name":"15.2.3.11 Object.isSealed ( O )"},{"section":"x15.2.3.12","name":"15.2.3.12 Object.isFrozen ( O )"},{"section":"x15.2.3.13","name":"15.2.3.13 Object.isExtensible ( O )"},{"section":"x15.2.3.14","name":"15.2.3.14 Object.keys ( O )"},{"section":"x15.2.4","name":"15.2.4 Properties of the Object Prototype Object"},{"section":"x15.2.4.1","name":"15.2.4.1 Object.prototype.constructor"},{"section":"x15.2.4.2","name":"15.2.4.2 Object.prototype.toString ( )"},{"section":"x15.2.4.3","name":"15.2.4.3 Object.prototype.toLocaleString ( )"},{"section":"x15.2.4.4","name":"15.2.4.4 Object.prototype.valueOf ( )"},{"section":"x15.2.4.5","name":"15.2.4.5 Object.prototype.hasOwnProperty (V)"},{"section":"x15.2.4.6","name":"15.2.4.6 Object.prototype.isPrototypeOf (V)"},{"section":"x15.2.4.7","name":"15.2.4.7 Object.prototype.propertyIsEnumerable (V)"},{"section":"x15.2.5","name":"15.2.5 Properties of Object Instances"},{"section":"x15.3","name":"15.3 Function Objects"},{"section":"x15.3.1","name":"15.3.1 The Function Constructor Called as a Function"},{"section":"x15.3.1.1","name":"15.3.1.1 Function (p1, p2, … , pn, body)"},{"section":"x15.3.2","name":"15.3.2 The Function Constructor"},{"section":"x15.3.2.1","name":"15.3.2.1 new Function (p1, p2, … , pn, body)"},{"section":"x15.3.3","name":"15.3.3 Properties of the Function Constructor"},{"section":"x15.3.3.1","name":"15.3.3.1 Function.prototype"},{"section":"x15.3.3.2","name":"15.3.3.2 Function.length"},{"section":"x15.3.4","name":"15.3.4 Properties of the Function Prototype Object"},{"section":"x15.3.4.1","name":"15.3.4.1 Function.prototype.constructor"},{"section":"x15.3.4.2","name":"15.3.4.2 Function.prototype.toString ( )"},{"section":"x15.3.4.3","name":"15.3.4.3 Function.prototype.apply (thisArg, argArray)"},{"section":"x15.3.4.4","name":"15.3.4.4 Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] )"},{"section":"x15.3.4.5","name":"15.3.4.5 Function.prototype.bind (thisArg [, arg1 [, arg2, …]])"},{"section":"x15.3.4.5.1","name":"15.3.4.5.1 [[Call]]"},{"section":"x15.3.4.5.2","name":"15.3.4.5.2 [[Construct]]"},{"section":"x15.3.4.5.3","name":"15.3.4.5.3 [[HasInstance]] (V)"},{"section":"x15.3.5","name":"15.3.5 Properties of Function Instances"},{"section":"x15.3.5.1","name":"15.3.5.1 length"},{"section":"x15.3.5.2","name":"15.3.5.2 prototype"},{"section":"x15.3.5.3","name":"15.3.5.3 [[HasInstance]] (V)"},{"section":"x15.3.5.4","name":"15.3.5.4 [[Get]] (P)"},{"section":"x15.4","name":"15.4 Array Objects"},{"section":"x15.4.1","name":"15.4.1 The Array Constructor Called as a Function"},{"section":"x15.4.1.1","name":"15.4.1.1 Array ( [ item1 [ , item2 [ , … ] ] ] )"},{"section":"x15.4.2","name":"15.4.2 The Array Constructor"},{"section":"x15.4.2.1","name":"15.4.2.1 new Array ( [ item0 [ , item1 [ , … ] ] ] )"},{"section":"x15.4.2.2","name":"15.4.2.2 new Array (len)"},{"section":"x15.4.3","name":"15.4.3 Properties of the Array Constructor"},{"section":"x15.4.3.1","name":"15.4.3.1 Array.prototype"},{"section":"x15.4.3.2","name":"15.4.3.2 Array.isArray ( arg )"},{"section":"x15.4.4","name":"15.4.4 Properties of the Array Prototype Object"},{"section":"x15.4.4.1","name":"15.4.4.1 Array.prototype.constructor"},{"section":"x15.4.4.2","name":"15.4.4.2 Array.prototype.toString ( )"},{"section":"x15.4.4.3","name":"15.4.4.3 Array.prototype.toLocaleString ( )"},{"section":"x15.4.4.4","name":"15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , … ] ] ] )"},{"section":"x15.4.4.5","name":"15.4.4.5 Array.prototype.join (separator)"},{"section":"x15.4.4.6","name":"15.4.4.6 Array.prototype.pop ( )"},{"section":"x15.4.4.7","name":"15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )"},{"section":"x15.4.4.8","name":"15.4.4.8 Array.prototype.reverse ( )"},{"section":"x15.4.4.9","name":"15.4.4.9 Array.prototype.shift ( )"},{"section":"x15.4.4.10","name":"15.4.4.10 Array.prototype.slice (start, end)"},{"section":"x15.4.4.11","name":"15.4.4.11 Array.prototype.sort (comparefn)"},{"section":"x15.4.4.12","name":"15.4.4.12 Array.prototype.splice (start, deleteCount [ , item1 [ , item2 [ , … ] ] ] )"},{"section":"x15.4.4.13","name":"15.4.4.13 Array.prototype.unshift ( [ item1 [ , item2 [ , … ] ] ] )"},{"section":"x15.4.4.14","name":"15.4.4.14 Array.prototype.indexOf ( searchElement [ , fromIndex ] )"},{"section":"x15.4.4.15","name":"15.4.4.15 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] )"},{"section":"x15.4.4.16","name":"15.4.4.16 Array.prototype.every ( callbackfn [ , thisArg ] )"},{"section":"x15.4.4.17","name":"15.4.4.17 Array.prototype.some ( callbackfn [ , thisArg ] )"},{"section":"x15.4.4.18","name":"15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )"},{"section":"x15.4.4.19","name":"15.4.4.19 Array.prototype.map ( callbackfn [ , thisArg ] )"},{"section":"x15.4.4.20","name":"15.4.4.20 Array.prototype.filter ( callbackfn [ , thisArg ] )"},{"section":"x15.4.4.21","name":"15.4.4.21 Array.prototype.reduce ( callbackfn [ , initialValue ] )"},{"section":"x15.4.4.22","name":"15.4.4.22 Array.prototype.reduceRight ( callbackfn [ , initialValue ] )"},{"section":"x15.4.5","name":"15.4.5 Properties of Array Instances"},{"section":"x15.4.5.1","name":"15.4.5.1 [[DefineOwnProperty]] ( P, Desc, Throw )"},{"section":"x15.4.5.2","name":"15.4.5.2 length"},{"section":"x15.5","name":"15.5 String Objects"},{"section":"x15.5.1","name":"15.5.1 The String Constructor Called as a Function"},{"section":"x15.5.1.1","name":"15.5.1.1 String ( [ value ] )"},{"section":"x15.5.2","name":"15.5.2 The String Constructor"},{"section":"x15.5.2.1","name":"15.5.2.1 new String ( [ value ] )"},{"section":"x15.5.3","name":"15.5.3 Properties of the String Constructor"},{"section":"x15.5.3.1","name":"15.5.3.1 String.prototype"},{"section":"x15.5.3.2","name":"15.5.3.2 String.fromCharCode ( [ char0 [ , char1 [ , … ] ] ] )"},{"section":"x15.5.4","name":"15.5.4 Properties of the String Prototype Object"},{"section":"x15.5.4.1","name":"15.5.4.1 String.prototype.constructor"},{"section":"x15.5.4.2","name":"15.5.4.2 String.prototype.toString ( )"},{"section":"x15.5.4.3","name":"15.5.4.3 String.prototype.valueOf ( )"},{"section":"x15.5.4.4","name":"15.5.4.4 String.prototype.charAt (pos)"},{"section":"x15.5.4.5","name":"15.5.4.5 String.prototype.charCodeAt (pos)"},{"section":"x15.5.4.6","name":"15.5.4.6 String.prototype.concat ( [ string1 [ , string2 [ , … ] ] ] )"},{"section":"x15.5.4.7","name":"15.5.4.7 String.prototype.indexOf (searchString, position)"},{"section":"x15.5.4.8","name":"15.5.4.8 String.prototype.lastIndexOf (searchString, position)"},{"section":"x15.5.4.9","name":"15.5.4.9 String.prototype.localeCompare (that)"},{"section":"x15.5.4.10","name":"15.5.4.10 String.prototype.match (regexp)"},{"section":"x15.5.4.11","name":"15.5.4.11 String.prototype.replace (searchValue, replaceValue)"},{"section":"x15.5.4.12","name":"15.5.4.12 String.prototype.search (regexp)"},{"section":"x15.5.4.13","name":"15.5.4.13 String.prototype.slice (start, end)"},{"section":"x15.5.4.14","name":"15.5.4.14 String.prototype.split (separator, limit)"},{"section":"x15.5.4.15","name":"15.5.4.15 String.prototype.substring (start, end)"},{"section":"x15.5.4.16","name":"15.5.4.16 String.prototype.toLowerCase ( )"},{"section":"x15.5.4.17","name":"15.5.4.17 String.prototype.toLocaleLowerCase ( )"},{"section":"x15.5.4.18","name":"15.5.4.18 String.prototype.toUpperCase ( )"},{"section":"x15.5.4.19","name":"15.5.4.19 String.prototype.toLocaleUpperCase ( )"},{"section":"x15.5.4.20","name":"15.5.4.20 String.prototype.trim ( )"},{"section":"x15.5.5","name":"15.5.5 Properties of String Instances"},{"section":"x15.5.5.1","name":"15.5.5.1 length"},{"section":"x15.5.5.2","name":"15.5.5.2 [[GetOwnProperty]] ( P )"},{"section":"x15.6","name":"15.6 Boolean Objects"},{"section":"x15.6.1","name":"15.6.1 The Boolean Constructor Called as a Function"},{"section":"x15.6.1.1","name":"15.6.1.1 Boolean (value)"},{"section":"x15.6.2","name":"15.6.2 The Boolean Constructor"},{"section":"x15.6.2.1","name":"15.6.2.1 new Boolean (value)"},{"section":"x15.6.3","name":"15.6.3 Properties of the Boolean Constructor"},{"section":"x15.6.3.1","name":"15.6.3.1 Boolean.prototype"},{"section":"x15.6.4","name":"15.6.4 Properties of the Boolean Prototype Object"},{"section":"x15.6.4.1","name":"15.6.4.1 Boolean.prototype.constructor"},{"section":"x15.6.4.2","name":"15.6.4.2 Boolean.prototype.toString ( )"},{"section":"x15.6.4.3","name":"15.6.4.3 Boolean.prototype.valueOf ( )"},{"section":"x15.6.5","name":"15.6.5 Properties of Boolean Instances"},{"section":"x15.7","name":"15.7 Number Objects"},{"section":"x15.7.1","name":"15.7.1 The Number Constructor Called as a Function"},{"section":"x15.7.1.1","name":"15.7.1.1 Number ( [ value ] )"},{"section":"x15.7.2","name":"15.7.2 The Number Constructor"},{"section":"x15.7.2.1","name":"15.7.2.1 new Number ( [ value ] )"},{"section":"x15.7.3","name":"15.7.3 Properties of the Number Constructor"},{"section":"x15.7.3.1","name":"15.7.3.1 Number.prototype"},{"section":"x15.7.3.2","name":"15.7.3.2 Number.MAX_VALUE"},{"section":"x15.7.3.3","name":"15.7.3.3 Number.MIN_VALUE"},{"section":"x15.7.3.4","name":"15.7.3.4 Number.NaN"},{"section":"x15.7.3.5","name":"15.7.3.5 Number.NEGATIVE_INFINITY"},{"section":"x15.7.3.6","name":"15.7.3.6 Number.POSITIVE_INFINITY"},{"section":"x15.7.4","name":"15.7.4 Properties of the Number Prototype Object"},{"section":"x15.7.4.1","name":"15.7.4.1 Number.prototype.constructor"},{"section":"x15.7.4.2","name":"15.7.4.2 Number.prototype.toString ( [ radix ] )"},{"section":"x15.7.4.3","name":"15.7.4.3 Number.prototype.toLocaleString()"},{"section":"x15.7.4.4","name":"15.7.4.4 Number.prototype.valueOf ( )"},{"section":"x15.7.4.5","name":"15.7.4.5 Number.prototype.toFixed (fractionDigits)"},{"section":"x15.7.4.6","name":"15.7.4.6 Number.prototype.toExponential (fractionDigits)"},{"section":"x15.7.4.7","name":"15.7.4.7 Number.prototype.toPrecision (precision)"},{"section":"x15.7.5","name":"15.7.5 Properties of Number Instances"},{"section":"x15.8","name":"15.8 The Math Object"},{"section":"x15.8.1","name":"15.8.1 Value Properties of the Math Object"},{"section":"x15.8.1.1","name":"15.8.1.1 E"},{"section":"x15.8.1.2","name":"15.8.1.2 LN10"},{"section":"x15.8.1.3","name":"15.8.1.3 LN2"},{"section":"x15.8.1.4","name":"15.8.1.4 LOG2E"},{"section":"x15.8.1.5","name":"15.8.1.5 LOG10E"},{"section":"x15.8.1.6","name":"15.8.1.6 PI"},{"section":"x15.8.1.7","name":"15.8.1.7 SQRT1_2"},{"section":"x15.8.1.8","name":"15.8.1.8 SQRT2"},{"section":"x15.8.2","name":"15.8.2 Function Properties of the Math Object"},{"section":"x15.8.2.1","name":"15.8.2.1 abs (x)"},{"section":"x15.8.2.2","name":"15.8.2.2 acos (x)"},{"section":"x15.8.2.3","name":"15.8.2.3 asin (x)"},{"section":"x15.8.2.4","name":"15.8.2.4 atan (x)"},{"section":"x15.8.2.5","name":"15.8.2.5 atan2 (y, x)"},{"section":"x15.8.2.6","name":"15.8.2.6 ceil (x)"},{"section":"x15.8.2.7","name":"15.8.2.7 cos (x)"},{"section":"x15.8.2.8","name":"15.8.2.8 exp (x)"},{"section":"x15.8.2.9","name":"15.8.2.9 floor (x)"},{"section":"x15.8.2.10","name":"15.8.2.10 log (x)"},{"section":"x15.8.2.11","name":"15.8.2.11 max ( [ value1 [ , value2 [ , … ] ] ] )"},{"section":"x15.8.2.12","name":"15.8.2.12 min ( [ value1 [ , value2 [ , … ] ] ] )"},{"section":"x15.8.2.13","name":"15.8.2.13 pow (x, y)"},{"section":"x15.8.2.14","name":"15.8.2.14 random ( )"},{"section":"x15.8.2.15","name":"15.8.2.15 round (x)"},{"section":"x15.8.2.16","name":"15.8.2.16 sin (x)"},{"section":"x15.8.2.17","name":"15.8.2.17 sqrt (x)"},{"section":"x15.8.2.18","name":"15.8.2.18 tan (x)"},{"section":"x15.9","name":"15.9 Date Objects"},{"section":"x15.9.1","name":"15.9.1 Overview of Date Objects and Definitions of Abstract Operators"},{"section":"x15.9.1.1","name":"15.9.1.1 Time Values and Time Range"},{"section":"x15.9.1.2","name":"15.9.1.2 Day Number and Time within Day"},{"section":"x15.9.1.3","name":"15.9.1.3 Year Number"},{"section":"x15.9.1.4","name":"15.9.1.4 Month Number"},{"section":"x15.9.1.5","name":"15.9.1.5 Date Number"},{"section":"x15.9.1.6","name":"15.9.1.6 Week Day"},{"section":"x15.9.1.7","name":"15.9.1.7 Local Time Zone Adjustment"},{"section":"x15.9.1.8","name":"15.9.1.8 Daylight Saving Time Adjustment"},{"section":"x15.9.1.9","name":"15.9.1.9 Local Time"},{"section":"x15.9.1.10","name":"15.9.1.10 Hours, Minutes, Second, and Milliseconds"},{"section":"x15.9.1.11","name":"15.9.1.11 MakeTime (hour, min, sec, ms)"},{"section":"x15.9.1.12","name":"15.9.1.12 MakeDay (year, month, date)"},{"section":"x15.9.1.13","name":"15.9.1.13 MakeDate (day, time)"},{"section":"x15.9.1.14","name":"15.9.1.14 TimeClip (time)"},{"section":"x15.9.1.15","name":"15.9.1.15 Date Time String Format"},{"section":"x15.9.1.15.1","name":"15.9.1.15.1 Extended years"},{"section":"x15.9.2","name":"15.9.2 The Date Constructor Called as a Function"},{"section":"x15.9.2.1","name":"15.9.2.1 Date ( [ year [, month [, date [, hours [, minutes [, seconds [, ms ] ] ] ] ] ] ] )"},{"section":"x15.9.3","name":"15.9.3 The Date Constructor"},{"section":"x15.9.3.1","name":"15.9.3.1 new Date (year, month [, date [, hours [, minutes [, seconds [, ms ] ] ] ] ] )"},{"section":"x15.9.3.2","name":"15.9.3.2 new Date (value)"},{"section":"x15.9.3.3","name":"15.9.3.3 new Date ( )"},{"section":"x15.9.4","name":"15.9.4 Properties of the Date Constructor"},{"section":"x15.9.4.1","name":"15.9.4.1 Date.prototype"},{"section":"x15.9.4.2","name":"15.9.4.2 Date.parse (string)"},{"section":"x15.9.4.3","name":"15.9.4.3 Date.UTC (year, month [, date [, hours [, minutes [, seconds [, ms ] ] ] ] ])"},{"section":"x15.9.4.4","name":"15.9.4.4 Date.now ( )"},{"section":"x15.9.5","name":"15.9.5 Properties of the Date Prototype Object"},{"section":"x15.9.5.1","name":"15.9.5.1 Date.prototype.constructor"},{"section":"x15.9.5.2","name":"15.9.5.2 Date.prototype.toString ( )"},{"section":"x15.9.5.3","name":"15.9.5.3 Date.prototype.toDateString ( )"},{"section":"x15.9.5.4","name":"15.9.5.4 Date.prototype.toTimeString ( )"},{"section":"x15.9.5.5","name":"15.9.5.5 Date.prototype.toLocaleString ( )"},{"section":"x15.9.5.6","name":"15.9.5.6 Date.prototype.toLocaleDateString ( )"},{"section":"x15.9.5.7","name":"15.9.5.7 Date.prototype.toLocaleTimeString ( )"},{"section":"x15.9.5.8","name":"15.9.5.8 Date.prototype.valueOf ( )"},{"section":"x15.9.5.9","name":"15.9.5.9 Date.prototype.getTime ( )"},{"section":"x15.9.5.10","name":"15.9.5.10 Date.prototype.getFullYear ( )"},{"section":"x15.9.5.11","name":"15.9.5.11 Date.prototype.getUTCFullYear ( )"},{"section":"x15.9.5.12","name":"15.9.5.12 Date.prototype.getMonth ( )"},{"section":"x15.9.5.13","name":"15.9.5.13 Date.prototype.getUTCMonth ( )"},{"section":"x15.9.5.14","name":"15.9.5.14 Date.prototype.getDate ( )"},{"section":"x15.9.5.15","name":"15.9.5.15 Date.prototype.getUTCDate ( )"},{"section":"x15.9.5.16","name":"15.9.5.16 Date.prototype.getDay ( )"},{"section":"x15.9.5.17","name":"15.9.5.17 Date.prototype.getUTCDay ( )"},{"section":"x15.9.5.18","name":"15.9.5.18 Date.prototype.getHours ( )"},{"section":"x15.9.5.19","name":"15.9.5.19 Date.prototype.getUTCHours ( )"},{"section":"x15.9.5.20","name":"15.9.5.20 Date.prototype.getMinutes ( )"},{"section":"x15.9.5.21","name":"15.9.5.21 Date.prototype.getUTCMinutes ( )"},{"section":"x15.9.5.22","name":"15.9.5.22 Date.prototype.getSeconds ( )"},{"section":"x15.9.5.23","name":"15.9.5.23 Date.prototype.getUTCSeconds ( )"},{"section":"x15.9.5.24","name":"15.9.5.24 Date.prototype.getMilliseconds ( )"},{"section":"x15.9.5.25","name":"15.9.5.25 Date.prototype.getUTCMilliseconds ( )"},{"section":"x15.9.5.26","name":"15.9.5.26 Date.prototype.getTimezoneOffset ( )"},{"section":"x15.9.5.27","name":"15.9.5.27 Date.prototype.setTime (time)"},{"section":"x15.9.5.28","name":"15.9.5.28 Date.prototype.setMilliseconds (ms)"},{"section":"x15.9.5.29","name":"15.9.5.29 Date.prototype.setUTCMilliseconds (ms)"},{"section":"x15.9.5.30","name":"15.9.5.30 Date.prototype.setSeconds (sec [, ms ] )"},{"section":"x15.9.5.31","name":"15.9.5.31 Date.prototype.setUTCSeconds (sec [, ms ] )"},{"section":"x15.9.5.32","name":"15.9.5.32 Date.prototype.setMinutes (min [, sec [, ms ] ] )"},{"section":"x15.9.5.33","name":"15.9.5.33 Date.prototype.setUTCMinutes (min [, sec [, ms ] ] )"},{"section":"x15.9.5.34","name":"15.9.5.34 Date.prototype.setHours (hour [, min [, sec [, ms ] ] ] )"},{"section":"x15.9.5.35","name":"15.9.5.35 Date.prototype.setUTCHours (hour [, min [, sec [, ms ] ] ] )"},{"section":"x15.9.5.36","name":"15.9.5.36 Date.prototype.setDate (date)"},{"section":"x15.9.5.37","name":"15.9.5.37 Date.prototype.setUTCDate (date)"},{"section":"x15.9.5.38","name":"15.9.5.38 Date.prototype.setMonth (month [, date ] )"},{"section":"x15.9.5.39","name":"15.9.5.39 Date.prototype.setUTCMonth (month [, date ] )"},{"section":"x15.9.5.40","name":"15.9.5.40 Date.prototype.setFullYear (year [, month [, date ] ] )"},{"section":"x15.9.5.41","name":"15.9.5.41 Date.prototype.setUTCFullYear (year [, month [, date ] ] )"},{"section":"x15.9.5.42","name":"15.9.5.42 Date.prototype.toUTCString ( )"},{"section":"x15.9.5.43","name":"15.9.5.43 Date.prototype.toISOString ( )"},{"section":"x15.9.5.44","name":"15.9.5.44 Date.prototype.toJSON ( key )"},{"section":"x15.9.6","name":"15.9.6 Properties of Date Instances"},{"section":"x15.10","name":"15.10 RegExp (Regular Expression) Objects"},{"section":"x15.10.1","name":"15.10.1 Patterns"},{"section":"x15.10.2","name":"15.10.2 Pattern Semantics"},{"section":"x15.10.2.1","name":"15.10.2.1 Notation"},{"section":"x15.10.2.2","name":"15.10.2.2 Pattern"},{"section":"x15.10.2.3","name":"15.10.2.3 Disjunction"},{"section":"x15.10.2.4","name":"15.10.2.4 Alternative"},{"section":"x15.10.2.5","name":"15.10.2.5 Term"},{"section":"x15.10.2.6","name":"15.10.2.6 Assertion"},{"section":"x15.10.2.7","name":"15.10.2.7 Quantifier"},{"section":"x15.10.2.8","name":"15.10.2.8 Atom"},{"section":"x15.10.2.9","name":"15.10.2.9 AtomEscape"},{"section":"x15.10.2.10","name":"15.10.2.10 CharacterEscape"},{"section":"x15.10.2.11","name":"15.10.2.11 DecimalEscape"},{"section":"x15.10.2.12","name":"15.10.2.12 CharacterClassEscape"},{"section":"x15.10.2.13","name":"15.10.2.13 CharacterClass"},{"section":"x15.10.2.14","name":"15.10.2.14 ClassRanges"},{"section":"x15.10.2.15","name":"15.10.2.15 NonemptyClassRanges"},{"section":"x15.10.2.16","name":"15.10.2.16 NonemptyClassRangesNoDash"},{"section":"x15.10.2.17","name":"15.10.2.17 ClassAtom"},{"section":"x15.10.2.18","name":"15.10.2.18 ClassAtomNoDash"},{"section":"x15.10.2.19","name":"15.10.2.19 ClassEscape"},{"section":"x15.10.3","name":"15.10.3 The RegExp Constructor Called as a Function"},{"section":"x15.10.3.1","name":"15.10.3.1 RegExp(pattern, flags)"},{"section":"x15.10.4","name":"15.10.4 The RegExp Constructor"},{"section":"x15.10.4.1","name":"15.10.4.1 new RegExp(pattern, flags)"},{"section":"x15.10.5","name":"15.10.5 Properties of the RegExp Constructor"},{"section":"x15.10.5.1","name":"15.10.5.1 RegExp.prototype"},{"section":"x15.10.6","name":"15.10.6 Properties of the RegExp Prototype Object"},{"section":"x15.10.6.1","name":"15.10.6.1 RegExp.prototype.constructor"},{"section":"x15.10.6.2","name":"15.10.6.2 RegExp.prototype.exec(string)"},{"section":"x15.10.6.3","name":"15.10.6.3 RegExp.prototype.test(string)"},{"section":"x15.10.6.4","name":"15.10.6.4 RegExp.prototype.toString()"},{"section":"x15.10.7","name":"15.10.7 Properties of RegExp Instances"},{"section":"x15.10.7.1","name":"15.10.7.1 source"},{"section":"x15.10.7.2","name":"15.10.7.2 global"},{"section":"x15.10.7.3","name":"15.10.7.3 ignoreCase"},{"section":"x15.10.7.4","name":"15.10.7.4 multiline"},{"section":"x15.10.7.5","name":"15.10.7.5 lastIndex"},{"section":"x15.11","name":"15.11 Error Objects"},{"section":"x15.11.1","name":"15.11.1 The Error Constructor Called as a Function"},{"section":"x15.11.1.1","name":"15.11.1.1 Error (message)"},{"section":"x15.11.2","name":"15.11.2 The Error Constructor"},{"section":"x15.11.2.1","name":"15.11.2.1 new Error (message)"},{"section":"x15.11.3","name":"15.11.3 Properties of the Error Constructor"},{"section":"x15.11.3.1","name":"15.11.3.1 Error.prototype"},{"section":"x15.11.4","name":"15.11.4 Properties of the Error Prototype Object"},{"section":"x15.11.4.1","name":"15.11.4.1 Error.prototype.constructor"},{"section":"x15.11.4.2","name":"15.11.4.2 Error.prototype.name"},{"section":"x15.11.4.3","name":"15.11.4.3 Error.prototype.message"},{"section":"x15.11.4.4","name":"15.11.4.4 Error.prototype.toString ( )"},{"section":"x15.11.5","name":"15.11.5 Properties of Error Instances"},{"section":"x15.11.6","name":"15.11.6 Native Error Types Used in This Standard"},{"section":"x15.11.6.1","name":"15.11.6.1 EvalError"},{"section":"x15.11.6.2","name":"15.11.6.2 RangeError"},{"section":"x15.11.6.3","name":"15.11.6.3 ReferenceError"},{"section":"x15.11.6.4","name":"15.11.6.4 SyntaxError"},{"section":"x15.11.6.5","name":"15.11.6.5 TypeError"},{"section":"x15.11.6.6","name":"15.11.6.6 URIError"},{"section":"x15.11.7","name":"15.11.7 NativeError Object Structure"},{"section":"x15.11.7.1","name":"15.11.7.1 NativeError Constructors Called as Functions"},{"section":"x15.11.7.2","name":"15.11.7.2 NativeError (message)"},{"section":"x15.11.7.3","name":"15.11.7.3 The NativeError Constructors"},{"section":"x15.11.7.4","name":"15.11.7.4 New NativeError (message)"},{"section":"x15.11.7.5","name":"15.11.7.5 Properties of the NativeError Constructors"},{"section":"x15.11.7.6","name":"15.11.7.6 NativeError.prototype"},{"section":"x15.11.7.7","name":"15.11.7.7 Properties of the NativeError Prototype Objects"},{"section":"x15.11.7.8","name":"15.11.7.8 NativeError.prototype.constructor"},{"section":"x15.11.7.9","name":"15.11.7.9 NativeError.prototype.name"},{"section":"x15.11.7.10","name":"15.11.7.10 NativeError.prototype.message"},{"section":"x15.11.7.11","name":"15.11.7.11 Properties of NativeError Instances"},{"section":"x15.12","name":"15.12 The JSON Object"},{"section":"x15.12.1","name":"15.12.1 The JSON Grammar "},{"section":"x15.12.1.1","name":"15.12.1.1 The JSON Lexical Grammar"},{"section":"x15.12.1.2","name":"15.12.1.2 The JSON Syntactic Grammar"},{"section":"x15.12.2","name":"15.12.2 parse ( text [ , reviver ] )"},{"section":"x15.12.3","name":"15.12.3 stringify ( value [ , replacer [ , space ] ] )"},{"section":"x16","name":"16 Errors"},{"section":"A","name":"Annex A (informative) Grammar Summary"},{"section":"A.1","name":"A.1 Lexical Grammar"},{"section":"A.2","name":"A.2 Number Conversions"},{"section":"A.3","name":"A.3 Expressions"},{"section":"A.4","name":"A.4 Statements"},{"section":"A.5","name":"A.5 Functions and Programs"},{"section":"A.6","name":"A.6 Universal Resource Identifier Character Classes"},{"section":"A.7","name":"A.7 Regular Expressions"},{"section":"A.8","name":"A.8 JSON"},{"section":"A.8.1","name":"A.8.1 JSON Lexical Grammar"},{"section":"A.8.2","name":"A.8.2 JSON Syntactic Grammar"},{"section":"B","name":"Annex B (informative) Compatibility"},{"section":"B.1","name":"B.1 Additional Syntax"},{"section":"B.1.1","name":"B.1.1 Numeric Literals"},{"section":"B.1.2","name":"B.1.2 String Literals"},{"section":"B.2","name":"B.2 Additional Properties"},{"section":"B.2.1","name":"B.2.1 escape (string)"},{"section":"B.2.2","name":"B.2.2 unescape (string)"},{"section":"B.2.3","name":"B.2.3 String.prototype.substr (start, length)"},{"section":"B.2.4","name":"B.2.4 Date.prototype.getYear ( )"},{"section":"B.2.5","name":"B.2.5 Date.prototype.setYear (year)"},{"section":"B.2.6","name":"B.2.6 Date.prototype.toGMTString ( )"},{"section":"C","name":"Annex C (informative) The Strict Mode of ECMAScript"},{"section":"D","name":"Annex D (informative) Corrections and Clarifications in the 5th Edition with Possible 3rd Edition Compatibility Impact"},{"section":"E","name":"Annex E (informative) Additions and Changes in the 5th Edition that Introduce Incompatibilities with the 3rd Edition"},{"section":"bibliography","name":"Bibliography"}];
  3378. function spec ( args ) {
  3379. var lookup = args.content.toLowerCase(), matches;
  3380. matches = specParts.filter( hasLookup ).map( mapLink );
  3381. bot.log( matches, '/spec done' );
  3382. if ( !matches.length ) {
  3383. return args + ' not found in spec';
  3384. }
  3385. return matches.join( ', ' );
  3386. function hasLookup ( obj ) {
  3387. return obj.name.toLowerCase().indexOf( lookup ) > -1;
  3388. }
  3389. function mapLink ( obj ) {
  3390. var name = args.escape( obj.name );
  3391. return '[' + name + '](http://es5.github.com/#' + obj.section + ')';
  3392. }
  3393. };
  3394. bot.addCommand({
  3395. name : 'spec',
  3396. fun : spec,
  3397. permissions : {
  3398. del : 'NONE'
  3399. },
  3400. description : 'Find a section in the ES5 spec'
  3401. });
  3402. }());
  3403. ;
  3404. (function () {
  3405. var types = {
  3406. answer : true,
  3407. question : true };
  3408. var ranges = {
  3409. //the result array is in descending order, so it's "reversed"
  3410. first : function ( arr ) {
  3411. return arr[ arr.length - 1 ];
  3412. },
  3413. last : function ( arr ) {
  3414. return arr[ 0 ];
  3415. },
  3416. between : function ( arr ) {
  3417. //SO api takes care of this for us
  3418. return arr;
  3419. }
  3420. };
  3421. function get ( args, cb ) {
  3422. //default:
  3423. // /get type range usrid
  3424. var parts = args.parse(),
  3425. type = parts[ 0 ] || 'answer',
  3426. plural = type + 's',
  3427. range = parts[ 1 ] || 'last',
  3428. usrid = parts[ 2 ];
  3429. //if "between" is given, fetch the correct usrid
  3430. // /get type between start end usrid
  3431. if ( range === 'between' ) {
  3432. usrid = parts[ 4 ];
  3433. }
  3434. //range is a number and no usrid, assume the range is the usrid, and
  3435. // default range to last
  3436. // /get type usrid
  3437. if ( !usrid && !isNaN(range) ) {
  3438. usrid = range;
  3439. range = 'last';
  3440. }
  3441. //if after all this usrid is falsy, assume the user's id
  3442. if ( !usrid ) {
  3443. usrid = args.get( 'user_id' );
  3444. }
  3445. bot.log( parts, 'get input' );
  3446. if ( !types.hasOwnProperty(type) ) {
  3447. return 'Invalid "getter" name ' + type;
  3448. }
  3449. if ( !ranges.hasOwnProperty(range) ) {
  3450. return 'Invalid range specifier ' + range;
  3451. }
  3452. var url = 'http://api.stackexchange.com/2.1/users/' + usrid + '/' + plural;
  3453. var params = {
  3454. site : bot.adapter.site,
  3455. sort : 'creation',
  3456. //basically, only show answer/question id and their link
  3457. filter : '!BGS1(RNaKd_71l)9SkX3zg.ifSRSSy'
  3458. };
  3459. bot.log( url, params, '/get building url' );
  3460. if ( range === 'between' ) {
  3461. params.fromdate = Date.parse( parts[2] );
  3462. params.todate = Date.parse( parts[3] );
  3463. bot.log( url, params, '/get building url between' );
  3464. }
  3465. IO.jsonp({
  3466. url : url,
  3467. data : params,
  3468. fun : parseResponse
  3469. });
  3470. function parseResponse ( respObj ) {
  3471. //Une erreru! L'horreur!
  3472. if ( respObj.error_message ) {
  3473. args.reply( respObj.error_message );
  3474. return;
  3475. }
  3476. //get only the part we care about in the result, based on which one
  3477. // the user asked for (first, last, between)
  3478. //respObj will have an answers or questions property, based on what we
  3479. // queried for, in array form
  3480. var posts = [].concat( ranges[range](respObj.items) ),
  3481. res;
  3482. bot.log( posts.slice(), '/get parseResponse parsing' );
  3483. if ( posts.length ) {
  3484. res = makeUserResponse( posts );
  3485. }
  3486. else {
  3487. res = 'User did not submit any ' + plural;
  3488. }
  3489. bot.log( res, '/get parseResponse parsed');
  3490. if ( cb && cb.call ) {
  3491. cb( res );
  3492. }
  3493. else {
  3494. args.directreply( res );
  3495. }
  3496. }
  3497. function makeUserResponse( posts ) {
  3498. return posts.map(function ( post ) {
  3499. return post.link;
  3500. }).join ( ' ; ');
  3501. }
  3502. };
  3503. bot.addCommand({
  3504. name : 'get',
  3505. fun : get,
  3506. permissions : {
  3507. del : 'NONE'
  3508. },
  3509. async : true
  3510. });
  3511. }());
  3512. ;
  3513. (function () {
  3514. var help_message = 'Fetches and beautifies a message containing html, ' +
  3515. 'css or js. `/beautify msgid [lang=js]`';
  3516. var err404 = 'Message {0} not found';
  3517. var beautifiers = {
  3518. js : js_beautify,
  3519. css : css_beautify,
  3520. html : style_html };
  3521. function beautify ( msg ) {
  3522. var args = msg.parse(),
  3523. possible_id = args.shift(),
  3524. lang = ( args.shift() || 'js' ).toLowerCase();
  3525. bot.log( possible_id, lang, '/beautify input' );
  3526. if ( !beautifiers.hasOwnProperty(lang) ) {
  3527. return 'Unrecognized language {0}. Options: {1}'
  3528. .supplant( lang, Object.keys(beautifiers).join(', ') );
  3529. }
  3530. var id = Number( fetch_message_id(possible_id, msg) );
  3531. if ( id < 0 ) {
  3532. return err404.supplant( id );
  3533. }
  3534. fetch_message( id, finish );
  3535. function finish ( code ) {
  3536. if ( !code ) {
  3537. bot.log( '/beautify not found' );
  3538. msg.reply( err404.supplant(id) );
  3539. }
  3540. else {
  3541. //so...we meet at last
  3542. bot.log( code, '/beautify beautifying' );
  3543. msg.send( msg.codify(beautifiers[lang](code)) );
  3544. }
  3545. }
  3546. }
  3547. function fetch_message( id, cb ) {
  3548. IO.xhr({
  3549. method : 'GET',
  3550. url : '/message/' + id,
  3551. data : {
  3552. plain : true
  3553. },
  3554. complete : complete
  3555. });
  3556. function complete ( resp ) {
  3557. //h4x everywhere
  3558. //the SO error page begins with a \r. that's the only way we can tell
  3559. // it apart from another, possibly valid message, since messages can't
  3560. // be whitespace padded
  3561. if ( resp[0] === '\r' ) {
  3562. resp = null;
  3563. }
  3564. else {
  3565. resp = IO.decodehtmlEntities( resp );
  3566. }
  3567. cb( resp );
  3568. }
  3569. }
  3570. function fetch_message_id ( id, msg ) {
  3571. if ( /^\d+$/.test(id) ) {
  3572. return id;
  3573. }
  3574. bot.log( id, '/beautify fetch_message_id' );
  3575. var message = fetch_last_message_of( msg.findUserid(id) );
  3576. if ( !message ) {
  3577. return -1;
  3578. }
  3579. return /\d+/.exec( message.id )[ 0 ];
  3580. }
  3581. function fetch_last_message_of ( usrid ) {
  3582. var last_monologue = [].filter.call(
  3583. document.getElementsByClassName( 'user-' + usrid ),
  3584. class_test
  3585. ).pop();
  3586. if ( !last_monologue ) {
  3587. return undefined;
  3588. }
  3589. return [].pop.call(
  3590. last_monologue.getElementsByClassName( 'message' ) );
  3591. function class_test ( elem ) {
  3592. return /\bmonologue\b/.test( elem.className )
  3593. }
  3594. }
  3595. bot.addCommand({
  3596. name : 'beautify',
  3597. fun : beautify,
  3598. permission : {
  3599. del : 'NONE'
  3600. },
  3601. description : help_message,
  3602. });
  3603. }());
  3604. /*jslint onevar: false, plusplus: false */
  3605. /*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
  3606. /*
  3607. JS Beautifier
  3608. ---------------
  3609. Written by Einar Lielmanis, <einar@jsbeautifier.org>
  3610. http://jsbeautifier.org/
  3611. Originally converted to javascript by Vital, <vital76@gmail.com>
  3612. "End braces on own line" added by Chris J. Shull, <chrisjshull@gmail.com>
  3613. You are free to use this in any way you want, in case you find this useful or working for you.
  3614. Usage:
  3615. js_beautify(js_source_text);
  3616. js_beautify(js_source_text, options);
  3617. The options are:
  3618. indent_size (default 4) - indentation size,
  3619. indent_char (default space) - character to indent with,
  3620. preserve_newlines (default true) - whether existing line breaks should be preserved,
  3621. max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk,
  3622. jslint_happy (default false) - if true, then jslint-stricter mode is enforced.
  3623. jslint_happy !jslint_happy
  3624. ---------------------------------
  3625. function () function()
  3626. brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "expand-strict"
  3627. put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line.
  3628. expand-strict: put brace on own line even in such cases:
  3629. var a =
  3630. {
  3631. a: 5,
  3632. b: 6
  3633. }
  3634. This mode may break your scripts - e.g "return { a: 1 }" will be broken into two lines, so beware.
  3635. space_before_conditional (default true) - should the space before conditional statement be added, "if(true)" vs "if (true)",
  3636. unescape_strings (default false) - should printable characters in strings encoded in \xNN notation be unescaped, "example" vs "\x65\x78\x61\x6d\x70\x6c\x65"
  3637. e.g
  3638. js_beautify(js_source_text, {
  3639. 'indent_size': 1,
  3640. 'indent_char': '\t'
  3641. });
  3642. */
  3643. function js_beautify(js_source_text, options) {
  3644. var input, output, token_text, last_type, last_text, last_last_text, last_word, flags, flag_store, indent_string;
  3645. var whitespace, wordchar, punct, parser_pos, line_starters, digits;
  3646. var prefix, token_type, do_block_just_closed;
  3647. var wanted_newline, just_added_newline, n_newlines;
  3648. var preindent_string = '';
  3649. // Some interpreters have unexpected results with foo = baz || bar;
  3650. options = options ? options : {};
  3651. var opt_brace_style;
  3652. // compatibility
  3653. if (options.space_after_anon_function !== undefined && options.jslint_happy === undefined) {
  3654. options.jslint_happy = options.space_after_anon_function;
  3655. }
  3656. if (options.braces_on_own_line !== undefined) { //graceful handling of deprecated option
  3657. opt_brace_style = options.braces_on_own_line ? "expand" : "collapse";
  3658. }
  3659. opt_brace_style = options.brace_style ? options.brace_style : (opt_brace_style ? opt_brace_style : "collapse");
  3660. var opt_indent_size = options.indent_size ? options.indent_size : 4,
  3661. opt_indent_char = options.indent_char ? options.indent_char : ' ',
  3662. opt_preserve_newlines = typeof options.preserve_newlines === 'undefined' ? true : options.preserve_newlines,
  3663. opt_break_chained_methods = typeof options.break_chained_methods === 'undefined' ? false : options.break_chained_methods,
  3664. opt_max_preserve_newlines = typeof options.max_preserve_newlines === 'undefined' ? false : options.max_preserve_newlines,
  3665. opt_jslint_happy = options.jslint_happy === 'undefined' ? false : options.jslint_happy,
  3666. opt_keep_array_indentation = typeof options.keep_array_indentation === 'undefined' ? false : options.keep_array_indentation,
  3667. opt_space_before_conditional = typeof options.space_before_conditional === 'undefined' ? true : options.space_before_conditional,
  3668. opt_unescape_strings = typeof options.unescape_strings === 'undefined' ? false : options.unescape_strings;
  3669. just_added_newline = false;
  3670. // cache the source's length.
  3671. var input_length = js_source_text.length;
  3672. function trim_output(eat_newlines) {
  3673. eat_newlines = typeof eat_newlines === 'undefined' ? false : eat_newlines;
  3674. while (output.length && (output[output.length - 1] === ' '
  3675. || output[output.length - 1] === indent_string
  3676. || output[output.length - 1] === preindent_string
  3677. || (eat_newlines && (output[output.length - 1] === '\n' || output[output.length - 1] === '\r')))) {
  3678. output.pop();
  3679. }
  3680. }
  3681. function trim(s) {
  3682. return s.replace(/^\s\s*|\s\s*$/, '');
  3683. }
  3684. // we could use just string.split, but
  3685. // IE doesn't like returning empty strings
  3686. function split_newlines(s) {
  3687. //return s.split(/\x0d\x0a|\x0a/);
  3688. s = s.replace(/\x0d/g, '');
  3689. var out = [],
  3690. idx = s.indexOf("\n");
  3691. while (idx !== -1) {
  3692. out.push(s.substring(0, idx));
  3693. s = s.substring(idx + 1);
  3694. idx = s.indexOf("\n");
  3695. }
  3696. if (s.length) {
  3697. out.push(s);
  3698. }
  3699. return out;
  3700. }
  3701. function force_newline() {
  3702. var old_keep_array_indentation = opt_keep_array_indentation;
  3703. opt_keep_array_indentation = false;
  3704. print_newline();
  3705. opt_keep_array_indentation = old_keep_array_indentation;
  3706. }
  3707. function print_newline(ignore_repeated, reset_statement_flags) {
  3708. flags.eat_next_space = false;
  3709. if (opt_keep_array_indentation && is_array(flags.mode)) {
  3710. return;
  3711. }
  3712. ignore_repeated = typeof ignore_repeated === 'undefined' ? true : ignore_repeated;
  3713. reset_statement_flags = typeof reset_statement_flags === 'undefined' ? true : reset_statement_flags;
  3714. if (reset_statement_flags) {
  3715. flags.if_line = false;
  3716. flags.chain_extra_indentation = 0;
  3717. }
  3718. trim_output();
  3719. if (!output.length) {
  3720. return; // no newline on start of file
  3721. }
  3722. if (output[output.length - 1] !== "\n" || !ignore_repeated) {
  3723. just_added_newline = true;
  3724. output.push("\n");
  3725. }
  3726. if (preindent_string) {
  3727. output.push(preindent_string);
  3728. }
  3729. for (var i = 0; i < flags.indentation_level + flags.chain_extra_indentation; i += 1) {
  3730. output.push(indent_string);
  3731. }
  3732. if (flags.var_line && flags.var_line_reindented) {
  3733. output.push(indent_string); // skip space-stuffing, if indenting with a tab
  3734. }
  3735. }
  3736. function print_single_space() {
  3737. if (last_type === 'TK_COMMENT') {
  3738. return print_newline();
  3739. }
  3740. if (flags.eat_next_space) {
  3741. flags.eat_next_space = false;
  3742. return;
  3743. }
  3744. var last_output = ' ';
  3745. if (output.length) {
  3746. last_output = output[output.length - 1];
  3747. }
  3748. if (last_output !== ' ' && last_output !== '\n' && last_output !== indent_string) { // prevent occassional duplicate space
  3749. output.push(' ');
  3750. }
  3751. }
  3752. function print_token() {
  3753. just_added_newline = false;
  3754. flags.eat_next_space = false;
  3755. output.push(token_text);
  3756. }
  3757. function indent() {
  3758. flags.indentation_level += 1;
  3759. }
  3760. function remove_indent() {
  3761. if (output.length && output[output.length - 1] === indent_string) {
  3762. output.pop();
  3763. }
  3764. }
  3765. function set_mode(mode) {
  3766. if (flags) {
  3767. flag_store.push(flags);
  3768. }
  3769. flags = {
  3770. previous_mode: flags ? flags.mode : 'BLOCK',
  3771. mode: mode,
  3772. var_line: false,
  3773. var_line_tainted: false,
  3774. var_line_reindented: false,
  3775. in_html_comment: false,
  3776. if_line: false,
  3777. chain_extra_indentation: 0,
  3778. in_case_statement: false, // switch(..){ INSIDE HERE }
  3779. in_case: false, // we're on the exact line with "case 0:"
  3780. case_body: false, // the indented case-action block
  3781. eat_next_space: false,
  3782. indentation_level: (flags ? flags.indentation_level + ((flags.var_line && flags.var_line_reindented) ? 1 : 0) : 0),
  3783. ternary_depth: 0
  3784. };
  3785. }
  3786. function is_array(mode) {
  3787. return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]';
  3788. }
  3789. function is_expression(mode) {
  3790. return in_array(mode, ['[EXPRESSION]', '(EXPRESSION)', '(FOR-EXPRESSION)', '(COND-EXPRESSION)']);
  3791. }
  3792. function restore_mode() {
  3793. do_block_just_closed = flags.mode === 'DO_BLOCK';
  3794. if (flag_store.length > 0) {
  3795. var mode = flags.mode;
  3796. flags = flag_store.pop();
  3797. flags.previous_mode = mode;
  3798. }
  3799. }
  3800. function all_lines_start_with(lines, c) {
  3801. for (var i = 0; i < lines.length; i++) {
  3802. var line = trim(lines[i]);
  3803. if (line.charAt(0) !== c) {
  3804. return false;
  3805. }
  3806. }
  3807. return true;
  3808. }
  3809. function is_special_word(word) {
  3810. return in_array(word, ['case', 'return', 'do', 'if', 'throw', 'else']);
  3811. }
  3812. function in_array(what, arr) {
  3813. for (var i = 0; i < arr.length; i += 1) {
  3814. if (arr[i] === what) {
  3815. return true;
  3816. }
  3817. }
  3818. return false;
  3819. }
  3820. function look_up(exclude) {
  3821. var local_pos = parser_pos;
  3822. var c = input.charAt(local_pos);
  3823. while (in_array(c, whitespace) && c !== exclude) {
  3824. local_pos++;
  3825. if (local_pos >= input_length) {
  3826. return 0;
  3827. }
  3828. c = input.charAt(local_pos);
  3829. }
  3830. return c;
  3831. }
  3832. function get_next_token() {
  3833. var i;
  3834. var resulting_string;
  3835. n_newlines = 0;
  3836. if (parser_pos >= input_length) {
  3837. return ['', 'TK_EOF'];
  3838. }
  3839. wanted_newline = false;
  3840. var c = input.charAt(parser_pos);
  3841. parser_pos += 1;
  3842. var keep_whitespace = opt_keep_array_indentation && is_array(flags.mode);
  3843. if (keep_whitespace) {
  3844. var whitespace_count = 0;
  3845. while (in_array(c, whitespace)) {
  3846. if (c === "\n") {
  3847. trim_output();
  3848. output.push("\n");
  3849. just_added_newline = true;
  3850. whitespace_count = 0;
  3851. } else {
  3852. if (c === '\t') {
  3853. whitespace_count += 4;
  3854. } else if (c === '\r') {
  3855. // nothing
  3856. } else {
  3857. whitespace_count += 1;
  3858. }
  3859. }
  3860. if (parser_pos >= input_length) {
  3861. return ['', 'TK_EOF'];
  3862. }
  3863. c = input.charAt(parser_pos);
  3864. parser_pos += 1;
  3865. }
  3866. if (just_added_newline) {
  3867. for (i = 0; i < whitespace_count; i++) {
  3868. output.push(' ');
  3869. }
  3870. }
  3871. } else {
  3872. while (in_array(c, whitespace)) {
  3873. if (c === "\n") {
  3874. n_newlines += ((opt_max_preserve_newlines) ? (n_newlines <= opt_max_preserve_newlines) ? 1 : 0 : 1);
  3875. }
  3876. if (parser_pos >= input_length) {
  3877. return ['', 'TK_EOF'];
  3878. }
  3879. c = input.charAt(parser_pos);
  3880. parser_pos += 1;
  3881. }
  3882. if (opt_preserve_newlines) {
  3883. if (n_newlines > 1) {
  3884. for (i = 0; i < n_newlines; i += 1) {
  3885. print_newline(i === 0);
  3886. just_added_newline = true;
  3887. }
  3888. }
  3889. }
  3890. wanted_newline = n_newlines > 0;
  3891. }
  3892. if (in_array(c, wordchar)) {
  3893. if (parser_pos < input_length) {
  3894. while (in_array(input.charAt(parser_pos), wordchar)) {
  3895. c += input.charAt(parser_pos);
  3896. parser_pos += 1;
  3897. if (parser_pos === input_length) {
  3898. break;
  3899. }
  3900. }
  3901. }
  3902. // small and surprisingly unugly hack for 1E-10 representation
  3903. if (parser_pos !== input_length && c.match(/^[0-9]+[Ee]$/) && (input.charAt(parser_pos) === '-' || input.charAt(parser_pos) === '+')) {
  3904. var sign = input.charAt(parser_pos);
  3905. parser_pos += 1;
  3906. var t = get_next_token();
  3907. c += sign + t[0];
  3908. return [c, 'TK_WORD'];
  3909. }
  3910. if (c === 'in') { // hack for 'in' operator
  3911. return [c, 'TK_OPERATOR'];
  3912. }
  3913. if (wanted_newline && last_type !== 'TK_OPERATOR'
  3914. && last_type !== 'TK_EQUALS'
  3915. && !flags.if_line && (opt_preserve_newlines || last_text !== 'var')) {
  3916. print_newline();
  3917. }
  3918. return [c, 'TK_WORD'];
  3919. }
  3920. if (c === '(' || c === '[') {
  3921. return [c, 'TK_START_EXPR'];
  3922. }
  3923. if (c === ')' || c === ']') {
  3924. return [c, 'TK_END_EXPR'];
  3925. }
  3926. if (c === '{') {
  3927. return [c, 'TK_START_BLOCK'];
  3928. }
  3929. if (c === '}') {
  3930. return [c, 'TK_END_BLOCK'];
  3931. }
  3932. if (c === ';') {
  3933. return [c, 'TK_SEMICOLON'];
  3934. }
  3935. if (c === '/') {
  3936. var comment = '';
  3937. // peek for comment /* ... */
  3938. var inline_comment = true;
  3939. if (input.charAt(parser_pos) === '*') {
  3940. parser_pos += 1;
  3941. if (parser_pos < input_length) {
  3942. while (parser_pos < input_length &&
  3943. ! (input.charAt(parser_pos) === '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) === '/')) {
  3944. c = input.charAt(parser_pos);
  3945. comment += c;
  3946. if (c === "\n" || c === "\r") {
  3947. inline_comment = false;
  3948. }
  3949. parser_pos += 1;
  3950. if (parser_pos >= input_length) {
  3951. break;
  3952. }
  3953. }
  3954. }
  3955. parser_pos += 2;
  3956. if (inline_comment && n_newlines === 0) {
  3957. return ['/*' + comment + '*/', 'TK_INLINE_COMMENT'];
  3958. } else {
  3959. return ['/*' + comment + '*/', 'TK_BLOCK_COMMENT'];
  3960. }
  3961. }
  3962. // peek for comment // ...
  3963. if (input.charAt(parser_pos) === '/') {
  3964. comment = c;
  3965. while (input.charAt(parser_pos) !== '\r' && input.charAt(parser_pos) !== '\n') {
  3966. comment += input.charAt(parser_pos);
  3967. parser_pos += 1;
  3968. if (parser_pos >= input_length) {
  3969. break;
  3970. }
  3971. }
  3972. if (wanted_newline) {
  3973. print_newline();
  3974. }
  3975. return [comment, 'TK_COMMENT'];
  3976. }
  3977. }
  3978. if (c === "'" || // string
  3979. c === '"' || // string
  3980. (c === '/' &&
  3981. ((last_type === 'TK_WORD' && is_special_word(last_text)) ||
  3982. (last_text === ')' && in_array(flags.previous_mode, ['(COND-EXPRESSION)', '(FOR-EXPRESSION)'])) ||
  3983. (last_type === 'TK_COMMA' || last_type === 'TK_COMMENT' || last_type === 'TK_START_EXPR' || last_type === 'TK_START_BLOCK' || last_type === 'TK_END_BLOCK' || last_type === 'TK_OPERATOR' || last_type === 'TK_EQUALS' || last_type === 'TK_EOF' || last_type === 'TK_SEMICOLON')))) { // regexp
  3984. var sep = c;
  3985. var esc = false;
  3986. var esc1 = 0;
  3987. var esc2 = 0;
  3988. resulting_string = c;
  3989. if (parser_pos < input_length) {
  3990. if (sep === '/') {
  3991. //
  3992. // handle regexp separately...
  3993. //
  3994. var in_char_class = false;
  3995. while (esc || in_char_class || input.charAt(parser_pos) !== sep) {
  3996. resulting_string += input.charAt(parser_pos);
  3997. if (!esc) {
  3998. esc = input.charAt(parser_pos) === '\\';
  3999. if (input.charAt(parser_pos) === '[') {
  4000. in_char_class = true;
  4001. } else if (input.charAt(parser_pos) === ']') {
  4002. in_char_class = false;
  4003. }
  4004. } else {
  4005. esc = false;
  4006. }
  4007. parser_pos += 1;
  4008. if (parser_pos >= input_length) {
  4009. // incomplete string/rexp when end-of-file reached.
  4010. // bail out with what had been received so far.
  4011. return [resulting_string, 'TK_STRING'];
  4012. }
  4013. }
  4014. } else {
  4015. //
  4016. // and handle string also separately
  4017. //
  4018. while (esc || input.charAt(parser_pos) !== sep) {
  4019. resulting_string += input.charAt(parser_pos);
  4020. if (esc1 && esc1 >= esc2) {
  4021. esc1 = parseInt(resulting_string.substr(-esc2), 16);
  4022. if (esc1 && esc1 >= 0x20 && esc1 <= 0x7e) {
  4023. esc1 = String.fromCharCode(esc1);
  4024. resulting_string = resulting_string.substr(0, resulting_string.length - esc2 - 2) + (((esc1 === sep) || (esc1 === '\\')) ? '\\' : '') + esc1;
  4025. }
  4026. esc1 = 0;
  4027. }
  4028. if (esc1) {
  4029. esc1++;
  4030. } else if (!esc) {
  4031. esc = input.charAt(parser_pos) === '\\';
  4032. } else {
  4033. esc = false;
  4034. if (opt_unescape_strings) {
  4035. if (input.charAt(parser_pos) === 'x') {
  4036. esc1++;
  4037. esc2 = 2;
  4038. } else if (input.charAt(parser_pos) === 'u') {
  4039. esc1++;
  4040. esc2 = 4;
  4041. }
  4042. }
  4043. }
  4044. parser_pos += 1;
  4045. if (parser_pos >= input_length) {
  4046. // incomplete string/rexp when end-of-file reached.
  4047. // bail out with what had been received so far.
  4048. return [resulting_string, 'TK_STRING'];
  4049. }
  4050. }
  4051. }
  4052. }
  4053. parser_pos += 1;
  4054. resulting_string += sep;
  4055. if (sep === '/') {
  4056. // regexps may have modifiers /regexp/MOD , so fetch those, too
  4057. while (parser_pos < input_length && in_array(input.charAt(parser_pos), wordchar)) {
  4058. resulting_string += input.charAt(parser_pos);
  4059. parser_pos += 1;
  4060. }
  4061. }
  4062. return [resulting_string, 'TK_STRING'];
  4063. }
  4064. if (c === '#') {
  4065. if (output.length === 0 && input.charAt(parser_pos) === '!') {
  4066. // shebang
  4067. resulting_string = c;
  4068. while (parser_pos < input_length && c !== '\n') {
  4069. c = input.charAt(parser_pos);
  4070. resulting_string += c;
  4071. parser_pos += 1;
  4072. }
  4073. output.push(trim(resulting_string) + '\n');
  4074. print_newline();
  4075. return get_next_token();
  4076. }
  4077. // Spidermonkey-specific sharp variables for circular references
  4078. // https://developer.mozilla.org/En/Sharp_variables_in_JavaScript
  4079. // http://mxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935
  4080. var sharp = '#';
  4081. if (parser_pos < input_length && in_array(input.charAt(parser_pos), digits)) {
  4082. do {
  4083. c = input.charAt(parser_pos);
  4084. sharp += c;
  4085. parser_pos += 1;
  4086. } while (parser_pos < input_length && c !== '#' && c !== '=');
  4087. if (c === '#') {
  4088. //
  4089. } else if (input.charAt(parser_pos) === '[' && input.charAt(parser_pos + 1) === ']') {
  4090. sharp += '[]';
  4091. parser_pos += 2;
  4092. } else if (input.charAt(parser_pos) === '{' && input.charAt(parser_pos + 1) === '}') {
  4093. sharp += '{}';
  4094. parser_pos += 2;
  4095. }
  4096. return [sharp, 'TK_WORD'];
  4097. }
  4098. }
  4099. if (c === '<' && input.substring(parser_pos - 1, parser_pos + 3) === '<!--') {
  4100. parser_pos += 3;
  4101. c = '<!--';
  4102. while (input.charAt(parser_pos) !== '\n' && parser_pos < input_length) {
  4103. c += input.charAt(parser_pos);
  4104. parser_pos++;
  4105. }
  4106. flags.in_html_comment = true;
  4107. return [c, 'TK_COMMENT'];
  4108. }
  4109. if (c === '-' && flags.in_html_comment && input.substring(parser_pos - 1, parser_pos + 2) === '-->') {
  4110. flags.in_html_comment = false;
  4111. parser_pos += 2;
  4112. if (wanted_newline) {
  4113. print_newline();
  4114. }
  4115. return ['-->', 'TK_COMMENT'];
  4116. }
  4117. if (c === '.') {
  4118. return [c, 'TK_DOT'];
  4119. }
  4120. if (in_array(c, punct)) {
  4121. while (parser_pos < input_length && in_array(c + input.charAt(parser_pos), punct)) {
  4122. c += input.charAt(parser_pos);
  4123. parser_pos += 1;
  4124. if (parser_pos >= input_length) {
  4125. break;
  4126. }
  4127. }
  4128. if (c === ',') {
  4129. return [c, 'TK_COMMA'];
  4130. } else if (c === '=') {
  4131. return [c, 'TK_EQUALS'];
  4132. } else {
  4133. return [c, 'TK_OPERATOR'];
  4134. }
  4135. }
  4136. return [c, 'TK_UNKNOWN'];
  4137. }
  4138. //----------------------------------
  4139. indent_string = '';
  4140. while (opt_indent_size > 0) {
  4141. indent_string += opt_indent_char;
  4142. opt_indent_size -= 1;
  4143. }
  4144. while (js_source_text && (js_source_text.charAt(0) === ' ' || js_source_text.charAt(0) === '\t')) {
  4145. preindent_string += js_source_text.charAt(0);
  4146. js_source_text = js_source_text.substring(1);
  4147. }
  4148. input = js_source_text;
  4149. last_word = ''; // last 'TK_WORD' passed
  4150. last_type = 'TK_START_EXPR'; // last token type
  4151. last_text = ''; // last token text
  4152. last_last_text = ''; // pre-last token text
  4153. output = [];
  4154. do_block_just_closed = false;
  4155. whitespace = "\n\r\t ".split('');
  4156. wordchar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split('');
  4157. digits = '0123456789'.split('');
  4158. punct = '+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |= ::';
  4159. punct += ' <%= <% %> <?= <? ?>'; // try to be a good boy and try not to break the markup language identifiers
  4160. punct = punct.split(' ');
  4161. // words which should always start on new line.
  4162. line_starters = 'continue,try,throw,return,var,if,switch,case,default,for,while,break,function'.split(',');
  4163. // states showing if we are currently in expression (i.e. "if" case) - 'EXPRESSION', or in usual block (like, procedure), 'BLOCK'.
  4164. // some formatting depends on that.
  4165. flag_store = [];
  4166. set_mode('BLOCK');
  4167. parser_pos = 0;
  4168. while (true) {
  4169. var t = get_next_token();
  4170. token_text = t[0];
  4171. token_type = t[1];
  4172. if (token_type === 'TK_EOF') {
  4173. break;
  4174. }
  4175. switch (token_type) {
  4176. case 'TK_START_EXPR':
  4177. if (token_text === '[') {
  4178. if (last_type === 'TK_WORD' || last_text === ')') {
  4179. // this is array index specifier, break immediately
  4180. // a[x], fn()[x]
  4181. if (in_array(last_text, line_starters)) {
  4182. print_single_space();
  4183. }
  4184. set_mode('(EXPRESSION)');
  4185. print_token();
  4186. break;
  4187. }
  4188. if (flags.mode === '[EXPRESSION]' || flags.mode === '[INDENTED-EXPRESSION]') {
  4189. if (last_last_text === ']' && last_text === ',') {
  4190. // ], [ goes to new line
  4191. if (flags.mode === '[EXPRESSION]') {
  4192. flags.mode = '[INDENTED-EXPRESSION]';
  4193. if (!opt_keep_array_indentation) {
  4194. indent();
  4195. }
  4196. }
  4197. set_mode('[EXPRESSION]');
  4198. if (!opt_keep_array_indentation) {
  4199. print_newline();
  4200. }
  4201. } else if (last_text === '[') {
  4202. if (flags.mode === '[EXPRESSION]') {
  4203. flags.mode = '[INDENTED-EXPRESSION]';
  4204. if (!opt_keep_array_indentation) {
  4205. indent();
  4206. }
  4207. }
  4208. set_mode('[EXPRESSION]');
  4209. if (!opt_keep_array_indentation) {
  4210. print_newline();
  4211. }
  4212. } else {
  4213. set_mode('[EXPRESSION]');
  4214. }
  4215. } else {
  4216. set_mode('[EXPRESSION]');
  4217. }
  4218. } else {
  4219. if (last_word === 'for') {
  4220. set_mode('(FOR-EXPRESSION)');
  4221. } else if (in_array(last_word, ['if', 'while'])) {
  4222. set_mode('(COND-EXPRESSION)');
  4223. } else {
  4224. set_mode('(EXPRESSION)');
  4225. }
  4226. }
  4227. if (last_text === ';' || last_type === 'TK_START_BLOCK') {
  4228. print_newline();
  4229. } else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK' || last_text === '.') {
  4230. if (wanted_newline) {
  4231. print_newline();
  4232. }
  4233. // do nothing on (( and )( and ][ and ]( and .(
  4234. } else if (last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') {
  4235. print_single_space();
  4236. } else if (last_word === 'function' || last_word === 'typeof') {
  4237. // function() vs function ()
  4238. if (opt_jslint_happy) {
  4239. print_single_space();
  4240. }
  4241. } else if (in_array(last_text, line_starters) || last_text === 'catch') {
  4242. if (opt_space_before_conditional) {
  4243. print_single_space();
  4244. }
  4245. }
  4246. print_token();
  4247. break;
  4248. case 'TK_DOT':
  4249. if (is_special_word(last_text)) {
  4250. print_single_space();
  4251. } else if (last_text === ')') {
  4252. if (opt_break_chained_methods || wanted_newline) {
  4253. flags.chain_extra_indentation = 1;
  4254. print_newline(true /* ignore_repeated */, false /* reset_statement_flags */);
  4255. }
  4256. }
  4257. print_token();
  4258. break;
  4259. case 'TK_END_EXPR':
  4260. if (token_text === ']') {
  4261. if (opt_keep_array_indentation) {
  4262. if (last_text === '}') {
  4263. // trim_output();
  4264. // print_newline(true);
  4265. remove_indent();
  4266. print_token();
  4267. restore_mode();
  4268. break;
  4269. }
  4270. } else {
  4271. if (flags.mode === '[INDENTED-EXPRESSION]') {
  4272. if (last_text === ']') {
  4273. restore_mode();
  4274. print_newline();
  4275. print_token();
  4276. break;
  4277. }
  4278. }
  4279. }
  4280. }
  4281. restore_mode();
  4282. print_token();
  4283. break;
  4284. case 'TK_START_BLOCK':
  4285. if (last_word === 'do') {
  4286. set_mode('DO_BLOCK');
  4287. } else {
  4288. set_mode('BLOCK');
  4289. }
  4290. if (opt_brace_style === "expand" || opt_brace_style === "expand-strict") {
  4291. var empty_braces = false;
  4292. if (opt_brace_style === "expand-strict") {
  4293. empty_braces = (look_up() === '}');
  4294. if (!empty_braces) {
  4295. print_newline(true);
  4296. }
  4297. } else {
  4298. if (last_type !== 'TK_OPERATOR') {
  4299. if (last_text === '=' || (is_special_word(last_text) && last_text !== 'else')) {
  4300. print_single_space();
  4301. } else {
  4302. print_newline(true);
  4303. }
  4304. }
  4305. }
  4306. print_token();
  4307. if (!empty_braces) {
  4308. indent();
  4309. }
  4310. } else {
  4311. if (last_type !== 'TK_OPERATOR' && last_type !== 'TK_START_EXPR') {
  4312. if (last_type === 'TK_START_BLOCK') {
  4313. print_newline();
  4314. } else {
  4315. print_single_space();
  4316. }
  4317. } else {
  4318. // if TK_OPERATOR or TK_START_EXPR
  4319. if (is_array(flags.previous_mode) && last_text === ',') {
  4320. if (last_last_text === '}') {
  4321. // }, { in array context
  4322. print_single_space();
  4323. } else {
  4324. print_newline(); // [a, b, c, {
  4325. }
  4326. }
  4327. }
  4328. indent();
  4329. print_token();
  4330. }
  4331. break;
  4332. case 'TK_END_BLOCK':
  4333. restore_mode();
  4334. if (opt_brace_style === "expand" || opt_brace_style === "expand-strict") {
  4335. if (last_text !== '{') {
  4336. print_newline();
  4337. }
  4338. print_token();
  4339. } else {
  4340. if (last_type === 'TK_START_BLOCK') {
  4341. // nothing
  4342. if (just_added_newline) {
  4343. remove_indent();
  4344. } else {
  4345. // {}
  4346. trim_output();
  4347. }
  4348. } else {
  4349. if (is_array(flags.mode) && opt_keep_array_indentation) {
  4350. // we REALLY need a newline here, but newliner would skip that
  4351. opt_keep_array_indentation = false;
  4352. print_newline();
  4353. opt_keep_array_indentation = true;
  4354. } else {
  4355. print_newline();
  4356. }
  4357. }
  4358. print_token();
  4359. }
  4360. break;
  4361. case 'TK_WORD':
  4362. // no, it's not you. even I have problems understanding how this works
  4363. // and what does what.
  4364. if (do_block_just_closed) {
  4365. // do {} ## while ()
  4366. print_single_space();
  4367. print_token();
  4368. print_single_space();
  4369. do_block_just_closed = false;
  4370. break;
  4371. }
  4372. prefix = 'NONE';
  4373. if (token_text === 'function') {
  4374. if (flags.var_line && last_type !== 'TK_EQUALS' ) {
  4375. flags.var_line_reindented = true;
  4376. }
  4377. if ((just_added_newline || last_text === ';') && last_text !== '{'
  4378. && last_type !== 'TK_BLOCK_COMMENT' && last_type !== 'TK_COMMENT') {
  4379. // make sure there is a nice clean space of at least one blank line
  4380. // before a new function definition
  4381. n_newlines = just_added_newline ? n_newlines : 0;
  4382. if (!opt_preserve_newlines) {
  4383. n_newlines = 1;
  4384. }
  4385. for (var i = 0; i < 2 - n_newlines; i++) {
  4386. print_newline(false);
  4387. }
  4388. }
  4389. if (last_type === 'TK_WORD') {
  4390. if (last_text === 'get' || last_text === 'set' || last_text === 'new' || last_text === 'return') {
  4391. print_single_space();
  4392. } else {
  4393. print_newline();
  4394. }
  4395. } else if (last_type === 'TK_OPERATOR' || last_text === '=') {
  4396. // foo = function
  4397. print_single_space();
  4398. } else if (is_expression(flags.mode)) {
  4399. // print nothing
  4400. } else {
  4401. print_newline();
  4402. }
  4403. print_token();
  4404. last_word = token_text;
  4405. break;
  4406. }
  4407. if (token_text === 'case' || (token_text === 'default' && flags.in_case_statement)) {
  4408. print_newline();
  4409. if (flags.case_body) {
  4410. // switch cases following one another
  4411. flags.indentation_level--;
  4412. flags.case_body = false;
  4413. remove_indent();
  4414. }
  4415. print_token();
  4416. flags.in_case = true;
  4417. flags.in_case_statement = true;
  4418. break;
  4419. }
  4420. if (last_type === 'TK_END_BLOCK') {
  4421. if (!in_array(token_text.toLowerCase(), ['else', 'catch', 'finally'])) {
  4422. prefix = 'NEWLINE';
  4423. } else {
  4424. if (opt_brace_style === "expand" || opt_brace_style === "end-expand" || opt_brace_style === "expand-strict") {
  4425. prefix = 'NEWLINE';
  4426. } else {
  4427. prefix = 'SPACE';
  4428. print_single_space();
  4429. }
  4430. }
  4431. } else if (last_type === 'TK_SEMICOLON' && (flags.mode === 'BLOCK' || flags.mode === 'DO_BLOCK')) {
  4432. prefix = 'NEWLINE';
  4433. } else if (last_type === 'TK_SEMICOLON' && is_expression(flags.mode)) {
  4434. prefix = 'SPACE';
  4435. } else if (last_type === 'TK_STRING') {
  4436. prefix = 'NEWLINE';
  4437. } else if (last_type === 'TK_WORD') {
  4438. if (last_text === 'else') {
  4439. // eat newlines between ...else *** some_op...
  4440. // won't preserve extra newlines in this place (if any), but don't care that much
  4441. trim_output(true);
  4442. }
  4443. prefix = 'SPACE';
  4444. } else if (last_type === 'TK_START_BLOCK') {
  4445. prefix = 'NEWLINE';
  4446. } else if (last_type === 'TK_END_EXPR') {
  4447. print_single_space();
  4448. prefix = 'NEWLINE';
  4449. }
  4450. if (in_array(token_text, line_starters) && last_text !== ')') {
  4451. if (last_text === 'else') {
  4452. prefix = 'SPACE';
  4453. } else {
  4454. prefix = 'NEWLINE';
  4455. }
  4456. }
  4457. if (flags.if_line && last_type === 'TK_END_EXPR') {
  4458. flags.if_line = false;
  4459. }
  4460. if (in_array(token_text.toLowerCase(), ['else', 'catch', 'finally'])) {
  4461. if (last_type !== 'TK_END_BLOCK' || opt_brace_style === "expand" || opt_brace_style === "end-expand" || opt_brace_style === "expand-strict") {
  4462. print_newline();
  4463. } else {
  4464. trim_output(true);
  4465. print_single_space();
  4466. }
  4467. } else if (prefix === 'NEWLINE') {
  4468. if (is_special_word(last_text)) {
  4469. // no newline between 'return nnn'
  4470. print_single_space();
  4471. } else if (last_type !== 'TK_END_EXPR') {
  4472. if ((last_type !== 'TK_START_EXPR' || token_text !== 'var') && last_text !== ':') {
  4473. // no need to force newline on 'var': for (var x = 0...)
  4474. if (token_text === 'if' && last_word === 'else' && last_text !== '{') {
  4475. // no newline for } else if {
  4476. print_single_space();
  4477. } else {
  4478. flags.var_line = false;
  4479. flags.var_line_reindented = false;
  4480. print_newline();
  4481. }
  4482. }
  4483. } else if (in_array(token_text, line_starters) && last_text !== ')') {
  4484. flags.var_line = false;
  4485. flags.var_line_reindented = false;
  4486. print_newline();
  4487. }
  4488. } else if (is_array(flags.mode) && last_text === ',' && last_last_text === '}') {
  4489. print_newline(); // }, in lists get a newline treatment
  4490. } else if (prefix === 'SPACE') {
  4491. print_single_space();
  4492. }
  4493. print_token();
  4494. last_word = token_text;
  4495. if (token_text === 'var') {
  4496. flags.var_line = true;
  4497. flags.var_line_reindented = false;
  4498. flags.var_line_tainted = false;
  4499. }
  4500. if (token_text === 'if') {
  4501. flags.if_line = true;
  4502. }
  4503. if (token_text === 'else') {
  4504. flags.if_line = false;
  4505. }
  4506. break;
  4507. case 'TK_SEMICOLON':
  4508. print_token();
  4509. flags.var_line = false;
  4510. flags.var_line_reindented = false;
  4511. if (flags.mode === 'OBJECT') {
  4512. // OBJECT mode is weird and doesn't get reset too well.
  4513. flags.mode = 'BLOCK';
  4514. }
  4515. break;
  4516. case 'TK_STRING':
  4517. if (last_type === 'TK_END_EXPR' && in_array(flags.previous_mode, ['(COND-EXPRESSION)', '(FOR-EXPRESSION)'])) {
  4518. print_single_space();
  4519. } else if (last_type === 'TK_COMMENT' || last_type === 'TK_STRING' || last_type === 'TK_START_BLOCK' || last_type === 'TK_END_BLOCK' || last_type === 'TK_SEMICOLON') {
  4520. print_newline();
  4521. } else if (last_type === 'TK_WORD') {
  4522. print_single_space();
  4523. } else {
  4524. if (opt_preserve_newlines && wanted_newline) {
  4525. print_newline();
  4526. output.push(indent_string);
  4527. }
  4528. }
  4529. print_token();
  4530. break;
  4531. case 'TK_EQUALS':
  4532. if (flags.var_line) {
  4533. // just got an '=' in a var-line, different formatting/line-breaking, etc will now be done
  4534. flags.var_line_tainted = true;
  4535. }
  4536. print_single_space();
  4537. print_token();
  4538. print_single_space();
  4539. break;
  4540. case 'TK_COMMA':
  4541. if (flags.var_line) {
  4542. if (is_expression(flags.mode) || last_type === 'TK_END_BLOCK' ) {
  4543. // do not break on comma, for(var a = 1, b = 2)
  4544. flags.var_line_tainted = false;
  4545. }
  4546. if (flags.var_line_tainted) {
  4547. print_token();
  4548. flags.var_line_reindented = true;
  4549. flags.var_line_tainted = false;
  4550. print_newline();
  4551. break;
  4552. } else {
  4553. flags.var_line_tainted = false;
  4554. }
  4555. print_token();
  4556. print_single_space();
  4557. break;
  4558. }
  4559. if (last_type === 'TK_COMMENT') {
  4560. print_newline();
  4561. }
  4562. if (last_type === 'TK_END_BLOCK' && flags.mode !== "(EXPRESSION)") {
  4563. print_token();
  4564. if (flags.mode === 'OBJECT' && last_text === '}') {
  4565. print_newline();
  4566. } else {
  4567. print_single_space();
  4568. }
  4569. } else {
  4570. if (flags.mode === 'OBJECT') {
  4571. print_token();
  4572. print_newline();
  4573. } else {
  4574. // EXPR or DO_BLOCK
  4575. print_token();
  4576. print_single_space();
  4577. }
  4578. }
  4579. break;
  4580. case 'TK_OPERATOR':
  4581. var space_before = true;
  4582. var space_after = true;
  4583. if (is_special_word(last_text)) {
  4584. // "return" had a special handling in TK_WORD. Now we need to return the favor
  4585. print_single_space();
  4586. print_token();
  4587. break;
  4588. }
  4589. // hack for actionscript's import .*;
  4590. if (token_text === '*' && last_type === 'TK_DOT' && !last_last_text.match(/^\d+$/)) {
  4591. print_token();
  4592. break;
  4593. }
  4594. if (token_text === ':' && flags.in_case) {
  4595. flags.case_body = true;
  4596. indent();
  4597. print_token();
  4598. print_newline();
  4599. flags.in_case = false;
  4600. break;
  4601. }
  4602. if (token_text === '::') {
  4603. // no spaces around exotic namespacing syntax operator
  4604. print_token();
  4605. break;
  4606. }
  4607. if (in_array(token_text, ['--', '++', '!']) || (in_array(token_text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) || in_array(last_text, line_starters) || last_text == ','))) {
  4608. // unary operators (and binary +/- pretending to be unary) special cases
  4609. space_before = false;
  4610. space_after = false;
  4611. if (last_text === ';' && is_expression(flags.mode)) {
  4612. // for (;; ++i)
  4613. // ^^^
  4614. space_before = true;
  4615. }
  4616. if (last_type === 'TK_WORD' && in_array(last_text, line_starters)) {
  4617. space_before = true;
  4618. }
  4619. if (flags.mode === 'BLOCK' && (last_text === '{' || last_text === ';')) {
  4620. // { foo; --i }
  4621. // foo(); --bar;
  4622. print_newline();
  4623. }
  4624. } else if (token_text === ':') {
  4625. if (flags.ternary_depth === 0) {
  4626. if (flags.mode === 'BLOCK') {
  4627. flags.mode = 'OBJECT';
  4628. }
  4629. space_before = false;
  4630. } else {
  4631. flags.ternary_depth -= 1;
  4632. }
  4633. } else if (token_text === '?') {
  4634. flags.ternary_depth += 1;
  4635. }
  4636. if (space_before) {
  4637. print_single_space();
  4638. }
  4639. print_token();
  4640. if (space_after) {
  4641. print_single_space();
  4642. }
  4643. break;
  4644. case 'TK_BLOCK_COMMENT':
  4645. var lines = split_newlines(token_text);
  4646. var j; // iterator for this case
  4647. if (all_lines_start_with(lines.slice(1), '*')) {
  4648. // javadoc: reformat and reindent
  4649. print_newline();
  4650. output.push(lines[0]);
  4651. for (j = 1; j < lines.length; j++) {
  4652. print_newline();
  4653. output.push(' ');
  4654. output.push(trim(lines[j]));
  4655. }
  4656. } else {
  4657. // simple block comment: leave intact
  4658. if (lines.length > 1) {
  4659. // multiline comment block starts with a new line
  4660. print_newline();
  4661. } else {
  4662. // single-line /* comment */ stays where it is
  4663. if (last_type === 'TK_END_BLOCK') {
  4664. print_newline();
  4665. } else {
  4666. print_single_space();
  4667. }
  4668. }
  4669. for (j = 0; j < lines.length; j++) {
  4670. output.push(lines[j]);
  4671. output.push("\n");
  4672. }
  4673. }
  4674. if (look_up('\n') !== '\n') {
  4675. print_newline();
  4676. }
  4677. break;
  4678. case 'TK_INLINE_COMMENT':
  4679. print_single_space();
  4680. print_token();
  4681. if (is_expression(flags.mode)) {
  4682. print_single_space();
  4683. } else {
  4684. force_newline();
  4685. }
  4686. break;
  4687. case 'TK_COMMENT':
  4688. if (last_text === ',' && !wanted_newline) {
  4689. trim_output(true);
  4690. }
  4691. if (last_type !== 'TK_COMMENT') {
  4692. if (wanted_newline) {
  4693. print_newline();
  4694. } else {
  4695. print_single_space();
  4696. }
  4697. }
  4698. print_token();
  4699. print_newline();
  4700. break;
  4701. case 'TK_UNKNOWN':
  4702. print_token();
  4703. break;
  4704. }
  4705. last_last_text = last_text;
  4706. last_type = token_type;
  4707. last_text = token_text;
  4708. }
  4709. var sweet_code = preindent_string + output.join('').replace(/[\r\n ]+$/, '');
  4710. return sweet_code;
  4711. }
  4712. // Add support for CommonJS. Just put this file somewhere on your require.paths
  4713. // and you will be able to `var js_beautify = require("beautify").js_beautify`.
  4714. if (typeof exports !== "undefined") {
  4715. exports.js_beautify = js_beautify;
  4716. }
  4717. /*
  4718. CSS Beautifier
  4719. ---------------
  4720. Written by Harutyun Amirjanyan, (amirjanyan@gmail.com)
  4721. Based on code initially developed by: Einar Lielmanis, <elfz@laacz.lv>
  4722. http://jsbeautifier.org/
  4723. You are free to use this in any way you want, in case you find this useful or working for you.
  4724. Usage:
  4725. css_beautify(source_text);
  4726. css_beautify(source_text, options);
  4727. The options are:
  4728. indent_size (default 4) — indentation size,
  4729. indent_char (default space) — character to indent with,
  4730. e.g
  4731. css_beautify(css_source_text, {
  4732. 'indent_size': 1,
  4733. 'indent_char': '\t'
  4734. });
  4735. */
  4736. // http://www.w3.org/TR/CSS21/syndata.html#tokenization
  4737. // http://www.w3.org/TR/css3-syntax/
  4738. function css_beautify(source_text, options) {
  4739. options = options || {};
  4740. var indentSize = options.indent_size || 4;
  4741. var indentCharacter = options.indent_char || ' ';
  4742. // compatibility
  4743. if (typeof indentSize == "string")
  4744. indentSize = parseInt(indentSize);
  4745. // tokenizer
  4746. var whiteRe = /^\s+$/;
  4747. var wordRe = /[\w$\-_]/;
  4748. var pos = -1, ch;
  4749. function next() {
  4750. return ch = source_text.charAt(++pos)
  4751. }
  4752. function peek() {
  4753. return source_text.charAt(pos+1)
  4754. }
  4755. function eatString(comma) {
  4756. var start = pos;
  4757. while(next()){
  4758. if (ch=="\\"){
  4759. next();
  4760. next();
  4761. } else if (ch == comma) {
  4762. break;
  4763. } else if (ch == "\n") {
  4764. break;
  4765. }
  4766. }
  4767. return source_text.substring(start, pos + 1);
  4768. }
  4769. function eatWhitespace() {
  4770. var start = pos;
  4771. while (whiteRe.test(peek()))
  4772. pos++;
  4773. return pos != start;
  4774. }
  4775. function skipWhitespace() {
  4776. var start = pos;
  4777. do{
  4778. }while (whiteRe.test(next()))
  4779. return pos != start + 1;
  4780. }
  4781. function eatComment() {
  4782. var start = pos;
  4783. next();
  4784. while (next()) {
  4785. if (ch == "*" && peek() == "/") {
  4786. pos ++;
  4787. break;
  4788. }
  4789. }
  4790. return source_text.substring(start, pos + 1);
  4791. }
  4792. function lookBack(str, index) {
  4793. return output.slice(-str.length + (index||0), index).join("").toLowerCase() == str;
  4794. }
  4795. // printer
  4796. var indentString = source_text.match(/^[\r\n]*[\t ]*/)[0];
  4797. var singleIndent = Array(indentSize + 1).join(indentCharacter);
  4798. var indentLevel = 0;
  4799. function indent() {
  4800. indentLevel++;
  4801. indentString += singleIndent;
  4802. }
  4803. function outdent() {
  4804. indentLevel--;
  4805. indentString = indentString.slice(0, -indentSize);
  4806. }
  4807. var print = {}
  4808. print["{"] = function(ch) {
  4809. print.singleSpace();
  4810. output.push(ch);
  4811. print.newLine();
  4812. }
  4813. print["}"] = function(ch) {
  4814. print.newLine();
  4815. output.push(ch);
  4816. print.newLine();
  4817. }
  4818. print.newLine = function(keepWhitespace) {
  4819. if (!keepWhitespace)
  4820. while (whiteRe.test(output[output.length - 1]))
  4821. output.pop();
  4822. if (output.length)
  4823. output.push('\n');
  4824. if (indentString)
  4825. output.push(indentString);
  4826. }
  4827. print.singleSpace = function() {
  4828. if (output.length && !whiteRe.test(output[output.length - 1]))
  4829. output.push(' ');
  4830. }
  4831. var output = [];
  4832. if (indentString)
  4833. output.push(indentString);
  4834. /*_____________________--------------------_____________________*/
  4835. while(true) {
  4836. var isAfterSpace = skipWhitespace();
  4837. if (!ch)
  4838. break;
  4839. if (ch == '{') {
  4840. indent();
  4841. print["{"](ch);
  4842. } else if (ch == '}') {
  4843. outdent();
  4844. print["}"](ch);
  4845. } else if (ch == '"' || ch == '\'') {
  4846. output.push(eatString(ch))
  4847. } else if (ch == ';') {
  4848. output.push(ch, '\n', indentString);
  4849. } else if (ch == '/' && peek() == '*') { // comment
  4850. print.newLine();
  4851. output.push(eatComment(), "\n", indentString);
  4852. } else if (ch == '(') { // may be a url
  4853. if (lookBack("url", -1)) {
  4854. output.push(ch);
  4855. eatWhitespace();
  4856. if (next()) {
  4857. if (ch != ')' && ch != '"' && ch != '\'')
  4858. output.push(eatString(')'));
  4859. else
  4860. pos--;
  4861. }
  4862. } else {
  4863. if (isAfterSpace)
  4864. print.singleSpace();
  4865. output.push(ch);
  4866. eatWhitespace();
  4867. }
  4868. } else if (ch == ')') {
  4869. output.push(ch);
  4870. } else if (ch == ',') {
  4871. eatWhitespace();
  4872. output.push(ch);
  4873. print.singleSpace();
  4874. } else if (ch == ']') {
  4875. output.push(ch);
  4876. } else if (ch == '[' || ch == '=') { // no whitespace before or after
  4877. eatWhitespace();
  4878. output.push(ch);
  4879. } else {
  4880. if (isAfterSpace)
  4881. print.singleSpace();
  4882. output.push(ch);
  4883. }
  4884. }
  4885. var sweetCode = output.join('').replace(/[\n ]+$/, '');
  4886. return sweetCode;
  4887. }
  4888. if (typeof exports !== "undefined")
  4889. exports.css_beautify = css_beautify;
  4890. /*
  4891. Style HTML
  4892. ---------------
  4893. Written by Nochum Sossonko, (nsossonko@hotmail.com)
  4894. Based on code initially developed by: Einar Lielmanis, <elfz@laacz.lv>
  4895. http://jsbeautifier.org/
  4896. You are free to use this in any way you want, in case you find this useful or working for you.
  4897. Usage:
  4898. style_html(html_source);
  4899. style_html(html_source, options);
  4900. The options are:
  4901. indent_size (default 4) — indentation size,
  4902. indent_char (default space) — character to indent with,
  4903. max_char (default 70) - maximum amount of characters per line,
  4904. brace_style (default "collapse") - "collapse" | "expand" | "end-expand"
  4905. put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line.
  4906. unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
  4907. indent_scripts (default normal) - "keep"|"separate"|"normal"
  4908. e.g.
  4909. style_html(html_source, {
  4910. 'indent_size': 2,
  4911. 'indent_char': ' ',
  4912. 'max_char': 78,
  4913. 'brace_style': 'expand',
  4914. 'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u']
  4915. });
  4916. */
  4917. function style_html(html_source, options) {
  4918. //Wrapper function to invoke all the necessary constructors and deal with the output.
  4919. var multi_parser,
  4920. indent_size,
  4921. indent_character,
  4922. max_char,
  4923. brace_style,
  4924. unformatted;
  4925. options = options || {};
  4926. indent_size = options.indent_size || 4;
  4927. indent_character = options.indent_char || ' ';
  4928. brace_style = options.brace_style || 'collapse';
  4929. max_char = options.max_char == 0 ? Infinity : options.max_char || 70;
  4930. unformatted = options.unformatted || ['a', 'span', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
  4931. function Parser() {
  4932. this.pos = 0; //Parser position
  4933. this.token = '';
  4934. this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
  4935. this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
  4936. parent: 'parent1',
  4937. parentcount: 1,
  4938. parent1: ''
  4939. };
  4940. this.tag_type = '';
  4941. this.token_text = this.last_token = this.last_text = this.token_type = '';
  4942. this.Utils = { //Uilities made available to the various functions
  4943. whitespace: "\n\r\t ".split(''),
  4944. single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML
  4945. extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them
  4946. in_array: function (what, arr) {
  4947. for (var i=0; i<arr.length; i++) {
  4948. if (what === arr[i]) {
  4949. return true;
  4950. }
  4951. }
  4952. return false;
  4953. }
  4954. }
  4955. this.get_content = function () { //function to capture regular content between tags
  4956. var input_char = '',
  4957. content = [],
  4958. space = false; //if a space is needed
  4959. while (this.input.charAt(this.pos) !== '<') {
  4960. if (this.pos >= this.input.length) {
  4961. return content.length?content.join(''):['', 'TK_EOF'];
  4962. }
  4963. input_char = this.input.charAt(this.pos);
  4964. this.pos++;
  4965. this.line_char_count++;
  4966. if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
  4967. if (content.length) {
  4968. space = true;
  4969. }
  4970. this.line_char_count--;
  4971. continue; //don't want to insert unnecessary space
  4972. }
  4973. else if (space) {
  4974. if (this.line_char_count >= this.max_char) { //insert a line when the max_char is reached
  4975. content.push('\n');
  4976. for (var i=0; i<this.indent_level; i++) {
  4977. content.push(this.indent_string);
  4978. }
  4979. this.line_char_count = 0;
  4980. }
  4981. else{
  4982. content.push(' ');
  4983. this.line_char_count++;
  4984. }
  4985. space = false;
  4986. }
  4987. content.push(input_char); //letter at-a-time (or string) inserted to an array
  4988. }
  4989. return content.length?content.join(''):'';
  4990. }
  4991. this.get_contents_to = function (name) { //get the full content of a script or style to pass to js_beautify
  4992. if (this.pos == this.input.length) {
  4993. return ['', 'TK_EOF'];
  4994. }
  4995. var input_char = '';
  4996. var content = '';
  4997. var reg_match = new RegExp('\<\/' + name + '\\s*\>', 'igm');
  4998. reg_match.lastIndex = this.pos;
  4999. var reg_array = reg_match.exec(this.input);
  5000. var end_script = reg_array?reg_array.index:this.input.length; //absolute end of script
  5001. if(this.pos < end_script) { //get everything in between the script tags
  5002. content = this.input.substring(this.pos, end_script);
  5003. this.pos = end_script;
  5004. }
  5005. return content;
  5006. }
  5007. this.record_tag = function (tag){ //function to record a tag and its parent in this.tags Object
  5008. if (this.tags[tag + 'count']) { //check for the existence of this tag type
  5009. this.tags[tag + 'count']++;
  5010. this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
  5011. }
  5012. else { //otherwise initialize this tag type
  5013. this.tags[tag + 'count'] = 1;
  5014. this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
  5015. }
  5016. this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
  5017. this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
  5018. }
  5019. this.retrieve_tag = function (tag) { //function to retrieve the opening tag to the corresponding closer
  5020. if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
  5021. var temp_parent = this.tags.parent; //check to see if it's a closable tag.
  5022. while (temp_parent) { //till we reach '' (the initial value);
  5023. if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
  5024. break;
  5025. }
  5026. temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
  5027. }
  5028. if (temp_parent) { //if we caught something
  5029. this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
  5030. this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
  5031. }
  5032. delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
  5033. delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
  5034. if (this.tags[tag + 'count'] == 1) {
  5035. delete this.tags[tag + 'count'];
  5036. }
  5037. else {
  5038. this.tags[tag + 'count']--;
  5039. }
  5040. }
  5041. }
  5042. this.get_tag = function () { //function to get a full tag and parse its type
  5043. var input_char = '',
  5044. content = [],
  5045. space = false,
  5046. tag_start, tag_end;
  5047. do {
  5048. if (this.pos >= this.input.length) {
  5049. return content.length?content.join(''):['', 'TK_EOF'];
  5050. }
  5051. input_char = this.input.charAt(this.pos);
  5052. this.pos++;
  5053. this.line_char_count++;
  5054. if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
  5055. space = true;
  5056. this.line_char_count--;
  5057. continue;
  5058. }
  5059. if (input_char === "'" || input_char === '"') {
  5060. if (!content[1] || content[1] !== '!') { //if we're in a comment strings don't get treated specially
  5061. input_char += this.get_unformatted(input_char);
  5062. space = true;
  5063. }
  5064. }
  5065. if (input_char === '=') { //no space before =
  5066. space = false;
  5067. }
  5068. if (content.length && content[content.length-1] !== '=' && input_char !== '>'
  5069. && space) { //no space after = or before >
  5070. if (this.line_char_count >= this.max_char) {
  5071. this.print_newline(false, content);
  5072. this.line_char_count = 0;
  5073. }
  5074. else {
  5075. content.push(' ');
  5076. this.line_char_count++;
  5077. }
  5078. space = false;
  5079. }
  5080. if (input_char === '<') {
  5081. tag_start = this.pos - 1;
  5082. }
  5083. content.push(input_char); //inserts character at-a-time (or string)
  5084. } while (input_char !== '>');
  5085. var tag_complete = content.join('');
  5086. var tag_index;
  5087. if (tag_complete.indexOf(' ') != -1) { //if there's whitespace, thats where the tag name ends
  5088. tag_index = tag_complete.indexOf(' ');
  5089. }
  5090. else { //otherwise go with the tag ending
  5091. tag_index = tag_complete.indexOf('>');
  5092. }
  5093. var tag_check = tag_complete.substring(1, tag_index).toLowerCase();
  5094. if (tag_complete.charAt(tag_complete.length-2) === '/' ||
  5095. this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /)
  5096. this.tag_type = 'SINGLE';
  5097. }
  5098. else if (tag_check === 'script') { //for later script handling
  5099. this.record_tag(tag_check);
  5100. this.tag_type = 'SCRIPT';
  5101. }
  5102. else if (tag_check === 'style') { //for future style handling (for now it justs uses get_content)
  5103. this.record_tag(tag_check);
  5104. this.tag_type = 'STYLE';
  5105. }
  5106. else if (this.Utils.in_array(tag_check, unformatted)) { // do not reformat the "unformatted" tags
  5107. var comment = this.get_unformatted('</'+tag_check+'>', tag_complete); //...delegate to get_unformatted function
  5108. content.push(comment);
  5109. // Preserve collapsed whitespace either before or after this tag.
  5110. if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)){
  5111. content.splice(0, 0, this.input.charAt(tag_start - 1));
  5112. }
  5113. tag_end = this.pos - 1;
  5114. if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)){
  5115. content.push(this.input.charAt(tag_end + 1));
  5116. }
  5117. this.tag_type = 'SINGLE';
  5118. }
  5119. else if (tag_check.charAt(0) === '!') { //peek for <!-- comment
  5120. if (tag_check.indexOf('[if') != -1) { //peek for <!--[if conditional comment
  5121. if (tag_complete.indexOf('!IE') != -1) { //this type needs a closing --> so...
  5122. var comment = this.get_unformatted('-->', tag_complete); //...delegate to get_unformatted
  5123. content.push(comment);
  5124. }
  5125. this.tag_type = 'START';
  5126. }
  5127. else if (tag_check.indexOf('[endif') != -1) {//peek for <!--[endif end conditional comment
  5128. this.tag_type = 'END';
  5129. this.unindent();
  5130. }
  5131. else if (tag_check.indexOf('[cdata[') != -1) { //if it's a <[cdata[ comment...
  5132. var comment = this.get_unformatted(']]>', tag_complete); //...delegate to get_unformatted function
  5133. content.push(comment);
  5134. this.tag_type = 'SINGLE'; //<![CDATA[ comments are treated like single tags
  5135. }
  5136. else {
  5137. var comment = this.get_unformatted('-->', tag_complete);
  5138. content.push(comment);
  5139. this.tag_type = 'SINGLE';
  5140. }
  5141. }
  5142. else {
  5143. if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending
  5144. this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors
  5145. this.tag_type = 'END';
  5146. }
  5147. else { //otherwise it's a start-tag
  5148. this.record_tag(tag_check); //push it on the tag stack
  5149. this.tag_type = 'START';
  5150. }
  5151. if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line
  5152. this.print_newline(true, this.output);
  5153. }
  5154. }
  5155. return content.join(''); //returns fully formatted tag
  5156. }
  5157. this.get_unformatted = function (delimiter, orig_tag) { //function to return unformatted content in its entirety
  5158. if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) != -1) {
  5159. return '';
  5160. }
  5161. var input_char = '';
  5162. var content = '';
  5163. var space = true;
  5164. do {
  5165. if (this.pos >= this.input.length) {
  5166. return content;
  5167. }
  5168. input_char = this.input.charAt(this.pos);
  5169. this.pos++
  5170. if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
  5171. if (!space) {
  5172. this.line_char_count--;
  5173. continue;
  5174. }
  5175. if (input_char === '\n' || input_char === '\r') {
  5176. content += '\n';
  5177. /* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array'
  5178. for (var i=0; i<this.indent_level; i++) {
  5179. content += this.indent_string;
  5180. }
  5181. space = false; //...and make sure other indentation is erased
  5182. */
  5183. this.line_char_count = 0;
  5184. continue;
  5185. }
  5186. }
  5187. content += input_char;
  5188. this.line_char_count++;
  5189. space = true;
  5190. } while (content.toLowerCase().indexOf(delimiter) == -1);
  5191. return content;
  5192. }
  5193. this.get_token = function () { //initial handler for token-retrieval
  5194. var token;
  5195. if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript
  5196. var type = this.last_token.substr(7)
  5197. token = this.get_contents_to(type);
  5198. if (typeof token !== 'string') {
  5199. return token;
  5200. }
  5201. return [token, 'TK_' + type];
  5202. }
  5203. if (this.current_mode === 'CONTENT') {
  5204. token = this.get_content();
  5205. if (typeof token !== 'string') {
  5206. return token;
  5207. }
  5208. else {
  5209. return [token, 'TK_CONTENT'];
  5210. }
  5211. }
  5212. if (this.current_mode === 'TAG') {
  5213. token = this.get_tag();
  5214. if (typeof token !== 'string') {
  5215. return token;
  5216. }
  5217. else {
  5218. var tag_name_type = 'TK_TAG_' + this.tag_type;
  5219. return [token, tag_name_type];
  5220. }
  5221. }
  5222. }
  5223. this.get_full_indent = function (level) {
  5224. level = this.indent_level + level || 0;
  5225. if (level < 1)
  5226. return '';
  5227. return Array(level + 1).join(this.indent_string);
  5228. }
  5229. this.printer = function (js_source, indent_character, indent_size, max_char, brace_style) { //handles input/output and some other printing functions
  5230. this.input = js_source || ''; //gets the input for the Parser
  5231. this.output = [];
  5232. this.indent_character = indent_character;
  5233. this.indent_string = '';
  5234. this.indent_size = indent_size;
  5235. this.brace_style = brace_style;
  5236. this.indent_level = 0;
  5237. this.max_char = max_char;
  5238. this.line_char_count = 0; //count to see if max_char was exceeded
  5239. for (var i=0; i<this.indent_size; i++) {
  5240. this.indent_string += this.indent_character;
  5241. }
  5242. this.print_newline = function (ignore, arr) {
  5243. this.line_char_count = 0;
  5244. if (!arr || !arr.length) {
  5245. return;
  5246. }
  5247. if (!ignore) { //we might want the extra line
  5248. while (this.Utils.in_array(arr[arr.length-1], this.Utils.whitespace)) {
  5249. arr.pop();
  5250. }
  5251. }
  5252. arr.push('\n');
  5253. for (var i=0; i<this.indent_level; i++) {
  5254. arr.push(this.indent_string);
  5255. }
  5256. }
  5257. this.print_token = function (text) {
  5258. this.output.push(text);
  5259. }
  5260. this.indent = function () {
  5261. this.indent_level++;
  5262. }
  5263. this.unindent = function () {
  5264. if (this.indent_level > 0) {
  5265. this.indent_level--;
  5266. }
  5267. }
  5268. }
  5269. return this;
  5270. }
  5271. /*_____________________--------------------_____________________*/
  5272. multi_parser = new Parser(); //wrapping functions Parser
  5273. multi_parser.printer(html_source, indent_character, indent_size, max_char, brace_style); //initialize starting values
  5274. while (true) {
  5275. var t = multi_parser.get_token();
  5276. multi_parser.token_text = t[0];
  5277. multi_parser.token_type = t[1];
  5278. if (multi_parser.token_type === 'TK_EOF') {
  5279. break;
  5280. }
  5281. switch (multi_parser.token_type) {
  5282. case 'TK_TAG_START':
  5283. multi_parser.print_newline(false, multi_parser.output);
  5284. multi_parser.print_token(multi_parser.token_text);
  5285. multi_parser.indent();
  5286. multi_parser.current_mode = 'CONTENT';
  5287. break;
  5288. case 'TK_TAG_STYLE':
  5289. case 'TK_TAG_SCRIPT':
  5290. multi_parser.print_newline(false, multi_parser.output);
  5291. multi_parser.print_token(multi_parser.token_text);
  5292. multi_parser.current_mode = 'CONTENT';
  5293. break;
  5294. case 'TK_TAG_END':
  5295. //Print new line only if the tag has no content and has child
  5296. if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
  5297. var tag_name = multi_parser.token_text.match(/\w+/)[0];
  5298. var tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length -1].match(/<\s*(\w+)/);
  5299. if (tag_extracted_from_last_output === null || tag_extracted_from_last_output[1] !== tag_name)
  5300. multi_parser.print_newline(true, multi_parser.output);
  5301. }
  5302. multi_parser.print_token(multi_parser.token_text);
  5303. multi_parser.current_mode = 'CONTENT';
  5304. break;
  5305. case 'TK_TAG_SINGLE':
  5306. // Don't add a newline before elements that should remain unformatted.
  5307. var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
  5308. if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)){
  5309. multi_parser.print_newline(false, multi_parser.output);
  5310. }
  5311. multi_parser.print_token(multi_parser.token_text);
  5312. multi_parser.current_mode = 'CONTENT';
  5313. break;
  5314. case 'TK_CONTENT':
  5315. if (multi_parser.token_text !== '') {
  5316. multi_parser.print_token(multi_parser.token_text);
  5317. }
  5318. multi_parser.current_mode = 'TAG';
  5319. break;
  5320. case 'TK_STYLE':
  5321. case 'TK_SCRIPT':
  5322. if (multi_parser.token_text !== '') {
  5323. multi_parser.output.push('\n');
  5324. var text = multi_parser.token_text;
  5325. if (multi_parser.token_type == 'TK_SCRIPT') {
  5326. var _beautifier = typeof js_beautify == 'function' && js_beautify;
  5327. } else if (multi_parser.token_type == 'TK_STYLE') {
  5328. var _beautifier = typeof css_beautify == 'function' && css_beautify;
  5329. }
  5330. if (options.indent_scripts == "keep") {
  5331. var script_indent_level = 0;
  5332. } else if (options.indent_scripts == "separate") {
  5333. var script_indent_level = -multi_parser.indent_level;
  5334. } else {
  5335. var script_indent_level = 1;
  5336. }
  5337. var indentation = multi_parser.get_full_indent(script_indent_level);
  5338. if (_beautifier) {
  5339. // call the Beautifier if avaliable
  5340. text = _beautifier(text.replace(/^\s*/, indentation), options);
  5341. } else {
  5342. // simply indent the string otherwise
  5343. var white = text.match(/^\s*/)[0];
  5344. var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
  5345. var reindent = multi_parser.get_full_indent(script_indent_level -_level);
  5346. text = text.replace(/^\s*/, indentation)
  5347. .replace(/\r\n|\r|\n/g, '\n' + reindent)
  5348. .replace(/\s*$/, '');
  5349. }
  5350. if (text) {
  5351. multi_parser.print_token(text);
  5352. multi_parser.print_newline(true, multi_parser.output);
  5353. }
  5354. }
  5355. multi_parser.current_mode = 'TAG';
  5356. break;
  5357. }
  5358. multi_parser.last_token = multi_parser.token_type;
  5359. multi_parser.last_text = multi_parser.token_text;
  5360. }
  5361. return multi_parser.output.join('');
  5362. }
  5363. ;
  5364. var cowsay = (function () {
  5365. "use strict";
  5366. var cowsay = {
  5367. defaults : {
  5368. e : 'oo',
  5369. T : ' ',
  5370. t : false,
  5371. W : 40
  5372. },
  5373. //in the "template", e is for eye, T for Tongue, L for bubble-Line
  5374. //it looks more like a donkey who was involved in a sledgehammer accident
  5375. // because of escaping and newlines
  5376. //the cow business is a dangerous one
  5377. cow : [
  5378. '',
  5379. ' L ^__^',
  5380. ' L (e)\\_______',
  5381. ' (__)\\ )\\/\\',
  5382. ' T ||----w |',
  5383. ' || ||'
  5384. ].join( '\n' ),
  5385. //message is the text to moo, opts is an optional object, mimicking the
  5386. // cowsay command arguments:
  5387. // e => eyes
  5388. // T => tongue
  5389. // t => is the cow thinking?
  5390. // W => word-wrapping width
  5391. //defaults specified in cowsay.defaults
  5392. moo : function ( message, opts ) {
  5393. var defs = this.defaults;
  5394. //the eyes and tongue should be exactly 2 characters
  5395. //if the ones the user gave are too short, pad'em
  5396. this.eyes = rightPad( opts.e || defs.e, 2 ).slice( 0, 2 );
  5397. this.tongue = rightPad( opts.T || defs.T, 2 ).slice( 0, 2 );
  5398. this.line = opts.t ? 'O' : '\\';
  5399. this.thinking = opts.t;
  5400. this.message = wordWrap( message, opts.W || defs.W ).trim();
  5401. //cowsay is actually the result of breeding a balloon and a cow
  5402. return this.makeBalloon() + this.makeCow();
  5403. },
  5404. makeCow : function () {
  5405. return this.cow
  5406. .replace( /e/g, this.eyes )
  5407. .replace( /T/g, this.tongue )
  5408. .replace( /L/g, this.line );
  5409. },
  5410. makeBalloon : function () {
  5411. var lines = this.message.split( '\n' );
  5412. var longest = lines.reduce( longestLine, 0 ),
  5413. lineCount = lines.length,
  5414. border = this.chooseBorders( lineCount );
  5415. var balloon = lines.map( baloonLine );
  5416. var boundaryOccurences = new Array( longest + 2 )
  5417. balloon.unshift( ' ' + boundaryOccurences.join('_') );
  5418. balloon.push ( ' ' + boundaryOccurences.join('-') );
  5419. return balloon.join( '\n' );
  5420. function baloonLine ( line, idx ) {
  5421. var padders;
  5422. //top left and top right
  5423. if ( idx === 0 ) {
  5424. padders = border.slice( 0, 2 );
  5425. }
  5426. //bottom left and bottom right
  5427. else if ( idx === lineCount-1 ) {
  5428. padders = border.slice( 2, 4 );
  5429. }
  5430. //the wall
  5431. else {
  5432. padders = border.slice( 2 );
  5433. }
  5434. //return the message, padded with spaces to the right as to fit
  5435. // with the border, enclosed in the matching borders
  5436. return (
  5437. padders[ 0 ] + ' ' +
  5438. rightPad( line, longest ) + ' ' +
  5439. padders[ 1 ]
  5440. );
  5441. }
  5442. function longestLine ( max, line ) {
  5443. return line.length > max ? line.length : max;
  5444. }
  5445. },
  5446. //choose the borders to use for the balloon
  5447. chooseBorders : function ( lineCount ) {
  5448. var border;
  5449. //thought bubbles always look the same
  5450. // ( moosage line 1 )
  5451. // ( moosage line 2 )
  5452. if ( this.thinking ) {
  5453. border = [ '(', ')', '(', ')', '(', ')' ];
  5454. }
  5455. //single line messages are enclosed in < > and have no other borders
  5456. // < mooosage >
  5457. else if ( lineCount === 1 ) {
  5458. border = [ '<', '>' ];
  5459. }
  5460. //multi-line messages have diaganol borders and straight walls
  5461. // / moosage line 1 \
  5462. // | moosage line 2 |
  5463. // \ moosage line 3 /
  5464. else {
  5465. border = [ '/', '\\', '\\', '/', '|', '|' ];
  5466. }
  5467. return border;
  5468. }
  5469. };
  5470. function wordWrap ( str, len ) {
  5471. var lineLen = 0;
  5472. return str.split( ' ' ).reduce( handleWord, '' );
  5473. function handleWord ( ret, word ) {
  5474. var wordLen = word.length;
  5475. //let the wrapping...commence!
  5476. if ( lineLen + wordLen > len ) {
  5477. ret += '\n';
  5478. lineLen = 0;
  5479. }
  5480. lineLen += wordLen + 1; //+1 for the space we now add
  5481. return ret + word + ' ';
  5482. }
  5483. }
  5484. function rightPad ( str, len, padder ) {
  5485. padder = padder || ' ';
  5486. return ( str + Array(len).join(padder) ).slice( 0, len );
  5487. }
  5488. return cowsay;
  5489. }());
  5490. bot.listen(
  5491. /cow(think|say)\s(?:([eT])=(.{0,2})\s)?(?:([eT])=(.{0,2})\s)?(.+)/,
  5492. function ( msg ) {
  5493. //the first item is the whole match, second item is the "think" or
  5494. // "say", last item is the message, we only want the "parameters"
  5495. var opts = getOpts( msg.matches.slice(2, -1) );
  5496. //cowsay or cowthink?
  5497. opts.t = msg.matches[ 1 ] === 'think';
  5498. bot.log( opts, 'cowsay opts' );
  5499. var cowreact = cowsay.moo( msg.matches.pop(), opts );
  5500. msg.send( msg.codify(cowreact) );
  5501. function getOpts ( args ) {
  5502. var ret = {};
  5503. //'e=^^ T=vv would represent in capturing groups as:
  5504. // ['e', '^^', 'T', 'vv']
  5505. //so we go through the pairs
  5506. for ( var i = 0, len = args.length; i < len; i += 2 ) {
  5507. if ( args[i] && args[i+1] ) {
  5508. ret[ args[i] ] = args[ i + 1 ];
  5509. }
  5510. }
  5511. return ret;
  5512. }
  5513. }
  5514. );