marvin 0.8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/bin/marvin +33 -0
  2. data/handlers/debug_handler.rb +5 -0
  3. data/handlers/hello_world.rb +9 -0
  4. data/handlers/keiki_thwopper.rb +21 -0
  5. data/handlers/simple_logger.rb +24 -0
  6. data/handlers/tweet_tweet.rb +19 -0
  7. data/lib/marvin.rb +56 -0
  8. data/lib/marvin/abstract_client.rb +146 -0
  9. data/lib/marvin/abstract_parser.rb +29 -0
  10. data/lib/marvin/base.rb +195 -0
  11. data/lib/marvin/client/actions.rb +104 -0
  12. data/lib/marvin/client/default_handlers.rb +97 -0
  13. data/lib/marvin/command_handler.rb +91 -0
  14. data/lib/marvin/console.rb +50 -0
  15. data/lib/marvin/core_commands.rb +49 -0
  16. data/lib/marvin/distributed.rb +8 -0
  17. data/lib/marvin/distributed/client.rb +225 -0
  18. data/lib/marvin/distributed/handler.rb +85 -0
  19. data/lib/marvin/distributed/protocol.rb +88 -0
  20. data/lib/marvin/distributed/server.rb +154 -0
  21. data/lib/marvin/dsl.rb +103 -0
  22. data/lib/marvin/exception_tracker.rb +19 -0
  23. data/lib/marvin/exceptions.rb +11 -0
  24. data/lib/marvin/irc.rb +7 -0
  25. data/lib/marvin/irc/client.rb +168 -0
  26. data/lib/marvin/irc/event.rb +39 -0
  27. data/lib/marvin/irc/replies.rb +154 -0
  28. data/lib/marvin/logging_handler.rb +76 -0
  29. data/lib/marvin/middle_man.rb +103 -0
  30. data/lib/marvin/parsers.rb +9 -0
  31. data/lib/marvin/parsers/command.rb +107 -0
  32. data/lib/marvin/parsers/prefixes.rb +8 -0
  33. data/lib/marvin/parsers/prefixes/host_mask.rb +35 -0
  34. data/lib/marvin/parsers/prefixes/server.rb +24 -0
  35. data/lib/marvin/parsers/ragel_parser.rb +720 -0
  36. data/lib/marvin/parsers/ragel_parser.rl +143 -0
  37. data/lib/marvin/parsers/simple_parser.rb +35 -0
  38. data/lib/marvin/settings.rb +31 -0
  39. data/lib/marvin/test_client.rb +58 -0
  40. data/lib/marvin/util.rb +54 -0
  41. data/templates/boot.erb +3 -0
  42. data/templates/connections.yml.erb +10 -0
  43. data/templates/debug_handler.erb +5 -0
  44. data/templates/hello_world.erb +10 -0
  45. data/templates/rakefile.erb +15 -0
  46. data/templates/settings.yml.erb +8 -0
  47. data/templates/setup.erb +31 -0
  48. data/templates/test_helper.erb +17 -0
  49. data/test/abstract_client_test.rb +63 -0
  50. data/test/parser_comparison.rb +62 -0
  51. data/test/parser_test.rb +266 -0
  52. data/test/test_helper.rb +62 -0
  53. data/test/util_test.rb +57 -0
  54. metadata +136 -0
@@ -0,0 +1,11 @@
1
+ module Marvin
2
+
3
+ class Error < StandardError; end
4
+
5
+ # Used to stop the flow of handler chains.
6
+ class HaltHandlerProcessing < Error; end
7
+
8
+ # Used for when an expression can't be parsed
9
+ class UnparseableMessage < Error; end
10
+
11
+ end
data/lib/marvin/irc.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Marvin
2
+ module IRC
3
+ autoload :Client, 'marvin/irc/client'
4
+ autoload :Event, 'marvin/irc/event'
5
+ autoload :Replies, 'marvin/irc/replies'
6
+ end
7
+ end
@@ -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