hipbot 0.2.0 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,30 @@
1
1
  module Hipbot
2
- class Plugin < Reactable
3
- attr_accessor :bot
2
+ module Plugin
3
+ extend ActiveSupport::Concern
4
4
 
5
- private
5
+ included do
6
+ extend Reactable
7
+ extend ClassMethods
6
8
 
7
- def reaction_target
8
- bot
9
+ include Singleton
10
+ include Helpers
11
+
12
+ attr_accessor :response
13
+
14
+ delegate :sender, :recipients, :body, :room, :to => :response
15
+ delegate :bot, :to => Hipbot
16
+
17
+ Hipbot.plugins.prepend(self.instance)
18
+ end
19
+
20
+ module ClassMethods
21
+ def configure
22
+ yield instance
23
+ end
24
+
25
+ def with_response response
26
+ instance.tap{ |i| i.response = response }
27
+ end
9
28
  end
10
29
  end
11
30
  end
@@ -1,60 +1,45 @@
1
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
2
+ module Reactable
3
+ def default *params, &block
4
+ scope *params do
5
+ default_reactions << to_reaction(block)
6
+ end
11
7
  end
12
8
 
13
9
  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
10
+ @default_reactions ||= []
21
11
  end
22
12
 
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
13
+ def desc text = nil
14
+ @description.tap{ @description = text }
15
+ end
36
16
 
37
- def default_reaction
38
- @default_reaction
17
+ def on *params, &block
18
+ scope *params do
19
+ reactions << to_reaction(block)
39
20
  end
40
21
  end
41
22
 
42
- private
23
+ def reactions
24
+ @reactions ||= []
25
+ end
43
26
 
44
- def class_reactions
45
- @class_reactions ||= self.class.reactions.map do |opts|
46
- to_reaction(opts[0], opts[-1])
47
- end
27
+ def scope *params, &block
28
+ options = params.last.kind_of?(Hash) ? params.pop : {}
29
+ options_stack << options.merge({ regexps: params, desc: desc })
30
+ yield
31
+ options_stack.pop
48
32
  end
49
33
 
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)
34
+ protected
35
+
36
+ def to_reaction block
37
+ current_options = options_stack.inject{ |all, h| all.merge(h) }
38
+ Reaction.new(self, current_options, block)
54
39
  end
55
40
 
56
- def reaction_target
57
- self
41
+ def options_stack
42
+ @options_stack ||= []
58
43
  end
59
44
  end
60
45
  end
@@ -1,66 +1,65 @@
1
1
  module Hipbot
2
- class Reaction < Struct.new(:bot, :regexps, :options, :block)
3
-
4
- def invoke sender, room, message
5
- message = message_for(message, sender)
6
- arguments = arguments_for(message)
7
- Response.new(bot, self, room, message).invoke(arguments)
2
+ class Reaction < Struct.new(:plugin, :options, :block)
3
+ def any_room?
4
+ options[:room] == true
8
5
  end
9
6
 
10
- def match? sender, room, message
11
- message = message_for(message, sender)
12
- matches_regexp?(message) && matches_scope?(room, message) && matches_sender?(message) && matches_room?(room)
7
+ def anything?
8
+ regexps.blank?
13
9
  end
14
10
 
15
- def inspect
16
- "#<Hipbot::Reaction #{regexps} #{options}>"
11
+ def anywhere?
12
+ options[:room].nil?
17
13
  end
18
14
 
19
- protected
20
-
21
- def message_for message, sender
22
- Message.new(message, sender)
15
+ def desc
16
+ options[:desc]
23
17
  end
24
18
 
25
- def arguments_for message
26
- (global? ? message.raw_body : message.body).match(matching_regexp(message))[1..-1]
19
+ def from_all?
20
+ options[:from].blank?
27
21
  end
28
22
 
29
- def matches_regexp?(message)
30
- matching_regexp(message).present?
23
+ def global?
24
+ !!options[:global]
31
25
  end
32
26
 
33
- def matching_regexp(message)
34
- regexps.find{ |regexp| regexp =~ (global? ? message.raw_body : message.body) }
27
+ def inspect
28
+ "#<Hipbot::Reaction #{options}>"
35
29
  end
36
30
 
37
- def matches_scope?(room, message)
38
- global? || message.for?(bot) || room.nil?
31
+ def plugin_name
32
+ plugin.name.demodulize
39
33
  end
