net-yail 1.4.6 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,214 @@
1
+ module Net
2
+ module IRCEvents
3
+
4
+ # All code here is going to be removed completely at some point, and only exists here to serve the 1.x branch
5
+ # (and remind me how awful the old system really was)
6
+ module LegacyEvents
7
+
8
+ # DEPRECATED
9
+ #
10
+ # Event handler hook. Kinda hacky. Calls your event(s) before the default
11
+ # event. Default stuff will happen if your handler doesn't return true.
12
+ def prepend_handler(event, *procs, &block)
13
+ raise "Cannot change handlers while threads are listening!" if @ioloop_thread
14
+
15
+ @log.warn "[DEPRECATED] - Net::YAIL#prepend_handler is deprecated as of 1.5.0 - please see documentation on the new " +
16
+ "event handling model methods - http://ruby-irc-yail.nerdbucket.com/"
17
+
18
+ # Allow blocks as well as procs
19
+ if block_given?
20
+ procs.push(block)
21
+ end
22
+
23
+ # See if this is a word for a numeric - only applies to incoming events
24
+ if (event.to_s =~ /^incoming_(.*)$/)
25
+ number = @event_number_lookup[$1].to_i
26
+ event = :"incoming_numeric_#{number}" if number > 0
27
+ end
28
+
29
+ @legacy_handlers[event] ||= Array.new
30
+ until procs.empty?
31
+ @legacy_handlers[event].unshift(procs.pop)
32
+ end
33
+ end
34
+
35
+ # Handles the given event (if it's in the @legacy_handlers array) with the
36
+ # arguments specified.
37
+ #
38
+ # The @legacy_handlers must be a hash where key = event to handle and value is
39
+ # a Proc object (via Class.method(:name) or just proc {...}).
40
+ # This should be fine if you're setting up handlers with the prepend_handler
41
+ # method, but if you get "clever," you're on your own.
42
+ def handle(event, *arguments)
43
+ # Don't bother with anything if there are no handlers registered.
44
+ return false unless Array === @legacy_handlers[event]
45
+
46
+ @log.debug "+++EVENT HANDLER: Handling event #{event} via #{@legacy_handlers[event].inspect}:"
47
+
48
+ # Call all hooks in order until one breaks the chain. For incoming
49
+ # events, we want something to break the chain or else it'll likely
50
+ # hit a reporter. For outgoing events, we tend to report them anyway,
51
+ # so no need to worry about ending the chain except when the bot wants
52
+ # to take full control over them.
53
+ result = false
54
+ for handler in @legacy_handlers[event]
55
+ result = handler.call(*arguments)
56
+ break if result == true
57
+ end
58
+
59
+ # Let the new system deal with legacy handlers that wanted to end the chain
60
+ return result
61
+ end
62
+
63
+ # Since numerics are so many and so varied, this method will auto-fallback
64
+ # to a simple report if no handler was defined.
65
+ def handle_numeric(number, fullactor, actor, target, message)
66
+ # All numerics share the same args, and rarely care about anything but
67
+ # message, so let's make it easier by passing a hash instead of a list
68
+ args = {:fullactor => fullactor, :actor => actor, :target => target}
69
+ base_event = :"incoming_numeric_#{number}"
70
+ if Array === @legacy_handlers[base_event]
71
+ return handle(base_event, message, args)
72
+ else
73
+ # No handler = report and don't worry about it
74
+ @log.info "Unknown raw #{number.to_s} from #{fullactor}: #{message}"
75
+ return false
76
+ end
77
+ end
78
+
79
+ # Gets some input, sends stuff off to a handler. Yay.
80
+ def legacy_process_event(event)
81
+ # Allow global handler to break the chain, filter the line, whatever. When we ditch these legacy
82
+ # events, this code will finally die!
83
+ if (Net::YAIL::IncomingEvent === event && Array === @legacy_handlers[:incoming_any])
84
+ for handler in @legacy_handlers[:incoming_any]
85
+ result = handler.call(event.raw)
86
+ return true if true == result
87
+ end
88
+ end
89
+
90
+ # Partial conversion to using events - we still have a horrible case statement, but
91
+ # we're at least using the event object. Slightly less hacky than before.
92
+
93
+ # Except for this - we still have to handle numerics the crappy way until we build the proper
94
+ # dispatching of events
95
+ event = event.parent if event.parent && :incoming_numeric == event.parent.type
96
+
97
+ case event.type
98
+ # Ping is important to handle quickly, so it comes first.
99
+ when :incoming_ping
100
+ return handle(event.type, event.message)
101
+
102
+ when :incoming_numeric
103
+ # Lovely - I passed in a "nick" - which, according to spec, is NEVER part of a numeric reply
104
+ handle_numeric(event.numeric, event.servername, nil, event.target, event.message)
105
+
106
+ when :incoming_invite
107
+ return handle(event.type, event.fullname, event.nick, event.channel)
108
+
109
+ # Fortunately, the legacy handler for all five "message" types is the same!
110
+ when :incoming_msg, :incoming_ctcp, :incoming_act, :incoming_notice, :incoming_ctcpreply
111
+ # Legacy handling requires merger of target and channel....
112
+ target = event.target if event.pm?
113
+ target = event.channel if !target
114
+
115
+ # Notices come from server sometimes, so... another merger for legacy fun!
116
+ nick = event.server? ? '' : event.nick
117
+ return handle(event.type, event.from, nick, target, event.message)
118
+
119
+ # This is a bit painful for right now - just use some hacks to make it work semi-nicely,
120
+ # but let's not put hacks into the core Event object. Modes need reworking soon anyway.
121
+ #
122
+ # NOTE: message is currently the mode settings ('+b', for instance) - very bad. TODO: FIX FIX FIX!
123
+ when :incoming_mode
124
+ # Modes can come from the server, so legacy system again regularly sent nil data....
125
+ nick = event.server? ? '' : event.nick
126
+ return handle(event.type, event.from, nick, event.channel, event.message, event.targets.join(' '))
127
+
128
+ when :incoming_topic_change
129
+ return handle(event.type, event.fullname, event.nick, event.channel, event.message)
130
+
131
+ when :incoming_join
132
+ return handle(event.type, event.fullname, event.nick, event.channel)
133
+
134
+ when :incoming_part
135
+ return handle(event.type, event.fullname, event.nick, event.channel, event.message)
136
+
137
+ when :incoming_kick
138
+ return handle(event.type, event.fullname, event.nick, event.channel, event.target, event.message)
139
+
140
+ when :incoming_quit
141
+ return handle(event.type, event.fullname, event.nick, event.message)
142
+
143
+ when :incoming_nick
144
+ return handle(event.type, event.fullname, event.nick, event.message)
145
+
146
+ when :incoming_error
147
+ return handle(event.type, event.message)
148
+
149
+ when :outgoing_privmsg, :outgoing_msg, :outgoing_ctcp, :outgoing_act, :outgoing_notice, :outgoing_ctcpreply
150
+ return handle(event.type, event.target, event.message)
151
+
152
+ when :outgoing_mode
153
+ return handle(event.type, event.target, event.modes, event.objects)
154
+
155
+ when :outgoing_join
156
+ return handle(event.type, event.channel, event.password)
157
+
158
+ when :outgoing_part
159
+ return handle(event.type, event.channel, event.message)
160
+
161
+ when :outgoing_quit
162
+ return handle(event.type, event.message)
163
+
164
+ when :outgoing_nick
165
+ return handle(event.type, event.nick)
166
+
167
+ when :outgoing_user
168
+ return handle(event.type, event.username, event.hostname, event.servername, event.realname)
169
+
170
+ when :outgoing_pass
171
+ return handle(event.type, event.password)
172
+
173
+ when :outgoing_oper
174
+ return handle(event.type, event.user, event.password)
175
+
176
+ when :outgoing_topic
177
+ return handle(event.type, event.channel, event.topic)
178
+
179
+ when :outgoing_names
180
+ return handle(event.type, event.channel)
181
+
182
+ when :outgoing_list
183
+ return handle(event.type, event.channel, event.server)
184
+
185
+ when :outgoing_invite
186
+ return handle(event.type, event.nick, event.channel)
187
+
188
+ when :outgoing_kick
189
+ return handle(event.type, event.nick, event.channel, event.reason)
190
+
191
+ when :outgoing_begin_connection
192
+ return handle(event.type, event.username, event.address, event.realname)
193
+
194
+ # Unknown line - if an incoming event, we need to log it as that shouldn't be able to happen,
195
+ # but we don't want to kill somebody's app for it. An outgoing event that's part of the
196
+ # system should NEVER hit this, so we throw an error in that case. Custom events just get
197
+ # handled with no arguments, to allow for things like :irc_loop.
198
+ else
199
+ case event
200
+ when Net::YAIL::IncomingEvent
201
+ @log.warn 'Unknown line: %s!' % event.raw.inspect
202
+ @log.warn "Please report this to the github repo at https://github.com/Nerdmaster/ruby-irc-yail/issues"
203
+ return handle(:incoming_miscellany, event.raw)
204
+ when Net::YAIL::OutgoingEvent
205
+ raise "Unknown outgoing event: #{event.inspect}"
206
+ else
207
+ handle(event.type)
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ end
214
+ end
@@ -1,52 +1,56 @@
1
1
  module Net
