lita 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +179 -10
- data/lib/lita.rb +37 -5
- data/lib/lita/adapter.rb +8 -1
- data/lib/lita/adapters/shell.rb +12 -5
- data/lib/lita/authorization.rb +29 -0
- data/lib/lita/cli.rb +7 -1
- data/lib/lita/config.rb +11 -4
- data/lib/lita/handler.rb +17 -13
- data/lib/lita/handlers/authorization.rb +60 -0
- data/lib/lita/handlers/help.rb +40 -0
- data/lib/lita/message.rb +1 -0
- data/lib/lita/robot.rb +12 -1
- data/lib/lita/rspec.rb +18 -2
- data/lib/lita/user.rb +54 -0
- data/lib/lita/version.rb +1 -1
- data/lita.gemspec +2 -0
- data/skeleton/Gemfile +5 -0
- data/skeleton/lita_config.rb +10 -0
- data/spec/lita/adapter_spec.rb +11 -3
- data/spec/lita/adapters/shell_spec.rb +10 -1
- data/spec/lita/authorization_spec.rb +60 -0
- data/spec/lita/config_spec.rb +2 -3
- data/spec/lita/handler_spec.rb +21 -0
- data/spec/lita/handlers/authorization_spec.rb +70 -0
- data/spec/lita/handlers/help_spec.rb +19 -0
- data/spec/lita/message_spec.rb +7 -0
- data/spec/lita/robot_spec.rb +33 -5
- data/spec/lita/rspec_spec.rb +27 -1
- data/spec/lita/user_spec.rb +58 -0
- data/spec/lita_spec.rb +39 -9
- metadata +16 -4
- data/lib/lita/errors.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 685f6216c8c89f5312c06bf1ae319148b664f7f7
|
4
|
+
data.tar.gz: d56a154473e9e27b2bd670b8e523c436659775b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73a167653dde936d3813aea9bcc9a8dc6d6c8de116773277a7e18b3e3e6a9a79f37854075f02d53c87d90c4f732e0541d4912557af591c5a6a7401b559ca2bf1
|
7
|
+
data.tar.gz: 0ae661969514a05918bcc7d11b8bef8c95cce9f4b40010aef44185d52158d39cf7774db522c47840ebbaae00c30c3a1550f1a8fff1cfad36f33b0bac65c1aecc
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -8,6 +8,16 @@
|
|
8
8
|
|
9
9
|
Automate your business and have fun with your very own robot companion.
|
10
10
|
|
11
|
+
## Why?
|
12
|
+
|
13
|
+
Lita draws much inspiration from GitHub's fantastic [Hubot](http://hubot.github.com/), but has a few key differences and strengths:
|
14
|
+
|
15
|
+
* It's written in Ruby.
|
16
|
+
* It exposes the full power of Redis rather than using it to serialize JSON.
|
17
|
+
* Is easy to develop and test plugins for with the provied [RSpec](https://github.com/rspec/rspec) extras. Lita strongly encourages thorough testing of plugins.
|
18
|
+
* It uses uses the Ruby ecosystem's standard tools (RubyGems and Bundler) for plugins.
|
19
|
+
* It's thoroughly documented.
|
20
|
+
|
11
21
|
## Dependencies
|
12
22
|
|
13
23
|
* Ruby 2.0
|
@@ -33,7 +43,7 @@ gem "lita-hipchat"
|
|
33
43
|
|
34
44
|
Adapters will likely require some configuration to be able to connect. See the documentation for the adapter for details.
|
35
45
|
|
36
|
-
Without installing an adapter, you can use the default shell adapter to chat with Lita in your terminal. Lita doesn't respond to
|
46
|
+
Without installing an adapter, you can use the default shell adapter to chat with Lita in your terminal. Lita doesn't respond to many messages by default, however, so you'll want to add some new behavior to Lita via handlers.
|
37
47
|
|
38
48
|
## Handlers
|
39
49
|
|
@@ -54,23 +64,182 @@ Lita.configure do |config|
|
|
54
64
|
config.adapter.username = "bottington"
|
55
65
|
config.adapter.password = "secret"
|
56
66
|
config.redis.host = "redis.example.com"
|
57
|
-
config.handlers.karma.
|
67
|
+
config.handlers.karma.cooldown = 300
|
58
68
|
config.handlers.google_images.safe_search = false
|
59
69
|
end
|
60
70
|
```
|
61
71
|
|
62
72
|
The main config objects are:
|
63
73
|
|
64
|
-
* robot
|
65
|
-
* name
|
66
|
-
* adapter
|
67
|
-
*
|
68
|
-
*
|
69
|
-
*
|
74
|
+
* `robot` - General settings for Lita.
|
75
|
+
* `name` - The name the bot will use on the chat service.
|
76
|
+
* `adapter` - A symbol or string indicating the adapter to load.
|
77
|
+
* `log_level` - A symbol or string indicating the severity level of log messages to output. Valid options are, in order of severity - `:debug`, `:info`, `:warn`, `:error`, and `:fatal`. For whichever level you choose, log messages of that severity and greater will be output. The default level is `:info`.
|
78
|
+
* `admins` - An array of string user IDs which tell Lita which users are considered administrators. Only these users will have access to Lita's `auth` command.
|
79
|
+
* `redis` - Options for the Redis connection. See the [Redis gem](https://github.com/redis/redis-rb) documentation.
|
80
|
+
* `adapter` - Options for the chosen adapter. See the adapter's documentation.
|
81
|
+
* `handlers` - Handlers may choose to expose a config object here with their own options. See the handler's documentation.
|
82
|
+
|
83
|
+
If you want to use a config file with a different name or location, invoke `lita` with the `-c` option and provide the path to the config file.
|
84
|
+
|
85
|
+
## Authorization
|
86
|
+
|
87
|
+
Access to commands can be allowed for only certain users by means of authorization groups. Users set as admins (by adding their user IDs to the `config.robot.admins` array in Lita's configuration) have access to two commands:
|
88
|
+
|
89
|
+
```
|
90
|
+
Lita: auth add joe committers
|
91
|
+
Lita: auth remove joe committers
|
92
|
+
```
|
93
|
+
|
94
|
+
The first command adds a user whose ID or name is "joe" to the authorization group "committers." If the group doesn't yet exist, it is created. The second command removes joe from the group. Handlers can specify that a route (a method that matches an incoming message) requires that the user sending the message be in a certain authorization group. See the section on writing handlers for more details.
|
95
|
+
|
96
|
+
## Online help
|
97
|
+
|
98
|
+
Message Lita `help` for a list of commands it knows about. You can also message it `help FOO` to list only commands beginning with FOO.
|
99
|
+
|
100
|
+
## Writing an adapter
|
101
|
+
|
102
|
+
An adapter is a packaged as a RubyGem. The adapter is a class that inherits from `Lita::Adapter`, implements a few required methods, and is registered by calling `Lita.register_adapter(:symbol_that_identifies_the_adapter, TheAdapterClass)`.
|
103
|
+
|
104
|
+
### Example
|
105
|
+
|
106
|
+
Here is a bare bones example of an adapter for the fictious chat service, FancyChat.
|
107
|
+
|
108
|
+
``` ruby
|
109
|
+
module Lita
|
110
|
+
module Adapters
|
111
|
+
class FancyChat < Adapter
|
112
|
+
# Optional. Makes the bot produce an error message and quit upon start up
|
113
|
+
# if `config.adapter.username` or `config.adapter.password` are not set.
|
114
|
+
require_configs :username, :password
|
115
|
+
|
116
|
+
# Connects to the chat service and dispatches incoming messages to a
|
117
|
+
# Lita::Robot instance.
|
118
|
+
def run
|
119
|
+
end
|
120
|
+
|
121
|
+
# Sends a message from the robot to a user or room on the chat service.
|
122
|
+
def send_messages(target, strings)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Sets the topic for a chat room.
|
126
|
+
def set_topic(target, topic)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Does any clean up necessary when disconnecting from the chat service.
|
130
|
+
def shut_down
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
Lita.register_adapter(:fancy_chat, FancyChat)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
It's important to note that each adapter should employ its own thread or event mechanism so that incoming messages can still be processed even while a handler is processing a previous message.
|
140
|
+
|
141
|
+
For more detailed examples, check out the built in shell adapter or [lita-hipchat](https://github.com/jimmycuadra/lita-hipchat). Also check out the API documentation.
|
142
|
+
|
143
|
+
## Writing a handler
|
144
|
+
|
145
|
+
A handler is packaged as a RubyGem. A handler is a class that inherits from `Lita::Handler` and is registered by calling `Lita.register_handler(TheHandlerClass)`. There are two components to a handler: route definitions, and the methods that implement those routes.
|
146
|
+
|
147
|
+
To define a route, use the class method `route`:
|
148
|
+
|
149
|
+
``` ruby
|
150
|
+
route /^echo\s+(.+)/, to: :echo
|
151
|
+
```
|
152
|
+
|
153
|
+
`route` takes a regular expression that will be used to determine whether or not an incoming message should trigger the route. The keyword argument `:to` is supplied the name of the method that should be called when this route is triggered. `route` takes two additional options:
|
154
|
+
|
155
|
+
* `:command` - A boolean which, if set to true, means that the route will only trigger when "directed" at the robot. Directed means that it's sent via a private message, or the message is prefixed with the bot's name in some form (optionally prefixed with an @, and optionally followed by a colon or comma and white space). This prefix is stripped from the message body itself, but the `command?` method available to handlers can be used if you need to determine whether or not a message was a command after it's been routed.
|
156
|
+
* `:required_groups` - A string, symbol, or array of strings/symbols, specifying authorization groups necessary to trigger the route. The user sending the message must be a member of at least one of the supplied groups. See the section on authorization for more information.
|
157
|
+
|
158
|
+
Here is an example of a route declaration with all the options:
|
159
|
+
|
160
|
+
``` ruby
|
161
|
+
route /^echo\s+(.+)/, to :echo, command: true, required_groups: [:testers, :committers]
|
162
|
+
```
|
163
|
+
|
164
|
+
Each method that is called by a route takes one argument, an array of matches extracted by calling `message.scan(route_pattern)`. Handler methods have several other methods available to them to assist in performing other tasks and in most cases responding to the user who sent the message:
|
165
|
+
|
166
|
+
* `reply` - Sends one or more string messages back to the source of the original message, either a private message or a chat room.
|
167
|
+
* `redis` - A `Redis::Namespace` object which provides each handler with its own isolated Redis store, suitable for many data persistence and manipulation tasks.
|
168
|
+
* `args` - The user's message as an array of strings, as it would be parsed by `Shellwords.split`. For example, if the message was "Lita: auth add joe committers", calling `args` would return `["auth", "add", "joe", "committers"]`. This is very handy for commands that take arguments in a way similar to how a UNIX shell would work.
|
169
|
+
* `user` - A `Lita::User` object for the user who sent the message.
|
170
|
+
* `command?` - A boolean indicating whether or not the current message was directed at the robot.
|
171
|
+
* `message_body` - The full body of the user's message, as a string.
|
172
|
+
|
173
|
+
To add entries to Lita's built-in `help` command for your handler's commands, add a class method called `help` that returns a hash, where the keys are the format of the command, and the values are a description of what it does.
|
174
|
+
|
175
|
+
### Example
|
176
|
+
|
177
|
+
Here is a basic handler which simply echoes back whatever the user says.
|
178
|
+
|
179
|
+
``` ruby
|
180
|
+
module Lita
|
181
|
+
module Handlers
|
182
|
+
class Echo < Handler
|
183
|
+
def self.help
|
184
|
+
{ "#{Lita.config.robot.name}: echo FOO" => "Echoes back FOO." }
|
185
|
+
end
|
186
|
+
|
187
|
+
route /^echo\s+(.+)/, to: :echo
|
188
|
+
|
189
|
+
def echo(matches)
|
190
|
+
reply matches
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
Lita.register_handler(Echo)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
```
|
198
|
+
For more detailed examples, check out the built in authorization and help handlers or [lita-karma](https://github.com/jimmycuadra/lita-karma). Also check out the API documentation.
|
199
|
+
|
200
|
+
## Testing
|
201
|
+
|
202
|
+
It's a core philosophy of Lita that any handlers you write for your robot should be as thoroughly tested as any other program you would write. To make this easier, Lita ships with some handy extras for [RSpec](https://github.com/rspec/rspec) that make testing a handler dead simple.
|
203
|
+
|
204
|
+
To include Lita's RSpec extras, require "lita/rspec" and add `lita: true` to any `describe` block where you want the extras:
|
205
|
+
|
206
|
+
``` ruby
|
207
|
+
require "lita/rspec"
|
208
|
+
|
209
|
+
describe Lita::Handlers::MyHandler, lita: true do
|
210
|
+
# ...
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
214
|
+
`Lita::RSpec` makes the following changes to make testing easier:
|
215
|
+
|
216
|
+
* `Lita.handlers` will return an array with only the class you're testing (`described_class`).
|
217
|
+
* All Redis interaction will be namespaced to a test environment and automatically cleared out before each example.
|
218
|
+
* `Lita::Robot#send_messages` will be stubbed out so the shell adapter doesn't actually spit messages out into your terminal.
|
219
|
+
* You have access to the following cached objects set with `let`: `robot`, `source`, and `user`.
|
220
|
+
|
221
|
+
The custom helper methods are where `Lita::RSpec` really shines. You can test routes very easily using this syntax:
|
222
|
+
|
223
|
+
``` ruby
|
224
|
+
it { routes("some message").to(:some_method) }
|
225
|
+
it { routes("#{robot.name} directed message").to(:some_command_method) }
|
226
|
+
it { doesnt_route("message").to(:some_command_method) }
|
227
|
+
```
|
228
|
+
|
229
|
+
You can also use the alias `does_not_route` if you prefer that to `doesnt_route`. These methods allow you to test your routing in a terse, expressive way.
|
230
|
+
|
231
|
+
To test the functionality of your methods, two additional methods are provided:
|
232
|
+
|
233
|
+
``` ruby
|
234
|
+
expect_reply("Hello, #{user.name}.")
|
235
|
+
send_test_message("#{robot.name}: hi")
|
236
|
+
```
|
237
|
+
|
238
|
+
`expect_reply` takes one or more argument matchers that it expects your handler to reply with. The arguments can be strings, regular expressions, or any other RSpec argument matcher. You can also use the alias `expect_replies` if you're passing multiple arguments.
|
70
239
|
|
71
|
-
|
240
|
+
`send_test_message` does what you would expect: It sends the given message to your handler, using the `user` and `source` provided by the cached `let` objects.
|
72
241
|
|
73
|
-
|
242
|
+
For negative message expectations, you can use `expect_no_reply` and its alias `expect_no_replies`, which also take any number of argument matchers.
|
74
243
|
|
75
244
|
## License
|
76
245
|
|
data/lib/lita.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
require "forwardable"
|
2
|
+
require "logger"
|
2
3
|
require "set"
|
3
4
|
require "shellwords"
|
4
5
|
|
5
6
|
require "redis-namespace"
|
6
7
|
|
8
|
+
require "lita/version"
|
9
|
+
require "lita/config"
|
10
|
+
|
7
11
|
module Lita
|
8
12
|
REDIS_NAMESPACE = "lita"
|
9
13
|
|
@@ -32,6 +36,17 @@ module Lita
|
|
32
36
|
yield config
|
33
37
|
end
|
34
38
|
|
39
|
+
def logger
|
40
|
+
@logger ||= begin
|
41
|
+
logger = Logger.new(STDERR)
|
42
|
+
logger.level = log_level
|
43
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
44
|
+
"[#{datetime.utc}] #{severity}: #{msg}\n"
|
45
|
+
end
|
46
|
+
logger
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
35
50
|
def redis
|
36
51
|
@redis ||= begin
|
37
52
|
redis = Redis.new(config.redis)
|
@@ -39,19 +54,36 @@ module Lita
|
|
39
54
|
end
|
40
55
|
end
|
41
56
|
|
42
|
-
def run
|
43
|
-
Config.load_user_config
|
57
|
+
def run(config_path = nil)
|
58
|
+
Config.load_user_config(config_path)
|
44
59
|
Robot.new.run
|
45
60
|
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def log_level
|
65
|
+
level = config.robot.log_level
|
66
|
+
|
67
|
+
if level
|
68
|
+
begin
|
69
|
+
Logger.const_get(level.to_s.upcase)
|
70
|
+
rescue NameError
|
71
|
+
return Logger::INFO
|
72
|
+
end
|
73
|
+
else
|
74
|
+
Logger::INFO
|
75
|
+
end
|
76
|
+
end
|
46
77
|
end
|
47
78
|
end
|
48
79
|
|
49
|
-
require "lita/
|
50
|
-
require "lita/errors"
|
51
|
-
require "lita/config"
|
80
|
+
require "lita/user"
|
52
81
|
require "lita/source"
|
82
|
+
require "lita/authorization"
|
53
83
|
require "lita/message"
|
54
84
|
require "lita/robot"
|
55
85
|
require "lita/adapter"
|
56
86
|
require "lita/adapters/shell"
|
57
87
|
require "lita/handler"
|
88
|
+
require "lita/handlers/authorization"
|
89
|
+
require "lita/handlers/help"
|
data/lib/lita/adapter.rb
CHANGED
@@ -18,6 +18,12 @@ module Lita
|
|
18
18
|
ensure_required_configs
|
19
19
|
end
|
20
20
|
|
21
|
+
[:run, :send_messages, :set_topic, :shut_down].each do |method|
|
22
|
+
define_method(method) do |*args|
|
23
|
+
Lita.logger.warn("This adapter has not implemented ##{method}.")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
21
27
|
private
|
22
28
|
|
23
29
|
def ensure_required_configs
|
@@ -31,9 +37,10 @@ module Lita
|
|
31
37
|
end
|
32
38
|
|
33
39
|
unless missing_keys.empty?
|
34
|
-
|
40
|
+
Lita.logger.fatal(
|
35
41
|
"The following keys are required on config.adapter: #{missing_keys.join(", ")}"
|
36
42
|
)
|
43
|
+
abort
|
37
44
|
end
|
38
45
|
end
|
39
46
|
end
|
data/lib/lita/adapters/shell.rb
CHANGED
@@ -2,19 +2,26 @@ module Lita
|
|
2
2
|
module Adapters
|
3
3
|
class Shell < Adapter
|
4
4
|
def run
|
5
|
+
user = User.new(1, name: "Shell User")
|
6
|
+
source = Source.new(user)
|
5
7
|
puts 'Type "exit" or "quit" to end the session.'
|
8
|
+
|
6
9
|
loop do
|
7
10
|
print "#{robot.name} > "
|
8
|
-
input = gets.chomp.strip
|
11
|
+
input = $stdin.gets.chomp.strip
|
9
12
|
break if input == "exit" || input == "quit"
|
10
|
-
source = Source.new("Shell User")
|
11
13
|
message = Message.new(robot, input, source)
|
12
|
-
robot.receive(message)
|
14
|
+
Thread.new { robot.receive(message) }
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
def send_messages(target,
|
17
|
-
puts
|
18
|
+
def send_messages(target, strings)
|
19
|
+
puts
|
20
|
+
puts strings
|
21
|
+
end
|
22
|
+
|
23
|
+
def shut_down
|
24
|
+
puts
|
18
25
|
end
|
19
26
|
end
|
20
27
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Lita
|
2
|
+
module Authorization
|
3
|
+
class << self
|
4
|
+
def add_user_to_group(user, group)
|
5
|
+
return unless user_is_admin?(user)
|
6
|
+
redis.sadd(group, user.id)
|
7
|
+
end
|
8
|
+
|
9
|
+
def remove_user_from_group(user, group)
|
10
|
+
return unless user_is_admin?(user)
|
11
|
+
redis.srem(group, user.id)
|
12
|
+
end
|
13
|
+
|
14
|
+
def user_in_group?(user, group)
|
15
|
+
redis.sismember(group, user.id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def user_is_admin?(user)
|
19
|
+
Array(Lita.config.robot.admins).include?(user.id)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def redis
|
25
|
+
@redis ||= Redis::Namespace.new("auth", redis: Lita.redis)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/lita/cli.rb
CHANGED
@@ -10,10 +10,16 @@ module Lita
|
|
10
10
|
|
11
11
|
default_task :start
|
12
12
|
|
13
|
+
class_option :config,
|
14
|
+
aliases: "-c",
|
15
|
+
banner: "PATH",
|
16
|
+
default: "lita_config.rb",
|
17
|
+
desc: "Path to the configuration file to use"
|
18
|
+
|
13
19
|
desc "start", "Starts Lita"
|
14
20
|
def start
|
15
21
|
Bundler.require
|
16
|
-
Lita.run
|
22
|
+
Lita.run(options[:config])
|
17
23
|
end
|
18
24
|
|
19
25
|
desc "new NAME", "Generates a new Lita project (default name: lita)"
|
data/lib/lita/config.rb
CHANGED
@@ -5,19 +5,26 @@ module Lita
|
|
5
5
|
c.robot = new
|
6
6
|
c.robot.name = "Lita"
|
7
7
|
c.robot.adapter = :shell
|
8
|
+
c.robot.log_level = :info
|
9
|
+
c.robot.admins = nil
|
8
10
|
c.redis = new
|
9
11
|
c.adapter = new
|
10
12
|
c.handlers = new
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
|
-
def self.load_user_config
|
15
|
-
config_path =
|
16
|
+
def self.load_user_config(config_path = nil)
|
17
|
+
config_path = "lita_config.rb" unless config_path
|
16
18
|
|
17
19
|
begin
|
18
20
|
load(config_path)
|
19
|
-
rescue Exception
|
20
|
-
|
21
|
+
rescue Exception => e
|
22
|
+
Lita.logger.fatal <<-MSG
|
23
|
+
Lita configuration file could not be processed. The exception was:
|
24
|
+
#{e.message}
|
25
|
+
#{e.backtrace.join("\n")}
|
26
|
+
MSG
|
27
|
+
abort
|
21
28
|
end if File.exist?(config_path)
|
22
29
|
end
|
23
30
|
|