40
34
 
41
- def matches_room?(room)
42
- return true if options[:room].nil?
43
- room.present? && (rooms.include?(room.name) || options[:room] == true)
35
+ def match_with message
36
+ Match.new(self, message)
44
37
  end
45
38
 
46
- def matches_sender?(message)
47
- from_all? || users.include?(message.sender.name)
39
+ def private_message_only?
40
+ options[:room] == false
48
41
  end
49
42
 
50
- def global?
51
- !!options[:global]
43
+ def readable_command
44
+ regexps.to_s.gsub(/(?<!\\)(\/|\[|\]|\^|\\z|\$|\\)/, '')
52
45
  end
53
46
 
54
- def from_all?
55
- options[:from].blank?
47
+ def regexps
48
+ options[:regexps]
56
49
  end
57
50
 
58
51
  def rooms
59
- Array(options[:room]).flat_map{ |v| bot.rooms[v].presence || [v] }
52
+ replace_symbols options[:room], Hipbot.rooms
60
53
  end
61
54
 
62
55
  def users
63
- Array(options[:from]).flat_map{ |v| bot.teams[v].presence || [v] }
56
+ replace_symbols options[:from], Hipbot.teams
57
+ end
58
+
59
+ protected
60
+
61
+ def replace_symbols values, replacements_hash
62
+ Array(values).flat_map{ |v| replacements_hash[v].presence || v.to_s }
64
63
  end
65
64
  end
66
65
  end
@@ -1,29 +1,32 @@
1
1
  module Hipbot
2
- class Response < Struct.new(:bot, :reaction, :room, :message)
3
- delegate :sender, :recipients, :body, :to => :message
2
+ class Response < Struct.new(:reaction, :message)
4
3
  include Helpers
5
4
 
6
- def initialize bot, reaction, room, message
7
- super
8
- extend(bot.helpers)
9
- end
5
+ delegate :sender, :recipients, :body, :room, :to => :message
6
+ delegate :bot, :to => Hipbot
10
7
 
11
8
  def invoke arguments
9
+ Hipbot.logger.info("REACTION #{reaction.inspect}")
12
10
  instance_exec(*arguments, &reaction.block)
11
+ true
13
12
  rescue Exception => e
14
13
  Hipbot.logger.error(e)
15
- instance_exec(e, &bot.error_handler)
14
+ instance_exec(e, &Hipbot.error_handler)
15
+ false
16
16
  end
17
17
 
18
- private
18
+ protected
19
19
 
20
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)
21
+ room.nil? ? Hipbot.send_to_user(sender, message) : Hipbot.send_to_room(room, message)
22
+ end
23
+
24
+ def method_missing method, *args, &block
25
+ plugin.send(method, *args, &block)
23
26
  end
24
27
 
25
28
  def plugin
26
- bot.plugin_for(reaction)
29
+ reaction.plugin.with_response(self)
27
30
  end
28
31
  end
29
32
  end
@@ -1,22 +1,15 @@
1
1
  module Hipbot
2
- class Room < Collection
3
- attr_accessor :user_ids
4
-
5
- def initialize *args
6
- super
7
- self.user_ids = []
8
- end
9
-
2
+ class Room
10
3
  def set_topic topic
11
- Bot.instance.set_topic(self, topic)
4
+ Hipbot.set_topic(self, topic)
12
5
  end
13
6
 
14
7
  def send_message message
15
- Bot.instance.send_to_room(self, message)
8
+ Hipbot.send_to_room(self, message)
16
9
  end
17
10
 
18
11
  def users
19
- User.find_many(user_ids)
12
+ @users ||= []
20
13
  end
21
14
  end
22
15
  end
@@ -1,7 +1,13 @@
1
1
  module Hipbot
2
- class User < Collection
2
+ class User
3
+ include Reactable
4
+
3
5
  def send_message message
4
- Bot.instance.send_to_user name, message
6
+ Hipbot.send_to_user self, message
7
+ end
8
+
9
+ def mention
10
+ attributes['mention'] || name.delete(' ')
5
11
  end
6
12
 
7
13
  def first_name
@@ -1,3 +1,3 @@
1
1
  module Hipbot
2
- VERSION = "0.2.0"
2
+ VERSION = "1.0.0.rc1"
3
3
  end
@@ -1,117 +1,145 @@
1
1
  require 'spec_helper'
2
+ require_relative './my_hipbot'
2
3
 