2
2
  module IRCEvents
3
3
 
4
- # This module contains all the "magic" methods that need to happen regardless
5
- # of user-defined event hooks and such.
4
+ # This module contains all the "magic" methods that need to happen by default. User could overwrite
5
+ # some of these, but really really shouldn't.
6
6
  module Magic
7
7
  private
8
8
 
9
- # Sets up the magic handlers that must happen no matter what else occurs
10
- def setup_magic_handlers
11
- prepend_handler :incoming_welcome, self.method(:magic_welcome)
12
- prepend_handler :incoming_ping, self.method(:magic_ping)
13
- prepend_handler :incoming_nick, self.method(:magic_nick)
9
+ # We dun connected to a server! Just sends password (if one is set) and
10
+ # user/nick. This isn't quite "essential" to a working IRC app, but this data
11
+ # *must* be sent at some point, so be careful before clobbering this handler.
12
+ def out_begin_connection(event)
13
+ pass(@password) if @password
14
+ user(event.username, '0.0.0.0', event.address, event.realname)
15
+ nick(@nicknames[0])
14
16
  end
15
17
 
16
18
  # We were welcomed, so we need to set up initial nickname and set that we
17
19
  # registered so nick change failure doesn't cause DEATH!
18
- def magic_welcome(text, args)
19
- report "#{args[:fullactor]} welcome message: #{text}"
20
- if (text =~ /(\S+)!\S+$/)
20
+ def magic_welcome(event)
21
+ # TODO: Ditch this call to report - move to report lib if necessary
22
+ report "#{event.from} welcome message: #{event.message}"
23
+ if (event.message =~ /(\S+)!\S+$/)
21
24
  @me = $1
