hipbot 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: