grinch 1.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 +180 -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/cinch.rb +5 -0
  41. data/lib/cinch/ban.rb +50 -0
  42. data/lib/cinch/bot.rb +479 -0
  43. data/lib/cinch/cached_list.rb +19 -0
  44. data/lib/cinch/callback.rb +20 -0
  45. data/lib/cinch/channel.rb +463 -0
  46. data/lib/cinch/channel_list.rb +29 -0
  47. data/lib/cinch/configuration.rb +73 -0
  48. data/lib/cinch/configuration/bot.rb +48 -0
  49. data/lib/cinch/configuration/dcc.rb +16 -0
  50. data/lib/cinch/configuration/plugins.rb +41 -0
  51. data/lib/cinch/configuration/sasl.rb +19 -0
  52. data/lib/cinch/configuration/ssl.rb +19 -0
  53. data/lib/cinch/configuration/timeouts.rb +14 -0
  54. data/lib/cinch/constants.rb +533 -0
  55. data/lib/cinch/dcc.rb +12 -0
  56. data/lib/cinch/dcc/dccable_object.rb +37 -0
  57. data/lib/cinch/dcc/incoming.rb +1 -0
  58. data/lib/cinch/dcc/incoming/send.rb +147 -0
  59. data/lib/cinch/dcc/outgoing.rb +1 -0
  60. data/lib/cinch/dcc/outgoing/send.rb +122 -0
  61. data/lib/cinch/exceptions.rb +46 -0
  62. data/lib/cinch/formatting.rb +125 -0
  63. data/lib/cinch/handler.rb +118 -0
  64. data/lib/cinch/handler_list.rb +90 -0
  65. data/lib/cinch/helpers.rb +231 -0
  66. data/lib/cinch/irc.rb +924 -0
  67. data/lib/cinch/isupport.rb +98 -0
  68. data/lib/cinch/log_filter.rb +21 -0
  69. data/lib/cinch/logger.rb +168 -0
  70. data/lib/cinch/logger/formatted_logger.rb +97 -0
  71. data/lib/cinch/logger/zcbot_logger.rb +22 -0
  72. data/lib/cinch/logger_list.rb +85 -0
  73. data/lib/cinch/mask.rb +69 -0
  74. data/lib/cinch/message.rb +392 -0
  75. data/lib/cinch/message_queue.rb +107 -0
  76. data/lib/cinch/mode_parser.rb +76 -0
  77. data/lib/cinch/network.rb +104 -0
  78. data/lib/cinch/open_ended_queue.rb +26 -0
  79. data/lib/cinch/pattern.rb +65 -0
  80. data/lib/cinch/plugin.rb +515 -0
  81. data/lib/cinch/plugin_list.rb +38 -0
  82. data/lib/cinch/rubyext/float.rb +3 -0
  83. data/lib/cinch/rubyext/module.rb +26 -0
  84. data/lib/cinch/rubyext/string.rb +33 -0
  85. data/lib/cinch/sasl.rb +34 -0
  86. data/lib/cinch/sasl/dh_blowfish.rb +71 -0
  87. data/lib/cinch/sasl/diffie_hellman.rb +47 -0
  88. data/lib/cinch/sasl/mechanism.rb +6 -0
  89. data/lib/cinch/sasl/plain.rb +26 -0
  90. data/lib/cinch/syncable.rb +83 -0
  91. data/lib/cinch/target.rb +199 -0
  92. data/lib/cinch/timer.rb +145 -0
  93. data/lib/cinch/user.rb +488 -0
  94. data/lib/cinch/user_list.rb +87 -0
  95. data/lib/cinch/utilities/deprecation.rb +16 -0
  96. data/lib/cinch/utilities/encoding.rb +37 -0
  97. data/lib/cinch/utilities/kernel.rb +13 -0
  98. data/lib/cinch/version.rb +4 -0
  99. metadata +140 -0
