hipbot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source :rubygems
2
+
3
+ gem 'rake'
4
+ gem 'eventmachine'
5
+ gem 'em-http-request'
6
+ gem 'xmpp4r'
7
+ gem 'httparty'
8
+ gem 'active_support'
9
+ gem 'i18n'
10
+ gem 'daemons'
11
+
12
+ gem 'rspec'
13
+ gem 'mocha'
14
+
15
+ # Plugins
16
+ gem 'google-weather'
data/Gemfile.lock ADDED
@@ -0,0 +1,50 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ active_support (3.0.0)
5
+ activesupport (= 3.0.0)
6
+ activesupport (3.0.0)
7
+ addressable (2.2.8)
8
+ crack (0.1.6)
9
+ daemons (1.1.8)
10
+ diff-lcs (1.1.3)
11
+ em-http-request (0.3.0)
12
+ addressable (>= 2.0.0)
13
+ escape_utils
14
+ eventmachine (>= 0.12.9)
15
+ escape_utils (0.2.4)
16
+ eventmachine (0.12.10)
17
+ google-weather (0.3.0)
18
+ httparty (~> 0.5.0)
19
+ httparty (0.5.2)
20
+ crack (= 0.1.6)
21
+ i18n (0.6.0)
22
+ metaclass (0.0.1)
23
+ mocha (0.11.4)
24
+ metaclass (~> 0.0.1)
25
+ rake (0.9.2.2)
26
+ rspec (2.10.0)
27
+ rspec-core (~> 2.10.0)
28
+ rspec-expectations (~> 2.10.0)
29
+ rspec-mocks (~> 2.10.0)
30
+ rspec-core (2.10.1)
31
+ rspec-expectations (2.10.0)
32
+ diff-lcs (~> 1.1.3)
33
+ rspec-mocks (2.10.1)
34
+ xmpp4r (0.5)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ active_support
41
+ daemons
42
+ em-http-request
43
+ eventmachine
44
+ google-weather
45
+ httparty
46
+ i18n
47
+ mocha
48
+ rake
49
+ rspec
50
+ xmpp4r
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Tomasz Pewiński
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Hipbot
2
+
3
+ [![Build Status](https://secure.travis-ci.org/pewniak747/hipbot.png?branch=master)](http://travis-ci.org/pewniak747/hipbot)
4
+
5
+ Hipbot is a bot for HipChat, written in ruby & eventmachine.
6
+
7
+ This is a work in progress.
8
+
9
+ ## Usage
10
+
11
+ Install via RubyGems
12
+
13
+ ```
14
+ gem install hipbot
15
+ ```
16
+
17
+ In bot.rb file, subclass Hipbot::Bot and customize robot responses.
18
+
19
+ ``` ruby
20
+ require 'hipbot'
21
+
22
+ class MyCompanyBot < Hipbot::Bot
23
+ configure do |c|
24
+ c.name = 'robot'
25
+ c.jid = 'changeme@chat.hipchat.com'
26
+ c.password = 'secret'
27
+ end
28
+
29
+ # simple reply to '@robot hello!'
30
+ on /^hello.*/ do
31
+ reply('hello!')
32
+ end
33
+
34
+ # tasks with arguments: '@robot deploy to production pls'
35
+ on /deploy to (.*) pls/ do |stage|
36
+ reply('deploying to #{stage}!')
37
+ post("http://#{stage}.example.com") do |http|
38
+ reply("deploy server responded with #{http.response_header.status}")
39
+ end
40
+ end
41
+
42
+ # global messages
43
+ on /.*hello everyone.*/, global: true do
44
+ reply('hello!')
45
+ end
46
+ end
47
+
48
+ MyCompanyBot.start!
49
+ ```
50
+
51
+ Run hipbot as daemon by saying:
52
+
53
+ ```
54
+ hipbot start
55
+ ```
56
+
57
+ Run `hipbot` to see all available commands.
58
+
59
+ ## TODO:
60
+
61
+ * add support for custom response helpers
62
+ * error handling, reconnecting
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'rspec/core/rake_task'
2
+ RSpec::Core::RakeTask.new(:spec)
3
+
4
+ task :default => :spec
data/bin/hipbot ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'daemons'
4
+
5
+ Daemons.run('bot.rb')
data/bot.rb ADDED
@@ -0,0 +1,92 @@
1
+ require_relative './lib/hipbot'
2
+
3
+ class MyBot < Hipbot::Bot
4
+ configure do |c|
5
+ c.jid = ENV['HIPBOT_JID']
6
+ c.password = ENV['HIPBOT_PASSWORD']
7
+ c.name = ENV['HIPBOT_NAME']
8
+ end
9
+
10
+ # Works for message and response
11
+ # TODO: Reload existing objects? / Full bot restart
12
+ on /^reload (.*)/, global: true do |clazz|
13
+ const = clazz.capitalize.to_sym
14
+ Hipbot.class_eval do
15
+ remove_const const if const_defined? const
16
+ end
17
+ load("./lib/hipbot/" + clazz + ".rb")
18
+ reply('Reloaded')
19
+ end
20
+
21
+ on /get (.*)/ do |url|
22
+ get(url) do |http|
23
+ reply("Server responded with code: #{http.response_header.status}")
24
+ end
25
+ end
26
+
27
+ on /^wiki (.+)/, global: true do |search|
28
+ search = URI::encode search
29
+ get("http://en.wikipedia.org/w/api.php?action=query&format=json&titles=#{search}&prop=extracts&exlimit=3&explaintext=1&exsentences=2") do |http|
30
+ extracts = JSON.parse http.response.force_encoding 'utf-8'
31
+ extracts['query']['pages'].each do |page|
32
+ reply(page[1]['extract'])
33
+ end
34
+ end
35
+ end
36
+
37
+ on /^google (.+)/, global: true do |search|
38
+ search = URI::encode search
39
+ get("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&safe=off&q=#{search}") do |http|
40
+ extracts = JSON.parse http.response.force_encoding 'utf-8'
41
+ extracts['responseData']['results'].each do |page|
42
+ reply("#{page['url']} - #{page['titleNoFormatting']}")
43
+ end
44
+ end
45
+ end
46
+
47
+ on /weather\s(.*)/ do |city|
48
+ reply("checking weather for #{city}")
49
+ conditions = ::GoogleWeather.new(city).forecast_conditions.first
50
+ reply("weather in #{city} - #{conditions.condition}, max #{conditions.high}F")
51
+ end
52
+
53
+ on /^hello.*/ do
54
+ reply('hello!')
55
+ end
56
+
57
+ on /deploy to (.*) pls/ do |stage|
58
+ reply("deploying #{room} to #{stage}!")
59
+ sleep(3)
60
+ reply("deployed!")
61
+ end
62
+
63
+ on /.*hello everyone.*/, global: true do
64
+ reply('hello!')
65
+ end
66
+
67
+ on /sleep/, global: true do
68
+ reply("sleeping...")
69
+ sleep(10)
70
+ reply("woke up")
71
+ end
72
+
73
+ on /help|commands/ do
74
+ commands = []
75
+ bot.reactions.each do |r|
76
+ commands << r.regexp
77
+ end
78
+ reply(commands.join("\n"))
79
+ end
80
+
81
+ on /^rand (.*)/, global: true do |message|
82
+ options = message.split(',').map { |s| s.strip }
83
+ rsp = options[rand(options.length)]
84
+ reply("And the winner is... #{rsp}") if rsp
85
+ end
86
+
87
+ on /^debug/, global: true do
88
+ reply "Debug " + (Jabber::debug = !Jabber::debug ? 'on' : 'off')
89
+ end
90
+ end
91
+
92
+ MyBot.start!
data/hipbot.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/hipbot/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Tomasz Pewiński"]
6
+ gem.email = ["pewniak747@gmail.com"]
7
+ gem.description = "Hipbot is a bot for HipChat, written in ruby & eventmachine."
8
+ gem.summary = "Hipbot is a bot for HipChat, written in ruby & eventmachine."
9
+ gem.homepage = "http://github.com/pewniak747/hipbot"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "hipbot"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Hipbot::VERSION
17
+ end
data/lib/hipbot.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'active_support/all'
2
+ require 'eventmachine'
3
+ require 'em-http-request'
4
+ require 'xmpp4r'
5
+ require 'xmpp4r/muc'
6
+ require 'hipbot/mucclient'
7
+
8
+ require 'hipbot/adapters/hipchat'
9
+ require 'hipbot/adapters/telnet'
10
+ require 'hipbot/bot'
11
+ require 'hipbot/configuration'
12
+ require 'hipbot/message'
13
+ require 'hipbot/reaction'
14
+ require 'hipbot/response'
15
+ require 'hipbot/room'
16
+ require 'hipbot/version'
17
+
18
+ # Plugins
19
+ require 'google_weather'
@@ -0,0 +1,79 @@
1
+ module Hipbot
2
+ module Adapters
3
+ module Hipchat
4
+ delegate :reply, to: :connection
5
+
6
+ class Connection
7
+
8
+ def initialize bot
9
+ initialize_bot(bot)
10
+ initialize_jabber
11
+ initialize_rooms
12
+ join_rooms
13
+ end
14
+
15
+ def reply room, message
16
+ for_room room do |room|
17
+ puts("Replied to #{room.name} - #{message}")
18
+ send_message(room, message)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def initialize_rooms
25
+ @rooms ||= []
26
+ @muc_browser = Jabber::MUC::MUCBrowser.new(@jabber)
27
+ @rooms = @muc_browser.muc_rooms('conf.hipchat.com').map { |jid, name|
28
+ ::Hipbot::Room.new(jid, name)
29
+ }
30
+ end
31
+
32
+ def initialize_bot bot
33
+ @bot = bot
34
+ @bot.connection = self
35
+ end
36
+
37
+ def initialize_jabber
38
+ @jabber = ::Jabber::Client.new(@bot.jid)
39
+ @jabber.connect
40
+ @jabber.auth(@bot.password)
41
+ end
42
+
43
+ def join_rooms
44
+ rooms.each do |room|
45
+ puts "joining #{room.name}"
46
+ room.connection = ::Jabber::MUC::SimpleMUCClient.new(@jabber)
47
+ room.connection.on_message do |time, sender, message|
48
+ puts "#{Time.now} <#{sender}> #{message}"
49
+ @bot.tell(sender, room.name, message)
50
+ end
51
+ room.connection.join("#{room.jid}/#{@bot.name}", nil, :history => false)
52
+ end
53
+ end
54
+
55
+ def for_room room_name
56
+ room = rooms.find { |r| r.name == room_name }
57
+ if room.present?
58
+ yield(room) if block_given?
59
+ end
60
+ end
61
+
62
+ def send_message room, message
63
+ room.connection.say(message)
64
+ end
65
+
66
+ def rooms
67
+ @rooms || []
68
+ end
69
+ end
70
+
71
+ def start!
72
+ ::EM::run do
73
+ Connection.new(self)
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,28 @@
1
+ module Hipbot
2
+ module Adapters
3
+ module Telnet
4
+ def reply room, message
5
+ connection.send_data("#{self}:#{room}:#{message}\n")
6
+ end
7
+
8
+ class Connection < EM::Connection
9
+ def initialize bot
10
+ @bot = bot
11
+ @bot.connection = self
12
+ end
13
+
14
+ def receive_data(data)
15
+ sender, room, message = *data.strip.split(':')
16
+ @bot.tell(sender, room, message)
17
+ end
18
+ end
19
+
20
+ def start!
21
+ ::EM::run do
22
+ ::EM::connect('0.0.0.0', 3001, Connection, self)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
data/lib/hipbot/bot.rb ADDED
@@ -0,0 +1,72 @@
1
+ module Hipbot
2
+ class Bot
3
+ attr_accessor :reactions, :configuration, :connection
4
+ CONFIGURABLE_OPTIONS = [:name, :jid, :password, :adapter]
5
+ delegate *CONFIGURABLE_OPTIONS, to: :configuration
6
+ alias_method :to_s, :name
7
+
8
+ def initialize
9
+ self.configuration = Configuration.new.tap(&self.class.configuration)
10
+ self.reactions = []
11
+ self.class.reactions.each do |opts|
12
+ on(opts[0], opts[1], &opts[-1])
13
+ end
14
+ extend(self.adapter || ::Hipbot::Adapters::Hipchat)
15
+ end
16
+
17
+ def on regexp, options={}, &block
18
+ self.reactions << Reaction.new(self, regexp, options, block)
19
+ end
20
+
21
+ def tell sender, room, message
22
+ return if sender == name
23
+ matches = matching_reactions(sender, room, message)
24
+ if matches.size > 0
25
+ matches[0].invoke(sender, room, message)
26
+ end
27
+ end
28
+
29
+ def reactions_list
30
+ self.reactions.regexp
31
+ end
32
+
33
+ class << self
34
+
35
+ def on regexp, options={}, &block
36
+ @reactions ||= []
37
+ @reactions << [regexp, options, block]
38
+ end
39
+
40
+ def configure &block
41
+ @configuration = block
42
+ end
43
+
44
+ def reactions
45
+ @reactions || []
46
+ end
47
+
48
+ def configuration
49
+ @configuration || Proc.new{}
50
+ end
51
+
52
+ def start!
53
+ new.start!
54
+ end
55
+
56
+ end
57
+
58
+ private
59
+
60
+ def matching_reactions sender, room, message
61
+ all_reactions = reactions + [default_reaction]
62
+ all_reactions.select { |r| r.match?(sender, room, message) }
63
+ end
64
+
65
+ def default_reaction
66
+ @default_reaction ||= Reaction.new(self, /.*/, {}, Proc.new {
67
+ reply("I don't understand \"#{message}\"")
68
+ })
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,12 @@
1
+ module Hipbot
2
+ class Configuration
3
+ attr_accessor *Bot::CONFIGURABLE_OPTIONS
4
+
5
+ def initialize
6
+ self.name = 'robot'
7
+ self.jid = 'changeme'
8
+ self.password = 'changeme'
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ module Hipbot
2
+ class Message
3
+ attr_accessor :body, :sender, :raw_body
4
+ def initialize body, sender
5
+ self.raw_body = body
6
+ self.body = strip_recipient(body)
7
+ self.sender = sender
8
+ end
9
+
10
+ def recipients
11
+ results = raw_body.scan(/@(\w+)/) + raw_body.scan(/@"(.*)"/)
12
+ results.flatten
13
+ end
14
+
15
+ def for? recipient
16
+ recipients.include? recipient.to_s.gsub(/\s+/, '')
17
+ end
18
+
19
+ def strip_recipient body
20
+ body.gsub(/^@\w+\W*/, '')
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,60 @@
1
+ module Jabber
2
+ module MUC
3
+ class MUCClient
4
+ def join(jid, password=nil, opts={})
5
+ if active?
6
+ raise "MUCClient already active"
7
+ end
8
+
9
+ @jid = (jid.kind_of?(JID) ? jid : JID.new(jid))
10
+ activate
11
+
12
+ # Joining
13
+ pres = Presence.new
14
+ pres.to = @jid
15
+ pres.from = @my_jid
16
+ xmuc = XMUC.new
17
+ xmuc.password = password
18
+
19
+ if !opts[:history]
20
+ history = REXML::Element.new( 'history').tap {|h| h.add_attribute('maxstanzas','0') }
21
+ xmuc.add_element history
22
+ end
23
+
24
+ pres.add(xmuc)
25
+
26
+ # We don't use Stream#send_with_id here as it's unknown
27
+ # if the MUC component *always* uses our stanza id.
28
+ error = nil
29
+ @stream.send(pres) { |r|
30
+ if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
31
+ # Error from room
32
+ error = r.error
33
+ true
34
+ # type='unavailable' may occur when the MUC kills our previous instance,
35
+ # but all join-failures should be type='error'
36
+ elsif r.from == jid and r.kind_of?(Presence) and r.type != :unavailable
37
+ # Our own presence reflected back - success
38
+ if r.x(XMUCUser) and (i = r.x(XMUCUser).items.first)
39
+ @affiliation = i.affiliation # we're interested in if it's :owner
40
+ @role = i.role # :moderator ?
41
+ end
42
+
43
+ handle_presence(r, false)
44
+ true
45
+ else
46
+ # Everything else
47
+ false
48
+ end
49
+ }
50
+
51
+ if error
52
+ deactivate
53
+ raise ServerError.new(error)
54
+ end
55
+
56
+ self
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,46 @@
1
+ module Hipbot
2
+ class Reaction < Struct.new(:robot, :regexp, :options, :block)
3
+
4
+ def invoke sender, room, message
5
+ message = message_for(message, sender)
6
+ arguments = arguments_for(message)
7
+ Response.new(robot, self, room, message).invoke(arguments)
8
+ end
9
+
10
+ def match? sender, room, message
11
+ message = message_for(message, sender)
12
+ matches?(message) && matches_scope?(message) && matches_sender?(message)
13
+ end
14
+
15
+ private
16
+
17
+ def message_for message, sender
18
+ Message.new(message, sender)
19
+ end
20
+
21
+ def arguments_for message
22
+ message.body.match(regexp)[1..-1]
23
+ end
24
+
25
+ def matches?(message)
26
+ regexp =~ message.body
27
+ end
28
+
29
+ def matches_scope?(message)
30
+ global? || message.for?(robot)
31
+ end
32
+
33
+ def matches_sender?(message)
34
+ from_all? || Array(options[:from]).include?(message.sender)
35
+ end
36
+
37
+ def global?
38
+ options[:global]
39
+ end
40
+
41
+ def from_all?
42
+ !options[:from]
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,36 @@
1
+ module Hipbot
2
+ class Response < Struct.new(:bot, :reaction, :room, :message_object)
3
+
4
+ def invoke arguments
5
+ instance_exec(*arguments, &reaction.block)
6
+ end
7
+
8
+ private
9
+ def reply string
10
+ bot.reply(room, string)
11
+ end
12
+
13
+ [:get, :post, :put, :delete].each do |http_verb|
14
+ define_method http_verb do |url, query={}, &block|
15
+ http = ::EM::HttpRequest.new(url).send(http_verb, :query => query)
16
+ http.callback { block.call(http) if block }
17
+ end
18
+ end
19
+
20
+ def first_name
21
+ message_object.sender.split(' ').first
22
+ end
23
+
24
+ def message
25
+ message_object.body
26
+ end
27
+
28
+ def sender
29
+ message_object.sender
30
+ end
31
+
32
+ def recipients
33
+ message_object.recipients
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ module Hipbot
2
+ class Room < Struct.new(:jid, :name)
3
+ attr_accessor :connection
4
+
5
+ def to_s
6
+ name
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Hipbot
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ class MyHipbot < Hipbot::Bot
4
+ configure do |config|
5
+ config.name = 'robbot'
6
+ config.jid = 'robbot@chat.hipchat.com'
7
+ end
8
+
9
+ on /^hello hipbot!$/ do
10
+ reply("hello!")
11
+ end
12
+ on /you're (.*), robot/ do |adj|
13
+ reply("I know I'm #{adj}")
14
+ end
15
+ on /hi everyone!/, global: true do
16
+ reply('hello!')
17
+ end
18
+ end
19
+
20
+ describe MyHipbot do
21
+ describe "configuration" do
22
+ it "should set robot name" do
23
+ subject.name.should == 'robbot'
24
+ end
25
+
26
+ it "should set hipchat token" do
27
+ subject.jid.should == 'robbot@chat.hipchat.com'
28
+ end
29
+ end
30
+
31
+ describe "replying" do
32
+ # TODO: replace with actual objects
33
+ let(:room) { stub_everything }
34
+ let(:sender) { stub_everything }
35
+
36
+ it "should reply to hello" do
37
+ subject.expects(:reply).with(room, 'hello!')
38
+ subject.tell(sender, room, '@robbot hello hipbot!')
39
+ end
40
+
41
+ it "should reply with argument" do
42
+ subject.expects(:reply).with(room, "I know I'm cool")
43
+ subject.tell(sender, room, "@robbot you're cool, robot")
44
+ end
45
+
46
+ it "should reply to global message" do
47
+ subject.expects(:reply).with(room, "hello!")
48
+ subject.tell(sender, room, "hi everyone!")
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ require_relative '../lib/hipbot'
2
+
3
+ RSpec.configure do |config|
4
+ config.mock_with :mocha
5
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hipbot::Bot do
4
+ context "#on" do
5
+ let(:sender) { stub_everything }
6
+ let(:room) { stub_everything }
7
+
8
+ it "should reply to no arguments" do
9
+ subject.on /^hello there$/ do
10
+ reply('hi!')
11
+ end
12
+ subject.expects(:reply).with(room, 'hi!')
13
+ subject.tell(sender, room, '@robot hello there')
14
+ end
15
+
16
+ it "should reply with one argument" do
17
+ subject.on /^you are (.*), robot$/ do |adj|
18
+ reply("i know i'm #{adj}!")
19
+ end
20
+ subject.expects(:reply).with(room, "i know i'm cool!")
21
+ subject.tell(sender, room, '@robot you are cool, robot')
22
+ end
23
+
24
+ it "should reply with multiple arguments" do
25
+ subject.on /^send "(.*)" to (.*@.*)$/ do |message, recipient|
26
+ reply("sent \"#{message}\" to #{recipient}")
27
+ end
28
+ subject.expects(:reply).with(room, 'sent "hello!" to robot@robots.org')
29
+ subject.tell(sender, room, '@robot send "hello!" to robot@robots.org')
30
+ end
31
+
32
+ it "should say when does not understand" do
33
+ subject.on /^hello there$/ do
34
+ reply('hi!')
35
+ end
36
+ subject.expects(:reply).with(room, 'I don\'t understand "hello robot!"')
37
+ subject.tell(sender, room, '@robot hello robot!')
38
+ end
39
+
40
+ it "should choose first option when multiple options match" do
41
+ subject.on /hello there/ do reply('hello there') end
42
+ subject.on /hello (.*)/ do reply('hello') end
43
+ subject.expects(:reply).with(room, 'hello there')
44
+ subject.tell(sender, room, '@robot hello there')
45
+ end
46
+
47
+ context "global messages" do
48
+ it "should reply if callback is global" do
49
+ subject.on /^you are (.*)$/, global: true do |adj|
50
+ reply("i know i'm #{adj}!")
51
+ end
52
+ subject.expects(:reply).with(room, "i know i'm cool!")
53
+ subject.tell(sender, room, 'you are cool')
54
+ end
55
+
56
+ it "should not reply if callback not global" do
57
+ subject.on /^you are (.*)$/ do |adj|
58
+ reply("i know i'm #{adj}!")
59
+ end
60
+ subject.expects(:reply).never
61
+ subject.tell(sender, room, 'you are cool')
62
+ end
63
+ end
64
+
65
+ context "messages from particular sender" do
66
+ it "should reply" do
67
+ subject.on /wazzup\?/, from: sender do
68
+ reply('Wazzup, Tom?')
69
+ end
70
+ subject.expects(:reply).with(room, 'Wazzup, Tom?')
71
+ subject.tell(sender, room, '@robot wazzup?')
72
+ end
73
+ it "should reply if sender acceptable" do
74
+ subject.on /wazzup\?/, from: [stub, sender] do
75
+ reply('wazzup, tom?')
76
+ end
77
+ subject.expects(:reply).with(room, 'wazzup, tom?')
78
+ subject.tell(sender, room, '@robot wazzup?')
79
+ end
80
+ it "should not reply if sender unacceptable" do
81
+ subject.on /wazzup\?/, from: sender do
82
+ reply('wazzup, tom?')
83
+ end
84
+ subject.expects(:reply).with(room, "I don't understand \"wazzup?\"")
85
+ subject.tell(stub, room, '@robot wazzup?')
86
+ end
87
+ it "should not reply if sender does not match" do
88
+ subject.on /wazzup\?/, from: [sender] do
89
+ reply('wazzup, tom?')
90
+ end
91
+ subject.expects(:reply).with(room, "I don't understand \"wazzup?\"")
92
+ subject.tell(stub, room, '@robot wazzup?')
93
+ end
94
+ end
95
+
96
+ context "default variables" do
97
+ it "message" do
98
+ subject.on /.*/ do
99
+ reply("you said: #{message}")
100
+ end
101
+ subject.expects(:reply).with(room, "you said: hello")
102
+ subject.tell(stub, room, "@robot hello")
103
+ end
104
+
105
+ it "sender" do
106
+ subject.on /.*/ do
107
+ reply("you are: #{sender}")
108
+ end
109
+ subject.expects(:reply).with(room, "you are: tom")
110
+ subject.tell('tom', room, "@robot hello")
111
+ end
112
+
113
+ it "recipients" do
114
+ subject.on /.*/ do
115
+ reply("recipients: #{recipients.join(', ')}")
116
+ end
117
+ subject.expects(:reply).with(room, "recipients: robot, dave")
118
+ subject.tell('tom', room, "@robot tell @dave hello from me")
119
+ end
120
+ end
121
+ end
122
+
123
+ describe "configurable options" do
124
+ Hipbot::Bot::CONFIGURABLE_OPTIONS.each do |option|
125
+ it "should delegate #{option} to configuration" do
126
+ value = stub
127
+ subject.configuration.expects(option).returns(value)
128
+ subject.send(option)
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hipbot::Message do
4
+ subject { Hipbot::Message }
5
+ let(:sender) { stub }
6
+
7
+ it "should have a body" do
8
+ message = subject.new('this is a message', sender)
9
+ message.body.should == 'this is a message'
10
+ end
11
+
12
+ it "should have a sender" do
13
+ message = subject.new('this is a message', sender)
14
+ message.sender.should == sender
15
+ end
16
+
17
+ it "should have no recipients" do
18
+ message = subject.new('this is a message', sender)
19
+ message.recipients.should be_empty
20
+ end
21
+
22
+ it "should have one recipient" do
23
+ message = subject.new('this is a message for @tom', sender)
24
+ message.recipients.should include('tom')
25
+ end
26
+
27
+ it "should have one long recipient" do
28
+ message = subject.new('message for @"tom jones", deal with it', sender)
29
+ message.recipients.should include('tom jones')
30
+ end
31
+
32
+ it "should have two recipients" do
33
+ message = subject.new('@dave, this is a message for @tom', sender)
34
+ message.recipients.should include('tom')
35
+ message.recipients.should include('dave')
36
+ end
37
+
38
+ it "should strip primary recipient from message" do
39
+ message = subject.new('@dave this is a message for @tom', sender)
40
+ message.body.should == 'this is a message for @tom'
41
+ end
42
+
43
+ it "should strip primary recipient from message with commma" do
44
+ message = subject.new('@dave, this is a message for @tom', sender)
45
+ message.body.should == 'this is a message for @tom'
46
+ end
47
+
48
+ it "should be for bot" do
49
+ bot = stub(:to_s => 'robot')
50
+ message = subject.new('hello @robot!', sender)
51
+ message.for?(bot).should be_true
52
+ end
53
+
54
+ it "should not be for bot" do
55
+ bot = stub(:to_s => 'robot')
56
+ message = subject.new('hello @tom!', sender)
57
+ message.for?(bot).should be_false
58
+ end
59
+ end
data/test_server.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'eventmachine'
2
+
3
+ $connections = []
4
+
5
+ class ChatServer < EM::Connection
6
+ def post_init
7
+ $connections << self
8
+ end
9
+
10
+ def receive_data data
11
+ messages = data.split("\n").map{ |m| "#{m}\n" }.each do |msg|
12
+ sender, room, message = *msg.strip.split(':')
13
+ $connections.reject{|c| c==self}.each do |connection|
14
+ connection.send_data msg
15
+ end
16
+ puts "#{sender}@#{room} > #{message}"
17
+ end
18
+ end
19
+ end
20
+
21
+ EM::run do
22
+ EM::start_server('0.0.0.0', 3001, ChatServer)
23
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hipbot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tomasz Pewiński
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-20 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Hipbot is a bot for HipChat, written in ruby & eventmachine.
15
+ email:
16
+ - pewniak747@gmail.com
17
+ executables:
18
+ - hipbot
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - .travis.yml
24
+ - Gemfile
25
+ - Gemfile.lock
26
+ - LICENSE
27
+ - README.md
28
+ - Rakefile
29
+ - bin/hipbot
30
+ - bot.rb
31
+ - hipbot.gemspec
32
+ - lib/hipbot.rb
33
+ - lib/hipbot/adapters/hipchat.rb
34
+ - lib/hipbot/adapters/telnet.rb
35
+ - lib/hipbot/bot.rb
36
+ - lib/hipbot/configuration.rb
37
+ - lib/hipbot/message.rb
38
+ - lib/hipbot/mucclient.rb
39
+ - lib/hipbot/reaction.rb
40
+ - lib/hipbot/response.rb
41
+ - lib/hipbot/room.rb
42
+ - lib/hipbot/version.rb
43
+ - spec/integration/hipbot_spec.rb
44
+ - spec/spec_helper.rb
45
+ - spec/unit/hipbot_spec.rb
46
+ - spec/unit/message_spec.rb
47
+ - test_server.rb
48
+ homepage: http://github.com/pewniak747/hipbot
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 1.8.23
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Hipbot is a bot for HipChat, written in ruby & eventmachine.
72
+ test_files:
73
+ - spec/integration/hipbot_spec.rb
74
+ - spec/spec_helper.rb
75
+ - spec/unit/hipbot_spec.rb
76
+ - spec/unit/message_spec.rb
77
+ has_rdoc: