boty 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/Gemfile.lock +3 -1
- data/README.md +25 -1
- data/Rakefile +5 -0
- data/boty.gemspec +1 -0
- data/lib/boty.rb +18 -15
- data/lib/boty/action.rb +88 -10
- data/lib/boty/action_description.rb +22 -0
- data/lib/boty/bot.rb +13 -55
- data/lib/boty/eventable.rb +1 -0
- data/lib/boty/match_handler.rb +79 -0
- data/lib/boty/version.rb +1 -1
- data/spec/boty/action_spec.rb +23 -0
- data/spec/boty/bot_spec.rb +3 -100
- data/spec/boty/eventable_spec.rb +57 -0
- data/spec/boty/match_handler_spec.rb +119 -0
- data/spec/script/i18n_spec.rb +2 -4
- data/spec/script/knows_spec.rb +1 -1
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9959fb9fd1218a64dc671442aef786311c03f10
|
4
|
+
data.tar.gz: bad2921130ae92b79ef775529861fb4c6a204cdf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0052d938808382105c9f4b2e242ca357a01cb7e83c5eead10f05a3225b6a021cf08c24d0de7dfe7d69b3945015f1595ff20291dbc33d41b1e93b758965023fb5
|
7
|
+
data.tar.gz: 2b31a41cb5443d4693728b2a8689b321371424c7a90b84c058a721a545c2cdf17a1feb952e2404b0f302fb403bc13936e3b8f1a957e3a6d4bfc715d7318782d3
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
boty (0.
|
4
|
+
boty (0.2.0)
|
5
5
|
eventmachine
|
6
6
|
faraday
|
7
7
|
faye-websocket
|
@@ -57,6 +57,7 @@ GEM
|
|
57
57
|
websocket-driver (0.6.3)
|
58
58
|
websocket-extensions (>= 0.1.0)
|
59
59
|
websocket-extensions (0.1.2)
|
60
|
+
yard (0.8.7.6)
|
60
61
|
|
61
62
|
PLATFORMS
|
62
63
|
ruby
|
@@ -70,6 +71,7 @@ DEPENDENCIES
|
|
70
71
|
rake (~> 10.0)
|
71
72
|
rspec
|
72
73
|
rubocop
|
74
|
+
yard
|
73
75
|
|
74
76
|
BUNDLED WITH
|
75
77
|
1.10.6
|
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Boty
|
2
2
|
[<img src="https://travis-ci.org/ricardovaleriano/boty.svg?branch=master" />](https://travis-ci.org/ricardovaleriano/boty)
|
3
|
+
[<img src="http://img.shields.io/badge/yard-docs-blue.svg" />](http://www.rubydoc.info/github/ricardovaleriano/boty)
|
3
4
|
|
4
5
|
`Boty` is a utilitary to create bots (at this time, specificaly Slack bots).
|
5
6
|
|
@@ -291,7 +292,7 @@ Before we start to study the "script" way of configuring `command` and
|
|
291
292
|
`listener` binds, let's see how we can use ruby regexps to capture message
|
292
293
|
parameters.
|
293
294
|
|
294
|
-
|
295
|
+
#### Regexes - a pattern that allow capture parameters within a message<a name="regexes" />
|
295
296
|
|
296
297
|
What is a bot if it can't annoy people when we want it to? So let's teach our
|
297
298
|
bot to send private messages to people in the Slack room.
|
@@ -316,6 +317,29 @@ _command_.
|
|
316
317
|
|
317
318
|
And this is it! :tada:
|
318
319
|
|
320
|
+
##### Commands, Listenners and aliases
|
321
|
+
|
322
|
+
Both *commands* and *listeners* can be aliased, which means that the same
|
323
|
+
`Action` can be triggered by different regexes:
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
command /hi/i, /hello/i do
|
327
|
+
say "Ohay #{user.name}! Hello there."
|
328
|
+
end
|
329
|
+
```
|
330
|
+
|
331
|
+
valeriano 12:04 PM
|
332
|
+
@jeeba: hi
|
333
|
+
|
334
|
+
jeeba BOT 12:04 PM
|
335
|
+
Ohay valeriano! Hello there.
|
336
|
+
|
337
|
+
valeriano 12:05 PM
|
338
|
+
@jeeba: hello
|
339
|
+
|
340
|
+
jeeba BOT 12:05 PM
|
341
|
+
Ohay valeriano! Hello there.
|
342
|
+
|
319
343
|
### Adding custom scripts<a name="custom_scripts" />
|
320
344
|
|
321
345
|
Now you already know what are [listeners](#listeners) and
|
data/Rakefile
CHANGED
data/boty.gemspec
CHANGED
data/lib/boty.rb
CHANGED
@@ -1,28 +1,30 @@
|
|
1
|
-
begin
|
2
|
-
require "pp"
|
3
|
-
|
4
|
-
require "dotenv"
|
5
|
-
if defined? Dotenv
|
6
|
-
Dotenv.load
|
7
|
-
Dotenv.overload ".env.local"
|
8
|
-
end
|
9
|
-
rescue LoadError
|
10
|
-
end
|
11
|
-
|
12
1
|
require "faye/websocket"
|
13
2
|
require "i18n"
|
14
3
|
require "faraday"
|
4
|
+
require "dotenv"
|
5
|
+
require "pp"
|
15
6
|
|
16
7
|
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
17
8
|
|
9
|
+
Dotenv.load
|
10
|
+
Dotenv.overload ".env.local"
|
11
|
+
|
12
|
+
# Namespace for all Boty code.
|
18
13
|
module Boty
|
14
|
+
# Public: Reloads the available locale configuration files and set the current
|
15
|
+
# language as the one passed in the paramenter `lang`.
|
16
|
+
#
|
17
|
+
# lang - String or Symbol representing the language that should be set for the
|
18
|
+
# current execution.
|
19
|
+
#
|
20
|
+
# Examples:
|
21
|
+
#
|
22
|
+
# Boty.locale = :en
|
23
|
+
#
|
24
|
+
# Returns nothing.
|
19
25
|
def self.locale=(lang)
|
20
26
|
Locale.reload lang
|
21
27
|
end
|
22
|
-
|
23
|
-
def self.locale
|
24
|
-
I18n.locale ||= :en
|
25
|
-
end
|
26
28
|
end
|
27
29
|
|
28
30
|
require "boty/version"
|
@@ -35,5 +37,6 @@ require "boty/script_loader"
|
|
35
37
|
require "boty/dsl"
|
36
38
|
require "boty/locale"
|
37
39
|
require "boty/eventable"
|
40
|
+
require "boty/match_handler"
|
38
41
|
require "boty/bot"
|
39
42
|
require "boty/http"
|
data/lib/boty/action.rb
CHANGED
@@ -1,32 +1,73 @@
|
|
1
1
|
module Boty
|
2
|
-
# Public: Wrap the idea of something that should happen when
|
3
|
-
#
|
2
|
+
# Public: Wrap the idea of something that should happen when a regex matches.
|
3
|
+
# Actions are the underlying mecanism for store and execute the blocks passed
|
4
|
+
# to `Bot#match` and `Bot#respond` invocations.
|
4
5
|
#
|
6
|
+
# Maybe one of the more important things to remember here is that an Action is
|
7
|
+
# executed within the scope of a `Boty::DSL` instance already binded with the
|
8
|
+
# `Bot` of the current `Session` (via `#instance_exec`).
|
5
9
|
#
|
10
|
+
# Examples:
|
11
|
+
#
|
12
|
+
# # some_script.rb
|
13
|
+
# desc "omg! whoo!"
|
14
|
+
# hear(/omg/i) do
|
15
|
+
# im "whoo!"
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # Will generate an `Action` like this:
|
19
|
+
# action = Action.new(bot, /omg/i, "omg! whoo!") { im "whoo!" }
|
20
|
+
#
|
21
|
+
# # Then this can be executed, by `#execute`.
|
22
|
+
# action.execute "omg! this string triggers the hear block."
|
23
|
+
#
|
24
|
+
# # If the regex matches the parameter passed to execute, it'll invoke the
|
25
|
+
# # block passed to `#hear`.
|
6
26
|
class Action
|
7
27
|
include Boty::Logger
|
8
28
|
attr_reader :regex, :desc, :action
|
9
29
|
|
30
|
+
# Public: Initialize an `Action` associated with a specific `Bot`.
|
31
|
+
#
|
32
|
+
# bot - A Bot that will react to the matches on the regex.
|
33
|
+
# regex - A Regexp to match against messages received in the bot session.
|
34
|
+
# action - A Proc that will be executed everytime the regex matches.
|
10
35
|
def initialize(bot, regex, description, &action)
|
11
|
-
if description
|
12
|
-
description.regex = regex
|
13
|
-
else
|
14
|
-
description = ActionDescription.new(nil, regex: regex)
|
15
|
-
end
|
16
|
-
|
17
36
|
@dsl = DSL.new bot
|
18
37
|
@regex = regex
|
19
|
-
@desc = description
|
20
38
|
@action = action
|
39
|
+
|
40
|
+
self.desc = description
|
21
41
|
end
|
22
42
|
|
43
|
+
# Public: Check if a given Regexp and an optional block were the same used
|
44
|
+
# to build this Action.
|
45
|
+
#
|
46
|
+
# Examples:
|
47
|
+
#
|
48
|
+
# send_im = -> { im "whoo!" }
|
49
|
+
# action = Action.new(bot, /omg/i, "omg! whoo!", &send_im)
|
50
|
+
# action.this? /omg/i, send_im
|
51
|
+
# # => return true
|
52
|
+
#
|
53
|
+
# regex - A Regexp to be compared against `regex`.
|
54
|
+
# block - An optional Proc to be checked against `action`.
|
55
|
+
#
|
56
|
+
# Returns True or False.
|
23
57
|
def this?(regex, block)
|
24
58
|
same_regex = regex == self.regex
|
25
59
|
block ? same_regex && block == action : same_regex
|
26
60
|
end
|
27
61
|
|
62
|
+
# Public: Creates a `Regexp::Match` based on the internal `regex` against a
|
63
|
+
# Slack::Message.
|
64
|
+
# Then pass this match to be used as argument to the action per se.
|
65
|
+
#
|
66
|
+
# message - A Slack::Message object to be matched against the `regex`.
|
67
|
+
#
|
68
|
+
# Returns nothing.
|
28
69
|
def execute(message)
|
29
|
-
action_call
|
70
|
+
action_call regex.match(message.text)
|
30
71
|
rescue => e
|
31
72
|
logger.error e.message
|
32
73
|
raise e
|
@@ -34,6 +75,43 @@ module Boty
|
|
34
75
|
|
35
76
|
private
|
36
77
|
|
78
|
+
# Internal: Store the ActionDescription for this action (create a new for it
|
79
|
+
# if a nil value is the parameter).
|
80
|
+
#
|
81
|
+
# To make it possible to create handlers (that will became actions) without
|
82
|
+
# a description, this method can work with a nil parameter. If a nil value
|
83
|
+
# is passed, it creates an `ActionDescription` with only a regex (without a
|
84
|
+
# description text).
|
85
|
+
#
|
86
|
+
# Returns an ActionDescription with the regex associated with this Action.
|
87
|
+
def desc=(description)
|
88
|
+
if description
|
89
|
+
description.regex = regex
|
90
|
+
else
|
91
|
+
description = ActionDescription.new(nil, regex: regex)
|
92
|
+
end
|
93
|
+
@desc = description
|
94
|
+
end
|
95
|
+
|
96
|
+
# Internal: Effectively executes the action.
|
97
|
+
# The regex match created on the `#execute` method will be exploded, so the
|
98
|
+
# matches can be passed as arguments to the block.
|
99
|
+
# Important to remember: the block passed to `Bot#match` or `Bot#respond`
|
100
|
+
# will be executed within the scope of a `Boty::DSL`.
|
101
|
+
#
|
102
|
+
# Examples:
|
103
|
+
#
|
104
|
+
# # some_script.rb
|
105
|
+
# hear(/(omg)\s(lol)/i) do |first_match, second_match|
|
106
|
+
# im "whoo!"
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# # will cause an invocation like this:
|
110
|
+
# @dsl.instance_exec("omg", "lol") do |first_match, second_match|
|
111
|
+
# im "whoo!"
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# Returns nothing.
|
37
115
|
def action_call(match)
|
38
116
|
return unless match
|
39
117
|
matches = Array(match)
|
@@ -1,4 +1,26 @@
|
|
1
1
|
module Boty
|
2
|
+
# Public: contains de descriptive information that was associated with an
|
3
|
+
# action via the `Bot#desc` method.
|
4
|
+
#
|
5
|
+
# Besides that also keep tracks of the regex for the associated Action, it is
|
6
|
+
# used to describe the handler in case no command name and/or description was
|
7
|
+
# passed via `Bot#desc`.
|
8
|
+
#
|
9
|
+
# The `ActionDescription` object will be stored inside the `Action#desc`. A
|
10
|
+
# good usage example can be found in the `scripts/knows.rb` script.
|
11
|
+
#
|
12
|
+
# Examples:
|
13
|
+
#
|
14
|
+
# # scripts/omg.rb
|
15
|
+
# desc "X omg!", "Make the bot scream X omgs."
|
16
|
+
# hear(/(\d+ )?omg!/i) do |how_many|
|
17
|
+
# how_many.times do say "LOL!" end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # => generates an action with a description like this:
|
21
|
+
# ActionDescription.new "X omg!",
|
22
|
+
# description: "Make the bot scream X omgs."
|
23
|
+
# regex: /(\d+ )?omg!/i
|
2
24
|
class ActionDescription
|
3
25
|
attr_reader :command, :description
|
4
26
|
attr_writer :regex
|
data/lib/boty/bot.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module Boty
|
2
2
|
class Bot
|
3
3
|
include Boty::Eventable
|
4
|
+
include Boty::MatchHandler
|
4
5
|
include Boty::Logger
|
5
6
|
include Slack
|
6
7
|
|
7
|
-
attr_reader :id, :name, :
|
8
|
+
attr_reader :id, :name, :brain
|
8
9
|
|
9
10
|
def initialize(bot_info)
|
10
11
|
Locale.reload
|
@@ -12,42 +13,14 @@ module Boty
|
|
12
13
|
@id = bot_info["id"]
|
13
14
|
@name = bot_info["name"]
|
14
15
|
|
15
|
-
@listeners ||= []
|
16
|
-
@commands ||= []
|
17
16
|
@brain ||= {}
|
18
17
|
|
19
18
|
on :message, &method(:message_handler)
|
20
19
|
ScriptLoader.new(self).load
|
21
20
|
end
|
22
21
|
|
23
|
-
def match(regex, &block)
|
24
|
-
@listeners << create_action(regex, &block)
|
25
|
-
end
|
26
|
-
|
27
|
-
def respond(regex, &block)
|
28
|
-
@commands << create_action(regex, &block)
|
29
|
-
end
|
30
|
-
|
31
|
-
def no_match(regex, &block)
|
32
|
-
remove_action @listeners, regex, block
|
33
|
-
end
|
34
|
-
|
35
|
-
def no_respond(regex, &block)
|
36
|
-
remove_action @commands, regex, block
|
37
|
-
end
|
38
|
-
|
39
|
-
def no(command)
|
40
|
-
remove_action @listeners, command: command
|
41
|
-
remove_action @commands, command: command
|
42
|
-
end
|
43
|
-
|
44
|
-
def desc(command, description = nil)
|
45
|
-
@current_desc = ActionDescription.new command,
|
46
|
-
description: description
|
47
|
-
end
|
48
|
-
|
49
22
|
def say(message, api_parameters = {})
|
50
|
-
channel = (
|
23
|
+
channel = (trigger_message && trigger_message.channel) || "#general"
|
51
24
|
options = { channel: channel }.merge api_parameters
|
52
25
|
post_response = Slack.chat.post_message message, options
|
53
26
|
logger.debug { "Post response: #{post_response}." }
|
@@ -63,41 +36,27 @@ module Boty
|
|
63
36
|
end
|
64
37
|
end
|
65
38
|
|
66
|
-
# TODO: return an Action object instead of a hash
|
67
39
|
def know_how
|
68
|
-
actions = Array(
|
40
|
+
actions = Array(commands) + Array(listeners)
|
69
41
|
actions.sort_by { |action| action.desc.command || "_" }
|
70
42
|
end
|
71
43
|
|
72
44
|
private
|
73
45
|
|
74
|
-
def remove_action(collection, regex = nil, block = nil, command: nil)
|
75
|
-
collection.delete_if do |action|
|
76
|
-
action.desc.command == command || action.this?(regex, block)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
46
|
def user_by_name(destiny, to)
|
81
47
|
to ||= destiny
|
82
48
|
if to
|
83
49
|
Slack.users.by_name to
|
84
50
|
else
|
85
|
-
|
51
|
+
trigger_message.user
|
86
52
|
end
|
87
53
|
end
|
88
54
|
|
89
|
-
def create_action(regex, &block)
|
90
|
-
regex = Regexp.new(regex) if regex.is_a? String
|
91
|
-
Boty::Action.new(self, regex, @current_desc, &block).tap {
|
92
|
-
@current_desc = nil
|
93
|
-
}
|
94
|
-
end
|
95
|
-
|
96
55
|
def message_from_bot_itself?(data)
|
97
56
|
data["user"] == id || data["user"] == name
|
98
57
|
end
|
99
58
|
|
100
|
-
def
|
59
|
+
def command?(data)
|
101
60
|
return false if message_from_bot_itself? data
|
102
61
|
|
103
62
|
if /(<@#{id}|#{name}>)/ =~ data["text"]
|
@@ -111,17 +70,16 @@ module Boty
|
|
111
70
|
unless data["text"]
|
112
71
|
return logger.debug do "Non text message, just ignoring." end
|
113
72
|
end
|
114
|
-
|
115
|
-
execute_all Message.new(data), actions
|
73
|
+
execute data
|
116
74
|
end
|
117
75
|
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
|
76
|
+
def execute(data)
|
77
|
+
message = Message.new data
|
78
|
+
if command?(data)
|
79
|
+
execute_commands message
|
80
|
+
else
|
81
|
+
execute_matches message
|
122
82
|
end
|
123
|
-
ensure
|
124
|
-
@trigger_message = nil
|
125
83
|
end
|
126
84
|
end
|
127
85
|
end
|
data/lib/boty/eventable.rb
CHANGED
@@ -0,0 +1,79 @@
|
|
1
|
+
module Boty
|
2
|
+
module MatchHandler
|
3
|
+
include Boty::Logger
|
4
|
+
|
5
|
+
attr_reader :trigger_message
|
6
|
+
|
7
|
+
def listeners
|
8
|
+
@listeners ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def commands
|
12
|
+
@commands ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def desc(command, description = nil)
|
16
|
+
@current_desc = ActionDescription.new command,
|
17
|
+
description: description
|
18
|
+
end
|
19
|
+
|
20
|
+
def match(*regexes, &block)
|
21
|
+
create_action_on_collection listeners, regexes, &block
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond(*regexes, &block)
|
25
|
+
create_action_on_collection commands, regexes, &block
|
26
|
+
end
|
27
|
+
|
28
|
+
def no_match(regex, &block)
|
29
|
+
remove_action listeners, regex, block
|
30
|
+
end
|
31
|
+
|
32
|
+
def no_respond(regex, &block)
|
33
|
+
remove_action commands, regex, block
|
34
|
+
end
|
35
|
+
|
36
|
+
def no(command)
|
37
|
+
remove_action listeners, command: command
|
38
|
+
remove_action commands, command: command
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute_matches(message)
|
42
|
+
execute_actions message, listeners
|
43
|
+
end
|
44
|
+
|
45
|
+
def execute_commands(message)
|
46
|
+
execute_actions message, commands
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def create_action_on_collection(collection, regexes, &block)
|
52
|
+
regexes.each do |regex|
|
53
|
+
collection << create_action(regex, &block)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def execute_actions(message, actions)
|
58
|
+
@trigger_message = message
|
59
|
+
Array(actions).each do |action|
|
60
|
+
action.execute @trigger_message
|
61
|
+
end
|
62
|
+
ensure
|
63
|
+
@trigger_message = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def remove_action(collection, regex = nil, block = nil, command: nil)
|
67
|
+
collection.delete_if do |action|
|
68
|
+
action.desc.command == command || action.this?(regex, block)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_action(regex, &block)
|
73
|
+
regex = Regexp.new(regex) if regex.is_a? String
|
74
|
+
Boty::Action.new(self, regex, @current_desc, &block).tap {
|
75
|
+
@current_desc = nil
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/boty/version.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Boty
|
2
|
+
RSpec.describe Action, :users do
|
3
|
+
let(:bot) { Bot.new("id" => "U1234", "name" => "boty") }
|
4
|
+
let(:event_data) {
|
5
|
+
{
|
6
|
+
"type" => "message",
|
7
|
+
"text" => "omg lol bbq"
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
11
|
+
subject(:action) {
|
12
|
+
Action.new(bot, /omg/, nil) do nil end
|
13
|
+
}
|
14
|
+
|
15
|
+
it "evals the block in the context of the bot" do
|
16
|
+
dsl = nil
|
17
|
+
bot.match(/omg/) do dsl = self end
|
18
|
+
bot.event event_data
|
19
|
+
|
20
|
+
expect(dsl.bot).to eq bot
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/spec/boty/bot_spec.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Boty
|
2
2
|
RSpec.describe Bot do
|
3
|
-
include Boty
|
4
3
|
subject(:bot) { described_class.new bot_info }
|
5
4
|
|
6
5
|
let(:bot_info) do
|
@@ -10,105 +9,9 @@ module Boty
|
|
10
9
|
}
|
11
10
|
end
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
"type" => "omg",
|
17
|
-
"text" => "lol"
|
18
|
-
}
|
19
|
-
|
20
|
-
expect { |b|
|
21
|
-
bot.on :omg, &b
|
22
|
-
bot.event data
|
23
|
-
}.to yield_with_args data
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "#off" do
|
28
|
-
let(:data) do
|
29
|
-
{
|
30
|
-
"type" => "bbq",
|
31
|
-
"text" => "omg. wtf."
|
32
|
-
}
|
33
|
-
end
|
34
|
-
|
35
|
-
it "unbounds an event by type AND block, just if the block is the same" do
|
36
|
-
@permanent_executed = false
|
37
|
-
permanent_block = ->(_) do @permanent_executed = true end
|
38
|
-
bot.on :bbq, &permanent_block
|
39
|
-
|
40
|
-
@ephemeral_executed = false
|
41
|
-
ephemeral_block = ->(_) do @ephemeral_executed = true end
|
42
|
-
bot.on :bbq, &ephemeral_block
|
43
|
-
bot.off :bbq, &ephemeral_block
|
44
|
-
|
45
|
-
bot.event data
|
46
|
-
|
47
|
-
expect(@permanent_executed).to eq true
|
48
|
-
expect(@ephemeral_executed).to eq false
|
49
|
-
end
|
50
|
-
|
51
|
-
it "unbounds all events by type" do
|
52
|
-
@first_block = @second_block = @third_block = false
|
53
|
-
bot.on(:bbq) do |_| @first_block = true end
|
54
|
-
bot.on(:bbq) do |_| @second_block = true end
|
55
|
-
bot.on(:bbq) do |_| @third_block = true end
|
56
|
-
bot.off(:bbq)
|
57
|
-
|
58
|
-
bot.event data
|
59
|
-
|
60
|
-
expect(@first_block).to eq false
|
61
|
-
expect(@second_block).to eq false
|
62
|
-
expect(@third_block).to eq false
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
describe "#match", :users do
|
67
|
-
let(:data) do
|
68
|
-
{
|
69
|
-
"type" => "message",
|
70
|
-
"text" => "bbq omg lol"
|
71
|
-
}
|
72
|
-
end
|
73
|
-
|
74
|
-
it "binds a regex to events of `type => message`" do
|
75
|
-
message = nil
|
76
|
-
bot.match(/omg/i) do
|
77
|
-
message = self.message
|
78
|
-
end
|
79
|
-
bot.event data
|
80
|
-
|
81
|
-
expect(message).to be_a Boty::Slack::Message
|
82
|
-
expect(message.text).to eq "bbq omg lol"
|
83
|
-
expect(message.match[0]).to eq "omg"
|
84
|
-
end
|
85
|
-
|
86
|
-
it "binds various actions to events of type message" do
|
87
|
-
iterations = []
|
88
|
-
bot.match(/omg/i) do iterations << "first" end
|
89
|
-
bot.match(/omg/i) do iterations << "second" end
|
90
|
-
bot.event data
|
91
|
-
|
92
|
-
expect(iterations).to eq %w(first second)
|
93
|
-
end
|
94
|
-
|
95
|
-
it "passes the matched strings as parameters to block action" do
|
96
|
-
rspec = self
|
97
|
-
bot.match(/(bbq) omg (lol)/i) do |bbq, lol|
|
98
|
-
rspec.expect(bbq).to rspec.eq "bbq"
|
99
|
-
rspec.expect(lol).to rspec.eq "lol"
|
100
|
-
end
|
101
|
-
|
102
|
-
bot.event data
|
103
|
-
end
|
104
|
-
|
105
|
-
it "evals the block in the context of the bot" do
|
106
|
-
dsl = nil
|
107
|
-
bot.match(/omg/) do dsl = self end
|
108
|
-
bot.event data
|
109
|
-
|
110
|
-
expect(dsl.bot).to eq bot
|
111
|
-
end
|
12
|
+
context "handling message events" do
|
13
|
+
# on message, check if the event is binded, or something...
|
14
|
+
# call execute_actions with a message object
|
112
15
|
end
|
113
16
|
|
114
17
|
describe "#respond", :users do
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Boty
|
2
|
+
RSpec.describe Boty::Eventable do
|
3
|
+
class Client
|
4
|
+
include Eventable
|
5
|
+
end
|
6
|
+
|
7
|
+
subject(:eventable) { Client.new }
|
8
|
+
|
9
|
+
describe "#on" do
|
10
|
+
it "binds an event `'type'` to a block" do
|
11
|
+
expect { |b|
|
12
|
+
eventable.on :omg, &b
|
13
|
+
eventable.event "type" => "omg", "other_field" => "lol"
|
14
|
+
}.to yield_with_args "type" => "omg", "other_field" => "lol"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#off" do
|
19
|
+
let(:data) do
|
20
|
+
{
|
21
|
+
"type" => "bbq",
|
22
|
+
"text" => "omg. wtf."
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "unbounds an event by type AND block, just if the block is the same" do
|
27
|
+
@permanent_executed = false
|
28
|
+
permanent_block = ->(_) do @permanent_executed = true end
|
29
|
+
eventable.on :bbq, &permanent_block
|
30
|
+
|
31
|
+
@ephemeral_executed = false
|
32
|
+
ephemeral_block = ->(_) do @ephemeral_executed = true end
|
33
|
+
eventable.on :bbq, &ephemeral_block
|
34
|
+
eventable.off :bbq, &ephemeral_block
|
35
|
+
|
36
|
+
eventable.event data
|
37
|
+
|
38
|
+
expect(@permanent_executed).to eq true
|
39
|
+
expect(@ephemeral_executed).to eq false
|
40
|
+
end
|
41
|
+
|
42
|
+
it "unbounds all events by type" do
|
43
|
+
@first_block = @second_block = @third_block = false
|
44
|
+
eventable.on(:bbq) do |_| @first_block = true end
|
45
|
+
eventable.on(:bbq) do |_| @second_block = true end
|
46
|
+
eventable.on(:bbq) do |_| @third_block = true end
|
47
|
+
eventable.off(:bbq)
|
48
|
+
|
49
|
+
eventable.event data
|
50
|
+
|
51
|
+
expect(@first_block).to eq false
|
52
|
+
expect(@second_block).to eq false
|
53
|
+
expect(@third_block).to eq false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Boty
|
2
|
+
RSpec.describe MatchHandler do
|
3
|
+
class Handler
|
4
|
+
include MatchHandler
|
5
|
+
end
|
6
|
+
|
7
|
+
subject(:handler) { Handler.new }
|
8
|
+
|
9
|
+
describe "#match", :users do
|
10
|
+
it "binds a regex to be executed by an Action object" do
|
11
|
+
action_block = ->(*) { "omg" }
|
12
|
+
expect(Boty::Action).to receive(:new)
|
13
|
+
.with(handler, /omg/i, nil, &action_block)
|
14
|
+
|
15
|
+
handler.match(/omg/i, &action_block)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "allows to bind more than one regex with the same action" do
|
19
|
+
counter = 0
|
20
|
+
action_block = ->(*) { counter += 1 }
|
21
|
+
handler.match(/omg/i, /lol/i, /bbq/i, &action_block)
|
22
|
+
|
23
|
+
handler.execute_matches OpenStruct.new(text: "omg")
|
24
|
+
handler.execute_matches OpenStruct.new(text: "lol")
|
25
|
+
handler.execute_matches OpenStruct.new(text: "bbq")
|
26
|
+
|
27
|
+
expect(counter).to eq 3
|
28
|
+
end
|
29
|
+
|
30
|
+
it "passes the matched strings as parameters to the block" do
|
31
|
+
first_match = nil
|
32
|
+
second_match = nil
|
33
|
+
handler.match(/(bbq) omg (lol)/i) do |bbq, lol|
|
34
|
+
first_match = bbq
|
35
|
+
second_match = lol
|
36
|
+
end
|
37
|
+
handler.execute_matches OpenStruct.new(text: "bbq omg lol")
|
38
|
+
|
39
|
+
expect(first_match).to eq "bbq"
|
40
|
+
expect(second_match).to eq "lol"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#respond" do
|
45
|
+
it "allows to bind more than one regex with the same action" do
|
46
|
+
counter = 0
|
47
|
+
action_block = ->(*) { counter += 1 }
|
48
|
+
handler.respond(/omg/i, /lol/i, /bbq/i, &action_block)
|
49
|
+
|
50
|
+
handler.execute_commands OpenStruct.new(text: "omg")
|
51
|
+
handler.execute_commands OpenStruct.new(text: "lol")
|
52
|
+
handler.execute_commands OpenStruct.new(text: "bbq")
|
53
|
+
|
54
|
+
expect(counter).to eq 3
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#execute_matches" do
|
59
|
+
it "executes handler binded to the regex" do
|
60
|
+
executed = false
|
61
|
+
action_block = -> { executed = true }
|
62
|
+
handler.match(/omg/i, &action_block)
|
63
|
+
handler.execute_matches OpenStruct.new(text: "omg")
|
64
|
+
|
65
|
+
expect(executed).to eq true
|
66
|
+
end
|
67
|
+
|
68
|
+
it "repasses the text message holder as a parameter" do
|
69
|
+
message = nil
|
70
|
+
holder = OpenStruct.new(text: "omg")
|
71
|
+
handler.match(/omg/i) do message = self.message end
|
72
|
+
handler.execute_matches holder
|
73
|
+
|
74
|
+
expect(message).to be holder
|
75
|
+
end
|
76
|
+
|
77
|
+
it "executes all handlers when more than one are found" do
|
78
|
+
first_executed = false
|
79
|
+
first_block = -> { first_executed = true }
|
80
|
+
handler.match(/omg/i, &first_block)
|
81
|
+
|
82
|
+
second_executed = false
|
83
|
+
second_block = -> { second_executed = true }
|
84
|
+
handler.match(/omg/i, &second_block)
|
85
|
+
|
86
|
+
handler.execute_matches OpenStruct.new(text: "omg")
|
87
|
+
|
88
|
+
expect(first_executed).to eq true
|
89
|
+
expect(second_executed).to eq true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#execute_commands" do
|
94
|
+
it "executes the command binded to the regex" do
|
95
|
+
executed = false
|
96
|
+
action_block = -> { executed = true }
|
97
|
+
handler.respond(/lol/i, &action_block)
|
98
|
+
handler.execute_commands OpenStruct.new(text: "lol")
|
99
|
+
|
100
|
+
expect(executed).to eq true
|
101
|
+
end
|
102
|
+
|
103
|
+
it "executes all commands when more than one are found" do
|
104
|
+
first_executed = false
|
105
|
+
first_block = -> { first_executed = true }
|
106
|
+
handler.respond(/lol/i, &first_block)
|
107
|
+
|
108
|
+
second_executed = false
|
109
|
+
second_block = -> { second_executed = true }
|
110
|
+
handler.respond(/lol/i, &second_block)
|
111
|
+
|
112
|
+
handler.execute_commands OpenStruct.new(text: "lol")
|
113
|
+
|
114
|
+
expect(first_executed).to eq true
|
115
|
+
expect(second_executed).to eq true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/spec/script/i18n_spec.rb
CHANGED
@@ -10,9 +10,8 @@ RSpec.describe "I18n on default scripts", :session do
|
|
10
10
|
know_how = self.know_how
|
11
11
|
end
|
12
12
|
|
13
|
-
actions = know_how.
|
13
|
+
actions = know_how.each_with_object({}) { |action, hsh|
|
14
14
|
hsh[action.desc.command] = action.desc.description
|
15
|
-
hsh
|
16
15
|
}
|
17
16
|
|
18
17
|
expect(actions["knows"]).to eq "List all the commands known by this bot."
|
@@ -26,9 +25,8 @@ RSpec.describe "I18n on default scripts", :session do
|
|
26
25
|
know_how = self.know_how
|
27
26
|
end
|
28
27
|
|
29
|
-
actions = know_how.
|
28
|
+
actions = know_how.each_with_object({}) { |action, hsh|
|
30
29
|
hsh[action.desc.command] = action.desc.description
|
31
|
-
hsh
|
32
30
|
}
|
33
31
|
|
34
32
|
expect(actions["knows"]).to eq "Lista os comandos conhecidos por esse bot."
|
data/spec/script/knows_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: boty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ricardo Valeriano
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: eventmachine
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: yard
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
167
181
|
description: |-
|
168
182
|
Boty is intendted to be a framework for construction of
|
169
183
|
automated Slack Bots for your needs.
|
@@ -204,6 +218,7 @@ files:
|
|
204
218
|
- lib/boty/http.rb
|
205
219
|
- lib/boty/locale.rb
|
206
220
|
- lib/boty/logger.rb
|
221
|
+
- lib/boty/match_handler.rb
|
207
222
|
- lib/boty/rspec.rb
|
208
223
|
- lib/boty/script_loader.rb
|
209
224
|
- lib/boty/session.rb
|
@@ -224,11 +239,14 @@ files:
|
|
224
239
|
- script/knows.rb
|
225
240
|
- script/pug.rb
|
226
241
|
- script/remember.rb
|
242
|
+
- spec/boty/action_spec.rb
|
227
243
|
- spec/boty/bot_spec.rb
|
228
244
|
- spec/boty/cli_spec.rb
|
229
245
|
- spec/boty/dsl_spec.rb
|
246
|
+
- spec/boty/eventable_spec.rb
|
230
247
|
- spec/boty/http_spec.rb
|
231
248
|
- spec/boty/logger_spec.rb
|
249
|
+
- spec/boty/match_handler_spec.rb
|
232
250
|
- spec/boty/rspec_spec.rb
|
233
251
|
- spec/boty/script_loader_spec.rb
|
234
252
|
- spec/boty/session_spec.rb
|
@@ -293,3 +311,4 @@ signing_key:
|
|
293
311
|
specification_version: 4
|
294
312
|
summary: Boty is a pretty bot specially tailored for Slack.
|
295
313
|
test_files: []
|
314
|
+
has_rdoc:
|