net-yail 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/net/yail.rb CHANGED
@@ -10,12 +10,14 @@ require 'net/yail/magic_events'
10
10
  require 'net/yail/default_events'
11
11
  require 'net/yail/output_api'
12
12
  require 'net/yail/legacy_events'
13
+ require 'net/yail/dispatch'
13
14
 
14
15
  # This tells us our version info.
15
16
  require 'net/yail/yail-version'
16
17
 
17
- # Finally, a real class to include!
18
+ # Finally, real classes to include!
18
19
  require 'net/yail/event'
20
+ require 'net/yail/handler'
19
21
 
20
22
  # If a thread crashes, I want the app to die. My threads are persistent, not
21
23
  # temporary.
@@ -40,7 +42,7 @@ module Net
40
42
  #
41
43
  # YAIL at its core is an event handler with some logic specific to IRC socket messages. BaseEvent
42
44
  # is the parent of all event objects. An event is run through various pre-callback filters, a
43
- # single callback, and post-callback filters. Up until the post-callback filters start, the handler
45
+ # single callback, and post-callback filters. Up until the callback is hit, the handler
44
46
  # "chain" can be stopped by calling the event's .handled! method. It is generally advised against
45
47
  # doing this, as it will stop things like post-callback stats gathering and similar plugin-friendly
46
48
  # features, but it does make sense in certain situations (an "ignore user" module, for instance).
@@ -58,9 +60,12 @@ module Net
58
60
  # and filter is explained above (1 callback per event, many filters), but at their core they are
59
61
  # just code that handles some aspect of the event.
60
62
  #
61
- # All handlers require some block of code. You can explicitly create a Proc object, use
62
- # Something.method, or just pass in a block. The block yields the event object which will have
63
- # all relevant data for the event. See the examples below for a basic idea.
63
+ # Handler methods must receive a block of code. This can be passed in as a simple Ruby block, or
64
+ # manually created via Proc.new, lambda, Foo.method(:bar), etc. The <tt>method</tt> parameter of
65
+ # all the handler methods is optional so that, as mentioned, a block can be used instead of a Proc.
66
+ #
67
+ # The handlers, when fired, will yield the event object containing all relevant data for the event.
68
+ # See the examples below for a basic idea.
64
69
  #
65
70
  # To register an event's callback, you have the following options:
66
71
  # * <tt>set_callback(event_type, method = nil, &block)</tt>: Sets the event type's callback, clobbering any
@@ -69,7 +74,7 @@ module Net
69
74
  # The "xxx" must be replaced by the incoming event's short type name. For example,
70
75
  # <tt>on_welcome {|event| ...}</tt> would be used in place of <tt>set_callback(:incoming_welcome, xxx)</tt>.
71
76
  #
72
- # To register a before- or after-callback filter:
77
+ # To register a before- or after-callback filter, the following methods are available:
73
78
  # * <tt>before_filter(event_type, method = nil, &block)</tt>: Sets a before-callback filter, adding it to
74
79
  # the current list of before-callback filters for the given event type.
75
80
  # * <tt>after_filter(event_type, method = nil, &block)</tt>: Sets an after-callback filter, adding it to
@@ -83,6 +88,23 @@ module Net
83
88
  # * <tt>said_xxx(method = nil, &block)</tt>: Adds an after-callback filter for the given outgoing event
84
89
  # type, such as <tt>said_act {|event| ...}</tt>
85
90
  #
91
+ # ===Conditional Filtering
92
+ #
93
+ # For some situations, you want your filter to only be called if a certain condition is met. Enter conditional filtering!
94
+ # By using this exciting feature, you can set up handlers and callbacks which only trigger when certain conditions are
95
+ # met. Be warned, though, this can get confusing....
96
+ #
97
+ # Conditions can be added to any filter method, but should **never** be used on the callback, since *there can be only one*.
98
+ # To add a filter, you simply supply a hash with a key of either `:if` or `:unless`, and a value which is either another
99
+ # hash of conditions, or a proc.
100
+ #
101
+ # If a proc is sent, it will be a method that is called and passed the event object. If the proc returns true, an `:if`
102
+ # condition is met and un `:unless` condition is not met. If a condition is not met, the filter is skipped entirely.
103
+ #
104
+ # If a hash is sent, each key is expected to be an attribute on the event object. It's similar to a lambda where you
105
+ # return true if each attribute equals the value in the hash. For instance, `:if => {:message => "food", :nick => "Simon"}`
106
+ # is the same as `:if => lambda {|e| e.message == "food" && e.nick == "Simon"}`.
107
+ #
86
108
  # ==Incoming events
87
109
  #
88
110
  # *All* incoming events will have, at the least, the following methods:
@@ -232,14 +254,14 @@ module Net
232
254
  #
233
255
  # require 'rubygems'
234
256
  # require 'net/yail'