22
- elsif (text =~ /(\S+)$/)
25
+ elsif (event.message =~ /(\S+)$/)
23
26
  @me = $1
24
27
  end
25
28
 
26
29
  @registered = true
27
30
  mode @me, 'i'
28
-
29
- # Don't break the chain if user wants their own handler
30
- return false
31
31
  end
32
32
 
33
- # Ping must have a PONG even if user wants their own handler
34
- def magic_ping(text)
35
- @socket.puts "PONG :#{text}"
33
+ # Ping must have a PONG, though crazy user can handle this her own way if she likes
34
+ def magic_ping(event); @socket.puts "PONG :#{event.message}"; end
36
35
 
37
- # Don't break the chain, man
38
- return false
36
+ # If bot changes his name, @me must change - this must be a filter, not the callback!
37
+ def magic_nick(event)
38
+ @me = event.message.dup if event.nick.downcase == @me.downcase
39
39
  end
40
40
 
41
- # If bot changes his name, @me must change
42
- def magic_nick(fullactor, actor, nickname)
43
- # Reset name if it's me
44
- if actor.downcase == @me.downcase
45
- @me = nickname.dup
46
- end
41
+ # User calls msg, sends a simple message out to the event's target (user or channel)
42
+ def magic_out_msg(event)
43
+ privmsg(event.target, event.message)
44
+ end
45
+
46
+ # CTCP
47
+ def magic_out_ctcp(event)
48
+ privmsg(event.target, "\001#{event.message}\001")
49
+ end
47
50
 