3
- module HipbotHelpers
4
- def project_name
5
- "#{room.name} project"
6
- end
4
+ describe MyHipbot do
5
+ before(:all) { MyHipbot.instance.setup }
6
+ subject { MyHipbot.instance }
7
7
 
8
- def sender_first_name
9
- "you are #{message.sender.split[0]}"
10
- end
11
- end
8
+ let(:room) { Hipbot::Room.create(id: '1', name: 'Project 1', topic: 'project 1 stuff only') }
9
+ let(:sender) { Hipbot::User.create(id: '1', name: 'John Doe') }
10
+ let(:other_room) { Hipbot::Room.create(id: '2', name: 'Hyde Park', topic: 'nice weather today') }
11
+ let(:other_sender) { Hipbot::User.create(id: '2', name: 'Other Guy') }
12
12
 
13
- class AwesomePlugin < Hipbot::Plugin
14
- on /respond awesome/ do
15
- reply("awesome responded")
13
+ before do
14
+ Hipbot.bot.configuration.user = Hipbot::User.create(name: 'robbot')
16
15
  end
17
- end
18
16
 
19
- class CoolPlugin < Hipbot::Plugin
20
- on /respond cool/ do
21
- reply("cool responded")
22
- end
23
- end
17
+ describe 'configuration' do
18
+ it 'should set robot name' do
19
+ subject.name.should == 'robbot'
20
+ end
24
21
 
25
- class MyHipbot < Hipbot::Bot
26
- configure do |config|
27
- config.name = 'robbot'
28
- config.jid = 'robbot@chat.hipchat.com'
29
- config.helpers = HipbotHelpers
30
- config.plugins = [ AwesomePlugin, CoolPlugin.new ]
22
+ it 'should set hipchat token' do
23
+ subject.jid.should == 'robbot@chat.hipchat.com'
24
+ end
31
25
  end
32
26
 
33
- on /^hello hipbot!$/ do
34
- reply("hello!")
35
- end
27
+ describe 'replying' do
28
+ it 'should reply to hello' do
29
+ subject.expects(:send_to_room).with(room, 'hello!')
30
+ subject.react(sender, room, '@robbot hello hipbot!')
31
+ end
36
32
 
37
- on /you're (.*), robot/ do |adj|
38
- reply("I know I'm #{adj}")
39
- end
33
+ it 'should reply with argument' do
34
+ subject.expects(:send_to_room).with(room, "I know I'm cool")
35
+ subject.react(sender, room, '@robbot you\'re cool, robot')
36
+ end
40
37
 
41
- on /hi everyone!/, global: true do
42
- reply('hello!')
43
- end
38
+ it 'should reply to global message' do
39
+ subject.expects(:send_to_room).with(room, 'hello!')
40
+ subject.react(sender, room, 'hi everyone!')
41
+ end
44
42
 
45
- on /tell me the project name/ do
46
- reply(project_name)
43
+ it 'should respond with default reply' do
44
+ subject.expects(:send_to_room).with(room, "I didn't understand you")
45
+ subject.react(sender, room, '@robbot blahlblah')
46
+ end
47
47
  end
48
48
 
49
- on /tell me my name/ do
50
- reply("you are #{sender.first_name}")
49
+ describe '"from" option' do
50
+ it 'reacts to sender from required team' do
51
+ subject.expects(:send_to_room).with(room, 'restarting')
52
+ subject.react(sender, room, '@robbot restart')
53
+ end
54
+
55
+ it 'ignores sender when not in team' do
56
+ subject.expects(:send_to_room).with(room, 'What do you mean, Other Guy?')
57
+ subject.react(other_sender, room, '@robbot restart')
58
+ end
51
59
  end
52
60
 
53
- default do
54
- reply("I didn't understand you")
61
+ describe '"room" option' do
62
+ it 'reacts in required room' do
63
+ subject.expects(:send_to_room).with(room, 'deploying')
64
+ subject.react(sender, room, '@robbot deploy')
65
+ end
66
+
67
+ it 'ignores other rooms' do
68
+ subject.expects(:send_to_room).with(other_room, "I didn't understand you")
69
+ subject.react(sender, other_room, '@robbot deploy')
70
+ end
55
71
  end
56
- end
57
72
 