@@ -0,0 +1,145 @@
1
+ require "cinch/helpers"
2
+
3
+ module Cinch
4
+ # Timers are used for executing code in the future, either
5
+ # repeatedly or only once.
6
+ #
7
+ # In Cinch, two ways for creating timers are available:
8
+ #
9
+ # - The first way is by declaring them for a plugin, in which case
10
+ # they will start as soon as the bot connects to a server.
11
+ #
12
+ # - The second way is to dynamically create new timers in response
13
+ # to user input. A common example for this is an alarm clock
14
+ # plugin, which has to execute at a specific time.
15
+ #
16
+ # @see Helpers#Timer For dynamically creating timers
17
+ # @see Plugin::ClassMethods#timer For declaring timers in plugins
18
+ # @note It is possible to directly create instances of this class,
19
+ # but the referenced methods should suffice.
20
+ # @since 2.0.0
21
+ class Timer
22
+ include Helpers
23
+
24
+ # @return [Bot]
25
+ attr_reader :bot
26
+
27
+ # @return [Numeric] The interval (in seconds) of the timer
28
+ attr_accessor :interval
29
+
30
+ # @return [Boolean] If true, each invocation will be
31
+ # executed in a thread of its own.
32
+ attr_accessor :threaded
33
+
34
+ # @return [Proc]
35
+ attr_reader :block
36
+
37
+ # @return [Boolean]
38
+ attr_reader :started
39
+
40
+ # @return [Integer] The remaining number of shots before this timer
41
+ # will stop. This value will automatically reset after
42
+ # restarting the timer.
43
+ attr_accessor :shots
44
+ alias_method :threaded?, :threaded
45
+ alias_method :started?, :started
46
+
47
+ # @return [ThreadGroup]
48
+ # @api private
49
+ attr_reader :thread_group
50
+
51
+ # @param [Bot] bot The instance of {Bot} the timer is associated
52
+ # with
53
+ # @option options [Numeric] :interval The interval (in seconds) of
54
+ # the timer
55
+ # @option options [Integer] :shots (Float::INFINITY) How often should the
56
+ # timer fire?
57
+ # @option options [Boolean] :threaded (true) If true, each invocation will be
58
+ # executed in a thread of its own.
59
+ # @option options [Boolean] :start_automatically (true) If true,
60
+ # the timer will automatically start after the bot finished
61
+ # connecting.
62
+ # @option options [Boolean] :stop_automaticall (true) If true, the
63
+ # timer will automatically stop when the bot disconnects.
64
+ def initialize(bot, options, &block)
65
+ options = {:threaded => true, :shots => Float::INFINITY, :start_automatically => true, :stop_automatically => true}.merge(options)
66
+
67
+ @bot = bot
68
+ @interval = options[:interval].to_f
69
+ @threaded = options[:threaded]
70
+ @orig_shots = options[:shots]
71
+ # Setting @shots here so the attr_reader won't return nil
72
+ @shots = @orig_shots
73
+ @block = block
74
+
75
+ @started = false
76
+ @thread_group = ThreadGroup.new
77
+
78
+ if options[:start_automatically]
79
+ @bot.on :connect, //, self do |m, timer|
80
+ timer.start
81
+ end
82
+ end
83
+
84
+ if options[:stop_automatically]
85
+ @bot.on :disconnect, //, self do |m, timer|
86
+ timer.stop
87
+ end
88
+ end
89
+ end
90
+
91
+ # @return [Boolean]
92
+ def stopped?
93
+ !@started
94
+ end
95
+
96
+ # Start the timer
97
+ #
98
+ # @return [void]
99
+ def start
100
+ return if @started
101
+
102
+ @bot.loggers.debug "[timer] Starting timer #{self}"
103
+
104
+ @shots = @orig_shots
105
+
106
+ @thread_group.add Thread.new {
107
+ while @shots > 0 do
108
+ sleep @interval
109
+ if threaded?
110
+ Thread.new do
111
+ rescue_exception do
112
+ @block.call
113
+ end
114
+ end
115
+ else
116
+ rescue_exception do
117
+ @block.call
118
+ end
119
+ end
120
+
121
+ @shots -= 1
122
+ end
123
+ }
124
+
125
+ @started = true
126
+ end
127
+
128
+ # Stop the timer
129
+ #
130
+ # @return [void]
131
+ def stop
132
+ return unless @started
133
+
134
+ @bot.loggers.debug "[timer] Stopping timer #{self}"
135
+
136
+ @thread_group.list.each { |thread| thread.kill }
137
+ @started = false
138
+ end
139
+
140
+ # @return [String]
141
+ def to_s
142
+ "<Cinch::Timer %s/%s shots, %ds interval, %sthreaded, %sstarted, block: %s>" % [@orig_shots - @shots, @orig_shots, @interval, @threaded ? "" : "not ", @started ? "" : "not ", @block]
143
+ end
144
+ end
145
+ end
data/lib/cinch/user.rb ADDED
@@ -0,0 +1,488 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "cinch/target"
3
+ require "timeout"
4
+
5
+ module Cinch
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
+ Cinch::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