Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

fuck a color history

  • Loading branch information...
commit b253e94e051c017eb7c8c0101c30a24f0851a499 0 parents
@atmos atmos authored
1  .gitignore
@@ -0,0 +1 @@
+node_modules
20 LICENSE.md
@@ -0,0 +1,20 @@
+Copyright (c) 2011 GitHub Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1  Procfile
@@ -0,0 +1 @@
+web: node_modules/coffee-script/bin/coffee bin/hubot
60 README.md
@@ -0,0 +1,60 @@
+Hubot
+=====
+
+This is a version of GitHub's Campfire bot, hubot. He's pretty cool.
+
+This version is designed to be deployed on heroku.
+
+
+Deployment
+==========
+
+ % git clone https://github.com/github/hubot.git
+ % cd hubot
+ % heroku create --stack cedar
+ % git push heroku master
+ % heroku ps:scale web=1
+
+Hubot needs four environmental variables set to run and to keep him
+running on heroku.
+
+Campfire Variables
+------------------
+
+Create a separate user for your bot and get their token from the web UI.
+
+ % heroku config:add HUBOT_CAMPFIRE_TOKEN="..."
+
+Get the numeric ids of the rooms you want the bot to join, comma
+delimited.
+
+ % heroku config:add HUBOT_CAMPFIRE_ROOMS="42,1024"
+
+Add the subdomain hubot should connect to. If you web URL looks like
+`http://mysubdomain.campfirenow.com` then you'd add it like this.
+
+ % heroku config:add HUBOT_CAMPFIRE_ACCOUNT="mysubdomain"
+
+The Web Host
+------------
+In order to keep hubot running, he needs to trick heroku into thinking
+he's constantly getting web traffic. Hubot will automatically ping his
+HTTP endpoint if you set the `HUBOT_WEB_HOST` variable. You can get the
+web endpoint by running `heroku info` and getting the hostname from the
+Web URL. Be sure to remove the `http://` prefix from it.
+
+ % heroku config:add HUBOT_WEB_HOST="galaxy324.herokuapp.com"
+
+
+Restart the bot
+---------------
+You may want to get comfortable with `heroku logs` and `heroku restart`
+if you're having issues.
+
+Local Testing
+=============
+
+It's easy to test scripts locally with the shell:
+
+ % bin/hubot -a stdio
+
57 bin/hubot
@@ -0,0 +1,57 @@
+#!/usr/bin/env coffee
+##
+# hubot [options]
+#
+# Launch an interactive hubot
+#
+require.paths.unshift __dirname + "/../node_modules"
+require.paths.unshift __dirname + "/../src"
+
+Path = require 'path'
+HTTP = require 'http'
+OptParse = require 'optparse'
+PortNumber = parseInt(process.env.PORT) || 8080
+
+Switches = [
+ [ "-h", "--help", "Display the help information"],
+ [ "-a", "--adapter ADAPTER", "The Adapter to use"]
+]
+
+Options =
+ adapter: "campfire"
+
+Parser = new OptParse.OptionParser(Switches)
+Parser.banner = "Usage hubot [options]"
+
+Parser.on "adapter", (opt, value) ->
+ Options.adapter = value
+
+Parser.parse process.ARGV
+
+Options.adapter
+
+switch Options.adapter
+ when "stdio"
+ Hubot = require("hubot/shell").Shell
+ when "campfire"
+ Hubot = require("hubot/campfire").Campfire
+
+robot = new Hubot Path.resolve(__dirname, "..", "src", "hubot", "scripts")
+robot.run()
+
+server = HTTP.createServer( (req, res) ->
+ res.writeHead 200, {'Content-Type': 'text/plain'}
+ res.end 'Hello from Hubot\n'
+).listen PortNumber
+
+setInterval(( ->
+ httpOpts =
+ host: process.env.HUBOT_WEB_HOST
+
+ HTTP.get( httpOpts, (res) ->
+ console.log "Got response: #{res}" unless res.statusCode == 200
+ ).on 'error', (e) ->
+ console.log "Got error: #{e.message}"
+), 60000)
+
+# vim:ft=coffee
29 package.json
@@ -0,0 +1,29 @@
+{
+ "name": "hubot",
+ "version": "0.1.0",
+ "author": "GitHub Inc.",
+ "keywords": "github hubot campfire bot",
+ "description": "A simple helpful Robot for your Company",
+ "licenses": [{
+ "type": "MIT",
+ "url": "http://github.com/github/hubot/raw/master/LICENSE"
+ }],
+
+ "repository" : {
+ "type" : "git",
+ "url" : "http://github.com/github/hubot.git"
+ },
+
+ "dependencies": {
+ "coffee-script": "1.1.1",
+ "optparse": "1.0.1"
+ },
+
+ "directories": {
+ "lib": "./src"
+ },
+ "main": "./src/hubot",
+ "bin": {
+ "hubot": "./bin/hubot"
+ }
+}
166 src/hubot/campfire.coffee
@@ -0,0 +1,166 @@
+Robot = require "robot"
+HTTPS = require "https"
+EventEmitter = require("events").EventEmitter
+
+class Campfire extends Robot
+ send: (user, strings...) ->
+ strings.forEach (str) =>
+ @bot.Room(user.room).speak str, (err, data) ->
+ console.log "#{user}: #{str}"
+
+ reply: (user, strings...) ->
+ strings.forEach (str) =>
+ @send user, "#{user.name}: #{str}"
+
+ run: ->
+ self = @
+ options =
+ token: process.env.HUBOT_CAMPFIRE_TOKEN
+ rooms: process.env.HUBOT_CAMPFIRE_ROOMS
+ account: process.env.HUBOT_CAMPFIRE_ACCOUNT
+
+ console.log options
+ bot = new CampfireStreaming(options)
+ console.log bot
+
+ bot.on "TextMessage", (id, created, room, user, body) ->
+ if body.match new RegExp "^#{bot.info.name}", "i"
+ bot.User user, (err, userData) ->
+ self.receive(
+ text: body
+ user: { id: user, name: userData.user.name, room: room }
+ )
+
+ bot.Me (err, data) ->
+ console.log data
+ bot.info = data.user
+ bot.rooms.forEach (room_id) ->
+ bot.Room(room_id).join (err, callback) ->
+ bot.Room(room_id).listen()
+
+ @bot = bot
+
+exports.Campfire = Campfire
+
+class CampfireStreaming extends EventEmitter
+ constructor: (options) ->
+ @token = options.token
+ @rooms = options.rooms.split(",")
+ @account = options.account
+ @domain = @account + ".campfirenow.com"
+ @authorization = "Basic " + new Buffer("#{@token}:x").toString("base64")
+
+ Rooms: (callback) ->
+ @get "/rooms", callback
+
+ User: (id, callback) ->
+ @get "/users/#{id}", callback
+
+ Me: (callback) ->
+ @get "/users/me", callback
+
+ Room: (id) ->
+ self = @
+
+ show: (callback) ->
+ self.post "/room/#{id}", "", callback
+ join: (callback) ->
+ self.post "/room/#{id}/join", "", callback
+ leave: (callback) ->
+ self.post "/room/#{id}/leave", "", callback
+ lock: (callback) ->
+ self.post "/room/#{id}/lock", "", callback
+ unlock: (callback) ->
+ self.post "/room/#{id}/unlock", "", callback
+
+ # say things to this channel on behalf of the token user
+ paste: (text, callback) ->
+ @message text, "PasteMessage", callback
+ sound: (text, callback) ->
+ @message text, "SoundMessage", callback
+ speak: (text, callback) ->
+ @message text, "TextMessage", callback
+ message: (text, type, callback) ->
+ body = { message: { "body":text, "type":type } }
+ self.post "/room/#{id}/speak", body, callback
+
+ # listen for activity in channels
+ listen: ->
+ headers =
+ "Host" : "streaming.campfirenow.com",
+ "Authorization" : self.authorization
+
+ options =
+ "host" : "streaming.campfirenow.com"
+ "port" : 443
+ "path" : "/room/#{id}/live.json"
+ "method" : "GET"
+ "headers": headers
+
+ request = HTTPS.request options, (response) ->
+ response.setEncoding("utf8")
+ response.on "data", (chunk) ->
+ if chunk.match(/^\S+/)
+ console.log "#{new Date}: Received #{id} \"#{chunk}\""
+ try
+ chunk.split("\r").forEach (part) ->
+ data = JSON.parse part
+
+ self.emit data.type, data.id, data.created_at, data.room_id, data.user_id, data.body
+ data
+
+ response.on "end", ->
+ console.log "Streaming Connection closed. :("
+ process.exit(1)
+
+ response.on "error", (err) ->
+ console.log err
+ request.end()
+ request.on "error", (err) ->
+ console.log err
+ console.log err.stack
+
+ # Convenience HTTP Methods for posting on behalf of the token"d user
+ get: (path, callback) ->
+ @request "GET", path, null, callback
+
+ post: (path, body, callback) ->
+ @request "POST", path, body, callback
+
+ request: (method, path, body, callback) ->
+ headers =
+ "Authorization" : @authorization
+ "Host" : @domain
+ "Content-Type" : "application/json"
+
+ options =
+ "host" : @domain
+ "port" : 443
+ "path" : path
+ "method" : method
+ "headers": headers
+
+ if method == "POST"
+ if typeof(body) != "string"
+ body = JSON.stringify body
+ options.headers["Content-Length"] = body.length
+
+ request = HTTPS.request options, (response) ->
+ data = ""
+ response.on "data", (chunk) ->
+ data += chunk
+ response.on "end", ->
+ try
+ callback null, JSON.parse(data)
+ catch err
+ callback null, data || { }
+ response.on "error", (err) ->
+ callback err, { }
+
+ if method == "POST"
+ request.end(body)
+ else
+ request.end()
+ request.on "error", (err) ->
+ console.log err
+ console.log err.stack
24 src/hubot/scripts/google-images.coffee
@@ -0,0 +1,24 @@
+module.exports = (robot) ->
+ robot.hear /(image|img)( me)? (.*)/i, (response) ->
+ imagery = response.match[3]
+ imgurl = 'http://ajax.googleapis.com/ajax/services/search/images?' +
+ 'v=1.0&rsz=8&q=' +escape(imagery)
+
+ response.fetch imgurl, (res) ->
+ images = JSON.parse(res.body)
+ images = images.responseData.results
+ image = response.random images
+
+ response.send image.unescapedUrl + "#.png"
+
+ robot.hear /animate me (.*)/i, (response) ->
+ imagery = response.match[1]
+ imgurl = 'http://ajax.googleapis.com/ajax/services/search/images?' +
+ 'v=1.0&rsz=8&as_filetype=gif&q=animted%20' +escape(imagery)
+
+ response.fetch imgurl, (res) ->
+ images = JSON.parse(res.body)
+ images = images.responseData.results
+ image = response.random images
+
+ response.send image.unescapedUrl + "#.png"
13 src/hubot/scripts/maps.coffee
@@ -0,0 +1,13 @@
+module.exports = (robot) ->
+ robot.hear /(?:(satellite|terrain|hybrid)[- ])?map me (.+)/i, (response) ->
+ mapType = response.match[1] || "roadmap"
+ location = response.match[2]
+ url = "http://maps.google.com/maps/api/staticmap?markers=" +
+ escape(location) +
+ "&size=400x400&maptype=" +
+ mapType +
+ "&sensor=false" +
+ "&format=png" # So campfire knows it's an image
+
+ response.send url
+
16 src/hubot/scripts/mustache.coffee
@@ -0,0 +1,16 @@
+module.exports = (robot) ->
+ robot.hear /(?:mo?u)?sta(?:s|c)he?(?: me)? (.*)/i, (response) ->
+ imagery = response.match[1]
+ mustachify = "http://mustachify.me/?src="
+ imageUrl = 'http://ajax.googleapis.com/ajax/services/search/images?' +
+ 'v=1.0&rsz=8&q=' +escape(imagery)
+
+ if imagery.match /^https?:\/\//i
+ response.send "#{mustachify}#{imagery}"
+ else
+ response.fetch imageUrl, (res) ->
+ images = JSON.parse(res.body)
+ images = images.responseData.results
+ image = response.random images
+
+ response.send "#{mustachify}#{image.unescapedUrl}#.png"
9 src/hubot/scripts/ping.coffee
@@ -0,0 +1,9 @@
+module.exports = (robot) ->
+ robot.hear /PING$/i, (response) ->
+ response.send "PONG"
+
+ robot.hear /ECHO (.*)$/i, (response) ->
+ response.send response.match[1]
+
+ robot.hear /TIME$/i, (response) ->
+ response.send "Server time is: #{new Date()}"
15 src/hubot/scripts/translate.coffee
@@ -0,0 +1,15 @@
+module.exports = (robot) ->
+ robot.hear /(translate)( me)? (.*)/i, (response) ->
+ term = "\"#{response.match[3]}\""
+ params = "client=t&hl=en&multires=1&sc=1&sl=auto&ssel=0&tl=en&tsel=0&uptl=en"
+
+ url = "http://translate.google.com/translate_a/t?#{params}&text=#{encodeURI(term)}"
+
+ response.fetch url, (res) ->
+ data = res.body
+ if data.length > 4 && data[0] == '['
+ parsed = eval(data)
+ parsed = parsed[0] && parsed[0][0] && parsed[0][0][0]
+ if parsed
+ response.send "#{term} means #{parsed}"
+
15 src/hubot/scripts/youtube.coffee
@@ -0,0 +1,15 @@
+module.exports = (robot) ->
+ robot.hear /(youtube|yt)( me)? (.*)/i, (response) ->
+ query = response.match[3]
+ url = "http://gdata.youtube.com/feeds/api/videos?" +
+ "orderBy=relevance&max-results=15&alt=json&q=" +
+ escape(query)
+
+ response.fetch url, (res) ->
+ videos = JSON.parse(res.body)
+ videos = videos.feed.entry
+ video = response.random videos
+
+ video.link.forEach (link) ->
+ if link.rel == "alternate" && link.type == "text/html"
+ response.send link.href
22 src/hubot/shell.coffee
@@ -0,0 +1,22 @@
+Robot = require 'robot'
+
+class Shell extends Robot
+ send: (user, strings...) ->
+ strings.forEach (str) ->
+ console.log str
+
+ reply: (user, strings...) ->
+ strings.forEach (str) =>
+ @send user, "#{user.name}: #{str}"
+
+ run: ->
+ self = @
+ process.openStdin().on 'data', (txt) ->
+ txt.toString().split("\n").forEach (line) ->
+ return if line.length == 0
+ self.receive(
+ text: line
+ user: { name: 'shell', id: 0 }
+ )
+
+exports.Shell = Shell
94 src/robot.coffee
@@ -0,0 +1,94 @@
+Fs = require 'fs'
+Url = require 'url'
+Path = require 'path'
+
+# Robots receive messages from a chat source (Campfire, irc, etc), and
+# dispatch them to matching listeners.
+class Robot
+ constructor: (path) ->
+ @listeners = []
+ if path then @load path
+
+ # Adds a Listener that attempts to match incoming messages based on a Regex.
+ hear: (regex, callback) ->
+ @listeners.push new Listener(@, regex, callback)
+
+ # Passes the given message to any interested Listeners.
+ receive: (message) ->
+ @listeners.forEach (lst) -> lst.call message
+
+ load: (path) ->
+ Fs.readdirSync(path).forEach (file) =>
+ ext = Path.extname file
+ full = Path.join path, Path.basename(file, ext)
+ if ext == '.coffee' or ext == '.js'
+ require(full) @
+
+ # Raw method for sending data back to the chat source. Extend this.
+ send: (user, strings...) ->
+
+ # Raw method for building a reply and sending it back to the chat source.
+ # Extend this.
+ reply: (user, strings...) ->
+
+ # Raw method for invoking the bot to run
+ # Extend this.
+ run: () ->
+
+# Listeners receive every message from the chat source and decide if they want
+# to act on it.
+class Listener
+ constructor: (@robot, @regex, @callback) ->
+
+ # Determines if the listener likes the content of the message. If so, a
+ # Response built from the given Message is passed to the Listener callback.
+ call: (message) ->
+ match = message.text.match @regex
+ return if not match
+ @callback new Response(@robot, message, match)
+
+# Responses are sent to matching listeners. Messages know about the content
+# and user that made the original message, and how to reply back to them.
+class Response
+ constructor: (@robot, @message, @match) ->
+
+ # Posts a message back to the chat source
+ #
+ # strings - One or more strings to be posted. The order of these strings
+ # should be kept intact.
+ #
+ # Returns nothing.
+ send: (strings...) ->
+ @robot.send @message.user, strings...
+
+ # Posts a message mentioning the current user.
+ #
+ # strings - One or more strings to be posted. The order of these strings
+ # should be kept intact.
+ #
+ # Returns nothing.
+ reply: (strings...) ->
+ @robot.reply @message.user, strings...
+
+ random: (items) ->
+ items[ Math.floor(Math.random() * items.length) ]
+
+ # helper for making quick HTTP GET requests.
+ # otherwise, use @http for the rad Node 0.4 HTTP Client.
+ fetch: (url, cb) ->
+ uri = Url.parse url
+ body = ''
+ @http.get({
+ host: uri.host
+ port: uri.port or 80
+ path: "#{uri.pathname}?#{uri.query}"
+ }, (res) ->
+ res.on 'data', (chunk) -> body += chunk.toString()
+ res.on 'end', ->
+ res.body = body
+ cb res
+ )
+
+Response.prototype.http = require('http')
+
+module.exports = Robot
Please sign in to comment.
Something went wrong with that request. Please try again.