48
- # Allow user-defined events (and/or reporting)
49
- return false
51
+ # CTCP ACTION
52
+ def magic_out_act(event)
53
+ privmsg(event.target, "\001ACTION #{event.message}\001")
50
54
  end
51
55
 
52
56
  end
@@ -1,290 +1,110 @@
1
1
  module Net
2
2
 
3
- # All output APIs live here. In most cases, an outgoing handler will get a
4
- # call, but will not be able to stop the socket output since that's sorta
5
- # an essential part of this whole library.
6
- #
7
- # ==Argument Duping
8
- #
9
- # Output APIs dup incoming args before sending them off to handlers. This
10
- # is a mechanism that I think could be done better, but I can't figure a good
11
- # way to do it at the moment. The reason this is necessary is for a specific
12
- # situation where a bot has an array of response messages, and needs to filter
13
- # those messages. A call to "msg(messages[rand(10)])" with a handler on :outgoing_msg
14
- # that does something like <code>text.gsub!('a', '@')</code> (like a leetspeek
15
- # filter) shouldn't destroy the original data in the messages array.
16
- #
17
- # This could be left up to the programmer, but it seems like something that
18
- # a library should own - protecting the programmer for having to remember that
19
- # sort of crap, especially if the app is calling msg, act, ctcp, etc. in
20
- # various ways from multiple points in the code....
21
- #
22
- # ==Apologies, good sirs
23
- #
24
- # If a method exists in this module, and it isn't the +raw+ method, chances
25
- # are it's got a handler in the form of :outgoing_<method name>. I am hoping
26
- # I document all of those in the main Net::YAIL code, but if I miss one, I
27
- # apologize.
3
+ # This module is responsible for the raw socket output, buffering of all "message" types of
4
+ # events, and exposing the magic to create a new output command + handler. All output methods
5
+ # are documented in the main Net::YAIL documentation.
28
6
  module IRCOutputAPI
29
7
  # Spits a raw string out to the server - in case a subclass wants to do
30
8
  # something special on *all* output, please make all output go through this
31
9
  # method. Don't use puts manually. I will kill violaters. Legally
32
10
  # speaking, that is.
33
- def raw(line, report = true)
11
+ def raw(line)
34
12
  @socket.puts "#{line}\r\n"
35
- report "bot: #{line.inspect}" if report
36
13
  end
37
14
 
38
- # Calls :outgoing_privmsg handler, then sends a message (text) out to the
39
- # given channel/user (target), and reports itself with the given string.
40
- # This method shouldn't be called directly most of the time - just use msg,
41
- # act, ctcp, etc.
42
- #
43
- # This is sort of the central message output - everything that's based on
44
- # PRIVMSG (messages, actions, other ctcp) uses this. Because these messages
45
- # aren't insanely important, we actually buffer them instead of sending
46
- # straight out to the channel. The output thread has to deal with
47
- # sending these out.
48
- def privmsg(target, text, report_string)
49
- # Dup strings so handler can filter safely
50
- target = target.dup
51
- text = text.dup
52
-
53
- handle(:outgoing_privmsg, target, text)
54
-
15
+ # Buffers the given event to be sent out when we are able to send something out to the given
16
+ # target. If buffering isn't turned on, the event will be processed in the next loop of outgoing
17
+ # messages.
18
+ def buffer_output(event)
55
19
  @privmsg_buffer_mutex.synchronize do
