├── .gitignore ├── .gitmodules ├── COMMANDS.md ├── README.md ├── logs └── .gitignore ├── package.json ├── plugins ├── caniuse.js ├── duckduckgo.js ├── google.js ├── octo.js ├── weather.js └── wolframalpha.js ├── protobot.js ├── sandboxed-ipc-repl.rkt ├── settings.json ├── srepl-1.0.0-SNAPSHOT-standalone.jar ├── strftime.js └── vendor ├── strftime └── strftime.js └── unescape └── unescape.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | nohup.out 3 | node_modules 4 | logs 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/WAT"] 2 | path = vendor/WAT 3 | url = git://github.com/gf3/WAT.git 4 | -------------------------------------------------------------------------------- /COMMANDS.md: -------------------------------------------------------------------------------- 1 | # Protobot 2 | 3 | You can add `@ user` to most commands/triggers to direct output towards others. E.g.: `pastie @ TheN00b` 4 | 5 | ## Commands: 6 | 7 |
8 |
api
9 |
Lookup prototype documentation. Syntax: Class.classMethod, Class#instanceMethod, Class. E.g.: api function#bind
10 | 11 |
cred
12 |
Display GitHub nerd cred for a user. E.g.: cred jeresig
13 | 14 |
ddg
15 |
Get an abstract of some term, from DuckDuckGo. E.g.: ddg jQuery
16 | 17 |
eval
18 |
Evaluate some Javascript. E.g.: eval var a = 1; a++
19 | 20 |
g
21 |
Return the first google result. E.g.: g fermion address map
22 | 23 |
gh
24 |
Display GitHub user information. E.g.: gh coldhead
25 | 26 |
mdc
27 |
Lookup Mozilla Developer Center documentation. E.g.: mdc function apply
28 | 29 |
time
30 |
Get the time at someone's location. E.g.: time divya
31 | 32 |
wa
33 |
Compute some shiz on Wolfram Alpha. E.g.: wa Neptune
34 | 35 |
weather
36 |
Get weather data. E.g.: weather gf3 or just weather
37 |
38 | 39 | ## Triggers: 40 | 41 | * === 42 | * about 43 | * accessproperty 44 | * anyone 45 | * appendscript 46 | * asi 47 | * backtrace 48 | * bracketnotation 49 | * bubble 50 | * casesensitive 51 | * commands 52 | * debugger 53 | * delegation 54 | * dotnotation 55 | * DRY 56 | * ES3 57 | * ES5 58 | * eventintro 59 | * jic 60 | * jsis 61 | * false 62 | * FOUC 63 | * gl / glwtd 64 | * help 65 | * jic 66 | * minimal 67 | * ninja 68 | * noob 69 | * pastie 70 | * PHP 71 | * point 72 | * plugins 73 | * proto 74 | * protoquery 75 | * reinvent 76 | * SOP 77 | * spelling 78 | * testcase 79 | * TIAS 80 | * truthy 81 | * TYVM 82 | * validid 83 | * vamp 84 | * wat 85 | * WET 86 | 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Install 2 | 3 | Require node and npm. 4 | 5 | ``` zsh 6 | git clone --recursive git@github.com:gf3/protobot.git 7 | cd protobot 8 | npm install 9 | ``` 10 | 11 | # Edit 12 | 13 | Edit `protobot.js` file and change the `options` at the top of the file. 14 | 15 | -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gf3/protobot/c08e286972f08d16549c65da3d8e740178b003e0/logs/.gitignore -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name": "protobot" 2 | , "version": "0.0.2" 3 | , "engines": ["node >= 0.6.0"] 4 | , "main": "protobot.js" 5 | , "dependencies": 6 | { "groupie": ">= 0" 7 | , "hiredis": ">= 0" 8 | , "redis": ">= 0" 9 | , "jerk": ">= 1.1.23" 10 | , "sandbox": ">= 0.8.2" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugins/caniuse.js: -------------------------------------------------------------------------------- 1 | var http = require( 'http' ) 2 | 3 | exports.register = register 4 | 5 | function register( j ) { 6 | j.watch_for( /^([\/.,`?]?)caniuse ([^#@]+)(?:\s*#([1-9]))?(?:\s*@\s*([-\[\]|_\w]+))?$/, canIUse ) 7 | } 8 | 9 | function canIUse ( message ) { 10 | var search = message.match_data[ 2 ].split( ' ' ).join( '+' ) 11 | 12 | if( !search ) 13 | return 14 | 15 | http 16 | .get( { host: 'api.html5please.com', path: '/' + search + '.json?noagent', port: 80 }, function ( res ) { 17 | var data = '' 18 | res 19 | .on( 'data', function ( c ) { data += c } ) 20 | .on( 'end', function() { 21 | 22 | var j = JSON.parse( data ) 23 | 24 | if ( j.supported != 'unknown' && j.features.length === 0 ) 25 | return 26 | 27 | var f = j.features 28 | , r = j.results 29 | , a = j.agents 30 | 31 | , use = '' 32 | , agents = '' 33 | , links = '' 34 | 35 | use += Object.keys( f ).map( function( k ) { 36 | links += ' http://caniuse.com/#search=' + k 37 | return f[ k ] 38 | }).join( ', ' ).replace( /,([^,]*?)$/, ', and$1' ) 39 | 40 | agents += Object.keys( r ).map( function( k ) { 41 | return j.agents[ k ].name + ' ' + r[ k ] 42 | }).join( ', ' ).replace( /,([^,]*?)$/, ', and$1' ) 43 | 44 | if( agents.length ) 45 | message.say( message.user + ': You can use ' + use + ' with ' + agents + '.' + links ) 46 | else 47 | message.say( message.user + ': ' + use + ' is not fully supported anywhere.' ) 48 | }) 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /plugins/duckduckgo.js: -------------------------------------------------------------------------------- 1 | exports.register = register 2 | 3 | var exec = require( 'child_process' ).exec 4 | 5 | /* ---------------------------- DuckDuckGo ---------------------------- */ 6 | function DuckDuckGo() { 7 | } 8 | 9 | DuckDuckGo.prototype.search = function( query, cloudback ) { 10 | exec("curl 'http://api.duckduckgo.com/?format=json&q=" + escape( query ) + "'", function ( err, stdout, stderr ) { 11 | var results = JSON.parse( stdout ) // What could possibly go wrong? 12 | cloudback.call( this, results ) 13 | }) 14 | } 15 | 16 | function register( j ) { 17 | var ddg = new DuckDuckGo() 18 | 19 | j.watch_for( /^([\/.,`?]?)ddg ([^#@]+)(?:\s*#([1-9]))?(?:\s*@\s*([-\[\]\{\}`|_\w]+))?$/, function( message ) { 20 | var user = message.match_data[4] || message.name 21 | , term = message.match_data[2] 22 | , num = +message.match_data[3]-1 || 0 23 | 24 | ddg.search( term, function( results ) { 25 | if ( results["AbstractText"] ) 26 | message.say( user + ': ' + unescapeAll( results["AbstractText"] ) + ' - ' + results["AbstractURL"] ) 27 | else if ( results["Definition"] ) 28 | message.say( user + ': ' + results["Definition"] + ' - ' + results["DefinitionURL"] ) 29 | else if ( results["Redirect"] ) // !bang syntax used 30 | message.say( user + ': ' + results["Redirect"] ) 31 | else if ( results["Results"].length ) 32 | message.say( user + ': ' + results["Results"][num]["Text"] + " - " + results["Results"][num]["FirstURL"] ) 33 | else 34 | message.say( user + ": Sorry, no results for '" + term + "'" ) 35 | }) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /plugins/google.js: -------------------------------------------------------------------------------- 1 | /* ------------------------------ Includes && Options ------------------------------ */ 2 | var exec = require( 'child_process' ).exec 3 | , unescapeAll = require( '../vendor/unescape/unescape' ) 4 | 5 | /* ------------------------------ Google ------------------------------ */ 6 | function Google() { 7 | this.search = function( query, hollaback ) { 8 | exec("curl -e 'http://gf3.ca/' 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=" + escape( query ) + "'", function ( err, stdout, stderr ) { 9 | var results = JSON.parse( stdout )[ 'responseData' ][ 'results' ] 10 | hollaback.call( this, results ) 11 | }) 12 | } 13 | } 14 | 15 | function register( j ) { 16 | var google = new Google 17 | 18 | j.watch_for( /^([\/.,`?]?)g(?:[ogle]{0,5}) ([^#@]+)(?:\s*#([1-9]))?(?:\s*@\s*([-\[\]\{\}`|_\w]+))?$/, function( message ) { 19 | var user = message.match_data[4] || message.user 20 | , res = +message.match_data[3]-1 || 0 21 | 22 | // Return if botty is present 23 | if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 ) 24 | return 25 | 26 | google.search( message.match_data[2], function( results ) { 27 | if ( results.length ) 28 | message.say( user + ': ' + unescapeAll( results[res].titleNoFormatting ) + ' - ' + results[res].unescapedUrl ) 29 | else 30 | message.say( user + ": Sorry, no results for '" + message.match_data[2] + "'" ) 31 | }) 32 | }) 33 | 34 | // MDN, formerly known as MDC 35 | j.watch_for( /^([\/.,`?]?)(?:mdc|mdn) ([^#@]+)(?:\s*#([1-9]))?(?:\s*@\s*([-\[\]|_\w]+))?$/, function( message ) { 36 | var user = message.match_data[4] || message.user 37 | , res = +message.match_data[2]-1 || 0 38 | 39 | // Return if botty is present 40 | if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 ) 41 | return 42 | 43 | google.search( message.match_data[2] + ' site:developer.mozilla.org', function( results ) { 44 | if ( results.length ) 45 | message.say( user + ": " + results[res].titleNoFormatting + " - " + results[res].unescapedUrl ) 46 | else 47 | message.say( user + ": Sorry, no results for '" + message.match_data[2] + "'" ) 48 | }) 49 | }) 50 | } 51 | 52 | /* ------------------------------ Export ------------------------------ */ 53 | exports.register = register 54 | -------------------------------------------------------------------------------- /plugins/octo.js: -------------------------------------------------------------------------------- 1 | var Octo = module.exports = {} 2 | , http = require( 'http' ) 3 | , https = require( 'https' ) 4 | , cache = {} 5 | 6 | Octo.register = function( j ) { 7 | // GitHub User 8 | j.watch_for( /^([\/.,`?]?)gh\s+(\w+)\s*$/, function( message ) { 9 | // Return if botty is present 10 | if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 ) 11 | return 12 | var name = message.match_data[2] 13 | Octo.user( name, function( err, user ) { 14 | if ( err ) 15 | message.say( 'Error: ' + err.message ) 16 | else 17 | message.say( 'GitHub user: ' + user.login + (user.name ? ' (' + user.name + ') ' : 'NO NAME') + 'Repos: ' + user.public_repos + ' • Following: ' + user.following + ' • Followers: ' + user.followers ) 18 | }) 19 | }) 20 | // Nerd Cred 21 | j.watch_for( /^([\/.,`?]?)cred\s+(\w+)\s*$/, function( message ) { 22 | // Return if botty is present 23 | if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 ) 24 | return 25 | var name = message.match_data[2] 26 | Octo.score( name, function( err, score ) { 27 | if ( err ) 28 | message.say( 'Error: ' + err.message ) 29 | else 30 | message.say( 'GitHub User: ' + name + ' • Score: ' + score ) 31 | }) 32 | }) 33 | } 34 | 35 | // Get GitHub User 36 | Octo.user = function user ( username, hollaback ) { 37 | // Cached? 38 | if ( cache[ username ] ) { 39 | hollaback.call( null, undefined, cache[ username ] ) 40 | return 41 | } 42 | 43 | api( '/users/' + username, function( err, data ) { 44 | if ( data && data.message && data.message == 'Not Found' ) 45 | hollaback.call( null, data ) 46 | else { 47 | cache[ username ] = data 48 | hollaback.call( null, err, data ) 49 | } 50 | }) 51 | } 52 | 53 | // Get Nerd Cred Score 54 | Octo.score = function score ( username, hollaback ) { 55 | Octo.user( username, function( err, user ) { 56 | if ( err ) { 57 | hollaback.call( null, err ) 58 | return 59 | } 60 | 61 | // Get repos 62 | api( '/repos/show/' + username, true, function( err, data ) { 63 | if ( err ) { 64 | hollaback.call( null, err ) 65 | return 66 | } 67 | 68 | // XXX Totally arbitrary 69 | var score = 0 70 | 71 | score += user.followers * 2 72 | score += user.public_gists * 3 73 | score += user.public_repos * 4 74 | 75 | data.repositories.forEach( function( r ) { 76 | score += r.watchers 77 | score += r.forks * 5 78 | }) 79 | 80 | score = score * 0.10 81 | hollaback.call( null, undefined, ~~score ) 82 | }) 83 | }) 84 | } 85 | 86 | // Make API Calls 87 | function api ( endpoint, v2, hollaback ) { 88 | var options = {} 89 | , method 90 | 91 | if ( hollaback == undefined ) { 92 | hollaback = v2 93 | v2 = false 94 | } 95 | 96 | if ( v2 ) { 97 | options.host = 'github.com' 98 | options.path = '/api/v2/json' + endpoint 99 | options.port = 80 100 | method = http 101 | } 102 | else { 103 | options.host = 'api.github.com' 104 | options.path = endpoint 105 | method = https 106 | } 107 | 108 | method.get( options, function( res ) { 109 | var data = '' 110 | 111 | res.on( 'data', function( chunk ) { 112 | data += chunk 113 | }).on( 'end', function() { 114 | hollaback.call( null, undefined, JSON.parse( data ) ) 115 | }) 116 | 117 | }).on( 'error', function( err ) { 118 | hollaback.call( null, err ) 119 | }) 120 | } 121 | -------------------------------------------------------------------------------- /plugins/weather.js: -------------------------------------------------------------------------------- 1 | var get = require( 'http' ).get 2 | , pipe_url = 3 | { host: "pipes.fy3.b.yahoo.com" 4 | , port: 80 5 | , path: "/pipes/pipe.run?_id=5a36359b823b6cb19e67fc6739c6a02b&_render=json&location=" 6 | } 7 | 8 | function extend ( dest, src ) { 9 | var prop 10 | for (prop in src) { 11 | dest[prop] = src[prop]; 12 | } 13 | return dest 14 | } 15 | 16 | function toC ( F ) { 17 | return Math.round( ( parseInt( F, 10 ) - 32 ) * ( 5 / 9 ) ) 18 | } 19 | 20 | function toF ( C ) { 21 | return Math.round( parseInt( C, 10 ) * ( 9 / 5 ) + 32 ) 22 | } 23 | 24 | function getWeather ( location, justTime, holla ) { 25 | if ( !location ) 26 | holla( "Please provide a location" ) 27 | 28 | var opt = extend( {}, pipe_url ) 29 | opt.path += encodeURIComponent( location ) 30 | 31 | get( opt, function( resp ) { 32 | var w, out, body = "" 33 | 34 | resp.on( 'data', function ( chunk ) { 35 | body += chunk 36 | }) 37 | 38 | resp.on( 'close', function ( chunk ) { 39 | holla( "Sorry, no results for: " + location ) 40 | }) 41 | 42 | resp.on( 'end', function () { 43 | try { 44 | w = JSON.parse( body ) 45 | 46 | if ( justTime ) { 47 | out = w.value.items[0].channel.item['yweather:condition'].date 48 | } 49 | else { 50 | out = w.value.items[0].channel.item.title 51 | out += ": " 52 | out += toC( w.value.items[0].channel.item['yweather:condition'].temp ) 53 | out += "°C / " 54 | out += w.value.items[0].channel.item['yweather:condition'].temp 55 | out += "°F " 56 | out += w.value.items[0].channel.item['yweather:condition'].text 57 | } 58 | 59 | holla( out ) 60 | } 61 | catch ( e ) { 62 | holla( "Sorry, no results for: " + location ) 63 | } 64 | }) 65 | }).on( "error", function( err ) { 66 | holla( "Sorry, no results for: " + location ) 67 | }) 68 | } 69 | 70 | exports.register = function( j, dynamic_json ) { 71 | j.watch_for( /^([\/.,`?]?)(time|weather)(\s+[^@]+)?(?:\s*@\s*([-\[\]\{\}`|_\w]+))?/, function( message ) { 72 | var user = message.match_data[4] || message.user 73 | , location = message.match_data[3] 74 | , person 75 | 76 | if ( location ) 77 | location = location.trim() 78 | 79 | // Return if botty is present 80 | if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 ) 81 | return 82 | 83 | // Try and find by person first 84 | person = dynamic_json.crew.filter( function( v, i, a ) { return v.irc == location } ) 85 | if ( person.length ) 86 | location = person[0].location 87 | else if ( !person.length ) 88 | person = dynamic_json.crew.filter( function( v, i, a ) { return v.irc == user } ) 89 | 90 | if ( location == undefined && person.length ) 91 | location = person[0].location 92 | 93 | getWeather( location, message.match_data[2] == 'time', function( result ) { 94 | message.say( user + ": " + result ) 95 | }) 96 | }) 97 | } -------------------------------------------------------------------------------- /plugins/wolframalpha.js: -------------------------------------------------------------------------------- 1 | /* ------------------------------ Includes && Options ----------------- */ 2 | var exec = require( 'child_process' ).exec 3 | , unescapeAll = require( '../vendor/unescape/unescape' ) 4 | 5 | /* ------------------------------ WolframAlpha ------------------------ */ 6 | function WolframAlpha() { 7 | this.search = function( query, hollaback ) { 8 | var result = 9 | { url: 'http://www.wolframalpha.com/input/?i=' + encodeURIComponent( query ) 10 | } 11 | 12 | exec( "curl -e 'http://www.wolframalpha.com' '" + result.url + "'", function ( err, stdout, stderr ) { 13 | var solution = />Solution:<[\s\S]*?alt\s*=\s*\"([^\""]*)\"/ 14 | , other = /stringified"\s*:\s*"([^"\r\n]*)/g 15 | 16 | if ( solution.test( stdout ) ) 17 | result.data = stdout 18 | .match( solution )[1] 19 | .replace( /\\\//, '/' ) 20 | else { 21 | match = stdout.match( other ) 22 | if ( !match || !match[1] ) 23 | result.data = null 24 | else 25 | result.data = match[1] 26 | .replace( /stringified"\s*:\s*"/g, '' ) 27 | .replace( /\\n/g, ' ' ) 28 | .replace( /\\\//, '/' ) 29 | } 30 | 31 | hollaback.call( this, result ) 32 | }) 33 | } 34 | } 35 | 36 | function register( j ) { 37 | var wa = new WolframAlpha 38 | 39 | j.watch_for( /^([\/.,`?]?)wa ([^@]+)(?:\s*@\s*([-\[\]\{\}`|_\w]+))?/, function( message ) { 40 | var user = message.match_data[3] || message.user 41 | 42 | // Return if botty is present 43 | if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 ) 44 | return 45 | 46 | wa.search( message.match_data[2], function( result ) { 47 | message.say( user + ": " + ( result && result.data ? unescapeAll( result.data ) : "Sorry, no results for '" + message.match_data[2] + "'" ) ) 48 | }) 49 | }) 50 | } 51 | 52 | /* ------------------------------ Export ------------------------------ */ 53 | exports.register = register 54 | -------------------------------------------------------------------------------- /protobot.js: -------------------------------------------------------------------------------- 1 | /* ------------------------------ Includes && Options ------------------------------ */ 2 | require( './vendor/strftime/strftime' ) 3 | 4 | var util = require( 'util' ) 5 | , fs = require( 'fs' ) 6 | , path = require( 'path' ) 7 | , http = require( 'http' ) 8 | , URL = require( 'url' ) 9 | , exec = require( 'child_process' ).exec 10 | , spawn = require( 'child_process' ).spawn 11 | , redis = require( 'redis' ) 12 | , groupie = require('groupie') 13 | , jerk = require( 'jerk' ) 14 | , Sandbox = require( 'sandbox' ) 15 | , unescapeAll = require( './vendor/unescape/unescape' ) 16 | , settingsFile = path.join( __dirname, "settings.json" ) 17 | , bot 18 | , rclient 19 | , sandbox 20 | , options 21 | , commands 22 | , dynamic_json 23 | , c 24 | 25 | options = JSON.parse( fs.readFileSync( process.argv[2] || settingsFile ) ); 26 | 27 | // Dynamic JSON reloads 28 | dynamic_json = {} 29 | reloadJSON( 30 | { wat: 'vendor/WAT/wat.json' 31 | , crew: 'http://ot-crew.com/crew.json' 32 | }) 33 | 34 | // Sandbox 35 | sandbox = new Sandbox() 36 | 37 | /* ------------------------------ Simple Commands ------------------------------ */ 38 | commands = 39 | { about: "http://github.com/gf3/protobot" 40 | , accessproperty: "https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Operators/Member_Operators" 41 | , anyone: "Has anyone really been far even as decided to use even go want to do look more like?" 42 | , appendscript: "var script = document.createElement( 'script' ); script.src='...'; document.body.appendChild( script );" 43 | , asi: "Automatic Semi-colon Insertion. Read: http://inimino.org/~inimino/blog/javascript_semicolons" 44 | , backtrace: "THE CONSEQUENCES WILL NEVER BE THE SAME" 45 | , bracketnotation: "https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Operators/Member_Operators#Bracket_notation" 46 | , bubble: "http://www.quirksmode.org/js/events_order.html" 47 | , 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." 48 | , cc: "CASE CLOASED >:|" 49 | , cheeseburger: "(|%|)" 50 | , commands: "http://github.com/gf3/protobot/blob/master/COMMANDS.md" 51 | , '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" 52 | , delegation: "Info: http://pxlz.org/tZ Code: http://pxlz.org/ua" 53 | , dotnotation: "https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Operators/Member_Operators#Dot_notation" 54 | , DRY: "Don't Repeat Yourself" 55 | , 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" 56 | , 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" 57 | , eventintro: "http://www.quirksmode.org/js/introevents.html" 58 | , 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" 59 | , flip: "(╯‵Д′)╯彡┻━┻" 60 | , help: "NO U!" 61 | , heyyy: "(☞゚∀゚)☞" 62 | , isjc: "☁ It's so just Cloud™ - http://itssojustcloud.com/ - http://groups.google.com/group/jquery-dev/browse_thread/thread/6a39be05b8477401#msg_f90341223bb9b68b ☁" 63 | , jic: "just in case™" 64 | , jsis: "javascript is javascript is javascript" 65 | , 'false': 'falsy values in js: null, undefined, NaN, false, zero ( the number 0 - "0" is true ), "" ( empty string )' 66 | , fouc: "http://paulirish.com/2009/avoiding-the-fouc-v3/" 67 | , 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." 68 | , mlmlm: "much like multi-level marketing" 69 | , mlu: "much like urself" 70 | , ninja: "http://ejohn.org/apps/learn" 71 | , noob: "http://www.marriedtothesea.com/022310/i-hate-thinking.gif" 72 | , os: "oh snao™" 73 | , osb: "oh snao™ bitch" 74 | , osisjc: "( oh snao™ is so just cloud™ )™" 75 | , pastie: "Paste links not code: http://pastie.org/ , http://jsbin.com/ , http://dpaste.de/ , http://gist.github.com/" 76 | , 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." 77 | , pizza: "(>" 78 | , plugins: "Check out: http://scripteka.com and http://livepipe.net" 79 | , 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." 80 | , 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." 81 | , proto: "http://dhtmlkitchen.com/learn/js/enumeration/prototype-chain.jsp" 82 | , 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." 83 | , 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" 84 | , spelling: "Spelling and capitalization are important in programming." 85 | , testcase: "see: minimal" 86 | , tias: "Try It And See" 87 | , 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" 88 | , tyvm: "Thank you SO SO SO much!" 89 | , uaa: "ur an alligator" 90 | , 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' 91 | , vamp: "http://slash7.com/pages/vampires" 92 | , wattt: "(″・ิ_・ิ)っ" 93 | , WET: "Write Everything Twice" 94 | , whyyy: "ლ(゚д゚ლ)" 95 | , zalgo: "H̹̙̦̮͉̩̗̗ͧ̇̏̊̾Eͨ͆͒̆ͮ̃͏̷̮̣̫̤̣ ̵̞̹̻̀̉̓ͬ͑͡ͅCͯ̂͐͏̨̛͔̦̟͈̻O̜͎͍͙͚̬̝̣̽ͮ͐͗̀ͤ̍̀͢M̴̡̲̭͍͇̼̟̯̦̉̒͠Ḛ̛̙̞̪̗ͥͤͩ̾͑̔͐ͅṮ̴̷̷̗̼͍̿̿̓̽͐H̙̙̔̄͜" 96 | , '( ?:gl|glwtd )': "http://goodluckwiththatdude.com/" 97 | , '===': "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." 98 | } 99 | 100 | for ( c in commands ) 101 | watchForSingle( c, commands[c] ) 102 | 103 | // Redis 104 | rclient = redis.createClient( 9307, 'stingfish.redistogo.com' ) 105 | rclient.auth( 'da834e6f78e4ea8c4c25ac20f0c8869a' ) 106 | rclient.on( 'error', function ( err ) { 107 | console.log( 'Redis error: ' + err ) 108 | }) 109 | rclient.hgetall( 'triggers', function ( err, obj ) { 110 | var i 111 | console.log( err, obj ) 112 | if ( ! err ) 113 | for ( i in obj ) 114 | watchForSingle( i, obj[i] ) 115 | }) 116 | 117 | /* ------------------------------ Protobot ------------------------------ */ 118 | bot = jerk( function( j ) { 119 | // Wat? 120 | j.watch_for( /\b(w[au]t)\b/, function( message ) { 121 | switch ( String( message.source ) ) { 122 | case '#jquery-ot': 123 | case '#runlevel6': 124 | message.say( dynamic_json.wat[ Math.floor( Math.random() * dynamic_json.wat.length ) ] ) 125 | break 126 | } 127 | }) 128 | 129 | // Noobs 130 | j.watch_for( RegExp("^(?:" + options.nick + "\\W+)?(?:hi|hello|hey)(?:\\W+" + options.nick + ".*)?$", "i"), function( message ) { 131 | var r = [ 'oh hai!', 'why hello there', 'hey', 'hi', 'sup?', 'hola', 'yo!' ] 132 | setTimeout( function() { 133 | message.say( message.user + ': ' + r[ Math.floor( Math.random() * r.length ) ] ) 134 | }, Math.round( Math.random() * 10000 )) 135 | }) 136 | 137 | // Boom! 138 | j.watch_for( /^Boom!$/i, function( message ) { 139 | message.say( 'Did you are unimpressed? and now?' ) 140 | }) 141 | 142 | // NO NO U 143 | j.watch_for( /^((?:NO )+)U$/, function( message ) { 144 | message.say( message.user + ': ' + message.match_data[1] + 'NO U' ) 145 | }) 146 | 147 | // Y U 148 | j.watch_for( /\by u\b/i, function( message ) { 149 | message.say( "(屮'Д')屮" ) 150 | }) 151 | 152 | // Shrug 153 | j.watch_for( /\bshrugs\b/i, function( message ) { 154 | message.say( "¯\\_(ツ)_/¯" ) 155 | }) 156 | 157 | // Alligator 158 | j.watch_for( /\balligator\b/i, function( message ) { 159 | message.say( "---,==,'<" ) 160 | }) 161 | 162 | // Live reload 163 | j.watch_for( /^[\/.,`?]?reload (\w+)$/, function( message ) { 164 | liveReload( message ) 165 | }) 166 | 167 | // Redis 168 | j.watch_for( /^(?:david_mark|protobot|bot\-t)[,:]? ([-_.:|\/\\\w]+) is[,:]? (.+)$/, function( message ) { 169 | rclient.hmset( 'triggers', message.match_data[1], message.match_data[2], function( err ) { 170 | if ( err ) 171 | message.say( message.user + ': Oops, there was an error: ' + err ) 172 | else { 173 | message.say( message.user + ': kk' ) 174 | watchForSingle( message.match_data[1], message.match_data[2] ) 175 | } 176 | }) 177 | }) 178 | 179 | j.watch_for( /^(?:david_mark|protobot|bot\-t)[,:]? forget[,:]? (.+)$/, function( message ) { 180 | rclient.hdel( 'triggers', message.match_data[1], function( err ) { 181 | if ( err ) 182 | message.say( message.user + ': Oops, there was an error: ' + err ) 183 | else { 184 | message.say( message.user + ': kk' ) 185 | bot.forget( new RegExp( "^[\\/.`?]?" + message.match_data[1] + "(?:\\s*@\\s*([-\\[\\]\\{\\}`|_\\w]+))?\\s*$", "i" ) ) 186 | } 187 | }) 188 | }) 189 | 190 | // Finger 191 | j.watch_for( /^([\/.,`?]?)f(?:inger)?(\s+[-\[\]\{\}`|_\w]+)?\s*$/, function( message ) { 192 | // Return if botty is present 193 | if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 ) 194 | return 195 | 196 | var name = to( message, 2 ) 197 | , user = dynamic_json.crew.filter( function( v, i, a ) { return v.irc == name } ) 198 | if ( user.length ) 199 | message.say( '-ot crew • ' + util.inspect( user[0] ).replace( /\n/g, '' ) ) 200 | else 201 | message.say( 'Error: User not found.' ) 202 | }) 203 | 204 | // Sandbox 205 | j.watch_for( /^([\/.,`?]?)eval (?:(.+?)(?:\/\/\s*@\s*([-\[\]\{\}`|_\w]+))|(.+))/, function( message ){ 206 | // Return if botty is present 207 | if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 ) 208 | return 209 | 210 | var js = message.match_data[2] || message.match_data[4] 211 | sandbox.run( js, function( output ) { var original_length 212 | output = output.result.replace( /\n/g, ' ' ) 213 | if ( ( original_length = output.length ) > ( 1024 - message.user.length - 3 ) ) 214 | output = output.slice( 0, 768 ) + ' (' + ( original_length - 768 ) + ' characters truncated)' 215 | message.say( to( message, 3 ) + ': ' + output ) 216 | }) 217 | }) 218 | 219 | // Racket Sandbox 220 | j.watch_for( /^rkt[→>] (.*)/, function ( message ) { 221 | var stdout = '' 222 | , stderr = '' 223 | , child = spawn( 'racket', [ 'sandboxed-ipc-repl.rkt' ] ) 224 | , stdoutput = function( data ) { 225 | if ( !!data ) 226 | stdout += data 227 | } 228 | , stderrput = function( data ) { 229 | if ( !!data ) 230 | stderr += data 231 | } 232 | 233 | child.stdout.on( 'data', stdoutput ) 234 | child.stderr.on( 'data', stderrput ) 235 | child.on( 'exit', function( code ) { var out 236 | if ( code ) 237 | out = stderr.split( '\n' )[0].replace( 'UNKNOWN::0: read', 'Error' ) 238 | else 239 | out = stdout 240 | message.say( message.user + ': ' + out ) 241 | }) 242 | child.stdin.write( message.match_data[1] ) 243 | child.stdin.end() 244 | }) 245 | 246 | // Clojure Sandbox 247 | j.watch_for( /^clj[→>] (.*)/, function ( message ) { 248 | var stdout = '' 249 | , stderr = '' 250 | , child = spawn( 'java', [ '-jar', 'srepl-1.0.0-SNAPSHOT-standalone.jar' ] ) 251 | , stdoutput = function( data ) { 252 | if ( !!data ) 253 | stdout += data 254 | } 255 | , stderrput = function( data ) { 256 | if ( !!data ) 257 | stderr += data 258 | } 259 | 260 | child.stdout.on( 'data', stdoutput ) 261 | child.stderr.on( 'data', stderrput ) 262 | child.on( 'exit', function( code ) { var out 263 | if ( code ) 264 | out = stderr 265 | else 266 | out = stdout 267 | message.say( message.user + ': ' + out ) 268 | }) 269 | child.stdin.write( message.match_data[1] ) 270 | child.stdin.end() 271 | }) 272 | 273 | // "it doesn't work" 274 | j.watch_for( /^(?:it )?doesn(?:')?t work(?:\s*@\s*([-\[\]\{\}`|_\w]+))?/, function( message ) { 275 | 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." ) 276 | }) 277 | 278 | // Prototype API 279 | j.watch_for( /^api ([$\w]+(?:[\.#]\w+)*)(?:\s+@\s*([-\[\]|_\w]+))?/, function( message ) { 280 | message.say( to( message, 2 ) + ": Sorry, the `api` command is temporarily disabled. Docs here: http://api.prototypejs.org/" ) 281 | }) 282 | 283 | // LOGS 284 | j.watch_for( /.*/, function ( message ) { 285 | var now = new Date() 286 | , location = path.join( options.logdir, message.source ) 287 | , file = path.join( location, now.strftime( '%Y-%m-%d.log' ) ) 288 | 289 | // Make the directory 290 | path.exists( location, function( exists ) { 291 | var log 292 | if ( ! exists ) 293 | fs.mkdirSync( location, 0755 ) 294 | log = fs.createWriteStream( file, { flags: 'a' }) 295 | log.write( message + '\n' ) 296 | log.end() 297 | }) 298 | }) 299 | }).connect( options ) 300 | 301 | // Register plugins 302 | if ( options["plugins"] ) 303 | options["plugins"].forEach( function( plugin ) { 304 | var plugReg = require( "./plugins/" + plugin ).register 305 | try { 306 | jerk( function( j ) { 307 | // Gross hack until global var is dead 308 | plugReg( j, dynamic_json ) 309 | }) 310 | } catch (e) { 311 | console.error( "Failed to register plugin %s: %s", plugin, e ) 312 | } 313 | }) 314 | 315 | /* ------------------------------ Functions ------------------------------ */ 316 | function to ( message, def, idx ) { 317 | if ( idx === undefined && typeof def === 'number' ) 318 | idx = def, def = null 319 | else 320 | idx = idx || 1 321 | return !!message.match_data[idx] ? message.match_data[idx].trim() : def || message.user 322 | } 323 | 324 | function watchForSingle ( trigger, msg ) { 325 | jerk( function( j ) { 326 | j.watch_for( new RegExp( "^([\\/.,`?])?" + trigger + "(?:\\s*@\\s*([-\\[\\]\\{\\}`|_\\w]+))?\\s*$", "i" ), function( message ) { 327 | // Return if botty is present 328 | if ( message.match_data[1] == '?' && message.source.clients.indexOf( 'bot-t' ) >= 0 ) 329 | return 330 | 331 | message.say( to( message, 2 ) + ": " + msg ) 332 | }) 333 | }) 334 | } 335 | function reloadJSON ( what, hollaback ) { 336 | hollaback = hollaback || function(){} 337 | 338 | Object.keys( what ).forEach( function( k ) { 339 | var url 340 | if ( what[k].slice( 0, 4 ) == 'http' ) { 341 | url = URL.parse( what[k] ) 342 | http 343 | .get( { host: url.host, path: url.pathname + ( url.search || '' ), port: 80 }, function ( res ) { 344 | var data = '' 345 | res 346 | .on( 'data', function ( c ) { data += c } ) 347 | .on( 'end', function(){ 348 | var j = JSON.parse( data ) 349 | hollaback.call( null, undefined, dynamic_json[k] = j ) 350 | }) 351 | }) 352 | .on( 'error', hollaback ) 353 | } 354 | else 355 | fs.readFile( what[k], function( er, data ) { 356 | if ( ! er ) 357 | dynamic_json[k] = JSON.parse( data ) 358 | if ( hollaback ) 359 | hollaback.call( null, er, dynamic_json[k] ) 360 | }) 361 | }) 362 | } 363 | 364 | function liveReload( message ) { var chain 365 | switch ( message.match_data[1] ) { 366 | case 'wat': 367 | chain = 368 | [ function( done ) { exec( 'git pull origin master', { cwd: path.join( __dirname, 'vendor', 'WAT' ) }, done ) } 369 | , function( done ) { reloadJSON( { wat: 'vendor/WAT/wat.json' }, done) } 370 | , function( done ) { message.say( message.user + ': Last WAT: ' + dynamic_json.wat[ dynamic_json.wat.length - 1 ] ); done() } 371 | ] 372 | break 373 | case 'crew': 374 | chain = 375 | [ function( done ) { reloadJSON( { crew: 'http://ot-crew.com/crew.json' }, done) } 376 | ] 377 | break 378 | case 'self': // Assumes it will be automagically restarted by forever/god/monit/whatever 379 | chain = 380 | [ function( done ) { exec( 'git pull origin master', { cwd: __dirname }, done ) } 381 | , function( done ) { exec( 'git submodule init', { cwd: __dirname }, done ) } 382 | , function( done ) { exec( 'git submodule update', { cwd: __dirname }, done ) } 383 | , function( done ) { exec( 'git pull origin master', { cwd: path.join( __dirname, 'vendor', 'WAT' ) }, done ) } // Always use latest WAT 384 | , function( done ) { process.exit( 0 ) } 385 | ] 386 | break 387 | } 388 | 389 | if ( chain ) 390 | groupie.chain( chain, function ( er, results ) { 391 | if ( er ) { 392 | message.say( message.user + ': Sorry there was an error reloading "' + message.match_data[1] + '"' ) 393 | message.say( message.user + ': ' + er.message ) 394 | } 395 | else 396 | message.say( message.user + ': Successfully reloaded "' + message.match_data[1] + '"' ) 397 | }) 398 | } 399 | 400 | -------------------------------------------------------------------------------- /sandboxed-ipc-repl.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | (require racket/sandbox 3 | racket/pretty) 4 | (require (planet "describe.rkt" ("williams" "describe.plt" 1 3))) 5 | 6 | (define e (make-evaluator 'racket/base '(require racket/list) 7 | '(require racket/vector) 8 | '(require racket/function) 9 | '(require racket/mpair) 10 | '(require racket/port) 11 | '(require net/url) 12 | '(require (planet "describe.rkt" ("williams" "describe.plt" 1 3))))) 13 | 14 | (e '(define (join lst str) 15 | (let jn ([l lst] [a ""]) 16 | (cond [(null? (cdr l)) (string-append a (car l))] 17 | [else (jn (cdr l) (string-append a (car l) str))])))) 18 | 19 | (e '(define (.. start end) 20 | (let ([diff (- end start)]) 21 | (build-list diff (λ (n) (+ n start)))))) 22 | 23 | (pretty-write (e (read))) 24 | -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { "server": "irc.freenode.net" 2 | , "nick": "david_mark" 3 | , "channels": [ "#RubyOnRails", "#runlevel6", "#inimino", "#prototype", "#jquery-ot", "#wadsup" ] 4 | , "user": 5 | { "username": "david_mark" 6 | , "hostname": "intertubes" 7 | , "servername": "tube001" 8 | , "realname": "Prototype Bot" 9 | } 10 | , "logdir": "logs" 11 | , "plugins": [ "caniuse" 12 | , "duckduckgo" 13 | , "google" 14 | , "octo" 15 | , "weather" 16 | , "wolframalpha" 17 | ] 18 | } -------------------------------------------------------------------------------- /srepl-1.0.0-SNAPSHOT-standalone.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gf3/protobot/c08e286972f08d16549c65da3d8e740178b003e0/srepl-1.0.0-SNAPSHOT-standalone.jar -------------------------------------------------------------------------------- /strftime.js: -------------------------------------------------------------------------------- 1 | // Written by Gianni Chiappetta - gianni[at]runlevel6[dot]org 2 | // Released under the WTFPL 3 | 4 | if ( typeof Date.prototype.strftime == 'undefined' ) { 5 | /** 6 | * Date#strftime(format) -> String 7 | * - format (String): Formats time according to the directives in the given format string. Any text not listed as a directive will be passed through to the output string. 8 | * 9 | * Ruby-style date formatting. Format matchers: 10 | * 11 | * %a - The abbreviated weekday name (``Sun'') 12 | * %A - The full weekday name (``Sunday'') 13 | * %b - The abbreviated month name (``Jan'') 14 | * %B - The full month name (``January'') 15 | * %c - The preferred local date and time representation 16 | * %d - Day of the month (01..31) 17 | * %e - Day of the month without leading zeroes (1..31) 18 | * %H - Hour of the day, 24-hour clock (00..23) 19 | * %I - Hour of the day, 12-hour clock (01..12) 20 | * %j - Day of the year (001..366) 21 | * %k - Hour of the day, 24-hour clock w/o leading zeroes (0..23) 22 | * %l - Hour of the day, 12-hour clock w/o leading zeroes (1..12) 23 | * %m - Month of the year (01..12) 24 | * %M - Minute of the hour (00..59) 25 | * %p - Meridian indicator (``AM'' or ``PM'') 26 | * %P - Meridian indicator (``am'' or ``pm'') 27 | * %S - Second of the minute (00..60) 28 | * %U - Week number of the current year, 29 | * starting with the first Sunday as the first 30 | * day of the first week (00..53) 31 | * %W - Week number of the current year, 32 | * starting with the first Monday as the first 33 | * day of the first week (00..53) 34 | * %w - Day of the week (Sunday is 0, 0..6) 35 | * %x - Preferred representation for the date alone, no time 36 | * %X - Preferred representation for the time alone, no date 37 | * %y - Year without a century (00..99) 38 | * %Y - Year with century 39 | * %Z - Time zone name 40 | * %z - Time zone expressed as a UTC offset (``-04:00'') 41 | * %% - Literal ``%'' character 42 | * 43 | * http://www.ruby-doc.org/core/classes/Time.html#M000298 44 | * 45 | **/ 46 | 47 | Object.defineProperty( Date.prototype, 'strftime', 48 | { value: (function(){ 49 | var cache = { start_of_year: new Date( 'Jan 1 ' + new Date()).getFullYear() } 50 | , regexp = /%([a-z]|%)/mig 51 | , day_in_ms = 1000 * 60 * 60 * 24 52 | , days = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ] 53 | , months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ] 54 | , abbr_days = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ] 55 | , abbr_months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] 56 | , formats = 57 | { 'a': weekday_name_abbr 58 | , 'A': weekday_name 59 | , 'b': month_name_abbr 60 | , 'B': month_name 61 | , 'c': default_local 62 | , 'd': day_padded 63 | , 'e': day 64 | , 'H': hour_24_padded 65 | , 'I': hour_padded 66 | , 'j': day_of_year 67 | , 'k': hour_24 68 | , 'l': hour 69 | , 'm': month 70 | , 'M': minute 71 | , 'p': meridian_upcase 72 | , 'P': meridian 73 | , 'S': second 74 | , 'U': week_number_from_sunday 75 | // , 'W': week_number_from_monday 76 | , 'w': day_of_week 77 | , 'x': default_local_date 78 | , 'X': default_local_time 79 | , 'y': year_abbr 80 | , 'Y': year 81 | // , 'Z': time_zone_name 82 | , 'z': time_zone_offset 83 | , '%': function() { return '%' } 84 | } 85 | 86 | // Strftime 87 | return strftime 88 | 89 | /* ------------------------------ Utility Functions ------------------------------ */ 90 | // day 91 | function day( date ) { 92 | return date.getDate() + '' 93 | } 94 | 95 | // day_of_week 96 | function day_of_week( date ) { 97 | return date.getDay() + '' 98 | } 99 | 100 | // day_of_year 101 | function day_of_year( date ) { 102 | return ( ( ( date.getTime() - cache[ 'start_of_year' ].getTime() ) / day_in_ms + 1 ) + '' ).split( /\./ )[0] 103 | } 104 | 105 | // day_padded 106 | function day_padded( date ) { 107 | return ( '0' + day( date ) ).slice(-2) 108 | } 109 | 110 | // default_local 111 | function default_local( date ) { 112 | return date.toLocaleString() 113 | } 114 | 115 | // default_local_date 116 | function default_local_date( date ) { 117 | return date.toLocaleDateString() 118 | } 119 | 120 | // default_local_time 121 | function default_local_time( date ) { 122 | return date.toLocaleTimeString() 123 | } 124 | 125 | // hour 126 | function hour( date ) { 127 | var hour = date.getHours() 128 | 129 | if ( hour === 0 ) 130 | hour = 12 131 | else if ( hour > 12 ) 132 | hour -= 12 133 | 134 | return hour + '' 135 | } 136 | 137 | // hour_24 138 | function hour_24( date ) { 139 | return date.getHours() 140 | } 141 | 142 | // hour_24_padded 143 | function hour_24_padded( date ) { 144 | return ( '0' + hour_24( date ) ).slice(-2) 145 | } 146 | 147 | // hour_padded 148 | function hour_padded( date ) { 149 | return ( '0' + hour( date ) ).slice(-2) 150 | } 151 | 152 | // meridian 153 | function meridian( date ) { 154 | return date.getHours() >= 12 ? 'pm' : 'am' 155 | } 156 | 157 | // meridian_upcase 158 | function meridian_upcase( date ) { 159 | return meridian( date ).toUpperCase() 160 | } 161 | 162 | // minute 163 | function minute( date ) { 164 | return ( '0' + date.getMinutes() ).slice(-2) 165 | } 166 | 167 | // month 168 | function month( date ) { 169 | return ( '0' + ( date.getMonth() + 1 ) ).slice(-2) 170 | } 171 | 172 | // month_name 173 | function month_name( date ) { 174 | return months[ date.getMonth() ] 175 | } 176 | 177 | // month_name_abbr 178 | function month_name_abbr( date ) { 179 | return abbr_months[ date.getMonth() ] 180 | } 181 | 182 | // second 183 | function second( date ) { 184 | return ( '0' + date.getSeconds() ).slice(-2) 185 | } 186 | 187 | // time_zone_offset 188 | function time_zone_offset( date ) { 189 | var tz_offset = date.getTimezoneOffset() 190 | return ( tz_offset >= 0 ? '-' : '' ) + ( '0' + ( tz_offset / 60 ) ).slice(-2) + ':' + ( '0' + ( tz_offset % 60 ) ).slice(-2) 191 | } 192 | 193 | // week_number_from_sunday 194 | function week_number_from_sunday( date ) { 195 | return ( '0' + Math.round( parseInt( day_of_year( date ), 10 ) / 7 ) ).slice(-2) 196 | } 197 | 198 | // weekday_name 199 | function weekday_name( date ) { 200 | return days[ date.getDay() ] 201 | } 202 | 203 | // weekday_name_abbr 204 | function weekday_name_abbr( date ) { 205 | return abbr_days[ date.getDay() ] 206 | } 207 | 208 | // year 209 | function year( date ) { 210 | return date.getFullYear() + '' 211 | } 212 | 213 | // year_abbr 214 | function year_abbr( date ) { 215 | return year( date ).slice(-2) 216 | } 217 | 218 | /*------------------------------ Main ------------------------------*/ 219 | function strftime( format ) { 220 | var match 221 | , output = format 222 | cache[ 'start_of_year' ] = new Date( 'Jan 1 ' + this.getFullYear() ) 223 | 224 | while ( match = regexp.exec( format ) ) 225 | if ( match[1] in formats ) 226 | output = output.replace( new RegExp( match[0], 'mg' ), formats[ match[1] ](this) ) 227 | 228 | return output 229 | } 230 | })() 231 | }) 232 | } 233 | 234 | -------------------------------------------------------------------------------- /vendor/strftime/strftime.js: -------------------------------------------------------------------------------- 1 | // Written by Gianni Chiappetta - gianni[at]runlevel6[dot]org 2 | // Released under the WTFPL 3 | 4 | if ( typeof Date.prototype.strftime == 'undefined' ) { 5 | /** 6 | * Date#strftime(format) -> String 7 | * - format (String): Formats time according to the directives in the given format string. Any text not listed as a directive will be passed through to the output string. 8 | * 9 | * Ruby-style date formatting. Format matchers: 10 | * 11 | * %a - The abbreviated weekday name (``Sun'') 12 | * %A - The full weekday name (``Sunday'') 13 | * %b - The abbreviated month name (``Jan'') 14 | * %B - The full month name (``January'') 15 | * %c - The preferred local date and time representation 16 | * %d - Day of the month (01..31) 17 | * %e - Day of the month without leading zeroes (1..31) 18 | * %H - Hour of the day, 24-hour clock (00..23) 19 | * %I - Hour of the day, 12-hour clock (01..12) 20 | * %j - Day of the year (001..366) 21 | * %k - Hour of the day, 24-hour clock w/o leading zeroes (0..23) 22 | * %l - Hour of the day, 12-hour clock w/o leading zeroes (1..12) 23 | * %m - Month of the year (01..12) 24 | * %M - Minute of the hour (00..59) 25 | * %p - Meridian indicator (``AM'' or ``PM'') 26 | * %P - Meridian indicator (``am'' or ``pm'') 27 | * %S - Second of the minute (00..60) 28 | * %U - Week number of the current year, 29 | * starting with the first Sunday as the first 30 | * day of the first week (00..53) 31 | * %W - Week number of the current year, 32 | * starting with the first Monday as the first 33 | * day of the first week (00..53) 34 | * %w - Day of the week (Sunday is 0, 0..6) 35 | * %x - Preferred representation for the date alone, no time 36 | * %X - Preferred representation for the time alone, no date 37 | * %y - Year without a century (00..99) 38 | * %Y - Year with century 39 | * %Z - Time zone name 40 | * %z - Time zone expressed as a UTC offset (``-04:00'') 41 | * %% - Literal ``%'' character 42 | * 43 | * http://www.ruby-doc.org/core/classes/Time.html#M000298 44 | * 45 | **/ 46 | 47 | Object.defineProperty( Date.prototype, 'strftime', 48 | { value: (function(){ 49 | var cache = { start_of_year: new Date( 'Jan 1 ' + new Date()).getFullYear() } 50 | , regexp = /%([a-z]|%)/mig 51 | , day_in_ms = 1000 * 60 * 60 * 24 52 | , days = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ] 53 | , months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ] 54 | , abbr_days = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ] 55 | , abbr_months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] 56 | , formats = 57 | { 'a': weekday_name_abbr 58 | , 'A': weekday_name 59 | , 'b': month_name_abbr 60 | , 'B': month_name 61 | , 'c': default_local 62 | , 'd': day_padded 63 | , 'e': day 64 | , 'H': hour_24_padded 65 | , 'I': hour_padded 66 | , 'j': day_of_year 67 | , 'k': hour_24 68 | , 'l': hour 69 | , 'm': month 70 | , 'M': minute 71 | , 'p': meridian_upcase 72 | , 'P': meridian 73 | , 'S': second 74 | , 'U': week_number_from_sunday 75 | // , 'W': week_number_from_monday 76 | , 'w': day_of_week 77 | , 'x': default_local_date 78 | , 'X': default_local_time 79 | , 'y': year_abbr 80 | , 'Y': year 81 | // , 'Z': time_zone_name 82 | , 'z': time_zone_offset 83 | , '%': function() { return '%' } 84 | } 85 | 86 | // Strftime 87 | return strftime 88 | 89 | /* ------------------------------ Utility Functions ------------------------------ */ 90 | // day 91 | function day( date ) { 92 | return date.getDate() + '' 93 | } 94 | 95 | // day_of_week 96 | function day_of_week( date ) { 97 | return date.getDay() + '' 98 | } 99 | 100 | // day_of_year 101 | function day_of_year( date ) { 102 | return ( ( ( date.getTime() - cache[ 'start_of_year' ].getTime() ) / day_in_ms + 1 ) + '' ).split( /\./ )[0] 103 | } 104 | 105 | // day_padded 106 | function day_padded( date ) { 107 | return ( '0' + day( date ) ).slice(-2) 108 | } 109 | 110 | // default_local 111 | function default_local( date ) { 112 | return date.toLocaleString() 113 | } 114 | 115 | // default_local_date 116 | function default_local_date( date ) { 117 | return date.toLocaleDateString() 118 | } 119 | 120 | // default_local_time 121 | function default_local_time( date ) { 122 | return date.toLocaleTimeString() 123 | } 124 | 125 | // hour 126 | function hour( date ) { 127 | var hour = date.getHours() 128 | 129 | if ( hour === 0 ) 130 | hour = 12 131 | else if ( hour > 12 ) 132 | hour -= 12 133 | 134 | return hour + '' 135 | } 136 | 137 | // hour_24 138 | function hour_24( date ) { 139 | return date.getHours() 140 | } 141 | 142 | // hour_24_padded 143 | function hour_24_padded( date ) { 144 | return ( '0' + hour_24( date ) ).slice(-2) 145 | } 146 | 147 | // hour_padded 148 | function hour_padded( date ) { 149 | return ( '0' + hour( date ) ).slice(-2) 150 | } 151 | 152 | // meridian 153 | function meridian( date ) { 154 | return date.getHours() >= 12 ? 'pm' : 'am' 155 | } 156 | 157 | // meridian_upcase 158 | function meridian_upcase( date ) { 159 | return meridian( date ).toUpperCase() 160 | } 161 | 162 | // minute 163 | function minute( date ) { 164 | return ( '0' + date.getMinutes() ).slice(-2) 165 | } 166 | 167 | // month 168 | function month( date ) { 169 | return ( '0' + ( date.getMonth() + 1 ) ).slice(-2) 170 | } 171 | 172 | // month_name 173 | function month_name( date ) { 174 | return months[ date.getMonth() ] 175 | } 176 | 177 | // month_name_abbr 178 | function month_name_abbr( date ) { 179 | return abbr_months[ date.getMonth() ] 180 | } 181 | 182 | // second 183 | function second( date ) { 184 | return ( '0' + date.getSeconds() ).slice(-2) 185 | } 186 | 187 | // time_zone_offset 188 | function time_zone_offset( date ) { 189 | var tz_offset = date.getTimezoneOffset() 190 | return ( tz_offset >= 0 ? '-' : '' ) + ( '0' + ( tz_offset / 60 ) ).slice(-2) + ':' + ( '0' + ( tz_offset % 60 ) ).slice(-2) 191 | } 192 | 193 | // week_number_from_sunday 194 | function week_number_from_sunday( date ) { 195 | return ( '0' + Math.round( parseInt( day_of_year( date ), 10 ) / 7 ) ).slice(-2) 196 | } 197 | 198 | // weekday_name 199 | function weekday_name( date ) { 200 | return days[ date.getDay() ] 201 | } 202 | 203 | // weekday_name_abbr 204 | function weekday_name_abbr( date ) { 205 | return abbr_days[ date.getDay() ] 206 | } 207 | 208 | // year 209 | function year( date ) { 210 | return date.getFullYear() + '' 211 | } 212 | 213 | // year_abbr 214 | function year_abbr( date ) { 215 | return year( date ).slice(-2) 216 | } 217 | 218 | /*------------------------------ Main ------------------------------*/ 219 | function strftime( format ) { 220 | var match 221 | , output = format 222 | cache[ 'start_of_year' ] = new Date( 'Jan 1 ' + this.getFullYear() ) 223 | 224 | while ( match = regexp.exec( format ) ) 225 | if ( match[1] in formats ) 226 | output = output.replace( new RegExp( match[0], 'mg' ), formats[ match[1] ](this) ) 227 | 228 | return output 229 | } 230 | })() 231 | }) 232 | } 233 | 234 | -------------------------------------------------------------------------------- /vendor/unescape/unescape.js: -------------------------------------------------------------------------------- 1 | var entities = 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 | function unescapeEntity( input ) { 257 | if ( input.charAt(1) === '#' ) 258 | return String.fromCharCode( parseInt( input.substr(2), 10 ) ) 259 | else if ( entities.hasOwnProperty( input ) ) 260 | return entities[ input ] 261 | } 262 | 263 | module.exports = function unescapeAll( input ) { 264 | var entityRe = /&(#?)(\d{1,5}|\w{1,8});/gm 265 | return input.replace( entityRe, unescapeEntity ) 266 | } 267 | --------------------------------------------------------------------------------