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