hipbot 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
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
+ def start!
9
+ ::EM::run do
10
+ ::EM::connect('0.0.0.0', 3001, Connection, self)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
data/lib/hipbot/bot.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Hipbot
2
2
  class Bot
3
3
  attr_accessor :reactions, :configuration, :connection
4
- CONFIGURABLE_OPTIONS = [:name, :jid, :password, :adapter]
4
+ CONFIGURABLE_OPTIONS = [:name, :jid, :password, :adapter, :helpers]
5
5
  delegate *CONFIGURABLE_OPTIONS, to: :configuration
6
6
  alias_method :to_s, :name
7
7
 
@@ -9,20 +9,21 @@ module Hipbot
9
9
  self.configuration = Configuration.new.tap(&self.class.configuration)
10
10
  self.reactions = []
11
11
  self.class.reactions.each do |opts|
12
- on(opts[0], opts[1], &opts[-1])
12
+ on(*opts[0], &opts[-1])
13
13
  end
14
14
  extend(self.adapter || ::Hipbot::Adapters::Hipchat)
15
15
  end
16
16
 
17
- def on regexp, options={}, &block
18
- self.reactions << Reaction.new(self, regexp, options, block)
17
+ def on *regexps, &block
18
+ options = regexps[-1].kind_of?(Hash) ? regexps.pop : {}
19
+ self.reactions << Reaction.new(self, regexps, options, block)
19
20
  end
20
21
 
21
22
  def tell sender, room, message
22
23
  return if sender == name
23
24
  matches = matching_reactions(sender, room, message)
24
25
  if matches.size > 0
25
- matches[0].invoke(sender, room, message)
26
+ matches.first.invoke(sender, room, message)
26
27
  end
27
28
  end
28
29
 
@@ -32,9 +33,9 @@ module Hipbot
32
33
 
33
34
  class << self
34
35
 
35
- def on regexp, options={}, &block
36
+ def on *regexps, &block
36
37
  @reactions ||= []
37
- @reactions << [regexp, options, block]
38
+ @reactions << [regexps, block]
38
39
  end
39
40
 
40
41
  def configure &block
@@ -63,8 +64,8 @@ module Hipbot
63
64
  end
64
65
 
65
66
  def default_reaction
