Permalink
Browse files

Merge remote-tracking branch 'origin/master' into groupme

  • Loading branch information...
2 parents 237e209 + f796552 commit f5c2bedcaeb70b7276efb7b2dbe27779cf0a3058 @atmos atmos committed Oct 11, 2011
Showing with 223 additions and 40 deletions.
  1. +5 −0 bin/hubot
  2. +3 −1 package.json
  3. +9 −15 src/creator.coffee
  4. +2 −1 src/hubot/campfire.coffee
  5. +63 −0 src/hubot/scripts/roles.coffee
  6. +9 −1 src/hubot/shell.coffee
  7. +89 −15 src/robot.coffee
  8. +43 −7 src/templates/README.md
View
@@ -18,6 +18,7 @@ Switches = [
[ "-h", "--help", "Display the help information"],
[ "-a", "--adapter ADAPTER", "The Adapter to use"],
[ "-c", "--create PATH", "Create a deployable hubot"],
+ [ "-s", "--enable-slash", "Enable replacing the robot's name with '/'"],
[ "-n", "--name NAME", "The name of the robot in chat" ]
]
@@ -40,6 +41,9 @@ Parser.on "create", (opt, value) ->
Options.path = value
Options.create = true
+Parser.on "enable-slash", (opt) ->
+ Options.enableSlash = true
+
Parser.parse process.ARGV
if Options.create
@@ -59,6 +63,7 @@ else
scriptsPath = Path.resolve "./scripts"
console.log "Loading deploy-local scripts at #{scriptsPath}"
robot = new Hubot scriptsPath, Options.name
+ robot.enableSlash = Options.enableSlash
scriptsPath = Path.resolve "src", "hubot", "scripts"
console.log "Loading hubot core scripts for relative scripts at #{scriptsPath}"
View
@@ -18,7 +18,9 @@
"coffee-script": "1.1.1",
"optparse": "1.0.1",
"scoped-http-client": "0.9.0",
- "irc": "0.2"
+ "irc": "0.2",
+ "hiredis": "0.1.12",
+ "redis": "0.6.7"
},
"directories": {
View
@@ -34,30 +34,24 @@ class Creator
# they want. It also provides them with a few examples and a top
# level scripts folder
#
- # path - The destination
+ # path - The destination
copyDefaultScripts: (path) ->
- copy = @copy
- scriptsDir = @scriptsDir
- Fs.readdirSync(scriptsDir).forEach (file) ->
- copy "#{scriptsDir}/#{file}", "#{path}/#{file}"
+ Fs.readdirSync(@scriptsDir).forEach (file) =>
+ @copy "#{@scriptsDir}/#{file}", "#{path}/#{file}"
# Run the creator process
#
# Setup a ready to deploy folder that uses the hubot npm package
# Overwriting basic hubot files if they exist
run: ->
- path = @path
- copy = @copy
- templates = @templateDir
+ console.log "Creating a hubot install at #{@path}"
- console.log "Creating a hubot install at #{path}"
+ @mkdirDashP(@path)
+ @mkdirDashP("#{@path}/scripts")
- @mkdirDashP(path)
- @mkdirDashP("#{path}/scripts")
+ @copyDefaultScripts("#{@path}/scripts")
- @copyDefaultScripts("#{path}/scripts")
-
- ["Procfile", "package.json", "README.md"].forEach (file) ->
- copy "#{templates}/#{file}", "#{path}/#{file}"
+ ["Procfile", "package.json", "README.md"].forEach (file) =>
+ @copy "#{@templateDir}/#{file}", "#{@path}/#{file}"
exports.Creator = Creator
@@ -26,7 +26,8 @@ class Campfire extends Robot
bot.on "TextMessage", (id, created, room, user, body) ->
bot.User user, (err, userData) ->
if userData.user
- author = new Robot.User user, userData.user.name, room: room
+ author = self.userForId(userData.user.id, userData.user)
+ author.room = room
self.receive new Robot.Message(author, body)
bot.Me (err, data) ->
@@ -0,0 +1,63 @@
+# Assign roles to people you're chatting with
+#
+# <user> is a badass guitarist - assign a role to a user
+# <user> is not a badass guitarist - remove a role from a user
+# who is <user> - see what roles a user has
+
+# hubot holman is an ego surfer
+# hubot holman is not an ego surfer
+#
+
+module.exports = (robot) ->
+ robot.respond /who is ([\w .-]+)\?*$/i, (msg) ->
+ name = msg.match[1]
+
+ if name == "you"
+ msg.send "Who ain't I?"
+ else if name == robot.name
+ msg.send "The best."
+ else if user = robot.userForName name
+ user.roles = user.roles or [ ]
+ if user.roles.length > 0
+ msg.send "#{name} is #{user.roles.join(", ")}."
+ else
+ msg.send "#{name} is nothing to me."
+ else
+ msg.send "#{name}? Never heard of 'em"
+
+ robot.respond /([\w .-]+) is (["'\w: ]+)[.!]*$/i, (msg) ->
+ name = msg.match[1]
+ newRole = msg.match[2].trim()
+
+ unless name in ['who', 'what', 'where', 'when', 'why']
+ unless newRole.match(/^not\s+/i)
+ if user = robot.userForName name
+ user.roles = user.roles or [ ]
+
+ if newRole in user.roles
+ msg.send "I know"
+ else
+ user.roles.push(newRole)
+ if name.toLowerCase() == robot.name
+ msg.send "Ok, I am #{newRole}."
+ else
+ msg.send "Ok, #{name} is #{newRole}."
+ else
+ msg.send "I don't know anything about #{name}."
+
+ robot.respond /([\w .-]+) is not (["'\w: ]+)[.!]*$/i, (msg) ->
+ name = msg.match[1]
+ newRole = msg.match[2].trim()
+
+ unless name in ['who', 'what', 'where', 'when', 'why']
+ if user = robot.userForName name
+ user.roles = user.roles or [ ]
+
+ if newRole not in user.roles
+ msg.send "I know."
+ else
+ user.roles = (role for role in user.roles when role != newRole)
+ msg.send "Ok, #{name} is no longer #{newRole}."
+
+ else
+ msg.send "I don't know anything about #{name}."
@@ -12,11 +12,19 @@ class Shell extends Robot
run: ->
console.log "Hubot: the Shell."
- user = new Robot.User 1, 'shell'
+ user = @userForId('1', {name: "Shell"})
+ console.log user
+
process.stdin.resume()
process.stdin.on 'data', (txt) =>
txt.toString().split("\n").forEach (line) =>
return if line.length == 0
@receive new Robot.Message user, line
+ setTimeout =>
+ user = @userForId('1', {name: "Shell"})
+ atmos = @userForId('2', {name: "atmos"})
+ holman = @userForId('3', {name: "Zach Holman"})
+ , 3000
+
exports.Shell = Shell
View
@@ -1,18 +1,21 @@
-Fs = require 'fs'
-Url = require 'url'
-Path = require 'path'
+Fs = require 'fs'
+Url = require 'url'
+Path = require 'path'
+Redis = require 'redis'
class Robot
# Robots receive messages from a chat source (Campfire, irc, etc), and
# dispatch them to matching listeners.
#
# path - String directory full of Hubot scripts to load.
constructor: (path, name = "Hubot") ->
- @name = name
- @commands = []
- @listeners = []
- @loadPaths = []
- @Response = Robot.Response
+ @name = name
+ @brain = new Robot.Brain()
+ @commands = []
+ @Response = Robot.Response
+ @listeners = []
+ @loadPaths = []
+ @enableSlash = false
if path then @load path
# Public: Adds a Listener that attempts to match incoming messages based on
@@ -26,15 +29,18 @@ class Robot
@listeners.push new Listener(@, regex, callback)
# Public: Adds a Listener that attempts to match incoming messages directed at the robot
- # based on a Regex.
+ # based on a Regex. All regexes treat patterns like they begin with a '^'
#
# regex - A Regex that determines if the callback should be called.
# callback - A Function that is called with a Response object.
#
# Returns nothing.
respond: (regex, callback) ->
re = regex.toString().split("/")
- newRegex = new RegExp("#{@name}:?\\s*#{re[1]}", re[2])
+ if @enableSlash
+ newRegex = new RegExp("(\/|#{@name}:?)\\s*#{re[1]}", re[2])
+ else
+ newRegex = new RegExp("#{@name}:?\\s*#{re[1]}", re[2])
@listeners.push new Listener(@, newRegex, callback)
# Public: Passes the given message to any interested Listeners.
@@ -107,15 +113,83 @@ class Robot
# Extend this.
run: ->
+ users: () ->
+ @brain.data.users
+
+ # Public: Get a User object given a unique identifier
+ #
+ userForId: (id, options) ->
+ user = @brain.data.users[id]
+ unless user
+ user = new Robot.User id, options
+ @brain.data.users[id] = user
+
+ user
+
+ # Public: Get a User object given a name
+ #
+ userForName: (name) ->
+ result = null
+ lowerName = name.toLowerCase()
+ for k of (@brain.data.users or { })
+ if @brain.data.users[k]['name'].toLowerCase() == lowerName
+ result = @brain.data.users[k]
+
+ result
+ # (user for id in @brain.data.users when @users[id]['name'].toLowerCase() == lowerName)
+
class Robot.User
# Represents a participating user in the chat.
#
# id - A unique ID for the user.
- # name - A String name of the user.
# options - An optional Hash of key, value pairs for this user.
- constructor: (@id, @name, options) ->
- for key, value of (options or {})
- this[key] = value
+ constructor: (@id, options = { }) ->
+ for k of (options or { })
+ @[k] = options[k]
+
+class Robot.Brain
+ # Represents somewhat persistent storage for the robot.
+ #
+ # Returns a new Brain that's trying to connect to redis
+ #
+ # Previously persisted data is loaded on a successful connection
+ #
+ # Redis connects to a environmental variable REDISTOGO_URL or
+ # fallsback to localhost
+ constructor: () ->
+ @data =
+ users: { }
+
+ info = Url.parse process.env.REDISTOGO_URL || 'localhost'
+ @client = Redis.createClient(info.port, info.hostname)
+
+ if info.auth
+ @client.auth info.auth.split(":")[1]
+
+ @client.on "error", (err) ->
+ console.log "Error #{err}"
+ @client.on "connect", () =>
+ console.log "BOOM: Connected to Redis"
+ @client.get "hubot:storage", (err, reply) =>
+ throw err if err
+ if reply
+ @mergeData JSON.parse reply.toString()
+
+ setInterval =>
+ # console.log JSON.stringify @data
+ data = JSON.stringify @data
+ @client.set "hubot:storage", data, (err, reply) ->
+ # console.log "Saved #{reply.toString()}"
+ , 5000
+
+ # Merge keys loaded from redis against the in memory representation
+ #
+ # Returns nothing
+ #
+ # Caveats: Deeply nested structures don't merge well
+ mergeData: (data) ->
+ for k of (data or { })
+ @data[k] = data[k]
class Robot.Message
# Represents an incoming message from the chat.
@@ -197,7 +271,7 @@ class Robot.Response
# url - String URL to access.
#
# Examples:
- #
+ #
# res.http("http://example.com")
# # set a single header
# .header('Authorization', 'bearer abcdef')
@@ -5,14 +5,56 @@ This is a version of GitHub's Campfire bot, hubot. He's pretty cool.
This version is designed to be deployed on heroku.
+Playing with Hubot
+==================
+
+You'll need to install the necessary dependencies for hubot
+ % npm install -g hubot
+ % hubot
+
+You'll see some startup output about where your scripts come from.
+
+ Loading deploy-local scripts at /Users/atmos/nubot/scripts
+ Loading hubot core scripts for relative scripts at /Users/atmos/nubot/src/hubot/scripts
+ Hubot: the Shell.
+ { id: '1', name: 'Shell' }
+ Loading hubot-scripts from /Users/atmos/nubot/hubot-scripts.json
+ BOOM: Connected to Redis
+
+Then you can interact with Hubot by typing `hubot help`.
+
+ hubot help
+
+ animate me <query> - The same thing as `image me`, except adds a few
+ convert me <expression> to <units> - Convert expression to given units.
+ help - Displays all of the help commands that Hubot knows about.
+ ...
+
+Take a look at the scripts in the `./scripts` folder for examples.
+Delete any scripts you think are silly. Add whatever functionality you
+want hubot to have.
+
+
+hubot-scripts
+=============
+
+There will inevitably be functionality that everyone will want. Instead
+of adding it to hubot itself, you can submit pull requests to
+[hubot-scripts](https://github.com/github/hubot-scripts). To enable
+scripts from the hubot-scripts package, add the script name to the
+hubot-scripts.json file in this repo.
+
Deployment
==========
% heroku create --stack cedar
% git push heroku master
% heroku ps:scale web=1
+ % heroku addons:add redistogo:nano
-You'll need to edit the `Procfile` to say what the bot's name is. Hubot also needs four environmental variables set to run and to keep him
+You'll need to edit the `Procfile` to say what the bot's name is.
+
+Hubot also needs four environmental variables set to run and to keep him
running on heroku.
Campfire Variables
@@ -46,9 +88,3 @@ Restart the bot
---------------
You may want to get comfortable with `heroku logs` and `heroku restart`
if you're having issues.
-
-Adding Your Own
-===============
-
-Take a look at the example scripts in the hubot codebase for now. He'll
-load any scripts you write that are in the `scripts/` folder.

0 comments on commit f5c2bed

Please sign in to comment.