ircinch 2.4.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.
- checksums.yaml +7 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +298 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +23 -0
- data/README.md +195 -0
- data/Rakefile +14 -0
- data/docs/bot_options.md +454 -0
- data/docs/changes.md +541 -0
- data/docs/common_mistakes.md +60 -0
- data/docs/common_tasks.md +57 -0
- data/docs/encodings.md +69 -0
- data/docs/events.md +273 -0
- data/docs/getting_started.md +184 -0
- data/docs/logging.md +90 -0
- data/docs/migrating.md +267 -0
- data/docs/plugins.md +4 -0
- data/docs/readme.md +20 -0
- data/examples/basic/autovoice.rb +32 -0
- data/examples/basic/google.rb +35 -0
- data/examples/basic/hello.rb +14 -0
- data/examples/basic/join_part.rb +35 -0
- data/examples/basic/memo.rb +39 -0
- data/examples/basic/msg.rb +15 -0
- data/examples/basic/seen.rb +37 -0
- data/examples/basic/urban_dict.rb +36 -0
- data/examples/basic/url_shorten.rb +36 -0
- data/examples/plugins/autovoice.rb +37 -0
- data/examples/plugins/custom_prefix.rb +22 -0
- data/examples/plugins/dice_roll.rb +38 -0
- data/examples/plugins/google.rb +36 -0
- data/examples/plugins/hello.rb +21 -0
- data/examples/plugins/hooks.rb +34 -0
- data/examples/plugins/join_part.rb +41 -0
- data/examples/plugins/lambdas.rb +35 -0
- data/examples/plugins/last_nick.rb +24 -0
- data/examples/plugins/msg.rb +21 -0
- data/examples/plugins/multiple_matches.rb +32 -0
- data/examples/plugins/own_events.rb +37 -0
- data/examples/plugins/seen.rb +44 -0
- data/examples/plugins/timer.rb +22 -0
- data/examples/plugins/url_shorten.rb +34 -0
- data/ircinch.gemspec +43 -0
- data/lib/cinch/ban.rb +53 -0
- data/lib/cinch/bot.rb +476 -0
- data/lib/cinch/cached_list.rb +21 -0
- data/lib/cinch/callback.rb +22 -0
- data/lib/cinch/channel.rb +465 -0
- data/lib/cinch/channel_list.rb +31 -0
- data/lib/cinch/configuration/bot.rb +50 -0
- data/lib/cinch/configuration/dcc.rb +18 -0
- data/lib/cinch/configuration/plugins.rb +43 -0
- data/lib/cinch/configuration/sasl.rb +21 -0
- data/lib/cinch/configuration/ssl.rb +21 -0
- data/lib/cinch/configuration/timeouts.rb +16 -0
- data/lib/cinch/configuration.rb +75 -0
- data/lib/cinch/constants.rb +535 -0
- data/lib/cinch/dcc/dccable_object.rb +39 -0
- data/lib/cinch/dcc/incoming/send.rb +149 -0
- data/lib/cinch/dcc/incoming.rb +3 -0
- data/lib/cinch/dcc/outgoing/send.rb +123 -0
- data/lib/cinch/dcc/outgoing.rb +3 -0
- data/lib/cinch/dcc.rb +14 -0
- data/lib/cinch/exceptions.rb +48 -0
- data/lib/cinch/formatting.rb +127 -0
- data/lib/cinch/handler.rb +120 -0
- data/lib/cinch/handler_list.rb +92 -0
- data/lib/cinch/helpers.rb +230 -0
- data/lib/cinch/i_support.rb +100 -0
- data/lib/cinch/irc.rb +924 -0
- data/lib/cinch/log_filter.rb +23 -0
- data/lib/cinch/logger/formatted_logger.rb +100 -0
- data/lib/cinch/logger/zcbot_logger.rb +26 -0
- data/lib/cinch/logger.rb +171 -0
- data/lib/cinch/logger_list.rb +88 -0
- data/lib/cinch/mask.rb +69 -0
- data/lib/cinch/message.rb +397 -0
- data/lib/cinch/message_queue.rb +104 -0
- data/lib/cinch/mode_parser.rb +78 -0
- data/lib/cinch/network.rb +106 -0
- data/lib/cinch/open_ended_queue.rb +26 -0
- data/lib/cinch/pattern.rb +66 -0
- data/lib/cinch/plugin.rb +517 -0
- data/lib/cinch/plugin_list.rb +40 -0
- data/lib/cinch/rubyext/float.rb +5 -0
- data/lib/cinch/rubyext/module.rb +28 -0
- data/lib/cinch/rubyext/string.rb +35 -0
- data/lib/cinch/sasl/dh_blowfish.rb +73 -0
- data/lib/cinch/sasl/diffie_hellman.rb +50 -0
- data/lib/cinch/sasl/mechanism.rb +8 -0
- data/lib/cinch/sasl/plain.rb +29 -0
- data/lib/cinch/sasl.rb +36 -0
- data/lib/cinch/syncable.rb +83 -0
- data/lib/cinch/target.rb +199 -0
- data/lib/cinch/timer.rb +147 -0
- data/lib/cinch/user.rb +489 -0
- data/lib/cinch/user_list.rb +89 -0
- data/lib/cinch/utilities/deprecation.rb +18 -0
- data/lib/cinch/utilities/encoding.rb +39 -0
- data/lib/cinch/utilities/kernel.rb +15 -0
- data/lib/cinch/version.rb +6 -0
- data/lib/cinch.rb +7 -0
- data/lib/ircinch.rb +7 -0
- metadata +205 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cinch
|
4
|
+
# @api private
|
5
|
+
# @since 1.1.0
|
6
|
+
class Pattern
|
7
|
+
# @param [String, Regexp, NilClass, Proc, #to_s] obj The object to
|
8
|
+
# convert to a regexp
|
9
|
+
# @return [Regexp, nil]
|
10
|
+
def self.obj_to_r(obj, anchor = nil)
|
11
|
+
case obj
|
12
|
+
when Regexp, NilClass
|
13
|
+
obj
|
14
|
+
else
|
15
|
+
escaped = Regexp.escape(obj.to_s)
|
16
|
+
case anchor
|
17
|
+
when :start
|
18
|
+
Regexp.new("^" + escaped)
|
19
|
+
when :end
|
20
|
+
Regexp.new(escaped + "$")
|
21
|
+
when nil
|
22
|
+
Regexp.new(Regexp.escape(obj.to_s))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.resolve_proc(obj, msg = nil)
|
28
|
+
if obj.is_a?(Proc)
|
29
|
+
resolve_proc(obj.call(msg), msg)
|
30
|
+
else
|
31
|
+
obj
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.generate(type, argument)
|
36
|
+
case type
|
37
|
+
when :ctcp
|
38
|
+
Pattern.new(/^/, /#{Regexp.escape(argument.to_s)}(?:$| .+)/, nil)
|
39
|
+
else
|
40
|
+
raise ArgumentError, "Unsupported type: #{type.inspect}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :prefix
|
45
|
+
attr_reader :suffix
|
46
|
+
attr_reader :pattern
|
47
|
+
def initialize(prefix, pattern, suffix)
|
48
|
+
@prefix, @pattern, @suffix = prefix, pattern, suffix
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_r(msg = nil)
|
52
|
+
pattern = Pattern.resolve_proc(@pattern, msg)
|
53
|
+
|
54
|
+
case pattern
|
55
|
+
when Regexp, NilClass
|
56
|
+
prefix = Pattern.obj_to_r(Pattern.resolve_proc(@prefix, msg), :start)
|
57
|
+
suffix = Pattern.obj_to_r(Pattern.resolve_proc(@suffix, msg), :end)
|
58
|
+
/#{prefix}#{pattern}#{suffix}/
|
59
|
+
else
|
60
|
+
prefix = Pattern.obj_to_r(Pattern.resolve_proc(@prefix, msg))
|
61
|
+
suffix = Pattern.obj_to_r(Pattern.resolve_proc(@suffix, msg))
|
62
|
+
/^#{prefix}#{Pattern.obj_to_r(pattern)}#{suffix}$/
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/cinch/plugin.rb
ADDED
@@ -0,0 +1,517 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "helpers"
|
4
|
+
|
5
|
+
module Cinch
|
6
|
+
# This class represents the core of the plugin functionality of
|
7
|
+
# Cinch. It provides both the methods for users to write their own
|
8
|
+
# plugins as well as for the Cinch framework to use them.
|
9
|
+
#
|
10
|
+
# The {ClassMethods} module, which will get included automatically
|
11
|
+
# in all classes that include `Cinch::Plugin`, includes all class
|
12
|
+
# methods that the user will use for creating plugins.
|
13
|
+
#
|
14
|
+
# Most of the instance methods are for use by the Cinch framework
|
15
|
+
# and part of the private API, but some will also be used by plugin
|
16
|
+
# authors, mainly {#config}, {#synchronize} and {#bot}.
|
17
|
+
module Plugin
|
18
|
+
include Helpers
|
19
|
+
|
20
|
+
# The ClassMethods module includes all methods that the user will
|
21
|
+
# need for creating plugins for the Cinch framework: Setting
|
22
|
+
# options (see {#set} and the attributes) as well as methods for
|
23
|
+
# configuring the actual pattern matching ({#match}, {#listen_to}).
|
24
|
+
#
|
25
|
+
# Furthermore, the attributes allow for programmatically
|
26
|
+
# inspecting plugins.
|
27
|
+
#
|
28
|
+
# @attr plugin_name
|
29
|
+
module ClassMethods
|
30
|
+
# @return [Hash{:pre, :post => Array<Hook>}] All hooks
|
31
|
+
attr_reader :hooks
|
32
|
+
|
33
|
+
# @return [Array<:message, :channel, :private>] The list of events to react on
|
34
|
+
attr_accessor :react_on
|
35
|
+
|
36
|
+
# The name of the plugin.
|
37
|
+
# @overload plugin_name
|
38
|
+
# @return [String, nil]
|
39
|
+
# @overload plugin_name=(new_name)
|
40
|
+
# @param [String, nil] new_name
|
41
|
+
# @return [String]
|
42
|
+
# @return [String, nil] The name of the plugin
|
43
|
+
attr_reader :plugin_name
|
44
|
+
|
45
|
+
# @return [String]
|
46
|
+
def plugin_name=(new_name)
|
47
|
+
@plugin_name = if new_name.nil? && name
|
48
|
+
name.split("::").last.downcase
|
49
|
+
else
|
50
|
+
new_name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Array<Matcher>] All matchers
|
55
|
+
attr_reader :matchers
|
56
|
+
|
57
|
+
# @return [Array<Listener>] All listeners
|
58
|
+
attr_reader :listeners
|
59
|
+
|
60
|
+
# @return [Array<Timer>] All timers
|
61
|
+
attr_reader :timers
|
62
|
+
|
63
|
+
# @return [Array<String>] All CTCPs
|
64
|
+
attr_reader :ctcps
|
65
|
+
|
66
|
+
# @return [String, nil] The help message
|
67
|
+
attr_accessor :help
|
68
|
+
|
69
|
+
# @return [String, Regexp, Proc] The prefix
|
70
|
+
attr_accessor :prefix
|
71
|
+
|
72
|
+
# @return [String, Regexp, Proc] The suffix
|
73
|
+
attr_accessor :suffix
|
74
|
+
|
75
|
+
# @return [Array<Symbol>] Required plugin options
|
76
|
+
attr_accessor :required_options
|
77
|
+
|
78
|
+
# Represents a Matcher as created by {#match}.
|
79
|
+
#
|
80
|
+
# @attr [String, Regexp, Proc] pattern
|
81
|
+
# @attr [Boolean] use_prefix
|
82
|
+
# @attr [Boolean] use_suffix
|
83
|
+
# @attr [Symbol] method
|
84
|
+
# @attr [Symbol] group
|
85
|
+
Matcher = Struct.new(:pattern,
|
86
|
+
:use_prefix,
|
87
|
+
:use_suffix,
|
88
|
+
:method,
|
89
|
+
:group,
|
90
|
+
:prefix,
|
91
|
+
:suffix,
|
92
|
+
:react_on,
|
93
|
+
:strip_colors)
|
94
|
+
|
95
|
+
# Represents a Listener as created by {#listen_to}.
|
96
|
+
#
|
97
|
+
# @attr [Symbol] event
|
98
|
+
# @attr [Symbol] method
|
99
|
+
Listener = Struct.new(:event, :method)
|
100
|
+
|
101
|
+
# Represents a Timer as created by {#timer}.
|
102
|
+
#
|
103
|
+
# @note This is not the same as a {Cinch::Timer} object, which
|
104
|
+
# will allow controlling and inspecting actually running
|
105
|
+
# timers. This class only describes a Timer that still has to
|
106
|
+
# be created.
|
107
|
+
#
|
108
|
+
# @attr [Numeric] interval
|
109
|
+
# @attr [Symbol] method
|
110
|
+
# @attr [Hash] options
|
111
|
+
# @attr [Boolean] registered
|
112
|
+
Timer = Struct.new(:interval, :options, :registered)
|
113
|
+
|
114
|
+
# Represents a Hook as created by {#hook}.
|
115
|
+
#
|
116
|
+
# @attr [Symbol] type
|
117
|
+
# @attr [Array<Symbol>] for
|
118
|
+
# @attr [Symbol] method
|
119
|
+
Hook = Struct.new(:type, :for, :method, :group)
|
120
|
+
|
121
|
+
# @api private
|
122
|
+
def self.extended(by)
|
123
|
+
by.instance_exec do
|
124
|
+
@matchers = []
|
125
|
+
@ctcps = []
|
126
|
+
@listeners = []
|
127
|
+
@timers = []
|
128
|
+
@help = nil
|
129
|
+
@hooks = Hash.new { |h, k| h[k] = [] }
|
130
|
+
@prefix = nil
|
131
|
+
@suffix = nil
|
132
|
+
@react_on = :message
|
133
|
+
@required_options = []
|
134
|
+
self.plugin_name = nil
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Set options.
|
139
|
+
#
|
140
|
+
# Available options:
|
141
|
+
#
|
142
|
+
# - {#help}
|
143
|
+
# - {#plugin_name}
|
144
|
+
# - {#prefix}
|
145
|
+
# - {#react_on}
|
146
|
+
# - {#required_options}
|
147
|
+
# - {#suffix}
|
148
|
+
#
|
149
|
+
# @overload set(key, value)
|
150
|
+
# @param [Symbol] key The option's name
|
151
|
+
# @param [Object] value
|
152
|
+
# @return [void]
|
153
|
+
# @overload set(options)
|
154
|
+
# @param [Hash{Symbol => Object}] options The options, as key => value associations
|
155
|
+
# @return [void]
|
156
|
+
# @example
|
157
|
+
# set(:help => "the help message",
|
158
|
+
# :prefix => "^")
|
159
|
+
# @return [void]
|
160
|
+
# @since 2.0.0
|
161
|
+
def set(*args)
|
162
|
+
case args.size
|
163
|
+
when 1
|
164
|
+
# {:key => value, ...}
|
165
|
+
args.first.each do |key, value|
|
166
|
+
send("#{key}=", value)
|
167
|
+
end
|
168
|
+
when 2
|
169
|
+
# key, value
|
170
|
+
send("#{args.first}=", args.last)
|
171
|
+
else
|
172
|
+
raise ArgumentError # TODO proper error message
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Set a match pattern.
|
177
|
+
#
|
178
|
+
# @param [Regexp, String] pattern A pattern
|
179
|
+
# @option options [Symbol] :method (:execute) The method to execute
|
180
|
+
# @option options [Boolean] :use_prefix (true) If true, the
|
181
|
+
# plugin prefix will automatically be prepended to the
|
182
|
+
# pattern.
|
183
|
+
# @option options [Boolean] :use_suffix (true) If true, the
|
184
|
+
# plugin suffix will automatically be appended to the
|
185
|
+
# pattern.
|
186
|
+
# @option options [String, Regexp, Proc] prefix (nil) A prefix
|
187
|
+
# overwriting the per-plugin prefix.
|
188
|
+
# @option options [String, Regexp, Proc] suffix (nil) A suffix
|
189
|
+
# overwriting the per-plugin suffix.
|
190
|
+
# @option options [Symbol, Fixnum] react_on (:message) The
|
191
|
+
# {file:docs/events.md event} to react on.
|
192
|
+
# @option options [Symbol] :group (nil) The group the match belongs to.
|
193
|
+
# @option options [Boolean] :strip_colors (false) Strip colors
|
194
|
+
# from message before attempting match
|
195
|
+
# @return [Matcher]
|
196
|
+
# @todo Document match/listener grouping
|
197
|
+
def match(pattern, options = {})
|
198
|
+
options = {
|
199
|
+
use_prefix: true,
|
200
|
+
use_suffix: true,
|
201
|
+
method: :execute,
|
202
|
+
group: nil,
|
203
|
+
prefix: nil,
|
204
|
+
suffix: nil,
|
205
|
+
react_on: nil,
|
206
|
+
strip_colors: false
|
207
|
+
}.merge(options)
|
208
|
+
if options[:react_on]
|
209
|
+
options[:react_on] = options[:react_on].to_s.to_sym
|
210
|
+
end
|
211
|
+
matcher = Matcher.new(pattern, *options.values_at(:use_prefix,
|
212
|
+
:use_suffix,
|
213
|
+
:method,
|
214
|
+
:group,
|
215
|
+
:prefix,
|
216
|
+
:suffix,
|
217
|
+
:react_on,
|
218
|
+
:strip_colors))
|
219
|
+
@matchers << matcher
|
220
|
+
|
221
|
+
matcher
|
222
|
+
end
|
223
|
+
|
224
|
+
# Events to listen to.
|
225
|
+
# @overload listen_to(*types, options = {})
|
226
|
+
# @param [String, Symbol, Integer] *types Events to listen to. Available
|
227
|
+
# events are all IRC commands in lowercase as symbols, all numeric
|
228
|
+
# replies and all events listed in the {file:docs/events.md list of events}.
|
229
|
+
# @param [Hash] options
|
230
|
+
# @option options [Symbol] :method (:listen) The method to
|
231
|
+
# execute
|
232
|
+
# @return [Array<Listener>]
|
233
|
+
def listen_to(*types)
|
234
|
+
options = {method: :listen}
|
235
|
+
if types.last.is_a?(Hash)
|
236
|
+
options.merge!(types.pop)
|
237
|
+
end
|
238
|
+
|
239
|
+
listeners = types.map { |type| Listener.new(type.to_s.to_sym, options[:method]) }
|
240
|
+
@listeners.concat listeners
|
241
|
+
|
242
|
+
listeners
|
243
|
+
end
|
244
|
+
|
245
|
+
# @version 1.1.1
|
246
|
+
def ctcp(command)
|
247
|
+
@ctcps << command.to_s.upcase
|
248
|
+
end
|
249
|
+
|
250
|
+
# @example
|
251
|
+
# timer 5, method: :some_method
|
252
|
+
# def some_method
|
253
|
+
# Channel("#cinch-bots").send(Time.now.to_s)
|
254
|
+
# end
|
255
|
+
#
|
256
|
+
# @param [Numeric] interval Interval in seconds
|
257
|
+
# @option options [Symbol] :method (:timer) Method to call (only
|
258
|
+
# if no proc is provided)
|
259
|
+
# @option options [Integer] :shots (Float::INFINITY) How often
|
260
|
+
# should the timer fire?
|
261
|
+
# @option options [Boolean] :threaded (true) Call method in a
|
262
|
+
# thread?
|
263
|
+
# @option options [Boolean] :start_automatically (true) If true,
|
264
|
+
# the timer will automatically start after the bot finished
|
265
|
+
# connecting.
|
266
|
+
# @option options [Boolean] :stop_automaticall (true) If true,
|
267
|
+
# the timer will automatically stop when the bot disconnects.
|
268
|
+
# @return [Timer]
|
269
|
+
# @since 1.1.0
|
270
|
+
def timer(interval, options = {})
|
271
|
+
options = {method: :timer, threaded: true}.merge(options)
|
272
|
+
timer = Timer.new(interval, options, false)
|
273
|
+
@timers << timer
|
274
|
+
|
275
|
+
timer
|
276
|
+
end
|
277
|
+
|
278
|
+
# Defines a hook which will be run before or after a handler is
|
279
|
+
# executed, depending on the value of `type`.
|
280
|
+
#
|
281
|
+
# @param [:pre, :post] type Run the hook before or after
|
282
|
+
# a handler?
|
283
|
+
# @option options [Array<:match, :listen_to, :ctcp>] :for ([:match, :listen_to, :ctcp])
|
284
|
+
# Which kinds of events to run the hook for.
|
285
|
+
# @option options [Symbol] :method (:hook) The method to execute.
|
286
|
+
# @option options [Symbol] :group (nil) The match group to
|
287
|
+
# execute the hook for. Hooks belonging to the `nil` group
|
288
|
+
# will execute for all matches.
|
289
|
+
# @return [Hook]
|
290
|
+
# @since 1.1.0
|
291
|
+
def hook(type, options = {})
|
292
|
+
options = {for: [:match, :listen_to, :ctcp], method: :hook, group: nil}.merge(options)
|
293
|
+
hook = Hook.new(type, options[:for], options[:method], options[:group])
|
294
|
+
__hooks(type) << hook
|
295
|
+
|
296
|
+
hook
|
297
|
+
end
|
298
|
+
|
299
|
+
# @return [Hash]
|
300
|
+
# @api private
|
301
|
+
def __hooks(type = nil, events = nil, group = nil)
|
302
|
+
hooks = if type.nil?
|
303
|
+
@hooks
|
304
|
+
else
|
305
|
+
@hooks[type]
|
306
|
+
end
|
307
|
+
|
308
|
+
if events.nil?
|
309
|
+
return hooks
|
310
|
+
else
|
311
|
+
events = [*events]
|
312
|
+
if hooks.is_a?(Hash)
|
313
|
+
hooks = hooks.map { |k, v| v }
|
314
|
+
end
|
315
|
+
hooks = hooks.select { |hook| (events & hook.for).size > 0 }
|
316
|
+
end
|
317
|
+
|
318
|
+
hooks.select { |hook| hook.group.nil? || hook.group == group }
|
319
|
+
end
|
320
|
+
|
321
|
+
# @return [Boolean] True if processing should continue
|
322
|
+
# @api private
|
323
|
+
def call_hooks(type, event, group, instance, args)
|
324
|
+
stop = __hooks(type, event, group).find { |hook|
|
325
|
+
# stop after the first hook that returns false
|
326
|
+
if hook.method.respond_to?(:call)
|
327
|
+
instance.instance_exec(*args, &hook.method) == false
|
328
|
+
else
|
329
|
+
instance.__send__(hook.method, *args) == false
|
330
|
+
end
|
331
|
+
}
|
332
|
+
|
333
|
+
!stop
|
334
|
+
end
|
335
|
+
|
336
|
+
# @param [Bot] bot
|
337
|
+
# @return [Array<Symbol>, nil]
|
338
|
+
# @since 2.0.0
|
339
|
+
# @api private
|
340
|
+
def check_for_missing_options(bot)
|
341
|
+
@required_options.select { |option|
|
342
|
+
!bot.config.plugins.options[self].has_key?(option)
|
343
|
+
}
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def __register_listeners
|
348
|
+
self.class.listeners.each do |listener|
|
349
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering listener for type `#{listener.event}`"
|
350
|
+
new_handler = Handler.new(@bot, listener.event, Pattern.new(nil, //, nil)) do |message, *args|
|
351
|
+
if self.class.call_hooks(:pre, :listen_to, nil, self, [message])
|
352
|
+
__send__(listener.method, message, *args)
|
353
|
+
self.class.call_hooks(:post, :listen_to, nil, self, [message])
|
354
|
+
else
|
355
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Dropping message due to hook"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
@handlers << new_handler
|
360
|
+
@bot.handlers.register(new_handler)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
private :__register_listeners
|
364
|
+
|
365
|
+
def __register_ctcps
|
366
|
+
self.class.ctcps.each do |ctcp|
|
367
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering CTCP `#{ctcp}`"
|
368
|
+
new_handler = Handler.new(@bot, :ctcp, Pattern.generate(:ctcp, ctcp)) do |message, *args|
|
369
|
+
if self.class.call_hooks(:pre, :ctcp, nil, self, [message])
|
370
|
+
__send__("ctcp_#{ctcp.downcase}", message, *args)
|
371
|
+
self.class.call_hooks(:post, :ctcp, nil, self, [message])
|
372
|
+
else
|
373
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Dropping message due to hook"
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
@handlers << new_handler
|
378
|
+
@bot.handlers.register(new_handler)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
private :__register_ctcps
|
382
|
+
|
383
|
+
def __register_timers
|
384
|
+
@timers = self.class.timers.map { |timer_struct|
|
385
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering timer with interval `#{timer_struct.interval}` for method `#{timer_struct.options[:method]}`"
|
386
|
+
|
387
|
+
block = method(timer_struct.options[:method])
|
388
|
+
options = timer_struct.options.merge(interval: timer_struct.interval)
|
389
|
+
Cinch::Timer.new(@bot, options, &block)
|
390
|
+
}
|
391
|
+
end
|
392
|
+
private :__register_timers
|
393
|
+
|
394
|
+
def __register_matchers
|
395
|
+
prefix = self.class.prefix || @bot.config.plugins.prefix
|
396
|
+
suffix = self.class.suffix || @bot.config.plugins.suffix
|
397
|
+
|
398
|
+
self.class.matchers.each do |matcher|
|
399
|
+
a_prefix = matcher.use_prefix ? matcher.prefix || prefix : nil
|
400
|
+
a_suffix = matcher.use_suffix ? matcher.suffix || suffix : nil
|
401
|
+
|
402
|
+
pattern_to_register = Pattern.new(a_prefix, matcher.pattern, a_suffix)
|
403
|
+
react_on = matcher.react_on || self.class.react_on || :message
|
404
|
+
|
405
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering executor with pattern `#{pattern_to_register.inspect}`, reacting on `#{react_on}`"
|
406
|
+
|
407
|
+
new_handler = Handler.new(@bot,
|
408
|
+
react_on,
|
409
|
+
pattern_to_register,
|
410
|
+
group: matcher.group,
|
411
|
+
strip_colors: matcher.strip_colors) do |message, *args|
|
412
|
+
method = method(matcher.method)
|
413
|
+
arity = method.arity - 1
|
414
|
+
if arity > 0
|
415
|
+
args = args[0..arity - 1]
|
416
|
+
elsif arity == 0
|
417
|
+
args = []
|
418
|
+
end
|
419
|
+
if self.class.call_hooks(:pre, :match, matcher.group, self, [message])
|
420
|
+
method.call(message, *args)
|
421
|
+
self.class.call_hooks(:post, :match, matcher.group, self, [message])
|
422
|
+
else
|
423
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Dropping message due to hook"
|
424
|
+
end
|
425
|
+
end
|
426
|
+
@handlers << new_handler
|
427
|
+
@bot.handlers.register(new_handler)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
private :__register_matchers
|
431
|
+
|
432
|
+
def __register_help
|
433
|
+
prefix = self.class.prefix || @bot.config.plugins.prefix
|
434
|
+
suffix = self.class.suffix || @bot.config.plugins.suffix
|
435
|
+
if self.class.help
|
436
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering help message"
|
437
|
+
help_pattern = Pattern.new(prefix, "help #{self.class.plugin_name}", suffix)
|
438
|
+
new_handler = Handler.new(@bot, :message, help_pattern) do |message|
|
439
|
+
message.reply(self.class.help)
|
440
|
+
end
|
441
|
+
|
442
|
+
@handlers << new_handler
|
443
|
+
@bot.handlers.register(new_handler)
|
444
|
+
end
|
445
|
+
end
|
446
|
+
private :__register_help
|
447
|
+
|
448
|
+
# @return [void]
|
449
|
+
# @api private
|
450
|
+
def __register
|
451
|
+
missing = self.class.check_for_missing_options(@bot)
|
452
|
+
unless missing.empty?
|
453
|
+
@bot.loggers.warn "[plugin] #{self.class.plugin_name}: Could not register plugin because the following options are not set: #{missing.join(", ")}"
|
454
|
+
return
|
455
|
+
end
|
456
|
+
|
457
|
+
__register_listeners
|
458
|
+
__register_matchers
|
459
|
+
__register_ctcps
|
460
|
+
__register_timers
|
461
|
+
__register_help
|
462
|
+
end
|
463
|
+
|
464
|
+
# @return [Bot]
|
465
|
+
attr_reader :bot
|
466
|
+
|
467
|
+
# @return [Array<Handler>] handlers
|
468
|
+
attr_reader :handlers
|
469
|
+
|
470
|
+
# @return [Array<Cinch::Timer>]
|
471
|
+
attr_reader :timers
|
472
|
+
|
473
|
+
# @api private
|
474
|
+
def initialize(bot)
|
475
|
+
@bot = bot
|
476
|
+
@handlers = []
|
477
|
+
@timers = []
|
478
|
+
__register
|
479
|
+
end
|
480
|
+
|
481
|
+
# @since 2.0.0
|
482
|
+
def unregister
|
483
|
+
@bot.loggers.debug "[plugin] #{self.class.plugin_name}: Unloading plugin"
|
484
|
+
@timers.each do |timer|
|
485
|
+
timer.stop
|
486
|
+
end
|
487
|
+
|
488
|
+
handlers.each do |handler|
|
489
|
+
handler.stop
|
490
|
+
handler.unregister
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
# (see Bot#synchronize)
|
495
|
+
def synchronize(name, &block)
|
496
|
+
@bot.synchronize(name, &block)
|
497
|
+
end
|
498
|
+
|
499
|
+
# Provides access to plugin-specific options.
|
500
|
+
#
|
501
|
+
# @return [Hash] A hash of options
|
502
|
+
def config
|
503
|
+
@bot.config.plugins.options[self.class] || {}
|
504
|
+
end
|
505
|
+
|
506
|
+
def shared
|
507
|
+
@bot.config.shared
|
508
|
+
end
|
509
|
+
|
510
|
+
# @api private
|
511
|
+
def self.included(by)
|
512
|
+
by.extend ClassMethods
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
# TODO more details in "message dropped" debug output
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cinch
|
4
|
+
# @since 2.0.0
|
5
|
+
class PluginList < Array
|
6
|
+
def initialize(bot)
|
7
|
+
@bot = bot
|
8
|
+
super()
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [Class<Plugin>] plugin
|
12
|
+
def register_plugin(plugin)
|
13
|
+
self << plugin.new(@bot)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Array<Class<Plugin>>] plugins
|
17
|
+
def register_plugins(plugins)
|
18
|
+
plugins.each { |plugin| register_plugin(plugin) }
|
19
|
+
end
|
20
|
+
|
21
|
+
# @since 2.0.0
|
22
|
+
def unregister_plugin(plugin)
|
23
|
+
plugin.unregister
|
24
|
+
delete(plugin)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @since 2.0.0
|
28
|
+
def unregister_plugins(plugins)
|
29
|
+
if plugins == self
|
30
|
+
plugins = dup
|
31
|
+
end
|
32
|
+
plugins.each { |plugin| unregister_plugin(plugin) }
|
33
|
+
end
|
34
|
+
|
35
|
+
# @since 2.0.0
|
36
|
+
def unregister_all
|
37
|
+
unregister_plugins(self)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extensions to Ruby's Module class.
|
4
|
+
class Module
|
5
|
+
# Like `attr_reader`, but for defining a synchronized attribute
|
6
|
+
# reader.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
def synced_attr_reader(attribute)
|
10
|
+
undef_method(attribute)
|
11
|
+
define_method(attribute) do
|
12
|
+
attr(attribute)
|
13
|
+
end
|
14
|
+
|
15
|
+
define_method("#{attribute}_unsynced") do
|
16
|
+
attr(attribute, false, true)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Like `attr_accessor`, but for defining a synchronized attribute
|
21
|
+
# accessor
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
def synced_attr_accessor(attr)
|
25
|
+
synced_attr_reader(attr)
|
26
|
+
attr_accessor(attr)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extensions to Ruby's String class.
|
4
|
+
class String
|
5
|
+
# Like `String#downcase`, but respecting different IRC casemaps.
|
6
|
+
#
|
7
|
+
# @param [:rfc1459, :"strict-rfc1459", :ascii] mapping
|
8
|
+
# @return [String]
|
9
|
+
def irc_downcase(mapping)
|
10
|
+
case mapping
|
11
|
+
when :rfc1459
|
12
|
+
tr("A-Z[]\\\\^", "a-z{}|~")
|
13
|
+
when :"strict-rfc1459"
|
14
|
+
tr("A-Z[]\\\\", "a-z{}|")
|
15
|
+
else
|
16
|
+
# when :ascii or unknown/nil
|
17
|
+
tr("A-Z", "a-z")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Like `String#upcase`, but respecting different IRC casemaps.
|
22
|
+
#
|
23
|
+
# @param [:rfc1459, :"strict-rfc1459", :ascii] mapping
|
24
|
+
# @return [String]
|
25
|
+
def irc_upcase(mapping)
|
26
|
+
case mapping
|
27
|
+
when :ascii
|
28
|
+
tr("a-z", "A-Z")
|
29
|
+
when :rfc1459
|
30
|
+
tr("a-z{}|~", "A-Z[]\\\\^")
|
31
|
+
when :"strict-rfc1459"
|
32
|
+
tr("a-z{}|", "A-Z[]\\\\")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|