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.
@@ -1,113 +1,10 @@
1
1
  module Net
2
2
  module IRCEvents
3
3
 
4
- # This module contains all the default events handling - mainly for
5
- # reporting things or simple logic. Users can put in their own event handlers
6
- # that return true and ignore these, so nothing in here should be truly
7
- # essential to a healthy IRC app.
4
+ # This module contains all the default events handling that hasn't yet been cleaned up for 2.0
8
5
  module Defaults
9
6
  private
10
7
 
11
- # Sets up all the default handlers for events - just reporting things others
12
- # don't handle in all but a few cases
13
- def setup_default_handlers
14
- # Incoming events
15
- prepend_handler :incoming_msg, self.method(:r_msg)
16
- prepend_handler :incoming_act, self.method(:r_act)
17
- prepend_handler :incoming_notice, self.method(:r_notice)
18
- prepend_handler :incoming_ctcp, self.method(:r_ctcp)
19
- prepend_handler :incoming_ctcpreply, self.method(:r_ctcpreply)
20
- prepend_handler :incoming_mode, self.method(:r_mode)
21
- prepend_handler :incoming_join, self.method(:r_join)
22
- prepend_handler :incoming_part, self.method(:r_part)
23
- prepend_handler :incoming_kick, self.method(:r_kick)
24
- prepend_handler :incoming_quit, self.method(:r_quit)
25
- prepend_handler :incoming_nick, self.method(:r_nick)
26
- prepend_handler :incoming_miscellany, self.method(:r_miscellany)
27
-
28
- # Incoming numeric events here
29
- prepend_handler :incoming_welcome, self.method(:r_welcome)
30
- prepend_handler :incoming_bannedfromchan, self.method(:r_bannedfromchan)
31
- prepend_handler :incoming_badchannelkey, self.method(:r_badchannelkey)
32
- prepend_handler :incoming_nicknameinuse, self.method(:_nicknameinuse)
33
- prepend_handler :incoming_channelurl, self.method(:r_channelurl)
34
- prepend_handler :incoming_topic, self.method(:r_topic)
35
- prepend_handler :incoming_topicinfo, self.method(:r_topicinfo)
36
- prepend_handler :incoming_namreply, self.method(:_namreply)
37
- prepend_handler :incoming_endofnames, self.method(:r_endofnames)
38
- prepend_handler :incoming_motd, self.method(:r_motd)
39
- prepend_handler :incoming_motdstart, self.method(:r_motdstart)
40
- prepend_handler :incoming_endofmotd, self.method(:r_endofmotd)
41
- prepend_handler :incoming_invite, self.method(:r_invite)
42
-
43
- # Outgoing events
44
- prepend_handler :outgoing_begin_connection, self.method(:out_begin_connection)
45
- end
46
-
47
- def r_msg(fullactor, actor, target, text)
48
- report "{#{target}} <#{actor}> #{text}"
49
- end
50
-
51
- def r_act(fullactor, actor, target, text)
52
- report "{#{target}} * #{actor} #{text}"
53
- end
54
-
55
- def r_notice(fullactor, actor, target, text)
56
- report "{#{target}} -#{actor}- #{text}"
57
- end
58
-
59
- def r_ctcp(fullactor, actor, target, text)
60
- report "{#{target}} [#{actor} #{text}]"
61
- end
62
-
63
- def r_ctcpreply(fullactor, actor, target, text)
64
- report "{#{target}} [Reply: #{actor} #{text}]"
65
- end
66
-
67
- def r_mode(fullactor, actor, target, modes, objects)
68
- report "{#{target}} #{actor} sets mode #{modes} #{objects}"
69
- end
70
-
71
- def r_join(fullactor, actor, target)
72
- report "{#{target}} #{actor} joins"
73
- end
74
-
75
- def r_part(fullactor, actor, target, text)
76
- report "{#{target}} #{actor} parts (#{text})"
77
- end
78
-
79
- def r_kick(fullactor, actor, target, object, text)
80
- report "{#{target}} #{actor} kicked #{object} (#{text})"
81
- end
82
-
83
- def r_quit(fullactor, actor, text)
84
- report "#{actor} quit (#{text})"
85
- end
86
-
87
- # Reports nick change unless nickname is us - we check nickname here since
88
- # the magic method changes @me to the new nickname.
89
- def r_nick(fullactor, actor, nickname)
90
- report "#{actor} changed nick to #{nickname}" unless nickname == @me
91
- end
92
-
93
- def r_bannedfromchan(text, args)
94
- text =~ /^(\S*) :Cannot join channel/
95
- report "Banned from channel #{$1}"
96
- end
97
-
98
- def r_badchannelkey(text, args)
99
- text =~ /^(\S*) :Cannot join channel/
100
- report "Bad channel key (password) for #{$1}"
101
- end
102
-
103
- def r_welcome(*args)
104
- report "*** Logged in as #{@me}. ***"
105
- end
106
-
107
- def r_miscellany(line)
108
- report "serv: #{line}"
109
- end
110
-
111
8
  # Nickname change failed: already in use. This needs a rewrite to at
