net-yail 1.2.0 → 1.2.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.
data/CHANGELOG CHANGED
@@ -34,3 +34,14 @@
34
34
  * Fixed logger's handler to not call the channel logging code on private message
35
35
  * Figured it's about time for a new gem, 1.2.0. Change to join handler breaks
36
36
  existing code in a minor way - don't forget to fix your handlers!
37
+
38
+ 2008-07-22:
39
+ * Added a bunch of new output APIs and corresponding outgoing handlers
40
+ * Auto-support for server password with new arg to constructor
41
+ * Moved TODO stuff into a separate file outside the gem's evil influence!
42
+
43
+ 2008-07-??:
44
+ * Fixed up a bunch of code to add a bit more stability for dead sockets and
45
+ such.
46
+ * IRCBot can now properly reconnect when the socket does end up dying.
47
+ * Bumped version to 1.2.1. Probably a good time to release another gem.
data/README CHANGED
@@ -56,8 +56,7 @@ Features of YAIL:
56
56
  for the most basic working examples.
57
57
 
58
58
  I still have a lot to do, though. The output API is definitely not fully
59
- fleshed out - there are many commands I haven't yet built a shortcut for, such
60
- as KICK or TOPIC. I believe that the library is also missing a lot for people
59
+ fleshed out. I believe that the library is also missing a lot for people
61
60
  who just have a different approach than me, since this was purely designed for
62
61
  my own benefit, and then released almost exclusively to piss off the people
63
62
  whose work I stole to get where I'm at today. (Just kiddin', Pope)
data/lib/net/yail.rb CHANGED
@@ -9,21 +9,6 @@ require 'net/yail/magic_events'
9
9
  require 'net/yail/default_events'
10
10
  require 'net/yail/output_api'
11
11
 
12
- # TODO:
13
- # * Extract all pattern matching into an external file - store both the
14
- # pattern and the event handle's symbol.
15
- # * Build a system to allow numeric events to get sent post-processed data
16
- # if it makes sense (converting the text to specific parts instead of all
17
- # handlers having to regex it themselves, for instance)
18
- # * To deal with the required post-handler logic in the output API, and
19
- # "nicely" deal with filtering output, it may be better to have a separate
20
- # hash of YAIL-required post-handlers. This would allow all logic to be
21
- # done as handlers (makes things more consistent) as well as getting rid of
22
- # the funky arg duping on a per-API-method basis (putting the auto-dupe back
23
- # in the handle method)
24
- # * Handle this:
25
- # (14:45.25) +++INCOMING: :Nerdmaster!xxx@yyyy INVITE botname :#channel_name
26
-
27
12
  # If a thread crashes, I want the app to die. My threads are persistent, not
28
13
  # temporary.
29
14
  Thread.abort_on_exception = true
@@ -82,10 +67,14 @@ module Net
82
67
  #
83
68
  # Generally speaking, you won't need these very often, but they're here for
84
69
  # the edge cases all the same. Note that the socket output cannot be skipped
85
- # (see Return value from events below), so this is truly just to allow
70
+ # (see "Return value from events" below), so this is truly just to allow
86
71
  # modifying things before they go out (filtering speech, converting or
87
72
  # stripping markup, etc) or just general stats-type logic.
88
73
  #
74
+ # Note that in all cases below, the client is *about* to perform an action.
75
+ # Text can be filtered, things can be logged, but keep in mind that the action
76
+ # has not yet happened.
77
+ #
89
78
  # Events:
90
79
  # * :outgoing_begin_connection(username, address, realname) - called when the
91
80
  # start_listening method has set up all threading and such. Default behavior
@@ -111,6 +100,21 @@ module Net
111
100
  # * :outgoing_nick(new_nick) - The client is about to change nickname
112
101
  # * :outgoing_user(username, myaddress, address, realname) - We're about to
113
102
  # send a USER command.
103
+ # * :outgoing_pass(password) - The client is about to send a password to the
104
+ # server via PASS.
105
+ # * :outgoing_oper(user, password) - The client is about to request ops from
106
+ # the server via OPER.
107
+ # * :outgoing_topic(channel, new_topic) - If new_topic is blank (nil or ''),
108
+ # the client is requesting the channel's topic, otherwise setting it.
109
+ # * :outgoing_names(channel) - Client is requesting a list of names in the
110
+ # given channel, or all channels and names if channel is blank.
111
+ # * :outgoing_list(channel, server) - Client is querying channel information.
112
+ # I honestly don't know what server is for from RFC, but asking for a
113
+ # specific channel gives just data on that channel.
114
+ # * :outgoing_invite(nick, channel) - Client is sending an INVITE message to
115
+ # nick for channel.
116
+ # * :outgoing_kick(nick, channel, comment) - Client is about to kick the user
117
+ # from the channel with an optional comment.
114
118
  #