56
- @privmsg_buffer[target] ||= Array.new
57
- @privmsg_buffer[target].push([text, report_string])
20
+ @privmsg_buffer[event.target] ||= Array.new
21
+ @privmsg_buffer[event.target].push event
58
22
  end
59
23
  end
60
24
 
61
- # Calls :outgoing_msg handler, then privmsg to send the message out. Could
62
- # be used to send any privmsg, but you're betting off using act and ctcp
63
- # shortcut methods for those types. Target is a channel or username, text
25
+ # Buffers an :outgoing_msg event. Could be used to send any privmsg, but you're betting off
26
+ # using act and ctcp shortcut methods for those types. Target is a channel or username, message
64
27
  # is the message.
65
- def msg(target, text)
66
- # Dup strings so handler can filter safely
67
- target = target.dup
68
- text = text.dup
69
-
70
- handle(:outgoing_msg, target, text)
71
-
72
- report_string = @log_silent ? '' : "{#{target}} <#{@me}> #{text}"
73
- privmsg(target, text, report_string)
74
- end
75
-
76
- # Calls :outgoing_ctcp handler, then sends CTCP to target channel or user
77
- def ctcp(target, text)
78
- # Dup strings so handler can filter safely
79
- target = target.dup
80
- text = text.dup
81
-
82
- handle(:outgoing_ctcp, target, text)
83
-
84
- report_string = @log_silent ? '' : "{#{target}} [#{@me} #{text}]"
85
- privmsg(target, "\001#{text}\001", report_string)
28
+ def msg(target, message)
29
+ buffer_output Net::YAIL::OutgoingEvent.new(:type => :msg, :target => target, :message => message)
86
30
  end
87
31
 
88
- # Calls :outgoing_act handler, then ctcp to send a CTCP ACTION (text) to
89
- # a given user or channel (target)
90
- def act(target, text)
91
- # Dup strings so handler can filter safely
92
- target = target.dup
93
- text = text.dup
94
-
95
- handle(:outgoing_act, target, text)
96
-
97
- ctcp(target, "ACTION #{text}")
32
+ # Buffers an :outgoing_ctcp event. Target is user or channel, message is message.
33
+ def ctcp(target, message)
34
+ buffer_output Net::YAIL::OutgoingEvent.new(:type => :ctcp, :target => target, :message => message)
98
35
  end
99
36
 
100
- # Calls :outgoing_notice handler, then outputs raw NOTICE message
101
- def notice(target, text)
102
- # Dup strings so handler can filter safely
103
- target = target.dup
104
- text = text.dup
105
-
106
- handle(:outgoing_notice, target, text)
107
-
108
- report "{#{target}} -#{@me}- #{text}" unless @log_silent
109
- raw("NOTICE #{target} :#{text}", false)
37
+ # Buffers an :outgoing_act event. Target is user or channel, message is message.
38
+ def act(target, message)
39
+ buffer_output Net::YAIL::OutgoingEvent.new(:type => :act, :target => target, :message => message)
110
40
  end
111
41
 
