marvin 0.8.0.0
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.
- 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
|