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
data/lib/cinch/user.rb
ADDED
@@ -0,0 +1,489 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "timeout"
|
4
|
+
|
5
|
+
require_relative "target"
|
6
|
+
|
7
|
+
module Cinch
|
8
|
+
# @attr_reader [String] authname
|
9
|
+
# @attr_reader [String, nil] away The user's away message, or
|
10
|
+
# `nil` if not away.
|
11
|
+
# @attr_reader [Array<Channel>] channels All channels the user is
|
12
|
+
# in.
|
13
|
+
# @attr_reader [String] host
|
14
|
+
# @attr_reader [Integer] idle How long this user has been idle, in seconds.
|
15
|
+
# This is a snapshot of the last WHOIS.
|
16
|
+
# @attr_reader [String] nick The user's nickname
|
17
|
+
# @attr_reader [Boolean] online True if the user is online.
|
18
|
+
# @attr_reader [Boolean] oper True if the user is an IRC operator.
|
19
|
+
# @attr_reader [String] realname
|
20
|
+
# @attr_reader [Boolean] secure True if the user is using a secure
|
21
|
+
# connection, i.e. SSL.
|
22
|
+
# @attr_reader [Time] signed_on_at
|
23
|
+
# @attr_reader [Boolean] unknown True if the instance references an user who
|
24
|
+
# cannot be found on the server.
|
25
|
+
# @attr_reader [String] user
|
26
|
+
#
|
27
|
+
# @version 2.0.0
|
28
|
+
class User < Target
|
29
|
+
include Syncable
|
30
|
+
|
31
|
+
def nick
|
32
|
+
name
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String]
|
36
|
+
# @since 1.1.0
|
37
|
+
attr_reader :last_nick
|
38
|
+
|
39
|
+
# @return [Boolean]
|
40
|
+
attr_reader :synced
|
41
|
+
# @since 2.1.0
|
42
|
+
alias_method :synced?, :synced
|
43
|
+
|
44
|
+
# @return [Boolean]
|
45
|
+
# @api private
|
46
|
+
attr_accessor :in_whois
|
47
|
+
|
48
|
+
def user
|
49
|
+
attr(:user, true, false)
|
50
|
+
end
|
51
|
+
|
52
|
+
def host
|
53
|
+
attr(:host, true, false)
|
54
|
+
end
|
55
|
+
|
56
|
+
def realname
|
57
|
+
attr(:realname, true, false)
|
58
|
+
end
|
59
|
+
|
60
|
+
def authname
|
61
|
+
attr(:authname, true, false)
|
62
|
+
end
|
63
|
+
|
64
|
+
def away
|
65
|
+
attr(:away, true, false)
|
66
|
+
end
|
67
|
+
|
68
|
+
def idle
|
69
|
+
attr(:idle, true, false)
|
70
|
+
end
|
71
|
+
|
72
|
+
def signed_on_at
|
73
|
+
attr(:signed_on_at, true, false)
|
74
|
+
end
|
75
|
+
|
76
|
+
def unknown
|
77
|
+
attr(:unknown?, true, false)
|
78
|
+
end
|
79
|
+
alias_method :unknown?, :unknown
|
80
|
+
|
81
|
+
# @note This attribute will be updated by various events, but
|
82
|
+
# unless {#monitor} is being used, this information cannot be
|
83
|
+
# ensured to be always correct.
|
84
|
+
def online
|
85
|
+
attr(:online?, true, false)
|
86
|
+
end
|
87
|
+
alias_method :online?, :online
|
88
|
+
|
89
|
+
def channels
|
90
|
+
attr(:channels, true, false)
|
91
|
+
end
|
92
|
+
|
93
|
+
def secure
|
94
|
+
attr(:secure?, true, false)
|
95
|
+
end
|
96
|
+
alias_method :secure?, :secure
|
97
|
+
|
98
|
+
# @since 2.1.0
|
99
|
+
def oper
|
100
|
+
attr(:oper?, true, false)
|
101
|
+
end
|
102
|
+
alias_method :oper?, :oper
|
103
|
+
|
104
|
+
# @private
|
105
|
+
def user_unsynced
|
106
|
+
attr(:user, true, true)
|
107
|
+
end
|
108
|
+
|
109
|
+
# @private
|
110
|
+
def host_unsynced
|
111
|
+
attr(:host, true, true)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @private
|
115
|
+
def realname_unsynced
|
116
|
+
attr(:realname, true, true)
|
117
|
+
end
|
118
|
+
|
119
|
+
# @private
|
120
|
+
def authname_unsynced
|
121
|
+
attr(:authname, true, true)
|
122
|
+
end
|
123
|
+
|
124
|
+
# @private
|
125
|
+
def idle_unsynced
|
126
|
+
attr(:idle, true, true)
|
127
|
+
end
|
128
|
+
|
129
|
+
# @private
|
130
|
+
def signed_on_at_unsynced
|
131
|
+
attr(:signed_on_at, true, true)
|
132
|
+
end
|
133
|
+
|
134
|
+
# @private
|
135
|
+
def unknown_unsynced
|
136
|
+
attr(:unknown?, true, true)
|
137
|
+
end
|
138
|
+
alias_method :"unknown?_unsynced", "unknown_unsynced"
|
139
|
+
|
140
|
+
# @private
|
141
|
+
def online_unsynced
|
142
|
+
attr(:online?, true, true)
|
143
|
+
end
|
144
|
+
alias_method :"online?_unsynced", "online_unsynced"
|
145
|
+
|
146
|
+
# @private
|
147
|
+
def channels_unsynced
|
148
|
+
attr(:channels, true, true)
|
149
|
+
end
|
150
|
+
|
151
|
+
# @private
|
152
|
+
def secure_unsynced
|
153
|
+
attr(:secure?, true, true)
|
154
|
+
end
|
155
|
+
alias_method :"secure?_unsynced", "secure_unsynced"
|
156
|
+
|
157
|
+
# @private
|
158
|
+
# @since 2.1.0
|
159
|
+
def oper_unsynced
|
160
|
+
attr(:oper?, true, true)
|
161
|
+
end
|
162
|
+
alias_method :"oper?_unsynced", "oper_unsynced"
|
163
|
+
|
164
|
+
# By default, you can use methods like {#user}, {#host} and
|
165
|
+
# alike – If you however fear that another thread might change
|
166
|
+
# data while you're using it and if this means a critical issue to
|
167
|
+
# your code, you can store a clone of the result of this method
|
168
|
+
# and work with that instead.
|
169
|
+
#
|
170
|
+
# @example
|
171
|
+
# on :channel do |m|
|
172
|
+
# data = m.user.data.dup
|
173
|
+
# do_something_with(data.user)
|
174
|
+
# do_something_with(data.host)
|
175
|
+
# end
|
176
|
+
# @return [Hash]
|
177
|
+
attr_reader :data
|
178
|
+
|
179
|
+
# @return [Boolean] True if the user is being monitored
|
180
|
+
# @see #monitor
|
181
|
+
# @see #unmonitor
|
182
|
+
# @note The attribute writer is in fact part of the private API
|
183
|
+
attr_reader :monitored
|
184
|
+
# @since 2.1.0
|
185
|
+
alias_method :monitored?, :monitored
|
186
|
+
|
187
|
+
# @api private
|
188
|
+
attr_writer :monitored
|
189
|
+
|
190
|
+
# @note Generally, you shouldn't initialize new instances of this
|
191
|
+
# class. Use {UserList#find_ensured} instead.
|
192
|
+
def initialize(*args)
|
193
|
+
@data = {
|
194
|
+
user: nil,
|
195
|
+
host: nil,
|
196
|
+
realname: nil,
|
197
|
+
authname: nil,
|
198
|
+
idle: 0,
|
199
|
+
signed_on_at: nil,
|
200
|
+
unknown?: false,
|
201
|
+
online?: false,
|
202
|
+
channels: [],
|
203
|
+
secure?: false,
|
204
|
+
away: nil,
|
205
|
+
oper?: false
|
206
|
+
}
|
207
|
+
case args.size
|
208
|
+
when 2
|
209
|
+
@name, @bot = args
|
210
|
+
when 4
|
211
|
+
@data[:user], @name, @data[:host], @bot = args
|
212
|
+
else
|
213
|
+
raise ArgumentError
|
214
|
+
end
|
215
|
+
|
216
|
+
@synced_attributes = Set.new
|
217
|
+
|
218
|
+
@when_requesting_synced_attribute = lambda { |attr|
|
219
|
+
unless attribute_synced?(attr)
|
220
|
+
@data[:unknown?] = false
|
221
|
+
unsync :unknown?
|
222
|
+
|
223
|
+
refresh
|
224
|
+
end
|
225
|
+
}
|
226
|
+
|
227
|
+
@monitored = false
|
228
|
+
end
|
229
|
+
|
230
|
+
# Checks if the user is identified. Currently officially supports
|
231
|
+
# Quakenet and Freenode.
|
232
|
+
#
|
233
|
+
# @return [Boolean] true if the user is identified
|
234
|
+
# @version 1.1.0
|
235
|
+
def authed?
|
236
|
+
!attr(:authname).nil?
|
237
|
+
end
|
238
|
+
|
239
|
+
# @see Syncable#attr
|
240
|
+
def attr(attribute, data = true, unsync = false)
|
241
|
+
super
|
242
|
+
end
|
243
|
+
|
244
|
+
# Queries the IRC server for information on the user. This will
|
245
|
+
# set the User's state to not synced. After all information are
|
246
|
+
# received, the object will be set back to synced.
|
247
|
+
#
|
248
|
+
# @return [void]
|
249
|
+
# @note The alias `whois` is deprecated and will be removed in a
|
250
|
+
# future version.
|
251
|
+
def refresh
|
252
|
+
return if @in_whois
|
253
|
+
@data.keys.each do |attr|
|
254
|
+
unsync attr
|
255
|
+
end
|
256
|
+
|
257
|
+
@in_whois = true
|
258
|
+
if @bot.irc.network.whois_only_one_argument?
|
259
|
+
@bot.irc.send "WHOIS #{@name}"
|
260
|
+
else
|
261
|
+
@bot.irc.send "WHOIS #{@name} #{@name}"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
alias_method :whois, :refresh # deprecated
|
265
|
+
undef_method(:whois) # yardoc hack
|
266
|
+
|
267
|
+
# @deprecated
|
268
|
+
def whois
|
269
|
+
Cinch::Utilities::Deprecation.print_deprecation("2.2.0", "User#whois", "User#refresh")
|
270
|
+
refresh
|
271
|
+
end
|
272
|
+
|
273
|
+
# @param [Hash, nil] values A hash of values gathered from WHOIS,
|
274
|
+
# or `nil` if no data was returned
|
275
|
+
# @return [void]
|
276
|
+
# @api private
|
277
|
+
# @since 1.0.1
|
278
|
+
def end_of_whois(values)
|
279
|
+
@in_whois = false
|
280
|
+
if values.nil?
|
281
|
+
# for some reason, we did not receive user information. one
|
282
|
+
# reason is freenode throttling WHOIS
|
283
|
+
Thread.new do
|
284
|
+
sleep 2
|
285
|
+
refresh
|
286
|
+
end
|
287
|
+
return
|
288
|
+
end
|
289
|
+
|
290
|
+
if values[:unknown?]
|
291
|
+
sync(:unknown?, true, true)
|
292
|
+
self.online = false
|
293
|
+
sync(:idle, 0, true)
|
294
|
+
sync(:channels, [], true)
|
295
|
+
|
296
|
+
fields = @data.keys
|
297
|
+
fields.delete(:unknown?)
|
298
|
+
fields.delete(:idle)
|
299
|
+
fields.delete(:channels)
|
300
|
+
fields.each do |field|
|
301
|
+
sync(field, nil, true)
|
302
|
+
end
|
303
|
+
|
304
|
+
return
|
305
|
+
end
|
306
|
+
|
307
|
+
if values[:registered]
|
308
|
+
values[:authname] ||= nick
|
309
|
+
values.delete(:registered)
|
310
|
+
end
|
311
|
+
{
|
312
|
+
authname: nil,
|
313
|
+
idle: 0,
|
314
|
+
secure?: false,
|
315
|
+
oper?: false,
|
316
|
+
away: nil,
|
317
|
+
channels: []
|
318
|
+
}.merge(values).each do |attr, value|
|
319
|
+
sync(attr, value, true)
|
320
|
+
end
|
321
|
+
|
322
|
+
sync(:unknown?, false, true)
|
323
|
+
self.online = true
|
324
|
+
end
|
325
|
+
|
326
|
+
# @return [void]
|
327
|
+
# @since 1.0.1
|
328
|
+
# @api private
|
329
|
+
# @see Syncable#unsync_all
|
330
|
+
def unsync_all
|
331
|
+
super
|
332
|
+
end
|
333
|
+
|
334
|
+
# @return [String]
|
335
|
+
def to_s
|
336
|
+
@name
|
337
|
+
end
|
338
|
+
|
339
|
+
# @return [String]
|
340
|
+
def inspect
|
341
|
+
"#<User nick=#{@name.inspect}>"
|
342
|
+
end
|
343
|
+
|
344
|
+
# Generates a mask for the user.
|
345
|
+
#
|
346
|
+
# @param [String] s a pattern for generating the mask.
|
347
|
+
#
|
348
|
+
# - %n = nickname
|
349
|
+
# - %u = username
|
350
|
+
# - %h = host
|
351
|
+
# - %r = realname
|
352
|
+
# - %a = authname
|
353
|
+
#
|
354
|
+
# @return [Mask]
|
355
|
+
def mask(s = "%n!%u@%h")
|
356
|
+
s = s.gsub(/%(.)/) {
|
357
|
+
case $1
|
358
|
+
when "n"
|
359
|
+
@name
|
360
|
+
when "u"
|
361
|
+
user
|
362
|
+
when "h"
|
363
|
+
host
|
364
|
+
when "r"
|
365
|
+
realname
|
366
|
+
when "a"
|
367
|
+
authname
|
368
|
+
end
|
369
|
+
}
|
370
|
+
|
371
|
+
Mask.new(s)
|
372
|
+
end
|
373
|
+
|
374
|
+
# Check if the user matches a mask.
|
375
|
+
#
|
376
|
+
# @param [Ban, Mask, User, String] other The user or mask to match against
|
377
|
+
# @return [Boolean]
|
378
|
+
def match(other)
|
379
|
+
Mask.from(other) =~ Mask.from(self)
|
380
|
+
end
|
381
|
+
alias_method :=~, :match
|
382
|
+
|
383
|
+
# Starts monitoring a user's online state by either using MONITOR
|
384
|
+
# or periodically running WHOIS.
|
385
|
+
#
|
386
|
+
# @since 2.0.0
|
387
|
+
# @return [void]
|
388
|
+
# @see #unmonitor
|
389
|
+
def monitor
|
390
|
+
if @bot.irc.isupport["MONITOR"] > 0
|
391
|
+
@bot.irc.send "MONITOR + #{@name}"
|
392
|
+
else
|
393
|
+
refresh
|
394
|
+
@monitored_timer = Timer.new(@bot, interval: 30) {
|
395
|
+
refresh
|
396
|
+
}
|
397
|
+
@monitored_timer.start
|
398
|
+
end
|
399
|
+
|
400
|
+
@monitored = true
|
401
|
+
end
|
402
|
+
|
403
|
+
# Stops monitoring a user's online state.
|
404
|
+
#
|
405
|
+
# @since 2.0.0
|
406
|
+
# @return [void]
|
407
|
+
# @see #monitor
|
408
|
+
def unmonitor
|
409
|
+
if @bot.irc.isupport["MONITOR"] > 0
|
410
|
+
@bot.irc.send "MONITOR - #{@name}"
|
411
|
+
else
|
412
|
+
@monitored_timer&.stop
|
413
|
+
end
|
414
|
+
|
415
|
+
@monitored = false
|
416
|
+
end
|
417
|
+
|
418
|
+
# Send data via DCC SEND to a user.
|
419
|
+
#
|
420
|
+
# @param [DCC::DCCableObject] io
|
421
|
+
# @param [String] filename
|
422
|
+
# @since 2.0.0
|
423
|
+
# @return [void]
|
424
|
+
# @note This method blocks.
|
425
|
+
def dcc_send(io, filename = File.basename(io.path))
|
426
|
+
own_ip = bot.config.dcc.own_ip || @bot.irc.socket.addr[2]
|
427
|
+
dcc = DCC::Outgoing::Send.new(receiver: self,
|
428
|
+
filename: filename,
|
429
|
+
io: io,
|
430
|
+
own_ip: own_ip)
|
431
|
+
|
432
|
+
dcc.start_server
|
433
|
+
|
434
|
+
handler = Handler.new(@bot, :message,
|
435
|
+
Pattern.new(/^/,
|
436
|
+
/\001DCC RESUME #{filename} #{dcc.port} (\d+)\001/,
|
437
|
+
/$/)) do |m, position|
|
438
|
+
next unless m.user == self
|
439
|
+
dcc.seek(position.to_i)
|
440
|
+
m.user.send "\001DCC ACCEPT #{filename} #{dcc.port} #{position}\001"
|
441
|
+
|
442
|
+
handler.unregister
|
443
|
+
end
|
444
|
+
@bot.handlers.register(handler)
|
445
|
+
|
446
|
+
@bot.loggers.info "DCC: Outgoing DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d - Status: waiting" % [filename, io.size, own_ip, dcc.port]
|
447
|
+
dcc.send_handshake
|
448
|
+
begin
|
449
|
+
dcc.listen
|
450
|
+
@bot.loggers.info "DCC: Outgoing DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d - Status: done" % [filename, io.size, own_ip, dcc.port]
|
451
|
+
rescue Timeout::Error
|
452
|
+
@bot.loggers.info "DCC: Outgoing DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d - Status: failed (timeout)" % [filename, io.size, own_ip, dcc.port]
|
453
|
+
ensure
|
454
|
+
handler.unregister
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
# Updates the user's online state and dispatch the correct event.
|
459
|
+
#
|
460
|
+
# @since 2.0.0
|
461
|
+
# @return [void]
|
462
|
+
# @api private
|
463
|
+
def online=(bool)
|
464
|
+
notify = __send__(:"online?_unsynced") != bool && @monitored
|
465
|
+
sync(:online?, bool, true)
|
466
|
+
|
467
|
+
return unless notify
|
468
|
+
if bool
|
469
|
+
@bot.handlers.dispatch(:online, nil, self)
|
470
|
+
else
|
471
|
+
@bot.handlers.dispatch(:offline, nil, self)
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
# Used to update the user's nick on nickchange events.
|
476
|
+
#
|
477
|
+
# @param [String] new_nick The user's new nick
|
478
|
+
# @api private
|
479
|
+
# @return [void]
|
480
|
+
def update_nick(new_nick)
|
481
|
+
@last_nick, @name = @name, new_nick
|
482
|
+
# Unsync authname because some networks tie authentication to
|
483
|
+
# the nick, so the user might not be authenticated anymore after
|
484
|
+
# changing their nick
|
485
|
+
unsync(:authname)
|
486
|
+
@bot.user_list.update_nick(self)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "cached_list"
|
4
|
+
|
5
|
+
module Cinch
|
6
|
+
# @since 2.0.0
|
7
|
+
# @version 1.1.0
|
8
|
+
# @note In prior versions, this class was called UserManager
|
9
|
+
class UserList < CachedList
|
10
|
+
# Finds or creates a user.
|
11
|
+
# @overload find_ensured(nick)
|
12
|
+
# Finds or creates a user based on their nick.
|
13
|
+
#
|
14
|
+
# @param [String] nick The user's nickname
|
15
|
+
# @return [User]
|
16
|
+
# @overload find_ensured(user, nick, host)
|
17
|
+
# Finds or creates a user based on their nick but already
|
18
|
+
# setting user and host.
|
19
|
+
#
|
20
|
+
# @param [String] user The username
|
21
|
+
# @param [String] nick The nickname
|
22
|
+
# @param [String] host The user's hostname
|
23
|
+
# @return [User]
|
24
|
+
# @return [User]
|
25
|
+
# @see Bot#User
|
26
|
+
def find_ensured(*args)
|
27
|
+
user, host = nil, nil
|
28
|
+
case args.size
|
29
|
+
when 1
|
30
|
+
nick = args.first
|
31
|
+
bargs = [nick]
|
32
|
+
when 3
|
33
|
+
nick = args[1]
|
34
|
+
bargs = args
|
35
|
+
user, _, host = bargs
|
36
|
+
else
|
37
|
+
raise ArgumentError
|
38
|
+
end
|
39
|
+
|
40
|
+
if nick == @bot.nick
|
41
|
+
user_obj = @bot
|
42
|
+
end
|
43
|
+
|
44
|
+
downcased_nick = nick.irc_downcase(@bot.irc.isupport["CASEMAPPING"])
|
45
|
+
@mutex.synchronize do
|
46
|
+
if user_obj.nil?
|
47
|
+
user_obj = @cache[downcased_nick] ||= User.new(*bargs, @bot)
|
48
|
+
end
|
49
|
+
if user && host
|
50
|
+
# Explicitly set user and host whenever we request a User
|
51
|
+
# object to update them on e.g. JOIN.
|
52
|
+
user_obj.sync(:user, user, true)
|
53
|
+
user_obj.sync(:host, host, true)
|
54
|
+
end
|
55
|
+
user_obj
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Finds a user.
|
60
|
+
#
|
61
|
+
# @param [String] nick nick of a user
|
62
|
+
# @return [User, nil]
|
63
|
+
def find(nick)
|
64
|
+
if nick == @bot.nick
|
65
|
+
return @bot
|
66
|
+
end
|
67
|
+
|
68
|
+
downcased_nick = nick.irc_downcase(@bot.irc.isupport["CASEMAPPING"])
|
69
|
+
@mutex.synchronize do
|
70
|
+
return @cache[downcased_nick]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# @api private
|
75
|
+
# @return [void]
|
76
|
+
def update_nick(user)
|
77
|
+
@mutex.synchronize do
|
78
|
+
@cache.delete user.last_nick.irc_downcase(@bot.irc.isupport["CASEMAPPING"])
|
79
|
+
@cache[user.nick.irc_downcase(@bot.irc.isupport["CASEMAPPING"])] = user
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# @api private
|
84
|
+
# @return [void]
|
85
|
+
def delete(user)
|
86
|
+
@cache.delete_if { |n, u| u == user }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cinch
|
4
|
+
module Utilities
|
5
|
+
# @since 2.0.0
|
6
|
+
# @api private
|
7
|
+
module Deprecation
|
8
|
+
def self.print_deprecation(version, method, instead = nil)
|
9
|
+
s = "Deprecation warning: Beginning with version #{version}, #{method} should not be used anymore."
|
10
|
+
if !instead.nil?
|
11
|
+
s << " Use #{instead} instead."
|
12
|
+
end
|
13
|
+
warn s
|
14
|
+
warn caller
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cinch
|
4
|
+
module Utilities
|
5
|
+
# @since 2.0.0
|
6
|
+
# @api private
|
7
|
+
module Encoding
|
8
|
+
def self.encode_incoming(string, encoding)
|
9
|
+
string = string.dup
|
10
|
+
if encoding == :irc
|
11
|
+
# If incoming text is valid UTF-8, it will be interpreted as
|
12
|
+
# such. If it fails validation, a CP1252 -> UTF-8 conversion
|
13
|
+
# is performed. This allows you to see non-ASCII from mIRC
|
14
|
+
# users (non-UTF-8) and other users sending you UTF-8.
|
15
|
+
#
|
16
|
+
# (from http://xchat.org/encoding/#hybrid)
|
17
|
+
string.force_encoding("UTF-8")
|
18
|
+
if !string.valid_encoding?
|
19
|
+
string.force_encoding("CP1252").encode!("UTF-8", invalid: :replace, undef: :replace)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
string.force_encoding(encoding).encode!(invalid: :replace, undef: :replace)
|
23
|
+
string = string.chars.select { |c| c.valid_encoding? }.join
|
24
|
+
end
|
25
|
+
|
26
|
+
string
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.encode_outgoing(string, encoding)
|
30
|
+
string = string.dup
|
31
|
+
if encoding == :irc
|
32
|
+
encoding = "UTF-8"
|
33
|
+
end
|
34
|
+
|
35
|
+
string.encode!(encoding, invalid: :replace, undef: :replace).force_encoding("ASCII-8BIT")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cinch
|
4
|
+
module Utilities
|
5
|
+
# @since 2.0.0
|
6
|
+
# @api private
|
7
|
+
module Kernel
|
8
|
+
# @return [Object]
|
9
|
+
def self.string_to_const(s)
|
10
|
+
return s unless s.is_a?(::String)
|
11
|
+
s.split("::").inject(Kernel) { |base, name| base.const_get(name) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/cinch.rb
ADDED