112
9
  # least hit a "failed too many times" handler of some kind - for a bot,
113
10
  # quitting may be fine, but for something else, we may want to prompt a
@@ -117,8 +14,8 @@ module Defaults
117
14
  #
118
15
  # TODO: This should really not even be here. Client should have full control over whether or not
119
16
  # they want this. Base IRC bot class should have this, but not the core YAIL lib.
120
- def _nicknameinuse(text, args)
121
- text =~ /^(\S+)/
17
+ def _nicknameinuse(event)
18
+ event.message =~ /^(\S+)/
122
19
  report "Nickname #{$1} is already in use."
123
20
 
124
21
  if (!@registered)
@@ -137,29 +34,11 @@ module Defaults
137
34
  end
138
35
  end
139
36
 
140
- # Channel URL
141
- def r_channelurl(text, args)
142
- text =~ /^(\S+) :?(.+)$/
143
- report "{#{$1}} URL is #{$2}"
144
- end
145
-
146
- # Channel topic
147
- def r_topic(text, args)
148
- text =~ /^(\S+) :?(.+)$/
149
- report "{#{$1}} Topic is: #{$2}"
150
- end
151
-
152
- # Channel topic setter
153
- def r_topicinfo(text, args)
154
- text =~ /^(\S+) (\S+) (\d+)$/
155
- report "{#{$1}} Topic set by #{$2} on #{Time.at($3.to_i).asctime}"
156
- end
157
-
158
37
  # Names line
159
38
  #
160
39
  # TODO: Either store this data silently or ditch this code - this verbosity doesn't belong in a core lib
161
- def _namreply(text, args)
162
- text =~ /^(@|\*|=) (\S+) :?(.+)$/
40
+ def _namreply(event)
41
+ event.message =~ /^(@|\*|=) (\S+) :?(.+)$/
163
42
  channeltype = {'@' => 'Secret', '*' => 'Private', '=' => 'Normal'}[$1]
164
43
  report "{#{$2}} #{channeltype} channel nickname list: #{$3}"
165
44
  @nicklist = $3.split(' ')
@@ -167,43 +46,6 @@ module Defaults
167
46
  report "First nick: #{@nicklist[0]}"
168
47
  end
169
48
 
170
- # End of names
171
- def r_endofnames(text, args)
172
- text =~ /^(\S+)/
173
- report "{#{$1}} Nickname list complete"
174
- end
175
-
176
- # MOTD line
177
- def r_motd(text, args)
178
- text =~ /^:?(.+)$/
179
- report "*MOTD* #{$1}"
180
- end
181
-
182
- # Beginning of MOTD
183
- def r_motdstart(text, args)
184
- text =~ /^:?(.+)$/
185
- report "*MOTD* #{$1}"
186
- end
187
-
188
- # End of MOTD
189
- def r_endofmotd(text, args)
190
- report "*MOTD* End of MOTD"
191
- end
192
-
193
- # We dun connected to a server! Just sends password (if one is set) and
194
- # user/nick. This isn't quite "essential" to a working IRC app, but this data
195
- # *must* be sent at some point, so be careful before skipping this handler.
196
- def out_begin_connection(username, address, realname)
197
- pass(@password) if @password
198
- user(username, '0.0.0.0', address, realname)
199
- nick(@nicknames[0])
200
- end
201
-
202
- # Incoming invitation
203
- def r_invite(fullactor, actor, target)
204
- report "[#{actor}] INVITE to #{target}"
205
- end
206
-
207
49
  end
