vetinari 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +69 -0
- data/examples/echo_bot.rb +24 -0
- data/lib/vetinari/bot.rb +358 -0
- data/lib/vetinari/callback.rb +48 -0
- data/lib/vetinari/callback_container.rb +85 -0
- data/lib/vetinari/channel.rb +249 -0
- data/lib/vetinari/channel_container.rb +74 -0
- data/lib/vetinari/configuration.rb +54 -0
- data/lib/vetinari/dcc/incoming/file.rb +113 -0
- data/lib/vetinari/dcc/server.rb +107 -0
- data/lib/vetinari/dcc/server_manager.rb +83 -0
- data/lib/vetinari/irc.rb +42 -0
- data/lib/vetinari/isupport.rb +175 -0
- data/lib/vetinari/logging/logger.rb +13 -0
- data/lib/vetinari/logging/logger_list.rb +19 -0
- data/lib/vetinari/logging/null_logger.rb +9 -0
- data/lib/vetinari/message_parser.rb +35 -0
- data/lib/vetinari/mode_parser.rb +46 -0
- data/lib/vetinari/user.rb +104 -0
- data/lib/vetinari/user_container.rb +52 -0
- data/lib/vetinari/version.rb +3 -0
- data/lib/vetinari.rb +38 -0
- data/spec/callback_spec.rb +19 -0
- data/spec/channel_management_spec.rb +39 -0
- data/spec/default_callbacks_spec.rb +53 -0
- data/spec/isupport_spec.rb +260 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/user_management_spec.rb +135 -0
- data/vetinari.gemspec +30 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0000606629bd97c6e36412292b86220a9279472b
|
4
|
+
data.tar.gz: 5cb4790367bb5037fa4aecd97c6058a88ba56f39
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a40248f184e7ee83ce0f6b8e12e806d5c71cf9b6f258f53c3dc774070bdf77ce02ced8ea274d241bf8adbab732cb3779b7b480dbdcc2a1080e7c98e219e21d85
|
7
|
+
data.tar.gz: 8989e04679431ca77300fb6195901a4c355e1a7047ec314b62cb14f8629afc011fecccb3d185eb0345dac6aaa9dda891129cc9b2bff39aaeb535e666b4ad696d
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Tobias Bühlmann
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# Vetinari
|
2
|
+
Vetinari is a Domain Specific Language for writing IRC Bots using the [Celluloid::IO](https://github.com/celluloid/celluloid-io "Celluloid::IO") library.
|
3
|
+
|
4
|
+
## Requirements
|
5
|
+
- Ruby >= 1.9.2
|
6
|
+
- Celluloid::IO ~> 0.14
|
7
|
+
|
8
|
+
## Wiki
|
9
|
+
Detailed information about using Vetinari can be found in the [Project Wiki](https://github.com/tbuehlmann/vetinari/wiki).
|
10
|
+
|
11
|
+
## Quick Setup
|
12
|
+
|
13
|
+
### Installation
|
14
|
+
```sh
|
15
|
+
$ gem install vetinari
|
16
|
+
```
|
17
|
+
|
18
|
+
### Usage
|
19
|
+
```ruby
|
20
|
+
require 'vetinari'
|
21
|
+
|
22
|
+
bot = Vetinari::Bot.new do |config|
|
23
|
+
config.server = 'chat.freenode.org'
|
24
|
+
config.port = 6667
|
25
|
+
config.nick = 'Vetinari'
|
26
|
+
end
|
27
|
+
|
28
|
+
bot.on(:connect) do
|
29
|
+
bot.join '#vetinari'
|
30
|
+
end
|
31
|
+
|
32
|
+
bot.on(:channel, /foo/) do |env|
|
33
|
+
env[:channel].message('bar')
|
34
|
+
end
|
35
|
+
|
36
|
+
bot.connect
|
37
|
+
```
|
38
|
+
|
39
|
+
## Contributing
|
40
|
+
|
41
|
+
1. Fork it
|
42
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
43
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
44
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
45
|
+
5. Create new Pull Request
|
46
|
+
|
47
|
+
## License
|
48
|
+
Copyright (c) 2013 Tobias Bühlmann
|
49
|
+
|
50
|
+
MIT License
|
51
|
+
|
52
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
53
|
+
a copy of this software and associated documentation files (the
|
54
|
+
"Software"), to deal in the Software without restriction, including
|
55
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
56
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
57
|
+
permit persons to whom the Software is furnished to do so, subject to
|
58
|
+
the following conditions:
|
59
|
+
|
60
|
+
The above copyright notice and this permission notice shall be
|
61
|
+
included in all copies or substantial portions of the Software.
|
62
|
+
|
63
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
64
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
65
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
66
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
67
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
68
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
69
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,24 @@
|
|
1
|
+
lib_dir = File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
$LOAD_PATH.unshift lib_dir
|
3
|
+
|
4
|
+
require 'vetinari'
|
5
|
+
|
6
|
+
echo_bot = Vetinari::Bot.new do |config|
|
7
|
+
config.server = 'chat.freenode.org'
|
8
|
+
config.port = 6667
|
9
|
+
config.nick = "Vetinari#{rand(10_000)}"
|
10
|
+
end
|
11
|
+
|
12
|
+
echo_bot.on :connect do
|
13
|
+
echo_bot.join '#vetinari'
|
14
|
+
end
|
15
|
+
|
16
|
+
echo_bot.on :channel, /\Aquit!\z/ do |env|
|
17
|
+
echo_bot.quit
|
18
|
+
end
|
19
|
+
|
20
|
+
echo_bot.on :channel, //, 1 do |env|
|
21
|
+
env[:channel].message(env[:message])
|
22
|
+
end
|
23
|
+
|
24
|
+
echo_bot.connect
|
data/lib/vetinari/bot.rb
ADDED
@@ -0,0 +1,358 @@
|
|
1
|
+
module Vetinari
|
2
|
+
class Bot
|
3
|
+
include Celluloid::IO, IRC
|
4
|
+
|
5
|
+
attr_reader :config, :users, :user, :channels, :server_manager, :callbacks
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
@actor = Actor.current
|
9
|
+
@config = Configuration.new(&block)
|
10
|
+
@callbacks = CallbackContainer.new(Actor.current)
|
11
|
+
@users = UserContainer.new
|
12
|
+
@channels = ChannelContainer.new
|
13
|
+
@socket = nil
|
14
|
+
@connected = false
|
15
|
+
@user = nil
|
16
|
+
|
17
|
+
setup_channel_and_user_tracking
|
18
|
+
setup_default_callbacks
|
19
|
+
setup_dcc
|
20
|
+
end
|
21
|
+
|
22
|
+
def on(event, pattern = //, worker = 0, &block)
|
23
|
+
@callbacks.add(event, pattern, worker, block)
|
24
|
+
end
|
25
|
+
|
26
|
+
exclusive :on
|
27
|
+
execute_block_on_receiver :on
|
28
|
+
|
29
|
+
def connect
|
30
|
+
@config.loggers.info '-- Starting Vetinari'
|
31
|
+
@socket = TCPSocket.open(@config.server, @config.port)
|
32
|
+
# port, ip = Socket.unpack_sockaddr_in(@socket.to_io.getpeername)
|
33
|
+
# @config.internal_port = port
|
34
|
+
# @config.internal_ip = ip
|
35
|
+
register
|
36
|
+
|
37
|
+
while message = @socket.gets do
|
38
|
+
parse message
|
39
|
+
end
|
40
|
+
|
41
|
+
disconnected
|
42
|
+
end
|
43
|
+
|
44
|
+
def raw(message, logging = true)
|
45
|
+
if @socket
|
46
|
+
@socket.puts("#{message}\r\n")
|
47
|
+
@config.loggers.info ">> #{message}" if logging
|
48
|
+
message
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def connected?
|
53
|
+
@connected ? true : false
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
nick = @user.nick rescue @config.nick
|
58
|
+
"#<Bot nick=#{nick}>"
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse(message)
|
62
|
+
message.chomp!
|
63
|
+
|
64
|
+
if message =~ /^PING \S+$/
|
65
|
+
if @config.hide_ping_pongs
|
66
|
+
raw message.sub(/PING/, 'PONG'), false
|
67
|
+
else
|
68
|
+
@config.loggers.info "<< #{message}"
|
69
|
+
raw message.sub(/PING/, 'PONG')
|
70
|
+
end
|
71
|
+
else
|
72
|
+
@config.loggers.info "<< #{message}"
|
73
|
+
env = MessageParser.parse(message, @config.isupport['CHANTYPES'])
|
74
|
+
@callbacks.call(env)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def disconnected
|
81
|
+
@connected = false
|
82
|
+
@config.loggers.info '-- Vetinari disconnected'
|
83
|
+
|
84
|
+
unless @quitted
|
85
|
+
if @config.reconnect
|
86
|
+
@quitted = nil
|
87
|
+
puts "-- Reconnecting in #{@config.reconnect_interval} seconds."
|
88
|
+
after(@config.reconnect_interval) { connect }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def setup_dcc
|
94
|
+
if @config.dcc.external_ip && @config.dcc.ports.any?
|
95
|
+
@server_manager = Dcc::ServerManager.new(Actor.current)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def setup_default_callbacks
|
100
|
+
[376, 422].each do |raw_numeric|
|
101
|
+
on raw_numeric do |env|
|
102
|
+
unless @connected
|
103
|
+
@connected = true
|
104
|
+
@callbacks.call({:type => :connect})
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
on 005 do |env|
|
110
|
+
@config.isupport.parse(env[:params])
|
111
|
+
end
|
112
|
+
|
113
|
+
# User ping request.
|
114
|
+
on :query, /^\001PING \d+\001$/ do |env|
|
115
|
+
time = env[:message].scan(/\d+/)[0]
|
116
|
+
env[:user].notice("\001PING #{time}\001")
|
117
|
+
end
|
118
|
+
|
119
|
+
on :query, /^\001VERSION\001$/ do |env|
|
120
|
+
env[:user].notice("\001VERSION Vetinari #{Vetinari::VERSION} (https://github.com/tbuehlmann/vetinari)")
|
121
|
+
end
|
122
|
+
|
123
|
+
on :query, /^\001TIME\001$/ do |env|
|
124
|
+
env[:user].notice("\001TIME #{Time.now.strftime('%a %b %d %H:%M:%S %Y')}\001")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def setup_channel_and_user_tracking
|
129
|
+
# Add the bot user to the user container when connected.
|
130
|
+
on 001 do |env|
|
131
|
+
nick = env[:params].split(/ /).first
|
132
|
+
@user = User.new(nick, @actor) # TODO
|
133
|
+
@users.add(@user)
|
134
|
+
end
|
135
|
+
|
136
|
+
on :join do |env|
|
137
|
+
joined_user = {
|
138
|
+
:nick => env.delete(:nick),
|
139
|
+
:user => env.delete(:user),
|
140
|
+
:host => env.delete(:host)
|
141
|
+
}
|
142
|
+
channel = env.delete(:channel)
|
143
|
+
|
144
|
+
# TODO: Update existing users with user/host information.
|
145
|
+
|
146
|
+
user = @users[joined_user[:nick]]
|
147
|
+
|
148
|
+
if user
|
149
|
+
if user.bot?
|
150
|
+
channel = Channel.new(channel, @actor)
|
151
|
+
channel.get_mode
|
152
|
+
@channels.add(channel)
|
153
|
+
else
|
154
|
+
channel = @channels[channel]
|
155
|
+
end
|
156
|
+
else
|
157
|
+
channel = @channels[channel]
|
158
|
+
user = User.new(joined_user[:nick], self)
|
159
|
+
@users.add(user)
|
160
|
+
end
|
161
|
+
|
162
|
+
channel.add_user(user, [])
|
163
|
+
env[:channel] = channel
|
164
|
+
env[:user] = user
|
165
|
+
end
|
166
|
+
|
167
|
+
on 353 do |env|
|
168
|
+
channel_name = env[:params].split(/ /)[2]
|
169
|
+
channel = @channels[channel_name]
|
170
|
+
nicks_with_prefixes = env[:params].scan(/:(.*)/)[0][0].split(/ /)
|
171
|
+
nicks, prefixes = [], []
|
172
|
+
channel_prefixes = @config.isupport['PREFIX'].values.map do |p|
|
173
|
+
Regexp.escape(p)
|
174
|
+
end.join('|')
|
175
|
+
|
176
|
+
nicks_with_prefixes.each do |nick_with_prefixes|
|
177
|
+
nick = nick_with_prefixes.gsub(/#{channel_prefixes}/, '')
|
178
|
+
prefixes = nick_with_prefixes.scan(/#{channel_prefixes}/)
|
179
|
+
|
180
|
+
unless user = @users[nick]
|
181
|
+
user = User.new(nick, @actor)
|
182
|
+
@users.add(user)
|
183
|
+
end
|
184
|
+
|
185
|
+
channel.add_user(user, prefixes)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
on :part do |env|
|
190
|
+
nick = env.delete(:nick)
|
191
|
+
user = env.delete(:user)
|
192
|
+
host = env.delete(:host)
|
193
|
+
|
194
|
+
# TODO: Update existing users with user/host information.
|
195
|
+
|
196
|
+
user = @users[nick]
|
197
|
+
channel = @channels[env.delete(:channel)]
|
198
|
+
|
199
|
+
if user.bot?
|
200
|
+
# Remove the channel from the channel_list.
|
201
|
+
@channels.remove(channel)
|
202
|
+
|
203
|
+
# Remove all users from the user_list that do not share channels
|
204
|
+
# with the Thaum.
|
205
|
+
all_known_users = @channels.channels.flat_map(&:users)
|
206
|
+
@users.kill_zombie_users(all_known_users)
|
207
|
+
else
|
208
|
+
channel.remove_user(user)
|
209
|
+
remove_user = @channels.channels.none? do |_channel|
|
210
|
+
_channel.has_user?(user)
|
211
|
+
end
|
212
|
+
|
213
|
+
@users.remove(user) if remove_user
|
214
|
+
end
|
215
|
+
|
216
|
+
env[:channel] = channel
|
217
|
+
env[:user] = user
|
218
|
+
end
|
219
|
+
|
220
|
+
on :kick do |env|
|
221
|
+
nick = env.delete(:nick)
|
222
|
+
user = env.delete(:user)
|
223
|
+
host = env.delete(:host)
|
224
|
+
|
225
|
+
# TODO: Update existing users with user/host information.
|
226
|
+
|
227
|
+
channel = @channels[env.delete(:channel)]
|
228
|
+
kicker = @users[nick]
|
229
|
+
kickee = @users[env.delete(:kickee)]
|
230
|
+
|
231
|
+
channel.remove_user(kickee)
|
232
|
+
|
233
|
+
if kickee.bot?
|
234
|
+
# Remove the channel from the channel_list.
|
235
|
+
@channels.remove(channel)
|
236
|
+
|
237
|
+
# Remove all users from the user_list that do not share channels
|
238
|
+
# with the Thaum.
|
239
|
+
all_known_users = @channels.channels.map(&:users).flatten
|
240
|
+
@users.kill_zombie_users(all_known_users)
|
241
|
+
else
|
242
|
+
remove_user = @channels.channels.none? do |_channel|
|
243
|
+
_channel.has_user?(kickee)
|
244
|
+
end
|
245
|
+
|
246
|
+
@users.remove(kickee) if remove_user
|
247
|
+
end
|
248
|
+
|
249
|
+
env[:kicker] = kicker
|
250
|
+
env[:kickee] = kickee
|
251
|
+
env[:channel] = channel
|
252
|
+
end
|
253
|
+
|
254
|
+
# If @config.rejoin_after_kick is set to `true`, let
|
255
|
+
# the Thaum rejoin a channel after being kicked.
|
256
|
+
on :kick do |env|
|
257
|
+
if @config.rejoin_after_kick && env[:kickee].bot?
|
258
|
+
key = env[:channel].modes['k']
|
259
|
+
env[:channel].join(key)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
on :quit do |env|
|
264
|
+
nick = env.delete(:nick)
|
265
|
+
user = env.delete(:user)
|
266
|
+
host = env.delete(:host)
|
267
|
+
|
268
|
+
# TODO: Update existing users with user/host information.
|
269
|
+
|
270
|
+
user = @users[nick]
|
271
|
+
|
272
|
+
if user.bot?
|
273
|
+
channels = @channels.clear
|
274
|
+
@users.clear
|
275
|
+
else
|
276
|
+
channels = @channels.remove_user(user)
|
277
|
+
@users.remove(user)
|
278
|
+
end
|
279
|
+
|
280
|
+
env[:user] = user
|
281
|
+
env[:channels] = channels
|
282
|
+
end
|
283
|
+
|
284
|
+
on :disconnect do
|
285
|
+
@channels.clear
|
286
|
+
@users.clear
|
287
|
+
end
|
288
|
+
|
289
|
+
on :channel do |env|
|
290
|
+
nick = env[:nick]
|
291
|
+
user = env[:user]
|
292
|
+
host = env[:host]
|
293
|
+
|
294
|
+
# TODO: Update existing users with user/host information.
|
295
|
+
|
296
|
+
env[:channel] = @channels[env[:channel]]
|
297
|
+
env[:user] = @users[nick]
|
298
|
+
end
|
299
|
+
|
300
|
+
on :query do |env|
|
301
|
+
nick = env[:nick]
|
302
|
+
user = env[:user]
|
303
|
+
host = env[:host]
|
304
|
+
# TODO: Update existing users with user/host information.
|
305
|
+
|
306
|
+
env[:user] = @users[nick] || User.new(nick, @actor)
|
307
|
+
end
|
308
|
+
|
309
|
+
on :query, /\A\001DCC SEND \"?\S+\"? \d+ \d+ \d+\001\z/ do |env|
|
310
|
+
results = env[:message].scan(/\A\001DCC SEND \"?(\S+)\"? (\d+) (\d+) (\d+)\001\z/)
|
311
|
+
filename, ip, port, filesize = results.first
|
312
|
+
filename = filename.delete("/\\")
|
313
|
+
ip = IPAddr.new(ip.to_i, Socket::AF_INET)
|
314
|
+
port = Integer(port)
|
315
|
+
filesize = Integer(filesize)
|
316
|
+
file = Dcc::Incoming::File.new(env[:user], filename, ip, port, filesize, @actor)
|
317
|
+
@callbacks.call(env.merge(:type => :dcc, :file => file))
|
318
|
+
end
|
319
|
+
|
320
|
+
on :channel_mode do |env|
|
321
|
+
# TODO: Update existing users with user/host information.
|
322
|
+
# nick = env[:nick]
|
323
|
+
# user = env[:user]
|
324
|
+
# host = env[:host]
|
325
|
+
|
326
|
+
nick = env.delete(:nick)
|
327
|
+
params = env.delete(:params)
|
328
|
+
modes = env.delete(:modes)
|
329
|
+
|
330
|
+
channel = @channels[env.delete(:channel)]
|
331
|
+
env[:channel] = channel
|
332
|
+
env[:user] = @users[nick]
|
333
|
+
env[:channel_modes] = ModeParser.parse(modes, params, @config.isupport)
|
334
|
+
|
335
|
+
env[:channel_modes].each do |mode|
|
336
|
+
channel.set_mode(mode, @config.isupport)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
# Response to MODE command, giving back the channel modes.
|
341
|
+
on 324 do |env|
|
342
|
+
split = env[:params].split(/ /)
|
343
|
+
channel_name = split[1]
|
344
|
+
channel = @channels[channel_name]
|
345
|
+
|
346
|
+
if channel
|
347
|
+
modes = split[2]
|
348
|
+
params = split[3..-1]
|
349
|
+
mode_changes = ModeParser.parse(modes, params, @config.isupport)
|
350
|
+
|
351
|
+
mode_changes.each do |mode_change|
|
352
|
+
channel.set_mode(mode_change, @config.isupport)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Vetinari
|
2
|
+
class Callback
|
3
|
+
include Celluloid
|
4
|
+
|
5
|
+
attr_reader :event
|
6
|
+
attr_writer :container
|
7
|
+
|
8
|
+
def initialize(event, pattern, proc, container, uuid)
|
9
|
+
@event = event
|
10
|
+
@pattern = pattern
|
11
|
+
@proc = proc
|
12
|
+
@container = container
|
13
|
+
@uuid = uuid
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
begin
|
18
|
+
@proc.call(env) if matching?(env)
|
19
|
+
rescue => e
|
20
|
+
loggers = @container.bot.config.loggers
|
21
|
+
loggers.error "-- #{e.class}: #{e.message}"
|
22
|
+
e.backtrace.each { |line| loggers.error("-- #{line}") }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove
|
27
|
+
@container.remove(@event, @uuid)
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect
|
31
|
+
event = @event.inspect
|
32
|
+
pattern = @pattern.inspect
|
33
|
+
uuid = @uuid.inspect
|
34
|
+
"#<Callback event=#{event} pattern=#{pattern} uuid=#{uuid}>"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def matching?(env)
|
40
|
+
case @event
|
41
|
+
when :channel, :query
|
42
|
+
env[:message] =~ @pattern
|
43
|
+
else
|
44
|
+
true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Vetinari
|
2
|
+
class CallbackContainer
|
3
|
+
attr_reader :bot
|
4
|
+
|
5
|
+
def initialize(bot)
|
6
|
+
@bot = bot
|
7
|
+
@callbacks = Hash.new { |hash, key| hash[key] = {} }
|
8
|
+
@mutex = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(event, pattern, worker, proc)
|
12
|
+
uuid = Celluloid.uuid
|
13
|
+
args = [event, pattern, proc, self, uuid]
|
14
|
+
worker = Integer(worker)
|
15
|
+
|
16
|
+
case
|
17
|
+
when worker == 1
|
18
|
+
callback = Callback.new(*args)
|
19
|
+
synchronicity = :async
|
20
|
+
when worker > 1
|
21
|
+
callback = Callback.pool(:size => worker, :args => args)
|
22
|
+
synchronicity = :async
|
23
|
+
else
|
24
|
+
callback = Callback.new(*args)
|
25
|
+
synchronicity = :sync
|
26
|
+
end
|
27
|
+
|
28
|
+
@mutex.synchronize do
|
29
|
+
@callbacks[callback.event][uuid] = {
|
30
|
+
:callback => callback,
|
31
|
+
:synchronicity => synchronicity
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
callback
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove(event, uuid)
|
39
|
+
@mutex.synchronize do
|
40
|
+
if @callbacks.key?(event)
|
41
|
+
hash = @callbacks[event].delete(uuid)
|
42
|
+
|
43
|
+
if hash
|
44
|
+
# https://github.com/celluloid/celluloid/issues/197
|
45
|
+
# callback.soft_terminate
|
46
|
+
|
47
|
+
# #terminate is broken for Pools:
|
48
|
+
# https://github.com/celluloid/celluloid/pull/207
|
49
|
+
#
|
50
|
+
# So, don't terminate for now.
|
51
|
+
# hash[:callback].terminate
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
def call(env)
|
61
|
+
callbacks = nil
|
62
|
+
|
63
|
+
@mutex.synchronize do
|
64
|
+
if @callbacks.key?(env[:type])
|
65
|
+
callbacks = @callbacks[env[:type]].values.dup
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if callbacks
|
70
|
+
callbacks.each do |hash|
|
71
|
+
case hash[:synchronicity]
|
72
|
+
when :sync
|
73
|
+
hash[:callback].call(env)
|
74
|
+
when :async
|
75
|
+
hash[:callback].async.call(env)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
"#<CallbackContainer bot=#{@bot.inspect}>"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|