hipbot 0.1.0 → 0.2.0
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/.rspec +2 -0
- data/.travis.yml +1 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +46 -25
- data/Guardfile +5 -0
- data/README.md +85 -17
- data/hipbot.gemspec +6 -5
- data/lib/hipbot.rb +4 -1
- data/lib/hipbot/adapters/hipchat/connection.rb +71 -66
- data/lib/hipbot/adapters/hipchat/hipchat.rb +1 -10
- data/lib/hipbot/adapters/telnet/telnet.rb +1 -3
- data/lib/hipbot/bot.rb +51 -34
- data/lib/hipbot/collection.rb +2 -4
- data/lib/hipbot/configuration.rb +2 -4
- data/lib/hipbot/helpers.rb +40 -0
- data/lib/hipbot/logger.rb +15 -0
- data/lib/hipbot/message.rb +1 -1
- data/lib/hipbot/patches/hipchat_client.rb +1 -1
- data/lib/hipbot/plugin.rb +11 -0
- data/lib/hipbot/reactable.rb +60 -0
- data/lib/hipbot/reaction.rb +6 -1
- data/lib/hipbot/response.rb +9 -8
- data/lib/hipbot/room.rb +3 -3
- data/lib/hipbot/user.rb +1 -1
- data/lib/hipbot/version.rb +1 -1
- data/spec/integration/hipbot_spec.rb +40 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/hipbot_spec.rb +54 -3
- metadata +35 -14
- data/lib/hipbot/http_response.rb +0 -15
@@ -2,16 +2,7 @@ module Hipbot
|
|
2
2
|
module Adapters
|
3
3
|
module Hipchat
|
4
4
|
def start!
|
5
|
-
|
6
|
-
::EM.error_handler do |e|
|
7
|
-
puts e.inspect
|
8
|
-
e.backtrace.each do |line|
|
9
|
-
puts line
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
Connection.new(self)
|
14
|
-
end
|
5
|
+
connection = Connection.new(self)
|
15
6
|
end
|
16
7
|
|
17
8
|
def method_missing(sym, *args, &block)
|
data/lib/hipbot/bot.rb
CHANGED
@@ -1,64 +1,81 @@
|
|
1
1
|
module Hipbot
|
2
|
-
class Bot
|
3
|
-
|
4
|
-
|
2
|
+
class Bot < Reactable
|
3
|
+
include Singleton
|
4
|
+
attr_accessor :configuration, :connection, :error_handler
|
5
5
|
|
6
|
-
CONFIGURABLE_OPTIONS = [:name, :jid, :password, :adapter, :helpers, :teams, :rooms]
|
6
|
+
CONFIGURABLE_OPTIONS = [:name, :jid, :password, :adapter, :helpers, :plugins, :teams, :rooms, :logger]
|
7
7
|
delegate *CONFIGURABLE_OPTIONS, to: :configuration
|
8
8
|
alias_method :to_s, :name
|
9
9
|
|
10
10
|
def initialize
|
11
|
+
super
|
11
12
|
self.configuration = Configuration.new.tap(&self.class.configuration)
|
12
|
-
self.
|
13
|
-
self.
|
14
|
-
on(*opts[0], &opts[-1])
|
15
|
-
end
|
16
|
-
on(*default_reaction[0], &default_reaction[-1]) if default_reaction.present?
|
17
|
-
extend(self.adapter || ::Hipbot::Adapters::Hipchat)
|
13
|
+
self.error_handler = self.class.error_handler
|
14
|
+
extend(self.adapter || Adapters::Hipchat)
|
18
15
|
end
|
19
16
|
|
20
|
-
def
|
21
|
-
|
22
|
-
self.reactions << Reaction.new(self, regexps, options, block)
|
17
|
+
def reactions
|
18
|
+
defined_reactions + plugin_reactions + default_reactions
|
23
19
|
end
|
24
20
|
|
25
21
|
def react sender, room, message
|
26
|
-
|
27
|
-
|
22
|
+
Hipbot.logger.info("MESSAGE from #{sender} in #{room}")
|
23
|
+
matching_reactions(sender, room, message) do |matches|
|
24
|
+
Hipbot.logger.info("REACTION #{matches.first.inspect}")
|
25
|
+
matches.first.invoke(sender, room, message)
|
26
|
+
end
|
28
27
|
end
|
29
28
|
|
30
29
|
class << self
|
31
|
-
|
32
|
-
@reactions ||= []
|
33
|
-
@reactions << [regexps, block]
|
34
|
-
end
|
30
|
+
ACCESSORS = { configure: :configuration, on_preload: :preloader, on_error: :error_handler }
|
35
31
|
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
ACCESSORS.each do |setter, getter|
|
33
|
+
define_method(setter) do |&block|
|
34
|
+
instance_variable_set("@#{getter}", block)
|
35
|
+
end
|
39
36
|
|
40
|
-
|
41
|
-
|
37
|
+
define_method(getter) do
|
38
|
+
instance_variable_get("@#{getter}") || Proc.new{}
|
39
|
+
end
|
42
40
|
end
|
43
41
|
|
44
|
-
def
|
45
|
-
|
42
|
+
def start!
|
43
|
+
::EM::run do
|
44
|
+
Jabber.debug = true
|
45
|
+
Jabber.logger = Hipbot::Bot.instance.logger
|
46
|
+
Helpers.module_exec(&preloader)
|
47
|
+
instance.start!
|
48
|
+
end
|
46
49
|
end
|
50
|
+
end
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
|
52
|
+
def plugin_for(reaction)
|
53
|
+
included_plugins.find { |p| p.defined_reactions.include?(reaction) }
|
54
|
+
end
|
51
55
|
|
52
|
-
|
53
|
-
|
56
|
+
private
|
57
|
+
|
58
|
+
def plugin_reactions
|
59
|
+
included_plugins.map(&:defined_reactions).flatten
|
60
|
+
end
|
61
|
+
|
62
|
+
def included_plugins
|
63
|
+
@included_plugins ||= begin
|
64
|
+
Array(plugins).map do |object|
|
65
|
+
plugin = object.kind_of?(Plugin) ? object : object.new
|
66
|
+
plugin.bot = self
|
67
|
+
plugin
|
68
|
+
end
|
54
69
|
end
|
55
70
|
end
|
56
71
|
|
57
|
-
|
72
|
+
def default_reactions
|
73
|
+
super + included_plugins.flat_map(&:default_reactions)
|
74
|
+
end
|
58
75
|
|
59
76
|
def matching_reactions sender, room, message
|
60
|
-
|
77
|
+
matches = reactions.select{ |r| r.match?(sender, room, message) }
|
78
|
+
yield matches if matches.any?
|
61
79
|
end
|
62
|
-
|
63
80
|
end
|
64
81
|
end
|
data/lib/hipbot/collection.rb
CHANGED
@@ -13,8 +13,6 @@ module Hipbot
|
|
13
13
|
end
|
14
14
|
|
15
15
|
class << self
|
16
|
-
attr_accessor :bot
|
17
|
-
|
18
16
|
def create *args, &block
|
19
17
|
collection[args[0]] = new(*args, &block)
|
20
18
|
end
|
@@ -28,7 +26,7 @@ module Hipbot
|
|
28
26
|
end
|
29
27
|
|
30
28
|
def find_one item
|
31
|
-
collection[item] ||
|
29
|
+
collection[item] || find{ |i| i.name == item }
|
32
30
|
end
|
33
31
|
|
34
32
|
def find_many *items
|
@@ -39,7 +37,7 @@ module Hipbot
|
|
39
37
|
protected
|
40
38
|
|
41
39
|
def method_missing name, *args, &block
|
42
|
-
return collection.public_send(name, *args, &block) if
|
40
|
+
return collection.values.public_send(name, *args, &block) if Array.instance_methods.include?(name)
|
43
41
|
super
|
44
42
|
end
|
45
43
|
end
|
data/lib/hipbot/configuration.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Hipbot
|
2
|
+
module Helpers
|
3
|
+
[:get, :post, :put, :delete].each do |http_verb|
|
4
|
+
define_method http_verb do |url, query = {}, &block|
|
5
|
+
Hipbot.logger.info("HTTP-REQUEST: #{url} #{query}")
|
6
|
+
query.merge!({ :head => {'accept-encoding' => 'gzip, compressed'} })
|
7
|
+
conn = ::EM::HttpRequest.new(url, :connect_timeout => 5, :inactivity_timeout => 10)
|
8
|
+
http = conn.send(http_verb, :query => query)
|
9
|
+
http.callback do
|
10
|
+
response = HttpResponse.new(http)
|
11
|
+
Hipbot.logger.info("HTTP-RESPONSE: #{response}")
|
12
|
+
block.call(response)
|
13
|
+
end if block.present?
|
14
|
+
http.errback do
|
15
|
+
Hipbot.logger.error("HTTP-RESPONSE-ERROR: #{url}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module_function http_verb
|
20
|
+
end
|
21
|
+
|
22
|
+
class HttpResponse < Struct.new(:raw_response)
|
23
|
+
def body
|
24
|
+
raw_response.response
|
25
|
+
end
|
26
|
+
|
27
|
+
def code
|
28
|
+
raw_response.response_header.status
|
29
|
+
end
|
30
|
+
|
31
|
+
def headers
|
32
|
+
raw_response.response_header
|
33
|
+
end
|
34
|
+
|
35
|
+
def json
|
36
|
+
@json ||= JSON.parse(body) rescue {}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Hipbot
|
4
|
+
class Logger < ::Logger
|
5
|
+
def add(severity, message = nil, progname = nil, &block)
|
6
|
+
msg = message || (block_given? and block.call) || progname
|
7
|
+
severity_name = { 0 => "DEBUG", 1 => "INFO", 2 => "WARN", 3 => "ERROR", 4 => "FATAL", 5 => "UNKNOWN" }[severity]
|
8
|
+
super(severity, "[#{severity_name}][#{Time.now}] #{msg}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.logger
|
13
|
+
Hipbot::Bot.instance.logger
|
14
|
+
end
|
15
|
+
end
|
data/lib/hipbot/message.rb
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
module Hipbot
|
2
|
+
class Reactable
|
3
|
+
def on *regexps, &block
|
4
|
+
@defined_reactions ||= []
|
5
|
+
@defined_reactions << to_reaction(regexps, block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def defined_reactions
|
9
|
+
@defined_reactions ||= []
|
10
|
+
@defined_reactions + class_reactions
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_reactions
|
14
|
+
@default_reactions ||= begin
|
15
|
+
if reaction = self.class.default_reaction
|
16
|
+
[ to_reaction(reaction[0], reaction[-1]) ]
|
17
|
+
else
|
18
|
+
[]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def on *regexps, &block
|
25
|
+
@reactions ||= []
|
26
|
+
@reactions << [regexps, block]
|
27
|
+
end
|
28
|
+
|
29
|
+
def reactions
|
30
|
+
@reactions || []
|
31
|
+
end
|
32
|
+
|
33
|
+
def default &block
|
34
|
+
@default_reaction = [[/(.*)/], block]
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_reaction
|
38
|
+
@default_reaction
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def class_reactions
|
45
|
+
@class_reactions ||= self.class.reactions.map do |opts|
|
46
|
+
to_reaction(opts[0], opts[-1])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_reaction(regexps, block)
|
51
|
+
regexps = regexps.dup
|
52
|
+
options = regexps[-1].kind_of?(Hash) ? regexps.pop : {}
|
53
|
+
Reaction.new(reaction_target, regexps, options, block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def reaction_target
|
57
|
+
self
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/hipbot/reaction.rb
CHANGED
@@ -12,6 +12,10 @@ module Hipbot
|
|
12
12
|
matches_regexp?(message) && matches_scope?(room, message) && matches_sender?(message) && matches_room?(room)
|
13
13
|
end
|
14
14
|
|
15
|
+
def inspect
|
16
|
+
"#<Hipbot::Reaction #{regexps} #{options}>"
|
17
|
+
end
|
18
|
+
|
15
19
|
protected
|
16
20
|
|
17
21
|
def message_for message, sender
|
@@ -35,7 +39,8 @@ module Hipbot
|
|
35
39
|
end
|
36
40
|
|
37
41
|
def matches_room?(room)
|
38
|
-
|
42
|
+
return true if options[:room].nil?
|
43
|
+
room.present? && (rooms.include?(room.name) || options[:room] == true)
|
39
44
|
end
|
40
45
|
|
41
46
|
def matches_sender?(message)
|
data/lib/hipbot/response.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Hipbot
|
2
2
|
class Response < Struct.new(:bot, :reaction, :room, :message)
|
3
3
|
delegate :sender, :recipients, :body, :to => :message
|
4
|
+
include Helpers
|
4
5
|
|
5
6
|
def initialize bot, reaction, room, message
|
6
7
|
super
|
@@ -9,20 +10,20 @@ module Hipbot
|
|
9
10
|
|
10
11
|
def invoke arguments
|
11
12
|
instance_exec(*arguments, &reaction.block)
|
13
|
+
rescue Exception => e
|
14
|
+
Hipbot.logger.error(e)
|
15
|
+
instance_exec(e, &bot.error_handler)
|
12
16
|
end
|
13
17
|
|
14
18
|
private
|
15
19
|
|
16
|
-
def reply
|
17
|
-
|
18
|
-
bot.send_to_room(room,
|
20
|
+
def reply message, room = self.room
|
21
|
+
Hipbot.logger.info("REPLY in #{room}: #{message}")
|
22
|
+
room.nil? ? bot.send_to_user(sender, message) : bot.send_to_room(room, message)
|
19
23
|
end
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
http = ::EM::HttpRequest.new(url).send(http_verb, :query => query)
|
24
|
-
http.callback { block.call(::Hipbot::HttpResponse.new(http)) if block }
|
25
|
-
end
|
25
|
+
def plugin
|
26
|
+
bot.plugin_for(reaction)
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
data/lib/hipbot/room.rb
CHANGED
@@ -8,15 +8,15 @@ module Hipbot
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def set_topic topic
|
11
|
-
|
11
|
+
Bot.instance.set_topic(self, topic)
|
12
12
|
end
|
13
13
|
|
14
14
|
def send_message message
|
15
|
-
|
15
|
+
Bot.instance.send_to_room(self, message)
|
16
16
|
end
|
17
17
|
|
18
18
|
def users
|
19
|
-
user_ids
|
19
|
+
User.find_many(user_ids)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/lib/hipbot/user.rb
CHANGED
data/lib/hipbot/version.rb
CHANGED
@@ -10,32 +10,54 @@ module HipbotHelpers
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
class AwesomePlugin < Hipbot::Plugin
|
14
|
+
on /respond awesome/ do
|
15
|
+
reply("awesome responded")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class CoolPlugin < Hipbot::Plugin
|
20
|
+
on /respond cool/ do
|
21
|
+
reply("cool responded")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
13
25
|
class MyHipbot < Hipbot::Bot
|
14
26
|
configure do |config|
|
15
27
|
config.name = 'robbot'
|
16
28
|
config.jid = 'robbot@chat.hipchat.com'
|
17
29
|
config.helpers = HipbotHelpers
|
30
|
+
config.plugins = [ AwesomePlugin, CoolPlugin.new ]
|
18
31
|
end
|
19
32
|
|
20
33
|
on /^hello hipbot!$/ do
|
21
34
|
reply("hello!")
|
22
35
|
end
|
36
|
+
|
23
37
|
on /you're (.*), robot/ do |adj|
|
24
38
|
reply("I know I'm #{adj}")
|
25
39
|
end
|
40
|
+
|
26
41
|
on /hi everyone!/, global: true do
|
27
42
|
reply('hello!')
|
28
43
|
end
|
44
|
+
|
29
45
|
on /tell me the project name/ do
|
30
46
|
reply(project_name)
|
31
47
|
end
|
48
|
+
|
32
49
|
on /tell me my name/ do
|
33
50
|
reply("you are #{sender.first_name}")
|
34
51
|
end
|
52
|
+
|
53
|
+
default do
|
54
|
+
reply("I didn't understand you")
|
55
|
+
end
|
35
56
|
end
|
36
57
|
|
37
58
|
describe MyHipbot do
|
38
|
-
|
59
|
+
subject { described_class.instance }
|
60
|
+
|
39
61
|
let(:room) { Hipbot::Room.create('1', 'private', topic: 'topic') }
|
40
62
|
let(:sender) { Hipbot::User.create('1', 'John Doe') }
|
41
63
|
|
@@ -64,6 +86,11 @@ describe MyHipbot do
|
|
64
86
|
subject.expects(:send_to_room).with(room, "hello!")
|
65
87
|
subject.react(sender, room, "hi everyone!")
|
66
88
|
end
|
89
|
+
|
90
|
+
it "should respond with default reply" do
|
91
|
+
subject.expects(:send_to_room).with(room, "I didn't understand you")
|
92
|
+
subject.react(sender, room, "@robbot blahlblah")
|
93
|
+
end
|
67
94
|
end
|
68
95
|
|
69
96
|
describe "custom helpers" do
|
@@ -77,4 +104,16 @@ describe MyHipbot do
|
|
77
104
|
subject.react(sender, room, '@robbot tell me my name')
|
78
105
|
end
|
79
106
|
end
|
107
|
+
|
108
|
+
describe "plugins" do
|
109
|
+
it "should reply to reaction defined in plugin" do
|
110
|
+
subject.expects(:send_to_room).with(room, 'awesome responded')
|
111
|
+
subject.react(sender, room, '@robbot respond awesome')
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should reply to reaction defined in second plugin" do
|
115
|
+
subject.expects(:send_to_room).with(room, 'cool responded')
|
116
|
+
subject.react(sender, room, '@robbot respond cool')
|
117
|
+
end
|
118
|
+
end
|
80
119
|
end
|