208
50
 
209
51
  end
@@ -6,16 +6,35 @@ class YAIL
6
6
  # Base event class for stuff shared by any type of event. Note that :type and :handled
7
7
  # apply to *all* events, so they're explicitly defined here.
8
8
  class BaseEvent
9
- attr_reader :type, :handled
10
-
11
- # Creates an event object and sets up some sane defaults for common elements
12
- def initialize()
9
+ # Creates an event object and sets up some sane defaults for common elements. Any elements
10
+ # in the data hash are converted to "magic" methods.
11
+ def initialize(data = {})
12
+ # Don't modify incoming data!
13
+ @data = data.dup
13
14
  @handled = false
14
- @type = nil
15
+ @type = @data.delete(:type)
16
+
17
+ # All events have the capacity for a parent
18
+ @data[:parent] ||= nil
19
+
20
+ # Give useful accessors in a hacky but fun way! I can't decide if I prefer the pain of
21
+ # using method_missing or the pain of not knowing how to do this without a string eval....
22
+ for key in @data.keys
23
+ key = key.to_s
24
+ self.instance_eval("def #{key}; return @data[:#{key}]; end")
25
+ end
26
+
27
+ raise "BaseEvent not usable - please subclass" if BaseEvent == self.class
28
+ end
29
+
30
+ # Helps us debug
31
+ def to_s
32
+ return super().gsub(self.class.name, "%s [%s]" % [self.class.name, @type.to_s])
15
33
  end
16
34
 
17
- # Sets this event as having been handled (i.e., no further handling should occur)
18
- def handle; @handled = true; end
35
+ # Slightly unintuitive name to avoid accidental use - we don't want it to be the norm to stop
36
+ # the event handling chain anymore! Filters + callback should make that a rarity.
37
+ def handled!; @handled = true; end
19
38
 
20
39
  # Cheesy shortcut to @handled in "boolean" form
21
40
  def handled?; return @handled; end
@@ -30,6 +49,11 @@ class YAIL
30
49
  end
31
50
  end
32
51
 
52
+ # Custom event is just a base event that doesn't crash when accessing type :)
53
+ class CustomEvent < BaseEvent
54
+ def type; return @type; end
55
+ end
56
+
33
57
  # This is the incoming event class. For all situations where the server
34
58
  # sent us some kind of event, this class handles all the data.
35
59
  #
@@ -37,9 +61,10 @@ class YAIL
37
61
  # the IRC server. Other possible pieces of data are as follows:
38
62
  # * fullname: Rarely needed, full text of origin of an action
39
63
  # * nick: Nickname of originator of an event
64
+ # * from: Nickname *or* server name, should be on every event
40
65
  # * channel: Where applicable, the name of the channel in which the event
41
66
  # happened.
42
- # * text: Actual message/emote/notice/etc
67
+ # * message: Actual message/emote/notice/etc
43
68
  # * target: User targeted for various commands - PRIVMSG/NOTICE recipient, KICK victim, etc
44
69
  # * pm?: Set to true if the event is a "private" event (not sent to the
45
70
  # channel). Useful primarily for message types of events (PRIVMSG).
@@ -59,27 +84,14 @@ class YAIL
59
84
  attr_reader :raw, :msg
60
85
  private_class_method :new
61
86
 
62
- # Sets up data and accessors
87
+ # Incoming events always have :raw and :msg in the data hash
63
88
  def initialize(data = {})
64
- super()
65
-
66
89
  # Don't modify incoming element!
67
90
  @data = data.dup
68
91
  @raw = @data.delete(:raw)
69
92
  @msg = @data.delete(:msg)
70
- @type = @data.delete(:type)
71
-
72
- # Give useful accessors in a hacky but fun way! I can't decide if I prefer the pain of
73
- # using method_missing or the pain of not knowing how to do this without a string eval....
74
- for key in @data.keys
75
- key = key.to_s
76
- self.instance_eval("def #{key}; return @data[:#{key}]; end")
77
- end
78
- end
79
93
 
80
- # Helps us debug
81
- def to_s
82
- return super().gsub('IncomingEvent', "IncomingEvent[#{@type.to_s}]")
94
+ super(data)
83
95
  end
