Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

fuck a color history

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

0 comments on commit b253e94

Please sign in to comment.
Something went wrong with that request. Please try again.