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