/
protobot.js
399 lines (362 loc) · 16.8 KB
/
protobot.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/* ------------------------------ Includes && Options ------------------------------ */
require( './vendor/strftime/strftime' )
var util = require( 'util' )
, fs = require( 'fs' )
, path = require( 'path' )
, http = require( 'http' )
, URL = require( 'url' )
, exec = require( 'child_process' ).exec
, spawn = require( 'child_process' ).spawn
, redis = require( 'redis' )
, groupie = require('groupie')
, jerk = require( 'jerk' )
, Sandbox = require( 'sandbox' )
, unescapeAll = require( './vendor/unescape/unescape' )
, settingsFile = path.join( __dirname, "settings.json" )
, bot
, rclient
, sandbox
, options
, commands
, dynamic_json
, c
options = JSON.parse( fs.readFileSync( process.argv[2] || settingsFile ) );
// Dynamic JSON reloads
dynamic_json = {}
reloadJSON(
{ wat: 'vendor/WAT/wat.json'
, crew: 'http://ot-crew.com/crew.json'
})
// Sandbox
sandbox = new Sandbox()
/* ------------------------------ Simple Commands ------------------------------ */
commands =
{ about: "http://github.com/gf3/protobot"
, accessproperty: "https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Operators/Member_Operators"
, anyone: "Has anyone really been far even as decided to use even go want to do look more like?"
, appendscript: "var script = document.createElement( 'script' ); script.src='...'; document.body.appendChild( script );"
, asi: "Automatic Semi-colon Insertion. Read: http://inimino.org/~inimino/blog/javascript_semicolons"
, backtrace: "THE CONSEQUENCES WILL NEVER BE THE SAME"
, bracketnotation: "https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Operators/Member_Operators#Bracket_notation"
, bubble: "http://www.quirksmode.org/js/events_order.html"
, casesensitive: "The case-sensitivity of document language element names in selectors depends on the document language. For example, in HTML, element names are case-insensitive, but in XML they are case-sensitive."
, cc: "CASE CLOASED >:|"
, cheeseburger: "(|%|)"
, commands: "http://github.com/gf3/protobot/blob/master/COMMANDS.md"
, 'debugger': "Debugging JavaScript is easy with the right tools! Try the Web Inspector for Safari + Chrome http://webkit.org/blog/197/web-inspector-redesign/ or Firebug for Firefox http://getfirebug.com/ or Dragonfly for Opera http://bit.ly/rNzdz"
, delegation: "Info: http://pxlz.org/tZ Code: http://pxlz.org/ua"
, dotnotation: "https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Operators/Member_Operators#Dot_notation"
, DRY: "Don't Repeat Yourself"
, ES3: "ES3 is edition 3 of ECMA-262, the ECMAScript specification: http://www.ecma-international.org/publications/standards/Ecma-262-arch.htm now obsoleted by ES5"
, ES5: "ES5 is edition 5 of ECMA-262, the ECMAScript ( aka JavaScript ) specification: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf"
, eventintro: "http://www.quirksmode.org/js/introevents.html"
, evil: "eval is evil! Don't! Read: http://blogs.msdn.com/b/ericlippert/archive/2003/11/01/53329.aspx and http://blogs.msdn.com/b/ericlippert/archive/2003/11/04/53335.aspx"
, flip: "(╯‵Д′)╯彡┻━┻"
, help: "NO U!"
, heyyy: "(☞゚∀゚)☞"
, isjc: "☁ It's so just Cloud™ - http://itssojustcloud.com/ - http://groups.google.com/group/jquery-dev/browse_thread/thread/6a39be05b8477401#msg_f90341223bb9b68b ☁"
, jic: "just in case™"
, jsis: "javascript is javascript is javascript"
, 'false': 'falsy values in js: null, undefined, NaN, false, zero ( the number 0 - "0" is true ), "" ( empty string )'
, fouc: "http://paulirish.com/2009/avoiding-the-fouc-v3/"
, minimal: "A minimal test case should contain precisely the HTML and JavaScript necessary to demonstrate the problem, no more and no less. If it is more than 32 lines, it is probably not a minimal test case."
, mlmlm: "much like multi-level marketing"
, mlu: "much like urself"
, ninja: "http://ejohn.org/apps/learn"
, noob: "http://www.marriedtothesea.com/022310/i-hate-thinking.gif"
, os: "oh snao™"
, osb: "oh snao™ bitch"
, osisjc: "( oh snao™ is so just cloud™ )™"
, pastie: "Paste links not code: http://pastie.org/ , http://jsbin.com/ , http://dpaste.de/ , http://gist.github.com/"
, PHP: "You're asking a JavaScript question but you're showing us PHP instead of HTML and JavaScript. Maybe your PHP code results in well-formed JavaScript code, maybe it doesn't; we don't know. Please show us the HTML JavaScript that the browser sees."
, pizza: "(>"
, plugins: "Check out: http://scripteka.com and http://livepipe.net"
, point: "If you have a question, please just ask it. Do not look for topic experts. Do not ask \"Can I ask a question?\", \"Can anyone help?\", or \"Does anybody use/know about foo?\". Don't make people work to find out what your question is."
, protoquery: "STOP! Don't do it. Prototype and jQuery do the same things, you don't need both. It just adds twice the overhead and potential for conflicts. Pick one or the other."
, proto: "http://dhtmlkitchen.com/learn/js/enumeration/prototype-chain.jsp"
, reinvent: "We will not help you reinvent the wheel if we recommend using the many wheels already available. If you choose to make your own, you're on your own."
, sop: "Requests must respect the Same Origin Policy ( http://en.wikipedia.org/wiki/Same_origin_policy ). Requesting cross-domain content in javascript is generally prohibited. Seeing OPTIONS requests? See https://developer.mozilla.org/en/HTTP_access_control"
, spelling: "Spelling and capitalization are important in programming."
, testcase: "see: minimal"
, tias: "Try It And See"
, truthy: "Truthy/Falsy Values & Comparison Operators: http://www.sitepoint.com/blogs/2009/07/01/javascript-truthy-falsy/ Truthy/Falsy Values & Boolean Operator Results: http://11heavens.com/falsy-and-truthy-in-javascript"
, tyvm: "Thank you SO SO SO much!"
, uaa: "ur an alligator"
, validid: 'ID attributes must begin with a letter ( [A-Za-z] ) and may be followed by any number of letters, digits ( [0-9] ), hyphens ( "-" ), underscores ( "_" ), colons ( ":" ), and periods ( "." ). http://www.w3.org/TR/html401/types.html#h-6.2 - furthermore, IDs are unique, meaning only one element in the DOM can have a given ID at any time'
, vamp: "http://slash7.com/pages/vampires"
, wattt: "(″・ิ_・ิ)っ"
, WET: "Write Everything Twice"
, whyyy: "ლ(゚д゚ლ)"
, zalgo: "H̹̙̦̮͉̩̗̗ͧ̇̏̊̾Eͨ͆͒̆ͮ̃͏̷̮̣̫̤̣ ̵̞̹̻̀̉̓ͬ͑͡ͅCͯ̂͐͏̨̛͔̦̟͈̻O̜͎͍͙͚̬̝̣̽ͮ͐͗̀ͤ̍̀͢M̴̡̲̭͍͇̼̟̯̦̉̒͠Ḛ̛̙̞̪̗ͥͤͩ̾͑̔͐ͅṮ̴̷̷̗̼͍̿̿̓̽͐H̙̙̔̄͜"
, '( ?:gl|glwtd )': "http://goodluckwiththatdude.com/"
, '===': "For any primitive values o and p, o === p if o and p have the same value and type. For any Objects o and p, o === p if mutating o will mutate p in the same way."
}
for ( c in commands )
watchForSingle( c, commands[c] )
// Redis
rclient = redis.createClient( 9307, 'stingfish.redistogo.com' )
rclient.auth( 'da834e6f78e4ea8c4c25ac20f0c8869a' )
rclient.on( 'error', function ( err ) {
console.log( 'Redis error: ' + err )
})
rclient.hgetall( 'triggers', function ( err, obj ) {
var i
console.log( err, obj )
if ( ! err )
for ( i in obj )
watchForSingle( i, obj[i] )
})
/* ------------------------------ Protobot ------------------------------ */
bot = jerk( function( j ) {
// Wat?
j.watch_for( /\b(w[au]t)\b/, function( message ) {
switch ( String( message.source ) ) {
case '#jquery-ot':
case '#runlevel6':
message.say( dynamic_json.wat[ Math.floor( Math.random() * dynamic_json.wat.length ) ] )
break
}
})
// Noobs
j.watch_for( RegExp("^(?:" + options.nick + "\\W+)?(?:hi|hello|hey)(?:\\W+" + options.nick + ".*)?$", "i"), function( message ) {
var r = [ 'oh hai!', 'why hello there', 'hey', 'hi', 'sup?', 'hola', 'yo!' ]
setTimeout( function() {
message.say( message.user + ': ' + r[ Math.floor( Math.random() * r.length ) ] )
}, Math.round( Math.random() * 10000 ))
})
// Boom!
j.watch_for( /^Boom!$/i, function( message ) {
message.say( 'Did you are unimpressed? and now?' )
})
// NO NO U
j.watch_for( /^((?:NO )+)U$/, function( message ) {
message.say( message.user + ': ' + message.match_data[1] + 'NO U' )
})
// Y U <something>
j.watch_for( /\by u\b/i, function( message ) {
message.say( "(屮'Д')屮" )
})
// Shrug
j.watch_for( /\bshrugs\b/i, function( message ) {
message.say( "¯\\_(ツ)_/¯" )
})
// Alligator
j.watch_for( /\balligator\b/i, function( message ) {
message.say( "---,==,'<" )
})
// Live reload
j.watch_for( /^[\/.,`?]?reload (\w+)$/, function( message ) {
liveReload( message )
})
// Redis
j.watch_for( /^(?:david_mark|protobot|bot\-t)[,:]? ([-_.:|\/\\\w]+) is[,:]? (.+)$/, function( message ) {
rclient.hmset( 'triggers', message.match_data[1], message.match_data[2], function( err ) {
if ( err )
message.say( message.user + ': Oops, there was an error: ' + err )
else {
message.say( message.user + ': kk' )
watchForSingle( message.match_data[1], message.match_data[2] )
}
})
})
j.watch_for( /^(?:david_mark|protobot|bot\-t)[,:]? forget[,:]? (.+)$/, function( message ) {
rclient.hdel( 'triggers', message.match_data[1], function( err ) {
if ( err )
message.say( message.user + ': Oops, there was an error: ' + err )
else {
message.say( message.user + ': kk' )
bot.forget( new RegExp( "^[\\/.`?]?" + message.match_data[1] + "(?:\\s*@\\s*([-\\[\\]\\{\\}`|_\\w]+))?\\s*$", "i" ) )
}
})
})
// Finger
j.watch_for( /^([\/.,`?]?)f(?:inger)?(\s+[-\[\]\{\}`|_\w]+)?\s*$/, function( message ) {
// Return if botty is present
if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 )
return
var name = to( message, 2 )
, user = dynamic_json.crew.filter( function( v, i, a ) { return v.irc == name } )
if ( user.length )
message.say( '-ot crew • ' + util.inspect( user[0] ).replace( /\n/g, '' ) )
else
message.say( 'Error: User not found.' )
})
// Sandbox
j.watch_for( /^([\/.,`?]?)eval (?:(.+?)(?:\/\/\s*@\s*([-\[\]\{\}`|_\w]+))|(.+))/, function( message ){
// Return if botty is present
if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 )
return
var js = message.match_data[2] || message.match_data[4]
sandbox.run( js, function( output ) { var original_length
output = output.result.replace( /\n/g, ' ' )
if ( ( original_length = output.length ) > ( 1024 - message.user.length - 3 ) )
output = output.slice( 0, 768 ) + ' (' + ( original_length - 768 ) + ' characters truncated)'
message.say( to( message, 3 ) + ': ' + output )
})
})
// Racket Sandbox
j.watch_for( /^rkt[→>] (.*)/, function ( message ) {
var stdout = ''
, stderr = ''
, child = spawn( 'racket', [ 'sandboxed-ipc-repl.rkt' ] )
, stdoutput = function( data ) {
if ( !!data )
stdout += data
}
, stderrput = function( data ) {
if ( !!data )
stderr += data
}
child.stdout.on( 'data', stdoutput )
child.stderr.on( 'data', stderrput )
child.on( 'exit', function( code ) { var out
if ( code )
out = stderr.split( '\n' )[0].replace( 'UNKNOWN::0: read', 'Error' )
else
out = stdout
message.say( message.user + ': ' + out )
})
child.stdin.write( message.match_data[1] )
child.stdin.end()
})
// Clojure Sandbox
j.watch_for( /^clj[→>] (.*)/, function ( message ) {
var stdout = ''
, stderr = ''
, child = spawn( 'java', [ '-jar', 'srepl-1.0.0-SNAPSHOT-standalone.jar' ] )
, stdoutput = function( data ) {
if ( !!data )
stdout += data
}
, stderrput = function( data ) {
if ( !!data )
stderr += data
}
child.stdout.on( 'data', stdoutput )
child.stderr.on( 'data', stderrput )
child.on( 'exit', function( code ) { var out
if ( code )
out = stderr
else
out = stdout
message.say( message.user + ': ' + out )
})
child.stdin.write( message.match_data[1] )
child.stdin.end()
})
// "it doesn't work"
j.watch_for( /^(?:it )?doesn(?:')?t work(?:\s*@\s*([-\[\]\{\}`|_\w]+))?/, function( message ) {
message.say( to( message, "doesn't work" ) + ": What do you mean it doesn't work? What happens when you try to run it? What's the output? What's the error message? Saying \"it doesn't work\" is pointless." )
})
// Prototype API
j.watch_for( /^api ([$\w]+(?:[\.#]\w+)*)(?:\s+@\s*([-\[\]|_\w]+))?/, function( message ) {
message.say( to( message, 2 ) + ": Sorry, the `api` command is temporarily disabled. Docs here: http://api.prototypejs.org/" )
})
// LOGS
j.watch_for( /.*/, function ( message ) {
var now = new Date()
, location = path.join( options.logdir, message.source )
, file = path.join( location, now.strftime( '%Y-%m-%d.log' ) )
// Make the directory
path.exists( location, function( exists ) {
var log
if ( ! exists )
fs.mkdirSync( location, 0755 )
log = fs.createWriteStream( file, { flags: 'a' })
log.write( message + '\n' )
log.end()
})
})
}).connect( options )
// Register plugins
if ( options["plugins"] )
options["plugins"].forEach( function( plugin ) {
var plugReg = require( "./plugins/" + plugin ).register
try {
jerk( function( j ) {
// Gross hack until global var is dead
plugReg( j, dynamic_json )
})
} catch (e) {
console.error( "Failed to register plugin %s: %s", plugin, e )
}
})
/* ------------------------------ Functions ------------------------------ */
function to ( message, def, idx ) {
if ( idx === undefined && typeof def === 'number' )
idx = def, def = null
else
idx = idx || 1
return !!message.match_data[idx] ? message.match_data[idx].trim() : def || message.user
}
function watchForSingle ( trigger, msg ) {
jerk( function( j ) {
j.watch_for( new RegExp( "^([\\/.,`?])?" + trigger + "(?:\\s*@\\s*([-\\[\\]\\{\\}`|_\\w]+))?\\s*$", "i" ), function( message ) {
// Return if botty is present
if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 )
return
message.say( to( message, 2 ) + ": " + msg )
})
})
}
function reloadJSON ( what, hollaback ) {
hollaback = hollaback || function(){}
Object.keys( what ).forEach( function( k ) {
var url
if ( what[k].slice( 0, 4 ) == 'http' ) {
url = URL.parse( what[k] )
http
.get( { host: url.host, path: url.pathname + ( url.search || '' ), port: 80 }, function ( res ) {
var data = ''
res
.on( 'data', function ( c ) { data += c } )
.on( 'end', function(){
var j = JSON.parse( data )
hollaback.call( null, undefined, dynamic_json[k] = j )
})
})
.on( 'error', hollaback )
}
else
fs.readFile( what[k], function( er, data ) {
if ( ! er )
dynamic_json[k] = JSON.parse( data )
if ( hollaback )
hollaback.call( null, er, dynamic_json[k] )
})
})
}
function liveReload( message ) { var chain
switch ( message.match_data[1] ) {
case 'wat':
chain =
[ function( done ) { exec( 'git pull origin master', { cwd: path.join( __dirname, 'vendor', 'WAT' ) }, done ) }
, function( done ) { reloadJSON( { wat: 'vendor/WAT/wat.json' }, done) }
, function( done ) { message.say( message.user + ': Last WAT: ' + dynamic_json.wat[ dynamic_json.wat.length - 1 ] ); done() }
]
break
case 'crew':
chain =
[ function( done ) { reloadJSON( { crew: 'http://ot-crew.com/crew.json' }, done) }
]
break
case 'self': // Assumes it will be automagically restarted by forever/god/monit/whatever
chain =
[ function( done ) { exec( 'git pull origin master', { cwd: __dirname }, done ) }
, function( done ) { exec( 'git submodule init', { cwd: __dirname }, done ) }
, function( done ) { exec( 'git submodule update', { cwd: __dirname }, done ) }
, function( done ) { exec( 'git pull origin master', { cwd: path.join( __dirname, 'vendor', 'WAT' ) }, done ) } // Always use latest WAT
, function( done ) { process.exit( 0 ) }
]
break
}
if ( chain )
groupie.chain( chain, function ( er, results ) {
if ( er ) {
message.say( message.user + ': Sorry there was an error reloading "' + message.match_data[1] + '"' )
message.say( message.user + ': ' + er.message )
}
else
message.say( message.user + ': Successfully reloaded "' + message.match_data[1] + '"' )
})
}