hipbot 0.2.0 → 1.0.0.rc1
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 +2 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +15 -0
- data/README.md +28 -20
- data/hipbot.gemspec +2 -2
- data/lib/hipbot.rb +1 -0
- data/lib/hipbot/adapters/hipchat/connection.rb +46 -41
- data/lib/hipbot/adapters/hipchat/hipchat.rb +1 -1
- data/lib/hipbot/adapters/telnet/connection.rb +4 -5
- data/lib/hipbot/bot.rb +61 -48
- data/lib/hipbot/collection.rb +34 -9
- data/lib/hipbot/configuration.rb +15 -7
- data/lib/hipbot/helpers.rb +10 -4
- data/lib/hipbot/logger.rb +0 -4
- data/lib/hipbot/match.rb +48 -0
- data/lib/hipbot/message.rb +13 -13
- data/lib/hipbot/patches/hipchat_client.rb +50 -12
- data/lib/hipbot/plugin.rb +24 -5
- data/lib/hipbot/reactable.rb +27 -42
- data/lib/hipbot/reaction.rb +33 -34
- data/lib/hipbot/response.rb +14 -11
- data/lib/hipbot/room.rb +4 -11
- data/lib/hipbot/user.rb +8 -2
- data/lib/hipbot/version.rb +1 -1
- data/spec/integration/hipbot_spec.rb +96 -68
- data/spec/integration/my_hipbot.rb +104 -0
- data/spec/spec_helper.rb +3 -2
- data/spec/unit/hipbot_spec.rb +45 -34
- data/spec/unit/message_spec.rb +17 -18
- metadata +10 -5
data/lib/hipbot/bot.rb
CHANGED
@@ -1,81 +1,94 @@
|
|
1
1
|
module Hipbot
|
2
|
-
class
|
2
|
+
class << self
|
3
|
+
attr_accessor :bot, :plugins
|
4
|
+
delegate :name, to: :bot
|
5
|
+
|
6
|
+
def plugins
|
7
|
+
@plugins ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing name, *params, &block
|
11
|
+
bot.send(name, *params, &block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Bot
|
16
|
+
extend Reactable
|
3
17
|
include Singleton
|
4
|
-
attr_accessor :configuration, :connection, :error_handler
|
5
18
|
|
6
|
-
|
19
|
+
attr_accessor :configuration, :connection
|
20
|
+
|
21
|
+
CONFIGURABLE_OPTIONS = [:adapter, :error_handler, :helpers, :jid, :logger, :password, :plugins, :preloader, :rooms, :storage, :teams, :user]
|
7
22
|
delegate *CONFIGURABLE_OPTIONS, to: :configuration
|
23
|
+
delegate :name, to: :user
|
8
24
|
alias_method :to_s, :name
|
9
25
|
|
10
26
|
def initialize
|
11
|
-
|
12
|
-
self.configuration = Configuration.new.tap(&self.class.configuration)
|
13
|
-
self.error_handler = self.class.error_handler
|
14
|
-
extend(self.adapter || Adapters::Hipchat)
|
27
|
+
self.configuration ||= Configuration.new
|
15
28
|
end
|
16
29
|
|
17
|
-
def
|
18
|
-
|
30
|
+
def react sender, room, message
|
31
|
+
message = Message.new(message, room, sender)
|
32
|
+
matching_reactions(message, sender.reactions, plugin_reactions, default_reactions).each(&:invoke)
|
19
33
|
end
|
20
34
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
35
|
+
def setup
|
36
|
+
extend adapter
|
37
|
+
Hipbot.bot = self
|
38
|
+
|
39
|
+
User.send(:include, storage)
|
40
|
+
Room.send(:include, storage)
|
41
|
+
Response.send(:include, helpers)
|
42
|
+
|
43
|
+
helpers.module_exec(&preloader)
|
44
|
+
plugins.append(self)
|
45
|
+
Jabber.debug = true
|
46
|
+
Jabber.logger = logger
|
47
|
+
end
|
48
|
+
|
49
|
+
def plugin_reactions
|
50
|
+
plugins.flat_map{ |p| p.class.reactions }
|
51
|
+
end
|
52
|
+
|
53
|
+
def default_reactions
|
54
|
+
plugins.flat_map{ |p| p.class.default_reactions }
|
27
55
|
end
|
28
56
|
|
29
57
|
class << self
|
30
|
-
|
58
|
+
def configure &block
|
59
|
+
instance.configuration = Configuration.new.tap(&block)
|
60
|
+
end
|
31
61
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
62
|
+
def on_preload &block
|
63
|
+
instance.configuration.preloader = block
|
64
|
+
end
|
36
65
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
66
|
+
def on_error &block
|
67
|
+
instance.configuration.error_handler = block
|
40
68
|
end
|
41
69
|
|
42
70
|
def start!
|
43
71
|
::EM::run do
|
44
|
-
|
45
|
-
Jabber.logger = Hipbot::Bot.instance.logger
|
46
|
-
Helpers.module_exec(&preloader)
|
72
|
+
instance.setup
|
47
73
|
instance.start!
|
48
74
|
end
|
49
75
|
end
|
50
76
|
end
|
51
77
|
|
52
|
-
|
53
|
-
included_plugins.find { |p| p.defined_reactions.include?(reaction) }
|
54
|
-
end
|
78
|
+
protected
|
55
79
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
80
|
+
def matching_reactions message, *reaction_sets
|
81
|
+
reaction_sets.each do |reactions|
|
82
|
+
matches = reactions.map{ |reaction| matching_rection(message, reaction) }.compact
|
83
|
+
return matches if matches.any?
|
69
84
|
end
|
85
|
+
[]
|
70
86
|
end
|
71
87
|
|
72
|
-
def
|
73
|
-
|
88
|
+
def matching_rection message, reaction
|
89
|
+
match = reaction.match_with(message)
|
90
|
+
match.matches? ? match : nil
|
74
91
|
end
|
75
92
|
|
76
|
-
def matching_reactions sender, room, message
|
77
|
-
matches = reactions.select{ |r| r.match?(sender, room, message) }
|
78
|
-
yield matches if matches.any?
|
79
|
-
end
|
80
93
|
end
|
81
94
|
end
|
data/lib/hipbot/collection.rb
CHANGED
@@ -1,20 +1,41 @@
|
|
1
1
|
module Hipbot
|
2
|
-
|
3
|
-
|
4
|
-
alias_method :to_s, :name
|
2
|
+
module Collection
|
3
|
+
extend ActiveSupport::Concern
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
included do
|
6
|
+
extend ClassMethods
|
7
|
+
|
8
|
+
attr_accessor :id, :name, :attributes
|
9
|
+
alias_method :to_s, :name
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize params
|
13
|
+
self.id = params.delete(:id)
|
14
|
+
self.name = params.delete(:name)
|
15
|
+
self.attributes = params
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_attribute key, value
|
19
|
+
if key == :name
|
20
|
+
self.name = value
|
21
|
+
else
|
22
|
+
self.attributes[key] = value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_attributes hash
|
27
|
+
hash.each do |k, v|
|
28
|
+
update_attribute k, v
|
29
|
+
end
|
9
30
|
end
|
10
31
|
|
11
32
|
def delete
|
12
33
|
self.class.collection.delete(self.id)
|
13
34
|
end
|
14
35
|
|
15
|
-
|
16
|
-
def create
|
17
|
-
collection[
|
36
|
+
module ClassMethods
|
37
|
+
def create params, &block
|
38
|
+
collection[params[:id]] = new(params, &block)
|
18
39
|
end
|
19
40
|
|
20
41
|
def collection
|
@@ -34,6 +55,10 @@ module Hipbot
|
|
34
55
|
items.map{ |i| find_one(i) }.compact.uniq
|
35
56
|
end
|
36
57
|
|
58
|
+
def find_or_create_by params
|
59
|
+
find_one(params[:id] || params[:name]) || create(params)
|
60
|
+
end
|
61
|
+
|
37
62
|
protected
|
38
63
|
|
39
64
|
def method_missing name, *args, &block
|
data/lib/hipbot/configuration.rb
CHANGED
@@ -3,13 +3,21 @@ module Hipbot
|
|
3
3
|
attr_accessor *Bot::CONFIGURABLE_OPTIONS
|
4
4
|
|
5
5
|
def initialize
|
6
|
-
self.
|
7
|
-
self.
|
8
|
-
self.
|
9
|
-
self.
|
10
|
-
self.
|
11
|
-
self.
|
12
|
-
self.
|
6
|
+
self.adapter = Adapters::Hipchat
|
7
|
+
self.error_handler = Proc.new{}
|
8
|
+
self.helpers = Module.new
|
9
|
+
self.jid = ''
|
10
|
+
self.logger = Logger.new($stdout)
|
11
|
+
self.password = ''
|
12
|
+
self.plugins = Hipbot.plugins
|
13
|
+
self.preloader = Proc.new{}
|
14
|
+
self.rooms = {}
|
15
|
+
self.storage = Collection
|
16
|
+
self.teams = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def user
|
20
|
+
@user ||= User.new(name: 'robot')
|
13
21
|
end
|
14
22
|
end
|
15
23
|
end
|
data/lib/hipbot/helpers.rb
CHANGED
@@ -7,10 +7,16 @@ module Hipbot
|
|
7
7
|
conn = ::EM::HttpRequest.new(url, :connect_timeout => 5, :inactivity_timeout => 10)
|
8
8
|
http = conn.send(http_verb, :query => query)
|
9
9
|
http.callback do
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
begin
|
11
|
+
response = HttpResponse.new(http)
|
12
|
+
Hipbot.logger.info("HTTP-RESPONSE: #{response}")
|
13
|
+
block.call(response)
|
14
|
+
rescue => e
|
15
|
+
Hipbot.logger.error(e)
|
16
|
+
instance_exec(e, &Hipbot.error_handler)
|
17
|
+
end
|
13
18
|
end if block.present?
|
19
|
+
|
14
20
|
http.errback do
|
15
21
|
Hipbot.logger.error("HTTP-RESPONSE-ERROR: #{url}")
|
16
22
|
end
|
@@ -33,7 +39,7 @@ module Hipbot
|
|
33
39
|
end
|
34
40
|
|
35
41
|
def json
|
36
|
-
@json ||= JSON.parse(body)
|
42
|
+
@json ||= JSON.parse(body)
|
37
43
|
end
|
38
44
|
end
|
39
45
|
end
|
data/lib/hipbot/logger.rb
CHANGED
data/lib/hipbot/match.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Hipbot
|
2
|
+
class Match < Struct.new(:reaction, :message)
|
3
|
+
def matches?
|
4
|
+
matches_regexp? && matches_scope? && matches_sender? && matches_place?
|
5
|
+
end
|
6
|
+
|
7
|
+
def invoke
|
8
|
+
Response.new(reaction, message).invoke(params)
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def params
|
14
|
+
reaction.anything? ? [message.body] : regexp_match[1..-1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches_regexp?
|
18
|
+
reaction.anything? || regexp_match.present?
|
19
|
+
end
|
20
|
+
|
21
|
+
def regexp_match
|
22
|
+
@regexp_match ||= reaction.regexps.inject(nil) do |result, regexp|
|
23
|
+
break result if result
|
24
|
+
message_text.match(regexp)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def matches_scope?
|
29
|
+
reaction.global? || message.for?(Hipbot.user) || message.private?
|
30
|
+
end
|
31
|
+
|
32
|
+
def matches_place?
|
33
|
+
reaction.anywhere? || (message.room.present? ? matches_room? : reaction.private_message_only?)
|
34
|
+
end
|
35
|
+
|
36
|
+
def matches_room?
|
37
|
+
reaction.any_room? || reaction.rooms.include?(message.room.name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def matches_sender?
|
41
|
+
reaction.from_all? || reaction.users.include?(message.sender.name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def message_text
|
45
|
+
reaction.global? ? message.raw_body : message.body
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/hipbot/message.rb
CHANGED
@@ -1,28 +1,28 @@
|
|
1
1
|
module Hipbot
|
2
|
-
class Message
|
3
|
-
|
2
|
+
class Message < Struct.new(:raw_body, :room, :sender)
|
3
|
+
attr_accessor :body, :recipients
|
4
4
|
|
5
|
-
def initialize
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def recipients
|
12
|
-
results = raw_body.scan(/@(\w+)/) + raw_body.scan(/@"(.*)"/)
|
13
|
-
results.flatten.uniq
|
5
|
+
def initialize *args
|
6
|
+
super
|
7
|
+
Hipbot.logger.info("MESSAGE from #{sender} in #{room}")
|
8
|
+
self.body = strip_recipient(raw_body)
|
9
|
+
self.recipients = raw_body.scan(/@(\p{Word}++)/).flatten.compact.uniq
|
14
10
|
end
|
15
11
|
|
16
12
|
def for? recipient
|
17
|
-
recipients.include? recipient.
|
13
|
+
recipients.include? recipient.mention
|
18
14
|
end
|
19
15
|
|
20
16
|
def strip_recipient body
|
21
|
-
body.gsub(/^@\
|
17
|
+
body.gsub(/^@\p{Word}++[^\p{Word}]*/, '').strip
|
22
18
|
end
|
23
19
|
|
24
20
|
def mentions
|
25
21
|
recipients[1..-1] || [] # TODO: Fix global message case
|
26
22
|
end
|
23
|
+
|
24
|
+
def private?
|
25
|
+
room.nil?
|
26
|
+
end
|
27
27
|
end
|
28
28
|
end
|
@@ -52,6 +52,10 @@ module Jabber
|
|
52
52
|
@my_jid.resource
|
53
53
|
end
|
54
54
|
|
55
|
+
def name= resource
|
56
|
+
@my_jid.resource = resource
|
57
|
+
end
|
58
|
+
|
55
59
|
def on_presence(prio = 0, ref = nil, &block)
|
56
60
|
@presence_cbs.add(prio, ref) do |room_jid, user_name, pres_type|
|
57
61
|
block.call(room_jid, user_name, pres_type)
|
@@ -89,6 +93,30 @@ module Jabber
|
|
89
93
|
@stream.send(pres) { |r| block.call(r) }
|
90
94
|
end
|
91
95
|
|
96
|
+
def kick(recipients, room_jid)
|
97
|
+
iq = Iq.new(:set, room_jid)
|
98
|
+
iq.from = @my_jid
|
99
|
+
iq.add(IqQueryMUCAdmin.new)
|
100
|
+
recipients.each do |recipient|
|
101
|
+
item = IqQueryMUCAdminItem.new
|
102
|
+
item.nick = recipient
|
103
|
+
item.role = :none
|
104
|
+
iq.query.add(item)
|
105
|
+
end
|
106
|
+
@stream.send_with_id(iq)
|
107
|
+
end
|
108
|
+
|
109
|
+
def invite(recipients, room_jid)
|
110
|
+
msg = Message.new
|
111
|
+
msg.from = @my_jid
|
112
|
+
msg.to = room_jid
|
113
|
+
x = msg.add(XMUCUser.new)
|
114
|
+
recipients.each do |jid|
|
115
|
+
x.add(XMUCUserInvite.new(jid))
|
116
|
+
end
|
117
|
+
@stream.send(msg)
|
118
|
+
end
|
119
|
+
|
92
120
|
def send_message(type, jid, text, subject = nil)
|
93
121
|
message = Message.new(JID.new(jid), text.to_s)
|
94
122
|
message.type = type
|
@@ -112,6 +140,8 @@ module Jabber
|
|
112
140
|
Jabber::debuglog "MUCBrowser initialized"
|
113
141
|
@conference_domain = @muc_browser.muc_rooms(@chat_domain).keys.first
|
114
142
|
Jabber::debuglog "No conference domain found" if !@conference_domain.present?
|
143
|
+
@roster = Roster::Helper.new(@stream) # TODO: Error handling
|
144
|
+
@vcard = Vcard::Helper.new(@stream) # TODO: Error handling
|
115
145
|
true
|
116
146
|
rescue => e
|
117
147
|
Jabber::debuglog "Connection failed"
|
@@ -121,7 +151,7 @@ module Jabber
|
|
121
151
|
|
122
152
|
def activate_callbacks
|
123
153
|
@stream.add_presence_callback(150, self) { |presence|
|
124
|
-
@presence_cbs.process(presence.from.strip, presence.from.resource, presence.type.to_s)
|
154
|
+
@presence_cbs.process(presence.from.strip.to_s, presence.from.resource, presence.type.to_s)
|
125
155
|
}
|
126
156
|
|
127
157
|
@stream.add_message_callback(150, self) { |message|
|
@@ -141,7 +171,7 @@ module Jabber
|
|
141
171
|
details = {}
|
142
172
|
item.first.children.each{ |c| details[c.name] = c.text }
|
143
173
|
rooms << {
|
144
|
-
:item
|
174
|
+
:item => item,
|
145
175
|
:details => details
|
146
176
|
}
|
147
177
|
end
|
@@ -150,15 +180,23 @@ module Jabber
|
|
150
180
|
end
|
151
181
|
|
152
182
|
def get_users
|
153
|
-
roster
|
154
|
-
|
155
|
-
roster.wait_for_roster
|
156
|
-
roster.items.map do |jid, item|
|
183
|
+
@roster.wait_for_roster
|
184
|
+
@roster.items.map do |jid, item|
|
157
185
|
{
|
158
|
-
|
159
|
-
|
186
|
+
jid: item.jid.to_s,
|
187
|
+
name: item.iname,
|
188
|
+
mention: item.attributes['mention_name'],
|
160
189
|
}
|
161
|
-
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def get_user_details user_jid
|
194
|
+
vcard = @vcard.get(user_jid)
|
195
|
+
{
|
196
|
+
email: vcard['EMAIL/USERID'],
|
197
|
+
title: vcard['TITLE'],
|
198
|
+
photo: vcard['PHOTO'],
|
199
|
+
}
|
162
200
|
end
|
163
201
|
|
164
202
|
def deactivate_callbacks
|
@@ -173,11 +211,11 @@ module Jabber
|
|
173
211
|
if is_invite?(message)
|
174
212
|
room_name = message.children.last.first_element_text('name')
|
175
213
|
topic = message.children.last.first_element_text('topic')
|
176
|
-
@invite_cbs.process(message.from.strip, message.from.resource, room_name, topic)
|
214
|
+
@invite_cbs.process(message.from.strip.to_s, message.from.resource, room_name, topic)
|
177
215
|
elsif message.type == :chat
|
178
|
-
@private_message_cbs.process(message.from.strip, message)
|
216
|
+
@private_message_cbs.process(message.from.strip.to_s, message)
|
179
217
|
elsif message.type == :groupchat
|
180
|
-
@message_cbs.process(message.from.strip, message.from.resource, message)
|
218
|
+
@message_cbs.process(message.from.strip.to_s, message.from.resource, message)
|
181
219
|
elsif message.type == :error
|
182
220
|
false
|
183
221
|
end
|