Sutto-marvin 0.4.0 → 0.8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/marvin +22 -156
- data/handlers/keiki_thwopper.rb +21 -0
- data/handlers/tweet_tweet.rb +1 -3
- data/lib/marvin/abstract_client.rb +75 -189
- data/lib/marvin/abstract_parser.rb +9 -11
- data/lib/marvin/base.rb +134 -101
- data/lib/marvin/client/actions.rb +104 -0
- data/lib/marvin/client/default_handlers.rb +97 -0
- data/lib/marvin/command_handler.rb +60 -49
- data/lib/marvin/console.rb +4 -31
- data/lib/marvin/core_commands.rb +30 -12
- 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/distributed.rb +4 -10
- data/lib/marvin/dsl.rb +103 -0
- data/lib/marvin/exception_tracker.rb +7 -4
- data/lib/marvin/irc/client.rb +127 -99
- data/lib/marvin/irc/event.rb +14 -10
- data/lib/marvin/irc.rb +0 -1
- data/lib/marvin/middle_man.rb +1 -1
- data/lib/marvin/parsers/command.rb +10 -8
- data/lib/marvin/parsers/prefixes/host_mask.rb +12 -7
- data/lib/marvin/parsers/prefixes/server.rb +1 -1
- data/lib/marvin/parsers/ragel_parser.rb +59 -52
- data/lib/marvin/parsers/ragel_parser.rl +6 -7
- data/lib/marvin/parsers/simple_parser.rb +4 -9
- data/lib/marvin/parsers.rb +1 -2
- data/lib/marvin/settings.rb +29 -79
- data/lib/marvin/test_client.rb +20 -26
- data/lib/marvin/util.rb +10 -3
- data/lib/marvin.rb +42 -39
- 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/{config/setup.rb → templates/setup.erb} +8 -10
- data/templates/test_helper.erb +17 -0
- data/test/abstract_client_test.rb +63 -0
- data/test/parser_comparison.rb +2 -2
- data/test/parser_test.rb +3 -3
- data/test/test_helper.rb +58 -6
- metadata +51 -83
- data/README.textile +0 -105
- data/TUTORIAL.textile +0 -54
- data/VERSION.yml +0 -4
- data/config/boot.rb +0 -14
- data/config/connections.yml.sample +0 -5
- data/config/settings.yml.sample +0 -13
- data/handlers/logging_handler.rb +0 -89
- data/lib/marvin/core_ext.rb +0 -11
- data/lib/marvin/daemon.rb +0 -71
- data/lib/marvin/data_store.rb +0 -73
- data/lib/marvin/dispatchable.rb +0 -99
- data/lib/marvin/distributed/dispatch_handler.rb +0 -83
- data/lib/marvin/distributed/drb_client.rb +0 -78
- data/lib/marvin/distributed/ring_server.rb +0 -41
- data/lib/marvin/handler.rb +0 -12
- data/lib/marvin/irc/server/abstract_connection.rb +0 -84
- data/lib/marvin/irc/server/base_connection.rb +0 -66
- data/lib/marvin/irc/server/channel.rb +0 -115
- data/lib/marvin/irc/server/named_store.rb +0 -14
- data/lib/marvin/irc/server/remote_interface.rb +0 -77
- data/lib/marvin/irc/server/user/handle_mixin.rb +0 -140
- data/lib/marvin/irc/server/user.rb +0 -5
- data/lib/marvin/irc/server/user_connection.rb +0 -134
- data/lib/marvin/irc/server/virtual_user_connection.rb +0 -80
- data/lib/marvin/irc/server.rb +0 -71
- data/lib/marvin/loader.rb +0 -149
- data/lib/marvin/logger.rb +0 -86
- data/lib/marvin/options.rb +0 -42
- data/lib/marvin/parsers/regexp_parser.rb +0 -93
- data/lib/marvin/status.rb +0 -72
- data/script/client +0 -3
- data/script/console +0 -3
- data/script/distributed_client +0 -3
- data/script/install +0 -1
- data/script/ring_server +0 -4
- data/script/server +0 -4
- data/script/status +0 -3
- data/spec/marvin/abstract_client_test.rb +0 -38
- data/spec/spec_helper.rb +0 -14
data/lib/marvin/base.rb
CHANGED
@@ -1,131 +1,147 @@
|
|
1
|
-
require 'ostruct'
|
2
|
-
|
3
1
|
module Marvin
|
4
|
-
|
2
|
+
|
3
|
+
def self.handler_parent_classes
|
4
|
+
@@handler_parent_classes ||= Hash.new { |h,k| h[k] = Set.new }
|
5
|
+
end
|
6
|
+
|
5
7
|
class Base
|
8
|
+
is :loggable
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
self.logger ||= Marvin::Logger
|
10
|
-
|
11
|
-
attr_accessor :client, :target, :from, :options, :logger
|
12
|
-
class_inheritable_accessor :registered_handlers
|
13
|
-
self.registered_handlers = {}
|
14
|
-
|
15
|
-
def initialize
|
16
|
-
self.registered_handlers ||= {}
|
17
|
-
self.logger ||= Marvin::Logger
|
10
|
+
@@handlers = Hash.new do |h,k|
|
11
|
+
h[k] = Hash.new { |h2, k2| h2[k2] = [] }
|
18
12
|
end
|
19
13
|
|
14
|
+
attr_accessor :client, :target, :from, :options
|
15
|
+
|
20
16
|
class << self
|
17
|
+
|
18
|
+
def registered?
|
19
|
+
@registered ||= false
|
20
|
+
end
|
21
|
+
|
22
|
+
def registered=(value)
|
23
|
+
@registered = !!value
|
24
|
+
end
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
else
|
32
|
-
return rh[self.name][message_name]
|
26
|
+
# Returns an array of all handlers associated with
|
27
|
+
# a specific event name (e.g. :incoming_message)
|
28
|
+
def event_handlers_for(message_name)
|
29
|
+
message_name = message_name.to_sym
|
30
|
+
items = []
|
31
|
+
klass = self
|
32
|
+
while klass != Object
|
33
|
+
items += @@handlers[klass][message_name]
|
34
|
+
klass = klass.superclass
|
33
35
|
end
|
36
|
+
items
|
34
37
|
end
|
35
38
|
|
36
|
-
# Registers a block
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
# on_event :incoming_message, :process_commands
|
41
|
-
#
|
42
|
-
# Will call process_commands the current object
|
43
|
-
# every time incoming message is called.
|
44
|
-
#
|
45
|
-
# on_event :incoming_message do
|
46
|
-
# Marvin::Logger.debug ">> #{options.inspect}"
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# Will invoke the block every time an incoming message
|
50
|
-
# is processed.
|
39
|
+
# Registers a block to be used as an event handler. The first
|
40
|
+
# argument is always the name of the event and the second
|
41
|
+
# is either a method name (e.g. :my_awesome_method) or
|
42
|
+
# a block (which is instance_evaled)
|
51
43
|
def on_event(name, method_name = nil, &blk)
|
52
|
-
|
53
|
-
|
54
|
-
blk = proc { self.send(method_name.to_sym) } if method_name.respond_to?(:to_sym) && blk.blank?
|
55
|
-
self.event_handlers_for(name, false) << blk
|
44
|
+
blk = proc { self.send(method_name) } if method_name.present?
|
45
|
+
@@handlers[self][name] << blk
|
56
46
|
end
|
57
47
|
|
48
|
+
# Like on_event but instead of taking an event name it takes
|
49
|
+
# either a number or a name - corresponding to an IRC numeric
|
50
|
+
# reply.
|
58
51
|
def on_numeric(value, method_name = nil, &blk)
|
59
|
-
|
60
|
-
|
61
|
-
else
|
62
|
-
new_value = Marvin::IRC::Replies[value]
|
63
|
-
end
|
64
|
-
if new_value.nil?
|
65
|
-
logger.error "The numeric '#{value}' was undefined"
|
66
|
-
else
|
67
|
-
blk = proc { self.send(method_name.to_sym) } if method_name.respond_to?(:to_sym) && blk.blank?
|
68
|
-
self.event_handlers_for(:"incoming_numeric_#{new_value}", false) << blk
|
69
|
-
end
|
52
|
+
value = value.is_a?(Numeric) ? ("%03d" % value) : Marvin::IRC::Replies[value]
|
53
|
+
on_event(:"incoming_numeric_#{new_value}", method_name, &blk) if value.present?
|
70
54
|
end
|
71
55
|
|
72
|
-
# Register
|
73
|
-
def register!(parent = Marvin::Settings.
|
56
|
+
# Register this specific handler on the IRC handler.
|
57
|
+
def register!(parent = Marvin::Settings.client)
|
74
58
|
return if self == Marvin::Base # Only do it for sub-classes.
|
75
|
-
parent.register_handler self.new
|
59
|
+
parent.register_handler self.new unless parent.handlers.any? { |h| h.class == self }
|
60
|
+
Marvin.handler_parent_classes[self.name] << parent
|
76
61
|
end
|
77
|
-
|
78
|
-
def
|
79
|
-
|
80
|
-
|
62
|
+
|
63
|
+
def reloading!
|
64
|
+
Marvin.handler_parent_classes[self.name].each do |dispatcher|
|
65
|
+
parent_handlers = dispatcher.handlers
|
66
|
+
related = parent_handlers.select { |h| h.class == self }
|
67
|
+
related.each do |h|
|
68
|
+
h.handle(:reloading, {})
|
69
|
+
dispatcher.delete_handler(h)
|
70
|
+
end
|
81
71
|
end
|
82
|
-
cattr_accessor local_name.to_sym
|
83
|
-
self.send("#{local_name}=", Marvin::DataStore.new(datastore_name))
|
84
|
-
rescue Exception => e
|
85
|
-
logger.debug "Exception in datastore declaration - #{e.inspect}"
|
86
72
|
end
|
87
73
|
|
74
|
+
def reloaded!
|
75
|
+
Marvin.handler_parent_classes[self.name].each do |dispatcher|
|
76
|
+
before = dispatcher.handlers
|
77
|
+
register!(dispatcher)
|
78
|
+
after = dispatcher.handlers
|
79
|
+
(after - before).each { |h| h.handle(:reloaded, {}) }
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
88
84
|
end
|
89
85
|
|
90
|
-
# Given an incoming message, handle it appropriatly.
|
91
86
|
def handle(message, options)
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
87
|
+
dup._handle(message, options)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Given an incoming message, handle it appropriately by getting all
|
91
|
+
# associated event handlers. It also logs any exceptions (aslong as
|
92
|
+
# they raised by halt)
|
93
|
+
def _handle(message, options)
|
94
|
+
setup_details(options)
|
95
|
+
h = self.class.event_handlers_for(message)
|
96
|
+
h.each { |eh| self.instance_eval(&eh) }
|
97
|
+
rescue Exception => e
|
98
|
+
# Pass on halt_handler_processing events.
|
99
|
+
raise e if e.is_a?(Marvin::HaltHandlerProcessing)
|
100
|
+
logger.fatal "Exception processing handler for #{message.inspect}"
|
101
|
+
Marvin::ExceptionTracker.log(e)
|
102
|
+
ensure
|
103
|
+
reset_details
|
100
104
|
end
|
101
105
|
|
106
|
+
# The default handler for numerics. mutates them into a more
|
107
|
+
# friendly version of themselves. It will also pass through
|
108
|
+
# the original incoming_numeric event.
|
102
109
|
def handle_incoming_numeric(opts)
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
110
|
+
handle(:incoming_numeric, opts)
|
111
|
+
handle(:"incoming_numeric_#{opts[:code]}", opts)
|
112
|
+
end
|
113
|
+
|
114
|
+
# msg sends the given text to the current target, be it
|
115
|
+
# either a channel or a specific user.
|
116
|
+
def msg(message, target = self.target)
|
117
|
+
client.msg(target, message)
|
108
118
|
end
|
109
119
|
|
110
|
-
|
111
|
-
|
120
|
+
alias say msg
|
121
|
+
|
122
|
+
def action(message, target = self.target)
|
123
|
+
client.action(target, message)
|
112
124
|
end
|
113
125
|
|
114
|
-
|
115
|
-
|
126
|
+
# A conditional version of message that will only send the message
|
127
|
+
# if the target / from is a user. To do this, it uses from_channel?
|
128
|
+
def pm(message, target)
|
129
|
+
say(message, target) unless from_channel?(target)
|
116
130
|
end
|
117
131
|
|
132
|
+
# Replies to a message. if it was received in a channel, it will
|
133
|
+
# use the standard irc "Name: text" convention for replying whilst
|
134
|
+
# if it was in a direct message it sends it as is.
|
118
135
|
def reply(message)
|
119
136
|
if from_channel?
|
120
|
-
say
|
137
|
+
say("#{from}: #{message}")
|
121
138
|
else
|
122
|
-
say
|
139
|
+
say(message, from)
|
123
140
|
end
|
124
141
|
end
|
125
142
|
|
126
143
|
def ctcp(message)
|
127
|
-
|
128
|
-
say "\01#{message}\01", self.from
|
144
|
+
say("\01#{message}\01", from) if !from_channel?
|
129
145
|
end
|
130
146
|
|
131
147
|
# Request information
|
@@ -133,29 +149,46 @@ module Marvin
|
|
133
149
|
# reflects whether or not the current message / previous message came
|
134
150
|
# from a user via pm.
|
135
151
|
def from_user?
|
136
|
-
|
152
|
+
!from_channel?
|
137
153
|
end
|
138
154
|
|
139
|
-
# Determines whether
|
140
|
-
|
141
|
-
|
155
|
+
# Determines whether a given target (defaulting to the target of the
|
156
|
+
# last message was in a channel)
|
157
|
+
def from_channel?(target = self.target)
|
158
|
+
target.present? && target =~ /^[\&\#]/
|
142
159
|
end
|
143
160
|
|
144
161
|
def addressed?
|
145
|
-
|
162
|
+
from_user? || options.message =~ /^#{client.nickname.downcase}:\s+/i
|
163
|
+
end
|
164
|
+
|
165
|
+
# A Perennial automagical helper for dispatch
|
166
|
+
def registered=(value)
|
167
|
+
self.class.registered = value
|
168
|
+
end
|
169
|
+
|
170
|
+
protected
|
171
|
+
|
172
|
+
# Initializes details for the current cycle - in essence, this makes the
|
173
|
+
# details of the current request available.
|
174
|
+
def setup_details(options)
|
175
|
+
@options = options.is_a?(Marvin::Nash) ? options : Marvin::Nash.new(options.to_hash)
|
176
|
+
@target = @options.target if @options.target?
|
177
|
+
@from = @options.nick if @options.nick?
|
146
178
|
end
|
147
179
|
|
148
|
-
def
|
149
|
-
|
150
|
-
|
151
|
-
|
180
|
+
def reset_details
|
181
|
+
@options = nil
|
182
|
+
@target = nil
|
183
|
+
@from = nil
|
152
184
|
end
|
153
185
|
|
154
|
-
# Halt
|
155
|
-
#
|
156
|
-
#
|
186
|
+
# Halt can be called during the handle / process. Doing so
|
187
|
+
# prevents any more handlers in the handler chain from being
|
188
|
+
# called. It's kind of like return but it works across all
|
189
|
+
# handlers, not just the current one.
|
157
190
|
def halt!
|
158
|
-
raise HaltHandlerProcessing
|
191
|
+
raise Marvin::HaltHandlerProcessing
|
159
192
|
end
|
160
193
|
|
161
194
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Marvin
|
2
|
+
class AbstractClient
|
3
|
+
|
4
|
+
## General IRC Functions / Actions
|
5
|
+
|
6
|
+
# Sends a specified command to the server.
|
7
|
+
# Takes name (e.g. :privmsg) and all of the args.
|
8
|
+
# Very simply formats them as a string correctly
|
9
|
+
# and calls send_data with the results.
|
10
|
+
def command(name, *args)
|
11
|
+
# First, get the appropriate command
|
12
|
+
name = name.to_s.upcase
|
13
|
+
args = args.flatten
|
14
|
+
args << util.last_param(args.pop)
|
15
|
+
send_line "#{name} #{args.compact.join(" ").strip}\r\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Join one or more channels on the current server
|
19
|
+
# e.g.
|
20
|
+
# client.join "#marvin-testing"
|
21
|
+
# client.join ["#marvin-testing", "#rubyonrails"]
|
22
|
+
# client.join "#marvin-testing", "#rubyonrails"
|
23
|
+
def join(*channels_to_join)
|
24
|
+
channels_to_join = channels_to_join.flatten.map { |c| util.channel_name(c) }
|
25
|
+
# If you're joining multiple channels at once, we join them together
|
26
|
+
command :JOIN, channels_to_join.join(",")
|
27
|
+
channels_to_join.each { |channel| dispatch :outgoing_join, :target => channel }
|
28
|
+
logger.info "Sent JOIN for channels #{channels_to_join.join(", ")}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Parts a channel, with an optional reason
|
32
|
+
# e.g.
|
33
|
+
# part "#marvin-testing"
|
34
|
+
# part "#marvin-testing", "Ninjas stole by felafel"
|
35
|
+
def part(channel, reason = nil)
|
36
|
+
channel = util.channel_name(channel)
|
37
|
+
# Send the command anyway, even if we're not a
|
38
|
+
# a recorded member something might of happened.
|
39
|
+
command :part, channel, reason
|
40
|
+
if channels.include?(channel)
|
41
|
+
dispatch :outgoing_part, :target => channel, :reason => reason
|
42
|
+
logger.info "Parted channel #{channel} - #{reason.present? ? reason : "Non given"}"
|
43
|
+
else
|
44
|
+
logger.warn "Parted channel #{channel} but wasn't recorded as member of channel"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Quites from a server, first parting all channels if a second
|
49
|
+
# argument is passed as true
|
50
|
+
# e.g.
|
51
|
+
# quit
|
52
|
+
# quit "Going to grab some z's"
|
53
|
+
def quit(reason = nil, part_before_quit = false)
|
54
|
+
@disconnect_expected = true
|
55
|
+
# If the user wants to part before quitting, they should
|
56
|
+
# pass a second, true, parameter
|
57
|
+
if part_before_quit
|
58
|
+
logger.info "Preparing to part from channels before quitting"
|
59
|
+
channels.to_a.each { |chan| part(chan, reason) }
|
60
|
+
logger.info "Parted from all channels, quitting"
|
61
|
+
end
|
62
|
+
command :quit, reason
|
63
|
+
dispatch :outgoing_quit
|
64
|
+
# Remove the connections from the pool
|
65
|
+
connections.delete(self)
|
66
|
+
logger.info "Quit from #{host_with_port}"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sends a message to a target (either a channel or a user)
|
70
|
+
# e.g.
|
71
|
+
# msg "#marvin-testing", "Hello there!"
|
72
|
+
# msg "SuttoL", "Hey, I'm playing with marvin!"
|
73
|
+
def msg(target, message)
|
74
|
+
command :privmsg, target, message
|
75
|
+
dispatch :outgoing_message, :target => target, :message => message
|
76
|
+
logger.info "Message #{target} - #{message}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Does a CTCP action in a channel (equiv. to doing /me in most IRC clients)
|
80
|
+
# e.g.
|
81
|
+
# action "#marvin-testing", "is about to sleep"
|
82
|
+
# action "SuttoL", "is about to sleep"
|
83
|
+
def action(target, message)
|
84
|
+
command :privmsg, target, "\01ACTION #{message.strip}\01"
|
85
|
+
dispatch :outgoing_action, :target => target, :message => message
|
86
|
+
logger.info "Action sent to #{target} - #{message}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def pong(data)
|
90
|
+
command :pong, data
|
91
|
+
dispatch :outgoing_pong
|
92
|
+
logger.info "PONG sent to #{host_with_port} w/ data - #{data}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def nick(new_nick)
|
96
|
+
logger.info "Changing nick to #{new_nick}"
|
97
|
+
command :nick, new_nick
|
98
|
+
@nickname = new_nick
|
99
|
+
dispatch :outgoing_nick, :new_nick => new_nick
|
100
|
+
logger.info "Nick changed to #{new_nick}"
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Marvin
|
2
|
+
class AbstractClient
|
3
|
+
|
4
|
+
# Default handlers
|
5
|
+
|
6
|
+
# The default handler for all things initialization-related
|
7
|
+
# on the client. Usually, this will send the user command,
|
8
|
+
# set out nick, join all of the channels / rooms we wish
|
9
|
+
# to be in and if a password is specified in the configuration,
|
10
|
+
# it will also attempt to identify us.
|
11
|
+
def handle_client_connected(opts = {})
|
12
|
+
logger.info "About to handle client connected"
|
13
|
+
# If the pass is set
|
14
|
+
unless pass.blank?
|
15
|
+
logger.info "Sending pass for connection"
|
16
|
+
command :pass, pass
|
17
|
+
end
|
18
|
+
# IRC Connection is establish so we send all the required commands to the server.
|
19
|
+
logger.info "Setting default nickname"
|
20
|
+
nick nicks.shift
|
21
|
+
logger.info "Sending user command"
|
22
|
+
command :user, configuration.user, "0", "*", configuration.name
|
23
|
+
rescue Exception => e
|
24
|
+
Marvin::ExceptionTracker.log(e)
|
25
|
+
end
|
26
|
+
|
27
|
+
# handle a bunch of default events that happen at a connection
|
28
|
+
# level instead of a per-app level.
|
29
|
+
|
30
|
+
# The default response for PING's - it simply replies
|
31
|
+
# with a PONG.
|
32
|
+
def handle_incoming_ping(opts = {})
|
33
|
+
logger.info "Received Incoming Ping - Handling with a PONG"
|
34
|
+
pong(opts[:data])
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO: Get the correct mapping for a given
|
38
|
+
# Code.
|
39
|
+
def handle_incoming_numeric(opts = {})
|
40
|
+
case opts[:code]
|
41
|
+
when Marvin::IRC::Replies[:RPL_WELCOME]
|
42
|
+
handle_welcome
|
43
|
+
when Marvin::IRC::Replies[:ERR_NICKNAMEINUSE]
|
44
|
+
handle_nick_taken
|
45
|
+
when Marvin::IRC::Replies[:RPL_TOPIC]
|
46
|
+
handle_channel_topic
|
47
|
+
end
|
48
|
+
code = opts[:code].to_i
|
49
|
+
args = Marvin::Util.arguments(opts[:data])
|
50
|
+
dispatch :incoming_numeric_processed, :code => code, :data => args
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_welcome
|
54
|
+
logger.info "Welcome received from server"
|
55
|
+
# If a password is specified, we will attempt to message
|
56
|
+
# NickServ to identify ourselves.
|
57
|
+
say ":IDENTIFY #{self.configuration.password}", "NickServ" if configuration.password.present?
|
58
|
+
# Join the default channels IF they're already set
|
59
|
+
# Note that Marvin::IRC::Client.connect will set them AFTER this stuff is run.
|
60
|
+
join default_channels
|
61
|
+
end
|
62
|
+
|
63
|
+
# The default handler for when a users nickname is taken on
|
64
|
+
# on the server. It will attempt to get the nicknickname from
|
65
|
+
# the nicknames part of the configuration (if available) and
|
66
|
+
# will then call #nick to change the nickname.
|
67
|
+
def handle_nick_taken
|
68
|
+
logger.info "Nickname '#{nickname}' on #{server} taken, trying next."
|
69
|
+
logger.info "Available Nicknames: #{nicks.empty? ? "None" : nicks.join(", ")}"
|
70
|
+
if !nicks.empty?
|
71
|
+
logger.info "Getting next nickname to switch"
|
72
|
+
next_nick = nicks.shift # Get the next nickname
|
73
|
+
logger.info "Attemping to set nickname to '#{next_nick}'"
|
74
|
+
nick next_nick
|
75
|
+
else
|
76
|
+
logger.fatal "No Nicknames available - QUITTING"
|
77
|
+
quit
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Only record joins when you've successfully joined the channel.
|
82
|
+
def handle_incoming_join(opts = {})
|
83
|
+
if opts[:nick] == @nickname
|
84
|
+
channels << opts[:target]
|
85
|
+
logger.info "Successfully joined channel #{opts[:target]}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Make sure we show user server errors
|
90
|
+
def handle_incoming_error(opts = {})
|
91
|
+
if opts[:message].present?
|
92
|
+
logger.info "Got ERROR Message: #{opts[:message]}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -1,80 +1,91 @@
|
|
1
1
|
require 'set'
|
2
2
|
|
3
3
|
module Marvin
|
4
|
-
|
5
|
-
# A Simple Marvin handler based on processing
|
6
|
-
# commands, similar in design to MatzBot.
|
7
4
|
class CommandHandler < Base
|
8
5
|
|
9
|
-
class_inheritable_accessor :
|
10
|
-
|
6
|
+
class_inheritable_accessor :command_prefix
|
7
|
+
self.command_prefix = ""
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
self.exposed_method_names = Set.new
|
9
|
+
@@exposed_method_mapping = Hash.new { |h,k| h[k] = [] }
|
10
|
+
@@method_descriptions = Hash.new { |h,k| h[k] = {} }
|
11
|
+
@@registered_classes = Set.new
|
16
12
|
|
17
13
|
class << self
|
18
14
|
|
15
|
+
def command(name, method_desc = nil, &blk)
|
16
|
+
exposes name
|
17
|
+
desc method_desc unless method_desc.blank?
|
18
|
+
define_method(name, &blk)
|
19
|
+
end
|
20
|
+
|
21
|
+
def prefix_is(p)
|
22
|
+
self.command_prefix = p
|
23
|
+
end
|
24
|
+
|
19
25
|
def exposes(*args)
|
20
|
-
|
21
|
-
|
22
|
-
|
26
|
+
args.each { |name| @@exposed_method_mapping[self] << name.to_sym }
|
27
|
+
end
|
28
|
+
|
29
|
+
def exposed_methods
|
30
|
+
methods = []
|
31
|
+
klass = self
|
32
|
+
while klass != Object
|
33
|
+
methods += @@exposed_method_mapping[klass]
|
34
|
+
klass = klass.superclass
|
35
|
+
end
|
36
|
+
return methods.uniq.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def prefix_regexp
|
40
|
+
/^#{command_prefix}/
|
41
|
+
end
|
42
|
+
|
43
|
+
def desc(description)
|
44
|
+
@last_description = description
|
45
|
+
end
|
46
|
+
|
47
|
+
def exposed_name(method)
|
48
|
+
"#{command_prefix}#{method}"
|
23
49
|
end
|
24
50
|
|
25
51
|
end
|
26
52
|
|
27
|
-
on_event :incoming_message
|
28
|
-
logger.debug "Incoming message"
|
29
|
-
check_for_commands
|
30
|
-
end
|
53
|
+
on_event :incoming_message, :check_for_commands
|
31
54
|
|
32
55
|
def check_for_commands
|
56
|
+
message = options.message.to_s.strip
|
33
57
|
data, command = nil, nil
|
34
|
-
if
|
35
|
-
|
36
|
-
|
37
|
-
prefix = split_message.shift
|
38
|
-
# Return if in channel and it isn't address to the user.
|
39
|
-
return unless prefix == "#{self.client.nickname}:"
|
40
|
-
command, data = split_message # Set remaining.
|
58
|
+
if from_channel?
|
59
|
+
name, command, data = message.split(/\s+/, 2)
|
60
|
+
return if name !~ /^#{client.nickname}:/i
|
41
61
|
else
|
42
|
-
command, data =
|
62
|
+
command, data = message.splt(/\s+/, 2)
|
43
63
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
logger.debug "Command Exists - processing"
|
49
|
-
# Dispatch the command.
|
50
|
-
self.send(command_name, data.to_s.split(" ")) if self.respond_to?(command_name)
|
64
|
+
data ||= ""
|
65
|
+
if (command_name = extract_command_name(command)).present?
|
66
|
+
logger.info "Processing command '#{command_name}' for #{from}"
|
67
|
+
send(command_name, data.to_s) if respond_to?(command_name)
|
51
68
|
end
|
52
69
|
end
|
53
70
|
|
54
71
|
def extract_command_name(command)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
method_name
|
59
|
-
return method_name if self.exposed_methods.to_a.include?(method_name)
|
72
|
+
re = self.class.prefix_regexp
|
73
|
+
if command =~ re
|
74
|
+
method_name = command.gsub(re, "").underscore.to_sym
|
75
|
+
return method_name if self.class.exposed_methods.include?(method_name)
|
60
76
|
end
|
61
77
|
end
|
62
78
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
descriptions[name.to_sym] = self.last_description
|
72
|
-
self.last_description = nil
|
73
|
-
end
|
79
|
+
def exposed_name(name)
|
80
|
+
self.class.exposed_name(name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.method_added(name)
|
84
|
+
if @last_description.present?
|
85
|
+
@@method_descriptions[self][name.to_sym] = @last_description
|
86
|
+
@last_description = nil
|
74
87
|
end
|
75
|
-
|
76
88
|
end
|
77
89
|
|
78
90
|
end
|
79
|
-
|
80
91
|
end
|