Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

merge from fork origin

  • Loading branch information...
commit 38fa0b6d04a5521ed80d5165a3f7191ab9a95e84 1 parent 20393ba
@markomanninen markomanninen authored
Showing with 3,100 additions and 100 deletions.
  1. +27 −0 CHANGELOG.md
  2. +6 −0 bin/hubot
  3. +2 −2 package.json
  4. +55 −0 src/scripts/abstract.coffee
  5. +1 −1  src/scripts/achievement_unlocked.coffee
  6. +30 −0 src/scripts/animal.coffee
  7. +6 −0 src/scripts/bij.coffee
  8. +19 −0 src/scripts/cheer.coffee
  9. +36 −0 src/scripts/chm.coffee
  10. +9 −0 src/scripts/clark.coffee
  11. +24 −0 src/scripts/coderwall.coffee
  12. +10 −0 src/scripts/coin.coffee
  13. +9 −0 src/scripts/commitmessage.coffee
  14. +63 −0 src/scripts/conversation.coffee
  15. +11 −0 src/scripts/cowsay.coffee
  16. +65 −0 src/scripts/deadline.coffee
  17. +27 −0 src/scripts/demolition-man.coffee
  18. +15 −0 src/scripts/destiny.coffee
  19. +46 −0 src/scripts/dialectizer.coffee
  20. +38 −0 src/scripts/dice.coffee
  21. +19 −0 src/scripts/dilbert.coffee
  22. +5 −0 src/scripts/disassemble.coffee
  23. +1 −1  src/scripts/eight-ball.coffee
  24. +26 −0 src/scripts/faceup.coffee
  25. +31 −0 src/scripts/fibonacci.coffee
  26. +9 −0 src/scripts/fortune.coffee
  27. +99 −0 src/scripts/gauges.coffee
  28. +33 −0 src/scripts/getajob.coffee
  29. +37 −0 src/scripts/github-credentials.coffee
  30. +54 −0 src/scripts/google-reader.coffee
  31. +1 −0  src/scripts/haters.coffee
  32. +21 −0 src/scripts/isup.coffee
  33. +34 −0 src/scripts/jenkins.coffee
  34. +150 −0 src/scripts/jira.coffee
  35. +15 −0 src/scripts/jordan.coffee
  36. +62 −0 src/scripts/karma.coffee
  37. +2 −1  src/scripts/keep-alive.coffee
  38. +24 −0 src/scripts/kittens.coffee
  39. +96 −0 src/scripts/list-jira-bugs.coffee
  40. +75 −0 src/scripts/location-decision-maker.coffee
  41. +15 −1 src/scripts/lolz.coffee
  42. +13 −0 src/scripts/look-of-disapproval.coffee
  43. +19 −0 src/scripts/lyrics.coffee
  44. +7 −0 src/scripts/megusta.coffee
  45. +22 −8 src/scripts/meme_generator.coffee
  46. +139 −0 src/scripts/mite.coffee
  47. +18 −0 src/scripts/nettipot.coffee
  48. +43 −0 src/scripts/news.coffee
  49. +21 −0 src/scripts/octocat.coffee
  50. +50 −0 src/scripts/ping.coffee
  51. +28 −0 src/scripts/pivotal.coffee
  52. +34 −0 src/scripts/pivotalstorylisten.coffee
  53. +161 −0 src/scripts/play.coffee
  54. +29 −6 src/scripts/polite.coffee
  55. +20 −0 src/scripts/punchfork.coffee
  56. +45 −0 src/scripts/pypi.coffee
  57. +20 −0 src/scripts/quote.coffee
  58. +1 −0  src/scripts/redis-brain.coffee
  59. 0  src/scripts/remind.coffee
  60. +29 −0 src/scripts/resque.coffee
  61. +15 −0 src/scripts/rimshot.coffee
  62. +29 −0 src/scripts/roll.coffee
  63. +5 −3 src/scripts/rubygems.coffee
  64. +77 −0 src/scripts/salesforce.coffee
  65. +83 −0 src/scripts/script.coffee
  66. +3 −1 src/scripts/shipit.coffee
  67. +16 −0 src/scripts/shorten.coffee
  68. +63 −0 src/scripts/sonos.coffee
  69. +51 −0 src/scripts/sosearch.coffee
  70. +25 −0 src/scripts/store-messages-couchdb.coffee
  71. +81 −0 src/scripts/talkative.coffee
  72. +23 −10 src/scripts/teamcity.coffee
  73. +21 −0 src/scripts/tell.coffee
  74. +60 −0 src/scripts/tvshow.coffee
  75. +9 −0 src/scripts/tweet-content.coffee
  76. +11 −0 src/scripts/twitter.coffee
  77. +7 −0 src/scripts/twss.coffee
  78. +49 −0 src/scripts/uptime-robot.coffee
  79. +36 −0 src/scripts/uptime.coffee
  80. +21 −0 src/scripts/urban.coffee
  81. 0  src/scripts/vanity.coffee
  82. +16 −0 src/scripts/walmart.coffee
  83. +46 −30 src/scripts/weather.coffee
  84. +51 −0 src/scripts/web.coffee
  85. +19 −0 src/scripts/webshot.coffee
  86. +10 −0 src/scripts/whos-turn.coffee
  87. +102 −0 src/scripts/wikipedia.coffee
  88. +5 −4 src/scripts/wolfram.coffee
  89. +67 −0 src/scripts/word-of-the-day.coffee
  90. +58 −24 src/scripts/wordnik.coffee
  91. +0 −6 src/scripts/xkcd.coffee
  92. +31 −0 test/scripts/isup_test.coffee
  93. +3 −2 test/tests.coffee
