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 +44 -54
- data/lib/net/yail/dispatch.rb +47 -0
- data/lib/net/yail/event.rb +14 -3
- data/lib/net/yail/handler.rb +38 -0
- data/lib/net/yail/legacy_events.rb +5 -18
- data/lib/net/yail/magic_events.rb +5 -0
- data/lib/net/yail/output_api.rb +9 -0
- data/lib/net/yail/yail-version.rb +1 -1
- data/tests/tc_yail.rb +150 -0
- metadata +25 -26
- data/examples/loudbot/bot_runner.rb +0 -114
- data/examples/loudbot/loudbot.rb +0 -87
- data/examples/loudbot/shuffle.rb +0 -17
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,
|
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
|
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
|
-
#
|
62
|
-
#
|
63
|
-
# all
|
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
|
data/lib/net/yail/event.rb
CHANGED
@@ -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
|
48
|
-
return
|
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
|
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
|
-
|
16
|
-
|
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
|
data/lib/net/yail/output_api.rb
CHANGED
@@ -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
|
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:
|
5
|
-
prerelease:
|
4
|
+
hash: 15
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 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:
|
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/
|
42
|
-
- examples/
|
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/
|
50
|
+
- lib/net/yail/dispatch.rb
|
51
|
+
- lib/net/yail/IRCBot.rb
|
51
52
|
- lib/net/yail/event.rb
|
52
|
-
- lib/net/yail/
|
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
|
-
|
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
|
-
-
|
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.
|
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!
|
data/examples/loudbot/loudbot.rb
DELETED
@@ -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
|
data/examples/loudbot/shuffle.rb
DELETED
@@ -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
|