cinch 1.1.3 → 2.0.0.pre.1
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/LICENSE +1 -0
- data/README.md +3 -3
- data/docs/bot_options.md +435 -0
- data/docs/changes.md +440 -0
- data/docs/common_mistakes.md +35 -0
- data/docs/common_tasks.md +47 -0
- data/docs/encodings.md +67 -0
- data/docs/events.md +272 -0
- data/docs/logging.md +5 -0
- data/docs/migrating.md +267 -0
- data/docs/readme.md +18 -0
- data/examples/plugins/custom_prefix.rb +1 -1
- data/examples/plugins/dice_roll.rb +38 -0
- data/examples/plugins/lambdas.rb +1 -1
- data/examples/plugins/memo.rb +16 -10
- data/examples/plugins/url_shorten.rb +1 -0
- data/lib/cinch.rb +5 -60
- data/lib/cinch/ban.rb +13 -7
- data/lib/cinch/bot.rb +228 -403
- data/lib/cinch/{cache_manager.rb → cached_list.rb} +5 -1
- data/lib/cinch/callback.rb +3 -0
- data/lib/cinch/channel.rb +119 -195
- data/lib/cinch/{channel_manager.rb → channel_list.rb} +6 -3
- data/lib/cinch/configuration.rb +73 -0
- data/lib/cinch/configuration/bot.rb +47 -0
- data/lib/cinch/configuration/dcc.rb +16 -0
- data/lib/cinch/configuration/plugins.rb +41 -0
- data/lib/cinch/configuration/sasl.rb +17 -0
- data/lib/cinch/configuration/ssl.rb +19 -0
- data/lib/cinch/configuration/storage.rb +37 -0
- data/lib/cinch/configuration/timeouts.rb +14 -0
- data/lib/cinch/constants.rb +531 -369
- data/lib/cinch/dcc.rb +12 -0
- data/lib/cinch/dcc/dccable_object.rb +37 -0
- data/lib/cinch/dcc/incoming.rb +1 -0
- data/lib/cinch/dcc/incoming/send.rb +131 -0
- data/lib/cinch/dcc/outgoing.rb +1 -0
- data/lib/cinch/dcc/outgoing/send.rb +115 -0
- data/lib/cinch/exceptions.rb +8 -1
- data/lib/cinch/formatting.rb +106 -0
- data/lib/cinch/handler.rb +104 -0
- data/lib/cinch/handler_list.rb +86 -0
- data/lib/cinch/helpers.rb +167 -10
- data/lib/cinch/irc.rb +525 -110
- data/lib/cinch/isupport.rb +11 -9
- data/lib/cinch/logger.rb +168 -0
- data/lib/cinch/logger/formatted_logger.rb +72 -55
- data/lib/cinch/logger/zcbot_logger.rb +9 -24
- data/lib/cinch/logger_list.rb +62 -0
- data/lib/cinch/mask.rb +19 -10
- data/lib/cinch/message.rb +94 -28
- data/lib/cinch/message_queue.rb +70 -28
- data/lib/cinch/mode_parser.rb +6 -1
- data/lib/cinch/network.rb +104 -0
- data/lib/cinch/{rubyext/queue.rb → open_ended_queue.rb} +8 -1
- data/lib/cinch/pattern.rb +24 -4
- data/lib/cinch/plugin.rb +352 -177
- data/lib/cinch/plugin_list.rb +35 -0
- data/lib/cinch/rubyext/float.rb +3 -0
- data/lib/cinch/rubyext/module.rb +7 -0
- data/lib/cinch/rubyext/string.rb +9 -0
- data/lib/cinch/sasl.rb +34 -0
- data/lib/cinch/sasl/dh_blowfish.rb +71 -0
- data/lib/cinch/sasl/diffie_hellman.rb +47 -0
- data/lib/cinch/sasl/mechanism.rb +6 -0
- data/lib/cinch/sasl/plain.rb +26 -0
- data/lib/cinch/storage.rb +62 -0
- data/lib/cinch/storage/null.rb +12 -0
- data/lib/cinch/storage/yaml.rb +96 -0
- data/lib/cinch/syncable.rb +13 -1
- data/lib/cinch/target.rb +144 -0
- data/lib/cinch/timer.rb +145 -0
- data/lib/cinch/user.rb +169 -225
- data/lib/cinch/{user_manager.rb → user_list.rb} +7 -2
- data/lib/cinch/utilities/deprecation.rb +12 -0
- data/lib/cinch/utilities/encoding.rb +54 -0
- data/lib/cinch/utilities/kernel.rb +13 -0
- data/lib/cinch/utilities/string.rb +13 -0
- data/lib/cinch/version.rb +4 -0
- metadata +88 -47
- data/lib/cinch/logger/logger.rb +0 -44
- data/lib/cinch/logger/null_logger.rb +0 -18
- data/lib/cinch/rubyext/infinity.rb +0 -1
@@ -0,0 +1,104 @@
|
|
1
|
+
module Cinch
|
2
|
+
# This class allows querying the IRC network for its name and used
|
3
|
+
# server software as well as certain non-standard behaviour.
|
4
|
+
#
|
5
|
+
# @since 2.0.0
|
6
|
+
class Network
|
7
|
+
# @return [Symbol] The name of the network. `:unknown` if the
|
8
|
+
# network couldn't be detected.
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
attr_writer :name
|
13
|
+
|
14
|
+
# @return [Symbol] The server software used by the network.
|
15
|
+
# `:unknown` if the software couldn't be detected.
|
16
|
+
attr_reader :ircd
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
attr_writer :ircd
|
20
|
+
|
21
|
+
# @return [Array<Symbol>] All client capabilities supported by the
|
22
|
+
# network.
|
23
|
+
attr_reader :capabilities
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
attr_writer :capabilities
|
27
|
+
|
28
|
+
# @param [Symbol] name
|
29
|
+
# @param [Symbol] ircd
|
30
|
+
# @api private
|
31
|
+
# @note The user should not create instances of this class but use
|
32
|
+
# {IRC#network} instead.
|
33
|
+
def initialize(name, ircd)
|
34
|
+
@name = name
|
35
|
+
@ircd = ircd
|
36
|
+
@capabilities = []
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String, nil] The mode used for getting the list of
|
40
|
+
# channel owners, if any
|
41
|
+
def owner_list_mode
|
42
|
+
return "q" if @ircd == :unreal
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String, nil] The mode used for getting the list of
|
46
|
+
# channel quiets, if any
|
47
|
+
def quiet_list_mode
|
48
|
+
return "q" if @ircd == :"ircd-seven"
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Boolean] Does WHOIS only support one argument?
|
52
|
+
def whois_only_one_argument?
|
53
|
+
@name == :jtv
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Boolean] True if connected to NgameTV
|
57
|
+
def ngametv?
|
58
|
+
@name == :ngametv
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Boolean] True if connected to JTV
|
62
|
+
def jtv?
|
63
|
+
@name == :jtv
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Boolean] True if we do not know which network we are
|
67
|
+
# connected to
|
68
|
+
def unknown_network?
|
69
|
+
@name == :unknown
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Boolean] True if we do not know which software the
|
73
|
+
# server is running
|
74
|
+
def unknown_ircd?
|
75
|
+
@ircd == :unknown
|
76
|
+
end
|
77
|
+
|
78
|
+
# Note for the default_* methods: Always make sure to return a
|
79
|
+
# value for when no network/ircd was detected so that MessageQueue
|
80
|
+
# doesn't break.
|
81
|
+
|
82
|
+
# @return [Number] The `messages per second` value that best suits
|
83
|
+
# the current network
|
84
|
+
def default_messages_per_second
|
85
|
+
case @network
|
86
|
+
when :freenode
|
87
|
+
0.7
|
88
|
+
else
|
89
|
+
0.5
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Number] The `server queue size` value that best suits
|
94
|
+
# the current network
|
95
|
+
def default_server_queue_size
|
96
|
+
case @network
|
97
|
+
when :quakenet
|
98
|
+
40
|
99
|
+
else
|
100
|
+
10
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -1,5 +1,12 @@
|
|
1
1
|
require "thread"
|
2
|
-
|
2
|
+
|
3
|
+
# Like Ruby's Queue class, but allowing both pushing and unshifting
|
4
|
+
# objects.
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class OpenEndedQueue < Queue
|
8
|
+
# @param [Object] obj
|
9
|
+
# @return [void]
|
3
10
|
def unshift(obj)
|
4
11
|
t = nil
|
5
12
|
@mutex.synchronize{
|
data/lib/cinch/pattern.rb
CHANGED
@@ -1,16 +1,25 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
module Cinch
|
3
3
|
# @api private
|
4
|
+
# @since 1.1.0
|
4
5
|
class Pattern
|
5
6
|
# @param [String, Regexp, NilClass, Proc, #to_s] obj The object to
|
6
7
|
# convert to a regexp
|
7
8
|
# @return [Regexp, nil]
|
8
|
-
def self.obj_to_r(obj)
|
9
|
+
def self.obj_to_r(obj, anchor = nil)
|
9
10
|
case obj
|
10
11
|
when Regexp, NilClass
|
11
12
|
return obj
|
12
13
|
else
|
13
|
-
|
14
|
+
escaped = Regexp.escape(obj.to_s)
|
15
|
+
case anchor
|
16
|
+
when :start
|
17
|
+
return Regexp.new("^" + escaped)
|
18
|
+
when :end
|
19
|
+
return Regexp.new(escaped + "$")
|
20
|
+
when nil
|
21
|
+
return Regexp.new(Regexp.escape(obj.to_s))
|
22
|
+
end
|
14
23
|
end
|
15
24
|
end
|
16
25
|
|
@@ -22,6 +31,15 @@ module Cinch
|
|
22
31
|
end
|
23
32
|
end
|
24
33
|
|
34
|
+
def self.generate(type, argument)
|
35
|
+
case type
|
36
|
+
when :ctcp
|
37
|
+
Pattern.new(/^/, /#{Regexp.escape(argument.to_s)}(?:$| .+)/, nil)
|
38
|
+
else
|
39
|
+
raise ArgumentError, "Unsupported type: #{type.inspect}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
25
43
|
attr_reader :prefix
|
26
44
|
attr_reader :suffix
|
27
45
|
attr_reader :pattern
|
@@ -30,14 +48,16 @@ module Cinch
|
|
30
48
|
end
|
31
49
|
|
32
50
|
def to_r(msg = nil)
|
33
|
-
prefix = Pattern.obj_to_r(Pattern.resolve_proc(@prefix, msg))
|
34
|
-
suffix = Pattern.obj_to_r(Pattern.resolve_proc(@suffix, msg))
|
35
51
|
pattern = Pattern.resolve_proc(@pattern, msg)
|
36
52
|
|
37
53
|
case pattern
|
38
54
|
when Regexp, NilClass
|
55
|
+
prefix = Pattern.obj_to_r(Pattern.resolve_proc(@prefix, msg), :start)
|
56
|
+
suffix = Pattern.obj_to_r(Pattern.resolve_proc(@suffix, msg), :end)
|
39
57
|
/#{prefix}#{pattern}#{suffix}/
|
40
58
|
else
|
59
|
+
prefix = Pattern.obj_to_r(Pattern.resolve_proc(@prefix, msg))
|
60
|
+
suffix = Pattern.obj_to_r(Pattern.resolve_proc(@suffix, msg))
|
41
61
|
/^#{prefix}#{Pattern.obj_to_r(pattern)}#{suffix}$/
|
42
62
|
end
|
43
63
|
end
|
data/lib/cinch/plugin.rb
CHANGED
@@ -1,17 +1,167 @@
|
|
1
|
+
# TODO more details in "message dropped" debug output
|
1
2
|
module Cinch
|
3
|
+
# This class represents the core of the plugin functionality of
|
4
|
+
# Cinch. It provides both the methods for users to write their own
|
5
|
+
# plugins as well as for the Cinch framework to use them.
|
6
|
+
#
|
7
|
+
# The {ClassMethods} module, which will get included automatically
|
8
|
+
# in all classes that include `Cinch::Plugin`, includes all class
|
9
|
+
# methods that the user will use for creating plugins.
|
10
|
+
#
|
11
|
+
# Most of the instance methods are for use by the Cinch framework
|
12
|
+
# and part of the private API, but some will also be used by plugin
|
13
|
+
# authors, mainly {#config}, {#synchronize}, {#storage} and {#bot}.
|
2
14
|
module Plugin
|
3
15
|
include Helpers
|
4
16
|
|
17
|
+
# The ClassMethods module includes all methods that the user will
|
18
|
+
# need for creating plugins for the Cinch framework: Setting
|
19
|
+
# options (see {#set} and the attributes) as well as methods for
|
20
|
+
# configuring the actual pattern matching ({#match}, {#listen_to}).
|
21
|
+
#
|
22
|
+
# Furthermore, the attributes allow for programmatically
|
23
|
+
# inspecting plugins.
|
24
|
+
#
|
25
|
+
# @attr plugin_name
|
5
26
|
module ClassMethods
|
6
|
-
# @
|
7
|
-
|
8
|
-
|
27
|
+
# @return [Hash<Symbol<:pre, :post> => Array<Hook>>] All hooks
|
28
|
+
attr_reader :hooks
|
29
|
+
|
30
|
+
# @return [Array<Symbol<:message, :channel, :private>>] The list of events to react on
|
31
|
+
attr_accessor :reacting_on
|
32
|
+
|
33
|
+
# The name of the plugin.
|
34
|
+
# @overload plugin_name
|
35
|
+
# @return [String, nil]
|
36
|
+
# @overload plugin_name=(new_name)
|
37
|
+
# @param [String, nil] new_name
|
38
|
+
# @return [String]
|
39
|
+
# @return [String, nil] The name of the plugin
|
40
|
+
attr_reader :plugin_name
|
41
|
+
|
42
|
+
# @return [String]
|
43
|
+
def plugin_name=(new_name)
|
44
|
+
if new_name.nil? && self.name
|
45
|
+
@plugin_name = self.name.split("::").last.downcase
|
46
|
+
else
|
47
|
+
@plugin_name = new_name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Array<Matcher>] All matchers
|
52
|
+
attr_reader :matchers
|
53
|
+
|
54
|
+
# @return [Array<Listener>] All listeners
|
55
|
+
attr_reader :listeners
|
56
|
+
|
57
|
+
# @return [Array<Timer>] All timers
|
58
|
+
attr_reader :timers
|
59
|
+
|
60
|
+
# @return [Array<String>] All CTCPs
|
61
|
+
attr_reader :ctcps
|
62
|
+
|
63
|
+
# @return [String, nil] The help message
|
64
|
+
attr_accessor :help
|
65
|
+
|
66
|
+
# @return [String, Regexp, Proc] The prefix
|
67
|
+
attr_accessor :prefix
|
68
|
+
|
69
|
+
# @return [String, Regexp, Proc] The suffix
|
70
|
+
attr_accessor :suffix
|
71
|
+
|
72
|
+
# @return [Array<Symbol>] Required plugin options
|
73
|
+
attr_accessor :required_options
|
74
|
+
|
75
|
+
# Represents a Matcher as created by {#match}.
|
76
|
+
#
|
77
|
+
# @attr [String, Regexp, Proc] pattern
|
78
|
+
# @attr [Boolean] use_prefix
|
79
|
+
# @attr [Boolean] use_suffix
|
80
|
+
# @attr [Symbol] method
|
81
|
+
# @attr [Symbol] group
|
82
|
+
Matcher = Struct.new(:pattern, :use_prefix, :use_suffix, :method, :group, :prefix, :suffix, :reacting_on)
|
83
|
+
|
84
|
+
# Represents a Listener as created by {#listen_to}.
|
85
|
+
#
|
86
|
+
# @attr [Symbol] event
|
87
|
+
# @attr [Symbol] method
|
9
88
|
Listener = Struct.new(:event, :method)
|
10
|
-
|
11
|
-
Timer
|
12
|
-
#
|
89
|
+
|
90
|
+
# Represents a Timer as created by {#timer}.
|
91
|
+
#
|
92
|
+
# @note This is not the same as a {Cinch::Timer} object, which
|
93
|
+
# will allow controlling and inspecting actually running
|
94
|
+
# timers. This class only describes a Timer that still has to
|
95
|
+
# be created.
|
96
|
+
#
|
97
|
+
# @attr [Number] interval
|
98
|
+
# @attr [Symbol] method
|
99
|
+
# @attr [Hash] options
|
100
|
+
# @attr [Boolean] registered
|
101
|
+
Timer = Struct.new(:interval, :options, :registered)
|
102
|
+
|
103
|
+
# Represents a Hook as created by {#hook}.
|
104
|
+
#
|
105
|
+
# @attr [Symbol] type
|
106
|
+
# @attr [Array<Symbol>] for
|
107
|
+
# @attr [Symbol] method
|
13
108
|
Hook = Struct.new(:type, :for, :method)
|
14
109
|
|
110
|
+
# @api private
|
111
|
+
def self.extended(by)
|
112
|
+
by.instance_exec do
|
113
|
+
@matchers = []
|
114
|
+
@ctcps = []
|
115
|
+
@listeners = []
|
116
|
+
@timers = []
|
117
|
+
@help = nil
|
118
|
+
@hooks = Hash.new{|h, k| h[k] = []}
|
119
|
+
@prefix = nil
|
120
|
+
@suffix = nil
|
121
|
+
@reacting_on = :message
|
122
|
+
@required_options = []
|
123
|
+
self.plugin_name = nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Set options.
|
128
|
+
#
|
129
|
+
# Available options:
|
130
|
+
#
|
131
|
+
# - {#help}
|
132
|
+
# - {#plugin_name}
|
133
|
+
# - {#prefix}
|
134
|
+
# - {#reacting_on}
|
135
|
+
# - {#required_options}
|
136
|
+
# - {#suffix}
|
137
|
+
#
|
138
|
+
# @overload set(key, value)
|
139
|
+
# @param [Symbol] key The option's name
|
140
|
+
# @param [Object] value
|
141
|
+
# @return [void]
|
142
|
+
# @overload set(options)
|
143
|
+
# @param [Hash<Symbol => Object>] options The options, as key => value associations
|
144
|
+
# @return [void]
|
145
|
+
# @example
|
146
|
+
# set(:help => "the help message",
|
147
|
+
# :prefix => "^")
|
148
|
+
# @return [void]
|
149
|
+
# @since 2.0.0
|
150
|
+
def set(*args)
|
151
|
+
case args.size
|
152
|
+
when 1
|
153
|
+
# {:key => value, ...}
|
154
|
+
args.first.each do |key, value|
|
155
|
+
self.send("#{key}=", value)
|
156
|
+
end
|
157
|
+
when 2
|
158
|
+
# key, value
|
159
|
+
self.send("#{args.first}=", args.last)
|
160
|
+
else
|
161
|
+
raise ArgumentError # TODO proper error message
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
15
165
|
# Set a match pattern.
|
16
166
|
#
|
17
167
|
# @param [Regexp, String] pattern A pattern
|
@@ -19,11 +169,19 @@ module Cinch
|
|
19
169
|
# @option options [Boolean] :use_prefix (true) If true, the
|
20
170
|
# plugin prefix will automatically be prepended to the
|
21
171
|
# pattern.
|
22
|
-
# @
|
172
|
+
# @option options [Boolean] :use_suffix (true) If true, the
|
173
|
+
# plugin suffix will automatically be appended to the
|
174
|
+
# pattern.
|
175
|
+
# @option options [Symbol] :group (nil) The group the match belongs to.
|
176
|
+
# @return [Matcher]
|
177
|
+
# @todo Document match/listener grouping
|
178
|
+
# @todo document new options
|
23
179
|
def match(pattern, options = {})
|
24
|
-
options = {:use_prefix => true, :use_suffix => true, :method => :execute}.merge(options)
|
25
|
-
|
26
|
-
@
|
180
|
+
options = {:use_prefix => true, :use_suffix => true, :method => :execute, :group => nil, :prefix => nil, :suffix => nil, :reacting_on => nil}.merge(options)
|
181
|
+
matcher = Matcher.new(pattern, *options.values_at(:use_prefix, :use_suffix, :method, :group, :prefix, :suffix, :reacting_on))
|
182
|
+
@matchers << matcher
|
183
|
+
|
184
|
+
matcher
|
27
185
|
end
|
28
186
|
|
29
187
|
# Events to listen to.
|
@@ -37,72 +195,34 @@ module Cinch
|
|
37
195
|
# - :message (both channel and private messages)
|
38
196
|
# - :error (IRC errors)
|
39
197
|
# - :ctcp (ctcp requests)
|
198
|
+
# - :action (actions, aka /me)
|
40
199
|
#
|
41
200
|
# @param [Hash] options
|
42
201
|
# @option options [Symbol] :method (:listen) The method to
|
43
202
|
# execute
|
44
|
-
# @return [
|
203
|
+
# @return [Array<Listener]
|
45
204
|
def listen_to(*types)
|
46
205
|
options = {:method => :listen}
|
47
206
|
if types.last.is_a?(Hash)
|
48
207
|
options.merge!(types.pop)
|
49
208
|
end
|
50
209
|
|
51
|
-
|
210
|
+
listeners = types.map {|type| Listener.new(type, options[:method])}
|
211
|
+
@listeners.concat listeners
|
52
212
|
|
53
|
-
|
54
|
-
@__cinch_listeners << Listener.new(type, options[:method])
|
55
|
-
end
|
213
|
+
listeners
|
56
214
|
end
|
57
215
|
|
216
|
+
# @version 1.1.1
|
58
217
|
def ctcp(command)
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
# Define a help message which will be returned on "<prefix>help
|
63
|
-
# <pluginname>".
|
64
|
-
#
|
65
|
-
# @param [String] message
|
66
|
-
# @return [void]
|
67
|
-
def help(message)
|
68
|
-
@__cinch_help_message = message
|
69
|
-
end
|
70
|
-
|
71
|
-
# Set the plugin prefix.
|
72
|
-
#
|
73
|
-
# @param [String] prefix
|
74
|
-
# @return [void]
|
75
|
-
def prefix(prefix = nil, &block)
|
76
|
-
raise ArgumentError if prefix.nil? && block.nil?
|
77
|
-
@__cinch_prefix = prefix || block
|
78
|
-
end
|
79
|
-
|
80
|
-
# Set the plugin suffix.
|
81
|
-
#
|
82
|
-
# @param [String] suffix
|
83
|
-
# @return [void]
|
84
|
-
def suffix(suffix = nil, &block)
|
85
|
-
raise ArgumentError if suffix.nil? && block.nil?
|
86
|
-
@__cinch_suffix = suffix || block
|
218
|
+
@ctcps << command.to_s.upcase
|
87
219
|
end
|
88
220
|
|
89
|
-
|
90
|
-
|
91
|
-
# Set which kind of messages to react on (i.e. call {#execute})
|
92
|
-
#
|
93
|
-
# @param [Symbol<:message, :channel, :private>] target React to all,
|
94
|
-
# only public or only private messages?
|
221
|
+
# Set which kind of messages to react on for matchers.
|
222
|
+
# @param [Symbol<:message, :channel, :private>] event Which event to react on
|
95
223
|
# @return [void]
|
96
|
-
def react_on(
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
# Define the plugin name.
|
101
|
-
#
|
102
|
-
# @param [String] name
|
103
|
-
# @return [void]
|
104
|
-
def plugin(name)
|
105
|
-
@__cinch_name = name
|
224
|
+
def react_on(event)
|
225
|
+
self.reacting_on = event
|
106
226
|
end
|
107
227
|
|
108
228
|
# @example
|
@@ -110,14 +230,27 @@ module Cinch
|
|
110
230
|
# def some_method
|
111
231
|
# Channel("#cinch-bots").send(Time.now.to_s)
|
112
232
|
# end
|
233
|
+
#
|
113
234
|
# @param [Number] interval Interval in seconds
|
114
|
-
# @option options [Symbol] :method (:timer) Method to call
|
115
|
-
#
|
116
|
-
# @
|
235
|
+
# @option options [Symbol] :method (:timer) Method to call (only
|
236
|
+
# if no proc is provided)
|
237
|
+
# @option options [Number] :shots (Float::INFINITY) How often
|
238
|
+
# should the timer fire?
|
239
|
+
# @option options [Boolean] :threaded (true) Call method in a
|
240
|
+
# thread?
|
241
|
+
# @option options [Boolean] :start_automatically (true) If true,
|
242
|
+
# the timer will automatically start after the bot finished
|
243
|
+
# connecting.
|
244
|
+
# @option options [Boolean] :stop_automaticall (true) If true,
|
245
|
+
# the timer will automatically stop when the bot disconnects.
|
246
|
+
# @return [Timer]
|
247
|
+
# @since 1.1.0
|
117
248
|
def timer(interval, options = {})
|
118
249
|
options = {:method => :timer, :threaded => true}.merge(options)
|
119
|
-
|
120
|
-
@
|
250
|
+
timer = Timer.new(interval, options, false)
|
251
|
+
@timers << timer
|
252
|
+
|
253
|
+
timer
|
121
254
|
end
|
122
255
|
|
123
256
|
# Defines a hook which will be run before or after a handler is
|
@@ -128,27 +261,23 @@ module Cinch
|
|
128
261
|
# @option options [Array<:match, :listen_to, :ctcp>] :for ([:match, :listen_to, :ctcp])
|
129
262
|
# Which kinds of events to run the hook for.
|
130
263
|
# @option options [Symbol] :method (true) The method to execute.
|
131
|
-
# @return [
|
264
|
+
# @return [Hook]
|
265
|
+
# @since 1.1.0
|
132
266
|
def hook(type, options = {})
|
133
267
|
options = {:for => [:match, :listen_to, :ctcp], :method => :hook}.merge(options)
|
134
|
-
|
135
|
-
|
268
|
+
hook = Hook.new(type, options[:for], options[:method])
|
269
|
+
__hooks(type) << hook
|
136
270
|
|
137
|
-
|
138
|
-
# @api private
|
139
|
-
def __plugin_name
|
140
|
-
@__cinch_name || self.name.split("::").last.downcase
|
271
|
+
hook
|
141
272
|
end
|
142
273
|
|
143
274
|
# @return [Hash]
|
144
275
|
# @api private
|
145
276
|
def __hooks(type = nil, events = nil)
|
146
|
-
@__cinch_hooks ||= Hash.new{|h,k| h[k] = []}
|
147
|
-
|
148
277
|
if type.nil?
|
149
|
-
hooks = @
|
278
|
+
hooks = @hooks
|
150
279
|
else
|
151
|
-
hooks = @
|
280
|
+
hooks = @hooks[type]
|
152
281
|
end
|
153
282
|
|
154
283
|
if events.nil?
|
@@ -162,133 +291,178 @@ module Cinch
|
|
162
291
|
end
|
163
292
|
end
|
164
293
|
|
165
|
-
# @return [
|
294
|
+
# @return [Boolean] True if processing should continue
|
166
295
|
# @api private
|
167
|
-
def
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
bot.on(listener.event, [], instance) do |message, plugin, *args|
|
173
|
-
if plugin.respond_to?(listener.method)
|
174
|
-
plugin.class.__hooks(:pre, :listen_to).each {|hook| plugin.__send__(hook.method, message)}
|
175
|
-
plugin.__send__(listener.method, message, *args)
|
176
|
-
plugin.class.__hooks(:post, :listen_to).each {|hook| plugin.__send__(hook.method, message)}
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
296
|
+
def call_hooks(type, event, instance, args)
|
297
|
+
stop = __hooks(type, event).find { |hook|
|
298
|
+
# stop after the first hook that returns false
|
299
|
+
instance.__send__(hook.method, *args) == false
|
300
|
+
}
|
180
301
|
|
181
|
-
|
182
|
-
|
183
|
-
end
|
302
|
+
!stop
|
303
|
+
end
|
184
304
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
bot.debug "[plugin] #{plugin_name}: Registering executor with pattern `#{pattern_to_register.inspect}`, reacting on `#{react_on}`"
|
196
|
-
|
197
|
-
bot.on(react_on, pattern_to_register, instance, pattern) do |message, plugin, pattern, *args|
|
198
|
-
if plugin.respond_to?(pattern.method)
|
199
|
-
method = plugin.method(pattern.method)
|
200
|
-
arity = method.arity - 1
|
201
|
-
if arity > 0
|
202
|
-
args = args[0..arity - 1]
|
203
|
-
elsif arity == 0
|
204
|
-
args = []
|
205
|
-
end
|
206
|
-
plugin.class.__hooks(:pre, :match).each {|hook| plugin.__send__(hook.method, message)}
|
207
|
-
method.call(message, *args)
|
208
|
-
plugin.class.__hooks(:post, :match).each {|hook| plugin.__send__(hook.method, message)}
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
305
|
+
# @param [Bot] bot
|
306
|
+
# @return [Array<Symbol>, nil]
|
307
|
+
# @since 2.0.0
|
308
|
+
# @api private
|
309
|
+
def check_for_missing_options(bot)
|
310
|
+
@required_options.select { |option|
|
311
|
+
!bot.config.plugins.options[self].has_key?(option)
|
312
|
+
}
|
313
|
+
end
|
314
|
+
end
|
212
315
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
316
|
+
def __register_listeners
|
317
|
+
self.class.listeners.each do |listener|
|
318
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering listener for type `#{listener.event}`"
|
319
|
+
new_handler = Handler.new(@bot, listener.event, Pattern.new(nil, //, nil)) do |message, *args|
|
320
|
+
if self.class.call_hooks(:pre, :listen_to, self, [message])
|
321
|
+
__send__(listener.method, message, *args)
|
322
|
+
self.class.call_hooks(:post, :listen_to, self, [message])
|
323
|
+
else
|
324
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Dropping message due to hook"
|
219
325
|
end
|
220
326
|
end
|
221
327
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
end
|
238
|
-
}
|
239
|
-
|
240
|
-
if timer.threaded
|
241
|
-
Thread.new do
|
242
|
-
l.call
|
243
|
-
end
|
244
|
-
else
|
245
|
-
l.call
|
246
|
-
end
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
328
|
+
@handlers << new_handler
|
329
|
+
@bot.handlers.register(new_handler)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
private :__register_listeners
|
333
|
+
|
334
|
+
def __register_ctcps
|
335
|
+
self.class.ctcps.each do |ctcp|
|
336
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering CTCP `#{ctcp}`"
|
337
|
+
new_handler = Handler.new(@bot, :ctcp, Pattern.generate(:ctcp, ctcp)) do |message, *args|
|
338
|
+
if self.class.call_hooks(:pre, :ctcp, self, [message])
|
339
|
+
__send__("ctcp_#{ctcp.downcase}", message, *args)
|
340
|
+
self.class.call_hooks(:post, :ctcp, self, [message])
|
341
|
+
else
|
342
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Dropping message due to hook"
|
250
343
|
end
|
251
344
|
end
|
252
345
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
346
|
+
@handlers << new_handler
|
347
|
+
@bot.handlers.register(new_handler)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
private :__register_ctcps
|
351
|
+
|
352
|
+
def __register_timers
|
353
|
+
@timers = self.class.timers.map {|timer_struct|
|
354
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering timer with interval `#{timer_struct.interval}` for method `#{timer_struct.options[:method]}`"
|
355
|
+
|
356
|
+
block = self.method(timer_struct.options[:method])
|
357
|
+
options = timer_struct.options.merge(interval: timer_struct.interval)
|
358
|
+
Cinch::Timer.new(@bot, options, &block)
|
359
|
+
}
|
360
|
+
end
|
361
|
+
private :__register_timers
|
362
|
+
|
363
|
+
def __register_matchers
|
364
|
+
prefix = self.class.prefix || @bot.config.plugins.prefix
|
365
|
+
suffix = self.class.suffix || @bot.config.plugins.suffix
|
366
|
+
|
367
|
+
self.class.matchers.each do |matcher|
|
368
|
+
_prefix = matcher.use_prefix ? matcher.prefix || prefix : nil
|
369
|
+
_suffix = matcher.use_suffix ? matcher.suffix || suffix : nil
|
370
|
+
|
371
|
+
pattern_to_register = Pattern.new(_prefix, matcher.pattern, _suffix)
|
372
|
+
react_on = matcher.reacting_on || self.class.reacting_on || :message
|
373
|
+
|
374
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering executor with pattern `#{pattern_to_register.inspect}`, reacting on `#{react_on}`"
|
375
|
+
|
376
|
+
new_handler = Handler.new(@bot, react_on, pattern_to_register, group: matcher.group) do |message, *args|
|
377
|
+
method = method(matcher.method)
|
378
|
+
arity = method.arity - 1
|
379
|
+
if arity > 0
|
380
|
+
args = args[0..arity - 1]
|
381
|
+
elsif arity == 0
|
382
|
+
args = []
|
258
383
|
end
|
384
|
+
if self.class.call_hooks(:pre, :match, self, [message])
|
385
|
+
method.call(message, *args)
|
386
|
+
self.class.call_hooks(:post, :match, self, [message])
|
387
|
+
else
|
388
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Dropping message due to hook"
|
389
|
+
end
|
390
|
+
end
|
391
|
+
@handlers << new_handler
|
392
|
+
@bot.handlers.register(new_handler)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
private :__register_matchers
|
396
|
+
|
397
|
+
def __register_help
|
398
|
+
prefix = self.class.prefix || @bot.config.plugins.prefix
|
399
|
+
suffix = self.class.suffix || @bot.config.plugins.suffix
|
400
|
+
if self.class.help
|
401
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering help message"
|
402
|
+
help_pattern = Pattern.new(prefix, "help #{self.class.plugin_name}", suffix)
|
403
|
+
new_handler = Handler.new(@bot, :message, help_pattern) do |message|
|
404
|
+
message.reply(self.class.help)
|
259
405
|
end
|
406
|
+
|
407
|
+
@handlers << new_handler
|
408
|
+
@bot.handlers.register(new_handler)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
private :__register_help
|
412
|
+
|
413
|
+
# @return [void]
|
414
|
+
# @api private
|
415
|
+
def __register
|
416
|
+
missing = self.class.check_for_missing_options(@bot)
|
417
|
+
unless missing.empty?
|
418
|
+
@bot.loggers.warn "[plugin] #{self.class.plugin_name}: Could not register plugin because the following options are not set: #{missing.join(", ")}"
|
419
|
+
return
|
260
420
|
end
|
421
|
+
|
422
|
+
__register_listeners
|
423
|
+
__register_matchers
|
424
|
+
__register_ctcps
|
425
|
+
__register_timers
|
426
|
+
__register_help
|
261
427
|
end
|
262
428
|
|
263
429
|
# @return [Bot]
|
264
430
|
attr_reader :bot
|
431
|
+
|
432
|
+
# @return [Array<Handler>] handlers
|
433
|
+
attr_reader :handlers
|
434
|
+
|
435
|
+
# @return [Array<Cinch::Timer>]
|
436
|
+
attr_reader :timers
|
437
|
+
|
438
|
+
# @return [Storage] The per-plugin persistent storage
|
439
|
+
attr_reader :storage
|
440
|
+
|
265
441
|
# @api private
|
266
442
|
def initialize(bot)
|
267
443
|
@bot = bot
|
268
|
-
|
444
|
+
@handlers = []
|
445
|
+
@timers = []
|
446
|
+
@storage = bot.config.storage.backend.new(@bot.config.storage, self)
|
447
|
+
__register
|
269
448
|
end
|
270
449
|
|
271
|
-
#
|
272
|
-
def
|
273
|
-
@bot.
|
274
|
-
|
450
|
+
# @since 2.0.0
|
451
|
+
def unregister
|
452
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Unloading plugin"
|
453
|
+
@timers.each do |timer|
|
454
|
+
timer.stop
|
455
|
+
end
|
275
456
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
# @return [void]
|
281
|
-
# @see Plugin::ClassMethods#listen_to
|
282
|
-
def listen(*args)
|
457
|
+
handlers.each do |handler|
|
458
|
+
handler.stop
|
459
|
+
handler.unregister
|
460
|
+
end
|
283
461
|
end
|
284
462
|
|
285
|
-
#
|
286
|
-
|
287
|
-
|
288
|
-
# @abstract
|
289
|
-
# @return [void]
|
290
|
-
# @see Plugin::ClassMethods#match
|
291
|
-
def execute(*args)
|
463
|
+
# (see Bot#synchronize)
|
464
|
+
def synchronize(*args, &block)
|
465
|
+
@bot.synchronize(*args, &block)
|
292
466
|
end
|
293
467
|
|
294
468
|
# Provides access to plugin-specific options.
|
@@ -298,6 +472,7 @@ module Cinch
|
|
298
472
|
@bot.config.plugins.options[self.class] || {}
|
299
473
|
end
|
300
474
|
|
475
|
+
# @api private
|
301
476
|
def self.included(by)
|
302
477
|
by.extend ClassMethods
|
303
478
|
end
|