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.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/.standard.yml +3 -0
  3. data/CHANGELOG.md +298 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/Gemfile +5 -0
  6. data/LICENSE.txt +23 -0
  7. data/README.md +195 -0
  8. data/Rakefile +14 -0
  9. data/docs/bot_options.md +454 -0
  10. data/docs/changes.md +541 -0
  11. data/docs/common_mistakes.md +60 -0
  12. data/docs/common_tasks.md +57 -0
  13. data/docs/encodings.md +69 -0
  14. data/docs/events.md +273 -0
  15. data/docs/getting_started.md +184 -0
  16. data/docs/logging.md +90 -0
  17. data/docs/migrating.md +267 -0
  18. data/docs/plugins.md +4 -0
  19. data/docs/readme.md +20 -0
  20. data/examples/basic/autovoice.rb +32 -0
  21. data/examples/basic/google.rb +35 -0
  22. data/examples/basic/hello.rb +14 -0
  23. data/examples/basic/join_part.rb +35 -0
  24. data/examples/basic/memo.rb +39 -0
  25. data/examples/basic/msg.rb +15 -0
  26. data/examples/basic/seen.rb +37 -0
  27. data/examples/basic/urban_dict.rb +36 -0
  28. data/examples/basic/url_shorten.rb +36 -0
  29. data/examples/plugins/autovoice.rb +37 -0
  30. data/examples/plugins/custom_prefix.rb +22 -0
  31. data/examples/plugins/dice_roll.rb +38 -0
  32. data/examples/plugins/google.rb +36 -0
  33. data/examples/plugins/hello.rb +21 -0
  34. data/examples/plugins/hooks.rb +34 -0
  35. data/examples/plugins/join_part.rb +41 -0
  36. data/examples/plugins/lambdas.rb +35 -0
  37. data/examples/plugins/last_nick.rb +24 -0
  38. data/examples/plugins/msg.rb +21 -0
  39. data/examples/plugins/multiple_matches.rb +32 -0
  40. data/examples/plugins/own_events.rb +37 -0
  41. data/examples/plugins/seen.rb +44 -0
  42. data/examples/plugins/timer.rb +22 -0
  43. data/examples/plugins/url_shorten.rb +34 -0
  44. data/ircinch.gemspec +43 -0
  45. data/lib/cinch/ban.rb +53 -0
  46. data/lib/cinch/bot.rb +476 -0
  47. data/lib/cinch/cached_list.rb +21 -0
  48. data/lib/cinch/callback.rb +22 -0
  49. data/lib/cinch/channel.rb +465 -0
  50. data/lib/cinch/channel_list.rb +31 -0
  51. data/lib/cinch/configuration/bot.rb +50 -0
  52. data/lib/cinch/configuration/dcc.rb +18 -0
  53. data/lib/cinch/configuration/plugins.rb +43 -0
  54. data/lib/cinch/configuration/sasl.rb +21 -0
  55. data/lib/cinch/configuration/ssl.rb +21 -0
  56. data/lib/cinch/configuration/timeouts.rb +16 -0
  57. data/lib/cinch/configuration.rb +75 -0
  58. data/lib/cinch/constants.rb +535 -0
  59. data/lib/cinch/dcc/dccable_object.rb +39 -0
  60. data/lib/cinch/dcc/incoming/send.rb +149 -0
  61. data/lib/cinch/dcc/incoming.rb +3 -0
  62. data/lib/cinch/dcc/outgoing/send.rb +123 -0
  63. data/lib/cinch/dcc/outgoing.rb +3 -0
  64. data/lib/cinch/dcc.rb +14 -0
  65. data/lib/cinch/exceptions.rb +48 -0
  66. data/lib/cinch/formatting.rb +127 -0
  67. data/lib/cinch/handler.rb +120 -0
  68. data/lib/cinch/handler_list.rb +92 -0
  69. data/lib/cinch/helpers.rb +230 -0
  70. data/lib/cinch/i_support.rb +100 -0
  71. data/lib/cinch/irc.rb +924 -0
  72. data/lib/cinch/log_filter.rb +23 -0
  73. data/lib/cinch/logger/formatted_logger.rb +100 -0
  74. data/lib/cinch/logger/zcbot_logger.rb +26 -0
  75. data/lib/cinch/logger.rb +171 -0
  76. data/lib/cinch/logger_list.rb +88 -0
  77. data/lib/cinch/mask.rb +69 -0
  78. data/lib/cinch/message.rb +397 -0
  79. data/lib/cinch/message_queue.rb +104 -0
  80. data/lib/cinch/mode_parser.rb +78 -0
  81. data/lib/cinch/network.rb +106 -0
  82. data/lib/cinch/open_ended_queue.rb +26 -0
  83. data/lib/cinch/pattern.rb +66 -0
  84. data/lib/cinch/plugin.rb +517 -0
  85. data/lib/cinch/plugin_list.rb +40 -0
  86. data/lib/cinch/rubyext/float.rb +5 -0
  87. data/lib/cinch/rubyext/module.rb +28 -0
  88. data/lib/cinch/rubyext/string.rb +35 -0
  89. data/lib/cinch/sasl/dh_blowfish.rb +73 -0
  90. data/lib/cinch/sasl/diffie_hellman.rb +50 -0
  91. data/lib/cinch/sasl/mechanism.rb +8 -0
  92. data/lib/cinch/sasl/plain.rb +29 -0
  93. data/lib/cinch/sasl.rb +36 -0
  94. data/lib/cinch/syncable.rb +83 -0
  95. data/lib/cinch/target.rb +199 -0
  96. data/lib/cinch/timer.rb +147 -0
  97. data/lib/cinch/user.rb +489 -0
  98. data/lib/cinch/user_list.rb +89 -0
  99. data/lib/cinch/utilities/deprecation.rb +18 -0
  100. data/lib/cinch/utilities/encoding.rb +39 -0
  101. data/lib/cinch/utilities/kernel.rb +15 -0
  102. data/lib/cinch/version.rb +6 -0
  103. data/lib/cinch.rb +7 -0
  104. data/lib/ircinch.rb +7 -0
  105. 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 -&gt; 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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cinch
4
+ # Version of the library
5
+ VERSION = "2.4.0"
6
+ end
data/lib/cinch.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cinch/version"
4
+ require "cinch/utilities/kernel"
5
+ require "cinch/utilities/deprecation"
6
+ require "cinch/utilities/encoding"
7
+ require "cinch/bot"
data/lib/ircinch.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cinch"
4
+
5
+ class Ircinch
6
+ class Error < StandardError; end
7
+ end