58
- describe MyHipbot do
59
- subject { described_class.instance }
73
+ describe 'room=true' do
74
+ it 'reacts in any room' do
75
+ subject.expects(:send_to_room).with(room, 'doing room thing')
76
+ subject.react(sender, room, '@robbot room thing')
77
+ end
60
78
 
61
- let(:room) { Hipbot::Room.create('1', 'private', topic: 'topic') }
62
- let(:sender) { Hipbot::User.create('1', 'John Doe') }
79
+ it 'ignores room commands if not in room' do
80
+ subject.expects(:send_to_user).with(sender, "I didn't understand you")
81
+ subject.react(sender, nil, 'room thing')
82
+ end
83
+ end
63
84
 
64
- describe "configuration" do
65
- it "should set robot name" do
66
- subject.name.should == 'robbot'
85
+ describe 'room=false' do
86
+ it 'ignores private command in room' do
87
+ subject.expects(:send_to_room).with(room, "I didn't understand you")
88
+ subject.react(sender, room, '@robbot private thing')
67
89
  end
68
90
 
69
- it "should set hipchat token" do
70
- subject.jid.should == 'robbot@chat.hipchat.com'
91
+ it 'allows private command if not in room' do
92
+ subject.expects(:send_to_user).with(sender, 'doing private thing')
93
+ subject.react(sender, nil, 'private thing')
71
94
  end
72
95
  end
73
96
 
74
- describe "replying" do
75
- it "should reply to hello" do
76
- subject.expects(:send_to_room).with(room, 'hello!')
77
- subject.react(sender, room, '@robbot hello hipbot!')
97
+ describe 'scope' do
98
+ it 'sets its attributes to every reaction inside' do
99
+ subject.expects(:send_to_room).with(room, 'doing John Doe thing')
100
+ subject.react(sender, room, '@robbot John Doe thing')
78
101
  end
79
102
 
80
- it "should reply with argument" do
81
- subject.expects(:send_to_room).with(room, "I know I'm cool")
82
- subject.react(sender, room, "@robbot you're cool, robot")
103
+ it 'does not match other senders' do
104
+ subject.expects(:send_to_room).with(room, 'What do you mean, Other Guy?')
105
+ subject.react(other_sender, room, '@robbot John Doe thing')
83
106
  end
84
107
 
85
- it "should reply to global message" do
86
- subject.expects(:send_to_room).with(room, "hello!")
87
- subject.react(sender, room, "hi everyone!")
108
+ it 'merges params if embedded' do
109
+ subject.expects(:send_to_room).with(room, 'doing John Doe project thing')
110
+ subject.react(sender, room, '@robbot John Doe project thing')
88
111
  end
89
112
 
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")
113
+ it 'ignores message from same sander in other room' do
114
+ subject.expects(:send_to_room).with(other_room, "I didn't understand you")
115
+ subject.react(sender, other_room, '@robbot John Doe project thing')
116
+ end
117
+
118
+ it 'ignores message from other sender in same room' do
119
+ subject.expects(:send_to_room).with(room, 'What do you mean, Other Guy?')
120
+ subject.react(other_sender, room, '@robbot John Doe project thing')
93
121
  end
94
122
  end
95
123
 
96
- describe "custom helpers" do
97
- it "should have access to room variable" do
98
- subject.expects(:send_to_room).with(room, 'private project')
124
+ describe 'custom helpers' do
125
+ it 'should have access to room variable' do
126
+ subject.expects(:send_to_room).with(room, 'Project: Project 1')
99
127
  subject.react(sender, room, '@robbot tell me the project name')
100
128
  end
101
129
 
102
- it "should have access to message variable" do
130
+ it 'should have access to message variable' do
103
131
  subject.expects(:send_to_room).with(room, 'you are John')
104
132
  subject.react(sender, room, '@robbot tell me my name')
105
133
  end
106
134
  end
107
135
 
108
- describe "plugins" do
109
- it "should reply to reaction defined in plugin" do
136
+ describe 'plugins' do
137
+ it 'should reply to reaction defined in plugin' do
110
138
  subject.expects(:send_to_room).with(room, 'awesome responded')
111
139
  subject.react(sender, room, '@robbot respond awesome')
112
140
  end
113
141
 
114
- it "should reply to reaction defined in second plugin" do
142
+ it 'should reply to reaction defined in second plugin' do
115
143
  subject.expects(:send_to_room).with(room, 'cool responded')
116
144
  subject.react(sender, room, '@robbot respond cool')
117
145
  end