84
96
 
85
97
  # Incoming events in our system are always :incoming_xxx
@@ -92,7 +104,15 @@ class YAIL
92
104
  # Parse with MessageParser to get raw IRC info
93
105
  raw = line.dup
94
106
  msg = Net::YAIL::MessageParser.new(line)
95
- data = { :raw => raw, :msg => msg, :parent => nil }
107
+
108
+ # All incoming events need .raw and .msg in addition to any base event attributes.
109
+ #
110
+ # "from" is a tricky case as it isn't used on all messages - but because it's something of
111
+ # a standard we rely on for so many messages, it has a default so that at the least one can
112
+ # rely on not getting a crash for some of the edge cases (like "NOTICE :ERROR from foo.bar.com"
113
+ # or a server-less "NOTICE AUTH :xxxx"). Maybe more elements should have defaults... not
114
+ # real sure yet.
115
+ data = { :raw => raw, :msg => msg, :from => nil }
96
116
 
97
117
  # Not all messages from the server identify themselves as such, so we just assume it's from
98
118
  # the server unless we explicitly see a nick
@@ -102,47 +122,42 @@ class YAIL
102
122
  if msg.servername
103
123
  data[:from] = data[:servername] = msg.servername
104
124
  elsif msg.prefix && msg.nick
105
- data[:from] = data[:fullname] = msg.prefix
106
- data[:nick] = msg.nick
125
+ data[:fullname] = msg.prefix
126
+ data[:from] = data[:nick] = msg.nick
107
127
  data[:server?] = false
108
128
  end
109
129
 
110
- # Since "from" is something of a standard we rely on for all kinds of messages, we default
111
- # to an empty string so that at the least one can rely on not getting a crash for some of
112
- # the edge cases (like "NOTICE :ERROR from foo.bar.com")
113
- data[:from] ||= ""
114
-
115
130
  case msg.command
116
131
  when 'ERROR'
117
132
  data[:type] = :error
118
- data[:text] = msg.params.last
133
+ data[:message] = msg.params.last
119
134
  event = new(data)
120
135
 
121
136
  when 'PING'
122
137
  data[:type] = :ping
123
- data[:text] = msg.params.last
138
+ data[:message] = msg.params.last
124
139
  event = new(data)
125
140
 
126
141
  when 'TOPIC'
127
142
  data[:type] = :topic_change
128
143
  data[:channel] = msg.params.first
129
- data[:text] = msg.params.last
144
+ data[:message] = msg.params.last
130
145
  event = new(data)
131
146
 
132
147
  when /^\d{3}$/
133
148
  # Get base event for the "numeric" type - so many of these exist, and so few are likely
134
- # to be handled directly. Sadly, some hackery has to happen here to make "text" backward-
149
+ # to be handled directly. Sadly, some hackery has to happen here to make "message" backward-
135
150
  # compatible since old YAIL auto-joined all parameters into one string.
136
151
  data[:type] = :numeric
137
152
  params = msg.params.dup
138
153
  data[:target] = params.shift
139
154
  data[:parameters] = params
140
- data[:text] = params.join(' ')
155
+ data[:message] = params.join(' ')
141
156
  data[:numeric] = msg.command.to_i
142
157
  event = new(data)
143
158
 
144
159
  # Create child event for the specific numeric
145
- data[:type] = msg.command.to_sym
160
+ data[:type] = :"numeric_#{msg.command.to_i}"
146
161
  data[:parent] = event
147
162
  event = new(data)
148
163
 
@@ -174,24 +189,24 @@ class YAIL
174
189
  when 'PART'
175
190
  data[:type] = :part
176
191
  data[:channel] = msg.params.first
177
- data[:text] = msg.params.last
192
+ data[:message] = msg.params.last
178
193
  event = new(data)
179
194
 
180
195
  when 'KICK'
181
196
  data[:type] = :kick
182
197
  data[:channel] = msg.params[0]
183
198
  data[:target] = msg.params[1]
184
- data[:text] = msg.params[2]
199
+ data[:message] = msg.params[2]
185
200
  event = new(data)
186
201
 
187
202
  when 'QUIT'
188
203
  data[:type] = :quit
189
- data[:text] = msg.params.first
204
+ data[:message] = msg.params.first
190
205
  event = new(data)