112
- # Calls :outgoing_ctcpreply handler, then uses notice method to send the
113
- # CTCP text
114
- def ctcpreply(target, text)
115
- # Dup strings so handler can filter safely
116
- target = target.dup
117
- text = text.dup
118
-
119
- handle(:outgoing_ctcpreply, target, text)
120
-
121
- report "{#{target}} [Reply: #{@me} #{text}]" unless @log_silent
122
- notice(target, "\001#{text}\001")
123
- end
124
-
125
- # Calls :outgoing_mode handler, then mode to set mode(s) on a channel
126
- # and possibly specific users (objects). If modes and objects are blank,
127
- # just sends a raw MODE query.
128
- def mode(target, modes = '', objects = '')
129
- # Dup strings so handler can filter safely
130
- target = target.dup
131
- modes = modes.dup
132
- objects = objects.dup
133
-
134
- handle(:outgoing_mode, target, modes, objects)
135
-
136
- message = "MODE #{target}"
137
- message += " #{modes}" unless modes.to_s.empty?
138
- message += " #{objects}" unless objects.to_s.empty?
139
- raw message
140
- end
141
-
142
- # Calls :outgoing_join handler and then raw JOIN message for a given channel
143
- def join(target, pass = '')
144
- # Dup strings so handler can filter safely
145
- target = target.dup
146
- pass = pass.dup
147
-
148
- handle(:outgoing_join, target, pass)
149
-
150
- text = "JOIN #{target}"
151
- text += " #{pass}" unless pass.empty?
152
- raw text
153
- end
154
-
155
- # Calls :outgoing_part handler and then raw PART for leaving a given channel
156
- # (with an optional message)
157
- def part(target, text = '')
158
- # Dup strings so handler can filter safely
159
- target = target.dup
160
- text = text.dup
161
-
162
- handle(:outgoing_part, target, text)
163
-
164
- request = "PART #{target}";
165
- request += " :#{text}" unless text.to_s.empty?
166
- raw request
167
- end
168
-
169
- # Calls :outgoing_quit handler and then raw QUIT message with an optional
170
- # reason
171
- def quit(text = '')
172
- # Dup strings so handler can filter safely
173
- text = text.dup
174
-
175
- handle(:outgoing_quit, text)
176
-
177
- request = "QUIT";
178
- request += " :#{text}" unless text.to_s.empty?
179
- raw request
180
- end
181
-
182
- # Calls :outgoing_nick handler and then sends raw NICK message to change
183
- # nickname.
184
- def nick(new_nick)
185
- # Dup strings so handler can filter safely
186
- new_nick = new_nick.dup
187
-
188
- handle(:outgoing_nick, new_nick)
189
-
190
- raw "NICK :#{new_nick}"
191
- end
192
-
193
- # Identifies ourselves to the server. Calls :outgoing_user and sends raw
194
- # USER command.
195
- def user(username, myaddress, address, realname)
196
- # Dup strings so handler can filter safely
197
- username = username.dup
198
- myaddress = myaddress.dup
199
- address = address.dup
200
- realname = realname.dup
201
-
202
- handle(:outgoing_user, username, myaddress, address, realname)
203
-
204
- raw "USER #{username} #{myaddress} #{address} :#{realname}"
205
- end
206
-
207
- # Sends a password to the server. This *must* be sent before NICK/USER.
208
- # Calls :outgoing_pass and sends raw PASS command.
209
- def pass(password)
210
- # Dupage
211
- password = password.dup
212
-
213
- handle(:outgoing_pass, password)
214
- raw "PASS #{password}"
215
- end
216
-
217
- # Sends an op request. Calls :outgoing_oper and raw OPER command.
218
- def oper(user, password)
219
- # Dupage
220
- user = user.dup
221
- password = password.dup
222
-
223
- handle(:outgoing_oper, user, password)
224
- raw "OPER #{user} #{password}"
225
- end
226
-
227
- # Gets or sets the topic. Calls :outgoing_topic and raw TOPIC command
228
- def topic(channel, new_topic = nil)
229
- # Dup for filter safety in outgoing handler
230
- channel = channel.dup
231
- new_topic = new_topic.dup unless new_topic.nil?
232
-
233
- handle(:outgoing_topic, channel, new_topic)
234
- output = "TOPIC #{channel}"
235
- output += " :#{new_topic}" unless new_topic.to_s.empty?
236
- raw output
237
- end
238
-
239
- # Gets a list of users and channels if channel isn't specified. If channel
240
- # is specified, only shows users in that channel. Will not show invisible
241
- # users or channels. Calls :outgoing_names and raw NAMES command.
242
- def names(channel = nil)
243
- channel = channel.dup unless channel.nil?
244
-
245
- handle(:outgoing_names, channel)
246
- output = "NAMES"
247
- output += " #{channel}" unless channel.to_s.empty?
248
- raw output
249
- end
250
-
251
- # I don't know what the server param is for, but it's in the RFC. If
252
- # channel is blank, lists all visible, otherwise just lists the channel in
253
- # question. Calls :outgoing_list and raw LIST command.
254
- def list(channel = nil, server = nil)
255
- channel = channel.dup unless channel.nil?
256
- server = server.dup unless server.nil?
257
-
258
- handle(:outgoing_list, channel, server)
259
- output = "LIST"
260
- output += " #{channel}" if channel
261
- output += " #{server}" if server
262
- raw output
263
- end
264
-
265
- # Invites a user to a channel. Calls :outgoing_invite and raw INVITE
266
- # command.
267
- def invite(nick, channel)
268
- channel = channel.dup
269
- server = server.dup
42
+ # Creates an output command and its handler. output_base is a template of the command without
43
+ # any conditional arguments (for simple commands this is the full template). args is a list of
44
+ # argument symbols to determine how the event is built and handled. If an argument symbol is
45
+ # followed by a string, that string is conditionally appended to the output in the handler if the
46
+ # event has data for that argument.
47
+ #
48
+ # I hate the hackiness here, but it's so much easier to build the commands and handlers with an
49
+ # ugly one-liner than manually, and things like define_method seem to fall short with how much
50
+ # crap this needs to do.
51
+ def create_command(command, output_base, *opts)
52
+ event_opts = lambda {|text| text.gsub(/:(\w+)/, '#{event.\1}') }
53
+
54
+ output_base = event_opts.call(output_base)
55
+
56
+ # Create a list of actual arg symbols and templates for optional args
57
+ args = []
58
+ optional_arg_templates = {}
59
+ last_symbol = nil
60
+ for opt in opts
61
+ case opt
62
+ when Symbol
63
+ args.push opt
64
+ last_symbol = opt
65
+ when String
66
+ raise ArgumentError.new("create_command optional argument must have an argument symbol preceding them") unless last_symbol
67
+ optional_arg_templates[last_symbol] = event_opts.call(opt)
68
+ last_symbol = nil
69
+ end
70
+ end
270
71
 
