marvin 0.8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/marvin +33 -0
- data/handlers/debug_handler.rb +5 -0
- data/handlers/hello_world.rb +9 -0
- data/handlers/keiki_thwopper.rb +21 -0
- data/handlers/simple_logger.rb +24 -0
- data/handlers/tweet_tweet.rb +19 -0
- data/lib/marvin.rb +56 -0
- data/lib/marvin/abstract_client.rb +146 -0
- data/lib/marvin/abstract_parser.rb +29 -0
- data/lib/marvin/base.rb +195 -0
- data/lib/marvin/client/actions.rb +104 -0
- data/lib/marvin/client/default_handlers.rb +97 -0
- data/lib/marvin/command_handler.rb +91 -0
- data/lib/marvin/console.rb +50 -0
- data/lib/marvin/core_commands.rb +49 -0
- data/lib/marvin/distributed.rb +8 -0
- data/lib/marvin/distributed/client.rb +225 -0
- data/lib/marvin/distributed/handler.rb +85 -0
- data/lib/marvin/distributed/protocol.rb +88 -0
- data/lib/marvin/distributed/server.rb +154 -0
- data/lib/marvin/dsl.rb +103 -0
- data/lib/marvin/exception_tracker.rb +19 -0
- data/lib/marvin/exceptions.rb +11 -0
- data/lib/marvin/irc.rb +7 -0
- data/lib/marvin/irc/client.rb +168 -0
- data/lib/marvin/irc/event.rb +39 -0
- data/lib/marvin/irc/replies.rb +154 -0
- data/lib/marvin/logging_handler.rb +76 -0
- data/lib/marvin/middle_man.rb +103 -0
- data/lib/marvin/parsers.rb +9 -0
- data/lib/marvin/parsers/command.rb +107 -0
- data/lib/marvin/parsers/prefixes.rb +8 -0
- data/lib/marvin/parsers/prefixes/host_mask.rb +35 -0
- data/lib/marvin/parsers/prefixes/server.rb +24 -0
- data/lib/marvin/parsers/ragel_parser.rb +720 -0
- data/lib/marvin/parsers/ragel_parser.rl +143 -0
- data/lib/marvin/parsers/simple_parser.rb +35 -0
- data/lib/marvin/settings.rb +31 -0
- data/lib/marvin/test_client.rb +58 -0
- data/lib/marvin/util.rb +54 -0
- data/templates/boot.erb +3 -0
- data/templates/connections.yml.erb +10 -0
- data/templates/debug_handler.erb +5 -0
- data/templates/hello_world.erb +10 -0
- data/templates/rakefile.erb +15 -0
- data/templates/settings.yml.erb +8 -0
- data/templates/setup.erb +31 -0
- data/templates/test_helper.erb +17 -0
- data/test/abstract_client_test.rb +63 -0
- data/test/parser_comparison.rb +62 -0
- data/test/parser_test.rb +266 -0
- data/test/test_helper.rb +62 -0
- data/test/util_test.rb +57 -0
- metadata +136 -0
data/lib/marvin/irc.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module Marvin::IRC
|
4
|
+
class Client < Marvin::AbstractClient
|
5
|
+
|
6
|
+
@@stopped = false
|
7
|
+
|
8
|
+
attr_accessor :em_connection
|
9
|
+
|
10
|
+
class EMConnection < EventMachine::Protocols::LineAndTextProtocol
|
11
|
+
is :loggable
|
12
|
+
|
13
|
+
attr_accessor :client, :port, :configuration
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
@configuration = args.last.is_a?(Marvin::Nash) ? args.pop : Marvin::Nash.new
|
17
|
+
super(*args)
|
18
|
+
@client = Marvin::IRC::Client.new(@configuration)
|
19
|
+
@client.em_connection = self
|
20
|
+
@connected = false
|
21
|
+
rescue Exception => e
|
22
|
+
Marvin::ExceptionTracker.log(e)
|
23
|
+
Marvin::IRC::Client.stop
|
24
|
+
end
|
25
|
+
|
26
|
+
def post_init
|
27
|
+
super
|
28
|
+
if should_use_ssl?
|
29
|
+
logger.info "Starting SSL for #{host_with_port}"
|
30
|
+
start_tls
|
31
|
+
else
|
32
|
+
connected!
|
33
|
+
end
|
34
|
+
rescue Exception => e
|
35
|
+
Marvin::ExceptionTracker.log(e)
|
36
|
+
Marvin::IRC::Client.stop
|
37
|
+
end
|
38
|
+
|
39
|
+
def ssl_handshake_completed
|
40
|
+
logger.info "SSL handshake completed for #{host_with_port}"
|
41
|
+
connected! if should_use_ssl?
|
42
|
+
rescue Exception => e
|
43
|
+
Marvin::ExceptionTracker.log(e)
|
44
|
+
Marvin::IRC::Client.stop
|
45
|
+
end
|
46
|
+
|
47
|
+
def unbind
|
48
|
+
@client.process_disconnect
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
def receive_line(line)
|
53
|
+
return unless @connected
|
54
|
+
line = line.strip
|
55
|
+
logger.debug "<< #{line}"
|
56
|
+
@client.receive_line(line)
|
57
|
+
rescue Exception => e
|
58
|
+
logger.warn "Uncaught exception raised; Likely in Marvin"
|
59
|
+
Marvin::ExceptionTracker.log(e)
|
60
|
+
end
|
61
|
+
|
62
|
+
def send_line(*lines)
|
63
|
+
return unless @connected
|
64
|
+
lines.each do |line|
|
65
|
+
logger.debug ">> #{line.strip}"
|
66
|
+
send_data line
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def connected!
|
71
|
+
@connected = true
|
72
|
+
@client.process_connect
|
73
|
+
end
|
74
|
+
|
75
|
+
def should_use_ssl?
|
76
|
+
@should_use_ssl ||= @configuration.ssl?
|
77
|
+
end
|
78
|
+
|
79
|
+
def host_with_port
|
80
|
+
"#{@configuration.host}:#{@configuration.port}"
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
## Client specific details
|
86
|
+
|
87
|
+
def send_line(*args)
|
88
|
+
@em_connection.send_line(*args)
|
89
|
+
end
|
90
|
+
|
91
|
+
class << self
|
92
|
+
|
93
|
+
# Starts the EventMachine loop and hence starts up the actual
|
94
|
+
# networking portion of the IRC Client.
|
95
|
+
def run(opts = {}, force = false)
|
96
|
+
self.development = opts[:development]
|
97
|
+
return if @stopped && !force
|
98
|
+
self.setup # So we have options etc
|
99
|
+
connections_file = Marvin::Settings.root / "config" / "connections.yml"
|
100
|
+
connections = Marvin::Nash.load_file(connections_file) rescue nil
|
101
|
+
if connections.present?
|
102
|
+
# Use epoll if available
|
103
|
+
EventMachine.kqueue
|
104
|
+
EventMachine.epoll
|
105
|
+
EventMachine.run do
|
106
|
+
connections.each_pair do |server, configuration|
|
107
|
+
connect(configuration.merge(:server => server.to_s))
|
108
|
+
end
|
109
|
+
Marvin::Distributed::Server.start if Marvin::Distributed::Handler.registered?
|
110
|
+
@@stopped = false
|
111
|
+
end
|
112
|
+
else
|
113
|
+
logger.fatal "config/connections.yml couldn't be loaded."
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def connect(c, &blk)
|
118
|
+
c = normalize_connection_options(c)
|
119
|
+
raise ArgumentError, "Your connection options must specify a server" if !c.server?
|
120
|
+
raise ArgumentError, "Your connection options must specify a port" if !c.port?
|
121
|
+
real_block = blk.present? ? proc { |c| blk.call(connection.client) } : nil
|
122
|
+
logger.info "Connecting to #{c.server}:#{c.port} (using ssl: #{c.ssl?}) - Channels: #{c.channels.join(", ")}"
|
123
|
+
EventMachine.connect(c.server, c.port, EMConnection, c, &real_block)
|
124
|
+
end
|
125
|
+
|
126
|
+
def stop
|
127
|
+
return if @@stopped
|
128
|
+
logger.debug "Telling all connections to quit"
|
129
|
+
connections.dup.each { |connection| connection.quit }
|
130
|
+
logger.debug "Telling Event Machine to Stop"
|
131
|
+
EventMachine.stop_event_loop
|
132
|
+
logger.debug "Stopped."
|
133
|
+
@@stoped = true
|
134
|
+
end
|
135
|
+
|
136
|
+
def add_reconnect(c)
|
137
|
+
logger.warn "Adding timer to reconnect to #{c.server}:#{c.port} in 15 seconds"
|
138
|
+
EventMachine.add_timer(15) do
|
139
|
+
logger.warn "Preparing to reconnect to #{c.server}:#{c.port}"
|
140
|
+
connect(c)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
protected
|
145
|
+
|
146
|
+
def normalize_connection_options(config)
|
147
|
+
config = Marvin::Nash.new(config) if !config.is_a?(Marvin::Nash)
|
148
|
+
config = config.normalized
|
149
|
+
channels = config.channels
|
150
|
+
if channels.present?
|
151
|
+
config.channels = [*channels].compact.reject { |c| c.blank? }
|
152
|
+
else
|
153
|
+
config.channels = []
|
154
|
+
end
|
155
|
+
config.host ||= config.server
|
156
|
+
return config
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
# Registers a callback handle that will be periodically run.
|
162
|
+
def periodically(timing, event_callback)
|
163
|
+
EventMachine.add_periodic_timer(timing) { dispatch(event_callback.to_sym) }
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Marvin::IRC
|
2
|
+
class Event
|
3
|
+
attr_accessor :keys, :name, :raw_arguments, :prefix
|
4
|
+
|
5
|
+
def initialize(name, *args)
|
6
|
+
@name = name.to_sym
|
7
|
+
@keys = args.flatten.map { |k| k.to_sym }
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_hash
|
11
|
+
return @hash_value unless @hash_value.blank?
|
12
|
+
results = {}
|
13
|
+
values = @raw_arguments.to_a
|
14
|
+
last_index = @keys.size - 1
|
15
|
+
@keys.each_with_index do |key, i|
|
16
|
+
results[key] = (i == last_index ? values.join(" ").strip : values.shift)
|
17
|
+
end
|
18
|
+
results.merge!(@prefix.to_hash) unless @prefix.blank?
|
19
|
+
@hash_value = results
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#<Marvin::IRC::Event name=#{@name} attributes=#{to_hash.inspect} >"
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_event_name(prefix = nil)
|
27
|
+
[prefix, @name].join("_").to_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_incoming_event_name
|
31
|
+
to_event_name :incoming
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_outgoing_event_name
|
35
|
+
to_event_name :outgoing
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module Marvin
|
2
|
+
module IRC
|
3
|
+
module Replies
|
4
|
+
|
5
|
+
def self.[](key)
|
6
|
+
key = key.to_s.upcase
|
7
|
+
if const_defined?(key)
|
8
|
+
value = const_get(key)
|
9
|
+
return "%03d" % value
|
10
|
+
else
|
11
|
+
return nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Common response codes.
|
16
|
+
RPL_WELCOME = 001
|
17
|
+
RPL_YOURHOST = 002
|
18
|
+
RPL_CREATED = 003
|
19
|
+
RPL_MYINFO = 004
|
20
|
+
RPL_BOUNCE = 005
|
21
|
+
RPL_TRACELINK = 200
|
22
|
+
RPL_TRACECONNECTING = 201
|
23
|
+
RPL_TRACEHANDSHAKE = 202
|
24
|
+
RPL_TRACEUNKNOWN = 203
|
25
|
+
RPL_TRACEOPERATOR = 204
|
26
|
+
RPL_TRACEUSER = 205
|
27
|
+
RPL_TRACESERVER = 206
|
28
|
+
RPL_TRACESERVICE = 207
|
29
|
+
RPL_TRACENEWTYPE = 208
|
30
|
+
RPL_TRACECLASS = 209
|
31
|
+
RPL_TRACERECONNECT = 210
|
32
|
+
RPL_TRACELOG = 261
|
33
|
+
RPL_TRACEEND = 262
|
34
|
+
RPL_STATSLINKINFO = 211
|
35
|
+
RPL_STATSCOMMANDS = 212
|
36
|
+
RPL_ENDOFSTATS = 219
|
37
|
+
RPL_STATSUPTIME = 242
|
38
|
+
RPL_STATSOLINE = 243
|
39
|
+
RPL_UMODEIS = 221
|
40
|
+
RPL_SERVLIST = 234
|
41
|
+
RPL_SERVLISTEND = 235
|
42
|
+
RPL_LUSERCLIENT = 251
|
43
|
+
RPL_LUSEROP = 252
|
44
|
+
RPL_LUSERUNKNOWN = 253
|
45
|
+
RPL_LUSERCHANNELS = 254
|
46
|
+
RPL_LUSERME = 255
|
47
|
+
RPL_ADMINME = 256
|
48
|
+
RPL_ADMINEMAIL = 259
|
49
|
+
RPL_TRYAGAIN = 263
|
50
|
+
RPL_USERHOST = 302
|
51
|
+
RPL_ISON = 303
|
52
|
+
RPL_AWAY = 301
|
53
|
+
RPL_UNAWAY = 305
|
54
|
+
RPL_NOWAWAY = 306
|
55
|
+
RPL_WHOISUSER = 311
|
56
|
+
RPL_WHOISSERVER = 312
|
57
|
+
RPL_WHOISOPERATOR = 313
|
58
|
+
RPL_WHOISIDLE = 317
|
59
|
+
RPL_ENDOFWHOIS = 318
|
60
|
+
RPL_WHOISCHANNELS = 319
|
61
|
+
RPL_WHOWASUSER = 314
|
62
|
+
RPL_ENDOFWHOWAS = 369
|
63
|
+
RPL_LISTSTART = 321
|
64
|
+
RPL_LIST = 322
|
65
|
+
RPL_LISTEND = 323
|
66
|
+
RPL_UNIQOPIS = 325
|
67
|
+
RPL_CHANNELMODEIS = 324
|
68
|
+
RPL_NOTOPIC = 331
|
69
|
+
RPL_TOPIC = 332
|
70
|
+
RPL_INVITING = 341
|
71
|
+
RPL_SUMMONING = 342
|
72
|
+
RPL_INVITELIST = 346
|
73
|
+
RPL_ENDOFINVITELIST = 347
|
74
|
+
RPL_EXCEPTLIST = 348
|
75
|
+
RPL_ENDOFEXCEPTLIST = 349
|
76
|
+
RPL_VERSION = 351
|
77
|
+
RPL_WHOREPLY = 352
|
78
|
+
RPL_ENDOFWHO = 315
|
79
|
+
RPL_NAMREPLY = 353
|
80
|
+
RPL_ENDOFNAMES = 366
|
81
|
+
RPL_LINKS = 364
|
82
|
+
RPL_ENDOFLINKS = 365
|
83
|
+
RPL_BANLIST = 367
|
84
|
+
RPL_ENDOFBANLIST = 368
|
85
|
+
RPL_INFO = 371
|
86
|
+
RPL_ENDOFINFO = 374
|
87
|
+
RPL_MOTDSTART = 375
|
88
|
+
RPL_MOTD = 372
|
89
|
+
RPL_ENDOFMOTD = 376
|
90
|
+
RPL_YOUREOPER = 381
|
91
|
+
RPL_REHASHING = 382
|
92
|
+
RPL_YOURESERVICE = 383
|
93
|
+
RPL_TIME = 391
|
94
|
+
RPL_USERSSTART = 392
|
95
|
+
RPL_USERS = 393
|
96
|
+
RPL_ENDOFUSERS = 394
|
97
|
+
RPL_NOUSERS = 395
|
98
|
+
ERR_NOSUCHNICK = 401
|
99
|
+
ERR_NOSUCHSERVER = 402
|
100
|
+
ERR_NOSUCHCHANNEL = 403
|
101
|
+
ERR_CANNOTSENDTOCHAN = 404
|
102
|
+
ERR_TOOMANYCHANNELS = 405
|
103
|
+
ERR_WASNOSUCHNICK = 406
|
104
|
+
ERR_TOOMANYTARGETS = 407
|
105
|
+
ERR_NOSUCHSERVICE = 408
|
106
|
+
ERR_NOORIGIN = 409
|
107
|
+
ERR_NORECIPIENT = 411
|
108
|
+
ERR_NOTEXTTOSEND = 412
|
109
|
+
ERR_NOTOPLEVEL = 413
|
110
|
+
ERR_WILDTOPLEVEL = 414
|
111
|
+
ERR_BADMASK = 415
|
112
|
+
ERR_UNKNOWNCOMMAND = 421
|
113
|
+
ERR_NOMOTD = 422
|
114
|
+
ERR_NOADMININFO = 423
|
115
|
+
ERR_FILEERROR = 424
|
116
|
+
ERR_NONICKNAMEGIVEN = 431
|
117
|
+
ERR_ERRONEUSNICKNAME = 432
|
118
|
+
ERR_NICKNAMEINUSE = 433
|
119
|
+
ERR_NICKCOLLISION = 436
|
120
|
+
ERR_UNAVAILRESOURCE = 437
|
121
|
+
ERR_USERNOTINCHANNEL = 441
|
122
|
+
ERR_NOTONCHANNEL = 442
|
123
|
+
ERR_USERONCHANNEL = 443
|
124
|
+
ERR_NOLOGIN = 444
|
125
|
+
ERR_SUMMONDISABLED = 445
|
126
|
+
ERR_USERSDISABLED = 446
|
127
|
+
ERR_NOTREGISTERED = 451
|
128
|
+
ERR_NEEDMOREPARAMS = 461
|
129
|
+
ERR_ALREADYREGISTRED = 462
|
130
|
+
ERR_NOPERMFORHOST = 463
|
131
|
+
ERR_PASSWDMISMATCH = 464
|
132
|
+
ERR_YOUREBANNEDCREEP = 465
|
133
|
+
ERR_YOUWILLBEBANNED = 466
|
134
|
+
ERR_KEYSET = 467
|
135
|
+
ERR_CHANNELISFULL = 471
|
136
|
+
ERR_UNKNOWNMODE = 472
|
137
|
+
ERR_INVITEONLYCHAN = 473
|
138
|
+
ERR_BANNEDFROMCHAN = 474
|
139
|
+
ERR_BADCHANNELKEY = 475
|
140
|
+
ERR_BADCHANMASK = 476
|
141
|
+
ERR_NOCHANMODES = 477
|
142
|
+
ERR_BANLISTFULL = 478
|
143
|
+
ERR_NOPRIVILEGES = 481
|
144
|
+
ERR_CHANOPRIVSNEEDED = 482
|
145
|
+
ERR_CANTKILLSERVER = 483
|
146
|
+
ERR_RESTRICTED = 484
|
147
|
+
ERR_UNIQOPPRIVSNEEDED = 485
|
148
|
+
ERR_NOOPERHOST = 491
|
149
|
+
ERR_UMODEUNKNOWNFLAG = 501
|
150
|
+
ERR_USERSDONTMATCH = 502
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Marvin
|
2
|
+
# A generic class to make it easy to implement loggers
|
3
|
+
# in marvin. Users only need to implement 1 method for simple
|
4
|
+
# logging but have control to do more. Please note that
|
5
|
+
# if you want it correctly log the order of messages, it
|
6
|
+
# needs to be the first registered handler, otherwise
|
7
|
+
# outgoing messages will be processed before incoming ones.
|
8
|
+
class LoggingHandler < CommandHandler
|
9
|
+
|
10
|
+
on_event :incoming_message do
|
11
|
+
if should_log?
|
12
|
+
log_incoming(@server, options.nick, options.target, options.message)
|
13
|
+
log_message(@server, options.nick, options.target, options.message)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
on_event :outgoing_message do
|
18
|
+
if should_log?
|
19
|
+
log_outgoing(@server, @nick, options.target, options.message)
|
20
|
+
log_message(@server, @nick, options.target, options.message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
on_event :client_connected do
|
25
|
+
@server = self.client.host_with_port
|
26
|
+
@nick = self.client.nickname
|
27
|
+
setup_logging
|
28
|
+
end
|
29
|
+
|
30
|
+
on_event :client_disconnected do
|
31
|
+
teardown_logging
|
32
|
+
end
|
33
|
+
|
34
|
+
on_event :outgoing_nick do
|
35
|
+
@nick = options.new_nick
|
36
|
+
end
|
37
|
+
|
38
|
+
# Called when the client connects, over ride it to
|
39
|
+
# do any of your specific setup.
|
40
|
+
def setup_logging
|
41
|
+
# NOP
|
42
|
+
end
|
43
|
+
|
44
|
+
# Called when the client disconnects, over ride it to
|
45
|
+
# do any of your specific teardown.
|
46
|
+
def teardown_logging
|
47
|
+
# NOP
|
48
|
+
end
|
49
|
+
|
50
|
+
# Called before logging a message for conditional logging.
|
51
|
+
# Override with your implementation specific version.
|
52
|
+
def should_log?
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
# Log an incoming message (i.e. from another user)
|
57
|
+
# Note that +server+ is in the port host:port,
|
58
|
+
# nick is the origin nick and target is either
|
59
|
+
# a channel or the bots nick (if PM'ed)
|
60
|
+
def log_incoming(server, nick, target, message)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Log an outgoing message (i.e. from this user)
|
64
|
+
# Note that +server+ is in the port host:port,
|
65
|
+
# nick is the bots nick and target is either
|
66
|
+
# a channel or the user the bot is replying to.
|
67
|
+
def log_outgoing(server, nick, target, message)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Logs a message - used when you want to log
|
71
|
+
# both incoming and outgoing messages
|
72
|
+
def log_message(server, nick, target, message)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|