191
206
 
192
207
  when 'NICK'
193
208
  data[:type] = :nick
194
- data[:text] = msg.params.first
209
+ data[:message] = msg.params.first
195
210
  event = new(data)
196
211
 
197
212
  # Unknown line! If this library is complete, we should *never* see this situation occur,
@@ -211,7 +226,7 @@ class YAIL
211
226
  def self.mode_events(msg, data)
212
227
  data[:type] = :mode
213
228
  data[:channel] = msg.params.shift
214
- data[:text] = msg.params.shift
229
+ data[:message] = msg.params.shift
215
230
  data[:targets] = msg.params
216
231
  event = new(data)
217
232
  end
@@ -240,22 +255,22 @@ class YAIL
240
255
 
241
256
  # Get base event
242
257
  data[:type] = :msg
243
- data[:text] = msg.params.last
258
+ data[:message] = msg.params.last
244
259
  event = new(data)
245
260
 
246
261
  # Is this CTCP?
247
- if event.text =~ /^\001(.+?)\001$/
262
+ if event.message =~ /^\001(.+?)\001$/
248
263
  data[:type] = :ctcp
249
- data[:text] = $1
264
+ data[:message] = $1
250
265
  data[:parent] = event
251
266
 
252
267
  event = new(data)
253
268
  end
254
269
 
255
270
  # CTCP action?
256
- if :ctcp == data[:type] && event.text =~ /^ACTION (.+)$/
271
+ if :ctcp == data[:type] && event.message =~ /^ACTION (.+)$/
257
272
  data[:type] = :act
258
- data[:text] = $1
273
+ data[:message] = $1
259
274
  data[:parent] = event
260
275
 
261
276
  event = new(data)
@@ -271,12 +286,12 @@ class YAIL
271
286
 
272
287
  # Get base event
273
288
  data[:type] = :notice
274
- data[:text] = msg.params.last
289
+ data[:message] = msg.params.last
275
290
  event = new(data)
276
291
 
277
- if event.text =~ /^\001(.+?)\001$/
292
+ if event.message =~ /^\001(.+?)\001$/
278
293
  data[:type] = :ctcp_reply
279
- data[:text] = $1
294
+ data[:message] = $1
280
295
  data[:parent] = event
281
296
 
282
297
  event = new(data)
@@ -1,11 +1,12 @@
1
1
  require 'rubygems'
2
2
  require 'net/yail'
3
+ require 'net/yail/report_events'
3
4
 
4
5
  # My abstraction from adapter to a real bot.
5
6
  class IRCBot
6
- attr_reader :irc
7
+ include Net::IRCEvents::Reports
7
8
 
8
- public
9
+ attr_reader :irc
9
10
 
10
11
  # Creates a new bot. Options are anything you can pass to the Net::YAIL constructor:
11
12
  # * <tt>:irc_network</tt>: Name/IP of the IRC server - backward-compatibility hack, and is
@@ -60,9 +61,10 @@ class IRCBot
60
61
  # add_custom_handlers to allow auto-creation in case of a restart.
61
62
  def connect_socket
62
63
  @irc = Net::YAIL.new(@options)
64
+ setup_reporting(@irc)
63
65
 
64
66
  # Simple hook for welcome to allow auto-joining of the channel
65
- @irc.prepend_handler :incoming_welcome, self.method(:welcome)
67
+ @irc.on_welcome self.method(:welcome)
66
68
 
67
69
  add_custom_handlers
68
70
  end
@@ -95,7 +97,7 @@ class IRCBot
95
97
  while true
96
98
  until @irc.dead_socket
97
99
  sleep 15
98
- @irc.handle(:irc_loop)
100
+ @irc.dispatch Net::YAIL::CustomEvent.new(:type => :irc_loop)
99
101
  Thread.pass
100
102
  end
101
103
 
@@ -109,10 +111,8 @@ class IRCBot
109
111
 
110
112
  private
111
113
  # Basic handler for joining our channels upon successful registration
112
- def welcome(text, args)
114
+ def welcome(event)
113
115
  @options[:channels].each {|channel| @irc.join(channel) }
114
- # Let the default welcome stuff still happen
115
- return false
116
116
  end
117
117
 
118
118
  ################