net-yail 1.4.6 → 1.5.1

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.
@@ -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