net-yail 1.5.1 → 1.6.0

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/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