115
119
  # Note that a single output call can hit multiple handlers, so you must plan
116
120
  # carefully. A call to act() will hit the act handler, then ctcp (since act
@@ -221,7 +225,7 @@ class YAIL
221
225
  :me, # Nickname on the IRC server
222
226
  :registered, # If true, we've been welcomed
223
227
  :nicknames, # Array of nicknames to try when logging on to server
224
- :dead_socket, # True if we have no data after a read operation
228
+ :dead_socket, # True if @socket.eof? or read/connect fail
225
229
  :socket # TCPSocket instance
226
230
  )
227
231
  attr_accessor(
@@ -248,6 +252,8 @@ class YAIL
248
252
  # * <tt>:throttle_seconds</tt>: Seconds between a cycle of privmsg sends.
249
253
  # Defaults to 1. One "cycle" is defined as sending one line of output to
250
254
  # *all* targets that have output buffered.
255
+ # * <tt>:server_password</tt>: Very optional. If set, this is the password
256
+ # sent out to the server before USER and NICK messages.
251
257
  def initialize(options = {})
252
258
  @me = ''
253
259
  @nicknames = options[:nicknames]
@@ -259,6 +265,7 @@ class YAIL
259
265
  @silent = options[:silent] || false
260
266
  @loud = options[:loud] || false
261
267
  @throttle_seconds = options[:throttle_seconds] || 1
268
+ @password = options[:server_password]
262
269
 
263
270
  # Read in map of event numbers and names. Yes, I stole this event map
264
271
  # file from RubyIRC and made very minor changes.... They stole it from
@@ -266,12 +273,17 @@ class YAIL
266
273
  eventmap = "#{File.dirname(__FILE__)}/yail/eventmap.yml"
267
274
  @event_number_lookup = File.open(eventmap) { |file| YAML::load(file) }.invert
268
275
 
269
- # Build our socket
270
- @socket = TCPSocket.new(@address, @port)
271
-
272
276
  # We're not dead... yet...
273
277
  @dead_socket = false
274
278
 
279
+ # Build our socket - if something goes wrong, it's immediately a dead socket.
280
+ begin
281
+ @socket = TCPSocket.new(@address, @port)
282
+ rescue StandardError => boom
283
+ report "+++ERROR: Unable to open socket connection in Net::YAIL.initialize: #{boom.inspect}"
284
+ @dead_socket = true
285
+ end
286
+
275
287
  # Shared resources for threads to try and coordinate.... I know very
276
288
  # little about thread safety, so this stuff may be a terrible disaster.
277
289
  # Please send me better approaches if you are less stupid than I.
@@ -294,6 +306,9 @@ class YAIL
294
306
  # We don't want to spawn an extra listener
295
307
  return if Thread === @ioloop_thread
296
308
 
309
+ # Don't listen if socket is dead
310
+ return if @dead_socket
311
+
297
312
  # Build forced / magic logic - welcome setting @me, ping response, etc.
298
313
  # Since we do these here, nobody can skip them and they're always first.
299
314
  setup_magic_handlers
@@ -312,10 +327,10 @@ class YAIL
312
327
  # regarding threads. Please help me if you know how to make this system
313
328
  # better. DEAR LORD HELP ME IF YOU CAN!
314
329
  def stop_listening
315
- @ioloop_thread.terminate
316
- @input_processor.terminate
317
- @privmsg_processor.terminate
330
+ # Kill all threads if they're really threads
331
+ [@ioloop_thread, @input_processor, @privmsg_processor].each {|thread| thread.terminate if Thread === thread}
318
332
 
333
+ # Just for safety, set everything to nil
319
334
  @ioloop_thread = nil
320
335
  @input_processor = nil
321
336
  @privmsg_processor = nil
@@ -326,10 +341,14 @@ class YAIL
326
341
  # Reads incoming data - should only be called by io_loop, and only when
327
342
  # we've already ensured that data is, in fact, available.
328
343
  def read_incoming_data
329
- line = @socket.gets
344
+ begin
345
+ line = @socket.gets
346
+ rescue StandardError => boom
347
+ @dead_socket = true
348
+ report "+++ERROR in read_incoming_data -> @socket.gets: #{boom.inspect}"
349
+ end
330
350
 
331
- # If we have no data, the socket closed. Nothing to do but set a
332
- # status and return
351
+ # If we somehow got no data here, the socket is closed. Run away!!!
333
352
  if !line
334
353
  @dead_socket = true
335
354
  return
@@ -352,6 +371,10 @@ class YAIL
352
371
  while true
353
372
  # if no data is coming in, don't block the socket!
354
373
  read_incoming_data if Kernel.select([@socket], nil, nil, 0)
374
+
375
+ # Check for dead socket
376
+ @dead_socket = true if @socket.eof?
377
+
355
378
  sleep 0.05
356
379
  end
357
380
  end
@@ -80,6 +80,12 @@ class IRCBot
80
80
 
81
81
  # Enters the socket's listening loop(s)
82
82
  def start_listening
83
+ # If socket's already dead (probably couldn't connect to server), don't
84
+ # try to listen!
85
+ if @irc.dead_socket
86
+ $stderr.puts "Dead socket, can't start listening!"
87
+ end
88
+
83
89
  @irc.start_listening
84
90
  end
85
91
 
@@ -90,7 +96,7 @@ class IRCBot
90
96
  # processing.
91
97
  def irc_loop
92
98
  while true
93
- until @irc.dead_socket || @irc.socket.eof?
99
+ until @irc.dead_socket
94
100
  sleep 15
95
101
  @irc.handle(:irc_loop)
96
102
  Thread.pass
@@ -185,12 +185,13 @@ module Defaults
185
185
  report "*MOTD* End of MOTD"
186
186
  end
187
187
 
188
- # We dun connected to a server! Just sends user info and nick. This isn't
189
- # quite "essential" to a working IRC app, but this data *must* be sent at
190
- # some point, so be careful before skipping this handler.
188
+ # We dun connected to a server! Just sends password (if one is set) and
189
+ # user/nick. This isn't quite "essential" to a working IRC app, but this data
190
+ # *must* be sent at some point, so be careful before skipping this handler.
191
191
  def out_begin_connection(username, address, realname)
192
- user username, '0.0.0.0', address, realname
193
- nick @nicknames[0]
192
+ pass(@password) if @password
193
+ user(username, '0.0.0.0', address, realname)
194
+ nick(@nicknames[0])
194
195
  end
195
196
 
196
197
  # Incoming invitation
@@ -18,6 +18,13 @@ module Net
18
18
  # a library should own - protecting the programmer for having to remember that
19
19
  # sort of crap, especially if the app is calling msg, act, ctcp, etc. in
20
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.
21
28
  module IRCOutputAPI
22
29
  # Spits a raw string out to the server - in case a subclass wants to do
23
30
  # something special on *all* output, please make all output go through this
@@ -31,7 +38,7 @@ module IRCOutputAPI
31
38
  # Calls :outgoing_privmsg handler, then sends a message (text) out to the
32
39
  # given channel/user (target), and reports itself with the given string.
33
40
  # This method shouldn't be called directly most of the time - just use msg,
34
- # act, or ctcp.
41
+ # act, ctcp, etc.
35
42
  #
36
43
  # This is sort of the central message output - everything that's based on
37
44
  # PRIVMSG (messages, actions, other ctcp) uses this. Because these messages
@@ -183,6 +190,8 @@ module IRCOutputAPI
183
190
  raw "NICK :#{new_nick}"
184
191
  end
185
192
 
193
+ # Identifies ourselves to the server. Calls :outgoing_user and sends raw
194
+ # USER command.
186
195
  def user(username, myaddress, address, realname)
187
196
  # Dup strings so handler can filter safely
188
197
  username = username.dup
@@ -195,6 +204,87 @@ module IRCOutputAPI
195
204
  raw "USER #{username} #{myaddress} #{address} :#{realname}"
196
205
  end
197
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
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
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
256
+ server = server.dup
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
270
+
271
+ handle(:outgoing_invite, nick, channel)
272
+ raw "INVITE #{nick} #{channel}"
273
+ end
274
+
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
281
+
282
+ handle(:outgoing_kick, nick, channel, comment)
283
+ output = "KICK #{channel} #{nick}"
284
+ output += " :#{comment}" unless comment.to_s.empty?
285
+ raw output
286
+ end
287
+
198
288
  end
199
289
 
200
290
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-yail
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Echols
@@ -9,7 +9,7 @@ autorequire: net/yail
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-07-17 00:00:00 -07:00
12
+ date: 2008-08-01 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15