Sutto-marvin 0.4.0 → 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 +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
|