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