View
27 CHANGELOG.md
@@ -1,3 +1,30 @@
+v1.1.8
+======
+* allow people to start tracks - Zach Holman <github.com@zachholman.com>
+* simplify local development - atmos
+
+v1.1.7
+======
+* urban dictionary definitions - Kevin Traver <kevintraver@gmail.com>
+* latest tweet from a user - Kevin Traver <kevintraver@gmail.com>
+* bit.ly shortening - Kevin Traver <kevintraver@gmail.com>
+* Hubot Play support - Zach Holman <github.com@zachholman.com>
+* tvshow information - Victor Butler <victorbutler@gmail.com>
+* JIRA issue support - crcastle <crcastle@gmail.com>
+* lulz scripts can lulzbomb now - atmos
+* gaug.es support - Tom Bell <tomb@tombell.org.uk>
+* google reader support - Ben Ubois <ben@benubois.com>
+* faceup script enhancements - Joe Ekiert <joe.ekiert@gmail.com>
+* get a job support - Craig Slusher <cslush@gmail.com>
+* website uptime support - lukesmith <stuff@lukesmith.net>
+* me gusta support - John-Michael Glenn <phyre19@gmail.com>
+* coderwall support - Arlo Carreon <arlo.carreon@gmail.com>
+* cheer me up script - Carl Lerche <me@carllerche.com>
+* google news scripts - Matt McCormick <mbmccormick@gmail.com>
+* various meme generator enhancements - a lot of people :)
+* moar shipit images - Raphael Crawford-Marks <raphael.crawfordmarks@gmail.com>
+
+
v1.1.4
======
* hubot define me - Pete Nicholls <pete@metanation.com>
View
6 bin/hubot
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+npm install
+export PATH="node_modules/.bin:node_modules/hubot/node_modules/.bin:$PATH"
+
+exec node_modules/.bin/hubot "$@"
View
4 package.json 100755 → 100644
@@ -30,7 +30,7 @@
"underscore": ">= 1.2.1",
"underscore.string": ">= 1.1.6",
"cradle": "0.5.7",
- "clark": "*",
+ "clark": ">= 0.0.3",
"scribe-node": "0.0.20"
},
@@ -38,4 +38,4 @@
"lib": "./src"
},
"engine": "node >= 0.4.1 < 0.5.0"
-}
View
55 src/scripts/abstract.coffee
@@ -0,0 +1,55 @@
+# abstract <topic> - Prints a nice abstract of the given topic.
+
+# Copyright (c) 2011 John Tantalo
+#
+# 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.
+
+module.exports = (robot) ->
+ robot.respond /(abs|abstract) (.+)/i, (res) ->
+ abstract_url = "http://api.duckduckgo.com/?format=json&q=#{encodeURIComponent(res.match[2])}"
+ res.http(abstract_url)
+ .header('User-Agent', 'Hubot Abstract Script')
+ .get() (err, _, body) ->
+ return res.send "Sorry, the tubes are broken." if err
+ data = JSON.parse(body.toString("utf8"))
+ return unless data
+ topic = data.RelatedTopics[0] if data.RelatedTopics and data.RelatedTopics.length
+ if data.AbstractText
+ # hubot abs numerology
+ # Numerology is any study of the purported mystical relationship between a count or measurement and life.
+ # http://en.wikipedia.org/wiki/Numerology
+ res.send data.AbstractText
+ res.send data.AbstractURL if data.AbstractURL
+ else if topic and not /\/c\//.test(topic.FirstURL)
+ # hubot abs astronomy
+ # Astronomy is the scientific study of celestial objects.
+ # http://duckduckgo.com/Astronomy
+ res.send topic.Text
+ res.send topic.FirstURL
+ else if data.Definition
+ # hubot abs contumacious
+ # contumacious definition: stubbornly disobedient.
+ # http://merriam-webster.com/dictionary/contumacious
+ res.send data.Definition
+ res.send data.DefinitionURL if data.DefinitionURL
+ else
+ res.send "I don't know anything about that."
View
2  src/scripts/achievement_unlocked.coffee
@@ -4,7 +4,7 @@
module.exports = (robot) ->
robot.hear /achievement (get|unlock(ed)?) (.+?)(\s*[^@\s]+@[^@\s]+)?\s*$/i, (msg) ->
caption = msg.match[3]
- email = msg.match[4]
+ email = msg.match[4] || msg.message.user.email_address
url = "http://achievement-unlocked.heroku.com/xbox/#{escape(caption)}.png"
if email
url += "?email=#{escape(email.trim())}.png"
View
30 src/scripts/animal.coffee
@@ -0,0 +1,30 @@
+# Because animals are animals.
+#
+# animal me - Grab a random gif from http://animalsbeingdicks.com/
+#
+Select = require("soupselect").select
+HtmlParser = require "htmlparser"
+
+module.exports = (robot) ->
+ robot.respond /animal me/i, (msg) ->
+ randimalMe msg, (url) ->
+ msg.send url
+
+randimalMe = (msg, cb) ->
+ msg.http("http://animalsbeingdicks.com/random")
+ .get() (err, res, body) ->
+ console.log res.headers.location
+ animalMe msg, res.headers.location, (location) ->
+ cb location
+
+animalMe = (msg, location, cb) ->
+ msg.http(location)
+ .get() (err, res, body) ->
+ handler = new HtmlParser.DefaultHandler()
+ parser = new HtmlParser.Parser handler
+
+ parser.parseComplete body
+ img = Select handler.dom, "#content .post .entry img"
+
+ console.log img
+ cb img[0].attribs.src
View
6 src/scripts/bij.coffee
@@ -0,0 +1,6 @@
+# EXPERIENCE BIJ
+#
+
+module.exports = (robot) ->
+ robot.hear /bij/i, (msg) ->
+ msg.send "EXPERIENCE BIJ!"
View
19 src/scripts/cheer.coffee
@@ -0,0 +1,19 @@
+# Feeling depressed?
+#
+# cheer me up - A little pick me up
+module.exports = (robot) ->
+ robot.respond /cheer me up/i, (msg) ->
+ aww msg
+
+ robot.hear /i( am|'m) emo/i, (msg) ->
+ msg.send "Let me cheer you up."
+ aww msg
+
+aww = (msg) ->
+ msg
+ .http('http://imgur.com/r/aww.json')
+ .get() (err, res, body) ->
+ images = JSON.parse(body)
+ images = images.gallery
+ image = msg.random images
+ msg.send "http://i.imgur.com/#{image.hash}#{image.ext}"
View
36 src/scripts/chm.coffee
@@ -0,0 +1,36 @@
+# Shows a short history lesson of the day from the Computer History Museum.
+#
+# today in computer history|tdih|chm - Displays the content from the This Day in History page on the Computer History Museum site.
+#
+Select = require("soupselect").select
+HtmlParser = require "htmlparser"
+
+module.exports = (robot) ->
+ robot.respond /(today in computer history|tdih|chm)$/i, (msg) ->
+ msg.http("http://www.computerhistory.org/tdih/")
+ .get() (err, res, body) ->
+ handler = new HtmlParser.DefaultHandler()
+ parser = new HtmlParser.Parser handler
+ parser.parseComplete body
+
+ contentEl = Select handler.dom, ".tdihevent p"
+ return unless contentEl
+ msg.send date(handler)
+ msg.send title(contentEl)
+ for sentence in blurbSentences(contentEl)
+ msg.send sentence + '.' if sentence and sentence isnt ""
+
+title = (contentEl) ->
+ trim contentEl[0].children[0].raw
+
+blurbSentences = (contentEl) ->
+ blurb = trim contentEl[1].children[0].raw
+ blurb.split('.')
+
+date = (handler) ->
+ dateEl = Select handler.dom, ".title"
+ return "" unless dateEl
+ trim dateEl[0].children[0].raw
+
+trim = (string) ->
+ return string.replace(/^\s*|\s*$/g, '')
View
9 src/scripts/clark.coffee
@@ -0,0 +1,9 @@
+# clark - build sparklines out of data
+
+clark = require('clark').clark
+
+module.exports = (robot) ->
+ robot.respond /clark (.*)/i, (msg) ->
+ data = msg.match[1].trim().split(' ')
+ msg.send(clark(data))
+
View
24 src/scripts/coderwall.coffee
@@ -0,0 +1,24 @@
+# Messing around with the Coderwall API.
+#
+# coderwall <coderwall username> - Returns coder achievements from coderwall.com
+#
+module.exports = (robot) ->
+ robot.respond /(coderwall)( me)? (.*)/i, (msg) ->
+ user = msg.match[3]
+ msg.http("http://coderwall.com/"+user+".json")
+ .get() (err, res, body) ->
+ # If not response bad username
+ if res.headers['content-length'] <= 1
+ letter_s = if user.substr(-1)=='s' then '' else 's'
+ msg.send "Sorry I cannot find "+user+"'"+letter_s+" coderwall"
+ # Else return the coder badges
+ else
+ profile = JSON.parse(body)
+ # Give an intro to the coderwall profile
+ resp_str = "";
+ resp_str += user + "'s coderwall -> http://coderwall.com/"+user + "\n"
+ # Iterate all badges and continue building string
+ profile.badges.forEach (badge) ->
+ resp_str += badge.name + " - " + badge.description + "\n"
+ # Return response
+ msg.send resp_str
View
10 src/scripts/coin.coffee
@@ -0,0 +1,10 @@
+# Help decide between two things
+#
+# throw a coin - Gives you heads or tails
+#
+
+thecoin = ["heads", "tails"]
+
+module.exports = (robot) ->
+ robot.respond /(throw|flip|toss) a coin/i, (msg) ->
+ msg.reply msg.random thecoin
View
9 src/scripts/commitmessage.coffee
@@ -0,0 +1,9 @@
+# Get a random commit message
+#
+# commit message - Displays a random commit message
+
+module.exports = (robot) ->
+ robot.respond /commit message/i, (msg) ->
+ msg.http("http://whatthecommit.com/index.txt")
+ .get() (err, res, body) ->
+ msg.reply body
View
63 src/scripts/conversation.coffee
@@ -0,0 +1,63 @@
+# Extends robot adding conversation features
+
+
+module.exports = (robot) ->
+ robot.eatListeners = {}
+
+ # Public: Adds a Listener that receives the next message from the user and av
+ # further processing of it.
+ #
+ # user - The user name.
+ # callback - A Function that is called with a Response object. msg.match[1] w
+ # contain the message text without the bot name
+ #
+ # Returns nothing.
+ robot.eatOneResponse = (user, callback) ->
+ robot.eatListeners[user.id] = new Listener(robot, callback)
+
+
+ # Change default receive command, addind processing of eatListeners
+ robot.origReceive = robot.receive
+ robot.receive = (message) ->
+ if robot.eatListeners[message.user.id]?
+ lst = robot.eatListeners[message.user.id]
+ delete robot.eatListeners[message.user.id]
+
+ if lst.call message
+ return
+
+ # Put back to process next message
+ robot.eatListeners[message.user.id] = lst
+
+ robot.origReceive(message)
+
+
+ # Public: Waits for the next message from the current user.
+ #
+ # callback - Called with the user response
+ #
+ # Returns nothing.
+ robot.Response.prototype.waitResponse = (callback) ->
+ robot.eatOneResponse this.message.user, callback
+
+
+
+class Listener
+ constructor: (@robot, @callback) ->
+ if robot.enableSlash
+ @regex = new RegExp("^(?:\/|#{robot.name}:?)\\s*(.*?)\\s*$", 'i')
+ else
+ @regex = new RegExp("^#{robot.name}:?\\s*(.*?)\\s*$", 'i')
+
+ @matcher = (message) =>
+ if message.text?
+ message.text.match @regex
+
+ call: (message) =>
+ if match = @matcher message
+ @callback new @robot.Response(@robot, message, match)
+ return true
+ else
+ return false
+
+
View
11 src/scripts/cowsay.coffee
@@ -0,0 +1,11 @@
+# Cowsay.
+#
+# cowsay <statement> - Returns a cow that says what you want.
+
+module.exports = (robot) ->
+ robot.respond /cowsay( me)? (.*)/i, (msg) ->
+ msg
+ .http("http://cowsay.morecode.org/say")
+ .query(format: 'text', message: msg.match[2])
+ .get() (err, res, body) ->
+ msg.send body
View
65 src/scripts/deadline.coffee
@@ -0,0 +1,65 @@
+# Tracks when stuff is due.
+#
+# deadlines - List what you have due
+# add deadline 2011-10-30 Thing - Add a deadline for October 10, 2011
+# remove deadline Thing - Remove a deadline named "Thing"
+# clear deadlines - Remove all the deadlines
+#
+# Written by @jmhobbs
+
+module.exports = (robot) ->
+
+ robot.respond /(create|add|set) deadline (\d\d\d\d-\d\d-\d\d) (.*) ?$/i, (msg) ->
+ due = msg.match[2]
+ what = msg.match[3]
+
+ robot.brain.data.deadlines ?= []
+
+ robot.brain.data.deadlines.push { what: what, due: due }
+ msg.send 'Got it! "' + what + '" is due on ' + due
+
+ robot.respond /(clear|flush) deadlines/i, (msg) ->
+ robot.brain.data.deadlines = []
+ msg.send "Deadlines cleared. Go do whatever you want."
+
+ robot.respond /(delete|remove|complete) deadline (.*) ?$/i, (msg) ->
+ what = msg.match[2]
+
+ robot.brain.data.deadlines ?= []
+
+ length_before = robot.brain.data.deadlines.length
+
+ index_of = -1
+ for deadline, i in robot.brain.data.deadlines
+ if deadline.what == what
+ index_of = i
+
+ robot.brain.data.deadlines.splice( index_of, 1 ) if -1 != index_of
+
+ if length_before > robot.brain.data.deadlines.length
+ msg.send 'Removed deadline "' + what + '", nice job!'
+ else
+ msg.send 'I couldn\'t find that deadline.'
+
+
+ robot.respond /deadlines/i, (msg) ->
+ robot.brain.data.deadlines ?= []
+
+ if robot.brain.data.deadlines.length > 0
+ deadlines = robot.brain.data.deadlines.map (deadline) ->
+ today = new Date()
+ due_date = new Date( deadline.due )
+ days_passed = Math.round( ( due_date.getTime() - today.getTime() ) / 86400000 )
+
+ interval_string = days_passed + ' days left'
+ interval_string = ( -1 * days_passed ) + ' days overdue' if days_passed < 0
+ interval_string = 'due today' if days_passed == 0
+
+ '"' + deadline.what + '" is due on ' + deadline.due + ' (' + interval_string + ')'
+
+ msg.send "Here are your upcoming deadlines:\n\n" + deadlines.join "\n"
+
+ else
+ msg.send "I'm not currently tracking any deadlines. Why don't you add one?"
+
+
View
27 src/scripts/demolition-man.coffee
@@ -0,0 +1,27 @@
+# Watch your language!
+
+module.exports = (robot) ->
+
+ words = [
+ 'arse',
+ 'ass',
+ 'bastard',
+ 'bugger',
+ 'bollocks',
+ 'bullshit',
+ 'cock',
+ 'cunt',
+ 'damn',
+ 'dick',
+ 'douche',
+ 'fag',
+ 'fuck',
+ 'fucked',
+ 'piss',
+ 'shit',
+ 'wank'
+ ]
+ regex = new RegExp('(?:^|\\s)(' + words.join('|') + ')(?:\\s|\\.|\\?|!|$)', 'i');
+
+ robot.hear regex, (msg) ->
+ msg.send 'You have been fined one credit for a violation of the verbal morality statute.'
View
15 src/scripts/destiny.coffee
@@ -0,0 +1,15 @@
+# Is it the day ?
+#
+# is it <action> day ? - Returns if it's the day for your action.
+#
+module.exports = (robot) ->
+ robot.respond /is it (\w+) day \?/i, (msg) ->
+ action = msg.match[1]
+ nbDay = Math.floor(new Date().getTime() / 1000 / 86400)
+ actionHash = action.length + action.charCodeAt(0) + action.charCodeAt(action.length - 1)
+ destiny = Math.cos(nbDay + actionHash) + 1
+ limit = (Math.sin(actionHash) + 1) / 2
+ if destiny > limit
+ msg.send "Sorry, it's not " + action + " day. But try tomorrow..."
+ else
+ msg.send "Yes, it's " + action + " day !"
View
46 src/scripts/dialectizer.coffee
@@ -0,0 +1,46 @@
+# Allows Hubot to translate text into various dialects.
+#
+# dialectize|dialect|dia <dialect>|help <text> - Translates the given text into the given dialect.
+#
+Select = require("soupselect").select
+HtmlParser = require "htmlparser"
+
+#TODO: get this dynamically?
+dialects = ["redneck", "jive", "cockney", "fudd", "bork", "moron", "piglatin", "hckr", "censor"]
+
+module.exports = (robot) ->
+ robot.respond /(?:dialectize|dialect|dia) (\w+)(.*)/i, (msg) ->
+ [dialect, text] = msg.match[1..2]
+ if dialect in ["help", "h"]
+ showHelp(msg)
+ return
+
+ return unless text
+ trim text
+ return unless text.length > 0
+
+ if dialect in ["all", "a"]
+ showDialectizedText(msg, dialect, text, true) for dialect in dialects
+ return
+ else if dialect is "hacker"
+ dialect = "hckr"
+ showDialectizedText(msg, dialect, text, false)
+
+showDialectizedText = (msg, dialect, text, showPrefix) ->
+ msg.http("http://www.rinkworks.com/dialect/dialectt.cgi?dialect=" + encodeURIComponent(dialect) + "&text=" + encodeURIComponent(text))
+ .get() (err, res, body) ->
+ handler = new HtmlParser.DefaultHandler()
+ parser = new HtmlParser.Parser handler
+ parser.parseComplete body
+ result = Select handler.dom, ".dialectized_text p"
+ return unless result
+ dialectizedText = trim result[0].children[0].raw
+ dialectizedText = "#{dialect}: " + dialectizedText if showPrefix
+ msg.send dialectizedText
+
+showHelp = (msg) ->
+ msg.send "Dialects: " + dialects.join(", ")
+
+trim = (string) ->
+ return string.replace(/^\s*|\s*$/g, '')
+
View
38 src/scripts/dice.coffee
@@ -0,0 +1,38 @@
+# Allows Hubot to roll dice.
+#
+# roll dice - Roll two six-sided dice.
+#
+# roll <x>d<y> - roll x dice, each of which has y sides.
+
+module.exports = (robot) ->
+ robot.respond /roll dice/i, (msg) ->
+ msg.reply report roll 2, 6
+ robot.respond /roll (\d+)d(\d+)/i, (msg) ->
+ dice = parseInt msg.match[1]
+ sides = parseInt msg.match[2]
+ answer = if sides < 1
+ "I don't know how to roll a zero-sided die."
+ else if dice > 100
+ "I'm not going to roll more than 100 dice for you."
+ else
+ report roll dice, sides
+ msg.reply answer
+
+report = (results) ->
+ if results?
+ switch results.length
+ when 0
+ "I didn't roll any dice."
+ when 1
+ "I rolled a #{results[0]}."
+ else
+ total = results.reduce (x, y) -> x + y
+ finalComma = if (results.length > 2) then "," else ""
+ last = results.pop()
+ "I rolled #{results.join(", ")}#{finalComma} and #{last}, making #{total}."
+
+roll = (dice, sides) ->
+ rollOne(sides) for i in [0...dice]
+
+rollOne = (sides) ->
+ 1 + Math.floor(Math.random() * sides)
View
19 src/scripts/dilbert.coffee
@@ -0,0 +1,19 @@
+htmlparser = require "htmlparser"
+
+module.exports = (robot) ->
+ robot.respond /(show( me)?|fetch ( me)? )?dilbert/i, (msg) ->
+ dilbertRss msg, (url) ->
+ msg.send url
+
+dilbertRegexp = /img src="(http:\/\/dilbert.com\/[^"]+)"/i
+dilbertRss = (msg, cb) ->
+ msg.http('http://feed.dilbert.com/dilbert/daily_strip?format=xml')
+ .get() (err, resp, body) ->
+ handler = new htmlparser.RssHandler (error, dom) ->
+ return if error || !dom
+ item = dom.items[0]
+ match = item.description.match(dilbertRegexp)
+ cb match[1] if match
+
+ parser = new htmlparser.Parser(handler)
+ parser.parseComplete(body)
View
5 src/scripts/disassemble.coffee
@@ -0,0 +1,5 @@
+# disassemble - NO DISASSEMBLE
+
+module.exports = (robot) ->
+ robot.hear /disassemble/i, (msg) ->
+ msg.send 'NO DISASSEMBLE!'
View
2  src/scripts/eight-ball.coffee
@@ -28,6 +28,6 @@ ball = [
module.exports = (robot) ->
robot.respond /(eightball|8ball)(.*)/i, (msg) ->
- msg.send msg.random ball
+ msg.reply msg.random ball
View
26 src/scripts/faceup.coffee
@@ -0,0 +1,26 @@
+# Overlay funny things on people's faces
+#
+# hipster me <img> - Overlay hipster glasses on a face.
+# clown me <img> - Overlay a clown nose on a face.
+# scumbag me <img> - Overlay a scumbag on a face.
+# jason me <img> - Overlay a jason on a face.
+
+module.exports = (robot) ->
+ robot.respond /(hipster|clown|scumbag|rohan|jason)( me)? (.*)/i, (msg) ->
+ type = msg.match[1]
+ imagery = msg.match[3]
+
+ if imagery.match /^https?:\/\//i
+ msg.send "http://faceup.me/img?overlay=#{type}&src=#{imagery}"
+ else
+ imageMe msg, imagery, (url) ->
+ msg.send "http://faceup.me/img?overlay=#{type}&src=#{url}"
+
+imageMe = (msg, query, cb) ->
+ msg.http('http://ajax.googleapis.com/ajax/services/search/images')
+ .query(v: "1.0", rsz: '8', q: query)
+ .get() (err, res, body) ->
+ images = JSON.parse(body)
+ images = images.responseData.results
+ image = msg.random images
+ cb "#{image.unescapedUrl}"
View
31 src/scripts/fibonacci.coffee
@@ -0,0 +1,31 @@
+# Calculate the nth fibonacci number. #webscale.
+#
+# From https://gist.github.com/1032685
+#
+# fibonacci me <integer> - Calculate Nth Fibonacci number
+
+fib_bits = (n) ->
+ # Represent an integer as an array of binary digits.
+ bits = []
+ while n > 0
+ [n, bit] = divmodBasic(n, 2)
+ bits.push(bit)
+ return bits.reverse()
+
+fibFast = (n) ->
+ [a, b, c] = [1, 0, 1]
+
+ for bit in fib_bits(n)
+ if bit
+ [a, b] = [(a+c)*b, b*b + c*c]
+ else
+ [a, b] = [a*a + b*b, (a+c)*b]
+ c = a + b
+ return b
+
+divmodBasic = (x, y) ->
+ return [(q = Math.floor(x/y)), (r = if x < y then x else x % y)]
+
+module.exports = (robot) ->
+ robot.hear /fibonacci me (\d+)/i, (msg) ->
+ msg.send fibFast(msg.match[1]).toString()
View
9 src/scripts/fortune.coffee
@@ -0,0 +1,9 @@
+# Get a fortune
+#
+# fortune me - Displays a super true fortune
+
+module.exports = (robot) ->
+ robot.respond /(fortune)( me)?/i, (msg) ->
+ msg.http('http://www.fortunefortoday.com/getfortuneonly.php')
+ .get() (err, res, body) ->
+ msg.reply body
View
99 src/scripts/gauges.coffee
@@ -0,0 +1,99 @@
+# Allows Hubot to fetch statistics from Gaug.es
+#
+# gauges for (today|yesterday) - Get views/people from today or yesterday.
+#
+# gauges for YYYY-MM-DD - Get views/people for the specified date.
+#
+
+class Gauges
+ constructor: (@robot, @token) ->
+
+ getViewsForToday: (callback) ->
+ @getGauges (err, data) ->
+ if err? or not data?
+ callback err
+ else
+ gauges = []
+ for g in data.gauges
+ gauges.push
+ title: g.title
+ views: g.today.views
+ people: g.today.people
+
+ callback null, gauges
+
+ getViewsForYesterday: (callback) ->
+ @getGauges (err, data) ->
+ if err? or not data?
+ callback err
+ else
+ gauges = []
+ for g in data.gauges
+ gauges.push
+ title: g.title
+ views: g.yesterday.views
+ people: g.yesterday.people
+
+ callback null, gauges
+
+ getViewsForDate: (date, callback) ->
+ @getGauges (err, data) ->
+ if err? or not data?
+ callback err
+ else
+ gauges = []
+ for g in data.gauges
+ for d in g.recent_days
+ if d.date is date
+ gauges.push
+ title: g.title
+ views: d.views
+ people: d.people
+
+ callback null, gauges
+
+ getGauges: (callback) ->
+ @robot.http("https://secure.gaug.es/gauges")
+ .headers("X-Gauges-Token": @token)
+ .get() (err, res, body) ->
+ if res.statusCode is 200
+ data = JSON.parse body
+ callback null, data
+ else if err?
+ callback err
+ else
+ callback "Could not get gauges for today"
+
+module.exports = (robot) ->
+
+ robot.respond /gauges for (today|yesterday)/i, (msg) ->
+ gauges = new Gauges robot, process.env.HUBOT_GAUGES_TOKEN
+ day = msg.match[1]
+
+ switch day
+ when "today"
+ gauges.getViewsForToday (err, gauges) ->
+ if err?
+ msg.send "#{err}"
+ else
+ for g in gauges
+ msg.send "#{g.title}: Views #{g.views}, People #{g.people}"
+
+ when "yesterday"
+ gauges.getViewsForYesterday (err, gauges) ->
+ if err?
+ msg.send "#{err}"
+ else
+ for g in gauges
+ msg.send "#{g.title}: Views #{g.views} People #{g.people}"
+
+ robot.respond /gauges for (\d{4}-\d{2}-\d{2})/i, (msg) ->
+ gauges = new Gauges robot, process.env.HUBOT_GAUGES_TOKEN
+ day = msg.match[1]
+
+ gauges.getViewsForDate day, (err, gauges) ->
+ if err?
+ msg.send "#{err}"
+ else
+ for g in gauges
+ msg.send "#{g.title}: Views #{g.views} People #{g.people}"
View
33 src/scripts/getajob.coffee
@@ -0,0 +1,33 @@
+# Search for a job and profit!
+#
+# find me a <technology> job in <location>
+
+module.exports = (robot) ->
+ robot.respond /find me a (.* )?job( in (.+))?/i, (msg) ->
+ [keywords, location] = [msg.match[1], msg.match[3]]
+
+ params =
+ api_key: process.env.HUBOT_AUTHENTIC_JOBS_API_KEY
+ method: "aj.jobs.search"
+ perpage: 100
+ format: "json"
+
+ params.keywords = keywords if keywords?
+ params.location = location if location?
+
+ msg
+ .http("http://www.authenticjobs.com/api/")
+ .query(params)
+ .get() (err, res, body) ->
+ response = JSON.parse body
+ msg.send get_a_job msg, response
+
+get_a_job = (msg, response) ->
+ listings = response.listings.listing
+
+ if not listings.length
+ return "Sorry, I couldn't find you a job. Guess you're going to be broke for a while!"
+
+ random_listing = msg.random listings
+
+ "#{random_listing.title} at #{random_listing.company.name}. Apply at #{random_listing.apply_url or random_listing.apply_email}"
View
37 src/scripts/github-credentials.coffee
@@ -0,0 +1,37 @@
+# Github Credentials allows you to map your user against your GitHub user.
+# This is specifically in order to work with apps that have GitHub Oauth users.
+#
+# who do you know - List all the users with github logins tracked by Hubot
+# i am `maddox` - map your user to the github login `maddox`
+# who am i - reveal your mapped github login
+# forget me - de-map your user to your github login
+
+module.exports = (robot) ->
+
+ robot.respond /who do you know/i, (msg) ->
+ theReply = "Here is who I know:\n"
+
+ for own key, user of robot.brain.data.users
+ if(user.githubLogin)
+ theReply += user.name + " is " + user.githubLogin + "\n"
+
+ msg.send theReply
+
+ robot.respond /i am (\w+)/i, (msg) ->
+ githubLogin = msg.match[1]
+ msg.message.user.githubLogin = githubLogin
+ msg.send "Ok, you are " + githubLogin + " on GitHub"
+
+ robot.respond /who am i/i, (msg) ->
+ user = msg.message.user
+ if user.githubLogin
+ msg.reply "You are known as " + user.githubLogin + " on GitHub"
+ else
+ msg.reply "I don't know who you are. You should probably identify yourself with your GitHub login"
+
+ robot.respond /forget me/i, (msg) ->
+ user = msg.message.user
+ user.githubLogin = null
+
+ msg.reply("Ok, I have no idea who you are anymore.")
+
View
54 src/scripts/google-reader.coffee
@@ -0,0 +1,54 @@
+# Subscribe to a feed in Google Reader, requires you set
+# GOOGLE_USERNAME & GOOGLE_PASSWORD environment variables
+#
+# subscribe <domainname> - returns whether you've subscribed succesfully
+#
+
+module.exports = (robot) ->
+ robot.hear /subscribe (.*)/i, (msg) ->
+ domain = msg.match[1]
+ getAuth msg, (auth) ->
+ getToken msg, auth, (token) ->
+ readerSubscribe msg, auth, token, domain
+
+getAuth = (msg, cb) ->
+ user = process.env.GOOGLE_USERNAME
+ pass = process.env.GOOGLE_PASSWORD
+ msg.http("https://www.google.com/accounts/ClientLogin")
+ .query
+ "service": "reader"
+ "Email": user
+ "Passwd": pass
+ .get() (err, res, body) ->
+ switch res.statusCode
+ when 200
+ cb body.match(/Auth=(.*)/)[1]
+ when 403
+ msg.send "You need to authenticate by setting the GOOGLE_USERNAME & GOOGLE_PASSWORD environment variables"
+ else
+ msg.send "Unable to get auth token, request returned with the status code: #{res.statusCode}"
+
+getToken = (msg, auth, cb) ->
+ msg.http('http://www.google.com/reader/api/0/token')
+ .headers
+ "Content-type": "application/x-www-form-urlencoded"
+ "Authorization": "GoogleLogin auth=#{auth}"
+ .get() (err, res, body) ->
+ cb body
+
+readerSubscribe = (msg, auth, token, domain) ->
+ msg.http('http://www.google.com/reader/api/0/subscription/quickadd?client=scroll')
+ .query
+ "quickadd": domain
+ "ac": 'subscribe'
+ "T": token
+ .headers
+ "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
+ "Content-Length": '0'
+ "Authorization": "GoogleLogin auth=#{auth}"
+ .post() (err, res, body) ->
+ switch res.statusCode
+ when 200
+ msg.send "You are now subscribing to #{domain}"
+ else
+ msg.send "Unable to subscribe, request returned with the status code: #{res.statusCode}"
View
1  src/scripts/haters.coffee
@@ -9,6 +9,7 @@ haters = [
, "http://jesad.com/img/life/haters-gonna-hate/haters-gonna-hate01.jpg"
, "http://i671.photobucket.com/albums/vv78/Sinsei55/HatersGonnaHatePanda.jpg"
, "http://24.media.tumblr.com/tumblr_lltwmdVpoL1qekprfo1_500.gif"
+, "http://s3.amazonaws.com/kym-assets/photos/images/newsfeed/000/087/536/1292102239519.gif"
]
module.exports = (robot) ->
robot.respond /haters/i, (msg) ->
View
21 src/scripts/isup.coffee
@@ -0,0 +1,21 @@
+# Uses downforeveryoneorjustme.com to check if a site is up.
+#
+# is <domain> up? - Checks if <domain> is up
+#
+# Written by @jmhobbs
+
+module.exports = (robot) ->
+ robot.respond /is (.*?) (up|down)(\?)?/i, (msg) ->
+ isUp msg, msg.match[1], (domain) ->
+ msg.send domain
+
+isUp = (msg, domain, cb) ->
+ msg.http('http://www.isup.me/' + domain)
+ .get() (err, res, body) ->
+ if body.match("It's just you.")
+ cb "#{domain} looks UP from here."
+ else if body.match("It's not just you!")
+ cb "#{domain} looks DOWN from here."
+ else
+ cb "Not sure, #{domain} returned an error."
+
View
34 src/scripts/jenkins.coffee
@@ -0,0 +1,34 @@
+# Interact with your jenkins CI server, assumes you have a parameterized build
+# with the branch to build as a parameter
+#
+# You need to set the following variables:
+# HUBOT_JENKINS_URL = "http://ci.example.com:8080"
+#
+# The following variables are optional
+# HUBOT_JENKINS_JOB - if not set you will have to specify job name every time
+# HUBOT_JENKINS_BRANCH_PARAMETER_NAME - if not set is assumed to be BRANCH_SPECIFIER
+#
+# build branch master -- starts a build for branch origin/master
+# build branch master on job Foo -- starts a build for branch origin/master on job Foo
+module.exports = (robot) ->
+ robot.respond /build\s*(branch\s+)?(\w+\/?\w+)(\s+(on job)?\s*(\w+))?/i, (msg)->
+
+ url = process.env.HUBOT_JENKINS_URL
+
+ job = msg.match[5] || process.env.HUBOT_JENKINS_JOB
+ job_parameter = process.env.HUBOT_JENKINS_BRANCH_PARAMETER_NAME || "BRANCH_SPECIFIER"
+
+ branch = msg.match[2]
+ branch = "origin/#{branch}" unless ~branch.indexOf("/")
+
+ json_val = JSON.stringify {"parameter": [{"name": job_parameter, "value": branch}]}
+ msg.http("#{url}/job/#{job}/build")
+ .query(json: json_val)
+ .post() (err, res, body) ->
+ if err
+ msg.send "Jenkins says: #{err}"
+ else if res.statusCode == 302
+ msg.send "Build started for #{branch}! #{res.headers.location}"
+ else
+ msg.send "Jenkins says: #{body}"
+
View
150 src/scripts/jira.coffee
@@ -0,0 +1,150 @@
+# Messing with the JIRA REST API
+#
+# <Project Key>-<Issue ID> - Displays information about the ticket (if it exists)
+# show watchers for <Issue Key> - Shows watchers for the given issue
+# search for <JQL> - Search JIRA with JQL
+# save filter <JQL> as <name> - Save JQL as filter in the brain
+# use filter - Use a filter from the brain
+# show filter(s) - Show all filters
+# show filter <name> - Show a specific filter
+
+class IssueFilters
+ constructor: (@robot) ->
+ @cache = []
+
+ @robot.brain.on 'loaded', =>
+ @cache = @robot.brain.data.jqls
+
+ add: (filter) ->
+ @cache.push filter
+ @robot.brain.data.jqls = @cache
+
+ delete: (name) ->
+ result = []
+ @cache.forEach (filter) ->
+ if filter.name isnt name
+ result.push filter
+
+ @cache = result
+ @robot.brain.data.jqls = @cache
+
+ get: (name) ->
+ result = null
+
+ @cache.forEach (filter) ->
+ if filter.name is name
+ result = filter
+
+ result
+ all: ->
+ return @cache
+
+class IssueFilter
+ constructor: (@name, @jql) ->
+ return {name: @name, jql: @jql}
+
+module.exports = (robot) ->
+ filters = new IssueFilters robot
+
+ get = (msg, where, cb) ->
+ console.log(process.env.HUBOT_JIRA_URL + "/rest/api/latest/" + where)
+ authdata = new Buffer(process.env.HUBOT_JIRA_USER+':'+process.env.HUBOT_JIRA_PASSWORD).toString('base64')
+
+ msg.http(process.env.HUBOT_JIRA_URL + "/rest/api/latest/" + where).
+ header('Authorization', 'Basic ' + authdata).
+ get() (err, res, body) ->
+ cb JSON.parse(body)
+
+ watchers = (msg, issue, cb) ->
+ get msg, "issue/#{issue}/watchers", (watchers) ->
+ if watchers.errors?
+ return
+
+ cb watchers.watchers.map((watcher) -> return watcher.displayName).join(", ")
+
+ info = (msg, issue, cb) ->
+ get msg, "issue/#{issue}", (issues) ->
+ if issues.errors?
+ return
+
+ issue =
+ key: issues.key
+ summary: issues.fields.summary.value
+ assignee: ->
+ if issues.fields.assignee.value != undefined
+ issues.fields.assignee.value.displayName
+ else
+ "no assignee"
+ status: issues.fields.status.value.name
+ fixVersion: ->
+ if issues.fields.fixVersions? and issues.fields.fixVersions.value != undefined
+ issues.fields.fixVersions.value.map((fixVersion) -> return fixVersion.name).join(", ")
+ else
+ "no fix version"
+ url: process.env.HUBOT_JIRA_URL + '/browse/' + issues.key
+
+ cb "[#{issue.key}] #{issue.summary}. #{issue.assignee()} / #{issue.status}, #{issue.fixVersion()} <#{issue.url}>"
+
+ search = (msg, jql, cb) ->
+ get msg, "search/?jql=#{escape(jql)}", (result) ->
+ if result.errors?
+ return
+
+ cb "I found #{result.total} issues for your search"
+ result.issues.forEach (issue) ->
+ info msg, issue.key, (info) ->
+ cb info
+
+ robot.respond /(show )?watchers (for )?(\w+-[0-9]+)/i, (msg) ->
+ if msg.message.user.id is robot.name
+ return
+
+ watchers msg, msg.match[3], (text) ->
+ msg.send text
+
+ robot.respond /search (for )?(.*)/i, (msg) ->
+ if msg.message.user.id is robot.name
+ return
+
+ search msg, msg.match[2], (text) ->
+ msg.send "#{msg.message.user.id}: #{text}"
+
+ robot.hear /(\w+-[0-9]+)/i, (msg) ->
+ if msg.message.user.id is robot.name
+ return
+
+ info msg, msg.match[0], (text) ->
+ msg.send text
+
+ robot.respond /save filter (.*) as (.*)/i, (msg) ->
+ filter = filters.get msg.match[2]
+
+ if filter
+ filters.delete filter.name
+ msg.send "Updated filter #{filter.name} for you"
+
+ filter = new IssueFilter msg.match[2], msg.match[1]
+ filters.add filter
+
+ robot.respond /delete filter (.*)/i, (msg) ->
+ filters.delete msg.match[1]
+
+ robot.respond /(use )?filter (.*)/i, (msg) ->
+ name = msg.match[2]
+ filter = filters.get name
+
+ search msg, filter.jql, (text) ->
+ msg.send text
+
+ robot.respond /(show )?filter(s)? ?(.*)?/i, (msg) ->
+ if filters.all().length == 0
+ msg.send "Sorry, I don't remember any filters."
+ return
+
+ if msg.match[3] == undefined
+ msg.send "I remember #{filters.all().length} filters"
+ filters.all().forEach (filter) ->
+ msg.send "#{filter.name}: #{filter.jql}"
+ else
+ filter = filters.get msg.match[3]
+ msg.send "#{filter.name}: #{filter.jql}"
View
15 src/scripts/jordan.coffee
@@ -0,0 +1,15 @@
+# Display a picture of Michael Jordan if anyone invokes "jordan" or says "23"
+# Cause Jordan is God. So much more than Steve Jobs :D
+
+images = [
+ "http://pictureloaders.com/images/pictures-of-michael-jordan18.jpg"
+ "http://a7.idata.over-blog.com/0/54/95/85/Michael_Jordan1414.jpg"
+ "http://02.img.v4.skyrock.net/028/michael-jordan-n23/pics/884566066_small.jpg"
+ "http://absolutezone.files.wordpress.com/2008/11/michael_jordan014.jpg"
+ "http://hrichert1.free.fr/IDD_BB_NT/postes_basket/Michael%20jordan.jpg"
+ "http://www.michaeljordansworld.com/pictures/images/michael_jordan_dunks_jazz.jpg"
+]
+
+module.exports = (robot) ->
+ robot.hear /(jordan|23)/i, (msg) ->
+ msg.send msg.random images
View
62 src/scripts/karma.coffee
@@ -0,0 +1,62 @@
+# Track arbitrary karma
+#
+# <thing>++ - give thing some karma
+# <thing>-- - take away some of thing's karma
+# karma <thing> - check thing's karma, if <thing> is ommitted get top and bottom 3
+class Karma
+ constructor: (@robot) ->
+ @cache = {}
+
+ @robot.brain.on 'loaded', =>
+ if @robot.brain.data.karma
+ @cache = @robot.brain.data.karma
+
+ add: (thing) ->
+ @cache[thing] ?= 0
+ @cache[thing] += 1
+ @robot.brain.data.karma = @cache
+
+ subtract: (thing) ->
+ @cache[thing] ?= 0
+ @cache[thing] -= 1
+ @robot.brain.data.karma = @cache
+
+ get: (thing) ->
+ k = if @cache[thing] then @cache[thing] else 0
+ return k
+
+ summary: ->
+ s = []
+ for key,val of @cache
+ s.push({name: key, karma: val})
+ s.sort (a,b) -> b.karma - a.karma
+ if s.length >= 6
+ return [s[0], s[1], s[2], s[s.length-3], s[s.length-2], s[s.length-1]]
+ else
+ return s
+
+module.exports = (robot) ->
+ karma = new Karma robot
+ robot.hear /(\S+[^+\s])\+\+(\s|$)/, (msg) ->
+ karma.add msg.match[1].toLowerCase()
+ msg.reply "The operation succeeded."
+
+ robot.hear /(\S+[^-\s])--(\s|$)/, (msg) ->
+ karma.subtract msg.match[1].toLowerCase()
+ msg.reply "The operation succeeded."
+
+ robot.respond /karma ?(\S*)/, (msg) ->
+ if msg.match[1]
+ match = msg.match[1].toLowerCase()
+ msg.send "\"#{match}\" has #{karma.get(match)} karma."
+ else
+ s = karma.summary()
+ if s.length >= 3
+ msg.send "Highest karma: \"#{s[0].name}\" (#{s[0].karma}), " +
+ "\"#{s[1].name}\" (#{s[1].karma}), and \"#{s[2].name}\" " +
+ "(#{s[2].karma}). Lowest karma: \"#{s[s.length-1].name}\" " +
+ "(#{s[s.length-1].karma}), \"#{s[s.length-2].name}\" " +
+ "(#{s[s.length-2].karma}), and \"#{s[s.length-3].name}\" " +
+ "(#{s[s.length-3].karma})."
+ else
+ msg.send "There aren't enough items with karma to give a top and bottom 3"
View
3  src/scripts/keep-alive.coffee
@@ -68,7 +68,8 @@ module.exports = (robot) ->
url = msg.match[1]
robot.brain.data.keepalives ?= []
- robot.brain.data.keepalives.pop url
+
+ robot.brain.data.keepalives.splice(robot.brain.data.keepalives.indexOf(url), 1);
msg.send "OK. I've removed that url from my list of urls to keep alive."
robot.respond /what are you keeping alive/i, (msg) ->
View
24 src/scripts/kittens.coffee
@@ -0,0 +1,24 @@
+
+# Kittens!
+#
+# kitten me - A randomly selected kitten
+# kitten me <w>x<h> - A kitten of the given size
+# kitten bomb me <number> - Many many kittens!
+
+module.exports = (robot) ->
+ robot.respond /kittens?(?: me)?$/i, (msg) ->
+ msg.send kittenMe()
+
+ robot.respond /kittens?(?: me)? (\d+)(?:[x ](\d+))?$/i, (msg) ->
+ msg.send kittenMe msg.match[1], (msg.match[2] || msg.match[1])
+
+ robot.respond /kitten bomb(?: me)?( \d+)?$/i, (msg) ->
+ kittens = msg.match[1] || 5
+ msg.send(kittenMe()) for i in [1..kittens]
+
+kittenMe = (height, width)->
+ h = height || Math.floor(Math.random()*250) + 250
+ w = width || Math.floor(Math.random()*250) + 250
+ root = "http://placekitten.com"
+ root += "/g" if Math.random() > 0.5 # greyscale kittens!
+ return "#{root}/#{h}/#{w}#.png"
View
96 src/scripts/list-jira-bugs.coffee
@@ -0,0 +1,96 @@
+# Get all bugs from JIRA assigned to user
+#
+# To configure, export the following shell variables
+# HUBOT_JIRA_DOMAIN
+# HUBOT_JIRA_USER
+# HUBOT_JIRA_PASSWORD
+# HUBOT_JIRA_ISSUE_TYPES
+# HUBOT_JIRA_ISSUE_PRIORITIES
+#
+# list my bugs - Retrieve the list of all a user's bugs from JIRA ('my' is optional)
+# list my bugs about <searchterm> - Retrieve list of all a user's bugs from JIRA where the summary or description field contains <phrase> ('my' is optional)
+# list my <priority> priority bugs - Retrieve the list of a user's <priority> priority bugs from JIRA ('my' is optional)
+# list my <priority> priority bugs about <phrase> - Retrieve list of all a user's <priority> priority bugs from JIRA where the summary or description field contains <phrase> ('my' is optional)
+#
+
+# e.g. "bug|task|sub task|support ticket|new feature|epic"
+issueTypes = process.env.HUBOT_JIRA_ISSUE_TYPES
+issueTypes or= "bug|task|sub task|support ticket|new feature|epic" #some defaults
+
+# e.g. "blocker|high|medium|minor|trivial"
+issuePriorities = process.env.HUBOT_JIRA_ISSUE_PRIORITIES
+issuePriorities or= "blocker|high|medium|minor|trivial" #some defaults
+
+# /list( my)?( (blocker|high|medium|minor|trivial)( priority)?)? (bug|task|sub task|support ticket|new feature|epic|issue)s( about (.*))?/i
+regexpString = "list( my)?( (" + issuePriorities + ")( priority)?)? (" + issueTypes + "|issue)s( about (.*))?"
+regexp = new RegExp(regexpString, "i")
+
+module.exports = (robot) ->
+
+ robot.respond regexp, (msg) ->
+ username = if msg.match[1] then msg.message.user.email.split('@')[0] else null
+ issueType = if msg.match[5] and msg.match[5] != "issue" then msg.match[5] else null
+ msg.send "Searching for issues..."
+ getIssues msg, issueType, username, msg.match[3], msg.match[6], (response) ->
+ msg.send response
+
+getIssues = (msg, issueType, assignee, priority, phrase, callback) ->
+ username = process.env.HUBOT_JIRA_USER
+ password = process.env.HUBOT_JIRA_PASSWORD
+ domain = process.env.HUBOT_JIRA_DOMAIN
+
+ # do some error handling
+ unless username
+ msg.send "HUBOT_JIRA_USER environment variable must be set to a valid JIRA user's username."
+ return
+ unless password
+ msg.send "HUBOT_JIRA_PASSWORD environment variable must be set to a valid JIRA user's password."
+ return
+ unless domain
+ msg.send "HUBOT_JIRA_DOMAIN environment variables must be set to a valid <ORG>.jira.com domain."
+ return
+
+ jiraTypeList = toJiraTypeList(process.env.HUBOT_JIRA_ISSUE_TYPES.split('|'))
+ type = if issueType? then 'issueType="' + issueType + '"' else 'issueType in (' + jiraTypeList + ')'
+ user = if assignee? then ' and assignee="' + assignee + '"' else ''
+ prio = if priority? then ' and priority=' + priority else ''
+ search = if phrase? then ' and (summary~"' + phrase + '" or description~"' + phrase + '")' else ''
+
+ path = '/rest/api/latest/search'
+ url = "https://" + domain + path
+ queryString = type + ' and status!=closed' + user + prio + search
+ auth = "Basic " + new Buffer(username + ':' + password).toString('base64')
+
+ getJSON msg, url, queryString, auth, (err, json) ->
+ if err
+ msg.send "error getting issue list from JIRA"
+ return
+ if json.total? and (json.total==0 or json.total=="0")
+ msg.send "No issues like that, or you don't have access to see the issues."
+ issueList = []
+ for issue in json.issues
+ getJSON msg, issue.self, null, auth, (err, details) ->
+ if err
+ msg.send "error getting issue details from JIRA"
+ return
+ issueList.push( {key: details.key, summary: details.fields.summary.value} )
+ callback(formatIssueList(issueList, domain)) if issueList.length == json.issues.length
+
+formatIssueList = (issueArray, domain) ->
+ formattedIssueList = ""
+ for issue in issueArray
+ formattedIssueList += issue.summary + " -> https://" + domain + "/browse/" + issue.key + "\n"
+ return formattedIssueList
+
+getJSON = (msg, url, query, auth, callback) ->
+ msg.http(url)
+ .header('Authorization', auth)
+ .query(jql: query)
+ .get() (err, res, body) ->
+ callback( err, JSON.parse(body) )
+
+toJiraTypeList = (arr) ->
+ newArr = []
+ for issueType in arr
+ newArr.push '"' + issueType + '"'
+ return newArr.join(',')
View
75 src/scripts/location-decision-maker.coffee
@@ -0,0 +1,75 @@
+# Decides where you should go.
+#
+# These commands are grabbed from comment blocks at the top of each file.
+#
+# remember <location> as a <group> location - Remembers the location for the group.
+# forget <location> as a <group> location - Forgets the location from the group.
+# forget all locations for <group> - Forgets all the locations for the group.
+# where can we go for <group>? - Returns a list of places that exist for the group.
+# where should we go for <group>? - Returns a randomly selected location for the group.
+
+class Locations
+ constructor: (@robot) ->
+ @robot.brain.data.locations = {}
+
+ add: (groupname, name) ->
+ if @robot.brain.data.locations[groupname] is undefined
+ @robot.brain.data.locations[groupname] = []
+
+ for location in @robot.brain.data.locations[groupname]
+ if location.toLowerCase() is name.toLowerCase()
+ return
+
+ @robot.brain.data.locations[groupname].push name
+
+ remove: (groupname, name) ->
+ group = @robot.brain.data.locations[groupname] or []
+ @robot.brain.data.locations[groupname] = (location for location in group when location.toLowerCase() isnt name.toLowerCase())
+
+ removeAll: (groupname) ->
+ delete @robot.brain.data.locations[groupname]
+
+ group: (name) ->
+ return @robot.brain.data.locations[name] or []
+
+
+module.exports = (robot) ->
+ locations = new Locations robot
+
+ robot.respond /remember (.*) as a (.*) location/i, (msg) ->
+ locationname = msg.match[1]
+ locationgroup = msg.match[2]
+ locations.add locationgroup, locationname
+
+ if locationname.toLowerCase() is "nandos"
+ msg.send "Nom peri peri. My fav."
+
+ robot.respond /forget (.*) as a (.*) location/i, (msg) ->
+ locationname = msg.match[1]
+ locationgroup = msg.match[2]
+ locations.remove locationgroup, locationname
+
+ robot.respond /forget all locations for (.*)/i, (msg) ->
+ locationgroup = msg.match[1]
+ locations.removeAll locationgroup
+
+ robot.respond /where can we go for (.*)\?$/i, (msg) ->
+ locationgroup = msg.match[1]
+ grouplocations = locations.group(locationgroup)
+
+ if grouplocations.length > 0
+ for location in grouplocations
+ msg.send location
+ else
+ msg.send "I don't know anywhere to go for #{locationgroup}"
+
+ robot.respond /where (should|shall) we go for (.*)\?$/i, (msg) ->
+ locationgroup = msg.match[2]
+ grouplocations = locations.group(locationgroup)
+
+ if grouplocations.length is 0
+ msg.send "I dont know anywhere to go for #{locationgroup}"
+ else
+ location = msg.random grouplocations
+
+ msg.send "I think you should goto #{location}"
View
16 src/scripts/lolz.coffee
@@ -4,7 +4,7 @@ Select = require("soupselect").select
HtmlParser = require "htmlparser"
module.exports = (robot) ->
- robot.respond /.*l[ou]lz/i, (msg) ->
+ robot.respond /l[ou]lz$/i, (msg) ->
msg.http("http://bukk.it")
.get() (err, res, body) ->
handler = new HtmlParser.DefaultHandler()
@@ -14,3 +14,17 @@ module.exports = (robot) ->
results = ("http://bukk.it/#{link.attribs.href}" for link in Select handler.dom, "td a")
msg.send msg.random results
+ robot.respond /l[ou]lz\s*bomb (\d+)?/i, (msg) ->
+ count = msg.match[1] || 5
+ count = 5 if count > 20
+
+ msg.http("http://bukk.it")
+ .get() (err, res, body) ->
+ handler = new HtmlParser.DefaultHandler()
+ parser = new HtmlParser.Parser handler
+
+ parser.parseComplete body
+
+ results = ("http://bukk.it/#{link.attribs.href}" for link in Select handler.dom, "td a")
+ for num in [count..1]
+ msg.send msg.random results
View
13 src/scripts/look-of-disapproval.coffee
@@ -0,0 +1,13 @@
+# Allows Hubot to give a look of disapproval.
+#
+# lod <name> - gives back the character for the look of disapproval, optionally @name.
+
+module.exports = (robot) ->
+ robot.respond /lod\s?(.*)/i, (msg) ->
+ response = 'ಠ_ಠ'
+
+ name = msg.match[1].trim()
+ response += " @" + name if name != ""
+
+ msg.send(response)
+
View
19 src/scripts/lyrics.coffee
@@ -0,0 +1,19 @@
+# Grabs snippets of song lyrics.
+# Limited to snippets due to copyright stuff.
+#
+# lyrics for <song> by <artist> - returns snippet of lyrics for this song
+#
+# Example: lyrics for purple haze by jimi hendrix
+module.exports = (robot) ->
+ robot.respond /lyrics for (.*) by (.*)/i, (msg) ->
+ song = msg.match[1]
+ artist = msg.match[2]
+ getLyrics msg, song, artist
+
+ getLyrics = (msg, song, artist) ->
+ msg.http("http://lyrics.wikia.com/api.php")
+ .query(artist: artist, song: song, fmt: "json")
+ .get() (err, res, body) ->
+ result = eval body # can't use JSON.parse :(
+ msg.send result['url']
+ msg.send result['lyrics']
View
7 src/scripts/megusta.coffee
@@ -0,0 +1,7 @@
+# Happiness in image form
+#
+# me gusta - Display "Me Gusta" face when heard
+#
+module.exports = (robot) ->
+ robot.hear /me gusta/i, (msg) ->
+ msg.send "http://s3.amazonaws.com/kym-assets/entries/icons/original/000/002/252/me-gusta.jpg"
View
30 src/scripts/meme_generator.coffee
@@ -10,6 +10,10 @@
# <text> (SUCCESS|NAILED IT) - Generates success kid with the top caption of <text>
#
# <text> ALL the <things> - Generates ALL THE THINGS
+#
+# <text> TOO DAMN <high> - Generates THE RENT IS TOO DAMN HIGH guy
+#
+# Good news everyone! <news> - Generates Professor Farnsworth
module.exports = (robot) ->
robot.respond /Y U NO (.+)/i, (msg) ->
@@ -18,7 +22,7 @@ module.exports = (robot) ->
memeGenerator msg, 2, 166088, "Y U NO", caption, (url) ->
msg.send url
- robot.respond /(I DON'?T ALWAYS .*) (BUT WHEN I DO .*)/i, (msg) ->
+ robot.respond /(I DON'?T ALWAYS .*) (BUT WHEN I DO,? .*)/i, (msg) ->
memeGenerator msg, 74, 2485, msg.match[1], msg.match[2], (url) ->
msg.send url
@@ -30,10 +34,18 @@ module.exports = (robot) ->
memeGenerator msg, 121, 1031, msg.match[1], msg.match[2], (url) ->
msg.send url
- robot.respond /(.*) (ALL the .*)/, (msg) ->
+ robot.respond /(.*) (ALL the .*)/i, (msg) ->
memeGenerator msg, 6013, 1121885, msg.match[1], msg.match[2], (url) ->
msg.send url
+ robot.respond /(.*) (\w+\sTOO DAMN .*)/i, (msg) ->
+ memeGenerator msg, 998, 203665, msg.match[1], msg.match[2], (url) ->
+ msg.send url
+
+ robot.respond /(GOOD NEWS EVERYONE[,.!]?) (.*)/i, (msg) ->
+ memeGenerator msg, 1591, 112464, msg.match[1], msg.match[2], (url) ->
+ msg.send url
+
memeGenerator = (msg, generatorID, imageID, text0, text1, callback) ->
username = process.env.HUBOT_MEMEGEN_USERNAME
password = process.env.HUBOT_MEMEGEN_PASSWORD
@@ -59,9 +71,11 @@ memeGenerator = (msg, generatorID, imageID, text0, text1, callback) ->
text1: text1
.get() (err, res, body) ->
result = JSON.parse(body)['result']
- instanceURL = result['instanceUrl']
- img = "http://memegenerator.net" + result['instanceImageUrl']
-
- msg.http(instanceURL).get() (err, res, body) ->
- # Need to hit instanceURL so that image gets generated
- callback img
+ if result? and result['instanceUrl']? and result['instanceImageUrl']?
+ instanceURL = result['instanceUrl']
+ img = result['instanceImageUrl']
+ msg.http(instanceURL).get() (err, res, body) ->
+ # Need to hit instanceURL so that image gets generated
+ callback img
+ else
+ msg.reply "Sorry, I couldn't generate that image."
View
139 src/scripts/mite.coffee
@@ -0,0 +1,139 @@
+# Allows Hubot to start and stop project time in mite.yo.lk
+#
+# save my mite key <key> for <account> - stores your personal API key for mite.yo.lk
+# mite me <task> on <project> - starts or stops the matched task on the given project in mite.yo.lk
+
+module.exports = (robot) ->
+ robot.respond /mite( me)? (.+) on (.+)/i, (msg) -> # user wants to track time
+ [task, project] = msg.match[2..3]
+
+ user_mite = msg.message.user.mite
+
+ unless user_mite? and user_mite.length == 2 # exit if the credentials are not provided
+ msg.reply "sorry, you first have to tell me your credentials."
+ return
+
+ [mite_key, mite_account] = user_mite[0..1] #ok, credentials are there...
+
+ mite = new Mite msg, mite_key, mite_account # create a new Mite instance
+ mite.projects msg, project, (projects) -> # first get the project information
+ if projects.length == 0 # no projects found
+ msg.reply "Oops.. could not find a matching project"
+ return
+ if projects.length > 1 # more than one project found
+ answer = "please be more precise, I found #{projects.length} projects: "
+ result = (project.project.name for project in projects)
+ answer += result.join(", ")
+ msg.reply answer # list the found projects and..
+ return # .. exit
+ project = projects[0].project # the first and only project is used
+ mite.services msg,task, (services) -> # then get the task
+ if services.length == 0
+ msg.reply "Oops.. could not find a matching task"
+ return
+ if services.length > 1
+ answer = "please be more precise, I found #{services.length} tasks: "
+ result = (service.service.name for service in services)
+ answer += result.join(", ")
+ msg.reply answer
+ return
+ service = services[0].service
+ # check if there is alreday a good time entry for today
+ mite.todays_matching_entry msg, project, service, (time_entry) ->
+ if time_entry? # if there is an existing time entry for today...
+ mite.tracker msg, time_entry # we start the timer on this one again
+ else
+ mite.time_entry msg, project, service, (time_entry) -> # create a new entry for this projects task
+ mite.tracker msg, time_entry # start the tracker on the time entry
+
+# we need to know the key to connect to mite.yo.lk
+ robot.respond /save my mite key (.+) for (.+)/i, (msg) ->
+ mite_key = msg.match[1] # the key we should keep in mind
+ mite_account = msg.match[2] # and the account
+ user = msg.message.user # for this user
+
+ user.mite = [mite_key, mite_account] # and in the Brain it goes
+ msg.reply "I'll hapilly punch your timecard from now on."
+
+# MITE CLASS #
+class Mite
+ constructor: (msg, key, account) ->
+ @url = "http://#{account}.mite.yo.lk"
+ @key = key
+
+ services: (msg, task, callback) ->
+ msg
+ .http(@url)
+ .headers
+ 'X-MiteApiKey': "#{@key}",
+ 'Accept': 'application/json'
+ .query
+ name: task
+ .path("services")
+ .get() (err, res, body) ->
+ if err
+ msg.reply "Mite says: #{err}"
+ return
+ callback JSON.parse body
+
+ projects: (msg, project, callback) ->
+ msg
+ .http(@url)
+ .headers
+ 'X-MiteApiKey': "#{@key}",
+ 'Accept': 'application/json'
+ .query
+ name: project
+ .path("projects")
+ .get() (err, res, body) ->
+ if err
+ msg.reply "Mite says: #{err}"
+ return
+ callback JSON.parse body
+
+ time_entry: (msg, project, service, callback) ->
+ data = JSON.stringify {time_entry: {service_id: "#{service.id}", project_id: "#{project.id}"}}
+ msg
+ .http(@url)
+ .headers
+ 'X-MiteApiKey': "#{@key}",
+ 'Content-type': "application/json",
+ 'Accept': 'application/json'
+ .path("time_entries")
+ .post(data) (err, res, body) ->
+ if err
+ msg.reply "Mite says: #{err}"
+ return
+ time_entries = JSON.parse body
+ callback time_entries.time_entry
+
+ todays_matching_entry: (msg, project, service, callback) ->
+ msg
+ .http(@url)
+ .headers
+ 'X-MiteApiKey': "#{@key}",
+ 'Accept': 'application/json'
+ .query
+ name: project
+ .path("daily")
+ .get() (err, res, body) ->
+ if err
+ msg.reply "Mite says: #{err}"
+ return
+ time_entries = JSON.parse body
+ time_entry = (t_entries.time_entry for t_entries in time_entries when t_entries.time_entry.project_id == project.id and t_entries.time_entry.service_id == service.id)
+ callback time_entry[0]
+
+ tracker: (msg, time_entry) ->
+ msg
+ .http(@url)
+ .headers
+ 'X-MiteApiKey': "#{@key}",
+ 'Content-type': "application/json",
+ 'Accept': 'application/json'
+ .path("tracker/#{time_entry.id}")
+ .put(" ") (err, res, body) ->
+ if err
+ msg.reply "Mite says: #{err}"
+ return
+ msg.reply "ok, time is running..."
View
18 src/scripts/nettipot.coffee
@@ -0,0 +1,18 @@
+#
+# nettipot - Send scarring, horrifying image of a nettipot in use.
+# Written by @alexpgates
+#
+
+nettipot = "http://i.imgur.com/EIqdZ.gif"
+
+module.exports = (robot) ->
+ robot.respond /nettipot/i, (msg) ->
+ msg.send nettipot
+
+ robot.respond /nettibomb/i, (msg) ->
+ msg.send nettipot
+ msg.send nettipot
+ msg.send nettipot
+ msg.send nettipot
+ msg.send nettipot
+
View
43 src/scripts/news.coffee
@@ -0,0 +1,43 @@
+# Returns the latest news headlines from Google
+#
+# news - Get the latest headlines
+# news <topic> - Get the latest headlines for a specific topic
+
+module.exports = (robot) ->
+ robot.respond /news(?: me| on)?\s?(.*)/, (msg) ->
+ query msg, (response, err) ->
+ return msg.send err if err
+
+ strings = []
+
+ topic = msg.match[1]
+
+ if (topic != "")
+ strings.push "Here's the latest news on \"#{topic}\":\n"
+ else
+ strings.push "Here's the latest news headlines:\n"
+
+ for story in response.responseData.results
+ strings.push story.titleNoFormatting.replace(/&#39;/g, "'").replace(/`/g, "'").replace(/&quot;/g, "\"")
+ strings.push story.unescapedUrl + "\n"
+
+ msg.send strings.join "\n"
+
+ query = (msg, cb) ->
+ if (msg.match[1] != "")
+ msg.http("https://ajax.googleapis.com/ajax/services/search/news?v=1.0&rsz=5")
+ .query(q: msg.match[1])
+ .get() (err, res, body) ->
+ complete cb, body, err
+ else
+ msg.http("https://ajax.googleapis.com/ajax/services/search/news?v=1.0&rsz=5&topic=h")
+ .get() (err, res, body) ->
+ complete cb, body, err
+
+ complete = (cb, body, err) ->
+ try
+ response = JSON.parse body
+ catch err
+ err = "Sorry, but I could not fetch the latest headlines."
+ cb(response, err)
+
View
21 src/scripts/octocat.coffee
@@ -0,0 +1,21 @@
+# Show random octocat
+#
+# octocat me - a randomly selected octocat
+# octocat bomb me <number> - octocat-splosion!
+
+xml2js = require('xml2js')
+
+module.exports = (robot) ->
+ robot.respond /octocat\s*(?:me)?$/i, (msg) ->
+ show_octocats msg, 1
+ robot.respond /octocat\s+(?:bomb)\s*(?:me)?\s*(\d+)?/i, (msg) ->
+ count = msg.match[1] || 5
+ show_octocats msg, count
+show_octocats = (msg, count) ->
+ msg.http('http://feeds.feedburner.com/Octocats')
+ .query(format: 'xml')
+ .get() (err, res, body) ->
+ parser = new xml2js.Parser()
+ parser.parseString body, (err, result) ->
+ octocats = (r["content"]["div"]["a"]["img"]["@"]["src"] for r in result["entry"])
+ msg.send msg.random octocats for i in [1..count]
View
50 src/scripts/ping.coffee
@@ -0,0 +1,50 @@
+# Hubot is very attentive (ping hubot)
+
+phrases = [
+ "Yes, master?"
+ "At your service"
+ "Unleash my strength"
+ "I'm here. As always"
+ "By your command"
+ "Ready to work!"
+ "Yes, milord?"
+ "More work?"
+ "Ready for action"
+ "Orders?"
+ "What do you need?"
+ "Say the word"
+ "Aye, my lord"
+ "Locked and loaded"
+ "Aye, sir?"
+ "I await your command"
+ "Your honor?"
+ "Command me!"
+ "At once"
+ "What ails you?"
+ "Yes, my firend?"
+ "Is my aid required?"
+ "Do you require my aid?"
+ "My powers are ready"
+ "It's hammer time!"
+ "I'm your robot"
+ "I'm on the job"
+ "You're interrupting my calculations!"
+ "What is your wish?"
+ "How may I serve?"
+ "At your call"
+ "You require my assistance?"
+ "What is it now?"
+ "Hmm?"
+ "I'm coming through!"
+ "I'm here, mortal"
+ "I'm ready and waiting"
+ "Ah, at last"
+ "I'm here"
+ "Something need doing?"
+]
+
+module.exports = (robot) ->
+ name_regex = new RegExp("#{robot.name}\\?$", "i")
+
+ robot.hear name_regex, (msg) ->
+ msg.reply msg.random phrases
View
28 src/scripts/pivotal.coffee
@@ -39,3 +39,31 @@ module.exports = (robot) ->
msg.send message
return
msg.send "No project #{project_name}"
+
+ robot.respond /(pivotal story)? (.*)/i, (msg)->
+ Parser = require("xml2js").Parser
+ token = process.env.HUBOT_PIVOTAL_TOKEN
+ project_id = process.env.HUBOT_PIVOTAL_PROJECT
+ story_id = msg.match[2]
+
+ msg.http("http://www.pivotaltracker.com/services/v3/projects").headers("X-TrackerToken": token).get() (err, res, body) ->
+ if err
+ msg.send "Pivotal says: #{err}"
+ return
+ (new Parser).parseString body, (err, json)->
+ for project in json.project
+ msg.http("https://www.pivotaltracker.com/services/v3/projects/#{project.id}/stories/#{story_id}").headers("X-TrackerToken": token).get() (err, res, body) ->
+ if err
+ msg.send "Pivotal says: #{err}"
+ return
+ if res.statusCode != 500
+ (new Parser).parseString body, (err, story)->
+ if !story.id
+ return
+ message = "##{story.id['#']} #{story.name}"
+ message += " (#{story.owned_by})" if story.owned_by
+ message += " is #{story.current_state}" if story.current_state && story.current_state != "unstarted"
+ msg.send message
+ storyReturned = true
+ return
+ return
View
34 src/scripts/pivotalstorylisten.coffee
@@ -0,0 +1,34 @@
+# Listen for a specific story from PivotalTracker
+#
+# You need to set the following variables:
+# HUBOT_PIVOTAL_TOKEN = <API token>
+#
+# paste a pivotal tracker link or type "sid-####" in the presence of hubot
+module.exports = (robot) ->
+ robot.hear /(sid-|SID-|pivotaltracker.com\/story\/show)/i, (msg) ->
+ Parser = require("xml2js").Parser
+ token = process.env.HUBOT_PIVOTAL_TOKEN
+ story_id = msg.message.text.match(/\d+$/) # look for some numbers in the string
+
+ msg.http("http://www.pivotaltracker.com/services/v3/projects").headers("X-TrackerToken": token).get() (err, res, body) ->
+ if err
+ msg.send "Pivotal says: #{err}"
+ return
+ (new Parser).parseString body, (err, json)->
+ for project in json.project
+ msg.http("https://www.pivotaltracker.com/services/v3/projects/#{project.id}/stories/#{story_id}").headers("X-TrackerToken": token).get() (err, res, body) ->
+ if err
+ msg.send "Pivotal says: #{err}"
+ return
+ if res.statusCode != 500
+ (new Parser).parseString body, (err, story)->
+ if !story.id
+ return
+ message = "##{story.id['#']} #{story.name}"
+ message += " (#{story.owned_by})" if story.owned_by
+ message += " is #{story.current_state}" if story.current_state && story.current_state != "unstarted"
+ msg.send message
+ storyReturned = true
+ return
+ return
+
View
161 src/scripts/play.coffee
@@ -0,0 +1,161 @@
+# Play music. At your office. Like a boss.
+#
+# play.coffee uses play, an open source API to playing music:
+# https://github.com/holman/play
+#
+# You can watch the screencast at:
+# http://zachholman.com/screencast/play/
+#
+# Make sure you set up your HUBOT_PLAY_URL environment variable with the URL to
+# your company's play.
+#
+# play - Plays music.
+# stop - Stops the music.
+# play next - Plays the next song.
+# what's playing - Returns the currently-played song.
+# I want this song - Returns a download link for the current song.
+# I want this album - Returns a download link for the current album.
+# play <artist> - Queue up ten songs from a given artist.
+# play <name> by <artist> - Queues up a song by an artist.
+# play album <album> - Queues up an album.
+# list songs by <artist> - Lists the songs by the artist String.
+# where's play - Gives you the URL to the web app.
+# volume [0-10] - Adjust the volume of play.
+# be quiet - Mute play.
+# say <message> - `say` your message over your speakers.
+# play stats - Show some play stats.
+
+URL = "#{process.env.HUBOT_PLAY_URL}/api"
+
+module.exports = (robot) ->
+ robot.respond /where'?s play/i, (message) ->
+ message.send("play's at #{process.env.HUBOT_PLAY_URL}")
+
+ robot.respond /what'?s playing/i, (message) ->
+ message.http("#{URL}/now_playing").get() (err, res, body) ->
+ json = JSON.parse(body)
+ str = "\"" + json.song_title + "\" by " +
+ json.artist_name + ", from \"" + json.album_name + "\"."
+ message.send("Now playing " + str)
+
+ robot.respond /say (.*)/i, (message) ->
+ message.http("#{URL}/say")
+ .query(message: message.match[1])
+ .get() (err, res, body) ->
+ message.send(message.match[1])
+
+ robot.respond /play stats/i, (message) ->
+ message.http("#{URL}/stats")
+ .get() (err, res, body) ->
+ json = JSON.parse(body)
+ message.send(json.message)
+
+ robot.respond /volume (.*)/i, (message) ->
+ message.http("#{URL}/volume")
+ .query(level: message.match[1])
+ .post() (err, res, body) ->
+ json = JSON.parse(body)
+ if json.success == 'true'
+ message.send("Bumped the volume for ya.")
+ else
+ message.send("Whoa, can't change the volume. Weird.")
+
+ robot.respond /quiet/i, (message) ->
+ message.http("#{URL}/volume")
+ .query(level: 1)