hipbot 0.0.3 → 0.0.5

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.
@@ -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