66
- @default_reaction ||= Reaction.new(self, /.*/, {}, Proc.new {
67
- reply("I don't understand \"#{message}\"")
67
+ @default_reaction ||= Reaction.new(self, [/.*/], {}, Proc.new {
68
+ reply("I don't understand \"#{message.body}\"")
68
69
  })
69
70
  end
70
71
 
@@ -8,5 +8,8 @@ module Hipbot
8
8
  self.password = 'changeme'
9
9
  end
10
10
 
11
+ def helpers
12
+ @helpers ||= Module.new
13
+ end
11
14
  end
12
15
  end
@@ -0,0 +1,15 @@
1
+ module Hipbot
2
+ class HttpResponse < Struct.new(:raw_response)
3
+ def body
4
+ raw_response.response
5
+ end
6
+
7
+ def code
8
+ raw_response.response_header.status
9
+ end
10
+
11
+ def headers
12
+ raw_response.response_header
13
+ end
14
+ end
15
+ end
@@ -9,7 +9,7 @@ module Hipbot
9
9
 
10
10
  def recipients
11
11
  results = raw_body.scan(/@(\w+)/) + raw_body.scan(/@"(.*)"/)
12
- results.flatten
12
+ results.flatten.uniq
13
13
  end
14
14
 
15
15
  def for? recipient
@@ -19,5 +19,13 @@ module Hipbot
19
19
  def strip_recipient body
20
20
  body.gsub(/^@\w+\W*/, '')
21
21
  end
22
+
23
+ def sender_name
24
+ sender.split.first
25
+ end
26
+
27
+ def mentions
28
+ recipients[1..-1]
29
+ end
22
30
  end
23
31
  end
File without changes
@@ -0,0 +1,147 @@
1
+ module Jabber
2
+ module MUC
3
+
4
+ class MUCClient
5
+
6
+ def join(jid, password=nil, opts={})
7
+ if active?
8
+ raise "MUCClient already active"
9
+ end
10
+
11
+ @jid = (jid.kind_of?(JID) ? jid : JID.new(jid))
12
+ activate
13
+
14
+ # Joining
15
+ pres = Presence.new
16
+ pres.to = @jid
17
+ pres.from = @my_jid
18
+ xmuc = XMUC.new
19
+ xmuc.password = password
20
+
21
+ if !opts[:history]
22
+ history = REXML::Element.new( 'history').tap {|h| h.add_attribute('maxstanzas','0') }
23
+ xmuc.add_element history
24
+ end
25
+
26
+ pres.add(xmuc)
27
+
28
+ # We don't use Stream#send_with_id here as it's unknown
29
+ # if the MUC component *always* uses our stanza id.
30
+ error = nil
31
+ @stream.send(pres) { |r|
32
+ if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
33
+ # Error from room
34
+ error = r.error
35
+ true
36
+ # type='unavailable' may occur when the MUC kills our previous instance,
37
+ # but all join-failures should be type='error'
38
+ elsif r.from == jid and r.kind_of?(Presence) and r.type != :unavailable
39
+ # Our own presence reflected back - success
40
+ if r.x(XMUCUser) and (i = r.x(XMUCUser).items.first)
41
+ @affiliation = i.affiliation # we're interested in if it's :owner
42
+ @role = i.role # :moderator ?
43
+ end
44
+
45
+ handle_presence(r, false)
46
+ true
47
+ else
48
+ # Everything else
49
+ false
50
+ end
51
+ }
52
+
53
+ if error
54
+ deactivate
55
+ raise ServerError.new(error)
56
+ end
57
+
58
+ self
59
+ end
60
+
61
+ private
62
+ def activate # :nodoc:
63
+ @active = true
64
+
65
+ # Callbacks
66
+ @stream.add_presence_callback(150, self) { |presence|
67
+ if from_room?(presence.from)
68
+ handle_presence(presence)
69
+ true
70
+ else
71
+ false
72
+ end
73
+ }
74
+
75
+ @stream.add_message_callback(150, self) { |message|
76
+ # Not sure if this was hipchat or client bug,
77
+ # but this callback didn't allow chat (private) messages since
78
+ # they don't belong to any conference room
79
+ if from_room?(message.from) || is_chat?(message.type)
80
+ handle_message(message)
81
+ true
82
+ else
83
+ false
84
+ end
85
+ }
86
+ end
87
+
88
+ def is_chat?(type)
89
+ type == :chat
90
+ end
91
+
92
+ def send(stanza, to = nil)
93
+ if stanza.kind_of? Message
94
+ stanza.type = to || stanza.to ? :chat : :groupchat
95
+ end
96
+ stanza.from = @my_jid
97
+ # We don't want to override existing message JID
98
+ stanza.to = JID.new(jid.node, jid.domain, to) if stanza.to.nil?
99
+ @stream.send(stanza)
100
+ end
101
+
102
+ end
103
+
104
+ class SimpleMUCClient < MUCClient
105
+
106
+ def say(text, jid = nil)
107
+ send(Message.new(jid, text))
108
+ end
109
+
110
+ private
111
+
112
+ def handle_message(msg)
113
+ super
114
+
115
+ time = Time.now # Hipchat doesn't provide time stamp for message elements
116
+ msg.each_element('x') { |x|
117
+ if x.kind_of?(Delay::XDelay)
118
+ time = x.stamp
119
+ end
120
+ }
121
+ sender_nick = msg.from.resource
122
+
123
+
124
+ if msg.subject
125
+ @subject = msg.subject
126
+ @subject_block.call(time, sender_nick, @subject) if @subject_block
127
+ end
128
+
129
+ if msg.body
130
+ if sender_nick.nil?
131
+ @room_message_block.call(time, msg.body) if @room_message_block
132
+ else
133
+ if msg.type == :chat
134
+ # We need to send full jid here (msg.from)
135
+ @private_message_block.call(time, msg.from, msg.body) if @private_message_block
136
+ elsif msg.type == :groupchat
137
+ @message_block.call(time, msg.from.resource, msg.body) if @message_block
138
+ else
139
+ # ...?
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ end
146
+ end
147
+ end
@@ -1,5 +1,5 @@
1
1
  module Hipbot
2
- class Reaction < Struct.new(:robot, :regexp, :options, :block)
2
+ class Reaction < Struct.new(:robot, :regexps, :options, :block)
3
3
 
4
4
  def invoke sender, room, message
5
5
  message = message_for(message, sender)
@@ -9,21 +9,26 @@ module Hipbot
9
9
 
10
10
  def match? sender, room, message
11
11
  message = message_for(message, sender)
12
- matches?(message) && matches_scope?(message) && matches_sender?(message)
12
+ matches_regexp?(message) && matches_scope?(message) && matches_sender?(message) && matches_room?(room)
13
13
  end
14
14
 
15
15
  private
16
16
 
17
+ # TODO: this is pointless since we can get actual message object from xmpp4r
17
18
  def message_for message, sender
18
19
  Message.new(message, sender)
19
20
  end
20
21
 
21
22
  def arguments_for message
22
- message.body.match(regexp)[1..-1]
23
+ message.body.match(matching_regexp(message))[1..-1]
23
24
  end
24
25
 
25
- def matches?(message)
26
- regexp =~ message.body
26
+ def matches_regexp?(message)
27
+ matching_regexp(message).present?
28
+ end
29
+
30
+ def matches_room?(room)
31
+ !options[:room] || Array(options[:room]).include?(room.name)
27
32
  end
28
33
 
29
34
  def matches_scope?(message)
@@ -34,6 +39,10 @@ module Hipbot
34
39
  from_all? || Array(options[:from]).include?(message.sender)
35
40
  end
36
41
 
42
+ def matching_regexp(message)
43
+ regexps.find { |regexp| regexp =~ message.body }
44
+ end
45
+
37
46
  def global?
38
47
  options[:global]
39
48
  end
@@ -41,6 +50,5 @@ module Hipbot
41
50
  def from_all?
42
51
  !options[:from]
43
52
  end
44
-
45
53
  end
46
54
  end
@@ -1,36 +1,28 @@
1
1
  module Hipbot
2
- class Response < Struct.new(:bot, :reaction, :room, :message_object)
2
+ class Response < Struct.new(:bot, :reaction, :room, :message)
3
+ delegate :sender, :recipients, :body, :to => :message
4
+
5
+ def initialize bot, reaction, room, message
6
+ super
7
+ extend(bot.helpers)
8
+ end
3
9
 
4
10
  def invoke arguments
5
11
  instance_exec(*arguments, &reaction.block)
6
12
  end
7
13
 
8
14
  private
9
- def reply string
15
+
16
+ def reply string, room = self.room
10
17
  bot.reply(room, string)
11
18
  end
12
19
 
13
20
  [:get, :post, :put, :delete].each do |http_verb|
14
21
  define_method http_verb do |url, query={}, &block|
15
22
  http = ::EM::HttpRequest.new(url).send(http_verb, :query => query)
16
- http.callback { block.call(http) if block }
23
+ http.callback { block.call(::Hipbot::HttpResponse.new(http)) if block }
17
24
  end
18
25
  end
19
26
 
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
27
  end
36
28
  end
data/lib/hipbot/room.rb CHANGED
@@ -1,10 +1,7 @@
1
1
  module Hipbot
2
2
  class Room < Struct.new(:jid, :name)
3
- attr_accessor :connection
4
-
5
- def to_s
6
- name
7
- end
3
+ attr_accessor :connection, :users
4
+ alias_method :to_s, :name
8
5
 
9
6
  end
10
7
  end
@@ -1,3 +1,3 @@
1
1
  module Hipbot
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -1,9 +1,20 @@
1
1
  require 'spec_helper'
2
2
 
3
+ module HipbotHelpers
4
+ def project_name
5
+ "#{room.name} project"
6
+ end
7
+
8
+ def sender_first_name
9
+ "you are #{message.sender.split[0]}"
10
+ end
11
+ end
12
+
3
13
  class MyHipbot < Hipbot::Bot
4
14
  configure do |config|
5
15
  config.name = 'robbot'
6
16
  config.jid = 'robbot@chat.hipchat.com'
17
+ config.helpers = HipbotHelpers
7
18
  end
8
19
 
9
20
  on /^hello hipbot!$/ do
@@ -15,9 +26,19 @@ class MyHipbot < Hipbot::Bot
15
26
  on /hi everyone!/, global: true do
16
27
  reply('hello!')
17
28
  end
29
+ on /tell me the project name/ do
30
+ reply(project_name)
31
+ end
32
+ on /tell me my name/ do
33
+ reply(sender_first_name)
34
+ end
18
35
  end
19
36
 
20
37
  describe MyHipbot do
38
+ # TODO: replace with actual objects
39
+ let(:room) { Hipbot::Room.new('123', 'private') }
40
+ let(:sender) { 'John Doe' }
41
+
21
42
  describe "configuration" do
22
43
  it "should set robot name" do
23
44
  subject.name.should == 'robbot'
@@ -29,10 +50,6 @@ describe MyHipbot do
29
50
  end
30
51
 
31
52
  describe "replying" do
32
- # TODO: replace with actual objects
33
- let(:room) { stub_everything }
34
- let(:sender) { stub_everything }
35
-
36
53
  it "should reply to hello" do
37
54
  subject.expects(:reply).with(room, 'hello!')
38
55
  subject.tell(sender, room, '@robbot hello hipbot!')
@@ -48,4 +65,16 @@ describe MyHipbot do
48
65
  subject.tell(sender, room, "hi everyone!")
49
66
  end
50
67
  end
68
+
69
+ describe "custom helpers" do
70
+ it "should have access to room variable" do
71
+ subject.expects(:reply).with(room, 'private project')
72
+ subject.tell(sender, room, '@robbot tell me the project name')
73
+ end
74
+
75
+ it "should have access to message variable" do
76
+ subject.expects(:reply).with(room, 'you are John')
77
+ subject.tell(sender, room, '@robbot tell me my name')
78
+ end
79
+ end
51
80
  end