235
- #
257
+ #
236
258
  # irc = Net::YAIL.new(
237
259
  # :address => 'irc.someplace.co.uk',
238
260
  # :username => 'Frakking Bot',
239
261
  # :realname => 'John Botfrakker',
240
262
  # :nicknames => ['bot1', 'bot2', 'bot3']
241
263
  # )
242
- #
264
+ #
243
265
  # # Automatically join #foo when the server welcomes us
244
266
  # irc.on_welcome {|event| irc.join("#foo") }
245
267
  #
@@ -249,11 +271,12 @@ module Net
249
271
  #
250
272
  # # Loops forever until CTRL+C
251
273
  # irc.start_listening!
252
- class YAIL
274
+ class YAIL
253
275
  include Net::IRCEvents::Magic
254
276
  include Net::IRCEvents::Defaults
255
277
  include Net::IRCOutputAPI
256
278
  include Net::IRCEvents::LegacyEvents
279
+ include Dispatch
257
280
 
258
281
  attr_reader(
259
282
  :me, # Nickname on the IRC server
@@ -358,7 +381,7 @@ class YAIL
358
381
  else
359
382
  @log = Logger.new(options[:log_io] || STDERR)
360
383
  @log.level = Logger::WARN
361
-
384
+
362
385
  if (options[:silent] || options[:loud])
363
386
  @log.warn '[DEPRECATED] - passing :silent and :loud options to constructor are deprecated as of 1.4.1'
364
387
  end
@@ -472,6 +495,8 @@ class YAIL
472
495
  set_callback :outgoing_ctcp, self.method(:magic_out_ctcp)
473
496
  set_callback :outgoing_act, self.method(:magic_out_act)
474
497
 
498
+ # WHOIS is tricky due to how weird its argument positioning is, so can't use create_command, either
499
+
475
500
  # All PRIVMSG events eventually hit this - it's a legacy thing, and kinda dumb, but there you
476
501
  # have it. Just sends a raw PRIVMSG out to the socket.
477
502
  create_command :privmsg, "PRIVMSG :target ::message", :target, :message
@@ -527,7 +552,7 @@ class YAIL
527
552
  return [@socket.gets] unless @ssl
528
553
 
529
554
  # SSL socket == return all lines available
530
- return @socket.readpartial(Buffering::BLOCK_SIZE).split($/).collect {|message| message}
555
+ return @socket.readpartial(OpenSSL::Buffering::BLOCK_SIZE).split($/).collect {|message| message}
531
556
  end
532
557
 
533
558
  # Reads incoming data - should only be called by io_loop, and only when
@@ -660,12 +685,12 @@ class YAIL
660
685
  # before the event callback has run, and can stop the event (and other filters) from running by calling the event's
661
686
  # end_chain() method. Filters shouldn't do this very often! Before-filtering can modify output text before the
662
687
  # event callback runs, ignore incoming events for a given user, etc.
663
- def before_filter(event_type, method = nil, &block)
688
+ def before_filter(event_type, method = nil, conditions = {}, &block)
664
689
  filter = block_given? ? block : method
665
690
  if filter
666
691
  event_type = numeric_event_type_convert(event_type)
667
692
  @before_filters[event_type] ||= Array.new
668
- @before_filters[event_type].unshift(filter)
693
+ @before_filters[event_type].unshift(Net::YAIL::Handler.new(filter, conditions))
669
694
  end
670
695
  end
671
696
 
@@ -673,22 +698,22 @@ class YAIL
673
698
  # longer a concept of multiple callbacks! Use filters for that kind of functionality. Think this way: the callback
674
699
  # is the action that takes place when an event hits. Filters are for functionality related to the event, but not
675
700
  # the definitive callback - logging, filtering messages, stats gathering, ignoring messages from a set user, etc.
676
- def set_callback(event_type, method = nil, &block)
701
+ def set_callback(event_type, method = nil, conditions = {}, &block)
677
702
  callback = block_given? ? block : method
678
703
  event_type = numeric_event_type_convert(event_type)
679
- @callback[event_type] = callback
704
+ @callback[event_type] = Net::YAIL::Handler.new(callback, conditions)
680
705
  @callback.delete(event_type) unless callback
681
706
  end
682
707
 
683
708
  # Prepends the given block or method to the after_filters array for the given type. After-filters are called after
684
709
  # the event callback has run, and cannot stop other after-filters from running. Best used for logging or statistics
685
710
  # gathering.
686
- def after_filter(event_type, method = nil, &block)
711
+ def after_filter(event_type, method = nil, conditions = {}, &block)
687
712
  filter = block_given? ? block : method
688
713
  if filter
689
714
  event_type = numeric_event_type_convert(event_type)
690
715
  @after_filters[event_type] ||= Array.new
691
- @after_filters[event_type].unshift(filter)
716
+ @after_filters[event_type].unshift(Net::YAIL::Handler.new(filter, conditions))
692
717
  end
693
718
  end
694
719
 
@@ -708,42 +733,6 @@ class YAIL
708
733
  return type
709
734
  end
710
735
 
711
- # Given an event, calls pre-callback filters, callback, and post-callback filters. Uses hacky
712
- # :incoming_any event if event object is of IncomingEvent type.
713
- def dispatch(event)
714
- # Add all before-callback stuff to our chain
715
- chain = []
716
- chain.push @before_filters[:incoming_any] if Net::YAIL::IncomingEvent === event
717
- chain.push @before_filters[:outgoing_any] if Net::YAIL::OutgoingEvent === event
718
- chain.push @before_filters[event.type]
719
- chain.flatten!
720
- chain.compact!
721
-
722
- # Run each filter in the chain, exiting early if event was handled
723
- for filter in chain
724
- filter.call(event)
725
- return if event.handled?
726
- end
727
-
728
- # Legacy handler - return if true, since that's how the old system works - EXCEPTION for outgoing events, since
729
- # the old system didn't allow the outgoing "core" code to be skipped!
730
- if true == legacy_process_event(event)
731
- return unless Net::YAIL::OutgoingEvent === event
732
- end
733
-
734
- # Add new callback and all after-callback stuff to a new chain
735
- chain = []
736
- chain.push @callback[event.type]
737
- chain.push @after_filters[event.type]
738
- chain.push @after_filters[:incoming_any] if Net::YAIL::IncomingEvent === event
739
- chain.push @after_filters[:outgoing_any] if Net::YAIL::OutgoingEvent === event
740
- chain.flatten!
741
- chain.compact!
742
-
743
- # Run all after-filters blindly - none can affect callback, so after-filters can't set handled to true
744
- chain.each {|filter| filter.call(event)}
745
- end
746
-
747
736
  # Handles magic listener setup methods: on_xxx, hearing_xxx, heard_xxx, saying_xxx, and said_xxx
748
737
  def method_missing(name, *args, &block)
749
738
  method = nil
@@ -773,13 +762,14 @@ class YAIL
773
762
 
774
763
  # Magic methods MUST have an arg or a block!
775
764
  filter_or_callback_method = block_given? ? block : args.shift
765
+ conditions = args.shift || {}
776
766
 
777
767
  # If we didn't match a magic method signature, or we don't have the expected parameters, call
778
768
  # parent's method_missing. Just to be safe, we also return, in case YAIL one day subclasses
779
769
  # from something that handles some method_missing stuff.
780
770
  return super if method.nil? || event_type.nil? || args.length > 0
781
771
 
782
- self.send(method, event_type, filter_or_callback_method)
772
+ self.send(method, event_type, filter_or_callback_method, conditions)
783
773
  end
784
774
  end
785
775
 
@@ -0,0 +1,47 @@
1
+ module Net
2
+ class YAIL
3
+
4
+ module Dispatch
5
+ # Given an event, calls pre-callback filters, callback, and post-callback filters. Uses
6
+ # *_any event, where * is the event's event_class value
7
+ def dispatch(event)
8
+ # We always have an "any" filter option, so we build the symbol first
9
+ any_filter_sym = (event.event_class + "_any").to_sym
10
+
11
+ before_any = @before_filters[any_filter_sym]
12
+ run_chain(event, :allow_halt => true, :handlers => [before_any, @before_filters[event.type]])
13
+
14
+ # Have to break here if before filters said so
15
+ return if event.handled?
16
+
17
+ # Legacy handler - return if true, since that's how the old system works - EXCEPTION for outgoing events, since
18
+ # the old system didn't allow the outgoing "core" code to be skipped!
19
+ if true == legacy_process_event(event)
20
+ return unless Net::YAIL::OutgoingEvent === event
21
+ end
22
+
23
+ # Add new callback and all after-callback stuff to a new chain
24
+ after_any = @after_filters[any_filter_sym]
25
+ run_chain(event, :allow_halt => false, :handlers => [@callback[event.type], @after_filters[event.type], after_any])
26
+ end
27
+
28
+ # Consolidates all handlers passed in, flattening into a single array of handlers and
29
+ # removing any nils, then runs the methods on each event, respecting filter conditions
30
+ def run_chain(event, opts = {})
31
+ handlers = opts[:handlers]
32
+ handlers.flatten!
33
+ handlers.compact!
34
+
35
+ allow_halt = opts[:allow_halt]
36
+
37
+ # Run each filter in the chain if conditions are met, exiting early if event was handled
38
+ for handler in handlers
39
+ handler.call(event)
40
+ return if event.handled? && true == allow_halt
41
+ end
42
+
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -38,19 +38,28 @@ class YAIL
38
38
 
39
39
  # Cheesy shortcut to @handled in "boolean" form
40
40
  def handled?; return @handled; end
41
+
42
+ def event_class
43
+ return "base"
44
+ end
45
+
46
+ def type
47
+ return ("%s_%s" % [event_class, @type]).to_sym
48
+ end
41
49
  end
42
50
 
43
51
  # The outgoing event class - outgoing events haven't got much in
44
52
  # common, so this class is primarily to facilitate the new system.
45
53
  class OutgoingEvent < BaseEvent
46
54
  # Outgoing events in our system are always :outgoing_xxx
47
- def type
48
- return :"outgoing_#{@type.to_s}"
55
+ def event_class
56
+ return "outgoing"
49
57
  end
50
58
  end
51
59
 
52
60
  # Custom event is just a base event that doesn't crash when accessing type :)
53
61
  class CustomEvent < BaseEvent
62
+ def event_class; return "custom"; end
54
63
  def type; return @type; end
55
64
  end
56
65
 
@@ -95,7 +104,9 @@ class YAIL
95
104
  end
96
105
 
97
106
  # Incoming events in our system are always :incoming_xxx
98
- def type; return :"incoming_#{@type.to_s}"; end
107
+ def event_class
108
+ return "incoming"
109
+ end
99
110
 
100
111
  # Effectively our event "factory" - uses Net::YAIL::MessageParser and returns an event
101
112
  # object - usually just one, but TODO: some lines actually contain multiple messages. When
@@ -0,0 +1,38 @@
1
+ module Net
2
+ class YAIL
3
+
4
+ # Represents a method and meta-data for handling an event
5
+ class Handler
6
+ def initialize(method, conditions = {})
7
+ @method = method
8
+
9
+ # Make sure even an explicit nil is turned into an empty hash
10
+ @conditions = conditions || {}
11
+ end
12
+
13
+ # Calls the handler with the given arguments if the conditions are met
14
+ def call(event)
15
+ # Get out if :if/:unless aren't met
16
+ return if @conditions[:if] && !condition_check(@conditions[:if], event)
17
+ return if @conditions[:unless] && condition_check(@conditions[:unless], event)
18
+
19
+ return @method.call(event)
20
+ end
21
+
22
+ # Checks the condition. Procs are simply run and returned, while Hash-based conditions return
23
+ # true if value === event.send(key)
24
+ def condition_check(condition, event)
25
+ # Procs are the easiest to evaluate
26
+ return condition.call(event) if condition.is_a?(Proc)
27
+
28
+ # If not a proc, condition must be a hash - iterate over values. All must be true to
29
+ # return true.
30
+ for (key, value) in condition
31
+ return false unless value === event.send(key)
32
+ end
33
+ return true
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -12,8 +12,11 @@ module LegacyEvents
12
12
  def prepend_handler(event, *procs, &block)
13
13
  raise "Cannot change handlers while threads are listening!" if @ioloop_thread
14
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/"
15
+ unless $deprecated_prepend_warning
16
+ @log.warn "[DEPRECATED] - Net::YAIL#prepend_handler is deprecated as of 1.5.0 - please see documentation on the new " +
17
+ "event handling model methods - http://ruby-irc-yail.nerdbucket.com/"
18
+ $deprecated_prepend_warning = true
19
+ end
17
20
 
18
21
  # Allow blocks as well as procs
19
22
  if block_given?
@@ -190,22 +193,6 @@ module LegacyEvents
190
193
 
191
194
  when :outgoing_begin_connection
192
195
  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
196
  end
210
197
  end
211
198
  end
@@ -53,6 +53,11 @@ module Magic
53
53
  privmsg(event.target, "\001ACTION #{event.message}\001")
54
54
  end
55
55
 
56
+ # WHOIS - here because first parameter might be the nick and might be the optional server
57
+ def magic_out_whois(event)
58
+ raw "WHOIS %s%s" % [event.server.to_s.empty? ? "" : event.server, event.nick]
59
+ end
60
+
56
61
  end
57
62
 
58
63
  end
@@ -39,6 +39,15 @@ module IRCOutputAPI
39
39
  buffer_output Net::YAIL::OutgoingEvent.new(:type => :act, :target => target, :message => message)
40
40
  end
41
41
 
42
+ # WHOIS is tricky - it can optionally take a :target parameter, which is the *first* parameter
43
+ # if it's present, rather than added to the parameter list. BAD SPECIFICATIONS! NO BISCUIT!
44
+ # (If it were more standard, we could just use create_command)
45
+ #
46
+ # Note that "nick" can actually be a comma-separated list of masks for whois querying.
47
+ def whois(nick, server = nil)
48
+ dispatch Net::YAIL::OutgoingEvent.new(:type => :whois, :nick => nick, :server => server)
49
+ end
50
+
42
51
  # Creates an output command and its handler. output_base is a template of the command without
43
52
  # any conditional arguments (for simple commands this is the full template). args is a list of
44
53
  # argument symbols to determine how the event is built and handled. If an argument symbol is
@@ -1,5 +1,5 @@
1
1
  module Net
2
2
  class YAIL
3
- VERSION = '1.5.1'
3
+ VERSION = '1.6.0'
4
4
  end
5
5
  end
data/tests/tc_yail.rb CHANGED
@@ -241,4 +241,154 @@ class YailSessionTest < Test::Unit::TestCase
241
241
  assert_equal 'Quit: Bye byes', @quit[:message]
242
242
  end
243
243
  end
244
+
245
+ # Verifies that chains are broken properly for before filters and the callback, but not for after filters
246
+ def test_chain_breaking
247
+ @msg = Hash.new(0)
248
+
249
+ # First call means last filter - this one proves that the last one did or didn't run
250
+ @yail.hearing_msg { |e| @msg[:before_last] += 1; e.handled! if e.message == "quit 2" }
251
+
252
+ # Second call means first filter - this one actually allows or skips all other filters and callbacks
253
+ @yail.hearing_msg { |e| e.handled! if e.message == "quit 1" }
254
+
255
+ # Actual callback - this is hit as long as a before filter didn't stop the chain
256
+ @yail.on_msg { |e| @msg[:callback] += 1; e.handled! }
257
+
258
+ # After filters never stop the chain
259
+ @yail.heard_msg { |e| @msg[:after_msg] += 1; e.handled! }
260
+ @yail.heard_msg { |e| @msg[:after_msg] += 1; e.handled! }
261
+ @yail.heard_msg { |e| @msg[:after_msg] += 1; e.handled! }
262
+ @yail.heard_msg { |e| @msg[:after_msg] += 1; e.handled! }
263
+
264
+ @yail.start_listening
265
+
266
+ wait_for_irc
267
+
268
+ # quit 1 means no filters are hit after the first
269
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #foosball :quit 1" do
270
+ assert_equal 0, @msg[:before_last]
271
+ assert_equal 0, @msg[:callback]
272
+ assert_equal 0, @msg[:after_msg]
273
+ end
274
+
275
+ # quit 2 means we hit the first before filter, but stopped after the second
276
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #foosball :quit 2" do
277
+ assert_equal 1, @msg[:before_last]
278
+ assert_equal 0, @msg[:callback]
279
+ assert_equal 0, @msg[:after_msg]
280
+ end
281
+
282
+ # After filters get run when before filters don't stop things, even though callback and after
283
+ # filters keep trying to break the chain
284
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #foosball :Well hello there, test!" do
285
+ assert_equal 1, @msg[:before_last]
286
+ assert_equal 1, @msg[:callback]
287
+ assert_equal 4, @msg[:after_msg]
288
+ end
289
+ end
290
+
291
+ # Verify that *_any filters are run appropriately and in order - before filter should be the
292
+ # first filter run, and after filter should be the last
293
+ def test_any_filters_and_filter_order
294
+ step = 1
295
+
296
+ # First we should hit the incoming "any" before filter
297
+ @yail.hearing_any do |e|
298
+ if e.type == :incoming_msg
299
+ assert_equal 1, step
300
+ step += 1
301
+ end
302
+ end
303
+
304
+ # Second, incoming message before filter
305
+ @yail.hearing_msg { |e| assert_equal 2, step; step += 1 }
306
+
307
+ # Third, the callback
308
+ @yail.on_msg { |e| assert_equal 3, step; step += 1 }
309
+
310
+ # Fourth, incoming message after callback
311
+ @yail.heard_msg { |e| assert_equal 4, step; step += 1 }
312
+
313
+ # Last, incoming "any" after filter
314
+ @yail.heard_any do |e|
315
+ if e.type == :incoming_msg
316
+ assert_equal 5, step
317
+ end
318
+ end
319
+
320
+ @yail.start_listening
321
+
322
+ wait_for_irc
323
+
324
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #foosball :Well hello there, test!" do
325
+ # We should have no handlers hit more than once - step should be 5 still
326
+ assert_equal 5, step
327
+ end
328
+ end
329
+
330
+ def test_conditional_filters
331
+ @msg = Hash.new(0)
332
+
333
+ # Conditional filters are really ugly when passing a block instead of a method, so let's
334
+ # at least try to make this look decent
335
+ hearing_food = lambda { @msg[:food] += 1 }
336
+ not_hearing_bad = lambda { @msg[:not_bad] += 1 }
337
+ heard_nothing = lambda { @msg[:nothing] += 1 }
338
+
339
+ # If the message looks like "food", the handler will be hit
340
+ @yail.hearing_msg(hearing_food, :if => lambda {|e| e.message =~ /food/})
341
+
342
+ # Unless the message looks like "bad", the handler will be hit
343
+ @yail.hearing_msg(not_hearing_bad, :unless => lambda {|e| e.message =~ /bad/})
344
+
345
+ # Verify after-filter is called, and hash conditions are respected
346
+ @yail.heard_msg(heard_nothing, :if => {:message => ""})
347
+
348
+ # Verify multiple hash conditions are anded - do it with a block just so we can see how
349
+ # wonderfully bad it looks
350
+ @yail.heard_msg(:if => {:message => "bah", :pm? => true}) do
351
+ @msg[:heard_2] += 1
352
+ end
353
+
354
+ # GO GO GO
355
+ @yail.start_listening
356
+
357
+ wait_for_irc
358
+
359
+ # Food
360
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #foosball :this food is bad" do
361
+ assert_equal({:food => 1}, @msg)
362
+ end
363
+
364
+ # Good food!
365
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #foosball :this food is good!" do
366
+ assert_equal({:food => 1, :not_bad => 1}, @msg)
367
+ end
368
+
369
+ # Good drink
370
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #foosball :this drink is good!" do
371
+ assert_equal({:not_bad => 1}, @msg)
372
+ end
373
+
374
+ # Nothing, but not bad
375
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #foosball :" do
376
+ assert_equal({:not_bad => 1, :nothing => 1}, @msg)
377
+ end
378
+
379
+ # Private message for multiple hash test
380
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #{@yail.me} :bah" do
381
+ assert_equal({:not_bad => 1, :heard_2 => 1}, @msg)
382
+ end
383
+
384
+ # Private message without bah does *not* fire off multiple hash handler
385
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #{@yail.me} :hey there buddy" do
386
+ assert_equal({:not_bad => 1}, @msg)
387
+ end
388
+
389
+ # "bah" without being pm doesn't fire off handler, either
390
+ mock_message ":Nerdmaster!nerd@nerdbucket.com PRIVMSG #foosball :bah" do
391
+ assert_equal({:not_bad => 1}, @msg)
392
+ end
393
+ end
244
394
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-yail
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1
5
- prerelease: false
4
+ hash: 15
5
+ prerelease:
6
6
  segments:
7
7
  - 1
8
- - 5
9
- - 1
10
- version: 1.5.1
8
+ - 6
9
+ - 0
10
+ version: 1.6.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jeremy Echols
@@ -15,8 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-08-18 00:00:00 -04:00
19
- default_executable:
18
+ date: 2012-11-08 00:00:00 Z
20
19
  dependencies: []
21
20
 
22
21
  description: |-
@@ -35,37 +34,37 @@ extra_rdoc_files: []
35
34
 
36
35
  files:
37
36
  - examples/simple/dumbbot.rb
38
- - examples/logger/logger_bot.rb
39
- - examples/logger/default.yml
40
37
  - examples/logger/run.rb
41
- - examples/loudbot/loudbot.rb
42
- - examples/loudbot/bot_runner.rb
43
- - examples/loudbot/shuffle.rb
38
+ - examples/logger/default.yml
39
+ - examples/logger/logger_bot.rb
44
40
  - lib/net/yail.rb
41
+ - lib/net/yail/irc_bot.rb
42
+ - lib/net/yail/yail-version.rb
43
+ - lib/net/yail/message_parser.rb
45
44
  - lib/net/yail/magic_events.rb
45
+ - lib/net/yail/legacy_events.rb
46
+ - lib/net/yail/report_events.rb
47
+ - lib/net/yail/output_api.rb
46
48
  - lib/net/yail/eventmap.yml
47
- - lib/net/yail/IRCBot.rb
48
- - lib/net/yail/message_parser.rb
49
49
  - lib/net/yail/default_events.rb
50
- - lib/net/yail/output_api.rb
50
+ - lib/net/yail/dispatch.rb
51
+ - lib/net/yail/IRCBot.rb
51
52
  - lib/net/yail/event.rb
52
- - lib/net/yail/yail-version.rb
53
- - lib/net/yail/irc_bot.rb
54
- - lib/net/yail/legacy_events.rb
55
- - lib/net/yail/report_events.rb
56
- - tests/net_yail.rb
57
- - tests/tc_message_parser.rb
53
+ - lib/net/yail/handler.rb
58
54
  - tests/tc_event.rb
59
55
  - tests/tc_yail.rb
56
+ - tests/tc_message_parser.rb
60
57
  - tests/mock_irc.rb
61
- has_rdoc: true
58
+ - tests/net_yail.rb
62
59
  homepage: http://ruby-irc-yail.nerdbucket.com/
63
60
  licenses: []
64
61
 
65
62
  post_install_message:
66
63
  rdoc_options:
67
- - --main
64
+ - -m
68
65
  - Net::YAIL
66
+ - -f
67
+ - sdoc
69
68
  require_paths:
70
69
  - lib
71
70
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -89,13 +88,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
88
  requirements: []
90
89
 
91
90
  rubyforge_project: net-yail
92
- rubygems_version: 1.3.7
91
+ rubygems_version: 1.8.24
93
92
  signing_key:
94
93
  specification_version: 3
95
94
  summary: "Yet Another IRC Library: wrapper for IRC communications in Ruby."
96
95
  test_files:
97
- - tests/net_yail.rb
98
- - tests/tc_message_parser.rb
99
96
  - tests/tc_event.rb
100
97
  - tests/tc_yail.rb
98
+ - tests/tc_message_parser.rb
101
99
  - tests/mock_irc.rb
100
+ - tests/net_yail.rb
@@ -1,114 +0,0 @@
1
- # This is a demonstration of net-yail just to see what a "real" bot could do without much work.
2
- # Chances are good that you'll want to put things in classes and/or modules rather than go this
3
- # route, so take this example with a grain of salt.
4
- #
5
- # Yes, this is a very simple copy of an existing "loudbot" implementation, but using YAIL to
6
- # demonstrate the INCREDIBLE POWER THAT IS Net::YAIL. Plus, plagiarism is a subset of the cool
7
- # crime of stealing.
8
- #
9
- # Example of running this thing:
10
- # ruby bot_runner.rb --network irc.somewhere.org --channel "#bots"
11
-
12
- require 'rubygems'
13
-
14
- # Want a specific version of net/yail? Try uncommenting this:
15
- # gem 'net-yail', '1.x.y'
16
-
17
- require 'net/yail'
18
- require 'getopt/long'
19
-
20
- # Hacks Array#shuffle and Array#shuffle! for people not using the latest ruby
21
- require 'shuffle'
22
-
23
- # Pulls in all of loudbot's methods - filter/callback handlers for IRC events
24
- require 'loudbot'
25
-
26
- # User specifies network, channel and nick
27
- opt = Getopt::Long.getopts(
28
- ['--network', Getopt::REQUIRED],
29
- ['--channel', Getopt::REQUIRED],
30
- ['--nick', Getopt::REQUIRED],
31
- ['--debug', Getopt::BOOLEAN]
32
- )
33
-
34
- # Create bot object
35
- @irc = Net::YAIL.new(
36
- :address => opt['network'],
37
- :username => 'Frakking Bot',
38
- :realname => 'John Botfrakker',
39
- :nicknames => [opt['nick'] || "SUPERLOUD"]
40
- )
41
-
42
- # Loud messages can be newline-separated strings in louds.txt or an array or hash serialized in
43
- # louds.yml. If messages are an array, we convert all of them to hash keys with a score of 1.
44
- @messages = FileTest.exist?("louds.yml") ? YAML.load_file("louds.yml") :
45
- FileTest.exist?("louds.txt") ? IO.readlines("louds.txt") :
46
- {"ROCK ON WITH SUPERLOUD" => 1}
47
- if Array === @messages
48
- dupes = @messages.dup
49
- @messages = {}
50
- dupes.each {|string| @messages[string.strip] = 1}
51
- end
52
-
53
- @random_messages = @messages.keys.shuffle
54
- @last_message = nil
55
- @dirty_messages = false
56
-
57
- # If --debug is passed on the command line, we spew lots of filth at the user
58
- @irc.log.level = Logger::DEBUG if opt['debug']
59
-
60
- #####
61
- #
62
- # To learn the YAIL, begin below with attentiveness to commented wording
63
- #
64
- #####
65
-
66
- # This is a filter. Because it's past-tense ("heard"), it runs after the server's welcome message
67
- # has been read - i.e., after any before-filters and the main hanler happen.
68
- @irc.heard_welcome { |e| @irc.join(opt['channel']) if opt['channel'] }
69
-
70
- # on_xxx means it's a callback for an incoming event. Callbacks run after before-filters, and
71
- # replaces any existing incoming invite callback. YAIL has very few built-in callbacks, so
72
- # this is a safe operation.
73
- @irc.on_invite { |e| @irc.join(e.channel) }
74
-
75
- # This is just another callback, using the do/end block form. We auto-message the channel on join.
76
- @irc.on_join do |e|
77
- @irc.msg(e.channel, "WHATS WRONG WITH BEING SEXY") if e.nick == @irc.me
78
- end
79
-
80
- # You should *never* override the on_ping callback unless you handle the PONG manually!!
81
- # Filters, however, are perfectly fine.
82
- #
83
- # Here we're using the ping filter to actually do the serialization of our messages hash. Since
84
- # we know pings are regular, this is kind of a hack to serialize every few minutes.
85
- @irc.heard_ping do
86
- unless @dirty_messages
87
- File.open("louds.yml", "w") {|f| f.puts @messages.to_yaml}
88
- @dirty_messages = false
89
- end
90
- end
91
-
92
- # This is a before-filter - using the present tense means it's a before-filter, and using a tense
93
- # of "hear" means it's for incoming messages (as opposed to "saying" and "said", where we'd filter
94
- # our outgoing messages). Here we intercept all potential commands and send them to a method.
95
- @irc.hearing_msg {|e| do_command($1, e) if e.message =~ /^!(.*)$/ }
96
-
97
- # Another filter, but in-line this time - we intercept messages directly to the bot. The call to
98
- # +handled!+ tells the event not to run any more filters or the main callback.
99
- @irc.hearing_msg do |e|
100
- if e.message =~ /^#{@irc.me}/
101
- random_message(e.channel)
102
- e.handled!
103
- end
104
- end
105
-
106
- # This is our primary message callback. We know our filters have caught people talking to us and
107
- # any command-style messages, so we don't need to worry about those situations here. The decision
108
- # to make this the primary callback is pretty arbitrary - do what makes the most sense to you.
109
- #
110
- # Note that this is a proc-based filter - we handle the message entirely in incoming_message.
111
- @irc.on_msg self.method(:incoming_message)
112
-
113
- # Start the bot - the bang (!) calls the version of start_listening that runs an endless loop
114
- @irc.start_listening!
@@ -1,87 +0,0 @@
1
- # Stores a LOUD message into the hash and responds.
2
- def it_was_loud(message, channel)
3
- @irc.log.debug "IT WAS LOUD! #{message.inspect}"
4
-
5
- @messages[message] ||= 1
6
- random_message(channel)
7
- end
8
-
9
- # This is our main message handler.
10
- #
11
- # We store and respond if messages meet the following criteria:
12
- # * It is long (11 characters or more)
13
- # * It has at least one space
14
- # * It has no lowercase letters
15
- # * At least 60% of the characters are uppercase letters
16
- def incoming_message(e)
17
- # We don't respond to "private" messages
18
- return if e.pm?
19
-
20
- text = e.message
21
-
22
- return if text =~ /[a-z]/
23
-
24
- # Count various exciting things
25
- len = text.length
26
- uppercase_count = text.scan(/[A-Z]/).length
27
- space_count = text.scan(/\s/).length
28
-
29
- if len >= 11 && uppercase_count >= (len * 0.60) && space_count >= 1
30
- it_was_loud(e.message, e.channel)
31
- end
32
- end
33
-
34
- # Pulls a random message from our messages array and sends it to the given channel. Reshuffles
35
- # the main array if the randomized array is empty.
36
- def random_message(channel)
37
- @random_messages = @messages.keys.shuffle if @random_messages.empty?
38
- @last_message = @random_messages.pop
39
- @irc.msg(channel, @last_message)
40
- end
41
-
42
- # Just keepin' the plagiarism alive, man. At least in my version, size is always based on requester.
43
- def send_dong(channel, user_hash)
44
- old_seed = srand(user_hash)
45
- @irc.msg(channel, "8" + ('=' * (rand(20).to_i + 8)) + "D")
46
- srand(old_seed)
47
- end
48
-
49
- # Adds +value+ to the score of the last message, if there was one. If the score goes too low, we
50
- # remove that message forever.
51
- def vote(value)
52
- return unless @last_message
53
-
54
- @messages[@last_message] += value
55
- if @messages[@last_message] <= -1
56
- @last_message = nil
57
- @messages.delete(@last_message)
58
- end
59
- @dirty_messages = true
60
- end
61
-
62
- # Reports the last message's score
63
- def score(channel)
64
- if !@last_message
65
- @irc.msg(channel, "NO LAST MESSAGE OR IT WAS DELETED BY !DOWNVOTE")
66
- return
67
- end
68
-
69
- @irc.msg(channel, "#{@last_message}: #{@messages[@last_message]}")
70
- end
71
-
72
- # Handles a command (string begins with ! - to keep with the pattern, I'm making our loudbot only
73
- # respond to loud commands)
74
- def do_command(command, e)
75
- case command
76
- when "DONGME" then send_dong(e.channel, e.msg.user.hash + e.msg.host.hash)
77
- when "UPVOTE" then vote(1)
78
- when "DOWNVOTE" then vote(-1)
79
- when "SCORE" then score(e.channel)
80
- when "HELP" then @irc.msg(e.channel, "I HAVE COMMANDS AND THEY ARE !DONGME !UPVOTE !DOWNVOTE !SCORE AND !HELP")
81
- end
82
-
83
- # Here we're saying that we don't want any other handling run - no filters, no handler. For
84
- # commands, I put this here because I know I don't want any other handlers having to deal with
85
- # strings beginning with a bang.
86
- e.handled!
87
- end
@@ -1,17 +0,0 @@
1
- # Dynamically adds Array shuffling as described in http://www.ruby-forum.com/topic/163649
2
- class Array
3
- # Shuffle the array
4
- def shuffle!
5
- n = length
6
- for i in 0...n
7
- r = Kernel.rand(n-i)+i
8
- self[r], self[i] = self[i], self[r]
9
- end
10
- self
11
- end
12
-
13
- # Return a shuffled copy of the array
14
- def shuffle
15
- dup.shuffle!
16
- end
17
- end