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,397 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
require_relative "formatting"
|
6
|
+
|
7
|
+
module Cinch
|
8
|
+
# This class serves two purposes. For one, it simply
|
9
|
+
# represents incoming messages and allows for querying various
|
10
|
+
# details (who sent the message, what kind of message it is, etc).
|
11
|
+
#
|
12
|
+
# At the same time, it allows **responding** to messages, which
|
13
|
+
# means sending messages to either users or channels.
|
14
|
+
class Message
|
15
|
+
# @return [String]
|
16
|
+
attr_reader :raw
|
17
|
+
|
18
|
+
# @return [String]
|
19
|
+
attr_reader :prefix
|
20
|
+
|
21
|
+
# @return [String]
|
22
|
+
attr_reader :command
|
23
|
+
|
24
|
+
# @return [Array<String>]
|
25
|
+
attr_reader :params
|
26
|
+
|
27
|
+
# @return [Hash]
|
28
|
+
attr_reader :tags
|
29
|
+
|
30
|
+
# @return [Array<Symbol>]
|
31
|
+
attr_reader :events
|
32
|
+
# @api private
|
33
|
+
attr_writer :events
|
34
|
+
|
35
|
+
# @return [Time]
|
36
|
+
# @since 2.0.0
|
37
|
+
attr_reader :time
|
38
|
+
|
39
|
+
# @return [Bot]
|
40
|
+
# @since 1.1.0
|
41
|
+
attr_reader :bot
|
42
|
+
|
43
|
+
# @return [User] The user who sent this message
|
44
|
+
attr_reader :user
|
45
|
+
|
46
|
+
# @return [String, nil]
|
47
|
+
attr_reader :server
|
48
|
+
|
49
|
+
# @return [Integer, nil] the numeric error code, if any
|
50
|
+
attr_reader :error
|
51
|
+
|
52
|
+
# @return [String, nil] the command part of an CTCP message
|
53
|
+
attr_reader :ctcp_command
|
54
|
+
|
55
|
+
# @return [Channel] The channel in which this message was sent
|
56
|
+
attr_reader :channel
|
57
|
+
|
58
|
+
# @return [String, nil] the CTCP message, without \001 control characters
|
59
|
+
attr_reader :ctcp_message
|
60
|
+
|
61
|
+
# @return [Array<String>, nil]
|
62
|
+
attr_reader :ctcp_args
|
63
|
+
|
64
|
+
# @return [String, nil]
|
65
|
+
attr_reader :message
|
66
|
+
|
67
|
+
# @return [String, nil] The action message
|
68
|
+
# @since 2.0.0
|
69
|
+
attr_reader :action_message
|
70
|
+
|
71
|
+
# @return [Target]
|
72
|
+
attr_reader :target
|
73
|
+
|
74
|
+
# The STATUSMSG mode a channel message was sent to.
|
75
|
+
#
|
76
|
+
# Some IRC servers allow sending messages limited to people in a
|
77
|
+
# channel who have a certain mode. For example, by sending a
|
78
|
+
# message to `+#channel`, only people who are voiced, or have a
|
79
|
+
# higher mode (op) will receive the message.
|
80
|
+
#
|
81
|
+
# This attribute contains the mode character the message was sent
|
82
|
+
# to, or nil if it was a normal message. For the previous example,
|
83
|
+
# this attribute would be set to `"v"`, for voiced.
|
84
|
+
#
|
85
|
+
# @return [String, nil]
|
86
|
+
# @since 2.3.0
|
87
|
+
attr_reader :statusmsg_mode
|
88
|
+
|
89
|
+
def initialize(msg, bot)
|
90
|
+
@raw = msg
|
91
|
+
@bot = bot
|
92
|
+
@matches = {ctcp: {}, action: {}, other: {}}
|
93
|
+
@events = []
|
94
|
+
@time = Time.now
|
95
|
+
@statusmsg_mode = nil
|
96
|
+
parse if msg
|
97
|
+
end
|
98
|
+
|
99
|
+
# @api private
|
100
|
+
# @return [void]
|
101
|
+
def parse
|
102
|
+
match = @raw.match(/(?:^@([^:]+))?(?::?(\S+) )?(\S+)(.*)/)
|
103
|
+
tags, @prefix, @command, raw_params = match.captures
|
104
|
+
|
105
|
+
if @bot.irc.network.ngametv?
|
106
|
+
if @prefix != "ngame"
|
107
|
+
@prefix = "%s!%s@%s" % [@prefix, @prefix, @prefix]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
@params = parse_params(raw_params)
|
112
|
+
@tags = parse_tags(tags)
|
113
|
+
|
114
|
+
@user = parse_user
|
115
|
+
@channel, @statusmsg_mode = parse_channel
|
116
|
+
@target = @channel || @user
|
117
|
+
@server = parse_server
|
118
|
+
@error = parse_error
|
119
|
+
@message = parse_message
|
120
|
+
|
121
|
+
@ctcp_message = parse_ctcp_message
|
122
|
+
@ctcp_command = parse_ctcp_command
|
123
|
+
@ctcp_args = parse_ctcp_args
|
124
|
+
|
125
|
+
@action_message = parse_action_message
|
126
|
+
end
|
127
|
+
|
128
|
+
# @group Type checking
|
129
|
+
|
130
|
+
# @return [Boolean] true if the message is an numeric reply (as
|
131
|
+
# opposed to a command)
|
132
|
+
def numeric_reply?
|
133
|
+
!!@command.match(/^\d{3}$/)
|
134
|
+
end
|
135
|
+
|
136
|
+
# @return [Boolean] true if the message describes an error
|
137
|
+
def error?
|
138
|
+
!@error.nil?
|
139
|
+
end
|
140
|
+
|
141
|
+
# @return [Boolean] true if this message was sent in a channel
|
142
|
+
def channel?
|
143
|
+
!@channel.nil?
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [Boolean] true if the message is an CTCP message
|
147
|
+
def ctcp?
|
148
|
+
!!(@params.last =~ /\001.+\001/)
|
149
|
+
end
|
150
|
+
|
151
|
+
# @return [Boolean] true if the message is an action (/me)
|
152
|
+
# @since 2.0.0
|
153
|
+
def action?
|
154
|
+
@ctcp_command == "ACTION"
|
155
|
+
end
|
156
|
+
|
157
|
+
# @endgroup
|
158
|
+
|
159
|
+
# @api private
|
160
|
+
# @return [MatchData]
|
161
|
+
def match(regexp, type, strip_colors)
|
162
|
+
text = ""
|
163
|
+
case type
|
164
|
+
when :ctcp
|
165
|
+
text = ctcp_message
|
166
|
+
when :action
|
167
|
+
text = action_message
|
168
|
+
else
|
169
|
+
text = message.to_s
|
170
|
+
type = :other
|
171
|
+
end
|
172
|
+
|
173
|
+
if strip_colors
|
174
|
+
text = Cinch::Formatting.unformat(text)
|
175
|
+
end
|
176
|
+
|
177
|
+
@matches[type][regexp] ||= text.match(regexp)
|
178
|
+
end
|
179
|
+
|
180
|
+
# @group Replying
|
181
|
+
|
182
|
+
# Replies to a message, automatically determining if it was a
|
183
|
+
# channel or a private message.
|
184
|
+
#
|
185
|
+
# If the message is a STATUSMSG, i.e. it was send to `+#channel`
|
186
|
+
# or `@#channel` instead of `#channel`, the reply will be sent as
|
187
|
+
# the same kind of STATUSMSG. See {#statusmsg_mode} for more
|
188
|
+
# information on STATUSMSG.
|
189
|
+
#
|
190
|
+
# @param [String] text the message
|
191
|
+
# @param [Boolean] prefix if prefix is true and the message was in
|
192
|
+
# a channel, the reply will be prefixed by the nickname of whoever
|
193
|
+
# send the mesage
|
194
|
+
# @return [void]
|
195
|
+
def reply(text, prefix = false)
|
196
|
+
text = text.to_s
|
197
|
+
if @channel && prefix
|
198
|
+
text = text.split("\n").map { |l| "#{user.nick}: #{l}" }.join("\n")
|
199
|
+
end
|
200
|
+
|
201
|
+
reply_target.send(text)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Like {#reply}, but using {Target#safe_send} instead
|
205
|
+
#
|
206
|
+
# @param (see #reply)
|
207
|
+
# @return (see #reply)
|
208
|
+
def safe_reply(text, prefix = false)
|
209
|
+
text = text.to_s
|
210
|
+
if channel && prefix
|
211
|
+
text = "#{@user.nick}: #{text}"
|
212
|
+
end
|
213
|
+
reply_target.safe_send(text)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Reply to a message with an action.
|
217
|
+
#
|
218
|
+
# For its behaviour with regard to STATUSMSG, see {#reply}.
|
219
|
+
#
|
220
|
+
# @param [String] text the action message
|
221
|
+
# @return [void]
|
222
|
+
def action_reply(text)
|
223
|
+
text = text.to_s
|
224
|
+
reply_target.action(text)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Like {#action_reply}, but using {Target#safe_action} instead
|
228
|
+
#
|
229
|
+
# @param (see #action_reply)
|
230
|
+
# @return (see #action_reply)
|
231
|
+
def safe_action_reply(text)
|
232
|
+
text = text.to_s
|
233
|
+
reply_target.safe_action(text)
|
234
|
+
end
|
235
|
+
|
236
|
+
# Reply to a CTCP message
|
237
|
+
#
|
238
|
+
# @return [void]
|
239
|
+
def ctcp_reply(answer)
|
240
|
+
return unless ctcp?
|
241
|
+
@user.notice "\001#{@ctcp_command} #{answer}\001"
|
242
|
+
end
|
243
|
+
|
244
|
+
# @endgroup
|
245
|
+
|
246
|
+
# @return [String]
|
247
|
+
# @since 1.1.0
|
248
|
+
def to_s
|
249
|
+
"#<Cinch::Message @raw=#{@raw.chomp.inspect} @params=#{@params.inspect} channel=#{@channel.inspect} user=#{@user.inspect}>"
|
250
|
+
end
|
251
|
+
|
252
|
+
private
|
253
|
+
|
254
|
+
def reply_target
|
255
|
+
if @channel.nil? || @statusmsg_mode.nil?
|
256
|
+
return @target
|
257
|
+
end
|
258
|
+
prefix = @bot.irc.isupport["PREFIX"][@statusmsg_mode]
|
259
|
+
Target.new(prefix + @channel.name, @bot)
|
260
|
+
end
|
261
|
+
|
262
|
+
def regular_command?
|
263
|
+
!numeric_reply? # a command can only be numeric or "regular"…
|
264
|
+
end
|
265
|
+
|
266
|
+
def parse_params(raw_params)
|
267
|
+
params = []
|
268
|
+
if (match = raw_params.match(/(?:^:| :)(.*)$/))
|
269
|
+
params = match.pre_match.split(" ")
|
270
|
+
params << match[1]
|
271
|
+
else
|
272
|
+
params = raw_params.split(" ")
|
273
|
+
end
|
274
|
+
|
275
|
+
params
|
276
|
+
end
|
277
|
+
|
278
|
+
def parse_tags(raw_tags)
|
279
|
+
return {} if raw_tags.nil?
|
280
|
+
|
281
|
+
class << self
|
282
|
+
def to_symbol(string)
|
283
|
+
string.tr(/-/, "_").downcase.to_sym
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
tags = {}
|
288
|
+
raw_tags.split(";").each do |tag|
|
289
|
+
tag_name, tag_value = tag.split("=")
|
290
|
+
if /,/.match?(tag_value)
|
291
|
+
tag_value = tag_value.split(",")
|
292
|
+
elsif tag_value.nil?
|
293
|
+
tag_value = tag_name
|
294
|
+
end
|
295
|
+
if /\//.match?(tag_name)
|
296
|
+
vendor, tag_name = tag_name.split("/")
|
297
|
+
tags[to_symbol(vendor)] = {
|
298
|
+
to_symbol(tag_name) => tag_value
|
299
|
+
}
|
300
|
+
else
|
301
|
+
tags[to_symbol(tag_name)] = tag_value
|
302
|
+
end
|
303
|
+
end
|
304
|
+
tags
|
305
|
+
end
|
306
|
+
|
307
|
+
def parse_user
|
308
|
+
return unless @prefix
|
309
|
+
nick = @prefix[/^(\S+)!/, 1]
|
310
|
+
user = @prefix[/^\S+!(\S+)@/, 1]
|
311
|
+
host = @prefix[/@(\S+)$/, 1]
|
312
|
+
|
313
|
+
return nil if nick.nil?
|
314
|
+
@bot.user_list.find_ensured(user, nick, host)
|
315
|
+
end
|
316
|
+
|
317
|
+
def parse_channel
|
318
|
+
# has to be called after parse_params
|
319
|
+
return nil if @params.empty?
|
320
|
+
|
321
|
+
case @command
|
322
|
+
when "INVITE", Constants::RPL_CHANNELMODEIS.to_s, Constants::RPL_BANLIST.to_s
|
323
|
+
@bot.channel_list.find_ensured(@params[1])
|
324
|
+
when Constants::RPL_NAMEREPLY.to_s
|
325
|
+
@bot.channel_list.find_ensured(@params[2])
|
326
|
+
else
|
327
|
+
# Note that this will also find channels for messages that
|
328
|
+
# don't actually include a channel parameter. For example
|
329
|
+
# `QUIT :#sometext` will be interpreted as a channel. The
|
330
|
+
# alternative to the currently used heuristic would be to
|
331
|
+
# hardcode a list of commands that provide a channel argument.
|
332
|
+
ch, status = privmsg_channel_name(@params.first)
|
333
|
+
if ch.nil? && numeric_reply? && @params.size > 1
|
334
|
+
ch, status = privmsg_channel_name(@params[1])
|
335
|
+
end
|
336
|
+
if ch
|
337
|
+
[@bot.channel_list.find_ensured(ch), status]
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def privmsg_channel_name(s)
|
343
|
+
chantypes = @bot.irc.isupport["CHANTYPES"]
|
344
|
+
statusmsg = @bot.irc.isupport["STATUSMSG"]
|
345
|
+
if statusmsg.include?(s[0]) && chantypes.include?(s[1])
|
346
|
+
status = @bot.irc.isupport["PREFIX"].invert[s[0]]
|
347
|
+
[s[1..], status]
|
348
|
+
elsif chantypes.include?(s[0])
|
349
|
+
[s, nil]
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def parse_server
|
354
|
+
return unless @prefix
|
355
|
+
return if /[@!]/.match?(@prefix)
|
356
|
+
@prefix[/^(\S+)/, 1]
|
357
|
+
end
|
358
|
+
|
359
|
+
def parse_error
|
360
|
+
return @command.to_i if numeric_reply? && @command[/[45]\d\d/]
|
361
|
+
end
|
362
|
+
|
363
|
+
def parse_message
|
364
|
+
# has to be called after parse_params
|
365
|
+
if error?
|
366
|
+
@error.to_s
|
367
|
+
elsif regular_command?
|
368
|
+
@params.last
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def parse_ctcp_message
|
373
|
+
# has to be called after parse_params
|
374
|
+
return unless ctcp?
|
375
|
+
@params.last =~ /\001(.+)\001/
|
376
|
+
$1
|
377
|
+
end
|
378
|
+
|
379
|
+
def parse_ctcp_command
|
380
|
+
# has to be called after parse_ctcp_message
|
381
|
+
return unless ctcp?
|
382
|
+
@ctcp_message.split(" ").first
|
383
|
+
end
|
384
|
+
|
385
|
+
def parse_ctcp_args
|
386
|
+
# has to be called after parse_ctcp_message
|
387
|
+
return unless ctcp?
|
388
|
+
@ctcp_message.split(" ")[1..]
|
389
|
+
end
|
390
|
+
|
391
|
+
def parse_action_message
|
392
|
+
# has to be called after parse_ctcp_message
|
393
|
+
return nil unless action?
|
394
|
+
@ctcp_message.split(" ", 2).last
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "open_ended_queue"
|
4
|
+
|
5
|
+
module Cinch
|
6
|
+
# This class manages all outgoing messages, applying rate throttling
|
7
|
+
# and fair distribution.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class MessageQueue
|
11
|
+
def initialize(socket, bot)
|
12
|
+
@socket = socket
|
13
|
+
@bot = bot
|
14
|
+
@queues = {generic: OpenEndedQueue.new}
|
15
|
+
@queues_to_process = Queue.new
|
16
|
+
@queued_queues = Set.new
|
17
|
+
@mutex = Mutex.new
|
18
|
+
@time_since_last_send = nil
|
19
|
+
@log = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [void]
|
23
|
+
def queue(message)
|
24
|
+
command, target, _ = message.split(" ", 3)
|
25
|
+
|
26
|
+
queue = nil
|
27
|
+
case command
|
28
|
+
when "PRIVMSG", "NOTICE"
|
29
|
+
@mutex.synchronize do
|
30
|
+
# we are assuming that each message has only one target,
|
31
|
+
# which will be true as long as the user does not send raw
|
32
|
+
# messages.
|
33
|
+
#
|
34
|
+
# this assumption is also reflected in the computation of
|
35
|
+
# passed time and processed messages, since our score does
|
36
|
+
# not take weights into account.
|
37
|
+
queue = @queues[target] ||= OpenEndedQueue.new
|
38
|
+
end
|
39
|
+
else
|
40
|
+
queue = @queues[:generic]
|
41
|
+
end
|
42
|
+
queue << message
|
43
|
+
|
44
|
+
@mutex.synchronize do
|
45
|
+
unless @queued_queues.include?(queue)
|
46
|
+
@queued_queues << queue
|
47
|
+
@queues_to_process << queue
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [void]
|
53
|
+
def process!
|
54
|
+
loop do
|
55
|
+
wait
|
56
|
+
process_one
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def wait
|
63
|
+
if @log.size > 1
|
64
|
+
mps = @bot.config.messages_per_second || @bot.irc.network.default_messages_per_second
|
65
|
+
max_queue_size = @bot.config.server_queue_size || @bot.irc.network.default_server_queue_size
|
66
|
+
|
67
|
+
time_passed = @log.last - @log.first
|
68
|
+
|
69
|
+
messages_processed = (time_passed * mps).floor
|
70
|
+
effective_size = @log.size - messages_processed
|
71
|
+
|
72
|
+
if effective_size <= 0
|
73
|
+
@log.clear
|
74
|
+
elsif effective_size >= max_queue_size
|
75
|
+
sleep 1.0 / mps
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def process_one
|
81
|
+
queue = @queues_to_process.pop
|
82
|
+
message = queue.pop.to_s.each_line.first.chomp
|
83
|
+
|
84
|
+
if queue.empty?
|
85
|
+
@mutex.synchronize do
|
86
|
+
@queued_queues.delete(queue)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
@queues_to_process << queue
|
90
|
+
end
|
91
|
+
|
92
|
+
begin
|
93
|
+
to_send = Cinch::Utilities::Encoding.encode_outgoing(message, @bot.config.encoding)
|
94
|
+
@socket.write(to_send + "\r\n")
|
95
|
+
@log << Time.now
|
96
|
+
@bot.loggers.outgoing(message)
|
97
|
+
|
98
|
+
@time_since_last_send = Time.now
|
99
|
+
rescue IOError
|
100
|
+
@bot.loggers.error "Could not send message (connectivity problems): #{message}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "exceptions"
|
4
|
+
|
5
|
+
module Cinch
|
6
|
+
# @api private
|
7
|
+
# @since 1.1.0
|
8
|
+
module ModeParser
|
9
|
+
ERR_EMPTY_STRING = "Empty mode string"
|
10
|
+
MalformedError = Struct.new(:modes)
|
11
|
+
EmptySequenceError = Struct.new(:modes)
|
12
|
+
NotEnoughParametersError = Struct.new(:op)
|
13
|
+
TooManyParametersError = Struct.new(:modes, :params)
|
14
|
+
|
15
|
+
# @param [String] modes The mode string as sent by the server
|
16
|
+
# @param [Array<String>] params Parameters belonging to the modes
|
17
|
+
# @param [Hash{:add, :remove => Array<String>}] param_modes
|
18
|
+
# A mapping describing which modes require parameters
|
19
|
+
# @return [(Array<(Symbol<:add, :remove>, String<char>, String<param>), foo)]
|
20
|
+
def self.parse_modes(modes, params, param_modes = {})
|
21
|
+
if modes.size == 0
|
22
|
+
return nil, ERR_EMPTY_STRING
|
23
|
+
# raise Exceptions::InvalidModeString, 'Empty mode string'
|
24
|
+
end
|
25
|
+
|
26
|
+
if !/[+-]/.match?(modes[0])
|
27
|
+
return nil, MalformedError.new(modes)
|
28
|
+
# raise Exceptions::InvalidModeString, "Malformed modes string: %s" % modes
|
29
|
+
end
|
30
|
+
|
31
|
+
changes = []
|
32
|
+
|
33
|
+
direction = nil
|
34
|
+
count = -1
|
35
|
+
|
36
|
+
modes.each_char do |ch|
|
37
|
+
if /[+-]/.match?(ch)
|
38
|
+
if count == 0
|
39
|
+
return changes, EmptySequenceError.new(modes)
|
40
|
+
# raise Exceptions::InvalidModeString, 'Empty mode sequence: %s' % modes
|
41
|
+
end
|
42
|
+
|
43
|
+
direction = case ch
|
44
|
+
when "+"
|
45
|
+
:add
|
46
|
+
when "-"
|
47
|
+
:remove
|
48
|
+
end
|
49
|
+
count = 0
|
50
|
+
else
|
51
|
+
param = nil
|
52
|
+
if param_modes.has_key?(direction) && param_modes[direction].include?(ch)
|
53
|
+
if params.size > 0
|
54
|
+
param = params.shift
|
55
|
+
else
|
56
|
+
return changes, NotEnoughParametersError.new(ch)
|
57
|
+
# raise Exceptions::InvalidModeString, 'Not enough parameters: %s' % ch.inspect
|
58
|
+
end
|
59
|
+
end
|
60
|
+
changes << [direction, ch, param]
|
61
|
+
count += 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
if params.size > 0
|
66
|
+
return changes, TooManyParametersError.new(modes, params)
|
67
|
+
# raise Exceptions::InvalidModeString, 'Too many parameters: %s %s' % [modes, params]
|
68
|
+
end
|
69
|
+
|
70
|
+
if count == 0
|
71
|
+
return changes, EmptySequenceError.new(modes)
|
72
|
+
# raise Exceptions::InvalidModeString, 'Empty mode sequence: %s' % modes
|
73
|
+
end
|
74
|
+
|
75
|
+
[changes, nil]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cinch
|
4
|
+
# This class allows querying the IRC network for its name and used
|
5
|
+
# server software as well as certain non-standard behaviour.
|
6
|
+
#
|
7
|
+
# @since 2.0.0
|
8
|
+
class Network
|
9
|
+
# @return [Symbol] The name of the network. `:unknown` if the
|
10
|
+
# network couldn't be detected.
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# @api private
|
14
|
+
attr_writer :name
|
15
|
+
|
16
|
+
# @return [Symbol] The server software used by the network.
|
17
|
+
# `:unknown` if the software couldn't be detected.
|
18
|
+
attr_reader :ircd
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
attr_writer :ircd
|
22
|
+
|
23
|
+
# @return [Array<Symbol>] All client capabilities supported by the
|
24
|
+
# network.
|
25
|
+
attr_reader :capabilities
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
attr_writer :capabilities
|
29
|
+
|
30
|
+
# @param [Symbol] name
|
31
|
+
# @param [Symbol] ircd
|
32
|
+
# @api private
|
33
|
+
# @note The user should not create instances of this class but use
|
34
|
+
# {IRC#network} instead.
|
35
|
+
def initialize(name, ircd)
|
36
|
+
@name = name
|
37
|
+
@ircd = ircd
|
38
|
+
@capabilities = []
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String, nil] The mode used for getting the list of
|
42
|
+
# channel owners, if any
|
43
|
+
def owner_list_mode
|
44
|
+
return "q" if @ircd == :unreal || @ircd == :inspircd
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [String, nil] The mode used for getting the list of
|
48
|
+
# channel quiets, if any
|
49
|
+
def quiet_list_mode
|
50
|
+
return "q" if @ircd == :"ircd-seven"
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Boolean] Does WHOIS only support one argument?
|
54
|
+
def whois_only_one_argument?
|
55
|
+
@name == :jtv
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Boolean] True if connected to NgameTV
|
59
|
+
def ngametv?
|
60
|
+
@name == :ngametv
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Boolean] True if connected to JTV
|
64
|
+
def jtv?
|
65
|
+
@name == :jtv
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Boolean] True if we do not know which network we are
|
69
|
+
# connected to
|
70
|
+
def unknown_network?
|
71
|
+
@name == :unknown
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [Boolean] True if we do not know which software the
|
75
|
+
# server is running
|
76
|
+
def unknown_ircd?
|
77
|
+
@ircd == :unknown
|
78
|
+
end
|
79
|
+
|
80
|
+
# Note for the default_* methods: Always make sure to return a
|
81
|
+
# value for when no network/ircd was detected so that MessageQueue
|
82
|
+
# doesn't break.
|
83
|
+
|
84
|
+
# @return [Numeric] The `messages per second` value that best suits
|
85
|
+
# the current network
|
86
|
+
def default_messages_per_second
|
87
|
+
case @name
|
88
|
+
when :freenode
|
89
|
+
0.7
|
90
|
+
else
|
91
|
+
0.5
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Integer] The `server queue size` value that best suits
|
96
|
+
# the current network
|
97
|
+
def default_server_queue_size
|
98
|
+
case @name
|
99
|
+
when :quakenet
|
100
|
+
40
|
101
|
+
else
|
102
|
+
10
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
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]
|
10
|
+
def unshift(obj)
|
11
|
+
t = nil
|
12
|
+
@mutex.synchronize {
|
13
|
+
@que.unshift obj
|
14
|
+
begin
|
15
|
+
t = @waiting.shift
|
16
|
+
t&.wakeup
|
17
|
+
rescue ThreadError
|
18
|
+
retry
|
19
|
+
end
|
20
|
+
}
|
21
|
+
begin
|
22
|
+
t&.run
|
23
|
+
rescue ThreadError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|