271
- handle(:outgoing_invite, nick, channel)
272
- raw "INVITE #{nick} #{channel}"
273
- end
72
+ # Format strings for command args and event creation
73
+ event_string = args.collect {|arg| ":#{arg} => #{arg}"}.join(",")
74
+ event_string = ", #{event_string}" unless event_string.empty?
75
+ args_string = args.collect {|arg| "#{arg} = ''"}.join(",")
76
+
77
+ # Create the command function
78
+ command_code = %Q|
79
+ def #{command}(#{args_string})
80
+ dispatch Net::YAIL::OutgoingEvent.new(:type => #{command.inspect}#{event_string})
81
+ end
82
+ |
83
+ self.class.class_eval command_code
84
+
85
+ # Create the handler piece by piece - wow how ugly this is!
86
+ command_handler = :"magic_out_#{command}"
87
+ handler_code = %Q|
88
+ def #{command_handler}(event)
89
+ output_string = "#{output_base}"
90
+ |
91
+ for arg in args
92
+ if optional_arg_templates[arg]
93
+ handler_code += %Q|
94
+ output_string += "#{optional_arg_templates[arg]}" unless event.#{arg}.to_s.empty?
95
+ |
96
+ end
97
+ end
98
+ handler_code += %Q|
99
+ raw output_string
100
+ end
101
+ |
274
102
 
275
- # Kicks the given user from the channel with the optional comment. Calls
276
- # :outgoing_kick and issues a raw KICK command.
277
- def kick(nick, channel, comment = nil)
278
- nick = nick.dup
279
- channel = channel.dup
280
- comment = comment.dup unless comment.nil?
103
+ self.class.class_eval handler_code
281
104
 
282
- handle(:outgoing_kick, nick, channel, comment)
283
- output = "KICK #{channel} #{nick}"
284
- output += " :#{comment}" unless comment.to_s.empty?
285
- raw output
105
+ # At least setting the callback isn't a giant pile of dumb
106
+ set_callback :"outgoing_#{command}", self.method(command_handler)
286
107
  end
287
-
288
108
  end
289
109
 
290
110
  end